mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 03:36:07 +03:00
Merge remote-tracking branch 'origin/develop' into dbkr/line_1_2
This commit is contained in:
commit
5282c6bbe8
32 changed files with 789 additions and 77 deletions
83
CHANGELOG.md
83
CHANGELOG.md
|
@ -1,3 +1,86 @@
|
||||||
|
Changes in [3.10.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.10.0) (2020-12-07)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.10.0-rc.1...v3.10.0)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 9.3.0
|
||||||
|
|
||||||
|
Changes in [3.10.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.10.0-rc.1) (2020-12-02)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.9.0...v3.10.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 9.3.0-rc.1
|
||||||
|
* Translations update from Weblate
|
||||||
|
[\#5461](https://github.com/matrix-org/matrix-react-sdk/pull/5461)
|
||||||
|
* Fix VoIP call plinth on dark theme
|
||||||
|
[\#5460](https://github.com/matrix-org/matrix-react-sdk/pull/5460)
|
||||||
|
* Add sanity checking around widget pinning
|
||||||
|
[\#5459](https://github.com/matrix-org/matrix-react-sdk/pull/5459)
|
||||||
|
* Update i18n for Appearance User Settings
|
||||||
|
[\#5457](https://github.com/matrix-org/matrix-react-sdk/pull/5457)
|
||||||
|
* Only show 'answered elsewhere' if we tried to answer too
|
||||||
|
[\#5455](https://github.com/matrix-org/matrix-react-sdk/pull/5455)
|
||||||
|
* Fixed Avatar for 3PID invites
|
||||||
|
[\#5442](https://github.com/matrix-org/matrix-react-sdk/pull/5442)
|
||||||
|
* Slightly better error if we can't capture user media
|
||||||
|
[\#5449](https://github.com/matrix-org/matrix-react-sdk/pull/5449)
|
||||||
|
* Make it possible in-code to hide rooms from the room list
|
||||||
|
[\#5445](https://github.com/matrix-org/matrix-react-sdk/pull/5445)
|
||||||
|
* Fix the stickerpicker
|
||||||
|
[\#5447](https://github.com/matrix-org/matrix-react-sdk/pull/5447)
|
||||||
|
* Add live password validation to change password dialog
|
||||||
|
[\#5436](https://github.com/matrix-org/matrix-react-sdk/pull/5436)
|
||||||
|
* LaTeX rendering in element-web using KaTeX
|
||||||
|
[\#5244](https://github.com/matrix-org/matrix-react-sdk/pull/5244)
|
||||||
|
* Add lifecycle customisation point after logout
|
||||||
|
[\#5448](https://github.com/matrix-org/matrix-react-sdk/pull/5448)
|
||||||
|
* Simplify UserMenu for Guests as they can't use most of the options
|
||||||
|
[\#5421](https://github.com/matrix-org/matrix-react-sdk/pull/5421)
|
||||||
|
* Fix known issues with modal widgets
|
||||||
|
[\#5444](https://github.com/matrix-org/matrix-react-sdk/pull/5444)
|
||||||
|
* Fix existing widgets not having approved capabilities for their function
|
||||||
|
[\#5443](https://github.com/matrix-org/matrix-react-sdk/pull/5443)
|
||||||
|
* Use the WidgetDriver to run OIDC requests
|
||||||
|
[\#5440](https://github.com/matrix-org/matrix-react-sdk/pull/5440)
|
||||||
|
* Add a customisation point for widget permissions and fix amnesia issues
|
||||||
|
[\#5439](https://github.com/matrix-org/matrix-react-sdk/pull/5439)
|
||||||
|
* Fix Widget event notification text including spurious space
|
||||||
|
[\#5441](https://github.com/matrix-org/matrix-react-sdk/pull/5441)
|
||||||
|
* Move call listener out of MatrixChat
|
||||||
|
[\#5438](https://github.com/matrix-org/matrix-react-sdk/pull/5438)
|
||||||
|
* New Look in-Call View
|
||||||
|
[\#5432](https://github.com/matrix-org/matrix-react-sdk/pull/5432)
|
||||||
|
* Support arbitrary widgets sticking to the screen + sending stickers
|
||||||
|
[\#5435](https://github.com/matrix-org/matrix-react-sdk/pull/5435)
|
||||||
|
* Auth typescripting and validation tweaks
|
||||||
|
[\#5433](https://github.com/matrix-org/matrix-react-sdk/pull/5433)
|
||||||
|
* Add new widget API actions for changing rooms and sending/receiving events
|
||||||
|
[\#5385](https://github.com/matrix-org/matrix-react-sdk/pull/5385)
|
||||||
|
* Revert room header click behaviour to opening room settings
|
||||||
|
[\#5434](https://github.com/matrix-org/matrix-react-sdk/pull/5434)
|
||||||
|
* Add option to send/edit a message with Ctrl + Enter / Command + Enter
|
||||||
|
[\#5160](https://github.com/matrix-org/matrix-react-sdk/pull/5160)
|
||||||
|
* Add Analytics instrumentation to the Homepage
|
||||||
|
[\#5409](https://github.com/matrix-org/matrix-react-sdk/pull/5409)
|
||||||
|
* Fix encrypted video playback in Chrome-based browsers
|
||||||
|
[\#5430](https://github.com/matrix-org/matrix-react-sdk/pull/5430)
|
||||||
|
* Add border-radius for video
|
||||||
|
[\#5333](https://github.com/matrix-org/matrix-react-sdk/pull/5333)
|
||||||
|
* Push name to the end, near text, in IRC layout
|
||||||
|
[\#5166](https://github.com/matrix-org/matrix-react-sdk/pull/5166)
|
||||||
|
* Disable notifications for the room you have recently been active in
|
||||||
|
[\#5325](https://github.com/matrix-org/matrix-react-sdk/pull/5325)
|
||||||
|
* Search through the list of unfiltered rooms rather than the rooms in the
|
||||||
|
state which are already filtered by the search text
|
||||||
|
[\#5331](https://github.com/matrix-org/matrix-react-sdk/pull/5331)
|
||||||
|
* Lighten blockquote colour in dark mode
|
||||||
|
[\#5353](https://github.com/matrix-org/matrix-react-sdk/pull/5353)
|
||||||
|
* Specify community description img must be mxc urls
|
||||||
|
[\#5364](https://github.com/matrix-org/matrix-react-sdk/pull/5364)
|
||||||
|
* Add keyboard shortcut to close the current conversation
|
||||||
|
[\#5253](https://github.com/matrix-org/matrix-react-sdk/pull/5253)
|
||||||
|
* Redirect user home from auth screens if they are already logged in
|
||||||
|
[\#5423](https://github.com/matrix-org/matrix-react-sdk/pull/5423)
|
||||||
|
|
||||||
Changes in [3.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.9.0) (2020-11-23)
|
Changes in [3.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.9.0) (2020-11-23)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.9.0-rc.1...v3.9.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.9.0-rc.1...v3.9.0)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.9.0",
|
"version": "3.10.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -58,6 +58,7 @@
|
||||||
"blueimp-canvas-to-blob": "^3.27.0",
|
"blueimp-canvas-to-blob": "^3.27.0",
|
||||||
"browser-encrypt-attachment": "^0.3.0",
|
"browser-encrypt-attachment": "^0.3.0",
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
|
"cheerio": "^1.0.0-rc.3",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"commonmark": "^0.29.1",
|
"commonmark": "^0.29.1",
|
||||||
"counterpart": "^0.18.6",
|
"counterpart": "^0.18.6",
|
||||||
|
@ -77,7 +78,6 @@
|
||||||
"html-entities": "^1.3.1",
|
"html-entities": "^1.3.1",
|
||||||
"is-ip": "^2.0.0",
|
"is-ip": "^2.0.0",
|
||||||
"katex": "^0.12.0",
|
"katex": "^0.12.0",
|
||||||
"cheerio": "^1.0.0-rc.3",
|
|
||||||
"linkifyjs": "^2.1.9",
|
"linkifyjs": "^2.1.9",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.19",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
|
@ -159,6 +159,7 @@
|
||||||
"lolex": "^5.1.2",
|
"lolex": "^5.1.2",
|
||||||
"matrix-mock-request": "^1.2.3",
|
"matrix-mock-request": "^1.2.3",
|
||||||
"matrix-react-test-utils": "^0.2.2",
|
"matrix-react-test-utils": "^0.2.2",
|
||||||
|
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||||
"react-test-renderer": "^16.13.1",
|
"react-test-renderer": "^16.13.1",
|
||||||
"rimraf": "^2.7.1",
|
"rimraf": "^2.7.1",
|
||||||
"stylelint": "^9.10.1",
|
"stylelint": "^9.10.1",
|
||||||
|
|
|
@ -170,7 +170,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||||
border: 1px solid rgba($primary-fg-color, .1);
|
border: 1px solid rgba($primary-fg-color, .1);
|
||||||
// these things should probably not be defined globally
|
// these things should probably not be defined globally
|
||||||
margin: 9px;
|
margin: 9px;
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_textinput {
|
.mx_textinput {
|
||||||
|
|
|
@ -81,6 +81,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Login_underlinedServerName {
|
.mx_Login_underlinedServerName {
|
||||||
|
width: max-content;
|
||||||
border-bottom: 1px dashed $accent-color;
|
border-bottom: 1px dashed $accent-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_GroupMemberList_query,
|
||||||
|
.mx_GroupRoomList_query {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MemberList_chevron {
|
.mx_MemberList_chevron {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 35px;
|
right: 35px;
|
||||||
|
@ -59,10 +64,8 @@ limitations under the License.
|
||||||
flex: 1 1 0px;
|
flex: 1 1 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberList_query,
|
.mx_MemberList_query {
|
||||||
.mx_GroupMemberList_query,
|
height: 16px;
|
||||||
.mx_GroupRoomList_query {
|
|
||||||
flex: 1 1 0;
|
|
||||||
|
|
||||||
// stricter rule to override the one in _common.scss
|
// stricter rule to override the one in _common.scss
|
||||||
&[type="text"] {
|
&[type="text"] {
|
||||||
|
@ -70,10 +73,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberList_query {
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MemberList_wrapper {
|
.mx_MemberList_wrapper {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ limitations under the License.
|
||||||
background-color: $voipcall-plinth-color;
|
background-color: $voipcall-plinth-color;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
|
margin: 5px 5px 5px 18px;
|
||||||
// XXX: CallContainer sets pointer-events: none - should probably be set back in a better place
|
// XXX: CallContainer sets pointer-events: none - should probably be set back in a better place
|
||||||
pointer-events: initial;
|
pointer-events: initial;
|
||||||
}
|
}
|
||||||
|
@ -135,9 +136,9 @@ limitations under the License.
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
color: $accent-fg-color;
|
color: $accent-fg-color;
|
||||||
font-weight: bold;
|
|
||||||
.mx_AccessibleButton_hasKind {
|
.mx_AccessibleButton_hasKind {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,6 +220,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CallView_header_callType {
|
.mx_CallView_header_callType {
|
||||||
|
font-size: 1.2rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
app-tests.sh
|
|
|
@ -416,14 +416,14 @@ export default class CallHandler {
|
||||||
title = _t("Unable to access microphone");
|
title = _t("Unable to access microphone");
|
||||||
description = <div>
|
description = <div>
|
||||||
{_t(
|
{_t(
|
||||||
"Call failed because no microphone could not be accessed. " +
|
"Call failed because microphone could not be accessed. " +
|
||||||
"Check that a microphone is plugged in and set up correctly.",
|
"Check that a microphone is plugged in and set up correctly.",
|
||||||
)}
|
)}
|
||||||
</div>;
|
</div>;
|
||||||
} else if (call.type === CallType.Video) {
|
} else if (call.type === CallType.Video) {
|
||||||
title = _t("Unable to access webcam / microphone");
|
title = _t("Unable to access webcam / microphone");
|
||||||
description = <div>
|
description = <div>
|
||||||
{_t("Call failed because no webcam or microphone could not be accessed. Check that:")}
|
{_t("Call failed because webcam or microphone could not be accessed. Check that:")}
|
||||||
<ul>
|
<ul>
|
||||||
<li>{_t("A microphone and webcam are plugged in and set up correctly")}</li>
|
<li>{_t("A microphone and webcam are plugged in and set up correctly")}</li>
|
||||||
<li>{_t("Permission is granted to use the webcam")}</li>
|
<li>{_t("Permission is granted to use the webcam")}</li>
|
||||||
|
|
|
@ -46,6 +46,7 @@ import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from
|
||||||
import SdkConfig from "./SdkConfig";
|
import SdkConfig from "./SdkConfig";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import {UIFeature} from "./settings/UIFeature";
|
import {UIFeature} from "./settings/UIFeature";
|
||||||
|
import {CHAT_EFFECTS} from "./effects"
|
||||||
import CallHandler from "./CallHandler";
|
import CallHandler from "./CallHandler";
|
||||||
|
|
||||||
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
||||||
|
@ -78,6 +79,7 @@ export const CommandCategories = {
|
||||||
"actions": _td("Actions"),
|
"actions": _td("Actions"),
|
||||||
"admin": _td("Admin"),
|
"admin": _td("Admin"),
|
||||||
"advanced": _td("Advanced"),
|
"advanced": _td("Advanced"),
|
||||||
|
"effects": _td("Effects"),
|
||||||
"other": _td("Other"),
|
"other": _td("Other"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1094,6 +1096,30 @@ export const Commands = [
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
hideCompletionAfterSpace: true,
|
hideCompletionAfterSpace: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
...CHAT_EFFECTS.map((effect) => {
|
||||||
|
return new Command({
|
||||||
|
command: effect.command,
|
||||||
|
description: effect.description(),
|
||||||
|
args: '<message>',
|
||||||
|
runFn: function(roomId, args) {
|
||||||
|
return success((async () => {
|
||||||
|
if (!args) {
|
||||||
|
args = effect.fallbackMessage();
|
||||||
|
MatrixClientPeg.get().sendEmoteMessage(roomId, args);
|
||||||
|
} else {
|
||||||
|
const content = {
|
||||||
|
msgtype: effect.msgType,
|
||||||
|
body: args,
|
||||||
|
};
|
||||||
|
MatrixClientPeg.get().sendMessage(roomId, content);
|
||||||
|
}
|
||||||
|
dis.dispatch({action: `effects.${effect.command}`});
|
||||||
|
})());
|
||||||
|
},
|
||||||
|
category: CommandCategories.effects,
|
||||||
|
})
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
// build a map from names and aliases to the Command objects.
|
// build a map from names and aliases to the Command objects.
|
||||||
|
|
|
@ -69,11 +69,15 @@ import AuxPanel from "../views/rooms/AuxPanel";
|
||||||
import RoomHeader from "../views/rooms/RoomHeader";
|
import RoomHeader from "../views/rooms/RoomHeader";
|
||||||
import {XOR} from "../../@types/common";
|
import {XOR} from "../../@types/common";
|
||||||
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||||
|
import EffectsOverlay from "../views/elements/EffectsOverlay";
|
||||||
|
import {containsEmoji} from '../../effects/utils';
|
||||||
|
import {CHAT_EFFECTS} from '../../effects';
|
||||||
import { CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
import { CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import WidgetStore from "../../stores/WidgetStore";
|
import WidgetStore from "../../stores/WidgetStore";
|
||||||
import {UPDATE_EVENT} from "../../stores/AsyncStore";
|
import {UPDATE_EVENT} from "../../stores/AsyncStore";
|
||||||
import Notifier from "../../Notifier";
|
import Notifier from "../../Notifier";
|
||||||
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
||||||
|
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
let debuglog = function(msg: string) {};
|
let debuglog = function(msg: string) {};
|
||||||
|
@ -248,6 +252,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
this.context.on("userTrustStatusChanged", this.onUserVerificationChanged);
|
this.context.on("userTrustStatusChanged", this.onUserVerificationChanged);
|
||||||
this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged);
|
this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged);
|
||||||
|
this.context.on("Event.decrypted", this.onEventDecrypted);
|
||||||
|
this.context.on("event", this.onEvent);
|
||||||
// Start listening for RoomViewStore updates
|
// Start listening for RoomViewStore updates
|
||||||
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
|
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
|
||||||
this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate);
|
this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate);
|
||||||
|
@ -581,6 +587,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
|
this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
|
||||||
this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged);
|
this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged);
|
||||||
|
this.context.removeListener("Event.decrypted", this.onEventDecrypted);
|
||||||
|
this.context.removeListener("event", this.onEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.removeEventListener('beforeunload', this.onPageUnload);
|
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||||
|
@ -781,6 +789,30 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onEventDecrypted = (ev) => {
|
||||||
|
if (ev.isDecryptionFailure()) return;
|
||||||
|
this.handleEffects(ev);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onEvent = (ev) => {
|
||||||
|
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return;
|
||||||
|
this.handleEffects(ev);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleEffects = (ev) => {
|
||||||
|
if (!this.state.room || !this.state.matrixClientIsReady) return; // not ready at all
|
||||||
|
if (ev.getRoomId() !== this.state.room.roomId) return; // not for us
|
||||||
|
|
||||||
|
const notifState = RoomNotificationStateStore.instance.getRoomState(this.state.room);
|
||||||
|
if (!notifState.isUnread) return;
|
||||||
|
|
||||||
|
CHAT_EFFECTS.forEach(effect => {
|
||||||
|
if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
|
||||||
|
dis.dispatch({action: `effects.${effect.command}`});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private onRoomName = (room: Room) => {
|
private onRoomName = (room: Room) => {
|
||||||
if (this.state.room && room.roomId == this.state.room.roomId) {
|
if (this.state.room && room.roomId == this.state.room.roomId) {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
@ -1946,9 +1978,14 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
mx_RoomView_inCall: Boolean(activeCall),
|
mx_RoomView_inCall: Boolean(activeCall),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showChatEffects = SettingsStore.getValue('showChatEffects');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoomContext.Provider value={this.state}>
|
<RoomContext.Provider value={this.state}>
|
||||||
<main className={mainClasses} ref={this.roomView} onKeyDown={this.onReactKeyDown}>
|
<main className={mainClasses} ref={this.roomView} onKeyDown={this.onReactKeyDown}>
|
||||||
|
{showChatEffects && this.roomView.current &&
|
||||||
|
<EffectsOverlay roomWidth={this.roomView.current.offsetWidth} />
|
||||||
|
}
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<RoomHeader
|
<RoomHeader
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
|
|
|
@ -149,7 +149,7 @@ export default class MessageContextMenu extends React.Component {
|
||||||
onRedactClick = () => {
|
onRedactClick = () => {
|
||||||
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
|
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
|
||||||
Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
|
Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
|
||||||
onFinished: async (proceed) => {
|
onFinished: async (proceed, reason) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
@ -157,6 +157,8 @@ export default class MessageContextMenu extends React.Component {
|
||||||
await cli.redactEvent(
|
await cli.redactEvent(
|
||||||
this.props.mxEvent.getRoomId(),
|
this.props.mxEvent.getRoomId(),
|
||||||
this.props.mxEvent.getId(),
|
this.props.mxEvent.getId(),
|
||||||
|
undefined,
|
||||||
|
reason ? { reason } : {},
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const code = e.errcode || e.statusCode;
|
const code = e.errcode || e.statusCode;
|
||||||
|
|
|
@ -23,15 +23,17 @@ import { _t } from '../../../languageHandler';
|
||||||
*/
|
*/
|
||||||
export default class ConfirmRedactDialog extends React.Component {
|
export default class ConfirmRedactDialog extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
|
const TextInputDialog = sdk.getComponent('views.dialogs.TextInputDialog');
|
||||||
return (
|
return (
|
||||||
<QuestionDialog onFinished={this.props.onFinished}
|
<TextInputDialog onFinished={this.props.onFinished}
|
||||||
title={_t("Confirm Removal")}
|
title={_t("Confirm Removal")}
|
||||||
description={
|
description={
|
||||||
_t("Are you sure you wish to remove (delete) this event? " +
|
_t("Are you sure you wish to remove (delete) this event? " +
|
||||||
"Note that if you delete a room name or topic change, it could undo the change.")}
|
"Note that if you delete a room name or topic change, it could undo the change.")}
|
||||||
|
placeholder={_t("Reason (optional)")}
|
||||||
|
focus
|
||||||
button={_t("Remove")}>
|
button={_t("Remove")}>
|
||||||
</QuestionDialog>
|
</TextInputDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from "react";
|
||||||
|
import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
|
||||||
|
|
||||||
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||||
import BaseDialog from './BaseDialog';
|
import BaseDialog from './BaseDialog';
|
||||||
|
@ -47,9 +48,20 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
||||||
|
|
||||||
const config = SdkConfig.get();
|
const config = SdkConfig.get();
|
||||||
this.defaultServer = config["validated_server_config"] as ValidatedServerConfig;
|
this.defaultServer = config["validated_server_config"] as ValidatedServerConfig;
|
||||||
|
const { serverConfig } = this.props;
|
||||||
|
|
||||||
|
let otherHomeserver = "";
|
||||||
|
if (!serverConfig.isDefault) {
|
||||||
|
if (serverConfig.isNameResolvable && serverConfig.hsName) {
|
||||||
|
otherHomeserver = serverConfig.hsName;
|
||||||
|
} else {
|
||||||
|
otherHomeserver = serverConfig.hsUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
defaultChosen: this.props.serverConfig.isDefault,
|
defaultChosen: serverConfig.isDefault,
|
||||||
otherHomeserver: this.props.serverConfig.isDefault ? "" : this.props.serverConfig.hsUrl,
|
otherHomeserver,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,10 +81,25 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
||||||
// If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
|
// If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
|
||||||
// find their homeserver without demanding they use "https://matrix.org"
|
// find their homeserver without demanding they use "https://matrix.org"
|
||||||
private validate = withValidation<this, { error?: string }>({
|
private validate = withValidation<this, { error?: string }>({
|
||||||
deriveData: async ({ value: hsUrl }) => {
|
deriveData: async ({ value }) => {
|
||||||
// Always try and use the defaults first
|
let hsUrl = value.trim(); // trim to account for random whitespace
|
||||||
const defaultConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"];
|
|
||||||
if (defaultConfig.hsUrl === hsUrl) return {};
|
// if the URL has no protocol, try validate it as a serverName via well-known
|
||||||
|
if (!hsUrl.includes("://")) {
|
||||||
|
try {
|
||||||
|
const discoveryResult = await AutoDiscovery.findClientConfig(hsUrl);
|
||||||
|
this.validatedConf = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(hsUrl, discoveryResult);
|
||||||
|
return {}; // we have a validated config, we don't need to try the other paths
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Attempted ${hsUrl} as a server_name but it failed`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got to this stage then either the well-known failed or the URL had a protocol specified,
|
||||||
|
// so validate statically only. If the URL has no protocol, default to https.
|
||||||
|
if (!hsUrl.includes("://")) {
|
||||||
|
hsUrl = "https://" + hsUrl;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl);
|
this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl);
|
||||||
|
@ -81,17 +108,22 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
||||||
const stateForError = AutoDiscoveryUtils.authComponentStateForError(e);
|
const stateForError = AutoDiscoveryUtils.authComponentStateForError(e);
|
||||||
if (!stateForError.isFatalError) {
|
if (stateForError.isFatalError) {
|
||||||
// carry on anyway
|
let error = _t("Unable to validate homeserver");
|
||||||
this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, null, true);
|
|
||||||
return {};
|
|
||||||
} else {
|
|
||||||
let error = _t("Unable to validate homeserver/identity server");
|
|
||||||
if (e.translatedMessage) {
|
if (e.translatedMessage) {
|
||||||
error = e.translatedMessage;
|
error = e.translatedMessage;
|
||||||
}
|
}
|
||||||
return { error };
|
return { error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try to carry on anyway
|
||||||
|
try {
|
||||||
|
this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, null, true);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return { error: _t("Invalid URL") };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rules: [
|
rules: [
|
||||||
|
@ -153,7 +185,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
||||||
>
|
>
|
||||||
<form className="mx_Dialog_content" id="mx_ServerPickerDialog" onSubmit={this.onSubmit}>
|
<form className="mx_Dialog_content" id="mx_ServerPickerDialog" onSubmit={this.onSubmit}>
|
||||||
<p>
|
<p>
|
||||||
{_t("We call the places you where you can host your account ‘homeservers’.")} {text}
|
{_t("We call the places where you can host your account ‘homeservers’.")} {text}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<StyledRadioButton
|
<StyledRadioButton
|
||||||
|
|
|
@ -146,7 +146,7 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
||||||
const events = this.props.target.getLiveTimeline().getEvents();
|
const events = this.props.target.getLiveTimeline().getEvents();
|
||||||
matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId());
|
matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId());
|
||||||
} else {
|
} else {
|
||||||
matrixToUrl = this.state.permalinkCreator.forRoom();
|
matrixToUrl = this.state.permalinkCreator.forShareableRoom();
|
||||||
}
|
}
|
||||||
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
|
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
|
||||||
matrixToUrl = makeUserPermalink(this.props.target.userId);
|
matrixToUrl = makeUserPermalink(this.props.target.userId);
|
||||||
|
|
94
src/components/views/elements/EffectsOverlay.tsx
Normal file
94
src/components/views/elements/EffectsOverlay.tsx
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
import React, { FunctionComponent, useEffect, useRef } from 'react';
|
||||||
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
|
import ICanvasEffect from '../../../effects/ICanvasEffect';
|
||||||
|
import {CHAT_EFFECTS} from '../../../effects'
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
roomWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const effectsRef = useRef<Map<string, ICanvasEffect>>(new Map<string, ICanvasEffect>());
|
||||||
|
|
||||||
|
const lazyLoadEffectModule = async (name: string): Promise<ICanvasEffect> => {
|
||||||
|
if (!name) return null;
|
||||||
|
let effect: ICanvasEffect | null = effectsRef.current[name] || null;
|
||||||
|
if (effect === null) {
|
||||||
|
const options = CHAT_EFFECTS.find((e) => e.command === name)?.options
|
||||||
|
try {
|
||||||
|
const { default: Effect } = await import(`../../../effects/${name}`);
|
||||||
|
effect = new Effect(options);
|
||||||
|
effectsRef.current[name] = effect;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Unable to load effect module at \'../../../effects/${name}\'.', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return effect;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const resize = () => {
|
||||||
|
if (canvasRef.current) {
|
||||||
|
canvasRef.current.height = window.innerHeight;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onAction = (payload: { action: string }) => {
|
||||||
|
const actionPrefix = 'effects.';
|
||||||
|
if (payload.action.indexOf(actionPrefix) === 0) {
|
||||||
|
const effect = payload.action.substr(actionPrefix.length);
|
||||||
|
lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dispatcherRef = dis.register(onAction);
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
window.addEventListener('resize', resize, true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dis.unregister(dispatcherRef);
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored
|
||||||
|
for (const effect in currentEffects) {
|
||||||
|
const effectModule: ICanvasEffect = currentEffects[effect];
|
||||||
|
if (effectModule && effectModule.isRunning) {
|
||||||
|
effectModule.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
width={roomWidth}
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
zIndex: 999999,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EffectsOverlay;
|
|
@ -67,7 +67,7 @@ const ServerPicker = ({ title, dialogTitle, serverConfig, onServerConfigChange }
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let serverName = serverConfig.hsName;
|
let serverName = serverConfig.isNameResolvable ? serverConfig.hsName : serverConfig.hsUrl;
|
||||||
if (serverConfig.hsNameIsDifferent) {
|
if (serverConfig.hsNameIsDifferent) {
|
||||||
serverName = <TextWithTooltip class="mx_Login_underlinedServerName" tooltip={serverConfig.hsUrl}>
|
serverName = <TextWithTooltip class="mx_Login_underlinedServerName" tooltip={serverConfig.hsUrl}>
|
||||||
{serverConfig.hsName}
|
{serverConfig.hsName}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import RoomContext from "../../../contexts/RoomContext";
|
||||||
import {UIFeature} from "../../../settings/UIFeature";
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
import {ChevronFace, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu";
|
import {ChevronFace, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu";
|
||||||
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
|
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
|
||||||
|
import {useRoomMemberCount} from "../../../hooks/useRoomMembers";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -210,14 +211,6 @@ const onRoomSettingsClick = () => {
|
||||||
defaultDispatcher.dispatch({ action: "open_room_settings" });
|
defaultDispatcher.dispatch({ action: "open_room_settings" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const useMemberCount = (room: Room) => {
|
|
||||||
const [count, setCount] = useState(room.getJoinedMembers().length);
|
|
||||||
useEventEmitter(room.currentState, "RoomState.members", () => {
|
|
||||||
setCount(room.getJoinedMembers().length);
|
|
||||||
});
|
|
||||||
return count;
|
|
||||||
};
|
|
||||||
|
|
||||||
const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
|
const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
|
@ -251,7 +244,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
|
||||||
const memberCount = useMemberCount(room);
|
const memberCount = useRoomMemberCount(room);
|
||||||
|
|
||||||
return <BaseCard header={header} className="mx_RoomSummaryCard" onClose={onClose}>
|
return <BaseCard header={header} className="mx_RoomSummaryCard" onClose={onClose}>
|
||||||
<Group title={_t("About")} className="mx_RoomSummaryCard_aboutGroup">
|
<Group title={_t("About")} className="mx_RoomSummaryCard_aboutGroup">
|
||||||
|
|
|
@ -60,8 +60,9 @@ const NewRoomIntro = () => {
|
||||||
{ caption && <p>{ caption }</p> }
|
{ caption && <p>{ caption }</p> }
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
} else {
|
} else {
|
||||||
|
const inRoom = room && room.getMyMembership() === "join";
|
||||||
const topic = room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic;
|
const topic = room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic;
|
||||||
const canAddTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getUserId());
|
const canAddTopic = inRoom && room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getUserId());
|
||||||
|
|
||||||
const onTopicClick = () => {
|
const onTopicClick = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
|
@ -99,9 +100,25 @@ const NewRoomIntro = () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInviteClick = () => {
|
let canInvite = inRoom;
|
||||||
dis.dispatch({ action: "view_invite", roomId });
|
const powerLevels = room.currentState.getStateEvents(EventType.RoomPowerLevels, "")?.getContent();
|
||||||
};
|
const me = room.getMember(cli.getUserId());
|
||||||
|
if (powerLevels && me && powerLevels.invite > me.powerLevel) {
|
||||||
|
canInvite = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons;
|
||||||
|
if (canInvite) {
|
||||||
|
const onInviteClick = () => {
|
||||||
|
dis.dispatch({ action: "view_invite", roomId });
|
||||||
|
};
|
||||||
|
|
||||||
|
buttons = <div className="mx_NewRoomIntro_buttons">
|
||||||
|
<AccessibleButton className="mx_NewRoomIntro_inviteButton" kind="primary" onClick={onInviteClick}>
|
||||||
|
{_t("Invite to this room")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
const avatarUrl = room.currentState.getStateEvents(EventType.RoomAvatar, "")?.getContent()?.url;
|
const avatarUrl = room.currentState.getStateEvents(EventType.RoomAvatar, "")?.getContent()?.url;
|
||||||
body = <React.Fragment>
|
body = <React.Fragment>
|
||||||
|
@ -119,11 +136,7 @@ const NewRoomIntro = () => {
|
||||||
roomName: () => <b>{ room.name }</b>,
|
roomName: () => <b>{ room.name }</b>,
|
||||||
})}</p>
|
})}</p>
|
||||||
<p>{topicText}</p>
|
<p>{topicText}</p>
|
||||||
<div className="mx_NewRoomIntro_buttons">
|
{ buttons }
|
||||||
<AccessibleButton className="mx_NewRoomIntro_inviteButton" kind="primary" onClick={onInviteClick}>
|
|
||||||
{_t("Invite to this room")}
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,8 @@ import {Key, isOnlyCtrlOrCmdKeyEvent} from "../../../Keyboard";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import RateLimitedFunc from '../../../ratelimitedfunc';
|
import RateLimitedFunc from '../../../ratelimitedfunc';
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
|
import {containsEmoji} from "../../../effects/utils";
|
||||||
|
import {CHAT_EFFECTS} from '../../../effects';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||||
|
|
||||||
|
@ -326,6 +328,11 @@ export default class SendMessageComposer extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dis.dispatch({action: "message_sent"});
|
dis.dispatch({action: "message_sent"});
|
||||||
|
CHAT_EFFECTS.forEach((effect) => {
|
||||||
|
if (containsEmoji(content, effect.emojis)) {
|
||||||
|
dis.dispatch({action: `effects.${effect.command}`});
|
||||||
|
}
|
||||||
|
});
|
||||||
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content);
|
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
|
||||||
'showAvatarChanges',
|
'showAvatarChanges',
|
||||||
'showDisplaynameChanges',
|
'showDisplaynameChanges',
|
||||||
'showImages',
|
'showImages',
|
||||||
|
'showChatEffects',
|
||||||
'Pill.shouldShowPillAvatar',
|
'Pill.shouldShowPillAvatar',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
47
src/effects/ICanvasEffect.ts
Normal file
47
src/effects/ICanvasEffect.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Defines the constructor of a canvas based room effect
|
||||||
|
*/
|
||||||
|
export interface ICanvasEffectConstructable {
|
||||||
|
/**
|
||||||
|
* @param {{[key:string]:any}} options? Optional animation options
|
||||||
|
* @returns ICanvasEffect Returns a new instance of the canvas effect
|
||||||
|
*/
|
||||||
|
new(options?: { [key: string]: any }): ICanvasEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the interface of a canvas based room effect
|
||||||
|
*/
|
||||||
|
export default interface ICanvasEffect {
|
||||||
|
/**
|
||||||
|
* @param {HTMLCanvasElement} canvas The canvas instance as the render target of the animation
|
||||||
|
* @param {number} timeout? A timeout that defines the runtime of the animation (defaults to false)
|
||||||
|
*/
|
||||||
|
start: (canvas: HTMLCanvasElement, timeout?: number) => Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the current animation
|
||||||
|
*/
|
||||||
|
stop: () => Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a value that defines if the animation is currently running
|
||||||
|
*/
|
||||||
|
isRunning: boolean;
|
||||||
|
}
|
191
src/effects/confetti/index.ts
Normal file
191
src/effects/confetti/index.ts
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
import ICanvasEffect from '../ICanvasEffect';
|
||||||
|
|
||||||
|
export 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 ConfettiParticle = {
|
||||||
|
color: string,
|
||||||
|
color2: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
diameter: number,
|
||||||
|
tilt: number,
|
||||||
|
tiltAngleIncrement: number,
|
||||||
|
tiltAngle: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultOptions: ConfettiOptions = {
|
||||||
|
maxCount: 150,
|
||||||
|
speed: 3,
|
||||||
|
frameInterval: 15,
|
||||||
|
alpha: 1.0,
|
||||||
|
gradient: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Confetti implements ICanvasEffect {
|
||||||
|
private readonly options: ConfettiOptions;
|
||||||
|
|
||||||
|
constructor(options: { [key: string]: any }) {
|
||||||
|
this.options = {...DefaultOptions, ...options};
|
||||||
|
}
|
||||||
|
|
||||||
|
private context: CanvasRenderingContext2D | null = null;
|
||||||
|
private supportsAnimationFrame = window.requestAnimationFrame;
|
||||||
|
private colors = ['rgba(30,144,255,', 'rgba(107,142,35,', 'rgba(255,215,0,',
|
||||||
|
'rgba(255,192,203,', 'rgba(106,90,205,', 'rgba(173,216,230,',
|
||||||
|
'rgba(238,130,238,', 'rgba(152,251,152,', 'rgba(70,130,180,',
|
||||||
|
'rgba(244,164,96,', 'rgba(210,105,30,', 'rgba(220,20,60,'];
|
||||||
|
|
||||||
|
private lastFrameTime = Date.now();
|
||||||
|
private particles: Array<ConfettiParticle> = [];
|
||||||
|
private waveAngle = 0;
|
||||||
|
|
||||||
|
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 ConfettiParticle, canvas.width, canvas.height));
|
||||||
|
}
|
||||||
|
this.isRunning = true;
|
||||||
|
this.runAnimation();
|
||||||
|
if (timeout) {
|
||||||
|
window.setTimeout(this.stop, timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop = async () => {
|
||||||
|
this.isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => {
|
||||||
|
particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')');
|
||||||
|
if (this.options.gradient) {
|
||||||
|
particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')');
|
||||||
|
} else {
|
||||||
|
particle.color2 = particle.color;
|
||||||
|
}
|
||||||
|
particle.x = Math.random() * width;
|
||||||
|
particle.y = Math.random() * -height;
|
||||||
|
particle.diameter = Math.random() * 10 + 5;
|
||||||
|
particle.tilt = Math.random() * -10;
|
||||||
|
particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05;
|
||||||
|
particle.tiltAngle = Math.random() * Math.PI;
|
||||||
|
return particle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private runAnimation = (): 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 now = Date.now();
|
||||||
|
const delta = now - this.lastFrameTime;
|
||||||
|
if (!this.supportsAnimationFrame || delta > this.options.frameInterval) {
|
||||||
|
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
||||||
|
this.updateParticles();
|
||||||
|
this.drawParticles(this.context);
|
||||||
|
this.lastFrameTime = now - (delta % this.options.frameInterval);
|
||||||
|
}
|
||||||
|
requestAnimationFrame(this.runAnimation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private drawParticles = (context: CanvasRenderingContext2D): void => {
|
||||||
|
if (!this.context || !this.context.canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let x; let x2; let y2;
|
||||||
|
for (const particle of this.particles) {
|
||||||
|
this.context.beginPath();
|
||||||
|
context.lineWidth = particle.diameter;
|
||||||
|
x2 = particle.x + particle.tilt;
|
||||||
|
x = x2 + particle.diameter / 2;
|
||||||
|
y2 = particle.y + particle.tilt + particle.diameter / 2;
|
||||||
|
if (this.options.gradient) {
|
||||||
|
const gradient = context.createLinearGradient(x, particle.y, x2, y2);
|
||||||
|
gradient.addColorStop(0, particle.color);
|
||||||
|
gradient.addColorStop(1.0, particle.color2);
|
||||||
|
context.strokeStyle = gradient;
|
||||||
|
} else {
|
||||||
|
context.strokeStyle = particle.color;
|
||||||
|
}
|
||||||
|
context.moveTo(x, particle.y);
|
||||||
|
context.lineTo(x2, y2);
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateParticles = () => {
|
||||||
|
if (!this.context || !this.context.canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const width = this.context.canvas.width;
|
||||||
|
const height = this.context.canvas.height;
|
||||||
|
let particle: ConfettiParticle;
|
||||||
|
this.waveAngle += 0.01;
|
||||||
|
for (let i = 0; i < this.particles.length; i++) {
|
||||||
|
particle = this.particles[i];
|
||||||
|
if (!this.isRunning && particle.y < -15) {
|
||||||
|
particle.y = height + 100;
|
||||||
|
} else {
|
||||||
|
particle.tiltAngle += particle.tiltAngleIncrement;
|
||||||
|
particle.x += Math.sin(this.waveAngle) - 0.5;
|
||||||
|
particle.y += (Math.cos(this.waveAngle) + particle.diameter + this.options.speed) * 0.5;
|
||||||
|
particle.tilt = Math.sin(particle.tiltAngle) * 15;
|
||||||
|
}
|
||||||
|
if (particle.x > width + 20 || particle.x < -20 || particle.y > height) {
|
||||||
|
if (this.isRunning && this.particles.length <= this.options.maxCount) {
|
||||||
|
this.resetParticle(particle, width, height);
|
||||||
|
} else {
|
||||||
|
this.particles.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
src/effects/index.ts
Normal file
89
src/effects/index.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration defines room effects that can be triggered by custom message types and emojis
|
||||||
|
*/
|
||||||
|
export const CHAT_EFFECTS: Array<Effect<{ [key: string]: any }>> = [
|
||||||
|
{
|
||||||
|
emojis: ['🎊', '🎉'],
|
||||||
|
msgType: 'nic.custom.confetti',
|
||||||
|
command: 'confetti',
|
||||||
|
description: () => _td("Sends the given message with confetti"),
|
||||||
|
fallbackMessage: () => _t("sends confetti") + " 🎉",
|
||||||
|
options: {
|
||||||
|
maxCount: 150,
|
||||||
|
speed: 3,
|
||||||
|
frameInterval: 15,
|
||||||
|
alpha: 1.0,
|
||||||
|
gradient: false,
|
||||||
|
},
|
||||||
|
} as Effect<ConfettiOptions>,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
24
src/effects/utils.ts
Normal file
24
src/effects/utils.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Checks a message if it contains one of the provided emojis
|
||||||
|
* @param {Object} content The message
|
||||||
|
* @param {Array<string>} emojis The list of emojis to check for
|
||||||
|
*/
|
||||||
|
export const containsEmoji = (content: { msgtype: string, body: string }, emojis: Array<string>): boolean => {
|
||||||
|
return emojis.some((emoji) => content.body && content.body.includes(emoji));
|
||||||
|
}
|
40
src/hooks/useRoomMembers.ts
Normal file
40
src/hooks/useRoomMembers.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {useState} from "react";
|
||||||
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||||
|
|
||||||
|
import {useEventEmitter} from "./useEventEmitter";
|
||||||
|
import {throttle} from "lodash";
|
||||||
|
|
||||||
|
// Hook to simplify watching Matrix Room joined members
|
||||||
|
export const useRoomMembers = (room: Room, throttleWait = 250) => {
|
||||||
|
const [members, setMembers] = useState<RoomMember[]>(room.getJoinedMembers());
|
||||||
|
useEventEmitter(room.currentState, "RoomState.members", throttle(() => {
|
||||||
|
setMembers(room.getJoinedMembers());
|
||||||
|
}, throttleWait, {leading: true, trailing: true}));
|
||||||
|
return members;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hook to simplify watching Matrix Room joined member count
|
||||||
|
export const useRoomMemberCount = (room: Room, throttleWait = 250) => {
|
||||||
|
const [count, setCount] = useState<number>(room.getJoinedMemberCount());
|
||||||
|
useEventEmitter(room.currentState, "RoomState.members", throttle(() => {
|
||||||
|
setCount(room.getJoinedMemberCount());
|
||||||
|
}, throttleWait, {leading: true, trailing: true}));
|
||||||
|
return count;
|
||||||
|
};
|
|
@ -47,9 +47,9 @@
|
||||||
"Try using turn.matrix.org": "Try using turn.matrix.org",
|
"Try using turn.matrix.org": "Try using turn.matrix.org",
|
||||||
"OK": "OK",
|
"OK": "OK",
|
||||||
"Unable to access microphone": "Unable to access microphone",
|
"Unable to access microphone": "Unable to access microphone",
|
||||||
"Call failed because no microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Call failed because no microphone could not be accessed. Check that a microphone is plugged in and set up correctly.",
|
"Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.",
|
||||||
"Unable to access webcam / microphone": "Unable to access webcam / microphone",
|
"Unable to access webcam / microphone": "Unable to access webcam / microphone",
|
||||||
"Call failed because no webcam or microphone could not be accessed. Check that:": "Call failed because no webcam or microphone could not be accessed. Check that:",
|
"Call failed because webcam or microphone could not be accessed. Check that:": "Call failed because webcam or microphone could not be accessed. Check that:",
|
||||||
"A microphone and webcam are plugged in and set up correctly": "A microphone and webcam are plugged in and set up correctly",
|
"A microphone and webcam are plugged in and set up correctly": "A microphone and webcam are plugged in and set up correctly",
|
||||||
"Permission is granted to use the webcam": "Permission is granted to use the webcam",
|
"Permission is granted to use the webcam": "Permission is granted to use the webcam",
|
||||||
"No other application is using the webcam": "No other application is using the webcam",
|
"No other application is using the webcam": "No other application is using the webcam",
|
||||||
|
@ -406,6 +406,7 @@
|
||||||
"Messages": "Messages",
|
"Messages": "Messages",
|
||||||
"Actions": "Actions",
|
"Actions": "Actions",
|
||||||
"Advanced": "Advanced",
|
"Advanced": "Advanced",
|
||||||
|
"Effects": "Effects",
|
||||||
"Other": "Other",
|
"Other": "Other",
|
||||||
"Command error": "Command error",
|
"Command error": "Command error",
|
||||||
"Usage": "Usage",
|
"Usage": "Usage",
|
||||||
|
@ -826,6 +827,7 @@
|
||||||
"Manually verify all remote sessions": "Manually verify all remote sessions",
|
"Manually verify all remote sessions": "Manually verify all remote sessions",
|
||||||
"IRC display name width": "IRC display name width",
|
"IRC display name width": "IRC display name width",
|
||||||
"Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout",
|
"Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout",
|
||||||
|
"Show chat effects": "Show chat effects",
|
||||||
"Collecting app version information": "Collecting app version information",
|
"Collecting app version information": "Collecting app version information",
|
||||||
"Collecting logs": "Collecting logs",
|
"Collecting logs": "Collecting logs",
|
||||||
"Uploading logs": "Uploading logs",
|
"Uploading logs": "Uploading logs",
|
||||||
|
@ -844,6 +846,8 @@
|
||||||
"When rooms are upgraded": "When rooms are upgraded",
|
"When rooms are upgraded": "When rooms are upgraded",
|
||||||
"My Ban List": "My Ban List",
|
"My Ban List": "My Ban List",
|
||||||
"This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!",
|
"This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!",
|
||||||
|
"Sends the given message with confetti": "Sends the given message with confetti",
|
||||||
|
"sends confetti": "sends confetti",
|
||||||
"You held the call <a>Resume</a>": "You held the call <a>Resume</a>",
|
"You held the call <a>Resume</a>": "You held the call <a>Resume</a>",
|
||||||
"%(peerName)s held the call": "%(peerName)s held the call",
|
"%(peerName)s held the call": "%(peerName)s held the call",
|
||||||
"Video Call": "Video Call",
|
"Video Call": "Video Call",
|
||||||
|
@ -1965,6 +1969,7 @@
|
||||||
"Removing…": "Removing…",
|
"Removing…": "Removing…",
|
||||||
"Confirm Removal": "Confirm Removal",
|
"Confirm Removal": "Confirm Removal",
|
||||||
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
||||||
|
"Reason (optional)": "Reason (optional)",
|
||||||
"Clear all data in this session?": "Clear all data in this session?",
|
"Clear all data in this session?": "Clear all data in this session?",
|
||||||
"Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.",
|
"Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.",
|
||||||
"Clear all data": "Clear all data",
|
"Clear all data": "Clear all data",
|
||||||
|
@ -2158,11 +2163,12 @@
|
||||||
"A connection error occurred while trying to contact the server.": "A connection error occurred while trying to contact the server.",
|
"A connection error occurred while trying to contact the server.": "A connection error occurred while trying to contact the server.",
|
||||||
"The server is not configured to indicate what the problem is (CORS).": "The server is not configured to indicate what the problem is (CORS).",
|
"The server is not configured to indicate what the problem is (CORS).": "The server is not configured to indicate what the problem is (CORS).",
|
||||||
"Recent changes that have not yet been received": "Recent changes that have not yet been received",
|
"Recent changes that have not yet been received": "Recent changes that have not yet been received",
|
||||||
"Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server",
|
"Unable to validate homeserver": "Unable to validate homeserver",
|
||||||
|
"Invalid URL": "Invalid URL",
|
||||||
"Specify a homeserver": "Specify a homeserver",
|
"Specify a homeserver": "Specify a homeserver",
|
||||||
"Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.": "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.",
|
"Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.": "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.",
|
||||||
"Sign into your homeserver": "Sign into your homeserver",
|
"Sign into your homeserver": "Sign into your homeserver",
|
||||||
"We call the places you where you can host your account ‘homeservers’.": "We call the places you where you can host your account ‘homeservers’.",
|
"We call the places where you can host your account ‘homeservers’.": "We call the places where you can host your account ‘homeservers’.",
|
||||||
"Other homeserver": "Other homeserver",
|
"Other homeserver": "Other homeserver",
|
||||||
"Use your preferred Matrix homeserver if you have one, or host your own.": "Use your preferred Matrix homeserver if you have one, or host your own.",
|
"Use your preferred Matrix homeserver if you have one, or host your own.": "Use your preferred Matrix homeserver if you have one, or host your own.",
|
||||||
"Learn more": "Learn more",
|
"Learn more": "Learn more",
|
||||||
|
|
|
@ -634,6 +634,11 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
displayName: _td("Enable experimental, compact IRC style layout"),
|
displayName: _td("Enable experimental, compact IRC style layout"),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"showChatEffects": {
|
||||||
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
|
displayName: _td("Show chat effects"),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
"Widgets.pinned": {
|
"Widgets.pinned": {
|
||||||
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
|
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
|
||||||
default: {},
|
default: {},
|
||||||
|
|
|
@ -42,7 +42,7 @@ for (const key of Object.keys(SETTINGS)) {
|
||||||
if (SETTINGS[key].invertedSettingName) {
|
if (SETTINGS[key].invertedSettingName) {
|
||||||
// Invert now so that the rest of the system will invert it back
|
// Invert now so that the rest of the system will invert it back
|
||||||
// to what was intended.
|
// to what was intended.
|
||||||
invertedDefaultSettings[key] = !SETTINGS[key].default;
|
invertedDefaultSettings[SETTINGS[key].invertedSettingName] = !SETTINGS[key].default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ export class ValidatedServerConfig {
|
||||||
isUrl: string;
|
isUrl: string;
|
||||||
|
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
|
// when the server config is based on static URLs the hsName is not resolvable and things may wish to use hsUrl
|
||||||
|
isNameResolvable: boolean;
|
||||||
|
|
||||||
warning: string;
|
warning: string;
|
||||||
}
|
}
|
||||||
|
@ -161,7 +163,7 @@ export default class AutoDiscoveryUtils {
|
||||||
const url = new URL(homeserverUrl);
|
const url = new URL(homeserverUrl);
|
||||||
const serverName = url.hostname;
|
const serverName = url.hostname;
|
||||||
|
|
||||||
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result, syntaxOnly);
|
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result, syntaxOnly, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -179,12 +181,12 @@ export default class AutoDiscoveryUtils {
|
||||||
* input.
|
* input.
|
||||||
* @param {string} serverName The domain name the AutoDiscovery result is for.
|
* @param {string} serverName The domain name the AutoDiscovery result is for.
|
||||||
* @param {*} discoveryResult The AutoDiscovery result.
|
* @param {*} discoveryResult The AutoDiscovery result.
|
||||||
* @param {boolean} syntaxOnly If true, errors relating to liveliness of the servers will
|
* @param {boolean} syntaxOnly If true, errors relating to liveliness of the servers will not be raised.
|
||||||
* not be raised.
|
* @param {boolean} isSynthetic If true, then the discoveryResult was synthesised locally.
|
||||||
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
|
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
|
||||||
*/
|
*/
|
||||||
static buildValidatedConfigFromDiscovery(
|
static buildValidatedConfigFromDiscovery(
|
||||||
serverName: string, discoveryResult, syntaxOnly=false): ValidatedServerConfig {
|
serverName: string, discoveryResult, syntaxOnly=false, isSynthetic=false): ValidatedServerConfig {
|
||||||
if (!discoveryResult || !discoveryResult["m.homeserver"]) {
|
if (!discoveryResult || !discoveryResult["m.homeserver"]) {
|
||||||
// This shouldn't happen without major misconfiguration, so we'll log a bit of information
|
// This shouldn't happen without major misconfiguration, so we'll log a bit of information
|
||||||
// in the log so we can find this bit of codee but otherwise tell teh user "it broke".
|
// in the log so we can find this bit of codee but otherwise tell teh user "it broke".
|
||||||
|
@ -252,6 +254,7 @@ export default class AutoDiscoveryUtils {
|
||||||
isUrl: preferredIdentityUrl,
|
isUrl: preferredIdentityUrl,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
warning: hsResult.error,
|
warning: hsResult.error,
|
||||||
|
isNameResolvable: !isSynthetic,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,6 +129,17 @@ export class RoomPermalinkCreator {
|
||||||
return getPermalinkConstructor().forEvent(this._roomId, eventId, this._serverCandidates);
|
return getPermalinkConstructor().forEvent(this._roomId, eventId, this._serverCandidates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forShareableRoom() {
|
||||||
|
if (this._room) {
|
||||||
|
// Prefer to use canonical alias for permalink if possible
|
||||||
|
const alias = this._room.getCanonicalAlias();
|
||||||
|
if (alias) {
|
||||||
|
return getPermalinkConstructor().forRoom(alias, this._serverCandidates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getPermalinkConstructor().forRoom(this._roomId, this._serverCandidates);
|
||||||
|
}
|
||||||
|
|
||||||
forRoom() {
|
forRoom() {
|
||||||
return getPermalinkConstructor().forRoom(this._roomId, this._serverCandidates);
|
return getPermalinkConstructor().forRoom(this._roomId, this._serverCandidates);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ function mockRoom(roomId, members, serverACL) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
roomId,
|
roomId,
|
||||||
|
getCanonicalAlias: () => roomId,
|
||||||
getJoinedMembers: () => members,
|
getJoinedMembers: () => members,
|
||||||
getMember: (userId) => members.find(m => m.userId === userId),
|
getMember: (userId) => members.find(m => m.userId === userId),
|
||||||
currentState: {
|
currentState: {
|
||||||
|
|
34
yarn.lock
34
yarn.lock
|
@ -1256,10 +1256,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/runtime@^7.11.2":
|
"@babel/runtime@^7.12.5":
|
||||||
version "7.11.2"
|
version "7.12.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
||||||
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
|
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
|
@ -4842,9 +4842,9 @@ has@^1.0.1, has@^1.0.3:
|
||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
highlight.js@^10.1.2:
|
highlight.js@^10.1.2:
|
||||||
version "10.1.2"
|
version "10.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.1.2.tgz#c20db951ba1c22c055010648dfffd7b2a968e00c"
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0"
|
||||||
integrity sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA==
|
integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==
|
||||||
|
|
||||||
hoist-non-react-statics@^3.3.0:
|
hoist-non-react-statics@^3.3.0:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
|
@ -6390,10 +6390,10 @@ log-symbols@^2.0.0, log-symbols@^2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^2.0.1"
|
chalk "^2.0.1"
|
||||||
|
|
||||||
loglevel@^1.7.0:
|
loglevel@^1.7.1:
|
||||||
version "1.7.0"
|
version "1.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
|
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
|
||||||
integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==
|
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
|
||||||
|
|
||||||
lolex@^5.0.0, lolex@^5.1.2:
|
lolex@^5.0.0, lolex@^5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
|
@ -6513,15 +6513,15 @@ mathml-tag-names@^2.0.1:
|
||||||
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||||
version "9.2.0"
|
version "9.3.0"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/6661bde6088e6e43f31198e8532432e162aef33c"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ff6612f9d0aa1a7c08b65a0b41c5ab997506016f"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.11.2"
|
"@babel/runtime" "^7.12.5"
|
||||||
another-json "^0.2.0"
|
another-json "^0.2.0"
|
||||||
browser-request "^0.3.3"
|
browser-request "^0.3.3"
|
||||||
bs58 "^4.0.1"
|
bs58 "^4.0.1"
|
||||||
content-type "^1.0.4"
|
content-type "^1.0.4"
|
||||||
loglevel "^1.7.0"
|
loglevel "^1.7.1"
|
||||||
qs "^6.9.4"
|
qs "^6.9.4"
|
||||||
request "^2.88.2"
|
request "^2.88.2"
|
||||||
unhomoglyph "^1.0.6"
|
unhomoglyph "^1.0.6"
|
||||||
|
@ -6994,6 +6994,10 @@ object.values@^1.1.1:
|
||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
|
|
||||||
|
"olm@https://packages.matrix.org/npm/olm/olm-3.2.1.tgz":
|
||||||
|
version "3.2.1"
|
||||||
|
resolved "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz#d623d76f99c3518dde68be8c86618d68bc7b004a"
|
||||||
|
|
||||||
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
|
|
Loading…
Reference in a new issue