diff --git a/.eslintrc.js b/.eslintrc.js index 67e6ab1e642..f3e38f94c3c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -86,6 +86,8 @@ module.exports = { "jsx-a11y/no-static-element-interactions": "off", "jsx-a11y/role-supports-aria-props": "off", "jsx-a11y/tabindex-no-positive": "off", + + "matrix-org/require-copyright-header": "error", }, overrides: [ { diff --git a/package.json b/package.json index a1dfd9d340a..808ede86b9d 100644 --- a/package.json +++ b/package.json @@ -173,7 +173,7 @@ "eslint-config-google": "^0.14.0", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-matrix-org": "^0.4.0", + "eslint-plugin-matrix-org": "^0.5.2", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", "fs-extra": "^10.0.1", diff --git a/res/css/_components.scss b/res/css/_components.scss index c77b5ea7069..6988c017895 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -126,7 +126,6 @@ @import "./views/dialogs/_SpacePreferencesDialog.scss"; @import "./views/dialogs/_SpaceSettingsDialog.scss"; @import "./views/dialogs/_SpotlightDialog.scss"; -@import "./views/dialogs/_TabbedIntegrationManagerDialog.scss"; @import "./views/dialogs/_TermsDialog.scss"; @import "./views/dialogs/_UntrustedDeviceDialog.scss"; @import "./views/dialogs/_UploadConfirmDialog.scss"; @@ -163,6 +162,7 @@ @import "./views/elements/_InviteReason.scss"; @import "./views/elements/_ManageIntegsButton.scss"; @import "./views/elements/_MiniAvatarUploader.scss"; +@import "./views/elements/_Pill.scss"; @import "./views/elements/_PowerSelector.scss"; @import "./views/elements/_ProgressBar.scss"; @import "./views/elements/_QRCode.scss"; diff --git a/res/css/structures/_RoomDirectory.scss b/res/css/structures/_RoomDirectory.scss index 0137db7ebf2..2b8def01d03 100644 --- a/res/css/structures/_RoomDirectory.scss +++ b/res/css/structures/_RoomDirectory.scss @@ -155,7 +155,7 @@ limitations under the License. line-height: $font-20px; padding: 0 5px; color: $accent-fg-color; - background-color: $rte-room-pill-color; + background-color: $pill-bg-color; } .mx_RoomDirectory_topic { diff --git a/res/css/views/dialogs/_TabbedIntegrationManagerDialog.scss b/res/css/views/dialogs/_TabbedIntegrationManagerDialog.scss deleted file mode 100644 index 6385dd76f52..00000000000 --- a/res/css/views/dialogs/_TabbedIntegrationManagerDialog.scss +++ /dev/null @@ -1,62 +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. -*/ - -.mx_TabbedIntegrationManagerDialog .mx_Dialog { - width: 60%; - height: 70%; - overflow: hidden; - padding: 0; - max-width: initial; - max-height: initial; - position: relative; -} - -.mx_TabbedIntegrationManagerDialog_container { - // Full size of the dialog, whatever it is - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - - .mx_TabbedIntegrationManagerDialog_currentManager { - width: 100%; - height: 100%; - border-top: 1px solid $accent; - - iframe { - background-color: #fff; - border: 0; - width: 100%; - height: 100%; - } - } -} - -.mx_TabbedIntegrationManagerDialog_tab { - display: inline-block; - border: 1px solid $accent; - border-bottom: 0; - border-top-left-radius: 3px; - border-top-right-radius: 3px; - padding: 10px 8px; - margin-right: 5px; -} - -.mx_TabbedIntegrationManagerDialog_currentTab { - background-color: $accent; - color: $accent-fg-color; -} diff --git a/res/css/views/elements/_Pill.scss b/res/css/views/elements/_Pill.scss new file mode 100644 index 00000000000..15511601ffe --- /dev/null +++ b/res/css/views/elements/_Pill.scss @@ -0,0 +1,63 @@ +/* +Copyright 2021 Šimon Brandner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_Pill { + padding: $font-1px 0.4em $font-1px 0; + line-height: $font-17px; + border-radius: $font-16px; + vertical-align: text-top; + display: inline-flex; + align-items: center; + + cursor: pointer; + + color: $accent-fg-color !important; // To override .markdown-body + background-color: $pill-bg-color !important; // To override .markdown-body + + &.mx_UserPill_me, + &.mx_AtRoomPill { + background-color: $alert !important; // To override .markdown-body + } + + &:hover { + background-color: $pill-hover-bg-color !important; // To override .markdown-body + } + + &.mx_UserPill_me:hover { + background-color: #ff6b75 !important; // To override .markdown-body | same on both themes + } + + // We don't want to indicate clickability + &.mx_AtRoomPill:hover { + background-color: $alert !important; // To override .markdown-body + cursor: unset; + } + + .mx_BaseAvatar { + position: relative; + display: inline-flex; + align-items: center; + border-radius: 10rem; + margin-right: 0.24rem; + } + + a& { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + text-decoration: none !important; // To override .markdown-body + } +} diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index 4615f5da23f..4f0b532c6b4 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -2,86 +2,6 @@ // naming scheme; it's completely unclear where or how they're being used // --Matthew -.mx_UserPill, -.mx_RoomPill, -.mx_AtRoomPill { - display: inline-flex; - align-items: center; - vertical-align: middle; - border-radius: $font-16px; - line-height: $font-15px; - padding-left: 0; -} - -a.mx_Pill { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - max-width: 100%; -} - -.mx_Pill { - padding: $font-1px; - padding-right: 0.4em; - vertical-align: text-top; - line-height: $font-17px; -} - -/* More specific to override `.markdown-body a` text-decoration */ -.mx_EventTile_content .markdown-body a.mx_Pill { - text-decoration: none; -} - -/* More specific to override `.markdown-body a` color */ -.mx_EventTile_content .markdown-body a.mx_UserPill, -.mx_UserPill { - color: $primary-content; - background-color: $other-user-pill-bg-color; -} - -.mx_UserPill_selected { - background-color: $accent !important; -} - -/* More specific to override `.markdown-body a` color */ -.mx_EventTile_highlight .mx_EventTile_content .markdown-body a.mx_UserPill_me, -.mx_EventTile_content .markdown-body a.mx_AtRoomPill, -.mx_EventTile_content .mx_AtRoomPill, -.mx_MessageComposer_input .mx_AtRoomPill { - color: $accent-fg-color; - background-color: $alert; -} - -/* More specific to override `.markdown-body a` color */ -.mx_EventTile_content .markdown-body a.mx_RoomPill, -.mx_RoomPill { - color: $accent-fg-color; - background-color: $rte-room-pill-color; -} - -.mx_EventTile_body .mx_UserPill, -.mx_EventTile_body .mx_RoomPill { - cursor: pointer; -} - -.mx_UserPill .mx_BaseAvatar, -.mx_RoomPill .mx_BaseAvatar, -.mx_AtRoomPill .mx_BaseAvatar { - position: relative; - display: inline-flex; - align-items: center; - border-radius: 10rem; - margin-right: 0.24rem; - pointer-events: none; -} - -.mx_Emoji { - // Should be 1.8rem for our default 1.4rem message bodies, - // and scale with the size of the surrounding text - font-size: calc(18 / 14 * 1em); - vertical-align: bottom; -} - .mx_Markdown_BOLD { font-weight: bold; } @@ -115,3 +35,10 @@ a.mx_Pill { .mx_Markdown_STRIKETHROUGH { text-decoration: line-through; } + +.mx_Emoji { + // Should be 1.8rem for our default message bodies, and scale with the + // surrounding text + font-size: max($font-18px, 1em); + vertical-align: bottom; +} diff --git a/res/css/views/messages/_MLocationBody.scss b/res/css/views/messages/_MLocationBody.scss index 72202ca6e1b..adca4ae4ba6 100644 --- a/res/css/views/messages/_MLocationBody.scss +++ b/res/css/views/messages/_MLocationBody.scss @@ -15,7 +15,10 @@ limitations under the License. */ .mx_MLocationBody { + max-width: 100%; + .mx_MLocationBody_map { + max-width: 100%; width: 450px; height: 300px; z-index: 0; // keeps the entire map under the message action bar @@ -27,15 +30,11 @@ limitations under the License. /* In the timeline, we fit the width of the container */ .mx_EventTile_line .mx_MLocationBody .mx_MLocationBody_map { - width: 100%; max-width: 450px; + width: 100%; } -.mx_EventTile[data-layout="bubble"] .mx_EventTile_line .mx_MLocationBody { +.mx_EventTile[data-layout="bubble"] .mx_EventTile_line .mx_MLocationBody .mx_MLocationBody_map { max-width: 100%; - - .mx_MLocationBody_map { - max-width: 100%; - width: 450px; - } + width: 450px; } diff --git a/res/css/views/rooms/_BasicMessageComposer.scss b/res/css/views/rooms/_BasicMessageComposer.scss index 73ff9f048b2..c89fa6028b6 100644 --- a/res/css/views/rooms/_BasicMessageComposer.scss +++ b/res/css/views/rooms/_BasicMessageComposer.scss @@ -51,9 +51,15 @@ limitations under the License. } &.mx_BasicMessageComposer_input_shouldShowPillAvatar { - span.mx_UserPill, span.mx_RoomPill { - position: relative; + span.mx_UserPill, span.mx_RoomPill, span.mx_SpacePill { user-select: all; + position: relative; + cursor: unset; // We don't want indicate clickability + + &:hover { + // We don't want indicate clickability | To override the overriding of .markdown-body + background-color: $pill-bg-color !important; + } // avatar psuedo element &::before { @@ -72,14 +78,6 @@ limitations under the License. font-size: $font-10-4px; } } - - span.mx_UserPill { - cursor: pointer; - } - - span.mx_RoomPill { - cursor: default; - } } &.mx_BasicMessageComposer_input_disabled { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index d802c4f9fb6..6f2fb704983 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -704,7 +704,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss .mx_MessagePanel_narrow .mx_ThreadSummary { min-width: initial; - max-width: initial; + max-width: 100%; // prevent overflow width: initial; } diff --git a/res/css/views/rooms/_ReplyPreview.scss b/res/css/views/rooms/_ReplyPreview.scss index eb0233108b8..598371bd368 100644 --- a/res/css/views/rooms/_ReplyPreview.scss +++ b/res/css/views/rooms/_ReplyPreview.scss @@ -25,30 +25,31 @@ limitations under the License. .mx_ReplyPreview_section { border-bottom: 1px solid $primary-hairline-color; + display: flex; + flex-flow: column; + row-gap: $spacing-8; + padding: $spacing-8 $spacing-8 0 $spacing-8; .mx_ReplyPreview_header { - margin: 8px; + display: flex; + justify-content: space-between; + column-gap: 8px; + color: $primary-content; font-weight: 400; opacity: 0.4; - } - - .mx_ReplyPreview_tile { - margin: 0 8px; - } - - .mx_ReplyPreview_title { - float: left; - } - - .mx_ReplyPreview_cancel { - float: right; - cursor: pointer; - display: flex; - } - .mx_ReplyPreview_clear { - clear: both; + .mx_ReplyPreview_header_cancel { + background-color: $primary-content; + mask: url('$(res)/img/cancel.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: 18px; + width: 18px; + height: 18px; + min-width: 18px; + min-height: 18px; + } } } } diff --git a/res/css/views/voip/_CallViewHeader.scss b/res/css/views/voip/_CallViewHeader.scss index 9340dfb0401..6280da8cbb7 100644 --- a/res/css/views/voip/_CallViewHeader.scss +++ b/res/css/views/voip/_CallViewHeader.scss @@ -45,6 +45,8 @@ limitations under the License. .mx_CallViewHeader_controls { margin-left: auto; + display: flex; + gap: 5px; } .mx_CallViewHeader_button { @@ -63,17 +65,23 @@ limitations under the License. mask-size: contain; mask-position: center; } -} -.mx_CallViewHeader_button_fullscreen { - &::before { - mask-image: url('$(res)/img/element-icons/call/fullscreen.svg'); + &.mx_CallViewHeader_button_fullscreen { + &::before { + mask-image: url('$(res)/img/element-icons/call/fullscreen.svg'); + } } -} -.mx_CallViewHeader_button_expand { - &::before { - mask-image: url('$(res)/img/element-icons/call/expand.svg'); + &.mx_CallViewHeader_button_pin { + &::before { + mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + } + } + + &.mx_CallViewHeader_button_expand { + &::before { + mask-image: url('$(res)/img/element-icons/call/expand.svg'); + } } } diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index ce187345942..4c5d50f9e1f 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -94,8 +94,8 @@ $roomheader-addroom-fg-color: $primary-content; // Rich-text-editor // ******************** -$rte-room-pill-color: $room-highlight-color; -$other-user-pill-bg-color: $room-highlight-color; +$pill-bg-color: $room-highlight-color; +$pill-hover-bg-color: #545a66; // ******************** // Inputs diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 9ee07c09685..8997538e0ab 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -27,8 +27,8 @@ $light-fg-color: $header-panel-text-secondary-color; // used for focusing form controls $focus-bg-color: $room-highlight-color; -$other-user-pill-bg-color: $room-highlight-color; -$rte-room-pill-color: $room-highlight-color; +$pill-bg-color: $room-highlight-color; +$pill-hover-bg-color: #545a66; // informational plinth $info-plinth-bg-color: $header-panel-bg-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index e1c475fc636..49f690d6a04 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -37,8 +37,6 @@ $selection-fg-color: $primary-bg-color; $focus-brightness: 105%; -$other-user-pill-bg-color: rgba(0, 0, 0, 0.1); - // informational plinth $info-plinth-bg-color: #f7f7f7; $info-plinth-fg-color: #888; @@ -117,10 +115,12 @@ $settings-subsection-fg-color: #61708b; $rte-bg-color: #e9e9e9; $rte-code-bg-color: rgba(0, 0, 0, 0.04); -$rte-room-pill-color: #aaa; $header-panel-text-primary-color: #91a1c0; +$pill-bg-color: #aaa; +$pill-hover-bg-color: #ccc; + $topleftmenu-color: #212121; $roomheader-bg-color: $primary-bg-color; $roomheader-addroom-bg-color: #91a1c0; diff --git a/res/themes/light-custom/css/_custom.scss b/res/themes/light-custom/css/_custom.scss index e2d4fef8fe1..54d3dfd15e7 100644 --- a/res/themes/light-custom/css/_custom.scss +++ b/res/themes/light-custom/css/_custom.scss @@ -142,5 +142,6 @@ $eventbubble-reply-color: var(--eventbubble-reply-color, $eventbubble-reply-colo $reaction-row-button-selected-bg-color: var(--reaction-row-button-selected-bg-color, $reaction-row-button-selected-bg-color); $menu-selected-color: var(--menu-selected-color, $menu-selected-color); -$other-user-pill-bg-color: var(--other-user-pill-bg-color, $other-user-pill-bg-color); +$pill-bg-color: var(--other-user-pill-bg-color, $pill-bg-color); +$pill-hover-bg-color: var(--other-user-pill-bg-color, $pill-hover-bg-color); $icon-button-color: var(--icon-button-color, $icon-button-color); diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 3d09e400819..bd7194e5fb4 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -147,8 +147,8 @@ $roomheader-addroom-fg-color: #5c6470; // Rich-text-editor // ******************** -$rte-room-pill-color: #aaa; -$other-user-pill-bg-color: rgba(0, 0, 0, 0.1); +$pill-bg-color: #aaa; +$pill-hover-bg-color: #ccc; $rte-bg-color: #e9e9e9; $rte-code-bg-color: rgba(0, 0, 0, 0.04); // ******************** diff --git a/src/NodeAnimator.tsx b/src/NodeAnimator.tsx index 1a8942f5f53..2bb79542404 100644 --- a/src/NodeAnimator.tsx +++ b/src/NodeAnimator.tsx @@ -1,3 +1,19 @@ +/* +Copyright 2022 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 ReactDom from "react-dom"; diff --git a/src/Notifier.ts b/src/Notifier.ts index 62e2f093703..892d5bc19cc 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -408,10 +408,6 @@ export const Notifier = { // don't bother notifying as user was recently active in this room return; } - if (SettingsStore.getValue("doNotDisturb")) { - // Don't bother the user if they didn't ask to be bothered - return; - } if (this.isEnabled()) { this._displayPopupNotification(ev, room); diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 328af853142..b7e89b08a40 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -129,7 +129,6 @@ import { DoAfterSyncPreparedPayload } from '../../dispatcher/payloads/DoAfterSyn import { ViewStartChatOrReusePayload } from '../../dispatcher/payloads/ViewStartChatOrReusePayload'; import { IConfigOptions } from "../../IConfigOptions"; import { SnakedObject } from "../../utils/SnakedObject"; -import InfoDialog from '../views/dialogs/InfoDialog'; import { leaveRoomBehaviour } from "../../utils/leave-behaviour"; import VideoChannelStore from "../../stores/VideoChannelStore"; @@ -1412,36 +1411,6 @@ export default class MatrixChat extends React.PureComponent { showNotificationsToast(false); } - if (!localStorage.getItem("mx_seen_feature_thread_experimental")) { - setTimeout(() => { - if (SettingsStore.getValue("feature_thread") && SdkConfig.get("show_labs_settings")) { - Modal.createDialog(InfoDialog, { - title: _t("Threads Approaching Beta 🎉"), - description: <> -

- { _t("We're getting closer to releasing a public Beta for Threads.") } -

-

- { _t("As we prepare for it, we need to make some changes: threads created " - + "before this point will be displayed as regular replies.", - {}, { - "strong": sub => { sub }, - }) } -

-

- { _t("This will be a one-off transition, as threads are now part " - + "of the Matrix specification.") } -

- , - button: _t("Got it"), - onFinished: () => { - localStorage.setItem("mx_seen_feature_thread_experimental", "true"); - }, - }); - } - }, 1 * 60 * 1000); // show after 1 minute to not overload user on launch - } - if (!localStorage.getItem("mx_seen_feature_spotlight_toast")) { setTimeout(() => { // Skip the toast if the beta is already enabled or the user has changed the setting from default diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 5dd5ea01936..1a403ba5065 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -23,6 +23,7 @@ import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { Relations } from "matrix-js-sdk/src/models/relations"; import { logger } from 'matrix-js-sdk/src/logger'; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; +import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts"; import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon'; import shouldHideEvent from '../../shouldHideEvent'; @@ -847,7 +848,7 @@ export default class MessagePanel extends React.Component { } const receipts: IReadReceiptProps[] = []; room.getReceiptsForEvent(event).forEach((r) => { - if (!r.userId || r.type !== "m.read" || r.userId === myUserId) { + if (!r.userId || ![ReceiptType.Read, ReceiptType.ReadPrivate].includes(r.type) || r.userId === myUserId) { return; // ignore non-read receipts and receipts from self. } if (MatrixClientPeg.get().isUserIgnored(r.userId)) { diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index f2595471c96..5db5e6daad9 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -17,14 +17,13 @@ limitations under the License. import React, { createRef, CSSProperties, ReactNode, KeyboardEvent } from "react"; import { logger } from "matrix-js-sdk/src/logger"; +import SettingsStore from '../../settings/SettingsStore'; import Timer from '../../utils/Timer'; import AutoHideScrollbar from "./AutoHideScrollbar"; import { getKeyBindingsManager } from "../../KeyBindingsManager"; import ResizeNotifier from "../../utils/ResizeNotifier"; import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; -const DEBUG_SCROLL = false; - // The amount of extra scroll distance to allow prior to unfilling. // See getExcessHeight. const UNPAGINATION_PADDING = 6000; @@ -36,13 +35,11 @@ const UNFILL_REQUEST_DEBOUNCE_MS = 200; // 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 = logger.log.bind(console, "ScrollPanel debuglog:"); -} else { - debuglog = function() {}; -} +const debuglog = (...args: any[]) => { + if (SettingsStore.getValue("debug_scroll_panel")) { + logger.log.call(console, "ScrollPanel debuglog:", ...args); + } +}; interface IProps { /* stickyBottom: if set to true, then once the user hits the bottom of diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 59b87c639ac..cdb3e8a4a7f 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -28,6 +28,7 @@ import { debounce } from 'lodash'; import { logger } from "matrix-js-sdk/src/logger"; import { ClientEvent } from "matrix-js-sdk/src/client"; import { Thread } from 'matrix-js-sdk/src/models/thread'; +import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts"; import SettingsStore from "../../settings/SettingsStore"; import { Layout } from "../../settings/enums/Layout"; @@ -61,13 +62,11 @@ const READ_RECEIPT_INTERVAL_MS = 500; const READ_MARKER_DEBOUNCE_MS = 100; -const DEBUG = false; - -let debuglog = function(...s: any[]) {}; -if (DEBUG) { - // using bind means that we get to keep useful line numbers in the console - debuglog = logger.log.bind(console, "TimelinePanel debuglog:"); -} +const debuglog = (...args: any[]) => { + if (SettingsStore.getValue("debug_timeline_panel")) { + logger.log.call(console, "TimelinePanel debuglog:", ...args); + } +}; interface IProps { // The js-sdk EventTimelineSet object for the timeline sequence we are @@ -864,14 +863,14 @@ class TimelinePanel extends React.Component { MatrixClientPeg.get().setRoomReadMarkers( roomId, this.state.readMarkerEventId, - lastReadEvent, // Could be null, in which case no RR is sent - { hidden: hiddenRR }, + hiddenRR ? null : lastReadEvent, // Could be null, in which case no RR is sent + lastReadEvent, // Could be null, in which case no private RR is sent ).catch((e) => { // /read_markers API is not implemented on this HS, fallback to just RR if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) { return MatrixClientPeg.get().sendReadReceipt( lastReadEvent, - {}, + hiddenRR ? ReceiptType.ReadPrivate : ReceiptType.Read, ).catch((e) => { logger.error(e); this.lastRRSentEventId = undefined; diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 78f1e29d003..a9f072b21fa 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -14,9 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, useContext, useRef, useState } from "react"; +import React, { createRef } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; -import classNames from "classnames"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import defaultDispatcher from "../../dispatcher/dispatcher"; @@ -32,9 +31,7 @@ import LogoutDialog from "../views/dialogs/LogoutDialog"; import SettingsStore from "../../settings/SettingsStore"; import { findHighContrastTheme, getCustomTheme, isHighContrastTheme } from "../../theme"; import { - RovingAccessibleButton, RovingAccessibleTooltipButton, - useRovingTabIndex, } from "../../accessibility/RovingTabIndex"; import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton"; import SdkConfig from "../../SdkConfig"; @@ -44,7 +41,6 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore"; import BaseAvatar from '../views/avatars/BaseAvatar'; import { SettingLevel } from "../../settings/SettingLevel"; import IconizedContextMenu, { - IconizedContextMenuCheckbox, IconizedContextMenuOption, IconizedContextMenuOptionList, } from "../views/context_menus/IconizedContextMenu"; @@ -52,65 +48,10 @@ import { UIFeature } from "../../settings/UIFeature"; import HostSignupAction from "./HostSignupAction"; import SpaceStore from "../../stores/spaces/SpaceStore"; import { UPDATE_SELECTED_SPACE } from "../../stores/spaces"; -import MatrixClientContext from "../../contexts/MatrixClientContext"; -import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload"; import UserIdentifierCustomisations from "../../customisations/UserIdentifier"; import PosthogTrackers from "../../PosthogTrackers"; import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload"; -const CustomStatusSection = () => { - const cli = useContext(MatrixClientContext); - const setStatus = cli.getUser(cli.getUserId()).unstable_statusMessage || ""; - const [value, setValue] = useState(setStatus); - - const ref = useRef(null); - const [onFocus, isActive] = useRovingTabIndex(ref); - - const classes = classNames({ - 'mx_UserMenu_CustomStatusSection_field': true, - 'mx_UserMenu_CustomStatusSection_field_hasQuery': value, - }); - - let details: JSX.Element; - if (value !== setStatus) { - details = <> -

{ _t("Your status will be shown to people you have a DM with.") }

- - cli._unstable_setStatusMessage(value)} - kind="primary_outline" - > - { value ? _t("Set status") : _t("Clear status") } - - ; - } - - return
-
- setValue(e.target.value)} - placeholder={_t("Set a new status")} - autoComplete="off" - onFocus={onFocus} - ref={ref} - tabIndex={isActive ? 0 : -1} - /> - setValue("")} - /> -
- - { details } -
; -}; - interface IProps { isPanelCollapsed: boolean; } @@ -122,7 +63,6 @@ interface IState { isDarkTheme: boolean; isHighContrast: boolean; selectedSpace?: Room; - dndEnabled: boolean; } const toRightOf = (rect: PartialDOMRect) => { @@ -154,19 +94,11 @@ export default class UserMenu extends React.Component { contextMenuPosition: null, isDarkTheme: this.isUserOnDarkTheme(), isHighContrast: this.isUserOnHighContrastTheme(), - dndEnabled: this.doNotDisturb, selectedSpace: SpaceStore.instance.activeSpaceRoom, }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); - - SettingsStore.monitorSetting("feature_dnd", null); - SettingsStore.monitorSetting("doNotDisturb", null); - } - - private get doNotDisturb(): boolean { - return SettingsStore.getValue("doNotDisturb"); } private get hasHomePage(): boolean { @@ -239,20 +171,6 @@ export default class UserMenu extends React.Component { if (this.buttonRef.current) this.buttonRef.current.click(); } break; - - case Action.SettingUpdated: { - const settingUpdatedPayload = payload as SettingUpdatedPayload; - switch (settingUpdatedPayload.settingName) { - case "feature_dnd": - case "doNotDisturb": { - const dndEnabled = this.doNotDisturb; - if (this.state.dndEnabled !== dndEnabled) { - this.setState({ dndEnabled }); - } - break; - } - } - } } }; @@ -348,12 +266,6 @@ export default class UserMenu extends React.Component { this.setState({ contextMenuPosition: null }); // also close the menu }; - private onDndToggle = (ev: ButtonEvent) => { - ev.stopPropagation(); - const current = SettingsStore.getValue("doNotDisturb"); - SettingsStore.setValue("doNotDisturb", null, SettingLevel.DEVICE, !current); - }; - private renderContextMenu = (): React.ReactNode => { if (!this.state.contextMenuPosition) return null; @@ -400,24 +312,6 @@ export default class UserMenu extends React.Component { ); } - let customStatusSection: JSX.Element; - if (SettingsStore.getValue("feature_custom_status")) { - customStatusSection = ; - } - - let dndButton: JSX.Element; - if (SettingsStore.getValue("feature_dnd")) { - dndButton = ( - - ); - } - let feedbackButton; if (SettingsStore.getValue(UIFeature.Feedback)) { feedbackButton = { let primaryOptionList = ( { homeButton } - { dndButton } { /> - { customStatusSection } { topSection } { primaryOptionList } ; @@ -515,11 +407,6 @@ export default class UserMenu extends React.Component { const displayName = OwnProfileStore.instance.displayName || userId; const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize); - let badge: JSX.Element; - if (this.state.dndEnabled) { - badge =
; - } - let name: JSX.Element; if (!this.props.isPanelCollapsed) { name =
@@ -534,9 +421,6 @@ export default class UserMenu extends React.Component { label={_t("User menu")} isExpanded={!!this.state.contextMenuPosition} onContextMenu={this.onContextMenu} - className={classNames({ - mx_UserMenu_cutout: badge, - })} >
{ resizeMethod="crop" className="mx_UserMenu_userAvatar_BaseAvatar" /> - { badge }
{ name } diff --git a/src/components/views/beacon/LeftPanelLiveShareWarning.tsx b/src/components/views/beacon/LeftPanelLiveShareWarning.tsx index 43b00a8fa76..b93d8de1ae7 100644 --- a/src/components/views/beacon/LeftPanelLiveShareWarning.tsx +++ b/src/components/views/beacon/LeftPanelLiveShareWarning.tsx @@ -15,8 +15,8 @@ limitations under the License. */ import classNames from 'classnames'; -import React from 'react'; -import { BeaconIdentifier, Room } from 'matrix-js-sdk/src/matrix'; +import React, { useEffect } from 'react'; +import { Beacon, BeaconIdentifier, Room } from 'matrix-js-sdk/src/matrix'; import { useEventEmitterState } from '../../../hooks/useEventEmitter'; import { _t } from '../../../languageHandler'; @@ -61,6 +61,25 @@ const getLabel = (hasStoppingErrors: boolean, hasLocationErrors: boolean): strin return _t('You are sharing your live location'); }; +const useLivenessMonitor = (liveBeaconIds: BeaconIdentifier[], beacons: Map): void => { + useEffect(() => { + // chromium sets the minimum timer interval to 1000ms + // for inactive tabs + // refresh beacon monitors when the tab becomes active again + const onPageVisibilityChanged = () => { + if (document.visibilityState === 'visible') { + liveBeaconIds.map(identifier => beacons.get(identifier)?.monitorLiveness()); + } + }; + if (liveBeaconIds.length) { + document.addEventListener("visibilitychange", onPageVisibilityChanged); + } + () => { + document.removeEventListener("visibilitychange", onPageVisibilityChanged); + }; + }, [liveBeaconIds, beacons]); +}; + const LeftPanelLiveShareWarning: React.FC = ({ isMinimized }) => { const isMonitoringLiveLocation = useEventEmitterState( OwnBeaconStore.instance, @@ -91,6 +110,8 @@ const LeftPanelLiveShareWarning: React.FC = ({ isMinimized }) => { const hasLocationPublishErrors = !!beaconIdsWithLocationPublishError.length; const hasStoppingErrors = !!beaconIdsWithStoppingError.length; + useLivenessMonitor(liveBeaconIds, OwnBeaconStore.instance.beacons); + if (!isMonitoringLiveLocation) { return null; } diff --git a/src/components/views/context_menus/WidgetContextMenu.tsx b/src/components/views/context_menus/WidgetContextMenu.tsx index 1aeeccc724b..b6a46bd2aca 100644 --- a/src/components/views/context_menus/WidgetContextMenu.tsx +++ b/src/components/views/context_menus/WidgetContextMenu.tsx @@ -110,7 +110,8 @@ const WidgetContextMenu: React.FC = ({ } let snapshotButton; - if (widgetMessaging?.hasCapability(MatrixCapabilities.Screenshots)) { + const screenshotsEnabled = SettingsStore.getValue("enableWidgetScreenshots"); + if (screenshotsEnabled && widgetMessaging?.hasCapability(MatrixCapabilities.Screenshots)) { const onSnapshotClick = () => { widgetMessaging?.takeScreenshot().then(data => { dis.dispatch({ diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 14fb69b1563..fb2f7113dd8 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -101,6 +101,7 @@ const DevtoolsDialog: React.FC = ({ roomId, onFinished }) => {

{ _t("Options") }

+
; } diff --git a/src/components/views/dialogs/SpotlightDialog.tsx b/src/components/views/dialogs/SpotlightDialog.tsx index 1d6078fa10c..f5efc0b8dc8 100644 --- a/src/components/views/dialogs/SpotlightDialog.tsx +++ b/src/components/views/dialogs/SpotlightDialog.tsx @@ -74,6 +74,7 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { PosthogAnalytics } from "../../../PosthogAnalytics"; import { getCachedRoomIDForAlias } from "../../../RoomAliasCache"; import { roomContextDetailsText, spaceContextDetailsText } from "../../../utils/i18n-helpers"; +import { RecentAlgorithm } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; const MAX_RECENT_SEARCHES = 10; const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons @@ -210,6 +211,8 @@ type Result = IRoomResult | IResult; const isRoomResult = (result: any): result is IRoomResult => !!result?.room; +const recentAlgorithm = new RecentAlgorithm(); + export const useWebSearchMetrics = (numResults: number, queryLength: number, viaSpotlight: boolean): void => { useEffect(() => { if (!queryLength) return; @@ -280,6 +283,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => const results: [Result[], Result[], Result[]] = [[], [], []]; + // Group results in their respective sections possibleResults.forEach(entry => { if (isRoomResult(entry)) { if (!entry.room.normalizedName.includes(normalizedQuery) && @@ -295,8 +299,25 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => results[entry.section].push(entry); }); + // Sort results by most recent activity + + const myUserId = cli.getUserId(); + for (const resultArray of results) { + resultArray.sort((a: Result, b: Result) => { + // This is not a room result, it should appear at the bottom of + // the list + if (!(a as IRoomResult).room) return 1; + if (!(b as IRoomResult).room) return -1; + + const roomA = (a as IRoomResult).room; + const roomB = (b as IRoomResult).room; + + return recentAlgorithm.getLastTs(roomB, myUserId) - recentAlgorithm.getLastTs(roomA, myUserId); + }); + } + return results; - }, [possibleResults, trimmedQuery]); + }, [possibleResults, trimmedQuery, cli]); const numResults = trimmedQuery ? people.length + rooms.length + spaces.length : 0; useWebSearchMetrics(numResults, query.length, true); diff --git a/src/components/views/dialogs/TabbedIntegrationManagerDialog.tsx b/src/components/views/dialogs/TabbedIntegrationManagerDialog.tsx deleted file mode 100644 index 5a5d6e38229..00000000000 --- a/src/components/views/dialogs/TabbedIntegrationManagerDialog.tsx +++ /dev/null @@ -1,176 +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. -*/ - -import React from 'react'; -import { Room } from "matrix-js-sdk/src/models/room"; -import classNames from 'classnames'; -import { logger } from "matrix-js-sdk/src/logger"; - -import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; -import { dialogTermsInteractionCallback, TermsNotSignedError } from "../../../Terms"; -import * as ScalarMessaging from "../../../ScalarMessaging"; -import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance"; -import ScalarAuthClient from "../../../ScalarAuthClient"; -import AccessibleButton from "../elements/AccessibleButton"; -import IntegrationManager from "../settings/IntegrationManager"; -import { IDialogProps } from "./IDialogProps"; - -interface IProps extends IDialogProps { - /** - * Optional room where the integration manager should be open to - */ - room?: Room; - - /** - * Optional screen to open on the integration manager - */ - screen?: string; - - /** - * Optional integration ID to open in the integration manager - */ - integrationId?: string; -} - -interface IState { - managers: IntegrationManagerInstance[]; - busy: boolean; - currentIndex: number; - currentConnected: boolean; - currentLoading: boolean; - currentScalarClient: ScalarAuthClient; -} - -export default class TabbedIntegrationManagerDialog extends React.Component { - constructor(props: IProps) { - super(props); - - this.state = { - managers: IntegrationManagers.sharedInstance().getOrderedManagers(), - busy: true, - currentIndex: 0, - currentConnected: false, - currentLoading: true, - currentScalarClient: null, - }; - } - - public componentDidMount(): void { - this.openManager(0, true); - } - - private openManager = async (i: number, force = false): Promise => { - if (i === this.state.currentIndex && !force) return; - - const manager = this.state.managers[i]; - const client = manager.getScalarClient(); - this.setState({ - busy: true, - currentIndex: i, - currentLoading: true, - currentConnected: false, - currentScalarClient: client, - }); - - ScalarMessaging.setOpenManagerUrl(manager.uiUrl); - - client.setTermsInteractionCallback((policyInfo, agreedUrls) => { - // To avoid visual glitching of two modals stacking briefly, we customise the - // terms dialog sizing when it will appear for the integration manager so that - // it gets the same basic size as the IM's own modal. - return dialogTermsInteractionCallback( - policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationManager', - ); - }); - - try { - await client.connect(); - if (!client.hasCredentials()) { - this.setState({ - busy: false, - currentLoading: false, - currentConnected: false, - }); - } else { - this.setState({ - busy: false, - currentLoading: false, - currentConnected: true, - }); - } - } catch (e) { - if (e instanceof TermsNotSignedError) { - return; - } - - logger.error(e); - this.setState({ - busy: false, - currentLoading: false, - currentConnected: false, - }); - } - }; - - private renderTabs(): JSX.Element[] { - return this.state.managers.map((m, i) => { - const classes = classNames({ - 'mx_TabbedIntegrationManagerDialog_tab': true, - 'mx_TabbedIntegrationManagerDialog_currentTab': this.state.currentIndex === i, - }); - return ( - this.openManager(i)} - key={`tab_${i}`} - disabled={this.state.busy} - > - { m.name } - - ); - }); - } - - public renderTab(): JSX.Element { - let uiUrl = null; - if (this.state.currentScalarClient) { - uiUrl = this.state.currentScalarClient.getScalarInterfaceUrlForRoom( - this.props.room, - this.props.screen, - this.props.integrationId, - ); - } - return {/* no-op */}} - />; - } - - public render(): JSX.Element { - return ( -
-
- { this.renderTabs() } -
-
- { this.renderTab() } -
-
- ); - } -} diff --git a/src/components/views/elements/AppWarning.tsx b/src/components/views/elements/AppWarning.tsx index 352c5990680..b3dfae99118 100644 --- a/src/components/views/elements/AppWarning.tsx +++ b/src/components/views/elements/AppWarning.tsx @@ -1,3 +1,19 @@ +/* +Copyright 2022 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'; interface IProps { diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.tsx similarity index 71% rename from src/components/views/elements/Pill.js rename to src/components/views/elements/Pill.tsx index 7d5a9973c7f..f344f894569 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.tsx @@ -13,67 +13,82 @@ 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 classNames from 'classnames'; import { Room } from 'matrix-js-sdk/src/models/room'; import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; -import PropTypes from 'prop-types'; import { logger } from "matrix-js-sdk/src/logger"; +import { MatrixClient } from 'matrix-js-sdk/src/client'; import dis from '../../../dispatcher/dispatcher'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { getPrimaryPermalinkEntity, parsePermalink } from "../../../utils/permalinks/Permalinks"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { Action } from "../../../dispatcher/actions"; -import Tooltip from './Tooltip'; -import RoomAvatar from "../avatars/RoomAvatar"; -import MemberAvatar from "../avatars/MemberAvatar"; +import Tooltip, { Alignment } from './Tooltip'; +import RoomAvatar from '../avatars/RoomAvatar'; +import MemberAvatar from '../avatars/MemberAvatar'; + +export enum PillType { + UserMention = 'TYPE_USER_MENTION', + RoomMention = 'TYPE_ROOM_MENTION', + AtRoomMention = 'TYPE_AT_ROOM_MENTION', // '@room' mention +} + +interface IProps { + // The Type of this Pill. If url is given, this is auto-detected. + type?: PillType; + // The URL to pillify (no validation is done) + url?: string; + // Whether the pill is in a message + inMessage?: boolean; + // The room in which this pill is being rendered + room?: Room; + // Whether to include an avatar in the pill + shouldShowPillAvatar?: boolean; +} + +interface IState { + // ID/alias of the room/user + resourceId: string; + // Type of pill + pillType: string; + // The member related to the user pill + member?: RoomMember; + // The room related to the room pill + room?: Room; + // Is the user hovering the pill + hover: boolean; +} -class Pill extends React.Component { - static roomNotifPos(text) { +export default class Pill extends React.Component { + private unmounted = true; + private matrixClient: MatrixClient; + + public static roomNotifPos(text: string): number { return text.indexOf("@room"); } - static roomNotifLen() { + public static roomNotifLen(): number { return "@room".length; } - static TYPE_USER_MENTION = 'TYPE_USER_MENTION'; - static TYPE_ROOM_MENTION = 'TYPE_ROOM_MENTION'; - static TYPE_AT_ROOM_MENTION = 'TYPE_AT_ROOM_MENTION'; // '@room' mention - - static propTypes = { - // The Type of this Pill. If url is given, this is auto-detected. - type: PropTypes.string, - // The URL to pillify (no validation is done) - url: PropTypes.string, - // Whether the pill is in a message - inMessage: PropTypes.bool, - // The room in which this pill is being rendered - room: PropTypes.instanceOf(Room), - // Whether to include an avatar in the pill - shouldShowPillAvatar: PropTypes.bool, - // Whether to render this pill as if it were highlit by a selection - isSelected: PropTypes.bool, - }; - - state = { - // ID/alias of the room/user - resourceId: null, - // Type of pill - pillType: null, + constructor(props: IProps) { + super(props); - // The member related to the user pill - member: null, - // The room related to the room pill - room: null, - // Is the user hovering the pill - hover: false, - }; + this.state = { + resourceId: null, + pillType: null, + member: null, + room: null, + hover: false, + }; + } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - // eslint-disable-next-line camelcase - async UNSAFE_componentWillReceiveProps(nextProps) { + // eslint-disable-next-line camelcase, @typescript-eslint/naming-convention + public async UNSAFE_componentWillReceiveProps(nextProps: IProps): Promise { let resourceId; let prefix; @@ -89,28 +104,28 @@ class Pill extends React.Component { } const pillType = this.props.type || { - '@': Pill.TYPE_USER_MENTION, - '#': Pill.TYPE_ROOM_MENTION, - '!': Pill.TYPE_ROOM_MENTION, + '@': PillType.UserMention, + '#': PillType.RoomMention, + '!': PillType.RoomMention, }[prefix]; let member; let room; switch (pillType) { - case Pill.TYPE_AT_ROOM_MENTION: { + case PillType.AtRoomMention: { room = nextProps.room; } break; - case Pill.TYPE_USER_MENTION: { + case PillType.UserMention: { const localMember = nextProps.room ? nextProps.room.getMember(resourceId) : undefined; member = localMember; if (!localMember) { member = new RoomMember(null, resourceId); this.doProfileLookup(resourceId, member); } - break; } - case Pill.TYPE_ROOM_MENTION: { + break; + case PillType.RoomMention: { const localRoom = resourceId[0] === '#' ? MatrixClientPeg.get().getRooms().find((r) => { return r.getCanonicalAlias() === resourceId || @@ -122,39 +137,39 @@ class Pill extends React.Component { // a room avatar and name. // this.doRoomProfileLookup(resourceId, member); } - break; } + break; } this.setState({ resourceId, pillType, member, room }); } - componentDidMount() { - this._unmounted = false; - this._matrixClient = MatrixClientPeg.get(); + public componentDidMount(): void { + this.unmounted = false; + this.matrixClient = MatrixClientPeg.get(); // eslint-disable-next-line new-cap this.UNSAFE_componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves. } - componentWillUnmount() { - this._unmounted = true; + public componentWillUnmount(): void { + this.unmounted = true; } - onMouseOver = () => { + private onMouseOver = (): void => { this.setState({ hover: true, }); }; - onMouseLeave = () => { + private onMouseLeave = (): void => { this.setState({ hover: false, }); }; - doProfileLookup(userId, member) { + private doProfileLookup(userId: string, member): void { MatrixClientPeg.get().getProfileInfo(userId).then((resp) => { - if (this._unmounted) { + if (this.unmounted) { return; } member.name = resp.displayname; @@ -173,7 +188,7 @@ class Pill extends React.Component { }); } - onUserPillClicked = (e) => { + private onUserPillClicked = (e): void => { e.preventDefault(); dis.dispatch({ action: Action.ViewUser, @@ -181,7 +196,7 @@ class Pill extends React.Component { }); }; - render() { + public render(): JSX.Element { const resource = this.state.resourceId; let avatar = null; @@ -191,7 +206,7 @@ class Pill extends React.Component { let href = this.props.url; let onClick; switch (this.state.pillType) { - case Pill.TYPE_AT_ROOM_MENTION: { + case PillType.AtRoomMention: { const room = this.props.room; if (room) { linkText = "@room"; @@ -200,9 +215,9 @@ class Pill extends React.Component { } pillClass = 'mx_AtRoomPill'; } - break; } - case Pill.TYPE_USER_MENTION: { + break; + case PillType.UserMention: { // If this user is not a member of this room, default to the empty member const member = this.state.member; if (member) { @@ -210,15 +225,15 @@ class Pill extends React.Component { member.rawDisplayName = member.rawDisplayName || ''; linkText = member.rawDisplayName; if (this.props.shouldShowPillAvatar) { - avatar =
diff --git a/src/components/views/rooms/MemberTile.tsx b/src/components/views/rooms/MemberTile.tsx index b652771a43e..f292ec3b589 100644 --- a/src/components/views/rooms/MemberTile.tsx +++ b/src/components/views/rooms/MemberTile.tsx @@ -20,12 +20,10 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; -import { UserEvent } from "matrix-js-sdk/src/models/user"; import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import { UserTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning'; -import SettingsStore from "../../../settings/SettingsStore"; import dis from "../../../dispatcher/dispatcher"; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -41,7 +39,6 @@ interface IProps { } interface IState { - statusMessage: string; isRoomEncrypted: boolean; e2eStatus: string; } @@ -58,7 +55,6 @@ export default class MemberTile extends React.Component { super(props); this.state = { - statusMessage: this.getStatusMessage(), isRoomEncrypted: false, e2eStatus: null, }; @@ -67,13 +63,6 @@ export default class MemberTile extends React.Component { componentDidMount() { const cli = MatrixClientPeg.get(); - if (SettingsStore.getValue("feature_custom_status")) { - const { user } = this.props.member; - if (user) { - user.on(UserEvent._UnstableStatusMessage, this.onStatusMessageCommitted); - } - } - const { roomId } = this.props.member; if (roomId) { const isRoomEncrypted = cli.isRoomEncrypted(roomId); @@ -94,11 +83,6 @@ export default class MemberTile extends React.Component { componentWillUnmount() { const cli = MatrixClientPeg.get(); - const { user } = this.props.member; - if (user) { - user.removeListener(UserEvent._UnstableStatusMessage, this.onStatusMessageCommitted); - } - if (cli) { cli.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); cli.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); @@ -158,21 +142,6 @@ export default class MemberTile extends React.Component { }); } - private getStatusMessage(): string { - const { user } = this.props.member; - if (!user) { - return ""; - } - return user.unstable_statusMessage; - } - - private onStatusMessageCommitted = (): void => { - // The `User` object has observed a status message change. - this.setState({ - statusMessage: this.getStatusMessage(), - }); - }; - shouldComponentUpdate(nextProps: IProps, nextState: IState): boolean { if ( this.memberLastModifiedTime === undefined || @@ -222,11 +191,6 @@ export default class MemberTile extends React.Component { const name = this.getDisplayName(); const presenceState = member.user ? member.user.presence : null; - let statusMessage = null; - if (member.user && SettingsStore.getValue("feature_custom_status")) { - statusMessage = this.state.statusMessage; - } - const av = (