mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' into gsouquet/switch-rooms
commit
282b9f9e0f
110
CHANGELOG.md
110
CHANGELOG.md
|
@ -1,3 +1,113 @@
|
|||
Changes in [3.21.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.21.0) (2021-05-17)
|
||||
=====================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.21.0-rc.1...v3.21.0)
|
||||
|
||||
## Security notice
|
||||
|
||||
matrix-react-sdk 3.21.0 fixes a low severity issue (GHSA-8796-gc9j-63rv)
|
||||
related to file upload. When uploading a file, the local file preview can lead
|
||||
to execution of scripts embedded in the uploaded file, but only after several
|
||||
user interactions to open the preview in a separate tab. This only impacts the
|
||||
local user while in the process of uploading. It cannot be exploited remotely
|
||||
or by other users. Thanks to [Muhammad Zaid Ghifari](https://github.com/MR-ZHEEV)
|
||||
for responsibly disclosing this via Matrix's Security Disclosure Policy.
|
||||
|
||||
## All changes
|
||||
|
||||
* Upgrade to JS SDK 11.0.0
|
||||
* [Release] Add missing space on beta feedback dialog
|
||||
[\#6019](https://github.com/matrix-org/matrix-react-sdk/pull/6019)
|
||||
* [Release] Add feedback mechanism for beta features, namely Spaces
|
||||
[\#6013](https://github.com/matrix-org/matrix-react-sdk/pull/6013)
|
||||
* Add feedback mechanism for beta features, namely Spaces
|
||||
[\#6012](https://github.com/matrix-org/matrix-react-sdk/pull/6012)
|
||||
|
||||
Changes in [3.21.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.21.0-rc.1) (2021-05-11)
|
||||
===============================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.20.0...v3.21.0-rc.1)
|
||||
|
||||
* Upgrade to JS SDK 11.0.0-rc.1
|
||||
* Add disclaimer about subspaces being experimental in add existing dialog
|
||||
[\#5978](https://github.com/matrix-org/matrix-react-sdk/pull/5978)
|
||||
* Spaces Beta release
|
||||
[\#5933](https://github.com/matrix-org/matrix-react-sdk/pull/5933)
|
||||
* Improve permissions error when adding new server to room directory
|
||||
[\#6009](https://github.com/matrix-org/matrix-react-sdk/pull/6009)
|
||||
* Allow user to progress through space creation & setup using Enter
|
||||
[\#6006](https://github.com/matrix-org/matrix-react-sdk/pull/6006)
|
||||
* Upgrade sanitize types
|
||||
[\#6008](https://github.com/matrix-org/matrix-react-sdk/pull/6008)
|
||||
* Upgrade `cheerio` and resolve type errors
|
||||
[\#6007](https://github.com/matrix-org/matrix-react-sdk/pull/6007)
|
||||
* Add slash commands support to edit message composer
|
||||
[\#5865](https://github.com/matrix-org/matrix-react-sdk/pull/5865)
|
||||
* Fix the two todays problem
|
||||
[\#5940](https://github.com/matrix-org/matrix-react-sdk/pull/5940)
|
||||
* Switch the Home Space out for an All rooms space
|
||||
[\#5969](https://github.com/matrix-org/matrix-react-sdk/pull/5969)
|
||||
* Show device ID in UserInfo when there is no device name
|
||||
[\#5985](https://github.com/matrix-org/matrix-react-sdk/pull/5985)
|
||||
* Switch back to release version of `sanitize-html`
|
||||
[\#6005](https://github.com/matrix-org/matrix-react-sdk/pull/6005)
|
||||
* Bump hosted-git-info from 2.8.8 to 2.8.9
|
||||
[\#5998](https://github.com/matrix-org/matrix-react-sdk/pull/5998)
|
||||
* Don't use the event's metadata to calc the scale of an image
|
||||
[\#5982](https://github.com/matrix-org/matrix-react-sdk/pull/5982)
|
||||
* Adjust MIME type of upload confirmation if needed
|
||||
[\#5981](https://github.com/matrix-org/matrix-react-sdk/pull/5981)
|
||||
* Forbid redaction of encryption events
|
||||
[\#5991](https://github.com/matrix-org/matrix-react-sdk/pull/5991)
|
||||
* Fix voice message playback being squished up against send button
|
||||
[\#5988](https://github.com/matrix-org/matrix-react-sdk/pull/5988)
|
||||
* Improve style of notification badges on the space panel
|
||||
[\#5983](https://github.com/matrix-org/matrix-react-sdk/pull/5983)
|
||||
* Add dev dependency for parse5 typings
|
||||
[\#5990](https://github.com/matrix-org/matrix-react-sdk/pull/5990)
|
||||
* Iterate Spaces admin UX around room management
|
||||
[\#5977](https://github.com/matrix-org/matrix-react-sdk/pull/5977)
|
||||
* Guard all isSpaceRoom calls behind the labs flag
|
||||
[\#5979](https://github.com/matrix-org/matrix-react-sdk/pull/5979)
|
||||
* Bump lodash from 4.17.20 to 4.17.21
|
||||
[\#5986](https://github.com/matrix-org/matrix-react-sdk/pull/5986)
|
||||
* Bump lodash from 4.17.19 to 4.17.21 in /test/end-to-end-tests
|
||||
[\#5987](https://github.com/matrix-org/matrix-react-sdk/pull/5987)
|
||||
* Bump ua-parser-js from 0.7.23 to 0.7.28
|
||||
[\#5984](https://github.com/matrix-org/matrix-react-sdk/pull/5984)
|
||||
* Update visual style of plain files in the timeline
|
||||
[\#5971](https://github.com/matrix-org/matrix-react-sdk/pull/5971)
|
||||
* Support for multiple streams (not MSC3077)
|
||||
[\#5833](https://github.com/matrix-org/matrix-react-sdk/pull/5833)
|
||||
* Update space ordering behaviour to match updates in MSC
|
||||
[\#5963](https://github.com/matrix-org/matrix-react-sdk/pull/5963)
|
||||
* Improve performance of search all spaces and space switching
|
||||
[\#5976](https://github.com/matrix-org/matrix-react-sdk/pull/5976)
|
||||
* Update colours and sizing for voice messages
|
||||
[\#5970](https://github.com/matrix-org/matrix-react-sdk/pull/5970)
|
||||
* Update link to Android SDK
|
||||
[\#5973](https://github.com/matrix-org/matrix-react-sdk/pull/5973)
|
||||
* Add cleanup functions for image view
|
||||
[\#5962](https://github.com/matrix-org/matrix-react-sdk/pull/5962)
|
||||
* Add a note about sharing your IP in P2P calls
|
||||
[\#5961](https://github.com/matrix-org/matrix-react-sdk/pull/5961)
|
||||
* Only aggregate DM notifications on the Space Panel in the Home Space
|
||||
[\#5968](https://github.com/matrix-org/matrix-react-sdk/pull/5968)
|
||||
* Add retry mechanism and progress bar to add existing to space dialog
|
||||
[\#5975](https://github.com/matrix-org/matrix-react-sdk/pull/5975)
|
||||
* Warn on access token reveal
|
||||
[\#5755](https://github.com/matrix-org/matrix-react-sdk/pull/5755)
|
||||
* Fix newly joined room appearing under the wrong space
|
||||
[\#5945](https://github.com/matrix-org/matrix-react-sdk/pull/5945)
|
||||
* Early rendering for voice messages in the timeline
|
||||
[\#5955](https://github.com/matrix-org/matrix-react-sdk/pull/5955)
|
||||
* Calculate the real waveform in the Playback class for voice messages
|
||||
[\#5956](https://github.com/matrix-org/matrix-react-sdk/pull/5956)
|
||||
* Don't recurse on arrayFastResample
|
||||
[\#5957](https://github.com/matrix-org/matrix-react-sdk/pull/5957)
|
||||
* Support a dark theme for voice messages
|
||||
[\#5958](https://github.com/matrix-org/matrix-react-sdk/pull/5958)
|
||||
* Handle no/blocked microphones in voice messages
|
||||
[\#5959](https://github.com/matrix-org/matrix-react-sdk/pull/5959)
|
||||
|
||||
Changes in [3.20.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.20.0) (2021-05-10)
|
||||
=====================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.20.0-rc.1...v3.20.0)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "3.20.0",
|
||||
"version": "3.21.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
|
@ -80,7 +80,7 @@
|
|||
"linkifyjs": "^2.1.9",
|
||||
"lodash": "^4.17.20",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"matrix-widget-api": "^0.1.0-beta.13",
|
||||
"matrix-widget-api": "^0.1.0-beta.14",
|
||||
"minimist": "^1.2.5",
|
||||
"opus-recorder": "^8.0.3",
|
||||
"pako": "^2.0.3",
|
||||
|
|
|
@ -54,7 +54,8 @@ limitations under the License.
|
|||
display: flex;
|
||||
margin-top: 12px;
|
||||
|
||||
.mx_BaseAvatar {
|
||||
// we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling
|
||||
.mx_DecoratedRoomAvatar {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
|
@ -75,6 +76,10 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_AddExistingToSpace_section_spaces {
|
||||
.mx_BaseAvatar {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.mx_BaseAvatar_image {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
@ -105,6 +110,90 @@ limitations under the License.
|
|||
mask-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpace_footer {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
|
||||
> span {
|
||||
flex-grow: 1;
|
||||
font-size: $font-12px;
|
||||
line-height: $font-15px;
|
||||
color: $secondary-fg-color;
|
||||
|
||||
.mx_ProgressBar {
|
||||
height: 8px;
|
||||
width: 100%;
|
||||
|
||||
@mixin ProgressBarBorderRadius 8px;
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpace_progressText {
|
||||
margin-top: 8px;
|
||||
font-size: $font-15px;
|
||||
line-height: $font-24px;
|
||||
color: $primary-fg-color;
|
||||
}
|
||||
|
||||
> * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpace_error {
|
||||
padding-left: 12px;
|
||||
|
||||
> img {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpace_errorHeading {
|
||||
font-weight: $font-semi-bold;
|
||||
font-size: $font-15px;
|
||||
line-height: $font-18px;
|
||||
color: $notice-primary-color;
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpace_errorCaption {
|
||||
margin-top: 4px;
|
||||
font-size: $font-12px;
|
||||
line-height: $font-15px;
|
||||
color: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AccessibleButton {
|
||||
display: inline-block;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_primary {
|
||||
padding: 8px 36px;
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpace_retryButton {
|
||||
margin-left: 12px;
|
||||
padding-left: 24px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color: $primary-fg-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-image: url('$(res)/img/element-icons/retry.svg');
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpaceDialog {
|
||||
|
@ -189,88 +278,4 @@ limitations under the License.
|
|||
.mx_AddExistingToSpace {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpaceDialog_footer {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
|
||||
> span {
|
||||
flex-grow: 1;
|
||||
font-size: $font-12px;
|
||||
line-height: $font-15px;
|
||||
color: $secondary-fg-color;
|
||||
|
||||
.mx_ProgressBar {
|
||||
height: 8px;
|
||||
width: 100%;
|
||||
|
||||
@mixin ProgressBarBorderRadius 8px;
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpaceDialog_progressText {
|
||||
margin-top: 8px;
|
||||
font-size: $font-15px;
|
||||
line-height: $font-24px;
|
||||
color: $primary-fg-color;
|
||||
}
|
||||
|
||||
> * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpaceDialog_error {
|
||||
padding-left: 12px;
|
||||
|
||||
> img {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpaceDialog_errorHeading {
|
||||
font-weight: $font-semi-bold;
|
||||
font-size: $font-15px;
|
||||
line-height: $font-18px;
|
||||
color: $notice-primary-color;
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpaceDialog_errorCaption {
|
||||
margin-top: 4px;
|
||||
font-size: $font-12px;
|
||||
line-height: $font-15px;
|
||||
color: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AccessibleButton {
|
||||
display: inline-block;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_primary {
|
||||
padding: 8px 36px;
|
||||
}
|
||||
|
||||
.mx_AddExistingToSpaceDialog_retryButton {
|
||||
margin-left: 12px;
|
||||
padding-left: 24px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color: $primary-fg-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-image: url('$(res)/img/element-icons/retry.svg');
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ limitations under the License.
|
|||
|
||||
.mx_ReactionsRow_addReactionButton {
|
||||
position: relative;
|
||||
display: none; // show on hover of the .mx_EventTile
|
||||
display: inline-block;
|
||||
visibility: hidden; // show on hover of the .mx_EventTile
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
vertical-align: middle;
|
||||
|
@ -39,7 +40,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
&.mx_ReactionsRow_addReactionButton_active {
|
||||
display: inline-block; // keep showing whilst the context menu is shown
|
||||
visibility: visible; // keep showing whilst the context menu is shown
|
||||
}
|
||||
|
||||
&:hover, &.mx_ReactionsRow_addReactionButton_active {
|
||||
|
@ -51,7 +52,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_EventTile:hover .mx_ReactionsRow_addReactionButton {
|
||||
display: inline-block;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.mx_ReactionsRow_showAll {
|
||||
|
|
|
@ -98,7 +98,7 @@ limitations under the License.
|
|||
position: relative;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 32px;
|
||||
border-radius: 8px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
|
@ -114,6 +114,11 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist_auxButton:hover,
|
||||
.mx_RoomSublist_menuButton:hover {
|
||||
background: $roomlist-button-bg-color;
|
||||
}
|
||||
|
||||
// Hide the menu button by default
|
||||
.mx_RoomSublist_menuButton {
|
||||
visibility: hidden;
|
||||
|
|
|
@ -40,6 +40,8 @@ export function eventTriggersUnreadCount(ev) {
|
|||
return false;
|
||||
} else if (ev.getType() == 'm.room.server_acl') {
|
||||
return false;
|
||||
} else if (ev.isRedacted()) {
|
||||
return false;
|
||||
}
|
||||
return haveTileForEvent(ev);
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
|
|||
const onKeyDownHandler = useCallback((ev) => {
|
||||
let handled = false;
|
||||
// Don't interfere with input default keydown behaviour
|
||||
if (handleHomeEnd && ev.target.tagName !== "INPUT") {
|
||||
if (handleHomeEnd && ev.target.tagName !== "INPUT" && ev.target.tagName !== "TEXTAREA") {
|
||||
// check if we actually have any items
|
||||
switch (ev.key) {
|
||||
case Key.HOME:
|
||||
|
|
|
@ -26,6 +26,8 @@ import EmojiProvider from './EmojiProvider';
|
|||
import NotifProvider from './NotifProvider';
|
||||
import {timeout} from "../utils/promise";
|
||||
import AutocompleteProvider, {ICommand} from "./AutocompleteProvider";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import SpaceProvider from "./SpaceProvider";
|
||||
|
||||
export interface ISelectionRange {
|
||||
beginning?: boolean; // whether the selection is in the first block of the editor or not
|
||||
|
@ -56,6 +58,11 @@ const PROVIDERS = [
|
|||
DuckDuckGoProvider,
|
||||
];
|
||||
|
||||
// as the spaces feature is device configurable only, and toggling it refreshes the page, we can do this here
|
||||
if (SettingsStore.getValue("feature_spaces")) {
|
||||
PROVIDERS.push(SpaceProvider);
|
||||
}
|
||||
|
||||
// Providers will get rejected if they take longer than this.
|
||||
const PROVIDER_COMPLETION_TIMEOUT = 3000;
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2017, 2018, 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,17 +16,19 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import {uniqBy, sortBy} from "lodash";
|
||||
import Room from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import {MatrixClientPeg} from '../MatrixClientPeg';
|
||||
import QueryMatcher from './QueryMatcher';
|
||||
import {PillCompletion} from './Components';
|
||||
import * as sdk from '../index';
|
||||
import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
|
||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
||||
import {uniqBy, sortBy} from "lodash";
|
||||
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
|
||||
const ROOM_REGEX = /\B#\S*/g;
|
||||
|
||||
|
@ -49,7 +50,7 @@ function matcherObject(room: Room, displayedAlias: string, matchName = "") {
|
|||
}
|
||||
|
||||
export default class RoomProvider extends AutocompleteProvider {
|
||||
matcher: QueryMatcher<Room>;
|
||||
protected matcher: QueryMatcher<Room>;
|
||||
|
||||
constructor() {
|
||||
super(ROOM_REGEX);
|
||||
|
@ -58,20 +59,28 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
});
|
||||
}
|
||||
|
||||
protected getRooms() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
let rooms = cli.getVisibleRooms();
|
||||
|
||||
if (SettingsStore.getValue("feature_spaces")) {
|
||||
rooms = rooms.filter(r => !r.isSpaceRoom());
|
||||
}
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
async getCompletions(
|
||||
query: string,
|
||||
selection: ISelectionRange,
|
||||
force = false,
|
||||
limit = -1,
|
||||
): Promise<ICompletion[]> {
|
||||
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
let completions = [];
|
||||
const {command, range} = this.getCurrentCommand(query, selection, force);
|
||||
if (command) {
|
||||
// the only reason we need to do this is because Fuse only matches on properties
|
||||
let matcherObjects = client.getVisibleRooms().reduce((aliases, room) => {
|
||||
let matcherObjects = this.getRooms().reduce((aliases, room) => {
|
||||
if (room.getCanonicalAlias()) {
|
||||
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias(), room.name));
|
||||
}
|
||||
|
@ -115,7 +124,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
),
|
||||
range,
|
||||
};
|
||||
}).filter((completion) => !!completion.completion && completion.completion.length > 0).slice(0, 4);
|
||||
}).filter((completion) => !!completion.completion && completion.completion.length > 0);
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { _t } from '../languageHandler';
|
||||
import {MatrixClientPeg} from '../MatrixClientPeg';
|
||||
import RoomProvider from "./RoomProvider";
|
||||
|
||||
export default class SpaceProvider extends RoomProvider {
|
||||
protected getRooms() {
|
||||
return MatrixClientPeg.get().getVisibleRooms().filter(r => r.isSpaceRoom());
|
||||
}
|
||||
|
||||
getName() {
|
||||
return _t("Spaces");
|
||||
}
|
||||
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
return (
|
||||
<div
|
||||
className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate"
|
||||
role="listbox"
|
||||
aria-label={_t("Space Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -222,10 +222,12 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||
// don't let keyboard handling escape the context menu
|
||||
ev.stopPropagation();
|
||||
|
||||
if (!this.props.managed) {
|
||||
if (ev.key === Key.ESCAPE) {
|
||||
this.props.onFinished();
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
return;
|
||||
|
@ -258,7 +260,6 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
|||
|
||||
if (handled) {
|
||||
// consume all other keys in context menu
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -50,6 +50,9 @@ class FilePanel extends React.Component {
|
|||
if (room?.roomId !== this.props?.roomId) return;
|
||||
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
client.decryptEventIfNeeded(ev);
|
||||
|
||||
if (ev.isBeingDecrypted()) {
|
||||
this.decryptingEvents.add(ev.getId());
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,7 @@ import CallMediaHandler from '../../CallMediaHandler';
|
|||
import { fixupColorFonts } from '../../utils/FontManager';
|
||||
import * as sdk from '../../index';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import {MatrixClientPeg, IMatrixClientCreds} from '../../MatrixClientPeg';
|
||||
import { IMatrixClientCreds } from '../../MatrixClientPeg';
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
|
||||
import TagOrderActions from '../../actions/TagOrderActions';
|
||||
|
@ -219,16 +219,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
// Child components assume that the client peg will not be null, so give them some
|
||||
// sort of assurance here by only allowing a re-render if the client is truthy.
|
||||
//
|
||||
// This is required because `LoggedInView` maintains its own state and if this state
|
||||
// updates after the client peg has been made null (during logout), then it will
|
||||
// attempt to re-render and the children will throw errors.
|
||||
shouldComponentUpdate() {
|
||||
return Boolean(MatrixClientPeg.get());
|
||||
}
|
||||
|
||||
canResetTimelineInRoom = (roomId) => {
|
||||
if (!this._roomView.current) {
|
||||
return true;
|
||||
|
|
|
@ -473,7 +473,7 @@ export default class MessagePanel extends React.Component {
|
|||
}
|
||||
|
||||
get _roomHasPendingEdit() {
|
||||
return localStorage.getItem(`mx_edit_room_${this.props.room.roomId}`);
|
||||
return this.props.room && localStorage.getItem(`mx_edit_room_${this.props.room.roomId}`);
|
||||
}
|
||||
|
||||
_getEventTiles() {
|
||||
|
|
|
@ -811,7 +811,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onEvent = (ev) => {
|
||||
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return;
|
||||
if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || ev.shouldAttemptDecryption()) return;
|
||||
this.handleEffects(ev);
|
||||
};
|
||||
|
||||
|
|
|
@ -52,8 +52,6 @@ import {useStateToggle} from "../../hooks/useStateToggle";
|
|||
import SpaceStore from "../../stores/SpaceStore";
|
||||
import FacePile from "../views/elements/FacePile";
|
||||
import {AddExistingToSpace} from "../views/dialogs/AddExistingToSpaceDialog";
|
||||
import {sleep} from "../../utils/promise";
|
||||
import {calculateRoomVia} from "../../utils/permalinks/Permalinks";
|
||||
import {ChevronFace, ContextMenuButton, useContextMenu} from "./ContextMenu";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuOption,
|
||||
|
@ -78,6 +76,7 @@ interface IProps {
|
|||
|
||||
interface IState {
|
||||
phase: Phase;
|
||||
createdRooms?: boolean; // internal state for the creation wizard
|
||||
showRightPanel: boolean;
|
||||
myMembership: string;
|
||||
}
|
||||
|
@ -461,7 +460,8 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
|||
setError("");
|
||||
setBusy(true);
|
||||
try {
|
||||
await Promise.all(roomNames.map(name => name.trim()).filter(Boolean).map(name => {
|
||||
const filteredRoomNames = roomNames.map(name => name.trim()).filter(Boolean);
|
||||
await Promise.all(filteredRoomNames.map(name => {
|
||||
return createRoom({
|
||||
createOpts: {
|
||||
preset: space.getJoinRule() === "public" ? Preset.PublicChat : Preset.PrivateChat,
|
||||
|
@ -474,7 +474,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
|||
parentSpace: space,
|
||||
});
|
||||
}));
|
||||
onFinished();
|
||||
onFinished(filteredRoomNames.length > 0);
|
||||
} catch (e) {
|
||||
console.error("Failed to create initial space rooms", e);
|
||||
setError(_t("Failed to create initial space rooms"));
|
||||
|
@ -484,7 +484,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
|||
|
||||
let onClick = (ev) => {
|
||||
ev.preventDefault();
|
||||
onFinished();
|
||||
onFinished(false);
|
||||
};
|
||||
let buttonLabel = _t("Skip for now");
|
||||
if (roomNames.some(name => name.trim())) {
|
||||
|
@ -517,39 +517,6 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
|||
};
|
||||
|
||||
const SpaceAddExistingRooms = ({ space, onFinished }) => {
|
||||
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
|
||||
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
let onClick = onFinished;
|
||||
let buttonLabel = _t("Skip for now");
|
||||
if (selectedToAdd.size > 0) {
|
||||
onClick = async () => {
|
||||
setBusy(true);
|
||||
|
||||
for (const room of selectedToAdd) {
|
||||
const via = calculateRoomVia(room);
|
||||
try {
|
||||
await SpaceStore.instance.addRoomToSpace(space, room.roomId, via).catch(async e => {
|
||||
if (e.errcode === "M_LIMIT_EXCEEDED") {
|
||||
await sleep(e.data.retry_after_ms);
|
||||
return SpaceStore.instance.addRoomToSpace(space, room.roomId, via); // retry
|
||||
}
|
||||
|
||||
throw e;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to add rooms to space", e);
|
||||
setError(_t("Failed to add rooms to space"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
buttonLabel = busy ? _t("Adding...") : _t("Add");
|
||||
}
|
||||
|
||||
return <div>
|
||||
<h1>{ _t("What do you want to organise?") }</h1>
|
||||
<div className="mx_SpaceRoomView_description">
|
||||
|
@ -557,35 +524,24 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
|
|||
"no one will be informed. You can add more later.") }
|
||||
</div>
|
||||
|
||||
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||
|
||||
<AddExistingToSpace
|
||||
space={space}
|
||||
selected={selectedToAdd}
|
||||
onChange={(checked, room) => {
|
||||
if (checked) {
|
||||
selectedToAdd.add(room);
|
||||
} else {
|
||||
selectedToAdd.delete(room);
|
||||
}
|
||||
setSelectedToAdd(new Set(selectedToAdd));
|
||||
}}
|
||||
emptySelectionButton={
|
||||
<AccessibleButton kind="primary" onClick={onFinished}>
|
||||
{ _t("Skip for now") }
|
||||
</AccessibleButton>
|
||||
}
|
||||
onFinished={onFinished}
|
||||
/>
|
||||
|
||||
<div className="mx_SpaceRoomView_buttons">
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
disabled={busy}
|
||||
onClick={onClick}
|
||||
>
|
||||
{ buttonLabel }
|
||||
</AccessibleButton>
|
||||
|
||||
</div>
|
||||
<SpaceFeedbackPrompt />
|
||||
</div>;
|
||||
};
|
||||
|
||||
const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished }) => {
|
||||
const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, createdRooms }) => {
|
||||
return <div className="mx_SpaceRoomView_publicShare">
|
||||
<h1>{ _t("Share %(name)s", {
|
||||
name: justCreatedOpts?.createOpts?.name || space.name,
|
||||
|
@ -598,7 +554,7 @@ const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished }) => {
|
|||
|
||||
<div className="mx_SpaceRoomView_buttons">
|
||||
<AccessibleButton kind="primary" onClick={onFinished}>
|
||||
{ _t("Go to my first room") }
|
||||
{ createdRooms ? _t("Go to my first room") : _t("Go to my space") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<SpaceFeedbackPrompt />
|
||||
|
@ -891,13 +847,14 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
|||
_t("Let's create a room for each of them.") + "\n" +
|
||||
_t("You can add more later too, including already existing ones.")
|
||||
}
|
||||
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
||||
onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.PublicShare, createdRooms })}
|
||||
/>;
|
||||
case Phase.PublicShare:
|
||||
return <SpaceSetupPublicShare
|
||||
justCreatedOpts={this.props.justCreatedOpts}
|
||||
space={this.props.space}
|
||||
onFinished={this.goToFirstRoom}
|
||||
createdRooms={this.state.createdRooms}
|
||||
/>;
|
||||
|
||||
case Phase.PrivateScope:
|
||||
|
@ -919,7 +876,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
|||
title={_t("What projects are you working on?")}
|
||||
description={_t("We'll create rooms for each of them. " +
|
||||
"You can add more later too, including already existing ones.")}
|
||||
onFinished={() => this.setState({ phase: Phase.Landing })}
|
||||
onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.Landing, createdRooms })}
|
||||
/>;
|
||||
case Phase.PrivateExistingRooms:
|
||||
return <SpaceAddExistingRooms
|
||||
|
|
|
@ -1149,9 +1149,8 @@ class TimelinePanel extends React.Component {
|
|||
arrayFastClone(events)
|
||||
.reverse()
|
||||
.forEach(event => {
|
||||
if (event.shouldAttemptDecryption()) {
|
||||
event.attemptDecryption(MatrixClientPeg.get()._crypto);
|
||||
}
|
||||
const client = MatrixClientPeg.get();
|
||||
client.decryptEventIfNeeded(event);
|
||||
});
|
||||
|
||||
const firstVisibleEventIndex = this._checkForPreJoinUISI(events);
|
||||
|
|
|
@ -20,7 +20,6 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
|||
import { User } from "matrix-js-sdk/src/models/user";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import { TagID } from '../../../stores/room-list/models';
|
||||
import RoomAvatar from "./RoomAvatar";
|
||||
import NotificationBadge from '../rooms/NotificationBadge';
|
||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||
|
@ -35,7 +34,6 @@ import {replaceableComponent} from "../../../utils/replaceableComponent";
|
|||
interface IProps {
|
||||
room: Room;
|
||||
avatarSize: number;
|
||||
tag: TagID;
|
||||
displayBadge?: boolean;
|
||||
forceCount?: boolean;
|
||||
oobData?: object;
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {useContext, useMemo, useState} from "react";
|
||||
import React, {ReactNode, useContext, useMemo, useState} from "react";
|
||||
import classNames from "classnames";
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||
|
@ -37,6 +37,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
|||
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||
import ProgressBar from "../elements/ProgressBar";
|
||||
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
|
||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
matrixClient: MatrixClient;
|
||||
|
@ -46,7 +47,10 @@ interface IProps extends IDialogProps {
|
|||
|
||||
const Entry = ({ room, checked, onChange }) => {
|
||||
return <label className="mx_AddExistingToSpace_entry">
|
||||
<RoomAvatar room={room} height={32} width={32} />
|
||||
{ room?.isSpaceRoom()
|
||||
? <RoomAvatar room={room} height={32} width={32} />
|
||||
: <DecoratedRoomAvatar room={room} avatarSize={32} />
|
||||
}
|
||||
<span className="mx_AddExistingToSpace_entry_name">{ room.name }</span>
|
||||
<StyledCheckbox
|
||||
onChange={onChange ? (e) => onChange(e.target.checked) : null}
|
||||
|
@ -58,14 +62,23 @@ const Entry = ({ room, checked, onChange }) => {
|
|||
|
||||
interface IAddExistingToSpaceProps {
|
||||
space: Room;
|
||||
selected: Set<Room>;
|
||||
onChange(checked: boolean, room: Room): void;
|
||||
footerPrompt?: ReactNode;
|
||||
emptySelectionButton?: ReactNode;
|
||||
onFinished(added: boolean): void;
|
||||
}
|
||||
|
||||
export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space, selected, onChange }) => {
|
||||
export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||
space,
|
||||
footerPrompt,
|
||||
emptySelectionButton,
|
||||
onFinished,
|
||||
}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const visibleRooms = useMemo(() => sortRooms(cli.getVisibleRooms()), [cli]);
|
||||
|
||||
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
|
||||
const [progress, setProgress] = useState<number>(null);
|
||||
const [error, setError] = useState<Error>(null);
|
||||
const [query, setQuery] = useState("");
|
||||
const lcQuery = query.toLowerCase();
|
||||
|
||||
|
@ -93,120 +106,6 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
|
|||
return arr;
|
||||
}, [[], [], []]);
|
||||
|
||||
return <div className="mx_AddExistingToSpace">
|
||||
<SearchBox
|
||||
className="mx_textinput_icon mx_textinput_search"
|
||||
placeholder={ _t("Filter your rooms and spaces") }
|
||||
onSearch={setQuery}
|
||||
autoComplete={true}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<AutoHideScrollbar className="mx_AddExistingToSpace_content" id="mx_AddExistingToSpace">
|
||||
{ rooms.length > 0 ? (
|
||||
<div className="mx_AddExistingToSpace_section">
|
||||
<h3>{ _t("Rooms") }</h3>
|
||||
{ rooms.map(room => {
|
||||
return <Entry
|
||||
key={room.roomId}
|
||||
room={room}
|
||||
checked={selected.has(room)}
|
||||
onChange={onChange ? (checked) => {
|
||||
onChange(checked, room);
|
||||
} : null}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : undefined }
|
||||
|
||||
{ spaces.length > 0 ? (
|
||||
<div className="mx_AddExistingToSpace_section mx_AddExistingToSpace_section_spaces">
|
||||
<h3>{ _t("Spaces") }</h3>
|
||||
<div className="mx_AddExistingToSpace_section_experimental">
|
||||
<div>{ _t("Feeling experimental?") }</div>
|
||||
<div>{ _t("You can add existing spaces to a space.") }</div>
|
||||
</div>
|
||||
{ spaces.map(space => {
|
||||
return <Entry
|
||||
key={space.roomId}
|
||||
room={space}
|
||||
checked={selected.has(space)}
|
||||
onChange={onChange ? (checked) => {
|
||||
onChange(checked, space);
|
||||
} : null}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
{ dms.length > 0 ? (
|
||||
<div className="mx_AddExistingToSpace_section">
|
||||
<h3>{ _t("Direct Messages") }</h3>
|
||||
{ dms.map(room => {
|
||||
return <Entry
|
||||
key={room.roomId}
|
||||
room={room}
|
||||
checked={selected.has(room)}
|
||||
onChange={onChange ? (checked) => {
|
||||
onChange(checked, room);
|
||||
} : null}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
{ spaces.length + rooms.length + dms.length < 1 ? <span className="mx_AddExistingToSpace_noResults">
|
||||
{ _t("No results") }
|
||||
</span> : undefined }
|
||||
</AutoHideScrollbar>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => {
|
||||
const [selectedSpace, setSelectedSpace] = useState(space);
|
||||
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
|
||||
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
|
||||
|
||||
const [progress, setProgress] = useState<number>(null);
|
||||
const [error, setError] = useState<Error>(null);
|
||||
|
||||
let spaceOptionSection;
|
||||
if (existingSubspaces.length > 0) {
|
||||
const options = [space, ...existingSubspaces].map((space) => {
|
||||
const classes = classNames("mx_AddExistingToSpaceDialog_dropdownOption", {
|
||||
mx_AddExistingToSpaceDialog_dropdownOptionActive: space === selectedSpace,
|
||||
});
|
||||
return <div key={space.roomId} className={classes}>
|
||||
<RoomAvatar room={space} width={24} height={24} />
|
||||
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
|
||||
</div>;
|
||||
});
|
||||
|
||||
spaceOptionSection = (
|
||||
<Dropdown
|
||||
id="mx_SpaceSelectDropdown"
|
||||
onOptionChange={(key: string) => {
|
||||
setSelectedSpace(existingSubspaces.find(space => space.roomId === key) || space);
|
||||
}}
|
||||
value={selectedSpace.roomId}
|
||||
label={_t("Space selection")}
|
||||
>
|
||||
{ options }
|
||||
</Dropdown>
|
||||
);
|
||||
} else {
|
||||
spaceOptionSection = <div className="mx_AddExistingToSpaceDialog_onlySpace">
|
||||
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
|
||||
</div>;
|
||||
}
|
||||
|
||||
const title = <React.Fragment>
|
||||
<RoomAvatar room={selectedSpace} height={40} width={40} />
|
||||
<div>
|
||||
<h1>{ _t("Add existing rooms") }</h1>
|
||||
{ spaceOptionSection }
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
|
||||
const addRooms = async () => {
|
||||
setError(null);
|
||||
setProgress(0);
|
||||
|
@ -269,20 +168,145 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
|||
</div>
|
||||
</span>;
|
||||
} else {
|
||||
let button = emptySelectionButton;
|
||||
if (!button || selectedToAdd.size > 0) {
|
||||
button = <AccessibleButton kind="primary" disabled={selectedToAdd.size < 1} onClick={addRooms}>
|
||||
{ _t("Add") }
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
footer = <>
|
||||
<span>
|
||||
<div>{ _t("Want to add a new room instead?") }</div>
|
||||
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
|
||||
{ _t("Create a new room") }
|
||||
</AccessibleButton>
|
||||
{ footerPrompt }
|
||||
</span>
|
||||
|
||||
<AccessibleButton kind="primary" disabled={selectedToAdd.size < 1} onClick={addRooms}>
|
||||
{ _t("Add") }
|
||||
</AccessibleButton>
|
||||
{ button }
|
||||
</>;
|
||||
}
|
||||
|
||||
const onChange = !busy && !error ? (checked, room) => {
|
||||
if (checked) {
|
||||
selectedToAdd.add(room);
|
||||
} else {
|
||||
selectedToAdd.delete(room);
|
||||
}
|
||||
setSelectedToAdd(new Set(selectedToAdd));
|
||||
} : null;
|
||||
|
||||
return <div className="mx_AddExistingToSpace">
|
||||
<SearchBox
|
||||
className="mx_textinput_icon mx_textinput_search"
|
||||
placeholder={ _t("Filter your rooms and spaces") }
|
||||
onSearch={setQuery}
|
||||
autoComplete={true}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<AutoHideScrollbar className="mx_AddExistingToSpace_content" id="mx_AddExistingToSpace">
|
||||
{ rooms.length > 0 ? (
|
||||
<div className="mx_AddExistingToSpace_section">
|
||||
<h3>{ _t("Rooms") }</h3>
|
||||
{ rooms.map(room => {
|
||||
return <Entry
|
||||
key={room.roomId}
|
||||
room={room}
|
||||
checked={selectedToAdd.has(room)}
|
||||
onChange={onChange ? (checked) => {
|
||||
onChange(checked, room);
|
||||
} : null}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : undefined }
|
||||
|
||||
{ spaces.length > 0 ? (
|
||||
<div className="mx_AddExistingToSpace_section mx_AddExistingToSpace_section_spaces">
|
||||
<h3>{ _t("Spaces") }</h3>
|
||||
<div className="mx_AddExistingToSpace_section_experimental">
|
||||
<div>{ _t("Feeling experimental?") }</div>
|
||||
<div>{ _t("You can add existing spaces to a space.") }</div>
|
||||
</div>
|
||||
{ spaces.map(space => {
|
||||
return <Entry
|
||||
key={space.roomId}
|
||||
room={space}
|
||||
checked={selectedToAdd.has(space)}
|
||||
onChange={onChange ? (checked) => {
|
||||
onChange(checked, space);
|
||||
} : null}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
{ dms.length > 0 ? (
|
||||
<div className="mx_AddExistingToSpace_section">
|
||||
<h3>{ _t("Direct Messages") }</h3>
|
||||
{ dms.map(room => {
|
||||
return <Entry
|
||||
key={room.roomId}
|
||||
room={room}
|
||||
checked={selectedToAdd.has(room)}
|
||||
onChange={onChange ? (checked) => {
|
||||
onChange(checked, room);
|
||||
} : null}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
{ spaces.length + rooms.length + dms.length < 1 ? <span className="mx_AddExistingToSpace_noResults">
|
||||
{ _t("No results") }
|
||||
</span> : undefined }
|
||||
</AutoHideScrollbar>
|
||||
|
||||
<div className="mx_AddExistingToSpace_footer">
|
||||
{ footer }
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => {
|
||||
const [selectedSpace, setSelectedSpace] = useState(space);
|
||||
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
|
||||
|
||||
let spaceOptionSection;
|
||||
if (existingSubspaces.length > 0) {
|
||||
const options = [space, ...existingSubspaces].map((space) => {
|
||||
const classes = classNames("mx_AddExistingToSpaceDialog_dropdownOption", {
|
||||
mx_AddExistingToSpaceDialog_dropdownOptionActive: space === selectedSpace,
|
||||
});
|
||||
return <div key={space.roomId} className={classes}>
|
||||
<RoomAvatar room={space} width={24} height={24} />
|
||||
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
|
||||
</div>;
|
||||
});
|
||||
|
||||
spaceOptionSection = (
|
||||
<Dropdown
|
||||
id="mx_SpaceSelectDropdown"
|
||||
onOptionChange={(key: string) => {
|
||||
setSelectedSpace(existingSubspaces.find(space => space.roomId === key) || space);
|
||||
}}
|
||||
value={selectedSpace.roomId}
|
||||
label={_t("Space selection")}
|
||||
>
|
||||
{ options }
|
||||
</Dropdown>
|
||||
);
|
||||
} else {
|
||||
spaceOptionSection = <div className="mx_AddExistingToSpaceDialog_onlySpace">
|
||||
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
|
||||
</div>;
|
||||
}
|
||||
|
||||
const title = <React.Fragment>
|
||||
<RoomAvatar room={selectedSpace} height={40} width={40} />
|
||||
<div>
|
||||
<h1>{ _t("Add existing rooms") }</h1>
|
||||
{ spaceOptionSection }
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
|
||||
return <BaseDialog
|
||||
title={title}
|
||||
className="mx_AddExistingToSpaceDialog"
|
||||
|
@ -293,21 +317,16 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
|||
<MatrixClientContext.Provider value={cli}>
|
||||
<AddExistingToSpace
|
||||
space={space}
|
||||
selected={selectedToAdd}
|
||||
onChange={!busy && !error ? (checked, room) => {
|
||||
if (checked) {
|
||||
selectedToAdd.add(room);
|
||||
} else {
|
||||
selectedToAdd.delete(room);
|
||||
}
|
||||
setSelectedToAdd(new Set(selectedToAdd));
|
||||
} : null}
|
||||
onFinished={onFinished}
|
||||
footerPrompt={<>
|
||||
<div>{ _t("Want to add a new room instead?") }</div>
|
||||
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
|
||||
{ _t("Create a new room") }
|
||||
</AccessibleButton>
|
||||
</>}
|
||||
/>
|
||||
</MatrixClientContext.Provider>
|
||||
|
||||
<div className="mx_AddExistingToSpaceDialog_footer">
|
||||
{ footer }
|
||||
</div>
|
||||
<SpaceFeedbackPrompt onClick={() => onFinished(false)} />
|
||||
</BaseDialog>;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import * as React from 'react';
|
||||
import BaseDialog from './BaseDialog';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { _t, getUserLanguage } from '../../../languageHandler';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import {
|
||||
ClientWidgetApi,
|
||||
|
@ -39,6 +39,8 @@ import {OwnProfileStore} from "../../../stores/OwnProfileStore";
|
|||
import { arrayFastClone } from "../../../utils/arrays";
|
||||
import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import {ELEMENT_CLIENT_ID} from "../../../identifiers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
interface IProps {
|
||||
widgetDefinition: IModalWidgetOpenRequestData;
|
||||
|
@ -129,6 +131,9 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
|||
currentUserId: MatrixClientPeg.get().getUserId(),
|
||||
userDisplayName: OwnProfileStore.instance.displayName,
|
||||
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(),
|
||||
clientId: ELEMENT_CLIENT_ID,
|
||||
clientTheme: SettingsStore.getValue("theme"),
|
||||
clientLanguage: getUserLanguage(),
|
||||
});
|
||||
|
||||
const parsed = new URL(templated);
|
||||
|
|
|
@ -217,6 +217,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
|||
value={this.state.otherHomeserver}
|
||||
validateOnChange={false}
|
||||
validateOnFocus={false}
|
||||
id="mx_homeserverInput"
|
||||
/>
|
||||
</StyledRadioButton>
|
||||
<p>
|
||||
|
|
|
@ -345,6 +345,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this.onPassPhraseNext}>
|
||||
<input
|
||||
type="password"
|
||||
id="mx_passPhraseInput"
|
||||
className="mx_AccessSecretStorageDialog_passPhraseInput"
|
||||
onChange={this.onPassPhraseChange}
|
||||
value={this.state.passPhrase}
|
||||
|
|
|
@ -37,7 +37,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
|
|||
effect = new Effect(options);
|
||||
effectsRef.current[name] = effect;
|
||||
} catch (err) {
|
||||
console.warn('Unable to load effect module at \'../../../effects/${name}\'.', err);
|
||||
console.warn(`Unable to load effect module at '../../../effects/${name}.`, err);
|
||||
}
|
||||
}
|
||||
return effect;
|
||||
|
|
|
@ -207,6 +207,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
a.href = this.props.src;
|
||||
a.download = this.props.name;
|
||||
a.target = "_blank";
|
||||
a.rel = "noreferrer noopener";
|
||||
a.click();
|
||||
};
|
||||
|
||||
|
|
|
@ -58,13 +58,8 @@ export default class LanguageDropdown extends React.Component {
|
|||
// If no value is given, we start with the first
|
||||
// country selected, but our parent component
|
||||
// doesn't know this, therefore we do this.
|
||||
const language = SettingsStore.getValue("language", null, /*excludeDefault:*/true);
|
||||
if (language) {
|
||||
this.props.onOptionChange(language);
|
||||
} else {
|
||||
const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser());
|
||||
this.props.onOptionChange(language);
|
||||
}
|
||||
const language = languageHandler.getUserLanguage();
|
||||
this.props.onOptionChange(language);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import {RovingAccessibleTooltipButton, useRovingTabIndex} from "../../../accessi
|
|||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import {canCancel} from "../context_menus/MessageContextMenu";
|
||||
import Resend from "../../../Resend";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
||||
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||
|
@ -122,6 +123,10 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) {
|
||||
this.props.mxEvent.on("Event.status", this.onSent);
|
||||
}
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
client.decryptEventIfNeeded(this.props.mxEvent);
|
||||
|
||||
if (this.props.mxEvent.isBeingDecrypted()) {
|
||||
this.props.mxEvent.once("Event.decrypted", this.onDecrypted);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,10 @@ const ReactButton = ({ mxEvent, reactions }: IProps) => {
|
|||
})}
|
||||
title={_t("Add reaction")}
|
||||
onClick={openMenu}
|
||||
onContextMenu={e => {
|
||||
e.preventDefault();
|
||||
openMenu();
|
||||
}}
|
||||
isExpanded={menuDisplayed}
|
||||
inputRef={button}
|
||||
/>
|
||||
|
@ -174,6 +178,8 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
|||
/>;
|
||||
}).filter(item => !!item);
|
||||
|
||||
if (!items.length) return null;
|
||||
|
||||
// Show the first MAX_ITEMS if there are MAX_ITEMS + 1 or more items.
|
||||
// The "+ 1" ensure that the "show all" reveals something that takes up
|
||||
// more space than the button itself.
|
||||
|
|
|
@ -18,6 +18,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
||||
@replaceableComponent("views.messages.ViewSourceEvent")
|
||||
export default class ViewSourceEvent extends React.PureComponent {
|
||||
|
@ -36,6 +37,10 @@ export default class ViewSourceEvent extends React.PureComponent {
|
|||
|
||||
componentDidMount() {
|
||||
const {mxEvent} = this.props;
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
client.decryptEventIfNeeded(mxEvent);
|
||||
|
||||
if (mxEvent.isBeingDecrypted()) {
|
||||
mxEvent.once("Event.decrypted", () => this.forceUpdate());
|
||||
}
|
||||
|
|
|
@ -23,11 +23,9 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
|
|||
import Analytics from "../../../Analytics";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||
import { DefaultTagID } from "../../../stores/room-list/models";
|
||||
import { RovingAccessibleTooltipButton } from "../../../accessibility/RovingTabIndex";
|
||||
import Toolbar from "../../../accessibility/Toolbar";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
}
|
||||
|
@ -84,8 +82,6 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
|
|||
|
||||
public render(): React.ReactElement {
|
||||
const tiles = BreadcrumbsStore.instance.rooms.map((r, i) => {
|
||||
const roomTags = RoomListStore.instance.getTagsForRoom(r);
|
||||
const roomTag = roomTags.includes(DefaultTagID.DM) ? DefaultTagID.DM : roomTags[0];
|
||||
return (
|
||||
<RovingAccessibleTooltipButton
|
||||
className="mx_RoomBreadcrumbs_crumb"
|
||||
|
@ -98,7 +94,6 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
|
|||
<DecoratedRoomAvatar
|
||||
room={r}
|
||||
avatarSize={32}
|
||||
tag={roomTag}
|
||||
displayBadge={true}
|
||||
forceCount={true}
|
||||
/>
|
||||
|
|
|
@ -27,7 +27,6 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||
import E2EIcon from './E2EIcon';
|
||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
import {DefaultTagID} from "../../../stores/room-list/models";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import RoomTopic from "../elements/RoomTopic";
|
||||
import RoomName from "../elements/RoomName";
|
||||
|
@ -177,7 +176,6 @@ export default class RoomHeader extends React.Component {
|
|||
roomAvatar = <DecoratedRoomAvatar
|
||||
room={this.props.room}
|
||||
avatarSize={32}
|
||||
tag={DefaultTagID.Untagged} // to apply room publicity badging
|
||||
oobData={this.props.oobData}
|
||||
viewAvatarOnClick={true}
|
||||
/>;
|
||||
|
|
|
@ -576,7 +576,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
const roomAvatar = <DecoratedRoomAvatar
|
||||
room={this.props.room}
|
||||
avatarSize={32}
|
||||
tag={this.props.tag}
|
||||
displayBadge={this.props.isMinimized}
|
||||
oobData={({avatarUrl: roomProfile.avatarMxc})}
|
||||
/>;
|
||||
|
|
|
@ -23,6 +23,7 @@ import {copyPlaintext} from "../../../utils/strings";
|
|||
import {sleep} from "../../../utils/promise";
|
||||
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
||||
import {showRoomInviteDialog} from "../../../RoomInvite";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
|
||||
interface IProps {
|
||||
space: Room;
|
||||
|
@ -50,7 +51,7 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
|
|||
<h3>{ _t("Share invite link") }</h3>
|
||||
<span>{ copiedText }</span>
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
{ space.canInvite(MatrixClientPeg.get()?.getUserId()) ? <AccessibleButton
|
||||
className="mx_SpacePublicShare_inviteButton"
|
||||
onClick={() => {
|
||||
showRoomInviteDialog(space.roomId);
|
||||
|
@ -59,7 +60,7 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
|
|||
>
|
||||
<h3>{ _t("Invite people") }</h3>
|
||||
<span>{ _t("Invite with email or username") }</span>
|
||||
</AccessibleButton>
|
||||
</AccessibleButton> : null }
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
|
|
@ -209,7 +209,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
|||
const userId = this.context.getUserId();
|
||||
|
||||
let inviteOption;
|
||||
if (this.props.space.canInvite(userId)) {
|
||||
if (this.props.space.getJoinRule() === "public" || this.props.space.canInvite(userId)) {
|
||||
inviteOption = (
|
||||
<IconizedContextMenuOption
|
||||
className="mx_SpacePanel_contextMenu_inviteButton"
|
||||
|
|
|
@ -57,8 +57,8 @@ export default class PlaybackWaveform extends React.PureComponent<IProps, IState
|
|||
};
|
||||
|
||||
private onTimeUpdate = (time: number[]) => {
|
||||
// Track percentages to very coarse precision, otherwise 0.002 ends up highlighting a bar.
|
||||
const progress = Number(percentageOf(time[0], 0, time[1]).toFixed(1));
|
||||
// Track percentages to a general precision to avoid over-waking the component.
|
||||
const progress = Number(percentageOf(time[0], 0, time[1]).toFixed(3));
|
||||
this.setState({progress});
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
Copyright 2020 Nurjin Jafar
|
||||
Copyright 2020 Nordeck IT + Consulting GmbH.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export type Effect<TOptions extends { [key: string]: any }> = {
|
||||
/**
|
||||
* one or more emojis that will trigger this effect
|
||||
*/
|
||||
emojis: Array<string>;
|
||||
/**
|
||||
* the matrix message type that will trigger this effect
|
||||
*/
|
||||
msgType: string;
|
||||
/**
|
||||
* the room command to trigger this effect
|
||||
*/
|
||||
command: string;
|
||||
/**
|
||||
* a function that returns the translated description of the effect
|
||||
*/
|
||||
description: () => string;
|
||||
/**
|
||||
* a function that returns the translated fallback message. this message will be shown if the user did not provide a custom message
|
||||
*/
|
||||
fallbackMessage: () => string;
|
||||
/**
|
||||
* animation options
|
||||
*/
|
||||
options: TOptions;
|
||||
}
|
|
@ -15,80 +15,11 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
import { _t, _td } from "../languageHandler";
|
||||
|
||||
export type Effect<TOptions extends { [key: string]: any }> = {
|
||||
/**
|
||||
* one or more emojis that will trigger this effect
|
||||
*/
|
||||
emojis: Array<string>;
|
||||
/**
|
||||
* the matrix message type that will trigger this effect
|
||||
*/
|
||||
msgType: string;
|
||||
/**
|
||||
* the room command to trigger this effect
|
||||
*/
|
||||
command: string;
|
||||
/**
|
||||
* a function that returns the translated description of the effect
|
||||
*/
|
||||
description: () => string;
|
||||
/**
|
||||
* a function that returns the translated fallback message. this message will be shown if the user did not provide a custom message
|
||||
*/
|
||||
fallbackMessage: () => string;
|
||||
/**
|
||||
* animation options
|
||||
*/
|
||||
options: TOptions;
|
||||
}
|
||||
|
||||
type ConfettiOptions = {
|
||||
/**
|
||||
* max confetti count
|
||||
*/
|
||||
maxCount: number;
|
||||
/**
|
||||
* particle animation speed
|
||||
*/
|
||||
speed: number;
|
||||
/**
|
||||
* the confetti animation frame interval in milliseconds
|
||||
*/
|
||||
frameInterval: number;
|
||||
/**
|
||||
* the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible)
|
||||
*/
|
||||
alpha: number;
|
||||
/**
|
||||
* use gradient instead of solid particle color
|
||||
*/
|
||||
gradient: boolean;
|
||||
};
|
||||
type FireworksOptions = {
|
||||
/**
|
||||
* max fireworks count
|
||||
*/
|
||||
maxCount: number;
|
||||
/**
|
||||
* gravity value that firework adds to shift from it's start position
|
||||
*/
|
||||
gravity: number;
|
||||
}
|
||||
type SnowfallOptions = {
|
||||
/**
|
||||
* The maximum number of snowflakes to render at a given time
|
||||
*/
|
||||
maxCount: number;
|
||||
/**
|
||||
* The amount of gravity to apply to the snowflakes
|
||||
*/
|
||||
gravity: number;
|
||||
/**
|
||||
* The amount of drift (horizontal sway) to apply to the snowflakes. Each snowflake varies.
|
||||
*/
|
||||
maxDrift: number;
|
||||
}
|
||||
import { ConfettiOptions } from "./confetti";
|
||||
import { Effect } from "./effect";
|
||||
import { FireworksOptions } from "./fireworks";
|
||||
import { SnowfallOptions } from "./snowfall";
|
||||
import { SpaceInvadersOptions } from "./spaceinvaders";
|
||||
|
||||
/**
|
||||
* This configuration defines room effects that can be triggered by custom message types and emojis
|
||||
|
@ -131,6 +62,17 @@ export const CHAT_EFFECTS: Array<Effect<{ [key: string]: any }>> = [
|
|||
maxDrift: 5,
|
||||
},
|
||||
} as Effect<SnowfallOptions>,
|
||||
{
|
||||
emojis: ["👾", "🌌"],
|
||||
msgType: "io.element.effects.space_invaders",
|
||||
command: "spaceinvaders",
|
||||
description: () => _td("Sends the given message with a space themed effect"),
|
||||
fallbackMessage: () => _t("sends space invaders") + " 👾",
|
||||
options: {
|
||||
maxCount: 50,
|
||||
gravity: 0.01,
|
||||
},
|
||||
} as Effect<SpaceInvadersOptions>,
|
||||
];
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import ICanvasEffect from '../ICanvasEffect';
|
||||
import { arrayFastClone } from "../../utils/arrays";
|
||||
|
||||
export type SpaceInvadersOptions = {
|
||||
/**
|
||||
* The maximum number of invaders to render at a given time
|
||||
*/
|
||||
maxCount: number;
|
||||
/**
|
||||
* The amount of gravity to apply to the invaders
|
||||
*/
|
||||
gravity: number;
|
||||
}
|
||||
|
||||
type Invader = {
|
||||
x: number;
|
||||
y: number;
|
||||
xCol: number;
|
||||
gravity: number;
|
||||
}
|
||||
|
||||
export const DefaultOptions: SpaceInvadersOptions = {
|
||||
maxCount: 50,
|
||||
gravity: 0.005,
|
||||
};
|
||||
|
||||
const KEY_FRAME_INTERVAL = 15; // 15ms, roughly
|
||||
const GLYPH = "👾";
|
||||
|
||||
export default class SpaceInvaders implements ICanvasEffect {
|
||||
private readonly options: SpaceInvadersOptions;
|
||||
|
||||
constructor(options: { [key: string]: any }) {
|
||||
this.options = {...DefaultOptions, ...options};
|
||||
}
|
||||
|
||||
private context: CanvasRenderingContext2D | null = null;
|
||||
private particles: Array<Invader> = [];
|
||||
private lastAnimationTime: number;
|
||||
|
||||
public isRunning: boolean;
|
||||
|
||||
public start = async (canvas: HTMLCanvasElement, timeout = 3000) => {
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
this.context = canvas.getContext('2d');
|
||||
this.particles = [];
|
||||
const count = this.options.maxCount;
|
||||
while (this.particles.length < count) {
|
||||
this.particles.push(this.resetParticle({} as Invader, canvas.width, canvas.height));
|
||||
}
|
||||
this.isRunning = true;
|
||||
requestAnimationFrame(this.renderLoop);
|
||||
if (timeout) {
|
||||
window.setTimeout(this.stop, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
public stop = async () => {
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
private resetParticle = (particle: Invader, width: number, height: number): Invader => {
|
||||
particle.x = Math.random() * width;
|
||||
particle.y = Math.random() * -height;
|
||||
particle.xCol = particle.x;
|
||||
particle.gravity = this.options.gravity + (Math.random() * 6) + 4;
|
||||
return particle;
|
||||
}
|
||||
|
||||
private renderLoop = (): void => {
|
||||
if (!this.context || !this.context.canvas) {
|
||||
return;
|
||||
}
|
||||
if (this.particles.length === 0) {
|
||||
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
||||
} else {
|
||||
const timeDelta = Date.now() - this.lastAnimationTime;
|
||||
if (timeDelta >= KEY_FRAME_INTERVAL || !this.lastAnimationTime) {
|
||||
// Clear the screen first
|
||||
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
||||
|
||||
this.lastAnimationTime = Date.now();
|
||||
this.animateAndRenderInvaders();
|
||||
}
|
||||
requestAnimationFrame(this.renderLoop);
|
||||
}
|
||||
};
|
||||
|
||||
private animateAndRenderInvaders() {
|
||||
if (!this.context || !this.context.canvas) {
|
||||
return;
|
||||
}
|
||||
this.context.font = "50px Twemoji";
|
||||
for (const particle of arrayFastClone(this.particles)) {
|
||||
particle.y += particle.gravity;
|
||||
|
||||
this.context.save();
|
||||
this.context.fillText(GLYPH, particle.x, particle.y);
|
||||
this.context.restore();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -600,6 +600,10 @@
|
|||
"See when the avatar changes in this room": "See when the avatar changes in this room",
|
||||
"Change the avatar of your active room": "Change the avatar of your active room",
|
||||
"See when the avatar changes in your active room": "See when the avatar changes in your active room",
|
||||
"Kick, ban, or invite people to this room, and make you leave": "Kick, ban, or invite people to this room, and make you leave",
|
||||
"See when people join, leave, or are invited to this room": "See when people join, leave, or are invited to this room",
|
||||
"Kick, ban, or invite people to your active room, and make you leave": "Kick, ban, or invite people to your active room, and make you leave",
|
||||
"See when people join, leave, or are invited to your active room": "See when people join, leave, or are invited to your active room",
|
||||
"Send stickers to this room as you": "Send stickers to this room as you",
|
||||
"See when a sticker is posted in this room": "See when a sticker is posted in this room",
|
||||
"Send stickers to your active room as you": "Send stickers to your active room as you",
|
||||
|
@ -880,6 +884,8 @@
|
|||
"sends fireworks": "sends fireworks",
|
||||
"Sends the given message with snowfall": "Sends the given message with snowfall",
|
||||
"sends snowfall": "sends snowfall",
|
||||
"Sends the given message with a space themed effect": "Sends the given message with a space themed effect",
|
||||
"sends space invaders": "sends space invaders",
|
||||
"unknown person": "unknown person",
|
||||
"Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>": "Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>",
|
||||
"You held the call <a>Switch</a>": "You held the call <a>Switch</a>",
|
||||
|
@ -2033,15 +2039,15 @@
|
|||
"Add a new server...": "Add a new server...",
|
||||
"%(networkName)s rooms": "%(networkName)s rooms",
|
||||
"Matrix rooms": "Matrix rooms",
|
||||
"Not all selected were added": "Not all selected were added",
|
||||
"Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)",
|
||||
"Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...",
|
||||
"Filter your rooms and spaces": "Filter your rooms and spaces",
|
||||
"Feeling experimental?": "Feeling experimental?",
|
||||
"You can add existing spaces to a space.": "You can add existing spaces to a space.",
|
||||
"Direct Messages": "Direct Messages",
|
||||
"Space selection": "Space selection",
|
||||
"Add existing rooms": "Add existing rooms",
|
||||
"Not all selected were added": "Not all selected were added",
|
||||
"Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)",
|
||||
"Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...",
|
||||
"Want to add a new room instead?": "Want to add a new room instead?",
|
||||
"Create a new room": "Create a new room",
|
||||
"Matrix ID": "Matrix ID",
|
||||
|
@ -2704,13 +2710,12 @@
|
|||
"Failed to create initial space rooms": "Failed to create initial space rooms",
|
||||
"Skip for now": "Skip for now",
|
||||
"Creating rooms...": "Creating rooms...",
|
||||
"Failed to add rooms to space": "Failed to add rooms to space",
|
||||
"Adding...": "Adding...",
|
||||
"What do you want to organise?": "What do you want to organise?",
|
||||
"Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.",
|
||||
"Share %(name)s": "Share %(name)s",
|
||||
"It's just you at the moment, it will be even better with others.": "It's just you at the moment, it will be even better with others.",
|
||||
"Go to my first room": "Go to my first room",
|
||||
"Go to my space": "Go to my space",
|
||||
"Who are you working with?": "Who are you working with?",
|
||||
"Make sure the right people have access to %(name)s": "Make sure the right people have access to %(name)s",
|
||||
"Just me": "Just me",
|
||||
|
@ -2834,6 +2839,7 @@
|
|||
"Room Notification": "Room Notification",
|
||||
"Notification Autocomplete": "Notification Autocomplete",
|
||||
"Room Autocomplete": "Room Autocomplete",
|
||||
"Space Autocomplete": "Space Autocomplete",
|
||||
"Users": "Users",
|
||||
"User Autocomplete": "User Autocomplete",
|
||||
"We'll store an encrypted copy of your keys on our server. Secure your backup with a Security Phrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a Security Phrase.",
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const ELEMENT_CLIENT_ID = "io.element.web";
|
|
@ -177,8 +177,10 @@ export default class EventIndex extends EventEmitter {
|
|||
* listener.
|
||||
*/
|
||||
onRoomTimeline = async (ev, room, toStartOfTimeline, removed, data) => {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
// We only index encrypted rooms locally.
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
|
||||
if (!client.isRoomEncrypted(room.roomId)) return;
|
||||
|
||||
// If it isn't a live event or if it's redacted there's nothing to
|
||||
// do.
|
||||
|
@ -187,10 +189,7 @@ export default class EventIndex extends EventEmitter {
|
|||
return;
|
||||
}
|
||||
|
||||
if (ev.isBeingDecrypted()) {
|
||||
// XXX: Private member access
|
||||
await ev._decryptionPromise;
|
||||
}
|
||||
await client.decryptEventIfNeeded(ev);
|
||||
|
||||
await this.addLiveEventToIndex(ev);
|
||||
}
|
||||
|
@ -518,19 +517,10 @@ export default class EventIndex extends EventEmitter {
|
|||
const decryptionPromises = matrixEvents
|
||||
.filter(event => event.isEncrypted())
|
||||
.map(event => {
|
||||
if (event.shouldAttemptDecryption()) {
|
||||
return event.attemptDecryption(client._crypto, {
|
||||
isRetry: true,
|
||||
emit: false,
|
||||
});
|
||||
} else {
|
||||
// TODO the decryption promise is a private property, this
|
||||
// should either be made public or we should convert the
|
||||
// event that gets fired when decryption is done into a
|
||||
// promise using the once event emitter method:
|
||||
// https://nodejs.org/api/events.html#events_events_once_emitter_name
|
||||
return event._decryptionPromise;
|
||||
}
|
||||
return client.decryptEventIfNeeded(event, {
|
||||
isRetry: true,
|
||||
emit: false,
|
||||
});
|
||||
});
|
||||
|
||||
// Let us wait for all the events to get decrypted.
|
||||
|
|
|
@ -56,6 +56,15 @@ export function newTranslatableError(message: string) {
|
|||
return error;
|
||||
}
|
||||
|
||||
export function getUserLanguage(): string {
|
||||
const language = SettingsStore.getValue("language", null, /*excludeDefault:*/true);
|
||||
if (language) {
|
||||
return language;
|
||||
} else {
|
||||
return normalizeLanguageKey(getLanguageFromBrowser());
|
||||
}
|
||||
}
|
||||
|
||||
// Function which only purpose is to mark that a string is translatable
|
||||
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
|
||||
export function _td(s: string): string {
|
||||
|
@ -455,10 +464,14 @@ function getLangsJson(): Promise<object> {
|
|||
request(
|
||||
{ method: "GET", url },
|
||||
(err, response, body) => {
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
reject(new Error(`Failed to load ${url}, got ${response.status}`));
|
||||
return;
|
||||
}
|
||||
resolve(JSON.parse(body));
|
||||
},
|
||||
);
|
||||
|
@ -498,10 +511,14 @@ function getLanguage(langPath: string): Promise<object> {
|
|||
request(
|
||||
{ method: "GET", url: langPath },
|
||||
(err, response, body) => {
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
reject(new Error(`Failed to load ${langPath}, got ${response.status}`));
|
||||
return;
|
||||
}
|
||||
resolve(weblateToCounterpart(JSON.parse(body)));
|
||||
},
|
||||
);
|
||||
|
|
|
@ -62,7 +62,7 @@ export const getOrder = (order: string, creationTs: number, roomId: string): Arr
|
|||
|
||||
if (typeof order === "string" && Array.from(order).every((c: string) => {
|
||||
const charCode = c.charCodeAt(0);
|
||||
return charCode >= 0x20 && charCode <= 0x7F;
|
||||
return charCode >= 0x20 && charCode <= 0x7E;
|
||||
})) {
|
||||
validatedOrder = order;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
|
|||
import { getListAlgorithmInstance } from "./list-ordering";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { VisibilityProvider } from "../filters/VisibilityProvider";
|
||||
import { MultiLock } from "../../../utils/MultiLock";
|
||||
|
||||
/**
|
||||
* Fired when the Algorithm has determined a list has been updated.
|
||||
|
@ -77,6 +78,7 @@ export class Algorithm extends EventEmitter {
|
|||
} = {};
|
||||
private allowedByFilter: Map<IFilterCondition, Room[]> = new Map<IFilterCondition, Room[]>();
|
||||
private allowedRoomsByFilters: Set<Room> = new Set<Room>();
|
||||
private handlerLock = new MultiLock();
|
||||
|
||||
/**
|
||||
* Set to true to suspend emissions of algorithm updates.
|
||||
|
@ -679,191 +681,204 @@ export class Algorithm extends EventEmitter {
|
|||
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`);
|
||||
console.log(`Acquiring lock for ${room.roomId} with cause ${cause}`);
|
||||
}
|
||||
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
|
||||
|
||||
// Note: check the isSticky against the room ID just in case the reference is wrong
|
||||
const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId;
|
||||
if (cause === RoomUpdateCause.NewRoom) {
|
||||
const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room;
|
||||
const roomTags = this.roomIdsToTags[room.roomId];
|
||||
const hasTags = roomTags && roomTags.length > 0;
|
||||
|
||||
// Don't change the cause if the last sticky room is being re-added. If we fail to
|
||||
// pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus
|
||||
// lose the room.
|
||||
if (hasTags && !isForLastSticky) {
|
||||
console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`);
|
||||
cause = RoomUpdateCause.PossibleTagChange;
|
||||
const release = await this.handlerLock.acquire(room.roomId);
|
||||
try {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`);
|
||||
}
|
||||
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
|
||||
|
||||
// Check to see if the room is known first
|
||||
let knownRoomRef = this.rooms.includes(room);
|
||||
if (hasTags && !knownRoomRef) {
|
||||
console.warn(`${room.roomId} might be a reference change - attempting to update reference`);
|
||||
this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r);
|
||||
knownRoomRef = this.rooms.includes(room);
|
||||
if (!knownRoomRef) {
|
||||
console.warn(`${room.roomId} is still not referenced. It may be sticky.`);
|
||||
// Note: check the isSticky against the room ID just in case the reference is wrong
|
||||
const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId;
|
||||
if (cause === RoomUpdateCause.NewRoom) {
|
||||
const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room;
|
||||
const roomTags = this.roomIdsToTags[room.roomId];
|
||||
const hasTags = roomTags && roomTags.length > 0;
|
||||
|
||||
// Don't change the cause if the last sticky room is being re-added. If we fail to
|
||||
// pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus
|
||||
// lose the room.
|
||||
if (hasTags && !isForLastSticky) {
|
||||
console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`);
|
||||
cause = RoomUpdateCause.PossibleTagChange;
|
||||
}
|
||||
|
||||
// Check to see if the room is known first
|
||||
let knownRoomRef = this.rooms.includes(room);
|
||||
if (hasTags && !knownRoomRef) {
|
||||
console.warn(`${room.roomId} might be a reference change - attempting to update reference`);
|
||||
this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r);
|
||||
knownRoomRef = this.rooms.includes(room);
|
||||
if (!knownRoomRef) {
|
||||
console.warn(`${room.roomId} is still not referenced. It may be sticky.`);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have tags for a room and don't have the room referenced, something went horribly
|
||||
// wrong - the reference should have been updated above.
|
||||
if (hasTags && !knownRoomRef && !isSticky) {
|
||||
throw new Error(`${room.roomId} is missing from room array but is known`);
|
||||
}
|
||||
|
||||
// Like above, update the reference to the sticky room if we need to
|
||||
if (hasTags && isSticky) {
|
||||
// Go directly in and set the sticky room's new reference, being careful not
|
||||
// to trigger a sticky room update ourselves.
|
||||
this._stickyRoom.room = room;
|
||||
}
|
||||
|
||||
// If after all that we're still a NewRoom update, add the room if applicable.
|
||||
// We don't do this for the sticky room (because it causes duplication issues)
|
||||
// or if we know about the reference (as it should be replaced).
|
||||
if (cause === RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) {
|
||||
this.rooms.push(room);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have tags for a room and don't have the room referenced, something went horribly
|
||||
// wrong - the reference should have been updated above.
|
||||
if (hasTags && !knownRoomRef && !isSticky) {
|
||||
throw new Error(`${room.roomId} is missing from room array but is known - trying to find duplicate`);
|
||||
}
|
||||
let didTagChange = false;
|
||||
if (cause === RoomUpdateCause.PossibleTagChange) {
|
||||
const oldTags = this.roomIdsToTags[room.roomId] || [];
|
||||
const newTags = this.getTagsForRoom(room);
|
||||
const diff = arrayDiff(oldTags, newTags);
|
||||
if (diff.removed.length > 0 || diff.added.length > 0) {
|
||||
for (const rmTag of diff.removed) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Removing ${room.roomId} from ${rmTag}`);
|
||||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||
this._cachedRooms[rmTag] = algorithm.orderedRooms;
|
||||
this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed
|
||||
}
|
||||
for (const addTag of diff.added) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Adding ${room.roomId} to ${addTag}`);
|
||||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
|
||||
this._cachedRooms[addTag] = algorithm.orderedRooms;
|
||||
}
|
||||
|
||||
// Like above, update the reference to the sticky room if we need to
|
||||
if (hasTags && isSticky) {
|
||||
// Go directly in and set the sticky room's new reference, being careful not
|
||||
// to trigger a sticky room update ourselves.
|
||||
this._stickyRoom.room = room;
|
||||
}
|
||||
// Update the tag map so we don't regen it in a moment
|
||||
this.roomIdsToTags[room.roomId] = newTags;
|
||||
|
||||
// If after all that we're still a NewRoom update, add the room if applicable.
|
||||
// We don't do this for the sticky room (because it causes duplication issues)
|
||||
// or if we know about the reference (as it should be replaced).
|
||||
if (cause === RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) {
|
||||
this.rooms.push(room);
|
||||
}
|
||||
}
|
||||
|
||||
let didTagChange = false;
|
||||
if (cause === RoomUpdateCause.PossibleTagChange) {
|
||||
const oldTags = this.roomIdsToTags[room.roomId] || [];
|
||||
const newTags = this.getTagsForRoom(room);
|
||||
const diff = arrayDiff(oldTags, newTags);
|
||||
if (diff.removed.length > 0 || diff.added.length > 0) {
|
||||
for (const rmTag of diff.removed) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Removing ${room.roomId} from ${rmTag}`);
|
||||
console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`);
|
||||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||
this._cachedRooms[rmTag] = algorithm.orderedRooms;
|
||||
this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed
|
||||
}
|
||||
for (const addTag of diff.added) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Adding ${room.roomId} to ${addTag}`);
|
||||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
|
||||
this._cachedRooms[addTag] = algorithm.orderedRooms;
|
||||
}
|
||||
|
||||
// Update the tag map so we don't regen it in a moment
|
||||
this.roomIdsToTags[room.roomId] = newTags;
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`);
|
||||
}
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
didTagChange = true;
|
||||
} else {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`);
|
||||
}
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
}
|
||||
|
||||
if (didTagChange && isSticky) {
|
||||
// Manually update the tag for the sticky room without triggering a sticky room
|
||||
// update. The update will be handled implicitly by the sticky room handling and
|
||||
// requires no changes on our part, if we're in the middle of a sticky room change.
|
||||
if (this._lastStickyRoom) {
|
||||
this._stickyRoom = {
|
||||
room,
|
||||
tag: this.roomIdsToTags[room.roomId][0],
|
||||
position: 0, // right at the top as it changed tags
|
||||
};
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
didTagChange = true;
|
||||
} else {
|
||||
// We have to clear the lock as the sticky room change will trigger updates.
|
||||
await this.setStickyRoom(room);
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`);
|
||||
}
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
}
|
||||
|
||||
if (didTagChange && isSticky) {
|
||||
// Manually update the tag for the sticky room without triggering a sticky room
|
||||
// update. The update will be handled implicitly by the sticky room handling and
|
||||
// requires no changes on our part, if we're in the middle of a sticky room change.
|
||||
if (this._lastStickyRoom) {
|
||||
this._stickyRoom = {
|
||||
room,
|
||||
tag: this.roomIdsToTags[room.roomId][0],
|
||||
position: 0, // right at the top as it changed tags
|
||||
};
|
||||
} else {
|
||||
// We have to clear the lock as the sticky room change will trigger updates.
|
||||
await this.setStickyRoom(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the update is for a room change which might be the sticky room, prevent it. We
|
||||
// need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though
|
||||
// as the sticky room relies on this.
|
||||
if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) {
|
||||
if (this.stickyRoom === room) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`);
|
||||
// If the update is for a room change which might be the sticky room, prevent it. We
|
||||
// need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though
|
||||
// as the sticky room relies on this.
|
||||
if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) {
|
||||
if (this.stickyRoom === room) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.warn(
|
||||
`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.roomIdsToTags[room.roomId]) {
|
||||
if (CAUSES_REQUIRING_ROOM.includes(cause)) {
|
||||
if (!this.roomIdsToTags[room.roomId]) {
|
||||
if (CAUSES_REQUIRING_ROOM.includes(cause)) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`);
|
||||
console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`);
|
||||
}
|
||||
|
||||
// Get the tags for the room and populate the cache
|
||||
const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t]));
|
||||
|
||||
// "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(),
|
||||
// which means we should *always* have a tag to go off of.
|
||||
if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`);
|
||||
|
||||
this.roomIdsToTags[room.roomId] = roomTags;
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`);
|
||||
console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`);
|
||||
}
|
||||
|
||||
// Get the tags for the room and populate the cache
|
||||
const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t]));
|
||||
const tags = this.roomIdsToTags[room.roomId];
|
||||
if (!tags) {
|
||||
console.warn(`No tags known for "${room.name}" (${room.roomId})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(),
|
||||
// which means we should *always* have a tag to go off of.
|
||||
if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`);
|
||||
let changed = didTagChange;
|
||||
for (const tag of tags) {
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[tag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
|
||||
|
||||
this.roomIdsToTags[room.roomId] = roomTags;
|
||||
await algorithm.handleRoomUpdate(room, cause);
|
||||
this._cachedRooms[tag] = algorithm.orderedRooms;
|
||||
|
||||
// Flag that we've done something
|
||||
this.recalculateFilteredRoomsForTag(tag); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags);
|
||||
console.log(
|
||||
`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`,
|
||||
);
|
||||
}
|
||||
return changed;
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`);
|
||||
}
|
||||
|
||||
const tags = this.roomIdsToTags[room.roomId];
|
||||
if (!tags) {
|
||||
console.warn(`No tags known for "${room.name}" (${room.roomId})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
let changed = didTagChange;
|
||||
for (const tag of tags) {
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[tag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
|
||||
|
||||
await algorithm.handleRoomUpdate(room, cause);
|
||||
this._cachedRooms[tag] = algorithm.orderedRooms;
|
||||
|
||||
// Flag that we've done something
|
||||
this.recalculateFilteredRoomsForTag(tag); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
* Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -52,6 +52,8 @@ import {getCustomTheme} from "../../theme";
|
|||
import CountlyAnalytics from "../../CountlyAnalytics";
|
||||
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { ELEMENT_CLIENT_ID } from "../../identifiers";
|
||||
import { getUserLanguage } from "../../languageHandler";
|
||||
|
||||
// TODO: Destroy all of this code
|
||||
|
||||
|
@ -194,6 +196,9 @@ export class StopGapWidget extends EventEmitter {
|
|||
currentUserId: MatrixClientPeg.get().getUserId(),
|
||||
userDisplayName: OwnProfileStore.instance.displayName,
|
||||
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(),
|
||||
clientId: ELEMENT_CLIENT_ID,
|
||||
clientTheme: SettingsStore.getValue("theme"),
|
||||
clientLanguage: getUserLanguage(),
|
||||
}, opts?.asPopout);
|
||||
|
||||
const parsed = new URL(templated);
|
||||
|
|
|
@ -44,6 +44,7 @@ import { CHAT_EFFECTS } from "../../effects";
|
|||
import { containsEmoji } from "../../effects/utils";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import {tryTransformPermalinkToLocalHref} from "../../utils/permalinks/Permalinks";
|
||||
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||
|
||||
// TODO: Purge this from the universe
|
||||
|
||||
|
@ -144,6 +145,52 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
return {roomId, eventId: r.event_id};
|
||||
}
|
||||
|
||||
public async readRoomEvents(eventType: string, msgtype: string | undefined, limit: number): Promise<MatrixEvent[]> {
|
||||
limit = limit > 0 ? Math.min(limit, 25) : 25; // arbitrary choice
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const roomId = ActiveRoomObserver.activeRoomId;
|
||||
const room = client.getRoom(roomId);
|
||||
if (!client || !roomId || !room) throw new Error("Not in a room or not attached to a client");
|
||||
|
||||
const results: MatrixEvent[] = [];
|
||||
const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
|
||||
for (let i = events.length - 1; i > 0; i--) {
|
||||
if (results.length >= limit) break;
|
||||
|
||||
const ev = events[i];
|
||||
if (ev.getType() !== eventType) continue;
|
||||
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()['msgtype']) continue;
|
||||
results.push(ev);
|
||||
}
|
||||
|
||||
return results.map(e => e.event);
|
||||
}
|
||||
|
||||
public async readStateEvents(
|
||||
eventType: string, stateKey: string | undefined, limit: number,
|
||||
): Promise<MatrixEvent[]> {
|
||||
limit = limit > 0 ? Math.min(limit, 100) : 100; // arbitrary choice
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const roomId = ActiveRoomObserver.activeRoomId;
|
||||
const room = client.getRoom(roomId);
|
||||
if (!client || !roomId || !room) throw new Error("Not in a room or not attached to a client");
|
||||
|
||||
const results: MatrixEvent[] = [];
|
||||
const state = room.currentState.events.get(eventType);
|
||||
if (state) {
|
||||
if (stateKey === "" || !!stateKey) {
|
||||
const forKey = state.get(stateKey);
|
||||
if (forKey) results.push(forKey);
|
||||
} else {
|
||||
results.push(...Array.from(state.values()));
|
||||
}
|
||||
}
|
||||
|
||||
return results.slice(0, limit).map(e => e.event);
|
||||
}
|
||||
|
||||
public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>) {
|
||||
const oidcState = WidgetPermissionStore.instance.getOIDCState(
|
||||
this.forWidget, this.forWidgetKind, this.inRoomId,
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EnhancedMap } from "./maps";
|
||||
import AwaitLock from "await-lock";
|
||||
|
||||
export type DoneFn = () => void;
|
||||
|
||||
export class MultiLock {
|
||||
private locks = new EnhancedMap<string, AwaitLock>();
|
||||
|
||||
public async acquire(key: string): Promise<DoneFn> {
|
||||
const lock = this.locks.getOrCreate(key, new AwaitLock());
|
||||
await lock.acquireAsync();
|
||||
return () => lock.release();
|
||||
}
|
||||
}
|
|
@ -75,7 +75,8 @@ export function arraySmoothingResample(input: number[], points: number): number[
|
|||
for (let i = 1; i < input.length - 1; i += 2) {
|
||||
const prevPoint = input[i - 1];
|
||||
const nextPoint = input[i + 1];
|
||||
const average = (prevPoint + nextPoint) / 2;
|
||||
const currPoint = input[i];
|
||||
const average = (prevPoint + nextPoint + currPoint) / 3;
|
||||
samples.push(average);
|
||||
}
|
||||
input = samples;
|
||||
|
|
|
@ -15,12 +15,13 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import EventEmitter from "events";
|
||||
import {UPDATE_EVENT} from "../stores/AsyncStore";
|
||||
import {arrayFastResample, arrayRescale, arraySeed, arraySmoothingResample} from "../utils/arrays";
|
||||
import {SimpleObservable} from "matrix-widget-api";
|
||||
import {IDestroyable} from "../utils/IDestroyable";
|
||||
import {PlaybackClock} from "./PlaybackClock";
|
||||
import {createAudioContext, decodeOgg} from "./compat";
|
||||
import { UPDATE_EVENT } from "../stores/AsyncStore";
|
||||
import { arrayFastResample, arrayRescale, arraySeed, arraySmoothingResample } from "../utils/arrays";
|
||||
import { SimpleObservable } from "matrix-widget-api";
|
||||
import { IDestroyable } from "../utils/IDestroyable";
|
||||
import { PlaybackClock } from "./PlaybackClock";
|
||||
import { createAudioContext, decodeOgg } from "./compat";
|
||||
import { clamp } from "../utils/numbers";
|
||||
|
||||
export enum PlaybackState {
|
||||
Decoding = "decoding",
|
||||
|
@ -33,9 +34,20 @@ export const PLAYBACK_WAVEFORM_SAMPLES = 39;
|
|||
const DEFAULT_WAVEFORM = arraySeed(0, PLAYBACK_WAVEFORM_SAMPLES);
|
||||
|
||||
function makePlaybackWaveform(input: number[]): number[] {
|
||||
// We use a smoothing resample to keep the rough shape of the waveform the user will be seeing. We
|
||||
// then rescale so the user can see the waveform properly (loud noises == 100%).
|
||||
return arrayRescale(arraySmoothingResample(input, PLAYBACK_WAVEFORM_SAMPLES), 0, 1);
|
||||
// First, convert negative amplitudes to positive so we don't detect zero as "noisy".
|
||||
const noiseWaveform = input.map(v => Math.abs(v));
|
||||
|
||||
// Next, we'll resample the waveform using a smoothing approach so we can keep the same rough shape.
|
||||
// We also rescale the waveform to be 0-1 for the remaining function logic.
|
||||
const resampled = arrayRescale(arraySmoothingResample(noiseWaveform, PLAYBACK_WAVEFORM_SAMPLES), 0, 1);
|
||||
|
||||
// Then, we'll do a high and low pass filter to isolate actual speaking volumes within the rescaled
|
||||
// waveform. Most speech happens below the 0.5 mark.
|
||||
const filtered = resampled.map(v => clamp(v, 0.1, 0.5));
|
||||
|
||||
// Finally, we'll rescale the filtered waveform (0.1-0.5 becomes 0-1 again) so the user sees something
|
||||
// sensible. This is what we return to keep our contract of "values between zero and one".
|
||||
return arrayRescale(filtered, 0, 1);
|
||||
}
|
||||
|
||||
export class Playback extends EventEmitter implements IDestroyable {
|
||||
|
@ -126,6 +138,7 @@ export class Playback extends EventEmitter implements IDestroyable {
|
|||
this.waveformObservable.update(this.resampledWaveform);
|
||||
|
||||
this.emit(PlaybackState.Stopped); // signal that we're not decoding anymore
|
||||
this.clock.flagLoadTime(); // must happen first because setting the duration fires a clock update
|
||||
this.clock.durationSeconds = this.audioBuf.duration;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,15 @@ export class PlaybackClock implements IDestroyable {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark the time in the audio context where the clip starts/has been loaded.
|
||||
* This is to ensure the clock isn't skewed into thinking it is ~0.5s into
|
||||
* a clip when the duration is set.
|
||||
*/
|
||||
public flagLoadTime() {
|
||||
this.clipStart = this.context.currentTime;
|
||||
}
|
||||
|
||||
public flagStart() {
|
||||
if (this.stopped) {
|
||||
this.clipStart = this.context.currentTime;
|
||||
|
|
|
@ -96,6 +96,16 @@ export class CapabilityText {
|
|||
[EventDirection.Receive]: _td("See when the avatar changes in your active room"),
|
||||
},
|
||||
},
|
||||
[EventType.RoomMember]: {
|
||||
[WidgetKind.Room]: {
|
||||
[EventDirection.Send]: _td("Kick, ban, or invite people to this room, and make you leave"),
|
||||
[EventDirection.Receive]: _td("See when people join, leave, or are invited to this room"),
|
||||
},
|
||||
[GENERIC_WIDGET_KIND]: {
|
||||
[EventDirection.Send]: _td("Kick, ban, or invite people to your active room, and make you leave"),
|
||||
[EventDirection.Receive]: _td("See when people join, leave, or are invited to your active room"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static nonStateSendRecvCaps: ISendRecvStaticCapText = {
|
||||
|
|
|
@ -95,6 +95,7 @@ export function createTestClient() {
|
|||
getItem: jest.fn(),
|
||||
},
|
||||
},
|
||||
decryptEventIfNeeded: () => Promise.resolve(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -73,10 +73,10 @@ describe('arrays', () => {
|
|||
// we'd be feeding a thousand values in and seeing what a curve of 250 values looks like,
|
||||
// but that's not really feasible to manually verify accuracy.
|
||||
[
|
||||
{input: [2, 2, 0, 2, 2, 0, 2, 2, 0], output: [1, 1, 2, 1]}, // Odd -> Even
|
||||
{input: [2, 2, 0, 2, 2, 0, 2, 2, 0], output: [1, 1, 2]}, // Odd -> Odd
|
||||
{input: [2, 2, 0, 2, 2, 0, 2, 2], output: [1, 1, 2]}, // Even -> Odd
|
||||
{input: [2, 2, 0, 2, 2, 0, 2, 2], output: [1, 2]}, // Even -> Even
|
||||
{input: [4, 4, 1, 4, 4, 1, 4, 4, 1], output: [3, 3, 3, 3]}, // Odd -> Even
|
||||
{input: [4, 4, 1, 4, 4, 1, 4, 4, 1], output: [3, 3, 3]}, // Odd -> Odd
|
||||
{input: [4, 4, 1, 4, 4, 1, 4, 4], output: [3, 3, 3]}, // Even -> Odd
|
||||
{input: [4, 4, 1, 4, 4, 1, 4, 4], output: [3, 3]}, // Even -> Even
|
||||
].forEach((c, i) => expectSample(i, c.input, c.output, true));
|
||||
});
|
||||
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -5670,8 +5670,8 @@ mathml-tag-names@^2.1.3:
|
|||
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
||||
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||
version "10.1.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2d73805ca3d8c5a140fe05e574f826696de1656a"
|
||||
version "11.0.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/52a893a8116d60bb76f1b015d3161a15404b3628"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
another-json "^0.2.0"
|
||||
|
@ -5704,10 +5704,10 @@ matrix-react-test-utils@^0.2.2:
|
|||
"@babel/traverse" "^7.13.17"
|
||||
walk "^2.3.14"
|
||||
|
||||
matrix-widget-api@^0.1.0-beta.13:
|
||||
version "0.1.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.13.tgz#ebddc83eaef39bbb87b621a02a35902e1a29b9ef"
|
||||
integrity sha512-DJAvuX2E7gxc/a9rtJPDh17ba9xGIOAoBHcWirNTN3KGodzsrZ+Ns+M/BREFWMwGS5yEBZko5eq7uhXStEbnyQ==
|
||||
matrix-widget-api@^0.1.0-beta.14:
|
||||
version "0.1.0-beta.14"
|
||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.14.tgz#e38beed71c5ebd62c1ac1d79ef262d7150b42c70"
|
||||
integrity sha512-5tC6LO1vCblKg/Hfzf5U1eHPz1nHUZIobAm3gkEKV5vpYPgRpr8KdkLiGB78VZid0tB17CVtAb4VKI8CQ3lhAQ==
|
||||
dependencies:
|
||||
"@types/events" "^3.0.0"
|
||||
events "^3.2.0"
|
||||
|
|
Loading…
Reference in New Issue