diff --git a/CHANGELOG.md b/CHANGELOG.md index ef31ef886f..8c460b4f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,63 @@ +Changes in [3.12.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.12.0) (2021-01-18) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.12.0-rc.1...v3.12.0) + + * Upgrade to JS SDK 9.5.0 + * Fix incoming call box on dark theme + [\#5543](https://github.com/matrix-org/matrix-react-sdk/pull/5543) + +Changes in [3.12.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.12.0-rc.1) (2021-01-13) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.11.1...v3.12.0-rc.1) + + * Upgrade to JS SDK 9.5.0-rc.1 + * Fix soft crash on soft logout page + [\#5539](https://github.com/matrix-org/matrix-react-sdk/pull/5539) + * Translations update from Weblate + [\#5538](https://github.com/matrix-org/matrix-react-sdk/pull/5538) + * Run TypeScript tests + [\#5537](https://github.com/matrix-org/matrix-react-sdk/pull/5537) + * Add a basic widget explorer to devtools (per-room) + [\#5528](https://github.com/matrix-org/matrix-react-sdk/pull/5528) + * Add to security key field + [\#5534](https://github.com/matrix-org/matrix-react-sdk/pull/5534) + * Fix avatar upload prompt/tooltip floating wrong and permissions + [\#5526](https://github.com/matrix-org/matrix-react-sdk/pull/5526) + * Add a dialpad UI for PSTN lookup + [\#5523](https://github.com/matrix-org/matrix-react-sdk/pull/5523) + * Basic call transfer initiation support + [\#5494](https://github.com/matrix-org/matrix-react-sdk/pull/5494) + * Fix #15988 + [\#5524](https://github.com/matrix-org/matrix-react-sdk/pull/5524) + * Bump node-notifier from 8.0.0 to 8.0.1 + [\#5520](https://github.com/matrix-org/matrix-react-sdk/pull/5520) + * Use TypeScript source for development, swap to build during release + [\#5503](https://github.com/matrix-org/matrix-react-sdk/pull/5503) + * Look for emoji in the body that will be displayed + [\#5517](https://github.com/matrix-org/matrix-react-sdk/pull/5517) + * Bump ini from 1.3.5 to 1.3.7 + [\#5486](https://github.com/matrix-org/matrix-react-sdk/pull/5486) + * Recognise `*.element.io` links as Element permalinks + [\#5514](https://github.com/matrix-org/matrix-react-sdk/pull/5514) + * Fixes for call UI + [\#5509](https://github.com/matrix-org/matrix-react-sdk/pull/5509) + * Add a snowfall chat effect (with /snowfall command) + [\#5511](https://github.com/matrix-org/matrix-react-sdk/pull/5511) + * fireworks effect + [\#5507](https://github.com/matrix-org/matrix-react-sdk/pull/5507) + * Don't play call end sound for calls that never started + [\#5506](https://github.com/matrix-org/matrix-react-sdk/pull/5506) + * Add /tableflip slash command + [\#5485](https://github.com/matrix-org/matrix-react-sdk/pull/5485) + * Import from src in IncomingCallBox.tsx + [\#5504](https://github.com/matrix-org/matrix-react-sdk/pull/5504) + * Social Login support both https and mxc icons + [\#5499](https://github.com/matrix-org/matrix-react-sdk/pull/5499) + * Fix padding in confirmation email registration prompt + [\#5501](https://github.com/matrix-org/matrix-react-sdk/pull/5501) + * Fix room list help prompt alignment + [\#5500](https://github.com/matrix-org/matrix-react-sdk/pull/5500) + Changes in [3.11.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.11.1) (2020-12-21) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.11.0...v3.11.1) diff --git a/package.json b/package.json index 92b574da34..1316b26030 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.11.1", + "version": "3.12.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -82,7 +82,7 @@ "linkifyjs": "^2.1.9", "lodash": "^4.17.19", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", - "matrix-widget-api": "^0.1.0-beta.10", + "matrix-widget-api": "0.1.0-beta.11", "minimist": "^1.2.5", "pako": "^1.0.11", "parse5": "^5.1.1", diff --git a/res/css/views/dialogs/_RoomSettingsDialogBridges.scss b/res/css/views/dialogs/_RoomSettingsDialogBridges.scss index a1793cc75e..c97a3b69b7 100644 --- a/res/css/views/dialogs/_RoomSettingsDialogBridges.scss +++ b/res/css/views/dialogs/_RoomSettingsDialogBridges.scss @@ -89,24 +89,18 @@ limitations under the License. } } - .mx_showMore { - display: block; - text-align: left; - margin-top: 10px; - } - .metadata { color: $muted-fg-color; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; margin-bottom: 0; - } - - .metadata.visible { overflow-y: visible; text-overflow: ellipsis; white-space: normal; + padding: 0; + + > li { + padding: 0; + border: 0; + } } } } diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index ec5afd13f0..70ec2b7033 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -140,7 +140,7 @@ class LoggedInView extends React.Component { protected readonly _matrixClient: MatrixClient; protected readonly _roomView: React.RefObject; protected readonly _resizeContainer: React.RefObject; - protected readonly _compactLayoutWatcherRef: string; + protected compactLayoutWatcherRef: string; protected resizer: Resizer; constructor(props, context) { @@ -157,18 +157,6 @@ class LoggedInView extends React.Component { CallMediaHandler.loadDevices(); - document.addEventListener('keydown', this._onNativeKeyDown, false); - - this._updateServerNoticeEvents(); - - this._matrixClient.on("accountData", this.onAccountData); - this._matrixClient.on("sync", this.onSync); - this._matrixClient.on("RoomState.events", this.onRoomStateEvents); - - this._compactLayoutWatcherRef = SettingsStore.watchSetting( - "useCompactLayout", null, this.onCompactLayoutChanged, - ); - fixupColorFonts(); this._roomView = React.createRef(); @@ -176,6 +164,24 @@ class LoggedInView extends React.Component { } componentDidMount() { + document.addEventListener('keydown', this._onNativeKeyDown, false); + + this._updateServerNoticeEvents(); + + this._matrixClient.on("accountData", this.onAccountData); + this._matrixClient.on("sync", this.onSync); + // Call `onSync` with the current state as well + this.onSync( + this._matrixClient.getSyncState(), + null, + this._matrixClient.getSyncStateData(), + ); + this._matrixClient.on("RoomState.events", this.onRoomStateEvents); + + this.compactLayoutWatcherRef = SettingsStore.watchSetting( + "useCompactLayout", null, this.onCompactLayoutChanged, + ); + this.resizer = this._createResizer(); this.resizer.attach(); this._loadResizerPreferences(); @@ -186,7 +192,7 @@ class LoggedInView extends React.Component { this._matrixClient.removeListener("accountData", this.onAccountData); this._matrixClient.removeListener("sync", this.onSync); this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents); - SettingsStore.unwatchSetting(this._compactLayoutWatcherRef); + SettingsStore.unwatchSetting(this.compactLayoutWatcherRef); this.resizer.detach(); } diff --git a/src/components/views/context_menus/WidgetContextMenu.tsx b/src/components/views/context_menus/WidgetContextMenu.tsx index 8026942038..4662c74d78 100644 --- a/src/components/views/context_menus/WidgetContextMenu.tsx +++ b/src/components/views/context_menus/WidgetContextMenu.tsx @@ -26,7 +26,6 @@ import {WidgetMessagingStore} from "../../../stores/widgets/WidgetMessagingStore import RoomContext from "../../../contexts/RoomContext"; import dis from "../../../dispatcher/dispatcher"; import SettingsStore from "../../../settings/SettingsStore"; -import {SettingLevel} from "../../../settings/SettingLevel"; import Modal from "../../../Modal"; import QuestionDialog from "../dialogs/QuestionDialog"; import {WidgetType} from "../../../widgets/WidgetType"; @@ -127,7 +126,8 @@ const WidgetContextMenu: React.FC = ({ console.info("Revoking permission for widget to load: " + app.eventId); const current = SettingsStore.getValue("allowedWidgets", roomId); current[app.eventId] = false; - SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).catch(err => { + const level = SettingsStore.firstSupportedLevel("allowedWidgets"); + SettingsStore.setValue("allowedWidgets", roomId, level, current).catch(err => { console.error(err); // We don't really need to do anything about this - the user will just hit the button again. }); diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 7e0ae965bb..213351889f 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -33,7 +33,6 @@ import SettingsStore from "../../../settings/SettingsStore"; import {aboveLeftOf, ContextMenuButton} from "../../structures/ContextMenu"; import PersistedElement, {getPersistKey} from "./PersistedElement"; import {WidgetType} from "../../../widgets/WidgetType"; -import {SettingLevel} from "../../../settings/SettingLevel"; import {StopGapWidget} from "../../../stores/widgets/StopGapWidget"; import {ElementWidgetActions} from "../../../stores/widgets/ElementWidgetActions"; import {MatrixCapabilities} from "matrix-widget-api"; @@ -240,7 +239,8 @@ export default class AppTile extends React.Component { console.info("Granting permission for widget to load: " + this.props.app.eventId); const current = SettingsStore.getValue("allowedWidgets", roomId); current[this.props.app.eventId] = true; - SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => { + const level = SettingsStore.firstSupportedLevel("allowedWidgets"); + SettingsStore.setValue("allowedWidgets", roomId, level, current).then(() => { this.setState({hasPermissionToLoad: true}); // Fetch a token for the integration manager, now that we're allowed to diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index ce11e63026..ff864d9c13 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -412,7 +412,10 @@ export default class TextualBody extends React.Component { ref: this._content, }); if (this.props.replacingEventId) { - body = [body, this._renderEditedMarker()]; + body = <> + {body} + {this._renderEditedMarker()} + ; } if (this.props.highlightLink) { diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index a241a13991..c985e80010 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -29,7 +29,7 @@ import ActiveRoomObserver from "../../../ActiveRoomObserver"; import { _t } from "../../../languageHandler"; import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; -import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore"; +import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -51,7 +51,6 @@ import IconizedContextMenu, { IconizedContextMenuRadio, } from "../context_menus/IconizedContextMenu"; import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore"; -import { UPDATE_EVENT } from "../../../stores/AsyncStore"; interface IProps { room: Room; @@ -99,12 +98,18 @@ export default class RoomTile extends React.PureComponent { ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate); this.dispatcherRef = defaultDispatcher.register(this.onAction); - MessagePreviewStore.instance.on(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged); + MessagePreviewStore.instance.on( + MessagePreviewStore.getPreviewChangedEventName(this.props.room), + this.onRoomPreviewChanged, + ); this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room); this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); this.roomProps = EchoChamber.forRoom(this.props.room); this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate); - CommunityPrototypeStore.instance.on(UPDATE_EVENT, this.onCommunityUpdate); + CommunityPrototypeStore.instance.on( + CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId), + this.onCommunityUpdate, + ); } private onNotificationUpdate = () => { @@ -128,6 +133,24 @@ export default class RoomTile extends React.PureComponent { if (prevProps.showMessagePreview !== this.props.showMessagePreview && this.showMessagePreview) { this.setState({messagePreview: this.generatePreview()}); } + if (prevProps.room?.roomId !== this.props.room?.roomId) { + MessagePreviewStore.instance.off( + MessagePreviewStore.getPreviewChangedEventName(prevProps.room), + this.onRoomPreviewChanged, + ); + MessagePreviewStore.instance.on( + MessagePreviewStore.getPreviewChangedEventName(this.props.room), + this.onRoomPreviewChanged, + ); + CommunityPrototypeStore.instance.off( + CommunityPrototypeStore.getUpdateEventName(prevProps.room?.roomId), + this.onCommunityUpdate, + ); + CommunityPrototypeStore.instance.on( + CommunityPrototypeStore.getUpdateEventName(this.props.room?.roomId), + this.onCommunityUpdate, + ); + } } public componentDidMount() { @@ -140,11 +163,17 @@ export default class RoomTile extends React.PureComponent { public componentWillUnmount() { if (this.props.room) { ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate); + MessagePreviewStore.instance.off( + MessagePreviewStore.getPreviewChangedEventName(this.props.room), + this.onRoomPreviewChanged, + ); + CommunityPrototypeStore.instance.off( + CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId), + this.onCommunityUpdate, + ); } defaultDispatcher.unregister(this.dispatcherRef); - MessagePreviewStore.instance.off(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged); this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); - CommunityPrototypeStore.instance.off(UPDATE_EVENT, this.onCommunityUpdate); } private onAction = (payload: ActionPayload) => { diff --git a/src/components/views/settings/BridgeTile.js b/src/components/views/settings/BridgeTile.js deleted file mode 100644 index e9c58518e4..0000000000 --- a/src/components/views/settings/BridgeTile.js +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2020 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 {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; -import {_t} from "../../../languageHandler"; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import Pill from "../elements/Pill"; -import {makeUserPermalink} from "../../../utils/permalinks/Permalinks"; -import BaseAvatar from "../avatars/BaseAvatar"; -import AccessibleButton from "../elements/AccessibleButton"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; -import SettingsStore from "../../../settings/SettingsStore"; - -@replaceableComponent("views.settings.BridgeTile") -export default class BridgeTile extends React.PureComponent { - static propTypes = { - ev: PropTypes.object.isRequired, - room: PropTypes.object.isRequired, - } - - state = { - visible: false, - } - - _toggleVisible() { - this.setState({ - visible: !this.state.visible, - }); - } - - render() { - const content = this.props.ev.getContent(); - const { channel, network, protocol } = content; - const protocolName = protocol.displayname || protocol.id; - const channelName = channel.displayname || channel.id; - const networkName = network ? network.displayname || network.id : protocolName; - - let creator = null; - if (content.creator) { - creator = _t("This bridge was provisioned by .", {}, { - user: , - }); - } - - const bot = _t("This bridge is managed by .", {}, { - user: , - }); - - let networkIcon; - - if (protocol.avatar) { - const avatarUrl = getHttpUriForMxc( - MatrixClientPeg.get().getHomeserverUrl(), - protocol.avatar, 64, 64, "crop", - ); - - networkIcon = ; - } else { - networkIcon =
; - } - - const id = this.props.ev.getId(); - const metadataClassname = "metadata" + (this.state.visible ? " visible" : ""); - return (
  • -
    - {networkIcon} -
    -
    -

    {protocolName}

    -

    - {_t("Workspace: %(networkName)s", {networkName})} - {_t("Channel: %(channelName)s", {channelName})} -

    -

    - {creator} {bot} -

    - - { this.state.visible ? _t("Show less") : _t("Show more") } - -
    -
  • ); - } -} diff --git a/src/components/views/settings/BridgeTile.tsx b/src/components/views/settings/BridgeTile.tsx new file mode 100644 index 0000000000..58499ebd25 --- /dev/null +++ b/src/components/views/settings/BridgeTile.tsx @@ -0,0 +1,167 @@ +/* +Copyright 2020 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 {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; +import {_t} from "../../../languageHandler"; +import {MatrixClientPeg} from "../../../MatrixClientPeg"; +import Pill from "../elements/Pill"; +import {makeUserPermalink} from "../../../utils/permalinks/Permalinks"; +import BaseAvatar from "../avatars/BaseAvatar"; +import SettingsStore from "../../../settings/SettingsStore"; +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { isUrlPermitted } from '../../../HtmlUtils'; + +interface IProps { + ev: MatrixEvent; + room: Room; +} + +/** + * This should match https://github.com/matrix-org/matrix-doc/blob/hs/msc-bridge-inf/proposals/2346-bridge-info-state-event.md#mbridge + */ +interface IBridgeStateEvent { + bridgebot: string; + creator?: string; + protocol: { + id: string; + displayname?: string; + // eslint-disable-next-line camelcase + avatar_url?: string; + // eslint-disable-next-line camelcase + external_url?: string; + }; + network?: { + id: string; + displayname?: string; + // eslint-disable-next-line camelcase + avatar_url?: string; + // eslint-disable-next-line camelcase + external_url?: string; + }; + channel: { + id: string; + displayname?: string; + // eslint-disable-next-line camelcase + avatar_url?: string; + // eslint-disable-next-line camelcase + external_url?: string; + }; +} + +export default class BridgeTile extends React.PureComponent { + static propTypes = { + ev: PropTypes.object.isRequired, + room: PropTypes.object.isRequired, + } + + render() { + const content: IBridgeStateEvent = this.props.ev.getContent(); + // Validate + if (!content.channel?.id || !content.protocol?.id) { + console.warn(`Bridge info event ${this.props.ev.getId()} has missing content. Tile will not render`); + return null; + } + if (!content.bridgebot) { + // Bridgebot was not required previously, so in order to not break rooms we are allowing + // the sender to be used in place. When the proposal is merged, this should be removed. + console.warn(`Bridge info event ${this.props.ev.getId()} does not provide a 'bridgebot' key which` + + "is deprecated behaviour. Using sender for now."); + content.bridgebot = this.props.ev.getSender(); + } + const { channel, network, protocol } = content; + const protocolName = protocol.displayname || protocol.id; + const channelName = channel.displayname || channel.id; + + let creator = null; + if (content.creator) { + creator =
  • {_t("This bridge was provisioned by .", {}, { + user: () => , + })}
  • ; + } + + const bot =
  • {_t("This bridge is managed by .", {}, { + user: () => , + })}
  • ; + + let networkIcon; + + if (protocol.avatar_url) { + const avatarUrl = getHttpUriForMxc( + MatrixClientPeg.get().getHomeserverUrl(), + protocol.avatar_url, 64, 64, "crop", + ); + + networkIcon = ; + } else { + networkIcon =
    ; + } + let networkItem = null; + if (network) { + const networkName = network.displayname || network.id; + let networkLink = {networkName}; + if (typeof network.external_url === "string" && isUrlPermitted(network.external_url)) { + networkLink = {networkName} + } + networkItem = _t("Workspace: ", {}, { + networkLink: () => networkLink, + }); + } + + let channelLink = {channelName}; + if (typeof channel.external_url === "string" && isUrlPermitted(channel.external_url)) { + channelLink = {channelName} + } + + const id = this.props.ev.getId(); + return (
  • +
    + {networkIcon} +
    +
    +

    {protocolName}

    +

    + {networkItem} + {_t("Channel: ", {}, { + channelLink: () => channelLink, + })} +

    +
      + {creator} {bot} +
    +
    +
  • ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8739d0cf2f..274bd247e2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -584,6 +584,7 @@ "Send stickers into this room": "Send stickers into this room", "Send stickers into your active room": "Send stickers into your active room", "Change which room you're viewing": "Change which room you're viewing", + "Change which room, message, or user you're viewing": "Change which room, message, or user you're viewing", "Change the topic of this room": "Change the topic of this room", "See when the topic changes in this room": "See when the topic changes in this room", "Change the topic of your active room": "Change the topic of your active room", @@ -965,10 +966,8 @@ "Upload": "Upload", "This bridge was provisioned by .": "This bridge was provisioned by .", "This bridge is managed by .": "This bridge is managed by .", - "Workspace: %(networkName)s": "Workspace: %(networkName)s", - "Channel: %(channelName)s": "Channel: %(channelName)s", - "Show less": "Show less", - "Show more": "Show more", + "Workspace: ": "Workspace: ", + "Channel: ": "Channel: ", "Failed to upload profile picture!": "Failed to upload profile picture!", "Upload new:": "Upload new:", "No display name": "No display name", @@ -1532,6 +1531,7 @@ "Jump to first invite.": "Jump to first invite.", "Show %(count)s more|other": "Show %(count)s more", "Show %(count)s more|one": "Show %(count)s more", + "Show less": "Show less", "Use default": "Use default", "All messages": "All messages", "Mentions & Keywords": "Mentions & Keywords", @@ -1594,6 +1594,7 @@ "New published address (e.g. #alias:server)": "New published address (e.g. #alias:server)", "Local Addresses": "Local Addresses", "Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)": "Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)", + "Show more": "Show more", "Error updating flair": "Error updating flair", "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.", "Invalid community ID": "Invalid community ID", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index bef1564e90..f6af68bedd 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -426,7 +426,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: true, }, "allowedWidgets": { - supportedLevels: [SettingLevel.ROOM_ACCOUNT], + supportedLevels: [SettingLevel.ROOM_ACCOUNT, SettingLevel.ROOM_DEVICE], + supportedLevelsAreOrdered: true, default: {}, // none allowed }, "analyticsOptIn": { diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 82f169f498..1b718a72b3 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -467,6 +467,32 @@ export default class SettingsStore { return LEVEL_HANDLERS[level].isSupported(); } + /** + * Determines the first supported level out of all the levels that can be used for a + * specific setting. + * @param {string} settingName The setting name. + * @return {SettingLevel} + */ + public static firstSupportedLevel(settingName: string): SettingLevel { + // Verify that the setting is actually a setting + const setting = SETTINGS[settingName]; + if (!setting) { + throw new Error("Setting '" + settingName + "' does not appear to be a setting."); + } + + const levelOrder = (setting.supportedLevelsAreOrdered ? setting.supportedLevels : LEVEL_ORDER); + if (!levelOrder.includes(SettingLevel.DEFAULT)) levelOrder.push(SettingLevel.DEFAULT); // always include default + + const handlers = SettingsStore.getHandlers(settingName); + + for (const level of levelOrder) { + const handler = handlers[level]; + if (!handler) continue; + return level; + } + return null; + } + /** * Debugging function for reading explicit setting values without going through the * complicated/biased functions in the SettingsStore. This will print information to diff --git a/src/settings/handlers/AccountSettingsHandler.ts b/src/settings/handlers/AccountSettingsHandler.ts index d609fd3231..e75faa49c1 100644 --- a/src/settings/handlers/AccountSettingsHandler.ts +++ b/src/settings/handlers/AccountSettingsHandler.ts @@ -169,7 +169,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa public isSupported(): boolean { const cli = MatrixClientPeg.get(); - return cli !== undefined && cli !== null; + return cli !== undefined && cli !== null && !cli.isGuest(); } private getSettings(eventType = "im.vector.web.settings"): any { // TODO: [TS] Types on return diff --git a/src/settings/handlers/RoomAccountSettingsHandler.ts b/src/settings/handlers/RoomAccountSettingsHandler.ts index 53e29653f8..416bdddb90 100644 --- a/src/settings/handlers/RoomAccountSettingsHandler.ts +++ b/src/settings/handlers/RoomAccountSettingsHandler.ts @@ -129,7 +129,7 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin public isSupported(): boolean { const cli = MatrixClientPeg.get(); - return cli !== undefined && cli !== null; + return cli !== undefined && cli !== null && !cli.isGuest(); } private getSettings(roomId: string, eventType = "im.vector.web.settings"): any { // TODO: [TS] Type return diff --git a/src/stores/CommunityPrototypeStore.ts b/src/stores/CommunityPrototypeStore.ts index 95d56bd40e..92e094c83b 100644 --- a/src/stores/CommunityPrototypeStore.ts +++ b/src/stores/CommunityPrototypeStore.ts @@ -48,6 +48,10 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient { return CommunityPrototypeStore.internalInstance; } + public static getUpdateEventName(roomId: string): string { + return `${UPDATE_EVENT}:${roomId}`; + } + public getSelectedCommunityId(): string { if (SettingsStore.getValue("feature_communities_v2_prototypes")) { return GroupFilterOrderStore.getSelectedTags()[0]; @@ -134,7 +138,8 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient { } } else if (payload.action === "MatrixActions.accountData") { if (payload.event_type.startsWith("im.vector.group_info.")) { - this.emit(UPDATE_EVENT, payload.event_type.substring("im.vector.group_info.".length)); + const roomId = payload.event_type.substring("im.vector.group_info.".length); + this.emit(CommunityPrototypeStore.getUpdateEventName(roomId), roomId); } } else if (payload.action === "select_tag") { // Automatically select the general chat when switching communities @@ -167,7 +172,7 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient { if (getEffectiveMembership(myMember.membership) === EffectiveMembership.Invite) { // Fake an update for anything that might have started listening before the invite // data was available (eg: RoomPreviewBar after a refresh) - this.emit(UPDATE_EVENT, room.roomId); + this.emit(CommunityPrototypeStore.getUpdateEventName(room.roomId), room.roomId); } } } diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 2803f0a23e..38e56881cc 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -30,7 +30,7 @@ import { UPDATE_EVENT } from "../AsyncStore"; // Emitted event for when a room's preview has changed. First argument will the room for which // the change happened. -export const ROOM_PREVIEW_CHANGED = "room_preview_changed"; +const ROOM_PREVIEW_CHANGED = "room_preview_changed"; const PREVIEWS = { 'm.room.message': { @@ -84,6 +84,10 @@ export class MessagePreviewStore extends AsyncStoreWithClient { return MessagePreviewStore.internalInstance; } + public static getPreviewChangedEventName(room: Room): string { + return `${ROOM_PREVIEW_CHANGED}:${room?.roomId}`; + } + /** * Gets the pre-translated preview for a given room * @param room The room to get the preview for. @@ -150,7 +154,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { // We've muted the underlying Map, so just emit that we've changed. this.previews.set(room.roomId, map); this.emit(UPDATE_EVENT, this); - this.emit(ROOM_PREVIEW_CHANGED, room); + this.emit(MessagePreviewStore.getPreviewChangedEventName(room), room); } return; // we're done } @@ -158,7 +162,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { // At this point, we didn't generate a preview so clear it this.previews.set(room.roomId, new Map()); this.emit(UPDATE_EVENT, this); - this.emit(ROOM_PREVIEW_CHANGED, room); + this.emit(MessagePreviewStore.getPreviewChangedEventName(room), room); } protected async onAction(payload: ActionPayload) { diff --git a/src/stores/widgets/ElementWidgetActions.ts b/src/stores/widgets/ElementWidgetActions.ts index 76390086ab..de48746a74 100644 --- a/src/stores/widgets/ElementWidgetActions.ts +++ b/src/stores/widgets/ElementWidgetActions.ts @@ -20,9 +20,16 @@ export enum ElementWidgetActions { ClientReady = "im.vector.ready", HangupCall = "im.vector.hangup", OpenIntegrationManager = "integration_manager_open", + + /** + * @deprecated Use MSC2931 instead + */ ViewRoom = "io.element.view_room", } +/** + * @deprecated Use MSC2931 instead + */ export interface IViewRoomApiRequest extends IWidgetApiRequest { data: { room_id: string; // eslint-disable-line camelcase diff --git a/src/stores/widgets/ElementWidgetCapabilities.ts b/src/stores/widgets/ElementWidgetCapabilities.ts index 3f17d27909..e493d5618f 100644 --- a/src/stores/widgets/ElementWidgetCapabilities.ts +++ b/src/stores/widgets/ElementWidgetCapabilities.ts @@ -15,5 +15,8 @@ */ export enum ElementWidgetCapabilities { + /** + * @deprecated Use MSC2931 instead. + */ CanChangeViewedRoom = "io.element.view_room", } diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index 2d2d1fcbdb..8baea97fe0 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -43,6 +43,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event"; import { CHAT_EFFECTS } from "../../effects"; import { containsEmoji } from "../../effects/utils"; import dis from "../../dispatcher/dispatcher"; +import {tryTransformPermalinkToLocalHref} from "../../utils/permalinks/Permalinks"; // TODO: Purge this from the universe @@ -171,4 +172,12 @@ export class StopGapWidgetDriver extends WidgetDriver { }, }); } + + public async navigate(uri: string): Promise { + const localUri = tryTransformPermalinkToLocalHref(uri); + if (!localUri || localUri === uri) { // parse failure can lead to an unmodified URL + throw new Error("Failed to transform URI"); + } + window.location.hash = localUri; // it'll just be a fragment + } } diff --git a/src/widgets/CapabilityText.tsx b/src/widgets/CapabilityText.tsx index 834ea3ec37..273d22dc81 100644 --- a/src/widgets/CapabilityText.tsx +++ b/src/widgets/CapabilityText.tsx @@ -60,6 +60,9 @@ export class CapabilityText { [ElementWidgetCapabilities.CanChangeViewedRoom]: { [GENERIC_WIDGET_KIND]: _td("Change which room you're viewing"), }, + [MatrixCapabilities.MSC2931Navigate]: { + [GENERIC_WIDGET_KIND]: _td("Change which room, message, or user you're viewing"), + }, }; private static stateSendRecvCaps: ISendRecvStaticCapText = { diff --git a/test/components/views/messages/TextualBody-test.js b/test/components/views/messages/TextualBody-test.js index bf55e9c430..fc627538f0 100644 --- a/test/components/views/messages/TextualBody-test.js +++ b/test/components/views/messages/TextualBody-test.js @@ -222,6 +222,7 @@ describe("", () => { getRoom: () => mkStubRoom("room_id"), getAccountData: () => undefined, getUrlPreview: (url) => new Promise(() => {}), + isGuest: () => false, }; const ev = mkEvent({ diff --git a/yarn.lock b/yarn.lock index b0769c2c04..d8f4278ae0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1809,6 +1809,11 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/events@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + "@types/fbemitter@*": version "2.0.32" resolved "https://registry.yarnpkg.com/@types/fbemitter/-/fbemitter-2.0.32.tgz#8ed204da0f54e9c8eaec31b1eec91e25132d082c" @@ -6554,8 +6559,8 @@ mathml-tag-names@^2.0.1: integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "9.4.1" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/1717fcf499b943517213f2a81c41ffec0b50748e" + version "9.5.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/214a9df3823e602400b24d9a81cfc7b7df0a863b" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" @@ -6580,11 +6585,12 @@ matrix-react-test-utils@^0.2.2: resolved "https://registry.yarnpkg.com/matrix-react-test-utils/-/matrix-react-test-utils-0.2.2.tgz#c87144d3b910c7edc544a6699d13c7c2bf02f853" integrity sha512-49+7gfV6smvBIVbeloql+37IeWMTD+fiywalwCqk8Dnz53zAFjKSltB3rmWHso1uecLtQEcPtCijfhzcLXAxTQ== -matrix-widget-api@^0.1.0-beta.10: - version "0.1.0-beta.10" - resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.10.tgz#2e4d658d90ff3152c5567089b4ddd21fb44ec1dd" - integrity sha512-yX2UURjM1zVp7snPiOFcH9+FDBdHfAdt5HEAyDUHGJ7w/F2zOtcK/y0dMlZ1+XhxY7Wv0IBZH0US8X/ioJRX1A== +matrix-widget-api@0.1.0-beta.11: + version "0.1.0-beta.11" + resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.11.tgz#3658e244bf82eebea36e64475ebfce86b778b476" + integrity sha512-RxIghopRKTQdmYMJzZg/QR+wcPcKe9S1pZZ31zN/M1LKsvTLThBYdMcP1idKh7MkhpO9eCdCVjJOMJTrqxNzbQ== dependencies: + "@types/events" "^3.0.0" events "^3.2.0" mdast-util-compact@^1.0.0: