Merge branch 'develop' into expand-codeblock

This commit is contained in:
Šimon Brandner 2021-01-18 17:54:06 +01:00
commit 8a4af2f348
No known key found for this signature in database
GPG key ID: 9760693FDD98A790
23 changed files with 382 additions and 172 deletions

View file

@ -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 <input type="password"> 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)

View file

@ -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",

View file

@ -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;
}
}
}
}

View file

@ -140,7 +140,7 @@ class LoggedInView extends React.Component<IProps, IState> {
protected readonly _matrixClient: MatrixClient;
protected readonly _roomView: React.RefObject<any>;
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
protected readonly _compactLayoutWatcherRef: string;
protected compactLayoutWatcherRef: string;
protected resizer: Resizer;
constructor(props, context) {
@ -157,18 +157,6 @@ class LoggedInView extends React.Component<IProps, IState> {
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<IProps, IState> {
}
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<IProps, IState> {
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();
}

View file

@ -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<IProps> = ({
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.
});

View file

@ -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

View file

@ -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) {

View file

@ -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<IProps, IState> {
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<IProps, IState> {
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<IProps, IState> {
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) => {

View file

@ -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 />.", {}, {
user: <Pill
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(content.creator)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
});
}
const bot = _t("This bridge is managed by <user />.", {}, {
user: <Pill
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(this.props.ev.getSender())}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
});
let networkIcon;
if (protocol.avatar) {
const avatarUrl = getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(),
protocol.avatar, 64, 64, "crop",
);
networkIcon = <BaseAvatar className="protocol-icon"
width={48}
height={48}
resizeMethod='crop'
name={ protocolName }
idName={ protocolName }
url={ avatarUrl }
/>;
} else {
networkIcon = <div class="noProtocolIcon"></div>;
}
const id = this.props.ev.getId();
const metadataClassname = "metadata" + (this.state.visible ? " visible" : "");
return (<li key={id}>
<div className="column-icon">
{networkIcon}
</div>
<div className="column-data">
<h3>{protocolName}</h3>
<p className="workspace-channel-details">
<span>{_t("Workspace: %(networkName)s", {networkName})}</span>
<span className="channel">{_t("Channel: %(channelName)s", {channelName})}</span>
</p>
<p className={metadataClassname}>
{creator} {bot}
</p>
<AccessibleButton className="mx_showMore" kind="secondary" onClick={this._toggleVisible.bind(this)}>
{ this.state.visible ? _t("Show less") : _t("Show more") }
</AccessibleButton>
</div>
</li>);
}
}

View file

@ -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<IProps> {
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 = <li>{_t("This bridge was provisioned by <user />.", {}, {
user: () => <Pill
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(content.creator)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
})}</li>;
}
const bot = <li>{_t("This bridge is managed by <user />.", {}, {
user: () => <Pill
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(content.bridgebot)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
})}</li>;
let networkIcon;
if (protocol.avatar_url) {
const avatarUrl = getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(),
protocol.avatar_url, 64, 64, "crop",
);
networkIcon = <BaseAvatar className="protocol-icon"
width={48}
height={48}
resizeMethod='crop'
name={ protocolName }
idName={ protocolName }
url={ avatarUrl }
/>;
} else {
networkIcon = <div className="noProtocolIcon"></div>;
}
let networkItem = null;
if (network) {
const networkName = network.displayname || network.id;
let networkLink = <span>{networkName}</span>;
if (typeof network.external_url === "string" && isUrlPermitted(network.external_url)) {
networkLink = <a href={network.external_url} target="_blank" rel="noreferrer noopener">{networkName}</a>
}
networkItem = _t("Workspace: <networkLink/>", {}, {
networkLink: () => networkLink,
});
}
let channelLink = <span>{channelName}</span>;
if (typeof channel.external_url === "string" && isUrlPermitted(channel.external_url)) {
channelLink = <a href={channel.external_url} target="_blank" rel="noreferrer noopener">{channelName}</a>
}
const id = this.props.ev.getId();
return (<li key={id}>
<div className="column-icon">
{networkIcon}
</div>
<div className="column-data">
<h3>{protocolName}</h3>
<p className="workspace-channel-details">
{networkItem}
<span className="channel">{_t("Channel: <channelLink/>", {}, {
channelLink: () => channelLink,
})}</span>
</p>
<ul className="metadata">
{creator} {bot}
</ul>
</div>
</li>);
}
}

View file

@ -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 <user />.": "This bridge was provisioned by <user />.",
"This bridge is managed by <user />.": "This bridge is managed by <user />.",
"Workspace: %(networkName)s": "Workspace: %(networkName)s",
"Channel: %(channelName)s": "Channel: %(channelName)s",
"Show less": "Show less",
"Show more": "Show more",
"Workspace: <networkLink/>": "Workspace: <networkLink/>",
"Channel: <channelLink/>": "Channel: <channelLink/>",
"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",

View file

@ -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": {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -48,6 +48,10 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient<IState> {
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<IState> {
}
} 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<IState> {
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);
}
}
}

View file

@ -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<IState> {
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<IState> {
// 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<IState> {
// At this point, we didn't generate a preview so clear it
this.previews.set(room.roomId, new Map<TagID|TAG_ANY, string|null>());
this.emit(UPDATE_EVENT, this);
this.emit(ROOM_PREVIEW_CHANGED, room);
this.emit(MessagePreviewStore.getPreviewChangedEventName(room), room);
}
protected async onAction(payload: ActionPayload) {

View file

@ -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

View file

@ -15,5 +15,8 @@
*/
export enum ElementWidgetCapabilities {
/**
* @deprecated Use MSC2931 instead.
*/
CanChangeViewedRoom = "io.element.view_room",
}

View file

@ -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<void> {
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
}
}

View file

@ -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 = {

View file

@ -222,6 +222,7 @@ describe("<TextualBody />", () => {
getRoom: () => mkStubRoom("room_id"),
getAccountData: () => undefined,
getUrlPreview: (url) => new Promise(() => {}),
isGuest: () => false,
};
const ev = mkEvent({

View file

@ -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: