mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-17 05:55:00 +08:00
Merge branch 'develop' into gsouquet/ts-components-migration
This commit is contained in:
commit
8b567944ec
44
CHANGELOG.md
44
CHANGELOG.md
@ -1,3 +1,47 @@
|
||||
Changes in [3.29.0](https://github.com/vector-im/element-desktop/releases/tag/v3.29.0) (2021-08-31)
|
||||
===================================================================================================
|
||||
|
||||
## ✨ Features
|
||||
* [Release]Increase general app performance by optimizing layers ([\#6672](https://github.com/matrix-org/matrix-react-sdk/pull/6672)). Fixes vector-im/element-web#18730 and vector-im/element-web#18730. Contributed by [Palid](https://github.com/Palid).
|
||||
* Add a warning on E2EE rooms if you try to make them public ([\#5698](https://github.com/matrix-org/matrix-react-sdk/pull/5698)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Allow pagination of the space hierarchy and use new APIs ([\#6507](https://github.com/matrix-org/matrix-react-sdk/pull/6507)). Fixes vector-im/element-web#18089 and vector-im/element-web#18427.
|
||||
* Improve emoji in composer ([\#6650](https://github.com/matrix-org/matrix-react-sdk/pull/6650)). Fixes vector-im/element-web#18593 and vector-im/element-web#18593. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Allow playback of replied-to voice message ([\#6629](https://github.com/matrix-org/matrix-react-sdk/pull/6629)). Fixes vector-im/element-web#18599 and vector-im/element-web#18599. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Format autocomplete suggestions vertically ([\#6620](https://github.com/matrix-org/matrix-react-sdk/pull/6620)). Fixes vector-im/element-web#17574 and vector-im/element-web#17574. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Remember last `MemberList` search query per-room ([\#6640](https://github.com/matrix-org/matrix-react-sdk/pull/6640)). Fixes vector-im/element-web#18613 and vector-im/element-web#18613. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Sentry rageshakes ([\#6597](https://github.com/matrix-org/matrix-react-sdk/pull/6597)). Fixes vector-im/element-web#11111 and vector-im/element-web#11111. Contributed by [novocaine](https://github.com/novocaine).
|
||||
* Autocomplete has been updated to match modern accessibility standards. Navigate via up/down arrows rather than Tab. Enter or Tab to confirm a suggestion. This should be familiar to Slack & Discord users. You can now use Tab to navigate around the application and do more without touching your mouse. No more accidentally sending half of people's names because the completion didn't fire on Enter! ([\#5659](https://github.com/matrix-org/matrix-react-sdk/pull/5659)). Fixes vector-im/element-web#4872, vector-im/element-web#11071, vector-im/element-web#17171, vector-im/element-web#15646 vector-im/element-web#4872 and vector-im/element-web#4872.
|
||||
* Add new call tile states ([\#6610](https://github.com/matrix-org/matrix-react-sdk/pull/6610)). Fixes vector-im/element-web#18521 and vector-im/element-web#18521. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Left align call tiles ([\#6609](https://github.com/matrix-org/matrix-react-sdk/pull/6609)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Make loading encrypted images look snappier ([\#6590](https://github.com/matrix-org/matrix-react-sdk/pull/6590)). Fixes vector-im/element-web#17878 and vector-im/element-web#17862. Contributed by [Palid](https://github.com/Palid).
|
||||
* Offer a way to create a space based on existing community ([\#6543](https://github.com/matrix-org/matrix-react-sdk/pull/6543)). Fixes vector-im/element-web#18092.
|
||||
* Accessibility improvements in and around Spaces ([\#6569](https://github.com/matrix-org/matrix-react-sdk/pull/6569)). Fixes vector-im/element-web#18094 and vector-im/element-web#18094.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
* [Release] Fix commit edit history ([\#6690](https://github.com/matrix-org/matrix-react-sdk/pull/6690)). Fixes vector-im/element-web#18742 and vector-im/element-web#18742. Contributed by [Palid](https://github.com/Palid).
|
||||
* Fix images not rendering when sent from other clients. ([\#6661](https://github.com/matrix-org/matrix-react-sdk/pull/6661)). Fixes vector-im/element-web#18702 and vector-im/element-web#18702.
|
||||
* Fix autocomplete scrollbar and make the autocomplete a little smaller ([\#6655](https://github.com/matrix-org/matrix-react-sdk/pull/6655)). Fixes vector-im/element-web#18682 and vector-im/element-web#18682. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix replies on the bubble layout ([\#6451](https://github.com/matrix-org/matrix-react-sdk/pull/6451)). Fixes vector-im/element-web#18184. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Show "Enable encryption in settings" only when the user can do that ([\#6646](https://github.com/matrix-org/matrix-react-sdk/pull/6646)). Fixes vector-im/element-web#18646 and vector-im/element-web#18646. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix cross signing setup from settings screen ([\#6633](https://github.com/matrix-org/matrix-react-sdk/pull/6633)). Fixes vector-im/element-web#17761 and vector-im/element-web#17761.
|
||||
* Fix call tiles on the bubble layout ([\#6647](https://github.com/matrix-org/matrix-react-sdk/pull/6647)). Fixes vector-im/element-web#18648 and vector-im/element-web#18648. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix error on accessing encrypted media without encryption keys ([\#6625](https://github.com/matrix-org/matrix-react-sdk/pull/6625)). Contributed by [Palid](https://github.com/Palid).
|
||||
* Fix jitsi widget sometimes being permanently stuck in the bottom-right corner ([\#6632](https://github.com/matrix-org/matrix-react-sdk/pull/6632)). Fixes vector-im/element-web#17226 and vector-im/element-web#17226. Contributed by [Palid](https://github.com/Palid).
|
||||
* Fix FilePanel pagination in E2EE rooms ([\#6630](https://github.com/matrix-org/matrix-react-sdk/pull/6630)). Fixes vector-im/element-web#18415 and vector-im/element-web#18415. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix call tile buttons ([\#6624](https://github.com/matrix-org/matrix-react-sdk/pull/6624)). Fixes vector-im/element-web#18565 and vector-im/element-web#18565. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix vertical call tile spacing issues ([\#6621](https://github.com/matrix-org/matrix-react-sdk/pull/6621)). Fixes vector-im/element-web#18558 and vector-im/element-web#18558. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix long display names in call tiles ([\#6618](https://github.com/matrix-org/matrix-react-sdk/pull/6618)). Fixes vector-im/element-web#18562 and vector-im/element-web#18562. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Avoid access token overflow ([\#6616](https://github.com/matrix-org/matrix-react-sdk/pull/6616)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Properly handle media errors ([\#6615](https://github.com/matrix-org/matrix-react-sdk/pull/6615)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix glare related regressions ([\#6614](https://github.com/matrix-org/matrix-react-sdk/pull/6614)). Fixes vector-im/element-web#18538 and vector-im/element-web#18538. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix long display names in call toasts ([\#6617](https://github.com/matrix-org/matrix-react-sdk/pull/6617)). Fixes vector-im/element-web#18557 and vector-im/element-web#18557. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix PiP of held calls ([\#6611](https://github.com/matrix-org/matrix-react-sdk/pull/6611)). Fixes vector-im/element-web#18539 and vector-im/element-web#18539. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix call tile behaviour on narrow layouts ([\#6556](https://github.com/matrix-org/matrix-react-sdk/pull/6556)). Fixes vector-im/element-web#18398. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Fix video call persisting when widget removed ([\#6608](https://github.com/matrix-org/matrix-react-sdk/pull/6608)). Fixes vector-im/element-web#15703 and vector-im/element-web#15703.
|
||||
* Fix toast colors ([\#6606](https://github.com/matrix-org/matrix-react-sdk/pull/6606)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Remove tiny scrollbar dot from code blocks ([\#6596](https://github.com/matrix-org/matrix-react-sdk/pull/6596)). Fixes vector-im/element-web#18474. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
* Improve handling of pills in the composer ([\#6353](https://github.com/matrix-org/matrix-react-sdk/pull/6353)). Fixes vector-im/element-web#10134 vector-im/element-web#10896 and vector-im/element-web#15037. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||
|
||||
Changes in [3.28.1](https://github.com/vector-im/element-desktop/releases/tag/v3.28.1) (2021-08-17)
|
||||
===================================================================================================
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "3.28.1",
|
||||
"version": "3.29.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
@ -82,7 +82,7 @@
|
||||
"katex": "^0.12.0",
|
||||
"linkifyjs": "^2.1.9",
|
||||
"lodash": "^4.17.20",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"matrix-js-sdk": "12.4.0",
|
||||
"matrix-widget-api": "^0.1.0-beta.15",
|
||||
"minimist": "^1.2.5",
|
||||
"opus-recorder": "^8.0.3",
|
||||
@ -151,7 +151,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^4.17.0",
|
||||
"@typescript-eslint/parser": "^4.17.0",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
||||
"allchange": "^1.0.0",
|
||||
"allchange": "^1.0.2",
|
||||
"babel-jest": "^26.6.3",
|
||||
"chokidar": "^3.5.1",
|
||||
"concurrently": "^5.3.0",
|
||||
|
@ -381,11 +381,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||
font-size: $font-14px;
|
||||
color: $primary-content;
|
||||
word-wrap: break-word;
|
||||
|
||||
a {
|
||||
color: $accent-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_Dialog_buttons {
|
||||
|
@ -15,8 +15,6 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_ScrollPanel {
|
||||
contain: strict;
|
||||
|
||||
.mx_RoomView_MessageList {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
/* a "block" completion takes up a whole line */
|
||||
.mx_Autocomplete_Completion_block {
|
||||
height: 34px;
|
||||
min-height: 34px;
|
||||
display: flex;
|
||||
padding: 0 12px;
|
||||
user-select: none;
|
||||
|
@ -480,7 +480,7 @@ $hover-select-border: 4px;
|
||||
}
|
||||
|
||||
pre code > * {
|
||||
display: inline-block;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
pre {
|
||||
@ -514,7 +514,7 @@ $hover-select-border: 4px;
|
||||
|
||||
.mx_EventTile:hover .mx_EventTile_body pre,
|
||||
.mx_EventTile.focus-visible:focus-within .mx_EventTile_body pre {
|
||||
border: 1px solid #e5e5e5; // deliberate constant as we're behind an invert filter
|
||||
border: 1px solid $tertiary-content;
|
||||
}
|
||||
|
||||
.mx_EventTile_pre_container {
|
||||
|
@ -74,9 +74,9 @@ limitations under the License.
|
||||
> .mx_VideoFeed {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-width: 0 !important; // Override mx_VideoFeed_speaking
|
||||
|
||||
&.mx_VideoFeed_voice {
|
||||
background-color: $inverted-bg-color;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
@ -33,10 +33,9 @@ limitations under the License.
|
||||
|
||||
> .mx_VideoFeed {
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
|
||||
&.mx_VideoFeed_voice {
|
||||
border-radius: 4px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
@ -17,12 +17,23 @@ limitations under the License.
|
||||
.mx_VideoFeed {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
border: transparent 2px solid;
|
||||
display: flex;
|
||||
|
||||
&.mx_VideoFeed_voice {
|
||||
background-color: $inverted-bg-color;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
&.mx_VideoFeed_speaking {
|
||||
border: $accent-color 2px solid;
|
||||
|
||||
.mx_VideoFeed_video {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_VideoFeed_video {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
|
@ -250,7 +250,15 @@ export default class CallHandler extends EventEmitter {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private areAnyCallsUnsilenced(): boolean {
|
||||
return this.calls.size > this.silencedCalls.size;
|
||||
for (const call of this.calls.values()) {
|
||||
if (
|
||||
call.state === CallState.Ringing &&
|
||||
!this.isCallSilenced(call.callId)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async checkProtocols(maxTries) {
|
||||
@ -878,6 +886,8 @@ export default class CallHandler extends EventEmitter {
|
||||
break;
|
||||
case 'hangup':
|
||||
case 'reject':
|
||||
this.stopRingingIfPossible(this.calls.get(payload.room_id).callId);
|
||||
|
||||
if (!this.calls.get(payload.room_id)) {
|
||||
return; // no call to hangup
|
||||
}
|
||||
@ -890,11 +900,15 @@ export default class CallHandler extends EventEmitter {
|
||||
// the hangup event away)
|
||||
break;
|
||||
case 'hangup_all':
|
||||
this.stopRingingIfPossible(this.calls.get(payload.room_id).callId);
|
||||
|
||||
for (const call of this.calls.values()) {
|
||||
call.hangup(CallErrorCode.UserHangup, false);
|
||||
}
|
||||
break;
|
||||
case 'answer': {
|
||||
this.stopRingingIfPossible(this.calls.get(payload.room_id).callId);
|
||||
|
||||
if (!this.calls.has(payload.room_id)) {
|
||||
return; // no call to answer
|
||||
}
|
||||
@ -929,6 +943,12 @@ export default class CallHandler extends EventEmitter {
|
||||
}
|
||||
};
|
||||
|
||||
private stopRingingIfPossible(callId: string): void {
|
||||
this.silencedCalls.delete(callId);
|
||||
if (this.areAnyCallsUnsilenced()) return;
|
||||
this.pause(AudioID.Ring);
|
||||
}
|
||||
|
||||
private async dialNumber(number: string) {
|
||||
const results = await this.pstnLookup(number);
|
||||
if (!results || results.length === 0 || !results[0].userid) {
|
||||
|
@ -50,7 +50,6 @@ import CallHandler from "./CallHandler";
|
||||
import { guessAndSetDMRoom } from "./Rooms";
|
||||
import { upgradeRoom } from './utils/RoomUpgrade';
|
||||
import UploadConfirmDialog from './components/views/dialogs/UploadConfirmDialog';
|
||||
import ErrorDialog from './components/views/dialogs/ErrorDialog';
|
||||
import DevtoolsDialog from './components/views/dialogs/DevtoolsDialog';
|
||||
import RoomUpgradeWarningDialog from "./components/views/dialogs/RoomUpgradeWarningDialog";
|
||||
import InfoDialog from "./components/views/dialogs/InfoDialog";
|
||||
@ -245,21 +244,6 @@ export const Commands = [
|
||||
},
|
||||
category: CommandCategories.messages,
|
||||
}),
|
||||
new Command({
|
||||
command: 'ddg',
|
||||
args: '<query>',
|
||||
description: _td('Searches DuckDuckGo for results'),
|
||||
runFn: function() {
|
||||
// TODO Don't explain this away, actually show a search UI here.
|
||||
Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, {
|
||||
title: _t('/ddg is not a command'),
|
||||
description: _t('To use it, just wait for autocomplete results to load and tab through them.'),
|
||||
});
|
||||
return success();
|
||||
},
|
||||
category: CommandCategories.actions,
|
||||
hideCompletionAfterSpace: true,
|
||||
}),
|
||||
new Command({
|
||||
command: 'upgraderoom',
|
||||
args: '<new_version>',
|
||||
|
@ -45,7 +45,13 @@ class MxVoiceWorklet extends AudioWorkletProcessor {
|
||||
|
||||
process(inputs, outputs, parameters) {
|
||||
const currentSecond = roundTimeToTargetFreq(currentTime);
|
||||
if (currentSecond === this.nextAmplitudeSecond) {
|
||||
// We special case the first ping because there's a fairly good chance that we'll miss the zeroth
|
||||
// update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first
|
||||
// time. Edge and Chrome occasionally lag behind too, but for the most part are on time.
|
||||
//
|
||||
// When this doesn't work properly we end up producing a waveform of nulls and no live preview
|
||||
// of the recorded message.
|
||||
if (currentSecond === this.nextAmplitudeSecond || this.nextAmplitudeSecond === 0) {
|
||||
// We're expecting exactly one mono input source, so just grab the very first frame of
|
||||
// samples for the analysis.
|
||||
const monoChan = inputs[0][0];
|
||||
|
@ -20,7 +20,6 @@ import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
|
||||
import CommandProvider from './CommandProvider';
|
||||
import CommunityProvider from './CommunityProvider';
|
||||
import DuckDuckGoProvider from './DuckDuckGoProvider';
|
||||
import RoomProvider from './RoomProvider';
|
||||
import UserProvider from './UserProvider';
|
||||
import EmojiProvider from './EmojiProvider';
|
||||
@ -55,7 +54,6 @@ const PROVIDERS = [
|
||||
EmojiProvider,
|
||||
NotifProvider,
|
||||
CommandProvider,
|
||||
DuckDuckGoProvider,
|
||||
];
|
||||
|
||||
if (SpaceStore.spacesEnabled) {
|
||||
|
@ -53,7 +53,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||
// The input looks like a command with arguments, perform exact match
|
||||
const name = command[1].substr(1); // strip leading `/`
|
||||
if (CommandMap.has(name) && CommandMap.get(name).isEnabled()) {
|
||||
// some commands, namely `me` and `ddg` don't suit having the usage shown whilst typing their arguments
|
||||
// some commands, namely `me` don't suit having the usage shown whilst typing their arguments
|
||||
if (CommandMap.get(name).hideCompletionAfterSpace) return [];
|
||||
matches = [CommandMap.get(name)];
|
||||
}
|
||||
@ -95,7 +95,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
return (
|
||||
<div
|
||||
className="mx_Autocomplete_Completion_container_block"
|
||||
className="mx_Autocomplete_Completion_container_pill"
|
||||
role="presentation"
|
||||
aria-label={_t("Command Autocomplete")}
|
||||
>
|
||||
|
@ -1,115 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
|
||||
import { TextualCompletion } from './Components';
|
||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||
|
||||
const DDG_REGEX = /\/ddg\s+(.+)$/g;
|
||||
const REFERRER = 'vector';
|
||||
|
||||
export default class DuckDuckGoProvider extends AutocompleteProvider {
|
||||
constructor() {
|
||||
super(DDG_REGEX);
|
||||
}
|
||||
|
||||
static getQueryUri(query: string) {
|
||||
return `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}`
|
||||
+ `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`;
|
||||
}
|
||||
|
||||
async getCompletions(
|
||||
query: string,
|
||||
selection: ISelectionRange,
|
||||
force = false,
|
||||
limit = -1,
|
||||
): Promise<ICompletion[]> {
|
||||
const { command, range } = this.getCurrentCommand(query, selection);
|
||||
if (!query || !command) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const response = await fetch(DuckDuckGoProvider.getQueryUri(command[1]), {
|
||||
method: 'GET',
|
||||
});
|
||||
const json = await response.json();
|
||||
const maxLength = limit > -1 ? limit : json.Results.length;
|
||||
const results = json.Results.slice(0, maxLength).map((result) => {
|
||||
return {
|
||||
completion: result.Text,
|
||||
component: (
|
||||
<TextualCompletion
|
||||
title={result.Text}
|
||||
description={result.Result} />
|
||||
),
|
||||
range,
|
||||
};
|
||||
});
|
||||
if (json.Answer) {
|
||||
results.unshift({
|
||||
completion: json.Answer,
|
||||
component: (
|
||||
<TextualCompletion
|
||||
title={json.Answer}
|
||||
description={json.AnswerType} />
|
||||
),
|
||||
range,
|
||||
});
|
||||
}
|
||||
if (json.RelatedTopics && json.RelatedTopics.length > 0) {
|
||||
results.unshift({
|
||||
completion: json.RelatedTopics[0].Text,
|
||||
component: (
|
||||
<TextualCompletion
|
||||
title={json.RelatedTopics[0].Text} />
|
||||
),
|
||||
range,
|
||||
});
|
||||
}
|
||||
if (json.AbstractText) {
|
||||
results.unshift({
|
||||
completion: json.AbstractText,
|
||||
component: (
|
||||
<TextualCompletion
|
||||
title={json.AbstractText} />
|
||||
),
|
||||
range,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return '🔍 ' + _t('Results from DuckDuckGo');
|
||||
}
|
||||
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
return (
|
||||
<div
|
||||
className="mx_Autocomplete_Completion_container_block"
|
||||
role="presentation"
|
||||
aria-label={_t("DuckDuckGo Results")}
|
||||
>
|
||||
{ completions }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -115,7 +115,7 @@ const LeftPanelWidget: React.FC = () => {
|
||||
aria-expanded={expanded}
|
||||
aria-level={1}
|
||||
onClick={() => {
|
||||
setExpanded(e => !e);
|
||||
setExpanded(!expanded);
|
||||
}}
|
||||
>
|
||||
<span className={classNames({
|
||||
|
@ -80,7 +80,7 @@ const SpaceChildPicker = ({ filterPlaceholder, rooms, selected, onChange }) => {
|
||||
|
||||
const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave }) => {
|
||||
const selected = useMemo(() => new Set(roomsToLeave), [roomsToLeave]);
|
||||
const [state, setState] = useState<string>(RoomsToLeave.All);
|
||||
const [state, setState] = useState<string>(RoomsToLeave.None);
|
||||
|
||||
useEffect(() => {
|
||||
if (state === RoomsToLeave.All) {
|
||||
@ -97,11 +97,11 @@ const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave
|
||||
onChange={setState}
|
||||
definitions={[
|
||||
{
|
||||
value: RoomsToLeave.All,
|
||||
label: _t("Leave all rooms and spaces"),
|
||||
}, {
|
||||
value: RoomsToLeave.None,
|
||||
label: _t("Don't leave any"),
|
||||
}, {
|
||||
value: RoomsToLeave.All,
|
||||
label: _t("Leave all rooms and spaces"),
|
||||
}, {
|
||||
value: RoomsToLeave.Specific,
|
||||
label: _t("Leave specific rooms and spaces"),
|
||||
|
@ -31,14 +31,6 @@ import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
import { CapabilityText } from "../../../widgets/CapabilityText";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
export function getRememberedCapabilitiesForWidget(widget: Widget): Capability[] {
|
||||
return JSON.parse(localStorage.getItem(`widget_${widget.id}_approved_caps`) || "[]");
|
||||
}
|
||||
|
||||
function setRememberedCapabilitiesForWidget(widget: Widget, caps: Capability[]) {
|
||||
localStorage.setItem(`widget_${widget.id}_approved_caps`, JSON.stringify(caps));
|
||||
}
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
requestedCapabilities: Set<Capability>;
|
||||
widget: Widget;
|
||||
@ -95,10 +87,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
|
||||
};
|
||||
|
||||
private closeAndTryRemember(approved: Capability[]) {
|
||||
if (this.state.rememberSelection) {
|
||||
setRememberedCapabilitiesForWidget(this.props.widget, approved);
|
||||
}
|
||||
this.props.onFinished({ approved });
|
||||
this.props.onFinished({ approved, remember: this.state.rememberSelection });
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -185,8 +185,8 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||
return {
|
||||
loading: false,
|
||||
members: members,
|
||||
filteredJoinedMembers: this.filterMembers(members, 'join'),
|
||||
filteredInvitedMembers: this.filterMembers(members, 'invite'),
|
||||
filteredJoinedMembers: this.filterMembers(members, 'join', searchQuery),
|
||||
filteredInvitedMembers: this.filterMembers(members, 'invite', searchQuery),
|
||||
canInvite: this.canInvite,
|
||||
|
||||
// ideally we'd size this to the page height, but
|
||||
|
@ -15,22 +15,25 @@ limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import AppTile from '../elements/AppTile';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import * as sdk from '../../../index';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import WidgetUtils from '../../../utils/WidgetUtils';
|
||||
import WidgetUtils, { IWidgetEvent } from '../../../utils/WidgetUtils';
|
||||
import PersistedElement from "../elements/PersistedElement";
|
||||
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { ContextMenu } from "../../structures/ContextMenu";
|
||||
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
|
||||
import { WidgetType } from "../../../widgets/WidgetType";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { ActionPayload } from '../../../dispatcher/payloads';
|
||||
import ScalarAuthClient from '../../../ScalarAuthClient';
|
||||
import GenericElementContextMenu from "../context_menus/GenericElementContextMenu";
|
||||
|
||||
// This should be below the dialog level (4000), but above the rest of the UI (1000-2000).
|
||||
// We sit in a context menu, so this should be given to the context menu.
|
||||
@ -39,27 +42,35 @@ const STICKERPICKER_Z_INDEX = 3500;
|
||||
// Key to store the widget's AppTile under in PersistedElement
|
||||
const PERSISTED_ELEMENT_KEY = "stickerPicker";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
showStickers: boolean;
|
||||
imError: string;
|
||||
stickerpickerX: number;
|
||||
stickerpickerY: number;
|
||||
stickerpickerChevronOffset?: number;
|
||||
stickerpickerWidget: IWidgetEvent;
|
||||
widgetId: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.rooms.Stickerpicker")
|
||||
export default class Stickerpicker extends React.PureComponent {
|
||||
export default class Stickerpicker extends React.PureComponent<IProps, IState> {
|
||||
static currentWidget;
|
||||
|
||||
constructor(props) {
|
||||
private dispatcherRef: string;
|
||||
|
||||
private prevSentVisibility: boolean;
|
||||
|
||||
private popoverWidth = 300;
|
||||
private popoverHeight = 300;
|
||||
// This is loaded by _acquireScalarClient on an as-needed basis.
|
||||
private scalarClient: ScalarAuthClient = null;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this._onShowStickersClick = this._onShowStickersClick.bind(this);
|
||||
this._onHideStickersClick = this._onHideStickersClick.bind(this);
|
||||
this._launchManageIntegrations = this._launchManageIntegrations.bind(this);
|
||||
this._removeStickerpickerWidgets = this._removeStickerpickerWidgets.bind(this);
|
||||
this._updateWidget = this._updateWidget.bind(this);
|
||||
this._onWidgetAction = this._onWidgetAction.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
this._onFinished = this._onFinished.bind(this);
|
||||
|
||||
this.popoverWidth = 300;
|
||||
this.popoverHeight = 300;
|
||||
|
||||
// This is loaded by _acquireScalarClient on an as-needed basis.
|
||||
this.scalarClient = null;
|
||||
|
||||
this.state = {
|
||||
showStickers: false,
|
||||
imError: null,
|
||||
@ -70,7 +81,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
_acquireScalarClient() {
|
||||
private acquireScalarClient(): Promise<void | ScalarAuthClient> {
|
||||
if (this.scalarClient) return Promise.resolve(this.scalarClient);
|
||||
// TODO: Pick the right manager for the widget
|
||||
if (IntegrationManagers.sharedInstance().hasManager()) {
|
||||
@ -79,15 +90,15 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
this.forceUpdate();
|
||||
return this.scalarClient;
|
||||
}).catch((e) => {
|
||||
this._imError(_td("Failed to connect to integration manager"), e);
|
||||
this.imError(_td("Failed to connect to integration manager"), e);
|
||||
});
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().openNoManagerDialog();
|
||||
}
|
||||
}
|
||||
|
||||
async _removeStickerpickerWidgets() {
|
||||
const scalarClient = await this._acquireScalarClient();
|
||||
private removeStickerpickerWidgets = async (): Promise<void> => {
|
||||
const scalarClient = await this.acquireScalarClient();
|
||||
console.log('Removing Stickerpicker widgets');
|
||||
if (this.state.widgetId) {
|
||||
if (scalarClient) {
|
||||
@ -109,36 +120,36 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
}).catch((e) => {
|
||||
console.error('Failed to remove sticker picker widget', e);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
// Close the sticker picker when the window resizes
|
||||
window.addEventListener('resize', this._onResize);
|
||||
window.addEventListener('resize', this.onResize);
|
||||
|
||||
this.dispatcherRef = dis.register(this._onWidgetAction);
|
||||
this.dispatcherRef = dis.register(this.onWidgetAction);
|
||||
|
||||
// Track updates to widget state in account data
|
||||
MatrixClientPeg.get().on('accountData', this._updateWidget);
|
||||
MatrixClientPeg.get().on('accountData', this.updateWidget);
|
||||
|
||||
// Initialise widget state from current account data
|
||||
this._updateWidget();
|
||||
this.updateWidget();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount(): void {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (client) client.removeListener('accountData', this._updateWidget);
|
||||
if (client) client.removeListener('accountData', this.updateWidget);
|
||||
|
||||
window.removeEventListener('resize', this._onResize);
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
this._sendVisibilityToWidget(this.state.showStickers);
|
||||
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
|
||||
this.sendVisibilityToWidget(this.state.showStickers);
|
||||
}
|
||||
|
||||
_imError(errorMsg, e) {
|
||||
private imError(errorMsg: string, e: Error): void {
|
||||
console.error(errorMsg, e);
|
||||
this.setState({
|
||||
showStickers: false,
|
||||
@ -146,7 +157,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
_updateWidget() {
|
||||
private updateWidget = (): void => {
|
||||
const stickerpickerWidget = WidgetUtils.getStickerpickerWidgets()[0];
|
||||
if (!stickerpickerWidget) {
|
||||
Stickerpicker.currentWidget = null;
|
||||
@ -175,9 +186,9 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
stickerpickerWidget,
|
||||
widgetId: stickerpickerWidget ? stickerpickerWidget.id : null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_onWidgetAction(payload) {
|
||||
private onWidgetAction = (payload: ActionPayload): void => {
|
||||
switch (payload.action) {
|
||||
case "user_widget_updated":
|
||||
this.forceUpdate();
|
||||
@ -191,11 +202,11 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
this.setState({ showStickers: false });
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_defaultStickerpickerContent() {
|
||||
private defaultStickerpickerContent(): JSX.Element {
|
||||
return (
|
||||
<AccessibleButton onClick={this._launchManageIntegrations}
|
||||
<AccessibleButton onClick={this.launchManageIntegrations}
|
||||
className='mx_Stickers_contentPlaceholder'>
|
||||
<p>{ _t("You don't currently have any stickerpacks enabled") }</p>
|
||||
<p className='mx_Stickers_addLink'>{ _t("Add some now") }</p>
|
||||
@ -204,29 +215,29 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
_errorStickerpickerContent() {
|
||||
private errorStickerpickerContent(): JSX.Element {
|
||||
return (
|
||||
<div style={{ "text-align": "center" }} className="error">
|
||||
<div style={{ textAlign: "center" }} className="error">
|
||||
<p> { this.state.imError } </p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_sendVisibilityToWidget(visible) {
|
||||
private sendVisibilityToWidget(visible: boolean): void {
|
||||
if (!this.state.stickerpickerWidget) return;
|
||||
const messaging = WidgetMessagingStore.instance.getMessagingForId(this.state.stickerpickerWidget.id);
|
||||
if (messaging && visible !== this._prevSentVisibility) {
|
||||
if (messaging && visible !== this.prevSentVisibility) {
|
||||
messaging.updateVisibility(visible).catch(err => {
|
||||
console.error("Error updating widget visibility: ", err);
|
||||
});
|
||||
this._prevSentVisibility = visible;
|
||||
this.prevSentVisibility = visible;
|
||||
}
|
||||
}
|
||||
|
||||
_getStickerpickerContent() {
|
||||
public getStickerpickerContent(): JSX.Element {
|
||||
// Handle integration manager errors
|
||||
if (this.state._imError) {
|
||||
return this._errorStickerpickerContent();
|
||||
if (this.state.imError) {
|
||||
return this.errorStickerpickerContent();
|
||||
}
|
||||
|
||||
// Stickers
|
||||
@ -239,12 +250,11 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
// Use a separate ReactDOM tree to render the AppTile separately so that it persists and does
|
||||
// not unmount when we (a) close the sticker picker (b) switch rooms. It's properties are still
|
||||
// updated.
|
||||
const PersistedElement = sdk.getComponent("elements.PersistedElement");
|
||||
|
||||
// Load stickerpack content
|
||||
if (stickerpickerWidget && stickerpickerWidget.content && stickerpickerWidget.content.url) {
|
||||
// Set default name
|
||||
stickerpickerWidget.content.name = stickerpickerWidget.name || _t("Stickerpack");
|
||||
stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("Stickerpack");
|
||||
|
||||
// FIXME: could this use the same code as other apps?
|
||||
const stickerApp = {
|
||||
@ -275,12 +285,12 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
|
||||
waitForIframeLoad={true}
|
||||
showMenubar={true}
|
||||
onEditClick={this._launchManageIntegrations}
|
||||
onDeleteClick={this._removeStickerpickerWidgets}
|
||||
onEditClick={this.launchManageIntegrations}
|
||||
onDeleteClick={this.removeStickerpickerWidgets}
|
||||
showTitle={false}
|
||||
showCancel={false}
|
||||
showPopout={false}
|
||||
onMinimiseClick={this._onHideStickersClick}
|
||||
onMinimiseClick={this.onHideStickersClick}
|
||||
handleMinimisePointerEvents={true}
|
||||
userWidget={true}
|
||||
/>
|
||||
@ -290,7 +300,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
);
|
||||
} else {
|
||||
// Default content to show if stickerpicker widget not added
|
||||
stickersContent = this._defaultStickerpickerContent();
|
||||
stickersContent = this.defaultStickerpickerContent();
|
||||
}
|
||||
return stickersContent;
|
||||
}
|
||||
@ -300,7 +310,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
* Show the sticker picker overlay
|
||||
* If no stickerpacks have been added, show a link to the integration manager add sticker packs page.
|
||||
*/
|
||||
_onShowStickersClick(e) {
|
||||
private onShowStickersClick = (e: React.MouseEvent<HTMLElement>): void => {
|
||||
if (!SettingsStore.getValue("integrationProvisioning")) {
|
||||
// Intercept this case and spawn a warning.
|
||||
return IntegrationManagers.sharedInstance().showDisabledDialog();
|
||||
@ -308,7 +318,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
|
||||
// XXX: Simplify by using a context menu that is positioned relative to the sticker picker button
|
||||
|
||||
const buttonRect = e.target.getBoundingClientRect();
|
||||
const buttonRect = e.currentTarget.getBoundingClientRect();
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
let x = buttonRect.right + window.pageXOffset - 41;
|
||||
@ -324,50 +334,50 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
// Offset the chevron location, which is relative to the left of the context menu
|
||||
// (10 = offset when context menu would not be displayed off viewport)
|
||||
// (2 = context menu borders)
|
||||
const stickerPickerChevronOffset = Math.max(10, 2 + window.pageXOffset + buttonRect.left - x);
|
||||
const stickerpickerChevronOffset = Math.max(10, 2 + window.pageXOffset + buttonRect.left - x);
|
||||
|
||||
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
|
||||
|
||||
this.setState({
|
||||
showStickers: true,
|
||||
stickerPickerX: x,
|
||||
stickerPickerY: y,
|
||||
stickerPickerChevronOffset,
|
||||
stickerpickerX: x,
|
||||
stickerpickerY: y,
|
||||
stickerpickerChevronOffset,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger hiding of the sticker picker overlay
|
||||
* @param {Event} ev Event that triggered the function call
|
||||
*/
|
||||
_onHideStickersClick(ev) {
|
||||
private onHideStickersClick = (ev: React.MouseEvent): void => {
|
||||
if (this.state.showStickers) {
|
||||
this.setState({ showStickers: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the window is resized
|
||||
*/
|
||||
_onResize() {
|
||||
private onResize = (): void => {
|
||||
if (this.state.showStickers) {
|
||||
this.setState({ showStickers: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The stickers picker was hidden
|
||||
*/
|
||||
_onFinished() {
|
||||
private onFinished = (): void => {
|
||||
if (this.state.showStickers) {
|
||||
this.setState({ showStickers: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Launch the integration manager on the stickers integration page
|
||||
*/
|
||||
_launchManageIntegrations = () => {
|
||||
private launchManageIntegrations = (): void => {
|
||||
// TODO: Open the right integration manager for the widget
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(
|
||||
@ -384,7 +394,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
public render(): JSX.Element {
|
||||
let stickerPicker;
|
||||
let stickersButton;
|
||||
const className = classNames(
|
||||
@ -400,26 +410,24 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
id='stickersButton'
|
||||
key="controls_hide_stickers"
|
||||
className={className}
|
||||
onClick={this._onHideStickersClick}
|
||||
active={this.state.showStickers.toString()}
|
||||
onClick={this.onHideStickersClick}
|
||||
title={_t("Hide Stickers")}
|
||||
/>;
|
||||
|
||||
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
|
||||
stickerPicker = <ContextMenu
|
||||
chevronOffset={this.state.stickerPickerChevronOffset}
|
||||
chevronFace="bottom"
|
||||
left={this.state.stickerPickerX}
|
||||
top={this.state.stickerPickerY}
|
||||
chevronOffset={this.state.stickerpickerChevronOffset}
|
||||
chevronFace={ChevronFace.Bottom}
|
||||
left={this.state.stickerpickerX}
|
||||
top={this.state.stickerpickerY}
|
||||
menuWidth={this.popoverWidth}
|
||||
menuHeight={this.popoverHeight}
|
||||
onFinished={this._onFinished}
|
||||
onFinished={this.onFinished}
|
||||
menuPaddingTop={0}
|
||||
menuPaddingLeft={0}
|
||||
menuPaddingRight={0}
|
||||
zIndex={STICKERPICKER_Z_INDEX}
|
||||
>
|
||||
<GenericElementContextMenu element={this._getStickerpickerContent()} onResize={this._onFinished} />
|
||||
<GenericElementContextMenu element={this.getStickerpickerContent()} onResize={this.onFinished} />
|
||||
</ContextMenu>;
|
||||
} else {
|
||||
// Show show-stickers button
|
||||
@ -428,7 +436,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||
id='stickersButton'
|
||||
key="controls_show_stickers"
|
||||
className="mx_MessageComposer_button mx_MessageComposer_stickers"
|
||||
onClick={this._onShowStickersClick}
|
||||
onClick={this.onShowStickersClick}
|
||||
title={_t("Show Stickers")}
|
||||
/>;
|
||||
}
|
@ -149,10 +149,12 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
"To avoid these issues, create a <a>new encrypted room</a> for " +
|
||||
"the conversation you plan to have.",
|
||||
null,
|
||||
{ "a": (sub) => <a onClick={() => {
|
||||
dialog.close();
|
||||
this.createNewRoom(false, true);
|
||||
}}> { sub } </a> },
|
||||
{ "a": (sub) => <a
|
||||
className="mx_linkButton"
|
||||
onClick={() => {
|
||||
dialog.close();
|
||||
this.createNewRoom(false, true);
|
||||
}}> { sub } </a> },
|
||||
) } </p>
|
||||
</div>,
|
||||
|
||||
@ -248,10 +250,12 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
"you plan to have.",
|
||||
null,
|
||||
{
|
||||
"a": (sub) => <a onClick={() => {
|
||||
dialog.close();
|
||||
this.createNewRoom(true, false);
|
||||
}}> { sub } </a>,
|
||||
"a": (sub) => <a
|
||||
className="mx_linkButton"
|
||||
onClick={() => {
|
||||
dialog.close();
|
||||
this.createNewRoom(true, false);
|
||||
}}> { sub } </a>,
|
||||
},
|
||||
) } </p>
|
||||
</div>,
|
||||
|
@ -54,8 +54,8 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
|
||||
{ space.canInvite(MatrixClientPeg.get()?.getUserId()) ? <AccessibleButton
|
||||
className="mx_SpacePublicShare_inviteButton"
|
||||
onClick={() => {
|
||||
showRoomInviteDialog(space.roomId);
|
||||
if (onFinished) onFinished();
|
||||
showRoomInviteDialog(space.roomId);
|
||||
}}
|
||||
>
|
||||
<h3>{ _t("Invite people") }</h3>
|
||||
|
@ -45,6 +45,7 @@ interface IProps {
|
||||
interface IState {
|
||||
audioMuted: boolean;
|
||||
videoMuted: boolean;
|
||||
speaking: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.voip.VideoFeed")
|
||||
@ -57,6 +58,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
|
||||
this.state = {
|
||||
audioMuted: this.props.feed.isAudioMuted(),
|
||||
videoMuted: this.props.feed.isVideoMuted(),
|
||||
speaking: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -103,11 +105,19 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
|
||||
if (oldFeed) {
|
||||
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
|
||||
this.props.feed.removeListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged);
|
||||
if (this.props.feed.purpose === SDPStreamMetadataPurpose.Usermedia) {
|
||||
this.props.feed.removeListener(CallFeedEvent.Speaking, this.onSpeaking);
|
||||
this.props.feed.measureVolumeActivity(false);
|
||||
}
|
||||
this.stopMedia();
|
||||
}
|
||||
if (newFeed) {
|
||||
this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream);
|
||||
this.props.feed.addListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged);
|
||||
if (this.props.feed.purpose === SDPStreamMetadataPurpose.Usermedia) {
|
||||
this.props.feed.addListener(CallFeedEvent.Speaking, this.onSpeaking);
|
||||
this.props.feed.measureVolumeActivity(true);
|
||||
}
|
||||
this.playMedia();
|
||||
}
|
||||
}
|
||||
@ -162,6 +172,10 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
|
||||
});
|
||||
};
|
||||
|
||||
private onSpeaking = (speaking: boolean): void => {
|
||||
this.setState({ speaking });
|
||||
};
|
||||
|
||||
private onResize = (e) => {
|
||||
if (this.props.onResize && !this.props.feed.isLocal()) {
|
||||
this.props.onResize(e);
|
||||
@ -173,6 +187,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
|
||||
|
||||
const wrapperClasses = classnames("mx_VideoFeed", {
|
||||
mx_VideoFeed_voice: this.state.videoMuted,
|
||||
mx_VideoFeed_speaking: this.state.speaking,
|
||||
});
|
||||
const micIconClasses = classnames("mx_VideoFeed_mic", {
|
||||
mx_VideoFeed_mic_muted: this.state.audioMuted,
|
||||
|
@ -426,9 +426,6 @@
|
||||
"Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message",
|
||||
"Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown",
|
||||
"Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown",
|
||||
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
|
||||
"/ddg is not a command": "/ddg is not a command",
|
||||
"To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.",
|
||||
"Upgrades a room to a new version": "Upgrades a room to a new version",
|
||||
"You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.",
|
||||
"Changes your display nickname": "Changes your display nickname",
|
||||
@ -2411,8 +2408,8 @@
|
||||
"Clear cache and resync": "Clear cache and resync",
|
||||
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
||||
"Updating %(brand)s": "Updating %(brand)s",
|
||||
"Leave all rooms and spaces": "Leave all rooms and spaces",
|
||||
"Don't leave any": "Don't leave any",
|
||||
"Leave all rooms and spaces": "Leave all rooms and spaces",
|
||||
"Leave specific rooms and spaces": "Leave specific rooms and spaces",
|
||||
"Search %(spaceName)s": "Search %(spaceName)s",
|
||||
"You won't be able to rejoin unless you are re-invited.": "You won't be able to rejoin unless you are re-invited.",
|
||||
@ -3002,8 +2999,6 @@
|
||||
"Commands": "Commands",
|
||||
"Command Autocomplete": "Command Autocomplete",
|
||||
"Community Autocomplete": "Community Autocomplete",
|
||||
"Results from DuckDuckGo": "Results from DuckDuckGo",
|
||||
"DuckDuckGo Results": "DuckDuckGo Results",
|
||||
"Emoji": "Emoji",
|
||||
"Emoji Autocomplete": "Emoji Autocomplete",
|
||||
"Notify the whole room": "Notify the whole room",
|
||||
|
@ -71,7 +71,13 @@ export default class DeviceSettingsHandler extends SettingsHandler {
|
||||
// Special case for old useIRCLayout setting
|
||||
if (settingName === "layout") {
|
||||
const settings = this.getSettings() || {};
|
||||
if (settings["useIRCLayout"]) return Layout.IRC;
|
||||
if (settings["useIRCLayout"]) {
|
||||
// Set the new layout setting and delete the old one so that we
|
||||
// can delete this block of code after some time
|
||||
settings["layout"] = Layout.IRC;
|
||||
delete settings["useIRCLayout"];
|
||||
localStorage.setItem("mx_local_settings", JSON.stringify(settings));
|
||||
}
|
||||
return settings[settingName];
|
||||
}
|
||||
|
||||
|
@ -678,12 +678,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||
}
|
||||
this.emit(room.roomId);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
case EventType.RoomMember:
|
||||
if (room.isSpaceRoom()) {
|
||||
this.onSpaceMembersChange(ev);
|
||||
}
|
||||
break;
|
||||
// listening for m.room.member events in onRoomState above doesn't work as the Member object isn't updated by then
|
||||
private onRoomStateMembers = (ev: MatrixEvent) => {
|
||||
const room = this.matrixClient.getRoom(ev.getRoomId());
|
||||
if (room?.isSpaceRoom()) {
|
||||
this.onSpaceMembersChange(ev);
|
||||
}
|
||||
};
|
||||
|
||||
@ -743,6 +745,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
|
||||
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
|
||||
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
|
||||
this.matrixClient.removeListener("RoomState.members", this.onRoomStateMembers);
|
||||
this.matrixClient.removeListener("accountData", this.onAccountData);
|
||||
}
|
||||
await this.reset();
|
||||
@ -754,6 +757,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||
this.matrixClient.on("Room.myMembership", this.onRoom);
|
||||
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
|
||||
this.matrixClient.on("RoomState.events", this.onRoomState);
|
||||
this.matrixClient.on("RoomState.members", this.onRoomStateMembers);
|
||||
this.matrixClient.on("accountData", this.onAccountData);
|
||||
|
||||
this.matrixClient.getCapabilities().then(capabilities => {
|
||||
|
@ -20,6 +20,27 @@ import { IAlgorithm } from "./IAlgorithm";
|
||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||
import * as Unread from "../../../../Unread";
|
||||
import { EffectiveMembership, getEffectiveMembership } from "../../../../utils/membership";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
export function shouldCauseReorder(event: MatrixEvent): boolean {
|
||||
const type = event.getType();
|
||||
const content = event.getContent();
|
||||
const prevContent = event.getPrevContent();
|
||||
|
||||
// Never ignore membership changes
|
||||
if (type === EventType.RoomMember && prevContent.membership !== content.membership) return true;
|
||||
|
||||
// Ignore status changes
|
||||
// XXX: This should be an enum
|
||||
if (type === "im.vector.user_status") return false;
|
||||
// Ignore display name changes
|
||||
if (type === EventType.RoomMember && prevContent.displayname !== content.displayname) return false;
|
||||
// Ignore avatar changes
|
||||
if (type === EventType.RoomMember && prevContent.avatar_url !== content.avatar_url) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export const sortRooms = (rooms: Room[]): Room[] => {
|
||||
// We cache the timestamp lookup to avoid iterating forever on the timeline
|
||||
@ -68,7 +89,10 @@ export const sortRooms = (rooms: Room[]): Room[] => {
|
||||
const ev = r.timeline[i];
|
||||
if (!ev.getTs()) continue; // skip events that don't have timestamps (tests only?)
|
||||
|
||||
if (ev.getSender() === myUserId || Unread.eventTriggersUnreadCount(ev)) {
|
||||
if (
|
||||
(ev.getSender() === myUserId && shouldCauseReorder(ev)) ||
|
||||
Unread.eventTriggersUnreadCount(ev)
|
||||
) {
|
||||
return ev.getTs();
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,7 @@ import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import ActiveRoomObserver from "../../ActiveRoomObserver";
|
||||
import Modal from "../../Modal";
|
||||
import WidgetOpenIDPermissionsDialog from "../../components/views/dialogs/WidgetOpenIDPermissionsDialog";
|
||||
import WidgetCapabilitiesPromptDialog, {
|
||||
getRememberedCapabilitiesForWidget,
|
||||
} from "../../components/views/dialogs/WidgetCapabilitiesPromptDialog";
|
||||
import WidgetCapabilitiesPromptDialog from "../../components/views/dialogs/WidgetCapabilitiesPromptDialog";
|
||||
import { WidgetPermissionCustomisations } from "../../customisations/WidgetPermissions";
|
||||
import { OIDCState, WidgetPermissionStore } from "./WidgetPermissionStore";
|
||||
import { WidgetType } from "../../widgets/WidgetType";
|
||||
@ -48,6 +46,14 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
// TODO: Purge this from the universe
|
||||
|
||||
function getRememberedCapabilitiesForWidget(widget: Widget): Capability[] {
|
||||
return JSON.parse(localStorage.getItem(`widget_${widget.id}_approved_caps`) || "[]");
|
||||
}
|
||||
|
||||
function setRememberedCapabilitiesForWidget(widget: Widget, caps: Capability[]) {
|
||||
localStorage.setItem(`widget_${widget.id}_approved_caps`, JSON.stringify(caps));
|
||||
}
|
||||
|
||||
export class StopGapWidgetDriver extends WidgetDriver {
|
||||
private allowedCapabilities: Set<Capability>;
|
||||
|
||||
@ -100,6 +106,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||
}
|
||||
}
|
||||
// TODO: Do something when the widget requests new capabilities not yet asked for
|
||||
let rememberApproved = false;
|
||||
if (missing.size > 0) {
|
||||
try {
|
||||
const [result] = await Modal.createTrackedDialog(
|
||||
@ -111,12 +118,19 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||
widgetKind: this.forWidgetKind,
|
||||
}).finished;
|
||||
(result.approved || []).forEach(cap => allowedSoFar.add(cap));
|
||||
rememberApproved = result.remember;
|
||||
} catch (e) {
|
||||
console.error("Non-fatal error getting capabilities: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
return new Set(iterableUnion(allowedSoFar, requested));
|
||||
const allAllowed = new Set(iterableUnion(allowedSoFar, requested));
|
||||
|
||||
if (rememberApproved) {
|
||||
setRememberedCapabilitiesForWidget(this.forWidget, Array.from(allAllowed));
|
||||
}
|
||||
|
||||
return allAllowed;
|
||||
}
|
||||
|
||||
public async sendEvent(eventType: string, content: any, stateKey: string = null): Promise<ISendEventDetails> {
|
||||
@ -129,6 +143,9 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||
if (stateKey !== null) {
|
||||
// state event
|
||||
r = await client.sendStateEvent(roomId, eventType, content, stateKey);
|
||||
} else if (eventType === EventType.RoomRedaction) {
|
||||
// special case: extract the `redacts` property and call redact
|
||||
r = await client.redactEvent(roomId, content['redacts']);
|
||||
} else {
|
||||
// message event
|
||||
r = await client.sendEvent(roomId, eventType, content);
|
||||
|
Loading…
Reference in New Issue
Block a user