From c6a058fb6fa0af190c117c2f3b5bc01a6eab17e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 19:32:58 +0100 Subject: [PATCH 001/100] Added surround with MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/rooms/BasicMessageComposer.tsx | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 2ececdeaed..cd34e25926 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -418,6 +418,10 @@ export default class BasicMessageEditor extends React.Component }; private onKeyDown = (event: React.KeyboardEvent) => { + const selectionRange = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()); + // trim the range as we want it to exclude leading/trailing spaces + selectionRange.trim(); + const model = this.props.model; const modKey = IS_MAC ? event.metaKey : event.ctrlKey; let handled = false; @@ -471,6 +475,43 @@ export default class BasicMessageEditor extends React.Component }); handled = true; // autocomplete or enter to send below shouldn't have any modifier keys pressed. + } else if (document.getSelection().type != "Caret") { + if (event.key === '(') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "(", ")"); + handled = true; + } else if (event.key === '[') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "[", "]"); + handled = true; + } else if (event.key === '{') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "{", "}"); + handled = true; + } else if (event.key === '<') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "<", ">"); + handled = true; + } else if (event.key === '"') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "\""); + handled = true; + } else if (event.key === '`') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "`"); + handled = true; + } else if (event.key === '\'') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "'"); + handled = true; + } } else { const metaOrAltPressed = event.metaKey || event.altKey; const modifierPressed = metaOrAltPressed || event.shiftKey; From e90f5ddf5b6ac14648d7fc36ecf414a04d668c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 19:36:56 +0100 Subject: [PATCH 002/100] Added a comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/BasicMessageComposer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index cd34e25926..587f13e8c2 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -512,6 +512,7 @@ export default class BasicMessageEditor extends React.Component toggleInlineFormat(selectionRange, "'"); handled = true; } + // Surround selected text with a character } else { const metaOrAltPressed = event.metaKey || event.altKey; const modifierPressed = metaOrAltPressed || event.shiftKey; From b330dd55a0cd61fe9b014d47c6dcfb085e835b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 07:53:09 +0100 Subject: [PATCH 003/100] Hide surround with behind a setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/BasicMessageComposer.tsx | 3 ++- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 1 + src/settings/Settings.ts | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 587f13e8c2..a91e92123b 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -418,6 +418,7 @@ export default class BasicMessageEditor extends React.Component }; private onKeyDown = (event: React.KeyboardEvent) => { + const surroundWith = SettingsStore.getValue("MessageComposerInput.surroundWith"); const selectionRange = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()); // trim the range as we want it to exclude leading/trailing spaces selectionRange.trim(); @@ -475,7 +476,7 @@ export default class BasicMessageEditor extends React.Component }); handled = true; // autocomplete or enter to send below shouldn't have any modifier keys pressed. - } else if (document.getSelection().type != "Caret") { + } else if (surroundWith && document.getSelection().type != "Caret") { if (event.key === '(') { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 4d8493401e..2544c03a22 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -34,6 +34,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'MessageComposerInput.suggestEmoji', 'sendTypingNotifications', 'MessageComposerInput.ctrlEnterToSend', + 'MessageComposerInput.surroundWith', ]; static TIMELINE_SETTINGS = [ diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b239b809fe..ed9b37d632 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -336,6 +336,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: isMac ? _td("Use Command + Enter to send a message") : _td("Use Ctrl + Enter to send a message"), default: false, }, + "MessageComposerInput.surroundWith": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("Use surround with"), + default: false, + }, "MessageComposerInput.autoReplaceEmoji": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Automatically replace plain text Emoji'), From 3f0d7673725f12b99e48b0f2e94c9c0f78f9c5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 07:57:15 +0100 Subject: [PATCH 004/100] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a9d31bb9f2..3af2a62c94 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -816,6 +816,7 @@ "Use Ctrl + F to search": "Use Ctrl + F to search", "Use Command + Enter to send a message": "Use Command + Enter to send a message", "Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message", + "Use surround with": "Use surround with", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", From cf25e15eb6d3071dccb92c07e1f96bd726d56755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= Date: Sat, 12 Jun 2021 12:49:15 +0200 Subject: [PATCH 005/100] Make call control buttons accessible to screen reader users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Peter Vágner --- src/components/views/voip/CallView.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index c084dacaa8..178df246d1 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -441,6 +441,7 @@ export default class CallView extends React.Component { const vidMuteButton = this.props.call.type === CallType.Video ? : null; // The dial pad & 'more' button actions are only relevant in a connected call @@ -450,6 +451,7 @@ export default class CallView extends React.Component { inputRef={this.dialpadButton} onClick={this.onDialpadClick} isExpanded={this.state.showDialpad} + aria-label={_t("Dialpad")} /> :
; const contextMenuButton = this.state.callState === CallState.Connected ? { onClick={this.onMoreClick} inputRef={this.contextMenuButton} isExpanded={this.state.showMoreMenu} + aria-label={_t("More")} /> :
; // in the near future, the dial pad button will go on the left. For now, it's the nothing button @@ -466,6 +469,7 @@ export default class CallView extends React.Component { Date: Sat, 12 Jun 2021 13:53:44 +0200 Subject: [PATCH 006/100] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Peter Vágner --- src/i18n/strings/en_EN.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 874dc11bd2..7df22432de 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -889,6 +889,12 @@ "sends snowfall": "sends snowfall", "Sends the given message with a space themed effect": "Sends the given message with a space themed effect", "sends space invaders": "sends space invaders", + "Start the camera": "Start the camera", + "Stop the camera": "Stop the camera", + "Dialpad": "Dialpad", + "More": "More", + "Unmute the microphone": "Unmute the microphone", + "Mute the microphone": "Mute the microphone", "unknown person": "unknown person", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consulting with %(transferTarget)s. Transfer to %(transferee)s", "You held the call Switch": "You held the call Switch", From a94d11235ed5352b8da0ae87964abf2a00eddb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= Date: Sat, 12 Jun 2021 14:17:10 +0200 Subject: [PATCH 007/100] Changed the buttons to TooltipButtons and added the tooltip for the hangup button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Peter Vágner --- src/components/views/voip/CallView.tsx | 21 ++++++++++++--------- src/i18n/strings/en_EN.json | 1 + 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 178df246d1..66b3f6b2d4 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -25,6 +25,8 @@ import RoomAvatar from "../avatars/RoomAvatar"; import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/webrtc/call'; import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import {isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard'; import {alwaysAboveLeftOf, alwaysAboveRightOf, ChevronFace, ContextMenuButton} from '../../structures/ContextMenu'; import CallContextMenu from '../context_menus/CallContextMenu'; @@ -438,40 +440,40 @@ export default class CallView extends React.Component { mx_CallView_callControls_hidden: !this.state.controlsVisible, }); - const vidMuteButton = this.props.call.type === CallType.Video ? : null; // The dial pad & 'more' button actions are only relevant in a connected call // When not connected, we have to put something there to make the flexbox alignment correct - const dialpadButton = this.state.callState === CallState.Connected ? :
; - const contextMenuButton = this.state.callState === CallState.Connected ? :
; // in the near future, the dial pad button will go on the left. For now, it's the nothing button // because something needs to have margin-right: auto to make the alignment correct. const callControls =
{dialpadButton} - - { dis.dispatch({ @@ -479,6 +481,7 @@ export default class CallView extends React.Component { room_id: callRoomId, }); }} + title={_t("Hangup")} /> {vidMuteButton}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7df22432de..d50348954a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -895,6 +895,7 @@ "More": "More", "Unmute the microphone": "Unmute the microphone", "Mute the microphone": "Mute the microphone", + "Hangup": "Hangup", "unknown person": "unknown person", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consulting with %(transferTarget)s. Transfer to %(transferee)s", "You held the call Switch": "You held the call Switch", From fd7eaddb2d4c170084fbfc6ca3ca4c26f40a57b9 Mon Sep 17 00:00:00 2001 From: pvagner Date: Wed, 16 Jun 2021 10:18:35 +0200 Subject: [PATCH 008/100] Update src/components/views/voip/CallView.tsx Co-authored-by: Michael Telatynski <7t3chguy@googlemail.com> --- src/components/views/voip/CallView.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 66b3f6b2d4..f7f82d4300 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -26,9 +26,13 @@ import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/we import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import {isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard'; -import {alwaysAboveLeftOf, alwaysAboveRightOf, ChevronFace, ContextMenuButton} from '../../structures/ContextMenu'; +import { + alwaysAboveLeftOf, + alwaysAboveRightOf, + ChevronFace, + ContextMenuTooltipButton, +} from '../../structures/ContextMenu'; import CallContextMenu from '../context_menus/CallContextMenu'; import { avatarUrlForMember } from '../../../Avatar'; import DialpadContextMenu from '../context_menus/DialpadContextMenu'; From 38c0cd27163effd85b5c12cd499f6291e1a06c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Jun 2021 08:21:33 +0200 Subject: [PATCH 009/100] Cache surroundWith setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/rooms/BasicMessageComposer.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index f5fddff45b..de72f8a348 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -107,6 +107,7 @@ interface IState { showVisualBell?: boolean; autoComplete?: AutocompleteWrapperModel; completionIndex?: number; + surroundWith: boolean, } @replaceableComponent("views.rooms.BasicMessageEditor") @@ -125,12 +126,14 @@ export default class BasicMessageEditor extends React.Component private readonly emoticonSettingHandle: string; private readonly shouldShowPillAvatarSettingHandle: string; + private readonly surroundWithHandle: string; private readonly historyManager = new HistoryManager(); constructor(props) { super(props); this.state = { showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"), + surroundWith: SettingsStore.getValue("MessageComposerInput.surroundWith"), }; this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null, @@ -138,6 +141,8 @@ export default class BasicMessageEditor extends React.Component this.configureEmoticonAutoReplace(); this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting("Pill.shouldShowPillAvatar", null, this.configureShouldShowPillAvatar); + this.surroundWithHandle = SettingsStore.watchSetting("MessageComposerInput.surroundWith", null, + this.surroundWithSettingChanged); } public componentDidUpdate(prevProps: IProps) { @@ -428,7 +433,6 @@ export default class BasicMessageEditor extends React.Component }; private onKeyDown = (event: React.KeyboardEvent) => { - const surroundWith = SettingsStore.getValue("MessageComposerInput.surroundWith"); const selectionRange = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()); // trim the range as we want it to exclude leading/trailing spaces selectionRange.trim(); @@ -436,7 +440,7 @@ export default class BasicMessageEditor extends React.Component const model = this.props.model; let handled = false; - if (surroundWith && document.getSelection().type != "Caret") { + if (this.state.surroundWith && document.getSelection().type != "Caret") { // Surround selected text with a character if (event.key === '(') { this.historyManager.ensureLastChangesPushed(this.props.model); @@ -628,6 +632,11 @@ export default class BasicMessageEditor extends React.Component this.setState({ showPillAvatar }); }; + private surroundWithSettingChanged = () => { + const surroundWith = SettingsStore.getValue("MessageComposerInput.surroundWith"); + this.setState({ surroundWith }); + }; + componentWillUnmount() { document.removeEventListener("selectionchange", this.onSelectionChange); this.editorRef.current.removeEventListener("input", this.onInput, true); @@ -635,6 +644,7 @@ export default class BasicMessageEditor extends React.Component this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true); SettingsStore.unwatchSetting(this.emoticonSettingHandle); SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle); + SettingsStore.unwatchSetting(this.surroundWithHandle); } componentDidMount() { From a772460f63681b85fbc1ad9d972d329d871cbd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Jun 2021 08:38:01 +0200 Subject: [PATCH 010/100] Simplifie surround with and make it more extensible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/rooms/BasicMessageComposer.tsx | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index de72f8a348..239624f5d8 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -55,6 +55,14 @@ const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.sourc const IS_MAC = navigator.platform.indexOf("Mac") !== -1; +const SURROUND_WITH_CHARACTERS = ["\"", "_", "`", "'", "*", "~", "$"]; +const SURROUND_WITH_DOUBLE_CHARACTERS = new Map([ + ["(", ")"], + ["[", "]"], + ["{", "}"], + ["<", ">"], +]); + function ctrlShortcutLabel(key) { return (IS_MAC ? "⌘" : "Ctrl") + "+" + key; } @@ -441,41 +449,18 @@ export default class BasicMessageEditor extends React.Component let handled = false; if (this.state.surroundWith && document.getSelection().type != "Caret") { - // Surround selected text with a character - if (event.key === '(') { + // This surrounds the selected text with a character. This is + // intentionally left out of the keybinding manager as the keybinds + // here shouldn't be changeable + if (SURROUND_WITH_CHARACTERS.includes(event.key)) { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "(", ")"); + toggleInlineFormat(selectionRange, event.key); handled = true; - } else if (event.key === '[') { + } else if ([...SURROUND_WITH_DOUBLE_CHARACTERS.keys()].includes(event.key)) { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "[", "]"); - handled = true; - } else if (event.key === '{') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "{", "}"); - handled = true; - } else if (event.key === '<') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "<", ">"); - handled = true; - } else if (event.key === '"') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "\""); - handled = true; - } else if (event.key === '`') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "`"); - handled = true; - } else if (event.key === '\'') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "'"); + toggleInlineFormat(selectionRange, event.key, SURROUND_WITH_DOUBLE_CHARACTERS.get(event.key)); handled = true; } } From 3e97847e7de3830e4a63d2d884a374a9091b03eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Jun 2021 08:47:21 +0200 Subject: [PATCH 011/100] Get selection range only if necessary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/BasicMessageComposer.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 239624f5d8..d8a872f1c6 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -441,10 +441,6 @@ export default class BasicMessageEditor extends React.Component }; private onKeyDown = (event: React.KeyboardEvent) => { - const selectionRange = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()); - // trim the range as we want it to exclude leading/trailing spaces - selectionRange.trim(); - const model = this.props.model; let handled = false; @@ -452,6 +448,15 @@ export default class BasicMessageEditor extends React.Component // This surrounds the selected text with a character. This is // intentionally left out of the keybinding manager as the keybinds // here shouldn't be changeable + + const selectionRange = getRangeForSelection( + this.editorRef.current, + this.props.model, + document.getSelection(), + ); + // trim the range as we want it to exclude leading/trailing spaces + selectionRange.trim(); + if (SURROUND_WITH_CHARACTERS.includes(event.key)) { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; From 314ab7a94d7868c6375d3efca8d46d6c4f97ffa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 11:20:20 +0200 Subject: [PATCH 012/100] If there already is a Jitsi widget pin it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 448b1cb780..5138d526dd 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -61,7 +61,6 @@ import Modal from './Modal'; import { _t } from './languageHandler'; import dis from './dispatcher/dispatcher'; import WidgetUtils from './utils/WidgetUtils'; -import WidgetEchoStore from './stores/WidgetEchoStore'; import SettingsStore from './settings/SettingsStore'; import {Jitsi} from "./widgets/Jitsi"; import {WidgetType} from "./widgets/WidgetType"; @@ -88,6 +87,7 @@ import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/ import EventEmitter from 'events'; import SdkConfig from './SdkConfig'; import { ensureDMExists, findDMForUser } from './createRoom'; +import { WidgetLayoutStore, Container } from './stores/widgets/WidgetLayoutStore'; export const PROTOCOL_PSTN = 'm.protocol.pstn'; export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; @@ -940,14 +940,10 @@ export default class CallHandler extends EventEmitter { // prevent double clicking the call button const room = MatrixClientPeg.get().getRoom(roomId); - const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI); - const hasJitsi = currentJitsiWidgets.length > 0 - || WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI); - if (hasJitsi) { - Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, { - title: _t('Call in Progress'), - description: _t('A call is currently being placed!'), - }); + const jitsiWidget = WidgetStore.instance.getApps(roomId).find((app) => WidgetType.JITSI.matches(app.type)); + if (jitsiWidget) { + // If there already is a Jitsi widget pin it + WidgetLayoutStore.instance.moveToContainer(room, jitsiWidget, Container.Top); return; } From ce47662b55e4296ad73cd395109934c4fe689ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 11:23:49 +0200 Subject: [PATCH 013/100] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b88dc79da5..1401fca4ba 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -65,8 +65,6 @@ "You cannot place a call with yourself.": "You cannot place a call with yourself.", "Unable to look up phone number": "Unable to look up phone number", "There was an error looking up the phone number": "There was an error looking up the phone number", - "Call in Progress": "Call in Progress", - "A call is currently being placed!": "A call is currently being placed!", "Permission Required": "Permission Required", "You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room", "End conference": "End conference", From d7b10e2ff4704afc17ed22983f086fa44b22d270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 17:26:06 +0200 Subject: [PATCH 014/100] Simplifie code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/BasicMessageComposer.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index d8a872f1c6..06759d0bf5 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -457,12 +457,7 @@ export default class BasicMessageEditor extends React.Component // trim the range as we want it to exclude leading/trailing spaces selectionRange.trim(); - if (SURROUND_WITH_CHARACTERS.includes(event.key)) { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, event.key); - handled = true; - } else if ([...SURROUND_WITH_DOUBLE_CHARACTERS.keys()].includes(event.key)) { + if ([...SURROUND_WITH_DOUBLE_CHARACTERS.keys(), ...SURROUND_WITH_CHARACTERS].includes(event.key)) { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; toggleInlineFormat(selectionRange, event.key, SURROUND_WITH_DOUBLE_CHARACTERS.get(event.key)); From b2292268bc9a551a1b9ecfe535eafd3b823789d4 Mon Sep 17 00:00:00 2001 From: pvagner Date: Wed, 30 Jun 2021 07:06:10 +0200 Subject: [PATCH 015/100] Update src/components/views/voip/CallView.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 6cb245f7b1..3f959622e7 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -26,7 +26,7 @@ import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/we import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard'; +import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../../Keyboard'; import { alwaysAboveLeftOf, alwaysAboveRightOf, From f53de5de99d3099564022c79e917dfc80c1af622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 22 Jul 2021 18:04:24 +0200 Subject: [PATCH 016/100] Make bubble layout background color less agressive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/dark/css/_dark.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 655492661c..7b83fe0cb2 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -230,7 +230,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28); // Bubble tiles $eventbubble-self-bg: #143A34; $eventbubble-others-bg: #394049; -$eventbubble-bg-hover: #433C23; +$eventbubble-bg-hover: $header-panel-bg-color; $eventbubble-avatar-outline: $bg-color; $eventbubble-reply-color: #C1C6CD; From 6c0e0dc64bbd2d219dff9fae016e5e52c05d2955 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Thu, 22 Jul 2021 21:45:20 -0500 Subject: [PATCH 017/100] Allow all of the URL schemes that Firefox allows Signed-off-by: Aaron Raimist --- src/HtmlUtils.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index af5d2b3019..27556a4012 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -57,7 +57,10 @@ const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i'); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; -export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet', 'matrix']; +export const PERMITTED_URL_SCHEMES = ["bitcoin", "ftp", "geo", "http", "https", "im", "irc", + "ircs", "magnet", "mailto", "matrix", "mms", "news", + "nntp", "openpgp4fpr", "sip", "sftp", "sms", "smsto", + "ssh", "tel", "urn", "webcal", "wtai", "xmpp"]; const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)\/(.+?)(?:[?/]|$)/; From ff37b8cc79205e57969ed44d867b215a492bbf63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 12:47:30 +0200 Subject: [PATCH 018/100] Remove IncomingCallBox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallContainer.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/voip/CallContainer.tsx b/src/components/views/voip/CallContainer.tsx index fa963e4e28..41046b9952 100644 --- a/src/components/views/voip/CallContainer.tsx +++ b/src/components/views/voip/CallContainer.tsx @@ -1,5 +1,6 @@ /* Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 Šimon Brandner Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import IncomingCallBox from './IncomingCallBox'; import CallPreview from './CallPreview'; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -31,7 +31,6 @@ interface IState { export default class CallContainer extends React.PureComponent { public render() { return
-
; } From 07be6dd78065e325f1f31ce9de51efaa2b5c6388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 13:04:06 +0200 Subject: [PATCH 019/100] Allow suppliing whole body to toasts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/ToastContainer.tsx | 22 +++++++++++++------- src/stores/ToastStore.ts | 4 +++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx index b7b0b7c652..75cf4a51fc 100644 --- a/src/components/structures/ToastContainer.tsx +++ b/src/components/structures/ToastContainer.tsx @@ -58,7 +58,7 @@ export default class ToastContainer extends React.Component<{}, IState> { let containerClasses; if (totalCount !== 0) { const topToast = this.state.toasts[0]; - const { title, icon, key, component, className, props } = topToast; + const { title, icon, key, component, className, props, supplyWholeBody } = topToast; const toastClasses = classNames("mx_Toast_toast", { "mx_Toast_hasIcon": icon, [`mx_Toast_icon_${icon}`]: icon, @@ -73,16 +73,22 @@ export default class ToastContainer extends React.Component<{}, IState> { key, toastKey: key, }); - toast = (
-
-

{ title }

- { countIndicator } -
-
{ React.createElement(component, toastProps) }
-
); + + const content = React.createElement(component, toastProps); + + toast = supplyWholeBody + ? content + :
+
+

{ title }

+ { countIndicator } +
+
{ content }
+
; containerClasses = classNames("mx_ToastContainer", { "mx_ToastContainer_stacked": isStacked, + [className]: supplyWholeBody, }); } return toast diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts index 850c3cb026..e831be7203 100644 --- a/src/stores/ToastStore.ts +++ b/src/stores/ToastStore.ts @@ -22,11 +22,13 @@ export interface IToast { key: string; // higher priority number will be shown on top of lower priority priority: number; - title: string; + title?: string; icon?: string; component: C; className?: string; props?: Omit, "toastKey">; // toastKey is injected by ToastContainer + supplyWholeBody?: boolean; + content?: JSX.Element; } /** From 410928745f41e86451f0f27c944577a82a800170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 13:05:14 +0200 Subject: [PATCH 020/100] IncomingCallBox -> IncomingCallToast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/_components.scss | 1 + res/css/views/toasts/_IncomingCallToast.scss | 100 ++++++++++ res/css/views/voip/_CallContainer.scss | 80 -------- src/CallHandler.tsx | 17 ++ src/components/views/voip/IncomingCallBox.tsx | 176 ------------------ src/toasts/IncomingCallToast.tsx | 139 ++++++++++++++ 6 files changed, 257 insertions(+), 256 deletions(-) create mode 100644 res/css/views/toasts/_IncomingCallToast.scss delete mode 100644 src/components/views/voip/IncomingCallBox.tsx create mode 100644 src/toasts/IncomingCallToast.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index f9e3ab1160..b87b45093c 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -263,6 +263,7 @@ @import "./views/spaces/_SpacePublicShare.scss"; @import "./views/terms/_InlineTermsAgreement.scss"; @import "./views/toasts/_AnalyticsToast.scss"; +@import "./views/toasts/_IncomingCallToast.scss"; @import "./views/toasts/_NonUrgentEchoFailureToast.scss"; @import "./views/verification/_VerificationShowSas.scss"; @import "./views/voip/_CallContainer.scss"; diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss new file mode 100644 index 0000000000..5ce99bd11e --- /dev/null +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -0,0 +1,100 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 Šimon Brandner + +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. +*/ + +.mx_IncomingCallToast { + // mx_Toast overrides + padding: 8px !important; + display: unset !important; + top: 8px !important; + border-radius: 8px; + + min-width: 250px; + box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); + background-color: $voipcall-plinth-color; // To match mx_Toast + + pointer-events: initial; // restore pointer events so the user can accept/decline + cursor: pointer; + + .mx_IncomingCallToast_CallerInfo { + display: flex; + direction: row; + + img, .mx_BaseAvatar_initial { + margin: 8px; + } + + > div { + display: flex; + flex-direction: column; + + justify-content: center; + } + + h1, p { + margin: 0px; + padding: 0px; + font-size: $font-14px; + line-height: $font-16px; + } + + h1 { + font-weight: bold; + } + } + + .mx_IncomingCallToast_buttons { + padding: 8px; + display: flex; + flex-direction: row; + + > .mx_IncomingCallToast_spacer { + width: 8px; + } + + > * { + flex-shrink: 0; + flex-grow: 1; + margin-right: 0; + font-size: $font-15px; + line-height: $font-24px; + } + } + + .mx_IncomingCallToast_iconButton { + position: absolute; + right: 8px; + + &::before { + content: ''; + + height: 20px; + width: 20px; + background-color: $icon-button-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + } + } + + .mx_IncomingCallToast_silence::before { + mask-image: url('$(res)/img/voip/silence.svg'); + } + + .mx_IncomingCallToast_unSilence::before { + mask-image: url('$(res)/img/voip/un-silence.svg'); + } +} diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss index 0c09070334..181a5ee0a3 100644 --- a/res/css/views/voip/_CallContainer.scss +++ b/res/css/views/voip/_CallContainer.scss @@ -43,84 +43,4 @@ limitations under the License. .mx_AppTile_persistedWrapper div { min-width: 350px; } - - .mx_IncomingCallBox { - min-width: 250px; - background-color: $voipcall-plinth-color; - padding: 8px; - box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); - border-radius: 8px; - - pointer-events: initial; // restore pointer events so the user can accept/decline - cursor: pointer; - - .mx_IncomingCallBox_CallerInfo { - display: flex; - direction: row; - - img, .mx_BaseAvatar_initial { - margin: 8px; - } - - > div { - display: flex; - flex-direction: column; - - justify-content: center; - } - - h1, p { - margin: 0px; - padding: 0px; - font-size: $font-14px; - line-height: $font-16px; - } - - h1 { - font-weight: bold; - } - } - - .mx_IncomingCallBox_buttons { - padding: 8px; - display: flex; - flex-direction: row; - - > .mx_IncomingCallBox_spacer { - width: 8px; - } - - > * { - flex-shrink: 0; - flex-grow: 1; - margin-right: 0; - font-size: $font-15px; - line-height: $font-24px; - } - } - - .mx_IncomingCallBox_iconButton { - position: absolute; - right: 8px; - - &::before { - content: ''; - - height: 20px; - width: 20px; - background-color: $icon-button-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - } - } - - .mx_IncomingCallBox_silence::before { - mask-image: url('$(res)/img/voip/silence.svg'); - } - - .mx_IncomingCallBox_unSilence::before { - mask-image: url('$(res)/img/voip/un-silence.svg'); - } - } } diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e7ba1aa9fb..e9831b5315 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -88,6 +88,9 @@ import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/ import EventEmitter from 'events'; import SdkConfig from './SdkConfig'; import { ensureDMExists, findDMForUser } from './createRoom'; +import { getIncomingCallToastKey } from './toasts/IncomingCallToast'; +import ToastStore from './stores/ToastStore'; +import IncomingCallToast from "./toasts/IncomingCallToast"; export const PROTOCOL_PSTN = 'm.protocol.pstn'; export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; @@ -641,6 +644,20 @@ export default class CallHandler extends EventEmitter { `Call state in ${mappedRoomId} changed to ${status}`, ); + const toastKey = getIncomingCallToastKey(call.callId); + if (status === CallState.Ringing) { + ToastStore.sharedInstance().addOrReplaceToast({ + key: toastKey, + supplyWholeBody: true, + priority: 100, + component: IncomingCallToast, + className: "mx_IncomingCallToast", + props: { call }, + }); + } else { + ToastStore.sharedInstance().dismissToast(toastKey); + } + dis.dispatch({ action: 'call_state', room_id: mappedRoomId, diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx deleted file mode 100644 index 95e97f1080..0000000000 --- a/src/components/views/voip/IncomingCallBox.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import { MatrixClientPeg } from '../../../MatrixClientPeg'; -import dis from '../../../dispatcher/dispatcher'; -import { _t } from '../../../languageHandler'; -import { ActionPayload } from '../../../dispatcher/payloads'; -import CallHandler, { CallHandlerEvent } from '../../../CallHandler'; -import RoomAvatar from '../avatars/RoomAvatar'; -import AccessibleButton from '../elements/AccessibleButton'; -import { CallState } from 'matrix-js-sdk/src/webrtc/call'; -import { replaceableComponent } from "../../../utils/replaceableComponent"; -import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; -import classNames from 'classnames'; - -interface IProps { -} - -interface IState { - incomingCall: any; - silenced: boolean; -} - -@replaceableComponent("views.voip.IncomingCallBox") -export default class IncomingCallBox extends React.Component { - private dispatcherRef: string; - - constructor(props: IProps) { - super(props); - - this.dispatcherRef = dis.register(this.onAction); - this.state = { - incomingCall: null, - silenced: false, - }; - } - - componentDidMount = () => { - CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); - }; - - public componentWillUnmount() { - dis.unregister(this.dispatcherRef); - CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); - } - - private onAction = (payload: ActionPayload) => { - switch (payload.action) { - case 'call_state': { - const call = CallHandler.sharedInstance().getCallForRoom(payload.room_id); - if (call && call.state === CallState.Ringing) { - this.setState({ - incomingCall: call, - silenced: false, // Reset silenced state for new call - }); - } else { - this.setState({ - incomingCall: null, - }); - } - } - } - }; - - private onSilencedCallsChanged = () => { - const callId = this.state.incomingCall?.callId; - if (!callId) return; - this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(callId) }); - }; - - private onAnswerClick: React.MouseEventHandler = (e) => { - e.stopPropagation(); - dis.dispatch({ - action: 'answer', - room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall), - }); - }; - - private onRejectClick: React.MouseEventHandler = (e) => { - e.stopPropagation(); - dis.dispatch({ - action: 'reject', - room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall), - }); - }; - - private onSilenceClick: React.MouseEventHandler = (e) => { - e.stopPropagation(); - const callId = this.state.incomingCall.callId; - this.state.silenced ? - CallHandler.sharedInstance().unSilenceCall(callId): - CallHandler.sharedInstance().silenceCall(callId); - }; - - public render() { - if (!this.state.incomingCall) { - return null; - } - - let room = null; - if (this.state.incomingCall) { - room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall)); - } - - const caller = room ? room.name : _t("Unknown caller"); - - let incomingCallText = null; - if (this.state.incomingCall) { - if (this.state.incomingCall.type === "voice") { - incomingCallText = _t("Incoming voice call"); - } else if (this.state.incomingCall.type === "video") { - incomingCallText = _t("Incoming video call"); - } else { - incomingCallText = _t("Incoming call"); - } - } - - const silenceClass = classNames({ - "mx_IncomingCallBox_iconButton": true, - "mx_IncomingCallBox_unSilence": this.state.silenced, - "mx_IncomingCallBox_silence": !this.state.silenced, - }); - - return
-
- -
-

{ caller }

-

{ incomingCallText }

-
- -
-
- - { _t("Decline") } - -
- - { _t("Accept") } - -
-
; - } -} diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx new file mode 100644 index 0000000000..2a3e2bd805 --- /dev/null +++ b/src/toasts/IncomingCallToast.tsx @@ -0,0 +1,139 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2018 New Vector Ltd +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 Šimon Brandner + +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 { CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; +import classNames from 'classnames'; +import { replaceableComponent } from '../utils/replaceableComponent'; +import CallHandler, { CallHandlerEvent } from '../CallHandler'; +import dis from '../dispatcher/dispatcher'; +import { MatrixClientPeg } from '../MatrixClientPeg'; +import { _t } from '../languageHandler'; +import RoomAvatar from '../components/views/avatars/RoomAvatar'; +import AccessibleTooltipButton from '../components/views/elements/AccessibleTooltipButton'; +import AccessibleButton from '../components/views/elements/AccessibleButton'; + +export const getIncomingCallToastKey = (callId: string) => `call_${callId}`; + +interface IProps { + call: MatrixCall; +} + +interface IState { + silenced: boolean; +} + +@replaceableComponent("views.voip.IncomingCallToast") +export default class IncomingCallToast extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + silenced: false, + }; + } + + componentDidMount = () => { + CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); + }; + + public componentWillUnmount() { + CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); + } + + private onSilencedCallsChanged = () => { + this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(this.props.call.callId) }); + }; + + private onAnswerClick= (e: React.MouseEvent) => { + e.stopPropagation(); + dis.dispatch({ + action: 'answer', + room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call), + }); + }; + + private onRejectClick= (e: React.MouseEvent) => { + e.stopPropagation(); + dis.dispatch({ + action: 'reject', + room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call), + }); + }; + + private onSilenceClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const callId = this.props.call.callId; + this.state.silenced ? + CallHandler.sharedInstance().unSilenceCall(callId) : + CallHandler.sharedInstance().silenceCall(callId); + }; + + public render() { + const call = this.props.call; + let room = null; + room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call)); + + const caller = room ? room.name : _t("Unknown caller"); + + const incomingCallText = call.type === CallType.Voice ? _t("Incoming voice call") : _t("Incoming video call"); + + const silenceClass = classNames({ + "mx_IncomingCallToast_iconButton": true, + "mx_IncomingCallToast_unSilence": this.state.silenced, + "mx_IncomingCallToast_silence": !this.state.silenced, + }); + + return +
+ +
+

{ caller }

+

{ incomingCallText }

+
+ +
+
+ + { _t("Decline") } + +
+ + { _t("Accept") } + +
+ ; + } +} From af22588682a8b64da1e929bbccec8fe3c197aa26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 13:39:39 +0200 Subject: [PATCH 021/100] Don't use a spacer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 5 +---- src/toasts/IncomingCallToast.tsx | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 5ce99bd11e..f7edf6a7bd 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -60,10 +60,7 @@ limitations under the License. padding: 8px; display: flex; flex-direction: row; - - > .mx_IncomingCallToast_spacer { - width: 8px; - } + gap: 12px; > * { flex-shrink: 0; diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 2a3e2bd805..83cd7aba80 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -125,7 +125,6 @@ export default class IncomingCallToast extends React.Component { > { _t("Decline") } -
Date: Sat, 24 Jul 2021 13:46:06 +0200 Subject: [PATCH 022/100] Correct button sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index f7edf6a7bd..02b27a94ab 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -63,6 +63,8 @@ limitations under the License. gap: 12px; > * { + height: 24px; + padding: 0px 12px; flex-shrink: 0; flex-grow: 1; margin-right: 0; From 24e6fc96f6c84b1d1676ee609671abbb3663ddfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 14:04:22 +0200 Subject: [PATCH 023/100] Reorganize content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 22 +++---- src/toasts/IncomingCallToast.tsx | 61 ++++++++++---------- 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 02b27a94ab..d2947395dc 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -18,10 +18,12 @@ limitations under the License. .mx_IncomingCallToast { // mx_Toast overrides padding: 8px !important; - display: unset !important; + display: flex !important; top: 8px !important; border-radius: 8px; + flex-direction: row; + gap: 8px; min-width: 250px; box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); background-color: $voipcall-plinth-color; // To match mx_Toast @@ -29,20 +31,9 @@ limitations under the License. pointer-events: initial; // restore pointer events so the user can accept/decline cursor: pointer; - .mx_IncomingCallToast_CallerInfo { + .mx_IncomingCallToast_content { display: flex; - direction: row; - - img, .mx_BaseAvatar_initial { - margin: 8px; - } - - > div { - display: flex; - flex-direction: column; - - justify-content: center; - } + flex-direction: column; h1, p { margin: 0px; @@ -57,7 +48,7 @@ limitations under the License. } .mx_IncomingCallToast_buttons { - padding: 8px; + margin-top: 8px; display: flex; flex-direction: row; gap: 12px; @@ -74,6 +65,7 @@ limitations under the License. } .mx_IncomingCallToast_iconButton { + display: flex; position: absolute; right: 8px; diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 83cd7aba80..cff0c82782 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -87,12 +87,7 @@ export default class IncomingCallToast extends React.Component { public render() { const call = this.props.call; - let room = null; - room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call)); - - const caller = room ? room.name : _t("Unknown caller"); - - const incomingCallText = call.type === CallType.Voice ? _t("Incoming voice call") : _t("Incoming video call"); + const room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call)); const silenceClass = classNames({ "mx_IncomingCallToast_iconButton": true, @@ -101,37 +96,39 @@ export default class IncomingCallToast extends React.Component { }); return -
- -
-

{ caller }

-

{ incomingCallText }

-
+ +
+

+ { room ? room.name : _t("Unknown caller") } +

+

+ { call.type === CallType.Voice ? _t("Incoming voice call") : _t("Incoming video call") } +

-
-
- - { _t("Decline") } - - - { _t("Accept") } - +
+ + { _t("Decline") } + + + { _t("Accept") } + +
; } From dd800549d734231b68aef31e484f56cd25410146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 14:06:52 +0200 Subject: [PATCH 024/100] Fix the silence icon color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index d2947395dc..0a36084f7a 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -74,7 +74,7 @@ limitations under the License. height: 20px; width: 20px; - background-color: $icon-button-color; + background-color: $secondary-fg-color; mask-repeat: no-repeat; mask-size: contain; mask-position: center; From a6f10a4aaa25b91172c6133b484da0df7be7dbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 18:59:54 +0200 Subject: [PATCH 025/100] Move around some CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 66 ++++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 0a36084f7a..a2d775c969 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -45,47 +45,47 @@ limitations under the License. h1 { font-weight: bold; } - } - .mx_IncomingCallToast_buttons { - margin-top: 8px; - display: flex; - flex-direction: row; - gap: 12px; + .mx_IncomingCallToast_buttons { + margin-top: 8px; + display: flex; + flex-direction: row; + gap: 12px; - > * { - height: 24px; - padding: 0px 12px; - flex-shrink: 0; - flex-grow: 1; - margin-right: 0; - font-size: $font-15px; - line-height: $font-24px; + > * { + height: 24px; + padding: 0px 12px; + flex-shrink: 0; + flex-grow: 1; + margin-right: 0; + font-size: $font-15px; + line-height: $font-24px; + } } - } - .mx_IncomingCallToast_iconButton { - display: flex; - position: absolute; - right: 8px; + .mx_IncomingCallToast_iconButton { + display: flex; + position: absolute; + right: 8px; - &::before { - content: ''; + &::before { + content: ''; - height: 20px; - width: 20px; - background-color: $secondary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; + height: 20px; + width: 20px; + background-color: $secondary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + } } - } - .mx_IncomingCallToast_silence::before { - mask-image: url('$(res)/img/voip/silence.svg'); - } + .mx_IncomingCallToast_silence::before { + mask-image: url('$(res)/img/voip/silence.svg'); + } - .mx_IncomingCallToast_unSilence::before { - mask-image: url('$(res)/img/voip/un-silence.svg'); + .mx_IncomingCallToast_unSilence::before { + mask-image: url('$(res)/img/voip/un-silence.svg'); + } } } From 25d62983de958b16300e88bc6524fb247d053eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 19:12:12 +0200 Subject: [PATCH 026/100] Add button icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 36 +++++++++++++++++++- src/toasts/IncomingCallToast.tsx | 20 ++++++----- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index a2d775c969..665109bc0f 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -46,13 +46,25 @@ limitations under the License. font-weight: bold; } + &.mx_IncomingCallToast_content_voice { + .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + } + } + + &.mx_IncomingCallToast_content_video { + .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + } + } + .mx_IncomingCallToast_buttons { margin-top: 8px; display: flex; flex-direction: row; gap: 12px; - > * { + .mx_IncomingCallToast_button { height: 24px; padding: 0px 12px; flex-shrink: 0; @@ -60,6 +72,28 @@ limitations under the License. margin-right: 0; font-size: $font-15px; line-height: $font-24px; + + span { + padding: 8px 0; + display: flex; + align-items: center; + + &::before { + content: ''; + display: inline-block; + background-color: $button-fg-color; + mask-position: center; + mask-repeat: no-repeat; + mask-size: 16px; + width: 16px; + height: 16px; + margin-right: 8px; + } + } + + &.mx_IncomingCallToast_button_decline span::before { + mask-image: url('$(res)/img/element-icons/call/hangup.svg'); + } } } diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index cff0c82782..9e5528d6a7 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -88,9 +88,13 @@ export default class IncomingCallToast extends React.Component { public render() { const call = this.props.call; const room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call)); + const isVoice = call.type === CallType.Voice; - const silenceClass = classNames({ - "mx_IncomingCallToast_iconButton": true, + const contentClass = classNames("mx_IncomingCallToast_content", { + "mx_IncomingCallToast_content_voice": isVoice, + "mx_IncomingCallToast_content_video": !isVoice, + }); + const silenceClass = classNames("mx_IncomingCallToast_iconButton", { "mx_IncomingCallToast_unSilence": this.state.silenced, "mx_IncomingCallToast_silence": !this.state.silenced, }); @@ -101,12 +105,12 @@ export default class IncomingCallToast extends React.Component { height={32} width={32} /> -
+

{ room ? room.name : _t("Unknown caller") }

- { call.type === CallType.Voice ? _t("Incoming voice call") : _t("Incoming video call") } + { isVoice ? _t("Incoming voice call") : _t("Incoming video call") }

{ />
- { _t("Decline") } + { _t("Decline") } - { _t("Accept") } + { _t("Accept") }
From 064544369a033fe3a84734466cca4710db9ac16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 19:30:37 +0200 Subject: [PATCH 027/100] Add call type icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 27 ++++++++++++++++++-- src/toasts/IncomingCallToast.tsx | 7 ++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 665109bc0f..bcfb61ed21 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -35,7 +35,7 @@ limitations under the License. display: flex; flex-direction: column; - h1, p { + h1, .mx_CallEvent_type { margin: 0px; padding: 0px; font-size: $font-14px; @@ -46,13 +46,36 @@ limitations under the License. font-weight: bold; } + .mx_CallEvent_type { + display: flex; + flex-direction: row; + + .mx_CallEvent_type_icon { + height: 16px; + width: 16px; + margin-right: 6px; + + &::before { + content: ''; + position: absolute; + height: inherit; + width: inherit; + background-color: $tertiary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + } + } + } + &.mx_IncomingCallToast_content_voice { + .mx_CallEvent_type .mx_CallEvent_type_icon::before, .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); } } &.mx_IncomingCallToast_content_video { + .mx_CallEvent_type .mx_CallEvent_type_icon::before, .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { mask-image: url('$(res)/img/element-icons/call/video-call.svg'); } @@ -107,7 +130,7 @@ limitations under the License. height: 20px; width: 20px; - background-color: $secondary-fg-color; + background-color: $tertiary-fg-color; mask-repeat: no-repeat; mask-size: contain; mask-position: center; diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 9e5528d6a7..f8a7f0591f 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -109,9 +109,10 @@ export default class IncomingCallToast extends React.Component {

{ room ? room.name : _t("Unknown caller") }

-

- { isVoice ? _t("Incoming voice call") : _t("Incoming video call") } -

+
+
+ { isVoice ? _t("Voice call") : _t("Video call") } +
Date: Sat, 24 Jul 2021 20:39:44 +0200 Subject: [PATCH 028/100] Move silence button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 43 ++++++++++---------- src/toasts/IncomingCallToast.tsx | 10 ++--- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index bcfb61ed21..04f92bb095 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -29,7 +29,6 @@ limitations under the License. background-color: $voipcall-plinth-color; // To match mx_Toast pointer-events: initial; // restore pointer events so the user can accept/decline - cursor: pointer; .mx_IncomingCallToast_content { display: flex; @@ -119,30 +118,30 @@ limitations under the License. } } } + } - .mx_IncomingCallToast_iconButton { - display: flex; - position: absolute; - right: 8px; + .mx_IncomingCallToast_iconButton { + display: flex; + height: 20px; + width: 20px; - &::before { - content: ''; + &::before { + content: ''; - height: 20px; - width: 20px; - background-color: $tertiary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - } - } - - .mx_IncomingCallToast_silence::before { - mask-image: url('$(res)/img/voip/silence.svg'); - } - - .mx_IncomingCallToast_unSilence::before { - mask-image: url('$(res)/img/voip/un-silence.svg'); + height: inherit; + width: inherit; + background-color: $tertiary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; } } + + .mx_IncomingCallToast_silence::before { + mask-image: url('$(res)/img/voip/silence.svg'); + } + + .mx_IncomingCallToast_unSilence::before { + mask-image: url('$(res)/img/voip/un-silence.svg'); + } } diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index f8a7f0591f..7a7aacac12 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -113,11 +113,6 @@ export default class IncomingCallToast extends React.Component {
{ isVoice ? _t("Voice call") : _t("Video call") }
-
{
+ ; } } From 1f9cd79bcfe4d5b3aeb024e72d316fa93297dbac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 20:59:15 +0200 Subject: [PATCH 029/100] Fix some spacing and other tiny things MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 37 +++++++++++++------- src/toasts/IncomingCallToast.tsx | 4 +-- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 04f92bb095..6bdbcdb8b0 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -23,7 +23,6 @@ limitations under the License. border-radius: 8px; flex-direction: row; - gap: 8px; min-width: 250px; box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); background-color: $voipcall-plinth-color; // To match mx_Toast @@ -33,21 +32,27 @@ limitations under the License. .mx_IncomingCallToast_content { display: flex; flex-direction: column; + margin-left: 8px; - h1, .mx_CallEvent_type { - margin: 0px; - padding: 0px; - font-size: $font-14px; - line-height: $font-16px; - } - - h1 { + .mx_CallEvent_caller { font-weight: bold; + font-size: $font-15px; + line-height: $font-18px; + + margin-top: 2px; } .mx_CallEvent_type { + font-size: $font-12px; + line-height: $font-15px; + color: $tertiary-fg-color; + + margin-top: 4px; + margin-bottom: 6px; + display: flex; flex-direction: row; + align-items: center; .mx_CallEvent_type_icon { height: 16px; @@ -88,7 +93,7 @@ limitations under the License. .mx_IncomingCallToast_button { height: 24px; - padding: 0px 12px; + padding: 0px 8px; flex-shrink: 0; flex-grow: 1; margin-right: 0; @@ -106,15 +111,21 @@ limitations under the License. background-color: $button-fg-color; mask-position: center; mask-repeat: no-repeat; - mask-size: 16px; - width: 16px; - height: 16px; margin-right: 8px; } } + &.mx_IncomingCallToast_button_accept span::before { + mask-size: 13px; + width: 13px; + height: 13px; + } + &.mx_IncomingCallToast_button_decline span::before { mask-image: url('$(res)/img/element-icons/call/hangup.svg'); + mask-size: 16px; + width: 16px; + height: 16px; } } } diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 7a7aacac12..8d14fbd883 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -106,9 +106,9 @@ export default class IncomingCallToast extends React.Component { width={32} />
-

+ { room ? room.name : _t("Unknown caller") } -

+
{ isVoice ? _t("Voice call") : _t("Video call") } From 85095df4b9663fd1d078b57efe01a6fa47f4d2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 21:03:26 +0200 Subject: [PATCH 030/100] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b36910b41b..46600c48f2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -734,6 +734,13 @@ "Notifications": "Notifications", "Enable desktop notifications": "Enable desktop notifications", "Enable": "Enable", + "Unknown caller": "Unknown caller", + "Voice call": "Voice call", + "Video call": "Video call", + "Decline": "Decline", + "Accept": "Accept", + "Sound on": "Sound on", + "Silence call": "Silence call", "Use app for a better experience": "Use app for a better experience", "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.", "Use app": "Use app", @@ -911,14 +918,6 @@ "Fill Screen": "Fill Screen", "Return to call": "Return to call", "%(name)s on hold": "%(name)s on hold", - "Unknown caller": "Unknown caller", - "Incoming voice call": "Incoming voice call", - "Incoming video call": "Incoming video call", - "Incoming call": "Incoming call", - "Sound on": "Sound on", - "Silence call": "Silence call", - "Decline": "Decline", - "Accept": "Accept", "The other party cancelled the verification.": "The other party cancelled the verification.", "Verified!": "Verified!", "You've successfully verified this user.": "You've successfully verified this user.", @@ -1580,8 +1579,6 @@ "Hide Widgets": "Hide Widgets", "Show Widgets": "Show Widgets", "Search": "Search", - "Voice call": "Voice call", - "Video call": "Video call", "Invites": "Invites", "Favourites": "Favourites", "People": "People", From 379101e3ff3e0b77bcf5c215292e649d9bd4c084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 21:09:00 +0200 Subject: [PATCH 031/100] Remove an unused member MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/stores/ToastStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts index e831be7203..093ea9fb6b 100644 --- a/src/stores/ToastStore.ts +++ b/src/stores/ToastStore.ts @@ -28,7 +28,6 @@ export interface IToast { className?: string; props?: Omit, "toastKey">; // toastKey is injected by ToastContainer supplyWholeBody?: boolean; - content?: JSX.Element; } /** From f2204aa1ffdb16de1592778409ab909fad82c52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 21:12:29 +0200 Subject: [PATCH 032/100] Remove nonsense comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 6bdbcdb8b0..d49014efdb 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -20,12 +20,12 @@ limitations under the License. padding: 8px !important; display: flex !important; top: 8px !important; - border-radius: 8px; + border-radius: 8px; flex-direction: row; min-width: 250px; box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); - background-color: $voipcall-plinth-color; // To match mx_Toast + background-color: $voipcall-plinth-color; pointer-events: initial; // restore pointer events so the user can accept/decline From 3287d51b179096a29b3f45f62458bad25c86d0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 08:06:11 +0200 Subject: [PATCH 033/100] Add some return types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/toasts/IncomingCallToast.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 8d14fbd883..c842581f9a 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -53,15 +53,15 @@ export default class IncomingCallToast extends React.Component { CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); }; - public componentWillUnmount() { + public componentWillUnmount(): void { CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); } - private onSilencedCallsChanged = () => { + private onSilencedCallsChanged = (): void => { this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(this.props.call.callId) }); }; - private onAnswerClick= (e: React.MouseEvent) => { + private onAnswerClick= (e: React.MouseEvent): void => { e.stopPropagation(); dis.dispatch({ action: 'answer', @@ -69,7 +69,7 @@ export default class IncomingCallToast extends React.Component { }); }; - private onRejectClick= (e: React.MouseEvent) => { + private onRejectClick= (e: React.MouseEvent): void => { e.stopPropagation(); dis.dispatch({ action: 'reject', @@ -77,7 +77,7 @@ export default class IncomingCallToast extends React.Component { }); }; - private onSilenceClick = (e: React.MouseEvent) => { + private onSilenceClick = (e: React.MouseEvent): void => { e.stopPropagation(); const callId = this.props.call.callId; this.state.silenced ? From 1d629f2557a7d618355436f99c31cc6b9ce856f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Jul 2021 10:34:59 +0200 Subject: [PATCH 034/100] More TS Co-authored-by: Germain --- src/toasts/IncomingCallToast.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index c842581f9a..a853e1652a 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -49,7 +49,7 @@ export default class IncomingCallToast extends React.Component { }; } - componentDidMount = () => { + public componentDidMount = (): void => { CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); }; From 40947a2a681f03b62013c9c002b1e4ddea7916db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Jul 2021 12:21:58 +0200 Subject: [PATCH 035/100] Simplifie toast handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 11 +----- src/CallHandler.tsx | 3 +- src/components/structures/ToastContainer.tsx | 37 +++++++++++--------- src/stores/ToastStore.ts | 2 +- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index d49014efdb..975628f948 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -16,17 +16,8 @@ limitations under the License. */ .mx_IncomingCallToast { - // mx_Toast overrides - padding: 8px !important; - display: flex !important; - top: 8px !important; - - border-radius: 8px; + display: flex; flex-direction: row; - min-width: 250px; - box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); - background-color: $voipcall-plinth-color; - pointer-events: initial; // restore pointer events so the user can accept/decline .mx_IncomingCallToast_content { diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e9831b5315..5018c44488 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -648,10 +648,9 @@ export default class CallHandler extends EventEmitter { if (status === CallState.Ringing) { ToastStore.sharedInstance().addOrReplaceToast({ key: toastKey, - supplyWholeBody: true, priority: 100, component: IncomingCallToast, - className: "mx_IncomingCallToast", + bodyClassName: "mx_IncomingCallToast", props: { call }, }); } else { diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx index 75cf4a51fc..0b0e871975 100644 --- a/src/components/structures/ToastContainer.tsx +++ b/src/components/structures/ToastContainer.tsx @@ -58,37 +58,42 @@ export default class ToastContainer extends React.Component<{}, IState> { let containerClasses; if (totalCount !== 0) { const topToast = this.state.toasts[0]; - const { title, icon, key, component, className, props, supplyWholeBody } = topToast; - const toastClasses = classNames("mx_Toast_toast", { + const { title, icon, key, component, className, bodyClassName, props } = topToast; + const bodyClasses = classNames("mx_Toast_body", bodyClassName); + const toastClasses = classNames("mx_Toast_toast", className, { "mx_Toast_hasIcon": icon, [`mx_Toast_icon_${icon}`]: icon, - }, className); - - let countIndicator; - if (isStacked || this.state.countSeen > 0) { - countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`; - } - + }); const toastProps = Object.assign({}, props, { key, toastKey: key, }); - const content = React.createElement(component, toastProps); - toast = supplyWholeBody - ? content - :
+ let countIndicator; + if (title && isStacked || this.state.countSeen > 0) { + countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`; + } + + let titleElement; + if (title) { + titleElement = (

{ title }

{ countIndicator }
-
{ content }
-
; + ); + } + + toast = ( +
+ { titleElement } +
{ content }
+
+ ); containerClasses = classNames("mx_ToastContainer", { "mx_ToastContainer_stacked": isStacked, - [className]: supplyWholeBody, }); } return toast diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts index 093ea9fb6b..5e51de3e26 100644 --- a/src/stores/ToastStore.ts +++ b/src/stores/ToastStore.ts @@ -26,8 +26,8 @@ export interface IToast { icon?: string; component: C; className?: string; + bodyClassName?: string; props?: Omit, "toastKey">; // toastKey is injected by ToastContainer - supplyWholeBody?: boolean; } /** From 277fdf1711186390ee3e6c119da1574d830888aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Jul 2021 12:42:18 +0200 Subject: [PATCH 036/100] voipcall-plinth-color -> quinary-content-color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_ToastContainer.scss | 4 ++-- res/css/views/voip/_CallView.scss | 2 +- res/themes/dark/css/_dark.scss | 2 +- res/themes/legacy-dark/css/_legacy-dark.scss | 2 +- res/themes/legacy-light/css/_legacy-light.scss | 8 ++++---- res/themes/light/css/_light.scss | 8 ++++---- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index d248568740..b6034be42d 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -28,7 +28,7 @@ limitations under the License. margin: 0 4px; grid-row: 2 / 4; grid-column: 1; - background-color: $dark-panel-bg-color; + background-color: $quinary-content-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; } @@ -37,7 +37,7 @@ limitations under the License. grid-row: 1 / 3; grid-column: 1; color: $primary-fg-color; - background-color: $dark-panel-bg-color; + background-color: $quinary-content-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; overflow: hidden; diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 205d431752..2be4a4b802 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -39,7 +39,7 @@ limitations under the License. .mx_CallView_pip { width: 320px; padding-bottom: 8px; - background-color: $voipcall-plinth-color; + background-color: $quinary-content-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20); border-radius: 8px; diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 655492661c..0907ccdd9a 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -113,7 +113,7 @@ $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #394049; +$quinary-content-color: #394049; // ******************** diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 0c0197cfb0..323fe0651e 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -112,7 +112,7 @@ $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #394049; +$quinary-content-color: #394049; // ******************** diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index b7d45452ff..a4e7af2bb9 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: Nunito, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; +$font-family: nunito, twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', arial, helvetica, sans-serif, 'Noto Color Emoji'; -$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; +$monospace-font-family: inconsolata, twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', courier, monospace, 'Noto Color Emoji'; // unified palette // try to use these colors when possible @@ -179,7 +179,7 @@ $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #F4F6FA; +$quinary-content-color: #F4F6FA; // ******************** @@ -390,7 +390,7 @@ $eventbubble-reply-color: #C1C6CD; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color ! important; + border: 1px solid $accent-color !important; color: $accent-color; background-color: $button-secondary-bg-color; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 32722515d8..2f81cb3407 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: Inter, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; +$font-family: inter, twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', arial, helvetica, sans-serif, 'Noto Color Emoji'; -$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; +$monospace-font-family: inconsolata, twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', courier, monospace, 'Noto Color Emoji'; // unified palette // try to use these colors when possible @@ -168,7 +168,7 @@ $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #F4F6FA; +$quinary-content-color: #F4F6FA; // ******************** @@ -392,7 +392,7 @@ $eventbubble-reply-color: #C1C6CD; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color ! important; + border: 1px solid $accent-color !important; color: $accent-color; background-color: $button-secondary-bg-color; } From 7ed5dee74bd57deb9de7e20a3b7813f1f94a07d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Jul 2021 13:17:06 +0200 Subject: [PATCH 037/100] Make colors a bit clearer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_ToastContainer.scss | 4 ++-- res/css/views/voip/_CallView.scss | 2 +- res/themes/dark/css/_dark.scss | 2 +- res/themes/legacy-dark/css/_legacy-dark.scss | 2 +- res/themes/legacy-light/css/_legacy-light.scss | 4 ++-- res/themes/light/css/_light.scss | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index b6034be42d..2c3f1c705c 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -28,7 +28,7 @@ limitations under the License. margin: 0 4px; grid-row: 2 / 4; grid-column: 1; - background-color: $quinary-content-color; + background-color: $toast-bg-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; } @@ -37,7 +37,7 @@ limitations under the License. grid-row: 1 / 3; grid-column: 1; color: $primary-fg-color; - background-color: $quinary-content-color; + background-color: $toast-bg-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; overflow: hidden; diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 2be4a4b802..8299ad8f9a 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -39,7 +39,7 @@ limitations under the License. .mx_CallView_pip { width: 320px; padding-bottom: 8px; - background-color: $quinary-content-color; + background-color: $toast-bg-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20); border-radius: 8px; diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 0907ccdd9a..6e8d64b807 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -112,8 +112,8 @@ $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -// this probably shouldn't have it's own colour $quinary-content-color: #394049; +$toast-bg-color: $quinary-content-color; // ******************** diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 323fe0651e..064b532bb0 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -111,8 +111,8 @@ $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -// this probably shouldn't have it's own colour $quinary-content-color: #394049; +$toast-bg-color: $quinary-content-color; // ******************** diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index a4e7af2bb9..cbf3d6d1b6 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -178,8 +178,8 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; -// this probably shouldn't have it's own colour -$quinary-content-color: #F4F6FA; +$system-light-color: #F4F6FA; +$toast-bg-color: $system-light-color; // ******************** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 2f81cb3407..1d786383f3 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -167,8 +167,8 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; -// this probably shouldn't have it's own colour -$quinary-content-color: #F4F6FA; +$system-light-color: #F4F6FA; +$toast-bg-color: $system-light-color; // ******************** From caefefc2c22704fc4f678a680592d885880193d0 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 27 Jul 2021 17:22:49 -0400 Subject: [PATCH 038/100] Add regional indicators to emoji picker Signed-off-by: Robin Townsend --- src/emoji.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/emoji.ts b/src/emoji.ts index 321eae63f6..1445f737d6 100644 --- a/src/emoji.ts +++ b/src/emoji.ts @@ -35,6 +35,9 @@ export const EMOTICON_TO_EMOJI = new Map(); export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode)); +const isRegionalIndicator = (x: string) => + Array.from(x).length === 1 && x >= '\u{1f1e6}' && x <= '\u{1f1ff}'; + const EMOJIBASE_GROUP_ID_TO_CATEGORY = [ "people", // smileys "people", // actually people @@ -72,7 +75,11 @@ export const EMOJI: IEmoji[] = EMOJIBASE.map((emojiData: Omit Date: Tue, 27 Jul 2021 17:35:34 -0400 Subject: [PATCH 039/100] Add more types Signed-off-by: Robin Townsend --- src/emoji.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emoji.ts b/src/emoji.ts index 1445f737d6..e871e0bb58 100644 --- a/src/emoji.ts +++ b/src/emoji.ts @@ -35,7 +35,7 @@ export const EMOTICON_TO_EMOJI = new Map(); export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode)); -const isRegionalIndicator = (x: string) => +const isRegionalIndicator = (x: string): boolean => Array.from(x).length === 1 && x >= '\u{1f1e6}' && x <= '\u{1f1ff}'; const EMOJIBASE_GROUP_ID_TO_CATEGORY = [ From f16b1d46b72430715a75bac495b6ac19e94efe44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 20:58:24 +0200 Subject: [PATCH 040/100] Fix sizing issue of the screen picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_DesktopCapturerSourcePicker.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/res/css/views/elements/_DesktopCapturerSourcePicker.scss b/res/css/views/elements/_DesktopCapturerSourcePicker.scss index 49a0a44417..bd81aafef3 100644 --- a/res/css/views/elements/_DesktopCapturerSourcePicker.scss +++ b/res/css/views/elements/_DesktopCapturerSourcePicker.scss @@ -35,7 +35,6 @@ limitations under the License. .mx_desktopCapturerSourcePicker_source_thumbnail { margin: 4px; padding: 4px; - width: 312px; border-width: 2px; border-radius: 8px; border-style: solid; @@ -53,6 +52,5 @@ limitations under the License. white-space: nowrap; text-overflow: ellipsis; overflow: hidden; - width: 312px; } } From e0df78553867add2cd18f05a487238e240ac9a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 30 Jul 2021 08:15:45 +0200 Subject: [PATCH 041/100] Mirror only usermedia feeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/VideoFeed.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 51d2adb845..5fd30ed7df 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -22,6 +22,7 @@ import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed'; import { logger } from 'matrix-js-sdk/src/logger'; import MemberAvatar from "../avatars/MemberAvatar"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { SDPStreamMetadataPurpose } from '../../../../../matrix-js-sdk/src/webrtc/callEventTypes'; interface IProps { call: MatrixCall; @@ -159,6 +160,7 @@ export default class VideoFeed extends React.Component { mx_VideoFeed_video: !this.state.videoMuted, mx_VideoFeed_mirror: ( this.props.feed.isLocal() && + this.props.feed.purpose === SDPStreamMetadataPurpose.Usermedia && SettingsStore.getValue('VideoView.flipVideoHorizontally') ), }; From 0c30566cae3293df8981d21ddc93e0358f1ca6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 30 Jul 2021 08:20:36 +0200 Subject: [PATCH 042/100] Fix import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/VideoFeed.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 5fd30ed7df..170e8362b5 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -22,7 +22,7 @@ import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed'; import { logger } from 'matrix-js-sdk/src/logger'; import MemberAvatar from "../avatars/MemberAvatar"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { SDPStreamMetadataPurpose } from '../../../../../matrix-js-sdk/src/webrtc/callEventTypes'; +import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; interface IProps { call: MatrixCall; From 7fd14c9ccc5c1cbee3d27d7880a7de285509971a Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Sun, 1 Aug 2021 16:18:06 +0200 Subject: [PATCH 043/100] Add data-mx-theme to theme css for hot-reload --- src/theme.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/theme.js b/src/theme.js index 2caf48b65a..cd14d2d9db 100644 --- a/src/theme.js +++ b/src/theme.js @@ -171,15 +171,10 @@ export async function setTheme(theme) { // look for the stylesheet elements. // styleElements is a map from style name to HTMLLinkElement. const styleElements = Object.create(null); - let a; - for (let i = 0; (a = document.getElementsByTagName("link")[i]); i++) { - const href = a.getAttribute("href"); - // shouldn't we be using the 'title' tag rather than the href? - const match = href && href.match(/^bundles\/.*\/theme-(.*)\.css$/); - if (match) { - styleElements[match[1]] = a; - } - } + const themes = Array.from(document.querySelectorAll('[data-mx-theme]')); + themes.forEach(theme => { + styleElements[theme.attributes['data-mx-theme'].value.toLowerCase()] = theme; + }); if (!(stylesheetName in styleElements)) { throw new Error("Unknown theme " + stylesheetName); From c5d11a9f17b9e523ecffe28c31102b0a3b4d63e9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 2 Aug 2021 23:29:46 -0600 Subject: [PATCH 044/100] Improve voice messages uploading state Fixes https://github.com/vector-im/element-web/issues/18226 Fixes https://github.com/vector-im/element-web/issues/18224 --- .../views/rooms/_VoiceRecordComposerTile.scss | 11 ++++ .../views/rooms/VoiceRecordComposerTile.tsx | 54 ++++++++++++++----- src/i18n/strings/en_EN.json | 3 +- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 5501ab343e..3d8d0e5ecc 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -46,6 +46,17 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/trashcan.svg'); } +.mx_VoiceRecordComposerTile_uploadState { + margin-right: 21px; + color: $secondary-fg-color; + + .mx_VoiceRecordComposerTile_uploadState_badge { + display: inline-block; + margin-right: 4px; + vertical-align: middle; + } +} + .mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer { // Note: remaining class properties are in the PlayerContainer CSS. diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 8323320520..490d6b2231 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -17,10 +17,7 @@ limitations under the License. import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { _t } from "../../../languageHandler"; import React, { ReactNode } from "react"; -import { - RecordingState, - VoiceRecording, -} from "../../../audio/VoiceRecording"; +import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording"; import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import classNames from "classnames"; @@ -34,6 +31,10 @@ import { MsgType } from "matrix-js-sdk/src/@types/event"; import Modal from "../../../Modal"; import ErrorDialog from "../dialogs/ErrorDialog"; import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler"; +import NotificationBadge from "./NotificationBadge"; +import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; +import { NotificationColor } from "../../../stores/notifications/NotificationColor"; +import InlineSpinner from "../elements/InlineSpinner"; interface IProps { room: Room; @@ -42,6 +43,7 @@ interface IProps { interface IState { recorder?: VoiceRecording; recordingPhase?: RecordingState; + didUploadFail?: boolean; } /** @@ -69,9 +71,19 @@ export default class VoiceRecordComposerTile extends React.PureComponent { @@ -234,7 +245,26 @@ export default class VoiceRecordComposerTile extends React.PureComponent; } + let uploadIndicator; + if (this.state.recordingPhase === RecordingState.Uploading) { + uploadIndicator = + + { _t("Uploading...") } + ; + } else if (this.state.didUploadFail && this.state.recordingPhase === RecordingState.Ended) { + uploadIndicator = + + { /* Need to stick the badge in a span to ensure it doesn't create a block component */ } + + + { _t("Failed to upload voice message") } + ; + } + return (<> + { uploadIndicator } { deleteButton } { this.renderWaveformArea() } { recordingInfo } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 87cd9afb5b..c929bd683b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1701,7 +1701,6 @@ "Invited by %(sender)s": "Invited by %(sender)s", "Jump to first unread message.": "Jump to first unread message.", "Mark all as read": "Mark all as read", - "The voice message failed to upload.": "The voice message failed to upload.", "Unable to access your microphone": "Unable to access your microphone", "We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.", "No microphone found": "No microphone found", @@ -1709,6 +1708,8 @@ "Record a voice message": "Record a voice message", "Stop the recording": "Stop the recording", "Delete recording": "Delete recording", + "Uploading...": "Uploading...", + "Failed to upload voice message": "Failed to upload voice message", "Error updating main address": "Error updating main address", "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.", From dc7aad1abfbd16808c7eb9f345b4f156e0aed4a3 Mon Sep 17 00:00:00 2001 From: James Salter Date: Tue, 3 Aug 2021 11:55:02 +0100 Subject: [PATCH 045/100] Revert "Revert "Add support for Posthog Analytics under a labs flag"" This reverts commit c5ea2531817e2d254692dd48bc4985184528233d. --- package.json | 1 + src/@types/posthog.d.ts | 748 ++++++++++++++++++ src/Lifecycle.ts | 5 + src/PosthogAnalytics.ts | 355 +++++++++ src/components/structures/MatrixChat.tsx | 6 + .../tabs/user/SecurityUserSettingsTab.js | 2 + src/i18n/strings/en_EN.json | 1 + src/settings/Settings.tsx | 8 + .../PseudonymousAnalyticsController.ts | 26 + test/PosthogAnalytics-test.ts | 232 ++++++ tsconfig.json | 9 +- yarn.lock | 12 + 12 files changed, 1403 insertions(+), 2 deletions(-) create mode 100644 src/@types/posthog.d.ts create mode 100644 src/PosthogAnalytics.ts create mode 100644 src/settings/controllers/PseudonymousAnalyticsController.ts create mode 100644 test/PosthogAnalytics-test.ts diff --git a/package.json b/package.json index 9744aa7685..b7e06fe012 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "pako": "^2.0.3", "parse5": "^6.0.1", "png-chunks-extract": "^1.0.0", + "posthog-js": "1.12.1", "prop-types": "^15.7.2", "qrcode": "^1.4.4", "re-resizable": "^6.9.0", diff --git a/src/@types/posthog.d.ts b/src/@types/posthog.d.ts new file mode 100644 index 0000000000..1ca475cd3b --- /dev/null +++ b/src/@types/posthog.d.ts @@ -0,0 +1,748 @@ +// A clone of the type definitions from posthog-js, stripped of references to transitive +// dependencies which we don't actually use, so that we don't need to install them. +// +// Original file lives in node_modules/posthog/dist/module.d.ts + +/* eslint-disable @typescript-eslint/member-delimiter-style */ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable camelcase */ + +// Type definitions for exported methods + +declare class posthog { + /** + * This function initializes a new instance of the PostHog capturing object. + * All new instances are added to the main posthog object as sub properties (such as + * posthog.library_name) and also returned by this function. To define a + * second instance on the page, you would call: + * + * posthog.init('new token', { your: 'config' }, 'library_name'); + * + * and use it like so: + * + * posthog.library_name.capture(...); + * + * @param {String} token Your PostHog API token + * @param {Object} [config] A dictionary of config options to override. See a list of default config options. + * @param {String} [name] The name for the new posthog instance that you want created + */ + static init(token: string, config?: posthog.Config, name?: string): posthog + + /** + * Clears super properties and generates a new random distinct_id for this instance. + * Useful for clearing data when a user logs out. + */ + static reset(reset_device_id?: boolean): void + + /** + * Capture an event. This is the most important and + * frequently used PostHog function. + * + * ### Usage: + * + * // capture an event named 'Registered' + * posthog.capture('Registered', {'Gender': 'Male', 'Age': 21}); + * + * // capture an event using navigator.sendBeacon + * posthog.capture('Left page', {'duration_seconds': 35}, {transport: 'sendBeacon'}); + * + * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. + * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. + * @param {Object} [options] Optional configuration for this capture request. + * @param {String} [options.transport] Transport method for network request ('XHR' or 'sendBeacon'). + */ + static capture( + event_name: string, + properties?: posthog.Properties, + options?: { transport: 'XHR' | 'sendBeacon' } + ): posthog.CaptureResult + + /** + * Capture a page view event, which is currently ignored by the server. + * This function is called by default on page load unless the + * capture_pageview configuration variable is false. + * + * @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url. + * @api private + */ + static capture_pageview(page?: string): void + + /** + * Register a set of super properties, which are included with all + * events. This will overwrite previous super property values. + * + * ### Usage: + * + * // register 'Gender' as a super property + * posthog.register({'Gender': 'Female'}); + * + * // register several super properties when a user signs up + * posthog.register({ + * 'Email': 'jdoe@example.com', + * 'Account Type': 'Free' + * }); + * + * @param {Object} properties An associative array of properties to store about the user + * @param {Number} [days] How many days since the user's last visit to store the super properties + */ + static register(properties: posthog.Properties, days?: number): void + + /** + * Register a set of super properties only once. This will not + * overwrite previous super property values, unlike register(). + * + * ### Usage: + * + * // register a super property for the first time only + * posthog.register_once({ + * 'First Login Date': new Date().toISOString() + * }); + * + * ### Notes: + * + * If default_value is specified, current super properties + * with that value will be overwritten. + * + * @param {Object} properties An associative array of properties to store about the user + * @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None' + * @param {Number} [days] How many days since the users last visit to store the super properties + */ + static register_once(properties: posthog.Properties, default_value?: posthog.Property, days?: number): void + + /** + * Delete a super property stored with the current user. + * + * @param {String} property The name of the super property to remove + */ + static unregister(property: string): void + + /** + * Identify a user with a unique ID instead of a PostHog + * randomly generated distinct_id. If the method is never called, + * then unique visitors will be identified by a UUID generated + * the first time they visit the site. + * + * If user properties are passed, they are also sent to posthog. + * + * ### Usage: + * + * posthog.identify('[user unique id]') + * posthog.identify('[user unique id]', { email: 'john@example.com' }) + * posthog.identify('[user unique id]', {}, { referral_code: '12345' }) + * + * ### Notes: + * + * You can call this function to overwrite a previously set + * unique ID for the current user. PostHog cannot translate + * between IDs at this time, so when you change a user's ID + * they will appear to be a new user. + * + * When used alone, posthog.identify will change the user's + * distinct_id to the unique ID provided. When used in tandem + * with posthog.alias, it will allow you to identify based on + * unique ID and map that back to the original, anonymous + * distinct_id given to the user upon her first arrival to your + * site (thus connecting anonymous pre-signup activity to + * post-signup activity). Though the two work together, do not + * call identify() at the same time as alias(). Calling the two + * at the same time can cause a race condition, so it is best + * practice to call identify on the original, anonymous ID + * right after you've aliased it. + * + * @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used. + * @param {Object} [userProperties] Optional: An associative array of properties to store about the user + * @param {Object} [userPropertiesToSetOnce] Optional: An associative array of properties to store about the user. If property is previously set, this does not override that value. + */ + static identify( + unique_id?: string, + userPropertiesToSet?: posthog.Properties, + userPropertiesToSetOnce?: posthog.Properties + ): void + + /** + * Create an alias, which PostHog will use to link two distinct_ids going forward (not retroactively). + * Multiple aliases can map to the same original ID, but not vice-versa. Aliases can also be chained - the + * following is a valid scenario: + * + * posthog.alias('new_id', 'existing_id'); + * ... + * posthog.alias('newer_id', 'new_id'); + * + * If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID. + * + * ### Notes: + * + * The best practice is to call alias() when a unique ID is first created for a user + * (e.g., when a user first registers for an account and provides an email address). + * alias() should never be called more than once for a given user, except to + * chain a newer ID to a previously new ID, as described above. + * + * @param {String} alias A unique identifier that you want to use for this user in the future. + * @param {String} [original] The current identifier being used for this user. + */ + static alias(alias: string, original?: string): posthog.CaptureResult | number + + /** + * Update the configuration of a posthog library instance. + * + * The default config is: + * + * { + * // HTTP method for capturing requests + * api_method: 'POST' + * + * // transport for sending requests ('XHR' or 'sendBeacon') + * // NB: sendBeacon should only be used for scenarios such as + * // page unload where a "best-effort" attempt to send is + * // acceptable; the sendBeacon API does not support callbacks + * // or any way to know the result of the request. PostHog + * // capturing via sendBeacon will not support any event- + * // batching or retry mechanisms. + * api_transport: 'XHR' + * + * // Automatically capture clicks, form submissions and change events + * autocapture: true + * + * // Capture rage clicks (beta) - useful for session recording + * rageclick: false + * + * // super properties cookie expiration (in days) + * cookie_expiration: 365 + * + * // super properties span subdomains + * cross_subdomain_cookie: true + * + * // debug mode + * debug: false + * + * // if this is true, the posthog cookie or localStorage entry + * // will be deleted, and no user persistence will take place + * disable_persistence: false + * + * // if this is true, PostHog will automatically determine + * // City, Region and Country data using the IP address of + * //the client + * ip: true + * + * // opt users out of capturing by this PostHog instance by default + * opt_out_capturing_by_default: false + * + * // opt users out of browser data storage by this PostHog instance by default + * opt_out_persistence_by_default: false + * + * // persistence mechanism used by opt-in/opt-out methods - cookie + * // or localStorage - falls back to cookie if localStorage is unavailable + * opt_out_capturing_persistence_type: 'localStorage' + * + * // customize the name of cookie/localStorage set by opt-in/opt-out methods + * opt_out_capturing_cookie_prefix: null + * + * // type of persistent store for super properties (cookie/ + * // localStorage) if set to 'localStorage', any existing + * // posthog cookie value with the same persistence_name + * // will be transferred to localStorage and deleted + * persistence: 'cookie' + * + * // name for super properties persistent store + * persistence_name: '' + * + * // names of properties/superproperties which should never + * // be sent with capture() calls + * property_blacklist: [] + * + * // if this is true, posthog cookies will be marked as + * // secure, meaning they will only be transmitted over https + * secure_cookie: false + * + * // should we capture a page view on page load + * capture_pageview: true + * + * // if you set upgrade to be true, the library will check for + * // a cookie from our old js library and import super + * // properties from it, then the old cookie is deleted + * // The upgrade config option only works in the initialization, + * // so make sure you set it when you create the library. + * upgrade: false + * + * // extra HTTP request headers to set for each API request, in + * // the format {'Header-Name': value} + * xhr_headers: {} + * + * // protocol for fetching in-app message resources, e.g. + * // 'https://' or 'http://'; defaults to '//' (which defers to the + * // current page's protocol) + * inapp_protocol: '//' + * + * // whether to open in-app message link in new tab/window + * inapp_link_new_window: false + * + * // a set of rrweb config options that PostHog users can configure + * // see https://github.com/rrweb-io/rrweb/blob/master/guide.md + * session_recording: { + * blockClass: 'ph-no-capture', + * blockSelector: null, + * ignoreClass: 'ph-ignore-input', + * maskAllInputs: false, + * maskInputOptions: {}, + * maskInputFn: null, + * slimDOMOptions: {}, + * collectFonts: false + * } + * + * // prevent autocapture from capturing any attribute names on elements + * mask_all_element_attributes: false + * + * // prevent autocapture from capturing textContent on all elements + * mask_all_text: false + * + * // will disable requests to the /decide endpoint (please review documentation for details) + * // autocapture, feature flags, compression and session recording will be disabled when set to `true` + * advanced_disable_decide: false + * + * } + * + * + * @param {Object} config A dictionary of new configuration values to update + */ + static set_config(config: posthog.Config): void + + /** + * returns the current config object for the library. + */ + static get_config(prop_name: T): posthog.Config[T] + + /** + * Returns the value of the super property named property_name. If no such + * property is set, get_property() will return the undefined value. + * + * ### Notes: + * + * get_property() can only be called after the PostHog library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // grab value for 'user_id' after the posthog library has loaded + * posthog.init('YOUR PROJECT TOKEN', { + * loaded: function(posthog) { + * user_id = posthog.get_property('user_id'); + * } + * }); + * + * @param {String} property_name The name of the super property you want to retrieve + */ + static get_property(property_name: string): posthog.Property | undefined + + /** + * Returns the current distinct id of the user. This is either the id automatically + * generated by the library or the id that has been passed by a call to identify(). + * + * ### Notes: + * + * get_distinct_id() can only be called after the PostHog library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // set distinct_id after the posthog library has loaded + * posthog.init('YOUR PROJECT TOKEN', { + * loaded: function(posthog) { + * distinct_id = posthog.get_distinct_id(); + * } + * }); + */ + static get_distinct_id(): string + + /** + * Opt the user out of data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * // opt user out + * posthog.opt_out_capturing(); + * + * // opt user out with different cookie configuration from PostHog instance + * posthog.opt_out_capturing({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) + */ + static opt_out_capturing(options?: posthog.OptInOutCapturingOptions): void + + /** + * Opt the user in to data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * // opt user in + * posthog.opt_in_capturing(); + * + * // opt user in with specific event name, properties, cookie configuration + * posthog.opt_in_capturing({ + * capture_event_name: 'User opted in', + * capture_event_properties: { + * 'Email': 'jdoe@example.com' + * }, + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {function} [options.capture] Function used for capturing a PostHog event to record the opt-in action (default is this PostHog instance's capture method) + * @param {string} [options.capture_event_name=$opt_in] Event name to be used for capturing the opt-in action + * @param {Object} [options.capture_properties] Set of properties to be captured along with the opt-in action + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) + */ + static opt_in_capturing(options?: posthog.OptInOutCapturingOptions): void + + /** + * Check whether the user has opted out of data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * const has_opted_out = posthog.has_opted_out_capturing(); + * // use has_opted_out value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-out status + */ + static has_opted_out_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean + + /** + * Check whether the user has opted in to data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * const has_opted_in = posthog.has_opted_in_capturing(); + * // use has_opted_in value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-in status + */ + static has_opted_in_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean + + /** + * Clear the user's opt in/out status of data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * // clear user's opt-in/out status + * posthog.clear_opt_in_out_capturing(); + * + * // clear user's opt-in/out status with specific cookie configuration - should match + * // configuration used when opt_in_capturing/opt_out_capturing methods were called. + * posthog.clear_opt_in_out_capturing({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) + */ + static clear_opt_in_out_capturing(options?: posthog.ClearOptInOutCapturingOptions): void + + /* + * See if feature flag is enabled for user. + * + * ### Usage: + * + * if(posthog.isFeatureEnabled('beta-feature')) { // do something } + * + * @param {Object|String} prop Key of the feature flag. + * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. + */ + static isFeatureEnabled(key: string, options?: posthog.isFeatureEnabledOptions): boolean + + /* + * See if feature flags are available. + * + * ### Usage: + * + * posthog.onFeatureFlags(function(featureFlags) { // do something }) + * + * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. + */ + static onFeatureFlags(callback: (flags: string[]) => void): false | undefined + + /* + * Reload all feature flags for the user. + * + * ### Usage: + * + * posthog.reloadFeatureFlags() + */ + static reloadFeatureFlags(): void + + static toString(): string + + /* Will log all capture requests to the Javascript console, including event properties for easy debugging */ + static debug(): void + + /* + * Starts session recording and updates disable_session_recording to false. + * Used for manual session recording management. By default, session recording is enabled and + * starts automatically. + * + * ### Usage: + * + * posthog.startSessionRecording() + */ + static startSessionRecording(): void + + /* + * Stops session recording and updates disable_session_recording to true. + * + * ### Usage: + * + * posthog.stopSessionRecording() + */ + static stopSessionRecording(): void + + /* + * Check if session recording is currently running. + * + * ### Usage: + * + * const isSessionRecordingOn = posthog.sessionRecordingStarted() + */ + static sessionRecordingStarted(): boolean +} + +declare namespace posthog { + /* eslint-disable @typescript-eslint/no-explicit-any */ + type Property = any; + type Properties = Record; + type CaptureResult = { event: string; properties: Properties } | undefined; + type CaptureCallback = (response: any, data: any) => void; + /* eslint-enable @typescript-eslint/no-explicit-any */ + + interface Config { + api_host?: string + api_method?: string + api_transport?: string + autocapture?: boolean + rageclick?: boolean + cdn?: string + cross_subdomain_cookie?: boolean + persistence?: 'localStorage' | 'cookie' | 'memory' + persistence_name?: string + cookie_name?: string + loaded?: (posthog_instance: typeof posthog) => void + store_google?: boolean + save_referrer?: boolean + test?: boolean + verbose?: boolean + img?: boolean + capture_pageview?: boolean + debug?: boolean + cookie_expiration?: number + upgrade?: boolean + disable_session_recording?: boolean + disable_persistence?: boolean + disable_cookie?: boolean + secure_cookie?: boolean + ip?: boolean + opt_out_capturing_by_default?: boolean + opt_out_persistence_by_default?: boolean + opt_out_capturing_persistence_type?: 'localStorage' | 'cookie' + opt_out_capturing_cookie_prefix?: string | null + respect_dnt?: boolean + property_blacklist?: string[] + xhr_headers?: { [header_name: string]: string } + inapp_protocol?: string + inapp_link_new_window?: boolean + request_batching?: boolean + sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties + properties_string_max_length?: number + mask_all_element_attributes?: boolean + mask_all_text?: boolean + advanced_disable_decide?: boolean + } + + interface OptInOutCapturingOptions { + clear_persistence: boolean + persistence_type: string + cookie_prefix: string + cookie_expiration: number + cross_subdomain_cookie: boolean + secure_cookie: boolean + } + + interface HasOptedInOutCapturingOptions { + persistence_type: string + cookie_prefix: string + } + + interface ClearOptInOutCapturingOptions { + enable_persistence: boolean + persistence_type: string + cookie_prefix: string + cookie_expiration: number + cross_subdomain_cookie: boolean + secure_cookie: boolean + } + + interface isFeatureEnabledOptions { + send_event: boolean + } + + export class persistence { + static properties(): posthog.Properties + + static load(): void + + static save(): void + + static remove(): void + + static clear(): void + + /** + * @param {Object} props + * @param {*=} default_value + * @param {number=} days + */ + static register_once(props: Properties, default_value?: Property, days?: number): boolean + + /** + * @param {Object} props + * @param {number=} days + */ + static register(props: posthog.Properties, days?: number): boolean + + static unregister(prop: string): void + + static update_campaign_params(): void + + static update_search_keyword(referrer: string): void + + static update_referrer_info(referrer: string): void + + static get_referrer_info(): posthog.Properties + + static safe_merge(props: posthog.Properties): posthog.Properties + + static update_config(config: posthog.Config): void + + static set_disabled(disabled: boolean): void + + static set_cross_subdomain(cross_subdomain: boolean): void + + static get_cross_subdomain(): boolean + + static set_secure(secure: boolean): void + + static set_event_timer(event_name: string, timestamp: Date): void + + static remove_event_timer(event_name: string): Date | undefined + } + + export class people { + /* + * Set properties on a user record. + * + * ### Usage: + * + * posthog.people.set('gender', 'm'); + * + * // or set multiple properties at once + * posthog.people.set({ + * 'Company': 'Acme', + * 'Plan': 'Premium', + * 'Upgrade date': new Date() + * }); + * // properties can be strings, integers, dates, or lists + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after capturing the event. + */ + static set( + prop: posthog.Properties | string, + to?: posthog.Property, + callback?: posthog.CaptureCallback + ): posthog.Properties + + /* + * Set properties on a user record, only if they do not yet exist. + * This will not overwrite previous people property values, unlike + * people.set(). + * + * ### Usage: + * + * posthog.people.set_once('First Login Date', new Date()); + * + * // or set multiple properties at once + * posthog.people.set_once({ + * 'First Login Date': new Date(), + * 'Starting Plan': 'Premium' + * }); + * + * // properties can be strings, integers or dates + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after capturing the event. + */ + static set_once( + prop: posthog.Properties | string, + to?: posthog.Property, + callback?: posthog.CaptureCallback + ): posthog.Properties + + static toString(): string + } + + export class featureFlags { + static getFlags(): string[] + + static reloadFeatureFlags(): void + + /* + * See if feature flag is enabled for user. + * + * ### Usage: + * + * if(posthog.isFeatureEnabled('beta-feature')) { // do something } + * + * @param {Object|String} prop Key of the feature flag. + * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. + */ + static isFeatureEnabled(key: string, options?: { send_event?: boolean }): boolean + + /* + * See if feature flags are available. + * + * ### Usage: + * + * posthog.onFeatureFlags(function(featureFlags) { // do something }) + * + * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. + */ + static onFeatureFlags(callback: (flags: string[]) => void): false | undefined + } + + export class feature_flags extends featureFlags {} +} + +export type PostHog = typeof posthog; + +export default posthog; diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 410124a637..e48fd52cb1 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -48,6 +48,7 @@ import { Jitsi } from "./widgets/Jitsi"; import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY } from "./BasePlatform"; import ThreepidInviteStore from "./stores/ThreepidInviteStore"; import CountlyAnalytics from "./CountlyAnalytics"; +import { PosthogAnalytics } from "./PosthogAnalytics"; import CallHandler from './CallHandler'; import LifecycleCustomisations from "./customisations/Lifecycle"; import ErrorDialog from "./components/views/dialogs/ErrorDialog"; @@ -573,6 +574,8 @@ async function doSetLoggedIn( await abortLogin(); } + PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId); + Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl); MatrixClientPeg.replaceUsingCreds(credentials); @@ -700,6 +703,8 @@ export function logout(): void { CountlyAnalytics.instance.enable(/* anonymous = */ true); } + PosthogAnalytics.instance.logout(); + if (MatrixClientPeg.get().isGuest()) { // logout doesn't work for guest sessions // Also we sometimes want to re-log in a guest session if we abort the login. diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts new file mode 100644 index 0000000000..860a155aff --- /dev/null +++ b/src/PosthogAnalytics.ts @@ -0,0 +1,355 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import posthog, { PostHog } from 'posthog-js'; +import PlatformPeg from './PlatformPeg'; +import SdkConfig from './SdkConfig'; +import SettingsStore from './settings/SettingsStore'; + +/* Posthog analytics tracking. + * + * Anonymity behaviour is as follows: + * + * - If Posthog isn't configured in `config.json`, events are not sent. + * - If [Do Not Track](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/doNotTrack) is + * enabled, events are not sent (this detection is built into posthog and turned on via the + * `respect_dnt` flag being passed to `posthog.init`). + * - If the `feature_pseudonymous_analytics_opt_in` labs flag is `true`, track pseudonomously, i.e. + * hash all matrix identifiers in tracking events (user IDs, room IDs etc) using SHA-256. + * - Otherwise, if the existing `analyticsOptIn` flag is `true`, track anonymously, i.e. + * redact all matrix identifiers in tracking events. + * - If both flags are false or not set, events are not sent. + */ + +interface IEvent { + // The event name that will be used by PostHog. Event names should use snake_case. + eventName: string; + + // The properties of the event that will be stored in PostHog. This is just a placeholder, + // extending interfaces must override this with a concrete definition to do type validation. + properties: {}; +} + +export enum Anonymity { + Disabled, + Anonymous, + Pseudonymous +} + +// If an event extends IPseudonymousEvent, the event contains pseudonymous data +// that won't be sent unless the user has explicitly consented to pseudonymous tracking. +// For example, it might contain hashed user IDs or room IDs. +// Such events will be automatically dropped if PosthogAnalytics.anonymity isn't set to Pseudonymous. +export interface IPseudonymousEvent extends IEvent {} + +// If an event extends IAnonymousEvent, the event strictly contains *only* anonymous data; +// i.e. no identifiers that can be associated with the user. +export interface IAnonymousEvent extends IEvent {} + +export interface IRoomEvent extends IPseudonymousEvent { + hashedRoomId: string; +} + +interface IPageView extends IAnonymousEvent { + eventName: "$pageview"; + properties: { + durationMs?: number; + screen?: string; + }; +} + +const hashHex = async (input: string): Promise => { + const buf = new TextEncoder().encode(input); + const digestBuf = await window.crypto.subtle.digest("sha-256", buf); + return [...new Uint8Array(digestBuf)].map((b: number) => b.toString(16).padStart(2, "0")).join(""); +}; + +const whitelistedScreens = new Set([ + "register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory", + "start_sso", "start_cas", "groups", "complete_security", "post_registration", "room", "user", "group", +]); + +export async function getRedactedCurrentLocation( + origin: string, + hash: string, + pathname: string, + anonymity: Anonymity, +): Promise { + // Redact PII from the current location. + // If anonymous is true, redact entirely, if false, substitute it with a hash. + // For known screens, assumes a URL structure of //might/be/pii + if (origin.startsWith('file://')) { + pathname = "//"; + } + + let hashStr; + if (hash == "") { + hashStr = ""; + } else { + let [beforeFirstSlash, screen, ...parts] = hash.split("/"); + + if (!whitelistedScreens.has(screen)) { + screen = ""; + } + + for (let i = 0; i < parts.length; i++) { + parts[i] = anonymity === Anonymity.Anonymous ? `` : await hashHex(parts[i]); + } + + hashStr = `${beforeFirstSlash}/${screen}/${parts.join("/")}`; + } + return origin + pathname + hashStr; +} + +interface PlatformProperties { + appVersion: string; + appPlatform: string; +} + +export class PosthogAnalytics { + /* Wrapper for Posthog analytics. + * 3 modes of anonymity are supported, governed by this.anonymity + * - Anonymity.Disabled means *no data* is passed to posthog + * - Anonymity.Anonymous means all identifers will be redacted before being passed to posthog + * - Anonymity.Pseudonymous means all identifiers will be hashed via SHA-256 before being passed + * to Posthog + * + * To update anonymity, call updateAnonymityFromSettings() or you can set it directly via setAnonymity(). + * + * To pass an event to Posthog: + * + * 1. Declare a type for the event, extending IAnonymousEvent, IPseudonymousEvent or IRoomEvent. + * 2. Call the appropriate track*() method. Pseudonymous events will be dropped when anonymity is + * Anonymous or Disabled; Anonymous events will be dropped when anonymity is Disabled. + */ + + private anonymity = Anonymity.Disabled; + // set true during the constructor if posthog config is present, otherwise false + private enabled = false; + private static _instance = null; + private platformSuperProperties = {}; + + public static get instance(): PosthogAnalytics { + if (!this._instance) { + this._instance = new PosthogAnalytics(posthog); + } + return this._instance; + } + + constructor(private readonly posthog: PostHog) { + const posthogConfig = SdkConfig.get()["posthog"]; + if (posthogConfig) { + this.posthog.init(posthogConfig.projectApiKey, { + api_host: posthogConfig.apiHost, + autocapture: false, + mask_all_text: true, + mask_all_element_attributes: true, + // This only triggers on page load, which for our SPA isn't particularly useful. + // Plus, the .capture call originating from somewhere in posthog makes it hard + // to redact URLs, which requires async code. + // + // To raise this manually, just call .capture("$pageview") or posthog.capture_pageview. + capture_pageview: false, + sanitize_properties: this.sanitizeProperties, + respect_dnt: true, + }); + this.enabled = true; + } else { + this.enabled = false; + } + } + + private sanitizeProperties = (properties: posthog.Properties): posthog.Properties => { + // Callback from posthog to sanitize properties before sending them to the server. + // + // Here we sanitize posthog's built in properties which leak PII e.g. url reporting. + // See utils.js _.info.properties in posthog-js. + + // Replace the $current_url with a redacted version. + // $redacted_current_url is injected by this class earlier in capture(), as its generation + // is async and can't be done in this non-async callback. + if (!properties['$redacted_current_url']) { + console.log("$redacted_current_url not set in sanitizeProperties, will drop $current_url entirely"); + } + properties['$current_url'] = properties['$redacted_current_url']; + delete properties['$redacted_current_url']; + + if (this.anonymity == Anonymity.Anonymous) { + // drop referrer information for anonymous users + properties['$referrer'] = null; + properties['$referring_domain'] = null; + properties['$initial_referrer'] = null; + properties['$initial_referring_domain'] = null; + + // drop device ID, which is a UUID persisted in local storage + properties['$device_id'] = null; + } + + return properties; + }; + + private static getAnonymityFromSettings(): Anonymity { + // determine the current anonymity level based on current user settings + + // "Send anonymous usage data which helps us improve Element. This will use a cookie." + const analyticsOptIn = SettingsStore.getValue("analyticsOptIn", null, true); + + // (proposed wording) "Send pseudonymous usage data which helps us improve Element. This will use a cookie." + // + // TODO: Currently, this is only a labs flag, for testing purposes. + const pseudonumousOptIn = SettingsStore.getValue("feature_pseudonymous_analytics_opt_in", null, true); + + let anonymity; + if (pseudonumousOptIn) { + anonymity = Anonymity.Pseudonymous; + } else if (analyticsOptIn) { + anonymity = Anonymity.Anonymous; + } else { + anonymity = Anonymity.Disabled; + } + + return anonymity; + } + + private registerSuperProperties(properties: posthog.Properties) { + if (this.enabled) { + this.posthog.register(properties); + } + } + + private static async getPlatformProperties(): Promise { + const platform = PlatformPeg.get(); + let appVersion; + try { + appVersion = await platform.getAppVersion(); + } catch (e) { + // this happens if no version is set i.e. in dev + appVersion = "unknown"; + } + + return { + appVersion, + appPlatform: platform.getHumanReadableName(), + }; + } + + private async capture(eventName: string, properties: posthog.Properties) { + if (!this.enabled) { + return; + } + const { origin, hash, pathname } = window.location; + properties['$redacted_current_url'] = await getRedactedCurrentLocation( + origin, hash, pathname, this.anonymity); + this.posthog.capture(eventName, properties); + } + + public isEnabled(): boolean { + return this.enabled; + } + + public setAnonymity(anonymity: Anonymity): void { + // Update this.anonymity. + // This is public for testing purposes, typically you want to call updateAnonymityFromSettings + // to ensure this value is in step with the user's settings. + if (this.enabled && (anonymity == Anonymity.Disabled || anonymity == Anonymity.Anonymous)) { + // when transitioning to Disabled or Anonymous ensure we clear out any prior state + // set in posthog e.g. distinct ID + this.posthog.reset(); + // Restore any previously set platform super properties + this.registerSuperProperties(this.platformSuperProperties); + } + this.anonymity = anonymity; + } + + public async identifyUser(userId: string): Promise { + if (this.anonymity == Anonymity.Pseudonymous) { + this.posthog.identify(await hashHex(userId)); + } + } + + public getAnonymity(): Anonymity { + return this.anonymity; + } + + public logout(): void { + if (this.enabled) { + this.posthog.reset(); + } + this.setAnonymity(Anonymity.Anonymous); + } + + public async trackPseudonymousEvent( + eventName: E["eventName"], + properties: E["properties"] = {}, + ) { + if (this.anonymity == Anonymity.Anonymous || this.anonymity == Anonymity.Disabled) return; + await this.capture(eventName, properties); + } + + public async trackAnonymousEvent( + eventName: E["eventName"], + properties: E["properties"] = {}, + ): Promise { + if (this.anonymity == Anonymity.Disabled) return; + await this.capture(eventName, properties); + } + + public async trackRoomEvent( + eventName: E["eventName"], + roomId: string, + properties: Omit, + ): Promise { + const updatedProperties = { + ...properties, + hashedRoomId: roomId ? await hashHex(roomId) : null, + }; + await this.trackPseudonymousEvent(eventName, updatedProperties); + } + + public async trackPageView(durationMs: number): Promise { + const hash = window.location.hash; + + let screen = null; + const split = hash.split("/"); + if (split.length >= 2) { + screen = split[1]; + } + + await this.trackAnonymousEvent("$pageview", { + durationMs, + screen, + }); + } + + public async updatePlatformSuperProperties(): Promise { + // Update super properties in posthog with our platform (app version, platform). + // These properties will be subsequently passed in every event. + // + // This only needs to be done once per page lifetime. Note that getPlatformProperties + // is async and can involve a network request if we are running in a browser. + this.platformSuperProperties = await PosthogAnalytics.getPlatformProperties(); + this.registerSuperProperties(this.platformSuperProperties); + } + + public async updateAnonymityFromSettings(userId?: string): Promise { + // Update this.anonymity based on the user's analytics opt-in settings + // Identify the user (via hashed user ID) to posthog if anonymity is pseudonmyous + this.setAnonymity(PosthogAnalytics.getAnonymityFromSettings()); + if (userId && this.getAnonymity() == Anonymity.Pseudonymous) { + await this.identifyUser(userId); + } + } +} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 8cfe35c4cf..60c78b5f9e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -107,6 +107,7 @@ import UIStore, { UI_EVENTS } from "../../stores/UIStore"; import SoftLogout from './auth/SoftLogout'; import { makeRoomPermalink } from "../../utils/permalinks/Permalinks"; import { copyPlaintext } from "../../utils/strings"; +import { PosthogAnalytics } from '../../PosthogAnalytics'; /** constants for MatrixChat.state.view */ export enum Views { @@ -387,6 +388,10 @@ export default class MatrixChat extends React.PureComponent { if (SettingsStore.getValue("analyticsOptIn")) { Analytics.enable(); } + + PosthogAnalytics.instance.updateAnonymityFromSettings(); + PosthogAnalytics.instance.updatePlatformSuperProperties(); + CountlyAnalytics.instance.enable(/* anonymous = */ true); } @@ -443,6 +448,7 @@ export default class MatrixChat extends React.PureComponent { const durationMs = this.stopPageChangeTimer(); Analytics.trackPageChange(durationMs); CountlyAnalytics.instance.trackPageChange(durationMs); + PosthogAnalytics.instance.trackPageView(durationMs); } if (this.focusComposer) { dis.fire(Action.FocusSendMessageComposer); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 79d501e712..25b0b86cb1 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -36,6 +36,7 @@ import { UIFeature } from "../../../../../settings/UIFeature"; import { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel"; import CountlyAnalytics from "../../../../../CountlyAnalytics"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; +import { PosthogAnalytics } from "../../../../../PosthogAnalytics"; export class IgnoredUser extends React.Component { static propTypes = { @@ -106,6 +107,7 @@ export default class SecurityUserSettingsTab extends React.Component { _updateAnalytics = (checked) => { checked ? Analytics.enable() : Analytics.disable(); CountlyAnalytics.instance.enable(/* anonymous = */ !checked); + PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId()); }; _onExportE2eKeysClicked = () => { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3ad8daa85c..87cd9afb5b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -813,6 +813,7 @@ "Show message previews for reactions in DMs": "Show message previews for reactions in DMs", "Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms", "Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices", + "Send pseudonymous analytics data": "Send pseudonymous analytics data", "Enable advanced debugging for the room list": "Enable advanced debugging for the room list", "Show info about bridges in room settings": "Show info about bridges in room settings", "New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index c36e2b90bf..cfe2c097fc 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -41,6 +41,7 @@ import { Layout } from "./Layout"; import ReducedMotionController from './controllers/ReducedMotionController'; import IncompatibleController from "./controllers/IncompatibleController"; import SdkConfig from "../SdkConfig"; +import PseudonymousAnalyticsController from './controllers/PseudonymousAnalyticsController'; import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController'; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times @@ -268,6 +269,13 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_pseudonymous_analytics_opt_in": { + isFeature: true, + supportedLevels: LEVELS_FEATURE, + displayName: _td('Send pseudonymous analytics data'), + default: false, + controller: new PseudonymousAnalyticsController(), + }, "advancedRoomListLogging": { // TODO: Remove flag before launch: https://github.com/vector-im/element-web/issues/14231 displayName: _td("Enable advanced debugging for the room list"), diff --git a/src/settings/controllers/PseudonymousAnalyticsController.ts b/src/settings/controllers/PseudonymousAnalyticsController.ts new file mode 100644 index 0000000000..a82b9685ef --- /dev/null +++ b/src/settings/controllers/PseudonymousAnalyticsController.ts @@ -0,0 +1,26 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SettingController from "./SettingController"; +import { SettingLevel } from "../SettingLevel"; +import { PosthogAnalytics } from "../../PosthogAnalytics"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; + +export default class PseudonymousAnalyticsController extends SettingController { + public onChange(level: SettingLevel, roomId: string, newValue: any) { + PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId()); + } +} diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts new file mode 100644 index 0000000000..6cb1743051 --- /dev/null +++ b/test/PosthogAnalytics-test.ts @@ -0,0 +1,232 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { + Anonymity, + getRedactedCurrentLocation, + IAnonymousEvent, + IPseudonymousEvent, + IRoomEvent, + PosthogAnalytics, +} from '../src/PosthogAnalytics'; + +import SdkConfig from '../src/SdkConfig'; + +class FakePosthog { + public capture; + public init; + public identify; + public reset; + public register; + + constructor() { + this.capture = jest.fn(); + this.init = jest.fn(); + this.identify = jest.fn(); + this.reset = jest.fn(); + this.register = jest.fn(); + } +} + +export interface ITestEvent extends IAnonymousEvent { + key: "jest_test_event"; + properties: { + foo: string; + }; +} + +export interface ITestPseudonymousEvent extends IPseudonymousEvent { + key: "jest_test_pseudo_event"; + properties: { + foo: string; + }; +} + +export interface ITestRoomEvent extends IRoomEvent { + key: "jest_test_room_event"; + properties: { + foo: string; + }; +} + +describe("PosthogAnalytics", () => { + let fakePosthog: FakePosthog; + const shaHashes = { + "42": "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049", + "some": "a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b", + "pii": "bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4", + "foo": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", + }; + + beforeEach(() => { + fakePosthog = new FakePosthog(); + + window.crypto = { + subtle: { + digest: async (_, encodedMessage) => { + const message = new TextDecoder().decode(encodedMessage); + const hexHash = shaHashes[message]; + const bytes = []; + for (let c = 0; c < hexHash.length; c += 2) { + bytes.push(parseInt(hexHash.substr(c, 2), 16)); + } + return bytes; + }, + }, + }; + }); + + afterEach(() => { + window.crypto = null; + }); + + describe("Initialisation", () => { + it("Should not be enabled without config being set", () => { + jest.spyOn(SdkConfig, "get").mockReturnValue({}); + const analytics = new PosthogAnalytics(fakePosthog); + expect(analytics.isEnabled()).toBe(false); + }); + + it("Should be enabled if config is set", () => { + jest.spyOn(SdkConfig, "get").mockReturnValue({ + posthog: { + projectApiKey: "foo", + apiHost: "bar", + }, + }); + const analytics = new PosthogAnalytics(fakePosthog); + analytics.setAnonymity(Anonymity.Pseudonymous); + expect(analytics.isEnabled()).toBe(true); + }); + }); + + describe("Tracking", () => { + let analytics: PosthogAnalytics; + + beforeEach(() => { + jest.spyOn(SdkConfig, "get").mockReturnValue({ + posthog: { + projectApiKey: "foo", + apiHost: "bar", + }, + }); + + analytics = new PosthogAnalytics(fakePosthog); + }); + + it("Should pass trackAnonymousEvent() to posthog", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + await analytics.trackAnonymousEvent("jest_test_event", { + foo: "bar", + }); + expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); + expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); + }); + + it("Should pass trackRoomEvent to posthog", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + const roomId = "42"; + await analytics.trackRoomEvent("jest_test_event", roomId, { + foo: "bar", + }); + expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); + expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); + expect(fakePosthog.capture.mock.calls[0][1]["hashedRoomId"]) + .toEqual("73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"); + }); + + it("Should pass trackPseudonymousEvent() to posthog", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + await analytics.trackPseudonymousEvent("jest_test_pseudo_event", { + foo: "bar", + }); + expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_pseudo_event"); + expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); + }); + + it("Should not track pseudonymous messages if anonymous", async () => { + analytics.setAnonymity(Anonymity.Anonymous); + await analytics.trackPseudonymousEvent("jest_test_event", { + foo: "bar", + }); + expect(fakePosthog.capture.mock.calls.length).toBe(0); + }); + + it("Should not track any events if disabled", async () => { + analytics.setAnonymity(Anonymity.Disabled); + await analytics.trackPseudonymousEvent("jest_test_event", { + foo: "bar", + }); + await analytics.trackAnonymousEvent("jest_test_event", { + foo: "bar", + }); + await analytics.trackRoomEvent("room id", "foo", { + foo: "bar", + }); + await analytics.trackPageView(200); + expect(fakePosthog.capture.mock.calls.length).toBe(0); + }); + + it("Should pseudonymise a location of a known screen", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "#/register/some/pii", "/", Anonymity.Pseudonymous); + expect(location).toBe( + `https://foo.bar/#/register/\ +a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\ +bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`); + }); + + it("Should anonymise a location of a known screen", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "#/register/some/pii", "/", Anonymity.Anonymous); + expect(location).toBe("https://foo.bar/#/register//"); + }); + + it("Should pseudonymise a location of an unknown screen", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Pseudonymous); + expect(location).toBe( + `https://foo.bar/#//\ +a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\ +bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`); + }); + + it("Should anonymise a location of an unknown screen", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Anonymous); + expect(location).toBe("https://foo.bar/#///"); + }); + + it("Should handle an empty hash", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "", "/", Anonymity.Anonymous); + expect(location).toBe("https://foo.bar/"); + }); + + it("Should identify the user to posthog if pseudonymous", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + await analytics.identifyUser("foo"); + expect(fakePosthog.identify.mock.calls[0][0]) + .toBe("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); + }); + + it("Should not identify the user to posthog if anonymous", async () => { + analytics.setAnonymity(Anonymity.Anonymous); + await analytics.identifyUser("foo"); + expect(fakePosthog.identify.mock.calls.length).toBe(0); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index b139e8e8d1..b982d40b07 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,10 +22,15 @@ "es2019", "dom", "dom.iterable" - ] + ], + "paths": { + "posthog-js": [ + "./src/@types/posthog.d.ts" + ] + } }, "include": [ "./src/**/*.ts", "./src/**/*.tsx" - ] + ], } diff --git a/yarn.lock b/yarn.lock index 2a03f640ee..daed6f4377 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3601,6 +3601,11 @@ fbjs@^0.8.4: setimmediate "^1.0.5" ua-parser-js "^0.7.18" +fflate@^0.4.1: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + file-entry-cache@^6.0.0, file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -6249,6 +6254,13 @@ postcss@^8.0.2: nanoid "^3.1.23" source-map-js "^0.6.2" +posthog-js@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.1.tgz#97834ee2574f34ffb5db2f5b07452c847e3c4d27" + integrity sha512-Y3lzcWkS8xFY6Ryj3I4ees7qWP2WGkLw0Arcbk5xaT0+5YlA6UC2jlL/+fN9bz/Bl62EoN3BML901Cuot/QNjg== + dependencies: + fflate "^0.4.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" From c66d0017de6d0540f428b26cfc66e8bf7461fba5 Mon Sep 17 00:00:00 2001 From: James Salter Date: Tue, 3 Aug 2021 11:32:51 +0100 Subject: [PATCH 046/100] Patch posthog's type definitions using patch-package Remove definitions for sentry and rrweb-snapshot --- package.json | 7 +- patches/posthog-js+1.12.2.patch | 78 ++++ src/@types/posthog.d.ts | 748 -------------------------------- tsconfig.json | 5 - yarn.lock | 102 ++++- 5 files changed, 177 insertions(+), 763 deletions(-) create mode 100644 patches/posthog-js+1.12.2.patch delete mode 100644 src/@types/posthog.d.ts diff --git a/package.json b/package.json index b7e06fe012..e7ea85ccd4 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "lint:style": "stylelint 'res/css/**/*.scss'", "test": "jest", "test:e2e": "./test/end-to-end-tests/run.sh --app-url http://localhost:8080", - "coverage": "yarn test --coverage" + "coverage": "yarn test --coverage", + "postinstall": "patch-package" }, "dependencies": { "@babel/runtime": "^7.12.5", @@ -87,7 +88,7 @@ "pako": "^2.0.3", "parse5": "^6.0.1", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.12.1", + "posthog-js": "1.12.2", "prop-types": "^15.7.2", "qrcode": "^1.4.4", "re-resizable": "^6.9.0", @@ -165,6 +166,8 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.3", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", + "patch-package": "^6.4.7", + "postinstall-postinstall": "^2.1.0", "react-test-renderer": "^17.0.2", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/patches/posthog-js+1.12.2.patch b/patches/posthog-js+1.12.2.patch new file mode 100644 index 0000000000..430a9f0a62 --- /dev/null +++ b/patches/posthog-js+1.12.2.patch @@ -0,0 +1,78 @@ +diff --git a/node_modules/posthog-js/dist/module.d.ts b/node_modules/posthog-js/dist/module.d.ts +index d3cba02..8c87ef8 100644 +--- a/node_modules/posthog-js/dist/module.d.ts ++++ b/node_modules/posthog-js/dist/module.d.ts +@@ -1,7 +1,5 @@ + // Type definitions for exported methods +- +-import { EventProcessor, Integration } from '@sentry/types' +-import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot' ++// Patched to remove references to sentry and rrweb-snapshot which aren't otherwise SDK dependencies + + declare class posthog { + /** +@@ -485,25 +483,6 @@ declare class posthog { + */ + static reloadFeatureFlags(): void + +- /** +- * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog +- * +- * ### Usage +- * +- * Sentry.init({ +- * dsn: 'https://example', +- * integrations: [ +- * new posthog.SentryIntegration(posthog) +- * ] +- * }) +- * +- * @param {Object} [posthog] The posthog object +- * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry +- * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry +- * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/) +- */ +- static SentryIntegration: typeof SentryIntegration +- + static toString(): string + + /* Will log all capture requests to the Javascript console, including event properties for easy debugging */ +@@ -585,7 +564,6 @@ declare namespace posthog { + request_batching?: boolean + sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties + properties_string_max_length?: number +- session_recording?: SessionRecordingOptions + mask_all_element_attributes?: boolean + mask_all_text?: boolean + advanced_disable_decide?: boolean +@@ -618,17 +596,6 @@ declare namespace posthog { + send_event: boolean + } + +- interface SessionRecordingOptions { +- blockClass?: string | RegExp +- blockSelector?: string +- ignoreClass?: string +- maskAllInputs?: boolean +- maskInputOptions?: MaskInputOptions +- maskInputFn?: (text: string) => string +- slimDOMOptions?: SlimDOMOptions | 'all' | true +- collectFonts?: boolean +- } +- + export class persistence { + static properties(): posthog.Properties + +@@ -768,12 +735,6 @@ declare namespace posthog { + export class feature_flags extends featureFlags {} + } + +-export class SentryIntegration implements Integration { +- constructor(posthog: posthog, organization?: string, projectId?: number, prefix?: string) +- name: string +- setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void): void +-} +- + export type PostHog = typeof posthog + + export default posthog diff --git a/src/@types/posthog.d.ts b/src/@types/posthog.d.ts deleted file mode 100644 index 1ca475cd3b..0000000000 --- a/src/@types/posthog.d.ts +++ /dev/null @@ -1,748 +0,0 @@ -// A clone of the type definitions from posthog-js, stripped of references to transitive -// dependencies which we don't actually use, so that we don't need to install them. -// -// Original file lives in node_modules/posthog/dist/module.d.ts - -/* eslint-disable @typescript-eslint/member-delimiter-style */ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable camelcase */ - -// Type definitions for exported methods - -declare class posthog { - /** - * This function initializes a new instance of the PostHog capturing object. - * All new instances are added to the main posthog object as sub properties (such as - * posthog.library_name) and also returned by this function. To define a - * second instance on the page, you would call: - * - * posthog.init('new token', { your: 'config' }, 'library_name'); - * - * and use it like so: - * - * posthog.library_name.capture(...); - * - * @param {String} token Your PostHog API token - * @param {Object} [config] A dictionary of config options to override. See a list of default config options. - * @param {String} [name] The name for the new posthog instance that you want created - */ - static init(token: string, config?: posthog.Config, name?: string): posthog - - /** - * Clears super properties and generates a new random distinct_id for this instance. - * Useful for clearing data when a user logs out. - */ - static reset(reset_device_id?: boolean): void - - /** - * Capture an event. This is the most important and - * frequently used PostHog function. - * - * ### Usage: - * - * // capture an event named 'Registered' - * posthog.capture('Registered', {'Gender': 'Male', 'Age': 21}); - * - * // capture an event using navigator.sendBeacon - * posthog.capture('Left page', {'duration_seconds': 35}, {transport: 'sendBeacon'}); - * - * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. - * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. - * @param {Object} [options] Optional configuration for this capture request. - * @param {String} [options.transport] Transport method for network request ('XHR' or 'sendBeacon'). - */ - static capture( - event_name: string, - properties?: posthog.Properties, - options?: { transport: 'XHR' | 'sendBeacon' } - ): posthog.CaptureResult - - /** - * Capture a page view event, which is currently ignored by the server. - * This function is called by default on page load unless the - * capture_pageview configuration variable is false. - * - * @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url. - * @api private - */ - static capture_pageview(page?: string): void - - /** - * Register a set of super properties, which are included with all - * events. This will overwrite previous super property values. - * - * ### Usage: - * - * // register 'Gender' as a super property - * posthog.register({'Gender': 'Female'}); - * - * // register several super properties when a user signs up - * posthog.register({ - * 'Email': 'jdoe@example.com', - * 'Account Type': 'Free' - * }); - * - * @param {Object} properties An associative array of properties to store about the user - * @param {Number} [days] How many days since the user's last visit to store the super properties - */ - static register(properties: posthog.Properties, days?: number): void - - /** - * Register a set of super properties only once. This will not - * overwrite previous super property values, unlike register(). - * - * ### Usage: - * - * // register a super property for the first time only - * posthog.register_once({ - * 'First Login Date': new Date().toISOString() - * }); - * - * ### Notes: - * - * If default_value is specified, current super properties - * with that value will be overwritten. - * - * @param {Object} properties An associative array of properties to store about the user - * @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None' - * @param {Number} [days] How many days since the users last visit to store the super properties - */ - static register_once(properties: posthog.Properties, default_value?: posthog.Property, days?: number): void - - /** - * Delete a super property stored with the current user. - * - * @param {String} property The name of the super property to remove - */ - static unregister(property: string): void - - /** - * Identify a user with a unique ID instead of a PostHog - * randomly generated distinct_id. If the method is never called, - * then unique visitors will be identified by a UUID generated - * the first time they visit the site. - * - * If user properties are passed, they are also sent to posthog. - * - * ### Usage: - * - * posthog.identify('[user unique id]') - * posthog.identify('[user unique id]', { email: 'john@example.com' }) - * posthog.identify('[user unique id]', {}, { referral_code: '12345' }) - * - * ### Notes: - * - * You can call this function to overwrite a previously set - * unique ID for the current user. PostHog cannot translate - * between IDs at this time, so when you change a user's ID - * they will appear to be a new user. - * - * When used alone, posthog.identify will change the user's - * distinct_id to the unique ID provided. When used in tandem - * with posthog.alias, it will allow you to identify based on - * unique ID and map that back to the original, anonymous - * distinct_id given to the user upon her first arrival to your - * site (thus connecting anonymous pre-signup activity to - * post-signup activity). Though the two work together, do not - * call identify() at the same time as alias(). Calling the two - * at the same time can cause a race condition, so it is best - * practice to call identify on the original, anonymous ID - * right after you've aliased it. - * - * @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used. - * @param {Object} [userProperties] Optional: An associative array of properties to store about the user - * @param {Object} [userPropertiesToSetOnce] Optional: An associative array of properties to store about the user. If property is previously set, this does not override that value. - */ - static identify( - unique_id?: string, - userPropertiesToSet?: posthog.Properties, - userPropertiesToSetOnce?: posthog.Properties - ): void - - /** - * Create an alias, which PostHog will use to link two distinct_ids going forward (not retroactively). - * Multiple aliases can map to the same original ID, but not vice-versa. Aliases can also be chained - the - * following is a valid scenario: - * - * posthog.alias('new_id', 'existing_id'); - * ... - * posthog.alias('newer_id', 'new_id'); - * - * If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID. - * - * ### Notes: - * - * The best practice is to call alias() when a unique ID is first created for a user - * (e.g., when a user first registers for an account and provides an email address). - * alias() should never be called more than once for a given user, except to - * chain a newer ID to a previously new ID, as described above. - * - * @param {String} alias A unique identifier that you want to use for this user in the future. - * @param {String} [original] The current identifier being used for this user. - */ - static alias(alias: string, original?: string): posthog.CaptureResult | number - - /** - * Update the configuration of a posthog library instance. - * - * The default config is: - * - * { - * // HTTP method for capturing requests - * api_method: 'POST' - * - * // transport for sending requests ('XHR' or 'sendBeacon') - * // NB: sendBeacon should only be used for scenarios such as - * // page unload where a "best-effort" attempt to send is - * // acceptable; the sendBeacon API does not support callbacks - * // or any way to know the result of the request. PostHog - * // capturing via sendBeacon will not support any event- - * // batching or retry mechanisms. - * api_transport: 'XHR' - * - * // Automatically capture clicks, form submissions and change events - * autocapture: true - * - * // Capture rage clicks (beta) - useful for session recording - * rageclick: false - * - * // super properties cookie expiration (in days) - * cookie_expiration: 365 - * - * // super properties span subdomains - * cross_subdomain_cookie: true - * - * // debug mode - * debug: false - * - * // if this is true, the posthog cookie or localStorage entry - * // will be deleted, and no user persistence will take place - * disable_persistence: false - * - * // if this is true, PostHog will automatically determine - * // City, Region and Country data using the IP address of - * //the client - * ip: true - * - * // opt users out of capturing by this PostHog instance by default - * opt_out_capturing_by_default: false - * - * // opt users out of browser data storage by this PostHog instance by default - * opt_out_persistence_by_default: false - * - * // persistence mechanism used by opt-in/opt-out methods - cookie - * // or localStorage - falls back to cookie if localStorage is unavailable - * opt_out_capturing_persistence_type: 'localStorage' - * - * // customize the name of cookie/localStorage set by opt-in/opt-out methods - * opt_out_capturing_cookie_prefix: null - * - * // type of persistent store for super properties (cookie/ - * // localStorage) if set to 'localStorage', any existing - * // posthog cookie value with the same persistence_name - * // will be transferred to localStorage and deleted - * persistence: 'cookie' - * - * // name for super properties persistent store - * persistence_name: '' - * - * // names of properties/superproperties which should never - * // be sent with capture() calls - * property_blacklist: [] - * - * // if this is true, posthog cookies will be marked as - * // secure, meaning they will only be transmitted over https - * secure_cookie: false - * - * // should we capture a page view on page load - * capture_pageview: true - * - * // if you set upgrade to be true, the library will check for - * // a cookie from our old js library and import super - * // properties from it, then the old cookie is deleted - * // The upgrade config option only works in the initialization, - * // so make sure you set it when you create the library. - * upgrade: false - * - * // extra HTTP request headers to set for each API request, in - * // the format {'Header-Name': value} - * xhr_headers: {} - * - * // protocol for fetching in-app message resources, e.g. - * // 'https://' or 'http://'; defaults to '//' (which defers to the - * // current page's protocol) - * inapp_protocol: '//' - * - * // whether to open in-app message link in new tab/window - * inapp_link_new_window: false - * - * // a set of rrweb config options that PostHog users can configure - * // see https://github.com/rrweb-io/rrweb/blob/master/guide.md - * session_recording: { - * blockClass: 'ph-no-capture', - * blockSelector: null, - * ignoreClass: 'ph-ignore-input', - * maskAllInputs: false, - * maskInputOptions: {}, - * maskInputFn: null, - * slimDOMOptions: {}, - * collectFonts: false - * } - * - * // prevent autocapture from capturing any attribute names on elements - * mask_all_element_attributes: false - * - * // prevent autocapture from capturing textContent on all elements - * mask_all_text: false - * - * // will disable requests to the /decide endpoint (please review documentation for details) - * // autocapture, feature flags, compression and session recording will be disabled when set to `true` - * advanced_disable_decide: false - * - * } - * - * - * @param {Object} config A dictionary of new configuration values to update - */ - static set_config(config: posthog.Config): void - - /** - * returns the current config object for the library. - */ - static get_config(prop_name: T): posthog.Config[T] - - /** - * Returns the value of the super property named property_name. If no such - * property is set, get_property() will return the undefined value. - * - * ### Notes: - * - * get_property() can only be called after the PostHog library has finished loading. - * init() has a loaded function available to handle this automatically. For example: - * - * // grab value for 'user_id' after the posthog library has loaded - * posthog.init('YOUR PROJECT TOKEN', { - * loaded: function(posthog) { - * user_id = posthog.get_property('user_id'); - * } - * }); - * - * @param {String} property_name The name of the super property you want to retrieve - */ - static get_property(property_name: string): posthog.Property | undefined - - /** - * Returns the current distinct id of the user. This is either the id automatically - * generated by the library or the id that has been passed by a call to identify(). - * - * ### Notes: - * - * get_distinct_id() can only be called after the PostHog library has finished loading. - * init() has a loaded function available to handle this automatically. For example: - * - * // set distinct_id after the posthog library has loaded - * posthog.init('YOUR PROJECT TOKEN', { - * loaded: function(posthog) { - * distinct_id = posthog.get_distinct_id(); - * } - * }); - */ - static get_distinct_id(): string - - /** - * Opt the user out of data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * // opt user out - * posthog.opt_out_capturing(); - * - * // opt user out with different cookie configuration from PostHog instance - * posthog.opt_out_capturing({ - * cookie_expiration: 30, - * secure_cookie: true - * }); - * - * @param {Object} [options] A dictionary of config options to override - * @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) - */ - static opt_out_capturing(options?: posthog.OptInOutCapturingOptions): void - - /** - * Opt the user in to data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * // opt user in - * posthog.opt_in_capturing(); - * - * // opt user in with specific event name, properties, cookie configuration - * posthog.opt_in_capturing({ - * capture_event_name: 'User opted in', - * capture_event_properties: { - * 'Email': 'jdoe@example.com' - * }, - * cookie_expiration: 30, - * secure_cookie: true - * }); - * - * @param {Object} [options] A dictionary of config options to override - * @param {function} [options.capture] Function used for capturing a PostHog event to record the opt-in action (default is this PostHog instance's capture method) - * @param {string} [options.capture_event_name=$opt_in] Event name to be used for capturing the opt-in action - * @param {Object} [options.capture_properties] Set of properties to be captured along with the opt-in action - * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) - */ - static opt_in_capturing(options?: posthog.OptInOutCapturingOptions): void - - /** - * Check whether the user has opted out of data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * const has_opted_out = posthog.has_opted_out_capturing(); - * // use has_opted_out value - * - * @param {Object} [options] A dictionary of config options to override - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @returns {boolean} current opt-out status - */ - static has_opted_out_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean - - /** - * Check whether the user has opted in to data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * const has_opted_in = posthog.has_opted_in_capturing(); - * // use has_opted_in value - * - * @param {Object} [options] A dictionary of config options to override - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @returns {boolean} current opt-in status - */ - static has_opted_in_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean - - /** - * Clear the user's opt in/out status of data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * // clear user's opt-in/out status - * posthog.clear_opt_in_out_capturing(); - * - * // clear user's opt-in/out status with specific cookie configuration - should match - * // configuration used when opt_in_capturing/opt_out_capturing methods were called. - * posthog.clear_opt_in_out_capturing({ - * cookie_expiration: 30, - * secure_cookie: true - * }); - * - * @param {Object} [options] A dictionary of config options to override - * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) - */ - static clear_opt_in_out_capturing(options?: posthog.ClearOptInOutCapturingOptions): void - - /* - * See if feature flag is enabled for user. - * - * ### Usage: - * - * if(posthog.isFeatureEnabled('beta-feature')) { // do something } - * - * @param {Object|String} prop Key of the feature flag. - * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. - */ - static isFeatureEnabled(key: string, options?: posthog.isFeatureEnabledOptions): boolean - - /* - * See if feature flags are available. - * - * ### Usage: - * - * posthog.onFeatureFlags(function(featureFlags) { // do something }) - * - * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. - */ - static onFeatureFlags(callback: (flags: string[]) => void): false | undefined - - /* - * Reload all feature flags for the user. - * - * ### Usage: - * - * posthog.reloadFeatureFlags() - */ - static reloadFeatureFlags(): void - - static toString(): string - - /* Will log all capture requests to the Javascript console, including event properties for easy debugging */ - static debug(): void - - /* - * Starts session recording and updates disable_session_recording to false. - * Used for manual session recording management. By default, session recording is enabled and - * starts automatically. - * - * ### Usage: - * - * posthog.startSessionRecording() - */ - static startSessionRecording(): void - - /* - * Stops session recording and updates disable_session_recording to true. - * - * ### Usage: - * - * posthog.stopSessionRecording() - */ - static stopSessionRecording(): void - - /* - * Check if session recording is currently running. - * - * ### Usage: - * - * const isSessionRecordingOn = posthog.sessionRecordingStarted() - */ - static sessionRecordingStarted(): boolean -} - -declare namespace posthog { - /* eslint-disable @typescript-eslint/no-explicit-any */ - type Property = any; - type Properties = Record; - type CaptureResult = { event: string; properties: Properties } | undefined; - type CaptureCallback = (response: any, data: any) => void; - /* eslint-enable @typescript-eslint/no-explicit-any */ - - interface Config { - api_host?: string - api_method?: string - api_transport?: string - autocapture?: boolean - rageclick?: boolean - cdn?: string - cross_subdomain_cookie?: boolean - persistence?: 'localStorage' | 'cookie' | 'memory' - persistence_name?: string - cookie_name?: string - loaded?: (posthog_instance: typeof posthog) => void - store_google?: boolean - save_referrer?: boolean - test?: boolean - verbose?: boolean - img?: boolean - capture_pageview?: boolean - debug?: boolean - cookie_expiration?: number - upgrade?: boolean - disable_session_recording?: boolean - disable_persistence?: boolean - disable_cookie?: boolean - secure_cookie?: boolean - ip?: boolean - opt_out_capturing_by_default?: boolean - opt_out_persistence_by_default?: boolean - opt_out_capturing_persistence_type?: 'localStorage' | 'cookie' - opt_out_capturing_cookie_prefix?: string | null - respect_dnt?: boolean - property_blacklist?: string[] - xhr_headers?: { [header_name: string]: string } - inapp_protocol?: string - inapp_link_new_window?: boolean - request_batching?: boolean - sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties - properties_string_max_length?: number - mask_all_element_attributes?: boolean - mask_all_text?: boolean - advanced_disable_decide?: boolean - } - - interface OptInOutCapturingOptions { - clear_persistence: boolean - persistence_type: string - cookie_prefix: string - cookie_expiration: number - cross_subdomain_cookie: boolean - secure_cookie: boolean - } - - interface HasOptedInOutCapturingOptions { - persistence_type: string - cookie_prefix: string - } - - interface ClearOptInOutCapturingOptions { - enable_persistence: boolean - persistence_type: string - cookie_prefix: string - cookie_expiration: number - cross_subdomain_cookie: boolean - secure_cookie: boolean - } - - interface isFeatureEnabledOptions { - send_event: boolean - } - - export class persistence { - static properties(): posthog.Properties - - static load(): void - - static save(): void - - static remove(): void - - static clear(): void - - /** - * @param {Object} props - * @param {*=} default_value - * @param {number=} days - */ - static register_once(props: Properties, default_value?: Property, days?: number): boolean - - /** - * @param {Object} props - * @param {number=} days - */ - static register(props: posthog.Properties, days?: number): boolean - - static unregister(prop: string): void - - static update_campaign_params(): void - - static update_search_keyword(referrer: string): void - - static update_referrer_info(referrer: string): void - - static get_referrer_info(): posthog.Properties - - static safe_merge(props: posthog.Properties): posthog.Properties - - static update_config(config: posthog.Config): void - - static set_disabled(disabled: boolean): void - - static set_cross_subdomain(cross_subdomain: boolean): void - - static get_cross_subdomain(): boolean - - static set_secure(secure: boolean): void - - static set_event_timer(event_name: string, timestamp: Date): void - - static remove_event_timer(event_name: string): Date | undefined - } - - export class people { - /* - * Set properties on a user record. - * - * ### Usage: - * - * posthog.people.set('gender', 'm'); - * - * // or set multiple properties at once - * posthog.people.set({ - * 'Company': 'Acme', - * 'Plan': 'Premium', - * 'Upgrade date': new Date() - * }); - * // properties can be strings, integers, dates, or lists - * - * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. - * @param {*} [to] A value to set on the given property name - * @param {Function} [callback] If provided, the callback will be called after capturing the event. - */ - static set( - prop: posthog.Properties | string, - to?: posthog.Property, - callback?: posthog.CaptureCallback - ): posthog.Properties - - /* - * Set properties on a user record, only if they do not yet exist. - * This will not overwrite previous people property values, unlike - * people.set(). - * - * ### Usage: - * - * posthog.people.set_once('First Login Date', new Date()); - * - * // or set multiple properties at once - * posthog.people.set_once({ - * 'First Login Date': new Date(), - * 'Starting Plan': 'Premium' - * }); - * - * // properties can be strings, integers or dates - * - * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. - * @param {*} [to] A value to set on the given property name - * @param {Function} [callback] If provided, the callback will be called after capturing the event. - */ - static set_once( - prop: posthog.Properties | string, - to?: posthog.Property, - callback?: posthog.CaptureCallback - ): posthog.Properties - - static toString(): string - } - - export class featureFlags { - static getFlags(): string[] - - static reloadFeatureFlags(): void - - /* - * See if feature flag is enabled for user. - * - * ### Usage: - * - * if(posthog.isFeatureEnabled('beta-feature')) { // do something } - * - * @param {Object|String} prop Key of the feature flag. - * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. - */ - static isFeatureEnabled(key: string, options?: { send_event?: boolean }): boolean - - /* - * See if feature flags are available. - * - * ### Usage: - * - * posthog.onFeatureFlags(function(featureFlags) { // do something }) - * - * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. - */ - static onFeatureFlags(callback: (flags: string[]) => void): false | undefined - } - - export class feature_flags extends featureFlags {} -} - -export type PostHog = typeof posthog; - -export default posthog; diff --git a/tsconfig.json b/tsconfig.json index b982d40b07..2e0131609c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,11 +23,6 @@ "dom", "dom.iterable" ], - "paths": { - "posthog-js": [ - "./src/@types/posthog.d.ts" - ] - } }, "include": [ "./src/**/*.ts", diff --git a/yarn.lock b/yarn.lock index daed6f4377..6bb5654db8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1790,6 +1790,11 @@ object.fromentries "^2.0.0" prop-types "^15.7.0" +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -2713,7 +2718,7 @@ cross-fetch@^3.0.4: dependencies: node-fetch "2.6.1" -cross-spawn@^6.0.0: +cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -3664,6 +3669,13 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -3743,6 +3755,15 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -3919,7 +3940,7 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" -graceful-fs@^4.1.11, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -4595,7 +4616,7 @@ is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^2.2.0: +is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -5217,6 +5238,13 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5266,6 +5294,13 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -5937,6 +5972,14 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -5966,6 +6009,11 @@ opus-recorder@^8.0.3: resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.3.tgz#f7b44f8f68500c9b96a15042a69f915fd9c1716d" integrity sha512-8vXGiRwlJAavT9D3yYzukNVXQ8vEcKHcsQL/zXO24DQtJ0PLXvoPHNQPJrbMCdB4ypJgWDExvHF4JitQDL7dng== +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -6074,6 +6122,25 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +patch-package@^6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^2.4.2" + cross-spawn "^6.0.5" + find-yarn-workspace-root "^2.0.0" + fs-extra "^7.0.1" + is-ci "^2.0.0" + klaw-sync "^6.0.0" + minimist "^1.2.0" + open "^7.4.2" + rimraf "^2.6.3" + semver "^5.6.0" + slash "^2.0.0" + tmp "^0.0.33" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -6254,13 +6321,18 @@ postcss@^8.0.2: nanoid "^3.1.23" source-map-js "^0.6.2" -posthog-js@1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.1.tgz#97834ee2574f34ffb5db2f5b07452c847e3c4d27" - integrity sha512-Y3lzcWkS8xFY6Ryj3I4ees7qWP2WGkLw0Arcbk5xaT0+5YlA6UC2jlL/+fN9bz/Bl62EoN3BML901Cuot/QNjg== +posthog-js@^1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.2.tgz#ff76e26634067e003f8af7df654d7ea0e647d946" + integrity sha512-I0d6c+Yu2f91PFidz65AIkkqZM219EY9Z1wlbTkW5Zqfq5oXqogBMKS8BaDBOrMc46LjLX7IH67ytCcBFRo1uw== dependencies: fflate "^0.4.1" +postinstall-postinstall@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" + integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -6834,6 +6906,13 @@ rfc4648@^1.4.0: resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.0.tgz#1ba940ec1649685ec4d88788dc57fb8e18855055" integrity sha512-FA6W9lDNeX8WbMY31io1xWg+TpZCbeDKsBo0ocwACZiWnh9TUAyk9CCuBQuOPmYnwwdEQZmraQ2ZK7yJsxErBg== +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -7520,6 +7599,13 @@ tmatch@^2.0.1: resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf" integrity sha1-DFYkbzPzDaG409colauvFmYPOM8= +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -7765,7 +7851,7 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" -universalify@^0.1.2: +universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== From 81b45f8aa929a5d132441d575999256de7578a72 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 3 Aug 2021 13:44:14 +0200 Subject: [PATCH 047/100] Make defaultProps a static property of LogoutDialog --- src/components/views/dialogs/LogoutDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.js index 469cd48093..e7e361a5b6 100644 --- a/src/components/views/dialogs/LogoutDialog.js +++ b/src/components/views/dialogs/LogoutDialog.js @@ -26,7 +26,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; @replaceableComponent("views.dialogs.LogoutDialog") export default class LogoutDialog extends React.Component { - defaultProps = { + static defaultProps = { onFinished: function() {}, }; From 54c685bfa8bba5f436332134dcf5e4454b94b6f6 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 3 Aug 2021 13:52:46 +0200 Subject: [PATCH 048/100] Migrate LogoutDialog to TypeScript --- .../{LogoutDialog.js => LogoutDialog.tsx} | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) rename src/components/views/dialogs/{LogoutDialog.js => LogoutDialog.tsx} (83%) diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.tsx similarity index 83% rename from src/components/views/dialogs/LogoutDialog.js rename to src/components/views/dialogs/LogoutDialog.tsx index e7e361a5b6..8c035dcbba 100644 --- a/src/components/views/dialogs/LogoutDialog.js +++ b/src/components/views/dialogs/LogoutDialog.tsx @@ -16,6 +16,7 @@ limitations under the License. */ import React from 'react'; +import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; import Modal from '../../../Modal'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; @@ -24,19 +25,25 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg'; import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + onFinished: (success: boolean) => void; +} + +interface IState { + shouldLoadBackupStatus: boolean; + loading: boolean; + backupInfo: IKeyBackupInfo; + error?: string; +} + @replaceableComponent("views.dialogs.LogoutDialog") -export default class LogoutDialog extends React.Component { +export default class LogoutDialog extends React.Component { static defaultProps = { onFinished: function() {}, }; - constructor() { - super(); - this._onSettingsLinkClick = this._onSettingsLinkClick.bind(this); - this._onExportE2eKeysClicked = this._onExportE2eKeysClicked.bind(this); - this._onFinished = this._onFinished.bind(this); - this._onSetRecoveryMethodClick = this._onSetRecoveryMethodClick.bind(this); - this._onLogoutConfirm = this._onLogoutConfirm.bind(this); + constructor(props) { + super(props); const cli = MatrixClientPeg.get(); const shouldLoadBackupStatus = cli.isCryptoEnabled() && !cli.getKeyBackupEnabled(); @@ -49,11 +56,11 @@ export default class LogoutDialog extends React.Component { }; if (shouldLoadBackupStatus) { - this._loadBackupStatus(); + this.loadBackupStatus(); } } - async _loadBackupStatus() { + private async loadBackupStatus() { try { const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); this.setState({ @@ -69,29 +76,29 @@ export default class LogoutDialog extends React.Component { } } - _onSettingsLinkClick() { + private onSettingsLinkClick = (): void => { // close dialog - this.props.onFinished(); - } + this.props.onFinished(true); + }; - _onExportE2eKeysClicked() { + private onExportE2eKeysClicked = (): void => { Modal.createTrackedDialogAsync('Export E2E Keys', '', import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, ); - } + }; - _onFinished(confirmed) { + private onFinished = (confirmed: boolean): void => { if (confirmed) { dis.dispatch({ action: 'logout' }); } // close dialog - this.props.onFinished(); - } + this.props.onFinished(confirmed); + }; - _onSetRecoveryMethodClick() { + private onSetRecoveryMethodClick = (): void => { if (this.state.backupInfo) { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and @@ -108,15 +115,15 @@ export default class LogoutDialog extends React.Component { } // close dialog - this.props.onFinished(); - } + this.props.onFinished(true); + }; - _onLogoutConfirm() { + private onLogoutConfirm = (): void => { dis.dispatch({ action: 'logout' }); // close dialog - this.props.onFinished(); - } + this.props.onFinished(true); + }; render() { if (this.state.shouldLoadBackupStatus) { @@ -152,16 +159,16 @@ export default class LogoutDialog extends React.Component {
-
{ _t("Advanced") } -

@@ -174,7 +181,7 @@ export default class LogoutDialog extends React.Component { title={_t("You'll lose access to your encrypted messages")} contentId='mx_Dialog_content' hasCancel={true} - onFinished={this._onFinished} + onFinished={this.onFinished} > { dialogContent } ); @@ -187,7 +194,7 @@ export default class LogoutDialog extends React.Component { "Are you sure you want to sign out?", )} button={_t("Sign out")} - onFinished={this._onFinished} + onFinished={this.onFinished} />); } } From 37f51c7536d878ad93767155037d6f9264bac50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 14:48:39 +0200 Subject: [PATCH 049/100] Don't ring if it's disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e7c1dda54f..d4a29a11b9 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -1,7 +1,8 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017, 2018 New Vector Ltd -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 Šimon Brandner Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -86,6 +87,8 @@ import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/ import EventEmitter from 'events'; import SdkConfig from './SdkConfig'; import { ensureDMExists, findDMForUser } from './createRoom'; +import { IPushRule, RuleId, TweakName, Tweaks } from "matrix-js-sdk/src/@types/PushRules"; +import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor'; export const PROTOCOL_PSTN = 'm.protocol.pstn'; export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; @@ -475,9 +478,22 @@ export default class CallHandler extends EventEmitter { this.silencedCalls.delete(call.callId); } + const incomingCallPushRule = ( + new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule + ); switch (newState) { case CallState.Ringing: - this.play(AudioID.Ring); + if ( + incomingCallPushRule?.enabled && + incomingCallPushRule.actions.some((a: Tweaks) => ( + a.set_tweak === TweakName.Sound && + a.value === "ring" + )) + ) { + this.play(AudioID.Ring); + } else { + this.silenceCall(call.callId); + } break; case CallState.InviteSent: this.play(AudioID.Ringback); From e787d14cde2783e3e83613a40355a0afba477191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 14:58:50 +0200 Subject: [PATCH 050/100] Set silenced state as soon as we get the call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/IncomingCallBox.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx index 95e97f1080..66fc2847f7 100644 --- a/src/components/views/voip/IncomingCallBox.tsx +++ b/src/components/views/voip/IncomingCallBox.tsx @@ -63,16 +63,12 @@ export default class IncomingCallBox extends React.Component { private onAction = (payload: ActionPayload) => { switch (payload.action) { case 'call_state': { - const call = CallHandler.sharedInstance().getCallForRoom(payload.room_id); - if (call && call.state === CallState.Ringing) { - this.setState({ - incomingCall: call, - silenced: false, // Reset silenced state for new call - }); + const incomingCall = CallHandler.sharedInstance().getCallForRoom(payload.room_id); + const silenced = CallHandler.sharedInstance().isCallSilenced(incomingCall.callId); + if (incomingCall && incomingCall.state === CallState.Ringing) { + this.setState({ incomingCall, silenced }); } else { - this.setState({ - incomingCall: null, - }); + this.setState({ incomingCall: null }); } } } From 75948587e43cf539dbef39c1f09a37ff7ed1d155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 15:13:40 +0200 Subject: [PATCH 051/100] Avoid calling with undefined MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/IncomingCallBox.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx index 66fc2847f7..4479c02e30 100644 --- a/src/components/views/voip/IncomingCallBox.tsx +++ b/src/components/views/voip/IncomingCallBox.tsx @@ -64,9 +64,11 @@ export default class IncomingCallBox extends React.Component { switch (payload.action) { case 'call_state': { const incomingCall = CallHandler.sharedInstance().getCallForRoom(payload.room_id); - const silenced = CallHandler.sharedInstance().isCallSilenced(incomingCall.callId); if (incomingCall && incomingCall.state === CallState.Ringing) { - this.setState({ incomingCall, silenced }); + this.setState({ + incomingCall, + silenced: CallHandler.sharedInstance().isCallSilenced(incomingCall.callId), + }); } else { this.setState({ incomingCall: null }); } From 2a378f30b7f3b71deddcba306078c78332c647a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 15:38:12 +0200 Subject: [PATCH 052/100] Attempt to fix tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- test/test-utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test-utils.js b/test/test-utils.js index 5e29fd242e..6d0d930d93 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -48,6 +48,7 @@ export function createTestClient() { getDomain: jest.fn().mockReturnValue("matrix.rog"), getUserId: jest.fn().mockReturnValue("@userId:matrix.rog"), + getPushRuleById: jest.fn().mockReturnValue(null), getPushActionsForEvent: jest.fn(), getRoom: jest.fn().mockImplementation(mkStubRoom), getRooms: jest.fn().mockReturnValue([]), From a18f41ceed92de96a2df3d21ac23799660142a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 15:43:56 +0200 Subject: [PATCH 053/100] Fix tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- test/test-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-utils.js b/test/test-utils.js index 6d0d930d93..fb2d866962 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -48,7 +48,6 @@ export function createTestClient() { getDomain: jest.fn().mockReturnValue("matrix.rog"), getUserId: jest.fn().mockReturnValue("@userId:matrix.rog"), - getPushRuleById: jest.fn().mockReturnValue(null), getPushActionsForEvent: jest.fn(), getRoom: jest.fn().mockImplementation(mkStubRoom), getRooms: jest.fn().mockReturnValue([]), @@ -96,6 +95,7 @@ export function createTestClient() { getItem: jest.fn(), }, }, + pushRules: {}, decryptEventIfNeeded: () => Promise.resolve(), isUserIgnored: jest.fn().mockReturnValue(false), getCapabilities: jest.fn().mockResolvedValue({}), From 28f5dc483b7f016ca1966fcb984d0e66fd322d93 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Aug 2021 17:07:37 +0100 Subject: [PATCH 054/100] Update eslint plugin & fix silly indenting As per https://github.com/matrix-org/eslint-plugin-matrix-org/pull/15 this caused a bunch of silly indenting to creep in, so this fixes it back to the previous style. --- package.json | 2 +- src/IdentityAuthClient.js | 30 +++++++++---------- src/accessibility/KeyboardShortcuts.tsx | 2 +- .../views/dialogs/DevtoolsDialog.tsx | 6 ++-- .../dialogs/KeySignatureUploadFailedDialog.js | 8 ++--- .../views/right_panel/PinnedMessagesCard.tsx | 2 +- src/components/views/right_panel/UserInfo.tsx | 4 +-- src/components/views/rooms/NewRoomIntro.tsx | 6 ++-- .../views/rooms/ReadReceiptMarker.js | 8 ++--- .../elements/MemberEventListSummary-test.js | 6 ++-- test/notifications/ContentRules-test.js | 2 +- test/test-utils.js | 4 +-- test/utils/ShieldUtils-test.js | 12 ++++---- yarn.lock | 6 ++-- 14 files changed, 49 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 9744aa7685..704315ca69 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "enzyme": "^3.11.0", "eslint": "7.18.0", "eslint-config-google": "^0.14.0", - "eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#main", + "eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#2306b3d4da4eba908b256014b979f1d3d43d2945", "eslint-plugin-react": "^7.22.0", "eslint-plugin-react-hooks": "^4.2.0", "glob": "^7.1.6", diff --git a/src/IdentityAuthClient.js b/src/IdentityAuthClient.js index e91e1d72cf..ffece510de 100644 --- a/src/IdentityAuthClient.js +++ b/src/IdentityAuthClient.js @@ -146,23 +146,23 @@ export default class IdentityAuthClient { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const { finished } = Modal.createTrackedDialog('Default identity server terms warning', '', QuestionDialog, { - title: _t("Identity server has no terms of service"), - description: ( -
-

{ _t( - "This action requires accessing the default identity server " + + title: _t("Identity server has no terms of service"), + description: ( +

+

{ _t( + "This action requires accessing the default identity server " + " to validate an email address or phone number, " + "but the server does not have any terms of service.", {}, - { - server: () => { abbreviateUrl(identityServerUrl) }, - }, - ) }

-

{ _t( - "Only continue if you trust the owner of the server.", - ) }

-
- ), - button: _t("Trust"), + { + server: () => { abbreviateUrl(identityServerUrl) }, + }, + ) }

+

{ _t( + "Only continue if you trust the owner of the server.", + ) }

+
+ ), + button: _t("Trust"), }); const [confirmed] = await finished; if (confirmed) { diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx index 9cc7b60c99..c66984191f 100644 --- a/src/accessibility/KeyboardShortcuts.tsx +++ b/src/accessibility/KeyboardShortcuts.tsx @@ -163,7 +163,7 @@ const shortcuts: Record = { modifiers: [Modifiers.SHIFT], key: Key.PAGE_UP, }], - description: _td("Jump to oldest unread message"), + description: _td("Jump to oldest unread message"), }, { keybinds: [{ modifiers: [CMD_OR_CTRL, Modifiers.SHIFT], diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 8ae9d0654f..30e6c70f0e 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -490,9 +490,9 @@ class RoomStateExplorer extends React.PureComponent; } diff --git a/src/components/views/dialogs/KeySignatureUploadFailedDialog.js b/src/components/views/dialogs/KeySignatureUploadFailedDialog.js index 9fa9fc1373..6b36c19977 100644 --- a/src/components/views/dialogs/KeySignatureUploadFailedDialog.js +++ b/src/components/views/dialogs/KeySignatureUploadFailedDialog.js @@ -20,10 +20,10 @@ import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; export default function KeySignatureUploadFailedDialog({ - failures, - source, - continuation, - onFinished, + failures, + source, + continuation, + onFinished, }) { const RETRIES = 2; const BaseDialog = sdk.getComponent('dialogs.BaseDialog'); diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 19ffe81ac1..c82e5a3f80 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -152,7 +152,7 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => {

{ _t("Nothing pinned, yet") }

{ _t("If you have permissions, open the menu on any message and select " + "Pin to stick them here.", {}, { - b: sub => { sub }, + b: sub => { sub }, }) }
; diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index c837e814c8..ba6bb26cbf 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -1381,8 +1381,8 @@ const BasicUserInfo: React.FC<{ className="mx_UserInfo_field" onClick={() => { dis.dispatch({ - action: Action.ViewUserSettings, - initialTabId: UserTab.Security, + action: Action.ViewUserSettings, + initialTabId: UserTab.Security, }); }} > diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index cdca9ab75d..674bcdaec2 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -64,9 +64,9 @@ const NewRoomIntro = () => { height={AVATAR_SIZE} onClick={() => { defaultDispatcher.dispatch({ - action: Action.ViewUser, - // XXX: We should be using a real member object and not assuming what the receiver wants. - member: member || { userId: dmPartner } as User, + action: Action.ViewUser, + // XXX: We should be using a real member object and not assuming what the receiver wants. + member: member || { userId: dmPartner } as User, }); }} /> diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index 4ea16f69a3..c9688b4d29 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -145,7 +145,7 @@ export default class ReadReceiptMarker extends React.PureComponent { if (oldInfo && oldInfo.left) { // start at the old height and in the old h pos startStyles.push({ top: startTopOffset+"px", - left: toPx(oldInfo.left) }); + left: toPx(oldInfo.left) }); } startStyles.push({ top: startTopOffset+'px', left: '0' }); @@ -174,14 +174,14 @@ export default class ReadReceiptMarker extends React.PureComponent { title = _t( "Seen by %(userName)s at %(dateTime)s", { userName: this.props.fallbackUserId, - dateTime: dateString }, + dateTime: dateString }, ); } else { title = _t( "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", { displayName: this.props.member.rawDisplayName, - userName: this.props.fallbackUserId, - dateTime: dateString }, + userName: this.props.fallbackUserId, + dateTime: dateString }, ); } } diff --git a/test/components/views/elements/MemberEventListSummary-test.js b/test/components/views/elements/MemberEventListSummary-test.js index e2d51f13a4..dcb895f09e 100644 --- a/test/components/views/elements/MemberEventListSummary-test.js +++ b/test/components/views/elements/MemberEventListSummary-test.js @@ -106,7 +106,7 @@ describe('MemberEventListSummary', function() { const result = wrapper.props.children; expect(result.props.children).toEqual([ -
Expanded membership
, +
Expanded membership
, ]); }); @@ -129,8 +129,8 @@ describe('MemberEventListSummary', function() { const result = wrapper.props.children; expect(result.props.children).toEqual([ -
Expanded membership
, -
Expanded membership
, +
Expanded membership
, +
Expanded membership
, ]); }); diff --git a/test/notifications/ContentRules-test.js b/test/notifications/ContentRules-test.js index 9c21c05da7..2b18a18488 100644 --- a/test/notifications/ContentRules-test.js +++ b/test/notifications/ContentRules-test.js @@ -56,7 +56,7 @@ describe("ContentRules", function() { describe("parseContentRules", function() { it("should handle there being no keyword rules", function() { const rules = { 'global': { 'content': [ - USERNAME_RULE, + USERNAME_RULE, ] } }; const parsed = ContentRules.parseContentRules(rules); expect(parsed.rules).toEqual([]); diff --git a/test/test-utils.js b/test/test-utils.js index 5e29fd242e..217c399443 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -129,8 +129,8 @@ export function mkEvent(opts) { if (opts.skey) { event.state_key = opts.skey; } else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules", - "m.room.power_levels", "m.room.topic", "m.room.history_visibility", "m.room.encryption", - "com.example.state"].indexOf(opts.type) !== -1) { + "m.room.power_levels", "m.room.topic", "m.room.history_visibility", "m.room.encryption", + "com.example.state"].indexOf(opts.type) !== -1) { event.state_key = ""; } return opts.event ? new MatrixEvent(event) : event; diff --git a/test/utils/ShieldUtils-test.js b/test/utils/ShieldUtils-test.js index b70031dc21..85f9de3150 100644 --- a/test/utils/ShieldUtils-test.js +++ b/test/utils/ShieldUtils-test.js @@ -49,7 +49,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [[true, true], [true, false], - [false, true], [false, false]], + [false, true], [false, false]], )("2 unverified: returns 'normal', self-trust = %s, DM = %s", async (trusted, dm) => { const client = mkClient(trusted); const room = { @@ -62,7 +62,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["verified", true, true], ["verified", true, false], - ["verified", false, true], ["warning", false, false]], + ["verified", false, true], ["warning", false, false]], )("2 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { @@ -75,7 +75,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["normal", true, true], ["normal", true, false], - ["normal", false, true], ["warning", false, false]], + ["normal", false, true], ["warning", false, false]], )("2 mixed: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { @@ -88,7 +88,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["verified", true, true], ["verified", true, false], - ["warning", false, true], ["warning", false, false]], + ["warning", false, true], ["warning", false, false]], )("0 others: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { @@ -101,7 +101,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["verified", true, true], ["verified", true, false], - ["verified", false, true], ["verified", false, false]], + ["verified", false, true], ["verified", false, false]], )("1 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { @@ -114,7 +114,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["normal", true, true], ["normal", true, false], - ["normal", false, true], ["normal", false, false]], + ["normal", false, true], ["normal", false, false]], )("1 unverified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { diff --git a/yarn.lock b/yarn.lock index 2a03f640ee..f121112a1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3233,9 +3233,9 @@ eslint-config-google@^0.14.0: resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a" integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw== -"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main": - version "0.3.4" - resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/45f6937539192e3820edcafc4d6d4d4187e85a6a" +"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#2306b3d4da4eba908b256014b979f1d3d43d2945": + version "0.3.5" + resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/2306b3d4da4eba908b256014b979f1d3d43d2945" eslint-plugin-react-hooks@^4.2.0: version "4.2.0" From 612384a6a36bf468cc4575d8fccb9d1cbf941ff0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Aug 2021 18:03:46 +0100 Subject: [PATCH 055/100] Switch to new changelog generator allchange is typescript so has a 'prepare' script to tsc it into javascript so it can be a binscript - hopefully this won't cause it to make too much of a pain of itself causing tsc to run on every yarn add/install --- package.json | 1 + yarn.lock | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 309 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 704315ca69..fcb1e9133c 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ "@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/parser": "^4.17.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", + "allchange": "github:matrix-org/allchange", "babel-jest": "^26.6.3", "chokidar": "^3.5.1", "concurrently": "^5.3.0", diff --git a/yarn.lock b/yarn.lock index f121112a1b..2b504d2366 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1324,6 +1324,107 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@octokit/auth-token@^2.4.4": + version "2.4.5" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" + integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/core@^3.5.0": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" + integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== + dependencies: + "@octokit/auth-token" "^2.4.4" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^5.6.0" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^6.0.1": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== + dependencies: + "@octokit/types" "^6.0.3" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.5.8": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.4.tgz#0c3f5bed440822182e972317122acb65d311a5ed" + integrity sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg== + dependencies: + "@octokit/request" "^5.6.0" + "@octokit/types" "^6.0.3" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.3.0.tgz#160347858d727527901c6aae7f7d5c2414cc1f2e" + integrity sha512-oz60hhL+mDsiOWhEwrj5aWXTOMVtQgcvP+sRzX4C3cH7WOK9QSAoEtjWh0HdOf6V3qpdgAmUMxnQPluzDWR7Fw== + +"@octokit/plugin-paginate-rest@^2.6.2": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.0.tgz#9c956c3710b2bd786eb3814eaf5a2b17392c150d" + integrity sha512-/vjcb0w6ggVRtsb8OJBcRR9oEm+fpdo8RJk45khaWw/W0c8rlB2TLCLyZt/knmC17NkX7T9XdyQeEY7OHLSV1g== + dependencies: + "@octokit/types" "^6.23.0" + +"@octokit/plugin-request-log@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.6.0.tgz#c28833b88d0f07bf94093405d02d43d73c7de99b" + integrity sha512-2G7lIPwjG9XnTlNhe/TRnpI8yS9K2l68W4RP/ki3wqw2+sVeTK8hItPxkqEI30VeH0UwnzpuksMU/yHxiVVctw== + dependencies: + "@octokit/types" "^6.23.0" + deprecation "^2.3.1" + +"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== + dependencies: + "@octokit/types" "^6.0.3" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.0.tgz#6084861b6e4fa21dc40c8e2a739ec5eff597e672" + integrity sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.16.1" + is-plain-object "^5.0.0" + node-fetch "^2.6.1" + universal-user-agent "^6.0.0" + +"@octokit/rest@^18.6.7": + version "18.8.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.8.0.tgz#ba24f7ba554f015a7ae2b7cc2aecef5386ddfea5" + integrity sha512-lsuNRhgzGnLMn/NmQTNCit/6jplFWiTUlPXhqN0zCMLwf2/9pseHzsnTW+Cjlp4bLMEJJNPa5JOzSLbSCOahKw== + dependencies: + "@octokit/core" "^3.5.0" + "@octokit/plugin-paginate-rest" "^2.6.2" + "@octokit/plugin-request-log" "^1.0.2" + "@octokit/plugin-rest-endpoint-methods" "5.6.0" + +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.23.0": + version "6.23.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.23.0.tgz#b39f242b20036e89fa8f34f7962b4e9b7ff8f65b" + integrity sha512-eG3clC31GSS7K3oBK6C6o7wyXPrkP+mu++eus8CSZdpRytJ5PNszYxudOQ0spWZQ3S9KAtoTG6v1WK5prJcJrA== + dependencies: + "@octokit/openapi-types" "^9.3.0" + "@peculiar/asn1-schema@^2.0.27", "@peculiar/asn1-schema@^2.0.32": version "2.0.37" resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.37.tgz#700476512ab903d809f64a3040fb1b2fe6fb6d4b" @@ -1850,6 +1951,17 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" +"allchange@github:matrix-org/allchange": + version "0.0.1" + resolved "https://codeload.github.com/matrix-org/allchange/tar.gz/56b37b06339a3ac3fe771f3ec3d0bff798df8dab" + dependencies: + "@octokit/rest" "^18.6.7" + cli-color "^2.0.0" + js-yaml "^4.1.0" + loglevel "^1.7.1" + semver "^7.3.5" + yargs "^17.0.1" + another-json@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc" @@ -1867,6 +1979,11 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" +ansi-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -1914,6 +2031,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -2206,6 +2328,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +before-after-hook@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -2506,6 +2633,18 @@ classnames@*, classnames@^2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +cli-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" + integrity sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A== + dependencies: + ansi-regex "^2.1.1" + d "^1.0.1" + es5-ext "^0.10.51" + es6-iterator "^2.0.3" + memoizee "^0.4.14" + timers-ext "^0.1.7" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -2524,6 +2663,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2788,6 +2936,14 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2895,6 +3051,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -3191,6 +3352,42 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + +es6-iterator@^2.0.3, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3383,6 +3580,14 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= + dependencies: + d "1" + es5-ext "~0.10.14" + events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3478,6 +3683,13 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -3783,7 +3995,7 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -4502,6 +4714,11 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +is-promise@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + is-regex@^1.0.3, is-regex@^1.0.5, is-regex@^1.1.2, is-regex@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" @@ -5122,6 +5339,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -5396,6 +5620,13 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= + dependencies: + es5-ext "~0.10.2" + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -5528,6 +5759,20 @@ memoize-one@^5.1.1: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== +memoizee@^0.4.14: + version "0.4.15" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + meow@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" @@ -5701,12 +5946,22 @@ nearley@^2.7.10: railroad-diagrams "^1.0.0" randexp "0.4.6" +next-tick@1, next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-fetch@2.6.1: +node-fetch@2.6.1, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -7498,6 +7753,14 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +timers-ext@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + tiny-invariant@^1.0.6: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" @@ -7657,6 +7920,16 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -7753,6 +8026,11 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -8056,6 +8334,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -8091,6 +8378,11 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" @@ -8117,7 +8409,7 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.3: +yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== @@ -8155,6 +8447,19 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb" + integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" From 82c644564870090dc9293bdef86b5fb69d0f6dad Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 30 Jul 2021 21:35:11 +0100 Subject: [PATCH 056/100] Add release_config for changelogging --- release_config.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 release_config.yaml diff --git a/release_config.yaml b/release_config.yaml new file mode 100644 index 0000000000..12e857cbdd --- /dev/null +++ b/release_config.yaml @@ -0,0 +1,4 @@ +subprojects: + matrix-js-sdk: + includeByDefault: false + From 7b565db02dacc067ae0d3b9adc7fbf3a277f8ee3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 12:52:21 -0600 Subject: [PATCH 057/100] Update uploading state designs --- res/css/views/rooms/_VoiceRecordComposerTile.scss | 8 ++++++-- src/components/views/rooms/VoiceRecordComposerTile.tsx | 9 ++++----- src/i18n/strings/en_EN.json | 2 -- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 3d8d0e5ecc..7acec5a133 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -46,9 +46,13 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/trashcan.svg'); } -.mx_VoiceRecordComposerTile_uploadState { - margin-right: 21px; +.mx_VoiceRecordComposerTile_uploadingState { + margin-right: 10px; color: $secondary-fg-color; +} + +.mx_VoiceRecordComposerTile_failedState { + margin-right: 21px; .mx_VoiceRecordComposerTile_uploadState_badge { display: inline-block; diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 490d6b2231..1f481d7531 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -246,20 +246,19 @@ export default class VoiceRecordComposerTile extends React.PureComponent + if (this.state.recordingPhase === RecordingState.Uploading || true) { + uploadIndicator = - { _t("Uploading...") } ; } else if (this.state.didUploadFail && this.state.recordingPhase === RecordingState.Ended) { - uploadIndicator = + uploadIndicator = { /* Need to stick the badge in a span to ensure it doesn't create a block component */ } - { _t("Failed to upload voice message") } + { _t("Failed to send") } ; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c929bd683b..9a717e31a4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1708,8 +1708,6 @@ "Record a voice message": "Record a voice message", "Stop the recording": "Stop the recording", "Delete recording": "Delete recording", - "Uploading...": "Uploading...", - "Failed to upload voice message": "Failed to upload voice message", "Error updating main address": "Error updating main address", "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.", From c4e6cc797302fd12af7c33c05a7b91cf11226524 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 12:57:16 -0600 Subject: [PATCH 058/100] Remove debugging --- src/components/views/rooms/VoiceRecordComposerTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 1f481d7531..b980b9295b 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -246,7 +246,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent ; From 32442068cf53144f101523d56b3dcd7ab80a82ce Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 13:26:27 -0600 Subject: [PATCH 059/100] Use a default waveform when recording to ease component pop-in Fixes https://github.com/vector-im/element-web/issues/18225 --- src/components/views/audio_messages/LiveRecordingWaveform.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/audio_messages/LiveRecordingWaveform.tsx b/src/components/views/audio_messages/LiveRecordingWaveform.tsx index 9c33889884..9f981e8ca2 100644 --- a/src/components/views/audio_messages/LiveRecordingWaveform.tsx +++ b/src/components/views/audio_messages/LiveRecordingWaveform.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from "react"; import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { arrayFastResample } from "../../../utils/arrays"; +import { arrayFastResample, arraySeed } from "../../../utils/arrays"; import { percentageOf } from "../../../utils/numbers"; import Waveform from "./Waveform"; import { MarkedExecution } from "../../../utils/MarkedExecution"; @@ -48,7 +48,7 @@ export default class LiveRecordingWaveform extends React.PureComponent Date: Tue, 3 Aug 2021 13:33:42 -0600 Subject: [PATCH 060/100] Improve hover states (tooltips) for voice message interactions Fixes https://github.com/vector-im/element-web/issues/18375 --- src/components/views/rooms/VoiceRecordComposerTile.tsx | 6 +++--- src/i18n/strings/en_EN.json | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 8323320520..7949f41784 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -209,9 +209,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3ad8daa85c..cf06112b14 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1705,9 +1705,8 @@ "We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.", "No microphone found": "No microphone found", "We didn't find a microphone on your device. Please check your settings and try again.": "We didn't find a microphone on your device. Please check your settings and try again.", - "Record a voice message": "Record a voice message", - "Stop the recording": "Stop the recording", - "Delete recording": "Delete recording", + "Send voice message": "Send voice message", + "Stop recording": "Stop recording", "Error updating main address": "Error updating main address", "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.", From 24da0291a05a4ddfc4c008415b3ea6688836a73a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 13:33:58 -0600 Subject: [PATCH 061/100] Re-center recording LED Fixes https://github.com/vector-im/element-web/issues/18375 --- res/css/views/rooms/_VoiceRecordComposerTile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 5501ab343e..8c13018c51 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -68,7 +68,7 @@ limitations under the License. height: 10px; position: absolute; left: 12px; // 12px from the left edge for container padding - top: 18px; // vertically center (middle align with clock) + top: 16px; // vertically center (middle align with clock) border-radius: 10px; } } From 1b9fe46733f8ae587ee592786a80a8b5b42a3ac7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 13:51:11 -0600 Subject: [PATCH 062/100] Remove unnecessary rescaling of voice waveforms Fixes https://github.com/vector-im/element-web/issues/18364 --- src/audio/Playback.ts | 14 +++----------- .../views/audio_messages/LiveRecordingWaveform.tsx | 9 ++------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/audio/Playback.ts b/src/audio/Playback.ts index 33d346629a..9dad828a79 100644 --- a/src/audio/Playback.ts +++ b/src/audio/Playback.ts @@ -38,17 +38,9 @@ function makePlaybackWaveform(input: number[]): number[] { // First, convert negative amplitudes to positive so we don't detect zero as "noisy". const noiseWaveform = input.map(v => Math.abs(v)); - // Next, we'll resample the waveform using a smoothing approach so we can keep the same rough shape. - // We also rescale the waveform to be 0-1 for the remaining function logic. - const resampled = arrayRescale(arraySmoothingResample(noiseWaveform, PLAYBACK_WAVEFORM_SAMPLES), 0, 1); - - // Then, we'll do a high and low pass filter to isolate actual speaking volumes within the rescaled - // waveform. Most speech happens below the 0.5 mark. - const filtered = resampled.map(v => clamp(v, 0.1, 0.5)); - - // Finally, we'll rescale the filtered waveform (0.1-0.5 becomes 0-1 again) so the user sees something - // sensible. This is what we return to keep our contract of "values between zero and one". - return arrayRescale(filtered, 0, 1); + // Then, we'll resample the waveform using a smoothing approach so we can keep the same rough shape. + // We also rescale the waveform to be 0-1 so we end up with a clamped waveform to rely upon. + return arrayRescale(arraySmoothingResample(noiseWaveform, PLAYBACK_WAVEFORM_SAMPLES), 0, 1); } export class Playback extends EventEmitter implements IDestroyable { diff --git a/src/components/views/audio_messages/LiveRecordingWaveform.tsx b/src/components/views/audio_messages/LiveRecordingWaveform.tsx index 9f981e8ca2..73e18626fe 100644 --- a/src/components/views/audio_messages/LiveRecordingWaveform.tsx +++ b/src/components/views/audio_messages/LiveRecordingWaveform.tsx @@ -18,7 +18,6 @@ import React from "react"; import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { arrayFastResample, arraySeed } from "../../../utils/arrays"; -import { percentageOf } from "../../../utils/numbers"; import Waveform from "./Waveform"; import { MarkedExecution } from "../../../utils/MarkedExecution"; @@ -54,12 +53,8 @@ export default class LiveRecordingWaveform extends React.PureComponent { - const bars = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES); - // The incoming data is between zero and one, but typically even screaming into a - // microphone won't send you over 0.6, so we artificially adjust the gain for the - // waveform. This results in a slightly more cinematic/animated waveform for the - // user. - this.waveform = bars.map(b => percentageOf(b, 0, 0.50)); + // The incoming data is between zero and one, so we don't need to clamp/rescale it. + this.waveform = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES); this.scheduledUpdate.mark(); }); } From 372cbbfe8ef3a4dd0396c2de49f7e71be2311511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 08:11:05 +0200 Subject: [PATCH 063/100] Increase snapping speed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 6261b9965f..46ff8ca838 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -36,7 +36,7 @@ const PIP_VIEW_WIDTH = 336; const PIP_VIEW_HEIGHT = 232; const MOVING_AMT = 0.2; -const SNAPPING_AMT = 0.05; +const SNAPPING_AMT = 0.1; const PADDING = { top: 58, From 7fc632d0cfc25e45d31a164c0e22179b1effe782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 08:42:07 +0200 Subject: [PATCH 064/100] Put font names into "" so that stylelint shuts up about them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/legacy-light/css/_legacy-light.scss | 5 +++-- res/themes/light/css/_light.scss | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index b7d45452ff..b37b4300c9 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -8,9 +8,10 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: Nunito, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; +$font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', 'Sans-Serif', 'Noto Color Emoji'; + +$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; -$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; // unified palette // try to use these colors when possible diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 32722515d8..db3aa95442 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: Inter, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; +$font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', 'Sans-Serif', 'Noto Color Emoji'; -$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; +$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; // unified palette // try to use these colors when possible From 050e260655ba1399cff0f835eba0da35a2f5360d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 08:45:58 +0200 Subject: [PATCH 065/100] Fix weird spacing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/legacy-light/css/_legacy-light.scss | 2 +- res/themes/light/css/_light.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index b37b4300c9..c784f18a2b 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -391,7 +391,7 @@ $eventbubble-reply-color: #C1C6CD; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color ! important; + border: 1px solid $accent-color !important; color: $accent-color; background-color: $button-secondary-bg-color; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index db3aa95442..1412b900f9 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -392,7 +392,7 @@ $eventbubble-reply-color: #C1C6CD; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color ! important; + border: 1px solid $accent-color !important; color: $accent-color; background-color: $button-secondary-bg-color; } From ca94518de5e3263f20f7778995bdb481e3ce82cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 08:46:09 +0200 Subject: [PATCH 066/100] Improve bubble colors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/dark/css/_dark.scss | 17 ++++++++++------- res/themes/legacy-light/css/_legacy-light.scss | 12 +++++++----- res/themes/light/css/_light.scss | 15 +++++++++------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 7b83fe0cb2..6adc821487 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -1,3 +1,6 @@ +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 +$system-dark: #21262C; + // unified palette // try to use these colors when possible $bg-color: #15191E; @@ -47,7 +50,7 @@ $inverted-bg-color: $base-color; $selected-color: $room-highlight-color; // selected for hoverover & selected event tiles -$event-selected-color: #21262c; +$event-selected-color: $system-dark; // used for the hairline dividers in RoomView $primary-hairline-color: transparent; @@ -91,7 +94,7 @@ $lightbox-background-bg-color: #000; $lightbox-background-bg-opacity: 0.85; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: #21262c; +$settings-profile-placeholder-bg-color: $system-dark; $settings-profile-overlay-placeholder-fg-color: #454545; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -175,7 +178,7 @@ $button-link-bg-color: transparent; $togglesw-off-color: $room-highlight-color; $progressbar-fg-color: $accent-color; -$progressbar-bg-color: #21262c; +$progressbar-bg-color: $system-dark; $visual-bell-bg-color: #800; @@ -210,7 +213,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; $message-body-panel-bg-color: #394049; // "Dark Tile" $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: #21262C; // "System Dark" +$message-body-panel-icon-bg-color: $system-dark; // "System Dark" $voice-record-stop-border-color: $quaternary-fg-color; $voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; @@ -228,9 +231,9 @@ $groupFilterPanel-background-blur-amount: 30px; $composer-shadow-color: rgba(0, 0, 0, 0.28); // Bubble tiles -$eventbubble-self-bg: #143A34; -$eventbubble-others-bg: #394049; -$eventbubble-bg-hover: $header-panel-bg-color; +$eventbubble-self-bg: #14322E; +$eventbubble-others-bg: $event-selected-color; +$eventbubble-bg-hover: #1C2026; $eventbubble-avatar-outline: $bg-color; $eventbubble-reply-color: #C1C6CD; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index c784f18a2b..e9bfe46c89 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -12,6 +12,8 @@ $font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial $monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 +$system-light: #F4F6FA; // unified palette // try to use these colors when possible @@ -180,7 +182,7 @@ $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #F4F6FA; +$voipcall-plinth-color: $system-light; // ******************** @@ -332,7 +334,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; $message-body-panel-bg-color: #E3E8F0; $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: #F4F6FA; +$message-body-panel-icon-bg-color: $system-light; // See non-legacy _light for variable information $voice-record-stop-symbol-color: #ff4b55; @@ -349,9 +351,9 @@ $appearance-tab-border-color: $input-darker-bg-color; $composer-shadow-color: tranparent; // Bubble tiles -$eventbubble-self-bg: #F8FDFC; -$eventbubble-others-bg: #F7F8F9; -$eventbubble-bg-hover: rgb(242, 242, 242); +$eventbubble-self-bg: #F0FBF8; +$eventbubble-others-bg: $system-light; +$eventbubble-bg-hover: #FAFBFD; $eventbubble-avatar-outline: #fff; $eventbubble-reply-color: #C1C6CD; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 1412b900f9..90ae808adc 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -12,6 +12,9 @@ $font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial' $monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 +$system-light: #F4F6FA; + // unified palette // try to use these colors when possible $accent-color: #0DBD8B; @@ -138,7 +141,7 @@ $blockquote-bar-color: #ddd; $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: #f4f6fa; +$settings-profile-placeholder-bg-color: $system-light; $settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -168,7 +171,7 @@ $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #F4F6FA; +$voipcall-plinth-color: $system-light; // ******************** @@ -327,7 +330,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; $message-body-panel-bg-color: #E3E8F0; // "Separator" $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: #F4F6FA; +$message-body-panel-icon-bg-color: $system-light; // These two don't change between themes. They are the $warning-color, but we don't // want custom themes to affect them by accident. @@ -350,9 +353,9 @@ $groupFilterPanel-background-blur-amount: 20px; $composer-shadow-color: rgba(0, 0, 0, 0.04); // Bubble tiles -$eventbubble-self-bg: #F8FDFC; -$eventbubble-others-bg: #F7F8F9; -$eventbubble-bg-hover: #FEFCF5; +$eventbubble-self-bg: #F0FBF8; +$eventbubble-others-bg: $system-light; +$eventbubble-bg-hover: #FAFBFD; $eventbubble-avatar-outline: $primary-bg-color; $eventbubble-reply-color: #C1C6CD; From 4b78edd65241256351e68b799b05c607de5bd574 Mon Sep 17 00:00:00 2001 From: James Salter Date: Wed, 4 Aug 2021 09:30:04 +0100 Subject: [PATCH 067/100] Remove patch-package and postinstall-postinstall --- package.json | 5 +- patches/posthog-js+1.12.2.patch | 78 --------------------------- yarn.lock | 96 ++------------------------------- 3 files changed, 6 insertions(+), 173 deletions(-) delete mode 100644 patches/posthog-js+1.12.2.patch diff --git a/package.json b/package.json index e7ea85ccd4..8f5459ab99 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,7 @@ "lint:style": "stylelint 'res/css/**/*.scss'", "test": "jest", "test:e2e": "./test/end-to-end-tests/run.sh --app-url http://localhost:8080", - "coverage": "yarn test --coverage", - "postinstall": "patch-package" + "coverage": "yarn test --coverage" }, "dependencies": { "@babel/runtime": "^7.12.5", @@ -166,8 +165,6 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.3", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "patch-package": "^6.4.7", - "postinstall-postinstall": "^2.1.0", "react-test-renderer": "^17.0.2", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/patches/posthog-js+1.12.2.patch b/patches/posthog-js+1.12.2.patch deleted file mode 100644 index 430a9f0a62..0000000000 --- a/patches/posthog-js+1.12.2.patch +++ /dev/null @@ -1,78 +0,0 @@ -diff --git a/node_modules/posthog-js/dist/module.d.ts b/node_modules/posthog-js/dist/module.d.ts -index d3cba02..8c87ef8 100644 ---- a/node_modules/posthog-js/dist/module.d.ts -+++ b/node_modules/posthog-js/dist/module.d.ts -@@ -1,7 +1,5 @@ - // Type definitions for exported methods -- --import { EventProcessor, Integration } from '@sentry/types' --import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot' -+// Patched to remove references to sentry and rrweb-snapshot which aren't otherwise SDK dependencies - - declare class posthog { - /** -@@ -485,25 +483,6 @@ declare class posthog { - */ - static reloadFeatureFlags(): void - -- /** -- * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog -- * -- * ### Usage -- * -- * Sentry.init({ -- * dsn: 'https://example', -- * integrations: [ -- * new posthog.SentryIntegration(posthog) -- * ] -- * }) -- * -- * @param {Object} [posthog] The posthog object -- * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry -- * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry -- * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/) -- */ -- static SentryIntegration: typeof SentryIntegration -- - static toString(): string - - /* Will log all capture requests to the Javascript console, including event properties for easy debugging */ -@@ -585,7 +564,6 @@ declare namespace posthog { - request_batching?: boolean - sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties - properties_string_max_length?: number -- session_recording?: SessionRecordingOptions - mask_all_element_attributes?: boolean - mask_all_text?: boolean - advanced_disable_decide?: boolean -@@ -618,17 +596,6 @@ declare namespace posthog { - send_event: boolean - } - -- interface SessionRecordingOptions { -- blockClass?: string | RegExp -- blockSelector?: string -- ignoreClass?: string -- maskAllInputs?: boolean -- maskInputOptions?: MaskInputOptions -- maskInputFn?: (text: string) => string -- slimDOMOptions?: SlimDOMOptions | 'all' | true -- collectFonts?: boolean -- } -- - export class persistence { - static properties(): posthog.Properties - -@@ -768,12 +735,6 @@ declare namespace posthog { - export class feature_flags extends featureFlags {} - } - --export class SentryIntegration implements Integration { -- constructor(posthog: posthog, organization?: string, projectId?: number, prefix?: string) -- name: string -- setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void): void --} -- - export type PostHog = typeof posthog - - export default posthog diff --git a/yarn.lock b/yarn.lock index 6bb5654db8..891822a66e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1790,11 +1790,6 @@ object.fromentries "^2.0.0" prop-types "^15.7.0" -"@yarnpkg/lockfile@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== - abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -2718,7 +2713,7 @@ cross-fetch@^3.0.4: dependencies: node-fetch "2.6.1" -cross-spawn@^6.0.0, cross-spawn@^6.0.5: +cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -3669,13 +3664,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-yarn-workspace-root@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" - integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== - dependencies: - micromatch "^4.0.2" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -3755,15 +3743,6 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fs-extra@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -3940,7 +3919,7 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -4616,7 +4595,7 @@ is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^2.1.1, is-wsl@^2.2.0: +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -5238,13 +5217,6 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5294,13 +5266,6 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -klaw-sync@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" - integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== - dependencies: - graceful-fs "^4.1.11" - kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -5972,14 +5937,6 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -open@^7.4.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -6009,11 +5966,6 @@ opus-recorder@^8.0.3: resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.3.tgz#f7b44f8f68500c9b96a15042a69f915fd9c1716d" integrity sha512-8vXGiRwlJAavT9D3yYzukNVXQ8vEcKHcsQL/zXO24DQtJ0PLXvoPHNQPJrbMCdB4ypJgWDExvHF4JitQDL7dng== -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -6122,25 +6074,6 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -patch-package@^6.4.7: - version "6.4.7" - resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" - integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== - dependencies: - "@yarnpkg/lockfile" "^1.1.0" - chalk "^2.4.2" - cross-spawn "^6.0.5" - find-yarn-workspace-root "^2.0.0" - fs-extra "^7.0.1" - is-ci "^2.0.0" - klaw-sync "^6.0.0" - minimist "^1.2.0" - open "^7.4.2" - rimraf "^2.6.3" - semver "^5.6.0" - slash "^2.0.0" - tmp "^0.0.33" - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -6321,18 +6254,13 @@ postcss@^8.0.2: nanoid "^3.1.23" source-map-js "^0.6.2" -posthog-js@^1.12.2: +posthog-js@1.12.2: version "1.12.2" resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.2.tgz#ff76e26634067e003f8af7df654d7ea0e647d946" integrity sha512-I0d6c+Yu2f91PFidz65AIkkqZM219EY9Z1wlbTkW5Zqfq5oXqogBMKS8BaDBOrMc46LjLX7IH67ytCcBFRo1uw== dependencies: fflate "^0.4.1" -postinstall-postinstall@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" - integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -6906,13 +6834,6 @@ rfc4648@^1.4.0: resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.0.tgz#1ba940ec1649685ec4d88788dc57fb8e18855055" integrity sha512-FA6W9lDNeX8WbMY31io1xWg+TpZCbeDKsBo0ocwACZiWnh9TUAyk9CCuBQuOPmYnwwdEQZmraQ2ZK7yJsxErBg== -rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -7599,13 +7520,6 @@ tmatch@^2.0.1: resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf" integrity sha1-DFYkbzPzDaG409colauvFmYPOM8= -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -7851,7 +7765,7 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" -universalify@^0.1.0, universalify@^0.1.2: +universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== From 17b935cbc4d7b9ab86e2a885c5da0bd0ff5a252b Mon Sep 17 00:00:00 2001 From: James Salter Date: Wed, 4 Aug 2021 09:34:12 +0100 Subject: [PATCH 068/100] Add @sentry/types and rrweb-snapshot as dev dependencies This is neccessary to resolve re-exported types referred to by posthog-js' type definitions. This isn't ideal, but * We intend to start using sentry anyway * Discussion with posthog maintainers about rrweb-snapshot at https://github.com/PostHog/posthog-js/issues/252, perhaps we can find another solution soon --- package.json | 2 ++ yarn.lock | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/package.json b/package.json index 8f5459ab99..0bcf86bff0 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "@babel/traverse": "^7.12.12", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "@peculiar/webcrypto": "^1.1.4", + "@sentry/types": "^6.10.0", "@sinonjs/fake-timers": "^7.0.2", "@types/classnames": "^2.2.11", "@types/commonmark": "^0.27.4", @@ -167,6 +168,7 @@ "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", "react-test-renderer": "^17.0.2", "rimraf": "^3.0.2", + "rrweb-snapshot": "1.1.7", "stylelint": "^13.9.0", "stylelint-config-standard": "^20.0.0", "stylelint-scss": "^3.18.0", diff --git a/yarn.lock b/yarn.lock index 891822a66e..836301b002 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1352,6 +1352,11 @@ tslib "^2.2.0" webcrypto-core "^1.2.0" +"@sentry/types@^6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.10.0.tgz#6b1f44e5ed4dbc2710bead24d1b32fb08daf04e1" + integrity sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -6841,6 +6846,11 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rrweb-snapshot@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.7.tgz#92a3b47b1112a1b566c2fae2edb02fa48a6f6653" + integrity sha512-+f2kCCvIQ1hbEeCWnV7mPVPDEdWEExqwcYqMd/r1nfK52QE7qU52jefUOyTe85Vy67rZGqWnfK/B25e/OTSgYg== + rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" From 7cb21c845faa2ef835414dd647999479877c9b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 10:33:37 +0200 Subject: [PATCH 069/100] Fix indent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/HtmlUtils.tsx | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 27556a4012..2eee5214af 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -57,10 +57,33 @@ const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i'); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; -export const PERMITTED_URL_SCHEMES = ["bitcoin", "ftp", "geo", "http", "https", "im", "irc", - "ircs", "magnet", "mailto", "matrix", "mms", "news", - "nntp", "openpgp4fpr", "sip", "sftp", "sms", "smsto", - "ssh", "tel", "urn", "webcal", "wtai", "xmpp"]; +export const PERMITTED_URL_SCHEMES = [ + "bitcoin", + "ftp", + "geo", + "http", + "https", + "im", + "irc", + "ircs", + "magnet", + "mailto", + "matrix", + "mms", + "news", + "nntp", + "openpgp4fpr", + "sip", + "sftp", + "sms", + "smsto", + "ssh", + "tel", + "urn", + "webcal", + "wtai", + "xmpp", +]; const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)\/(.+?)(?:[?/]|$)/; From c9c8177f58353fc4b71aa39cb6b8d40ee74d4779 Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Wed, 4 Aug 2021 09:46:39 +0100 Subject: [PATCH 070/100] Start a call immediately after creating a room via the dial pad (#6529) --- src/CallHandler.tsx | 2 ++ test/CallHandler-test.ts | 6 +++++- test/test-utils.js | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index caf010200a..30fcf46790 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -930,6 +930,8 @@ export default class CallHandler extends EventEmitter { action: 'view_room', room_id: roomId, }); + + await this.placeCall(roomId, PlaceCallType.Voice, null); } private async startTransferToPhoneNumber(call: MatrixCall, destination: string, consultFirst: boolean) { diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts index 4c3dd25fb4..1a4560ad4a 100644 --- a/test/CallHandler-test.ts +++ b/test/CallHandler-test.ts @@ -156,13 +156,14 @@ describe('CallHandler', () => { DMRoomMap.setShared(null); // @ts-ignore window.mxCallHandler = null; + fakeCall = null; MatrixClientPeg.unset(); document.body.removeChild(audioElement); SdkConfig.unset(); }); - it('should look up the correct user and open the room when a phone number is dialled', async () => { + it('should look up the correct user and start a call in the room when a phone number is dialled', async () => { MatrixClientPeg.get().getThirdpartyUser = jest.fn().mockResolvedValue([{ userid: '@user2:example.org', protocol: "im.vector.protocol.sip_native", @@ -179,6 +180,9 @@ describe('CallHandler', () => { const viewRoomPayload = await untilDispatch('view_room'); expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID); + + // Check that a call was started + expect(fakeCall.roomId).toEqual(MAPPED_ROOM_ID); }); it('should move calls between rooms when remote asserted identity changes', async () => { diff --git a/test/test-utils.js b/test/test-utils.js index 217c399443..bbab47589a 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -78,6 +78,7 @@ export function createTestClient() { }, mxcUrlToHttp: (mxc) => 'http://this.is.a.url/', setAccountData: jest.fn(), + setRoomAccountData: jest.fn(), sendTyping: jest.fn().mockResolvedValue({}), sendMessage: () => jest.fn().mockResolvedValue({}), getSyncState: () => "SYNCING", From b4b788e8d5b223fe4ec0b5ff29d9b993a890f3f7 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 4 Aug 2021 11:21:52 +0200 Subject: [PATCH 071/100] Fix right margin for events on IRC layout Fixes vector-im/element-web#18354 --- res/css/views/rooms/_EventTile.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 6e207d674b..1c9d8e87d9 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -310,14 +310,12 @@ $hover-select-border: 4px; } .mx_RoomView_timeline_rr_enabled { - - .mx_EventTile:not([data-layout=bubble]) { + .mx_EventTile[data-layout=group] { .mx_EventTile_line { /* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */ margin-right: 110px; } } - // on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter } From c31f9335dc4ee7a6f5a601e0afda46d3dcc6e1d6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Aug 2021 11:51:55 +0100 Subject: [PATCH 072/100] Declare node 14 requirement Mostly for netlify (we need node 14 now because element-web's webpack config uses the `??` operator). --- .node-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .node-version diff --git a/.node-version b/.node-version new file mode 100644 index 0000000000..8351c19397 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +14 From 6350cecb5e4c389ef19c17aca9569b03553bd18a Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Aug 2021 12:02:14 +0100 Subject: [PATCH 073/100] emoty commit to try & wake netlify up From 4a1789be53212266073de2b9f021b344d9f7b479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 13:04:23 +0200 Subject: [PATCH 074/100] Update copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/CallEvent.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 2de66f897a..12e7f22173 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -117,14 +117,12 @@ export default class CallEvent extends React.Component { if (state === CallState.Ended) { const hangupReason = this.props.callEventGrouper.hangupReason; const gotRejected = this.props.callEventGrouper.gotRejected; - const rejectParty = this.props.callEventGrouper.rejectParty; if (gotRejected) { - const weDeclinedCall = MatrixClientPeg.get().getUserId() === rejectParty; return (
- { weDeclinedCall ? _t("You declined this call") : _t("They declined this call") } - { this.renderCallBackButton(weDeclinedCall ? _t("Call back") : _t("Call again")) } + { _t("Call declined") } + { this.renderCallBackButton(_t("Call back")) }
); } else if (([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason)) { @@ -136,14 +134,14 @@ export default class CallEvent extends React.Component { // Also, if we don't have a reason return (
- { _t("This call has ended") } + { _t("Call ended") }
); } else if (hangupReason === CallErrorCode.InviteTimeout) { return (
- { _t("They didn't pick up") } - { this.renderCallBackButton(_t("Call again")) } + { _t("Missed call") } + { this.renderCallBackButton(_t("Call back")) }
); } @@ -176,7 +174,8 @@ export default class CallEvent extends React.Component { className="mx_CallEvent_content_tooltip" kind={InfoTooltipKind.Warning} /> - { _t("This call has failed") } + { _t("Connection failed") } + { this.renderCallBackButton(_t("Retry")) }
); } @@ -190,7 +189,7 @@ export default class CallEvent extends React.Component { if (state === CustomCallState.Missed) { return (
- { _t("You missed this call") } + { _t("Missed call") } { this.renderCallBackButton(_t("Call back")) }
); From d05be441134fd9d1b66cf6269a1e669e3faf2035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 13:07:54 +0200 Subject: [PATCH 075/100] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6867f28b74..aa736bb1e8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1865,19 +1865,15 @@ "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", "Connected": "Connected", - "You declined this call": "You declined this call", - "They declined this call": "They declined this call", + "Call declined": "Call declined", "Call back": "Call back", - "Call again": "Call again", - "This call has ended": "This call has ended", - "They didn't pick up": "They didn't pick up", + "Missed call": "Missed call", "Could not connect media": "Could not connect media", "Connection failed": "Connection failed", "Their device couldn't start the camera or microphone": "Their device couldn't start the camera or microphone", "An unknown error occurred": "An unknown error occurred", "Unknown failure: %(reason)s)": "Unknown failure: %(reason)s)", - "This call has failed": "This call has failed", - "You missed this call": "You missed this call", + "Retry": "Retry", "The call is in an unknown state!": "The call is in an unknown state!", "Sunday": "Sunday", "Monday": "Monday", @@ -1900,7 +1896,6 @@ "Error processing audio message": "Error processing audio message", "React": "React", "Edit": "Edit", - "Retry": "Retry", "Reply": "Reply", "Message Actions": "Message Actions", "Download %(text)s": "Download %(text)s", From f97f410d0939f7c59e8efed819d60967ce528218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 13:16:01 +0200 Subject: [PATCH 076/100] Unused import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/CallEvent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 12e7f22173..a204907caa 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -25,7 +25,6 @@ import { CallErrorCode, CallState } from 'matrix-js-sdk/src/webrtc/call'; import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip'; import classNames from 'classnames'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; -import { MatrixClientPeg } from '../../../MatrixClientPeg'; interface IProps { mxEvent: MatrixEvent; From d0b295f1a737808e4602999a7dfaf901c65008ee Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Aug 2021 13:28:31 +0100 Subject: [PATCH 077/100] Add CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..2c068fff33 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @matrix-org/element-web From 5877e936c06c383ecde92db4eea1a58c2c8a309f Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Wed, 4 Aug 2021 09:24:18 -0400 Subject: [PATCH 078/100] Fix fallback fonts Signed-off-by: Robin Townsend --- res/themes/legacy-light/css/_legacy-light.scss | 4 ++-- res/themes/light/css/_light.scss | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index e945efb219..1a63c9bd07 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', 'Sans-Serif', 'Noto Color Emoji'; +$font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji'; -$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; +$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji'; // Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 $system-light: #F4F6FA; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index aa17dddc56..eff9abe5af 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', 'Sans-Serif', 'Noto Color Emoji'; +$font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji'; -$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; +$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji'; // Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 $system-light: #F4F6FA; From 79e4a95b13bded8accd3a6a4b7e6002ed246fde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 16:25:44 +0200 Subject: [PATCH 079/100] Move stuff out of if statement for better readability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index d4a29a11b9..8d862498d2 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -481,15 +481,14 @@ export default class CallHandler extends EventEmitter { const incomingCallPushRule = ( new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule ); + const pushRuleEnabled = incomingCallPushRule?.enabled; + const tweakSetToRing = incomingCallPushRule.actions.some((action: Tweaks) => ( + action.set_tweak === TweakName.Sound && + action.value === "ring" + )); switch (newState) { case CallState.Ringing: - if ( - incomingCallPushRule?.enabled && - incomingCallPushRule.actions.some((a: Tweaks) => ( - a.set_tweak === TweakName.Sound && - a.value === "ring" - )) - ) { + if (pushRuleEnabled && tweakSetToRing) { this.play(AudioID.Ring); } else { this.silenceCall(call.callId); From 023d8749499605da27d949308e91d58e25cb7183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 16:34:59 +0200 Subject: [PATCH 080/100] Add missing ? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 0de6723d33..0cb916578e 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -485,7 +485,7 @@ export default class CallHandler extends EventEmitter { new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule ); const pushRuleEnabled = incomingCallPushRule?.enabled; - const tweakSetToRing = incomingCallPushRule.actions.some((action: Tweaks) => ( + const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( action.set_tweak === TweakName.Sound && action.value === "ring" )); From 74e1342fa8266c97e6d4dcae664b9a1d4447f0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 16:35:46 +0200 Subject: [PATCH 081/100] Wrap cases in {} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 0cb916578e..77569711df 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -481,27 +481,29 @@ export default class CallHandler extends EventEmitter { this.silencedCalls.delete(call.callId); } - const incomingCallPushRule = ( - new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule - ); - const pushRuleEnabled = incomingCallPushRule?.enabled; - const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( - action.set_tweak === TweakName.Sound && - action.value === "ring" - )); switch (newState) { - case CallState.Ringing: + case CallState.Ringing: { + const incomingCallPushRule = ( + new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule + ); + const pushRuleEnabled = incomingCallPushRule?.enabled; + const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( + action.set_tweak === TweakName.Sound && + action.value === "ring" + )); + if (pushRuleEnabled && tweakSetToRing) { this.play(AudioID.Ring); } else { this.silenceCall(call.callId); } break; - case CallState.InviteSent: + } + case CallState.InviteSent: { this.play(AudioID.Ringback); break; - case CallState.Ended: - { + } + case CallState.Ended: { const hangupReason = call.hangupReason; Analytics.trackEvent('voip', 'callEnded', 'hangupReason', hangupReason); this.removeCallForRoom(mappedRoomId); From fe643b2401d38457deb891af17bd6543ec5526c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 17:09:48 +0200 Subject: [PATCH 082/100] Use flex-start as it has more universal support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/auth/_InteractiveAuthEntryComponents.scss | 2 +- res/css/views/rooms/_EventBubbleTile.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss index ffaad3cd7a..ec07b765fd 100644 --- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss @@ -85,7 +85,7 @@ limitations under the License. .mx_InteractiveAuthEntryComponents_termsPolicy { display: flex; flex-direction: row; - justify-content: start; + justify-content: flex-start; align-items: center; } diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 1e25deba26..c6170bf7c0 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -271,7 +271,7 @@ limitations under the License. display: flex; align-items: center; - justify-content: start; + justify-content: flex-start; padding: 5px 0; .mx_EventTile_avatar { From e2bc76a1294158a9f149b3e6048b0586bbfedd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 17:17:21 +0200 Subject: [PATCH 083/100] Fix voice feed cut-off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_CallView.scss | 2 -- res/css/views/voip/_CallViewSidebar.scss | 2 -- res/css/views/voip/_VideoFeed.scss | 1 + 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 60b7aa69ea..fb18c6a5ee 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -75,8 +75,6 @@ limitations under the License. height: 100%; &.mx_VideoFeed_voice { - // We don't want to collide with the call controls that have 52px of height - margin-bottom: 52px; background-color: $inverted-bg-color; display: flex; justify-content: center; diff --git a/res/css/views/voip/_CallViewSidebar.scss b/res/css/views/voip/_CallViewSidebar.scss index 892a137a32..dbadc22028 100644 --- a/res/css/views/voip/_CallViewSidebar.scss +++ b/res/css/views/voip/_CallViewSidebar.scss @@ -40,8 +40,6 @@ limitations under the License. display: flex; align-items: center; justify-content: center; - - aspect-ratio: 16 / 9; } .mx_VideoFeed_video { diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 3a0f62636e..7a8d39dfe3 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -20,6 +20,7 @@ limitations under the License. &.mx_VideoFeed_voice { background-color: $inverted-bg-color; + aspect-ratio: 16 / 9; } .mx_VideoFeed_video { From 91b4b50969719db75d9c75d2af9d1647add38112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 17:25:12 +0200 Subject: [PATCH 084/100] Fix wrong cursor being used in PiP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_CallContainer.scss | 1 - res/css/views/voip/_CallView.scss | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss index 181a5ee0a3..d11ab9bf9f 100644 --- a/res/css/views/voip/_CallContainer.scss +++ b/res/css/views/voip/_CallContainer.scss @@ -28,7 +28,6 @@ limitations under the License. .mx_CallPreview { pointer-events: initial; // restore pointer events so the user can leave/interact - cursor: pointer; .mx_VideoFeed_remote.mx_VideoFeed_voice { min-height: 150px; diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 60b7aa69ea..c473a1fc79 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -208,6 +208,7 @@ limitations under the License. align-items: center; justify-content: left; flex-shrink: 0; + cursor: pointer; } .mx_CallView_header_callType { From ee500ad68403fd79730bfbec0b01f6c44c368a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 18:14:32 +0200 Subject: [PATCH 085/100] Update copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/settings/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 8c30a55f8d..f196f1cda9 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -473,7 +473,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "MessageComposerInput.surroundWith": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td("Use surround with"), + displayName: _td("Surround selected text when typing special characters"), default: false, }, "MessageComposerInput.autoReplaceEmoji": { From b9b98c6a817b33f1cb23b76b4cf1adbea4d325fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 18:15:11 +0200 Subject: [PATCH 086/100] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3eedb9510c..70bb1e436b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -847,7 +847,7 @@ "Use Ctrl + F to search timeline": "Use Ctrl + F to search timeline", "Use Command + Enter to send a message": "Use Command + Enter to send a message", "Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message", - "Use surround with": "Use surround with", + "Surround selected text when typing special characters": "Surround selected text when typing special characters", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", From 4d5bdf70b70aa57d6e4394eacfe0baef9e4d189c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 4 Aug 2021 15:08:22 -0600 Subject: [PATCH 087/100] Fix worklet reference for new webpack pipeline --- src/audio/VoiceRecording.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts index efd616e5ae..67b2acda0c 100644 --- a/src/audio/VoiceRecording.ts +++ b/src/audio/VoiceRecording.ts @@ -30,6 +30,7 @@ import { IEncryptedFile } from "matrix-js-sdk/src/@types/event"; import { uploadFile } from "../ContentMessages"; import { FixedRollingArray } from "../utils/FixedRollingArray"; import { clamp } from "../utils/numbers"; +import mxRecorderWorkletPath from "./RecorderWorklet"; const CHANNELS = 1; // stereo isn't important export const SAMPLE_RATE = 48000; // 48khz is what WebRTC uses. 12khz is where we lose quality. @@ -113,16 +114,10 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { }); this.recorderSource = this.recorderContext.createMediaStreamSource(this.recorderStream); - // Set up our worklet. We use this for timing information and waveform analysis: the - // web audio API prefers this be done async to avoid holding the main thread with math. - const mxRecorderWorkletPath = document.body.dataset.vectorRecorderWorkletScript; - if (!mxRecorderWorkletPath) { - // noinspection ExceptionCaughtLocallyJS - throw new Error("Unable to create recorder: no worklet script registered"); - } - // Connect our inputs and outputs if (this.recorderContext.audioWorklet) { + // Set up our worklet. We use this for timing information and waveform analysis: the + // web audio API prefers this be done async to avoid holding the main thread with math. await this.recorderContext.audioWorklet.addModule(mxRecorderWorkletPath); this.recorderWorklet = new AudioWorkletNode(this.recorderContext, WORKLET_NAME); this.recorderSource.connect(this.recorderWorklet); From 9b32a1c60923fa9dc22ffb8c7be60fa65ae5fd52 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 4 Aug 2021 15:23:35 -0600 Subject: [PATCH 088/100] Appease Jest --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index cccb92d5d9..2445e3c973 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,8 @@ "decoderWorker\\.min\\.js": "/__mocks__/empty.js", "decoderWorker\\.min\\.wasm": "/__mocks__/empty.js", "waveWorker\\.min\\.js": "/__mocks__/empty.js", - "workers/(.+)\\.worker\\.ts": "/__mocks__/workerMock.js" + "workers/(.+)\\.worker\\.ts": "/__mocks__/workerMock.js", + "RecorderWorklet": "/__mocks__/empty.js" }, "transformIgnorePatterns": [ "/node_modules/(?!matrix-js-sdk).+$" From 5e3b79eecafb1d70a3cf076b5048d50d5c4ec3e5 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Wed, 4 Aug 2021 18:42:47 -0400 Subject: [PATCH 089/100] Remove seams from pin icon Signed-off-by: Robin Townsend --- res/img/element-icons/room/pin.svg | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/res/img/element-icons/room/pin.svg b/res/img/element-icons/room/pin.svg index 2448fc61c5..f090f60be8 100644 --- a/res/img/element-icons/room/pin.svg +++ b/res/img/element-icons/room/pin.svg @@ -1,7 +1,3 @@ - - - - - + From a9d7d010141052f6b1d85c2aefab7de5c6b1a314 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 12:30:22 +0100 Subject: [PATCH 090/100] Null guard space inviter to prevent the app exploding --- src/components/structures/SpaceRoomView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 4064b2f48e..6f63ea090c 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -192,11 +192,11 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => if (inviteSender) { inviterSection =
- +
{ _t(" invites you", {}, { - inviter: () => { inviter.name || inviteSender }, + inviter: () => { inviter?.name || inviteSender }, }) }
{ inviter ?
From 99adbfd1ea896e6fbaf6417d6ecf8aab50d58a8b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 13:04:20 +0100 Subject: [PATCH 091/100] Skip sending a thumbnail if it is not a sufficient saving over the original --- src/ContentMessages.tsx | 49 +++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index c5bcb226ff..8973219b49 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -209,6 +209,14 @@ async function loadImageElement(imageFile: File) { return { width, height, img }; } +// Minimum size for image files before we generate a thumbnail for them. +const IMAGE_SIZE_THRESHOLD_THUMBNAIL = 1 << 15; // 32KB +// Minimum size improvement for image thumbnails, if both are not met then don't bother uploading thumbnail. +const IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE = 1 << 16; // 1MB +const IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT = 0.1; // 10% +// We don't apply these thresholds to video thumbnails as a poster image is always useful +// and videos tend to be much larger. + /** * Read the metadata for an image file and create and upload a thumbnail of the image. * @@ -217,23 +225,40 @@ async function loadImageElement(imageFile: File) { * @param {File} imageFile The image to read and thumbnail. * @return {Promise} A promise that resolves with the attachment info. */ -function infoForImageFile(matrixClient, roomId, imageFile) { +async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imageFile: File) { + if (imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL) { + // don't bother generating a thumbnail, image is small enough already + return {}; + } + let thumbnailType = "image/png"; if (imageFile.type === "image/jpeg") { thumbnailType = "image/jpeg"; } - let imageInfo; - return loadImageElement(imageFile).then((r) => { - return createThumbnail(r.img, r.width, r.height, thumbnailType); - }).then((result) => { - imageInfo = result.info; - return uploadFile(matrixClient, roomId, result.thumbnail); - }).then((result) => { - imageInfo.thumbnail_url = result.url; - imageInfo.thumbnail_file = result.file; - return imageInfo; - }); + const imageElement = await loadImageElement(imageFile); + + if (imageElement.width < MAX_WIDTH && imageElement.height < MAX_HEIGHT) { + // don't bother uploading thumbnail as it'd be the same resolution as the original. + return {}; + } + + const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType); + const imageInfo = result.info; + + const sizeDifference = imageFile.size - imageInfo.thumbnail_info.size; + if (sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE && + sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT) + ) { + // don't bother uploading thumbnail as it not sufficiently smaller than the original. + return {}; + } + + const uploadResult = await uploadFile(matrixClient, roomId, result.thumbnail); + + imageInfo["thumbnail_url"] = uploadResult.url; + imageInfo["thumbnail_file"] = uploadResult.file; + return imageInfo; } /** From 980b284642cbbdbcb88920b8dd1e89bf312cc820 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 13:12:01 +0100 Subject: [PATCH 092/100] Fix image & blurhash info when skipping thumbnail due to thresholds --- src/ContentMessages.tsx | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index 8973219b49..14a0c1ed51 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -226,11 +226,6 @@ const IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT = 0.1; // 10% * @return {Promise} A promise that resolves with the attachment info. */ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imageFile: File) { - if (imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL) { - // don't bother generating a thumbnail, image is small enough already - return {}; - } - let thumbnailType = "image/png"; if (imageFile.type === "image/jpeg") { thumbnailType = "image/jpeg"; @@ -238,20 +233,18 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag const imageElement = await loadImageElement(imageFile); - if (imageElement.width < MAX_WIDTH && imageElement.height < MAX_HEIGHT) { - // don't bother uploading thumbnail as it'd be the same resolution as the original. - return {}; - } - const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType); const imageInfo = result.info; + // we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from. const sizeDifference = imageFile.size - imageInfo.thumbnail_info.size; - if (sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE && - sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT) + if ( + imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL || // image is small enough already + (sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE && // thumbnail is not sufficiently smaller than original + sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT)) ) { - // don't bother uploading thumbnail as it not sufficiently smaller than the original. - return {}; + delete imageInfo["thumbnail_info"]; + return imageInfo; } const uploadResult = await uploadFile(matrixClient, roomId, result.thumbnail); From a8b050a385f8831904c083f7ef555a083e26eeff Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 13:37:23 +0100 Subject: [PATCH 093/100] Post-merge conflict resolution and improve alignment of tooltips --- src/components/views/voip/CallView.tsx | 50 ++++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 356e642d65..3178566e68 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -23,11 +23,16 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t, _td } from '../../../languageHandler'; import VideoFeed from './VideoFeed'; import RoomAvatar from "../avatars/RoomAvatar"; -import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/webrtc/call'; +import { CallEvent, CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../../Keyboard'; -import { alwaysAboveLeftOf, alwaysAboveRightOf, ChevronFace, ContextMenuButton } from '../../structures/ContextMenu'; +import { + alwaysAboveLeftOf, + alwaysAboveRightOf, + ChevronFace, + ContextMenuTooltipButton, +} from '../../structures/ContextMenu'; import CallContextMenu from '../context_menus/CallContextMenu'; import { avatarUrlForMember } from '../../../Avatar'; import DialpadContextMenu from '../context_menus/DialpadContextMenu'; @@ -37,6 +42,8 @@ import DesktopCapturerSourcePicker from "../elements/DesktopCapturerSourcePicker import Modal from '../../../Modal'; import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; import CallViewSidebar from './CallViewSidebar'; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import { Alignment } from "../elements/Tooltip"; interface IProps { // The call for us to display @@ -115,7 +122,6 @@ export default class CallView extends React.Component { private controlsHideTimer: number = null; private dialpadButton = createRef(); private contextMenuButton = createRef(); - private contextMenu = createRef(); constructor(props: IProps) { super(props); @@ -479,9 +485,12 @@ export default class CallView extends React.Component { let vidMuteButton; if (this.props.call.type === CallType.Video) { vidMuteButton = ( - ); } @@ -496,9 +505,15 @@ export default class CallView extends React.Component { this.props.call.state === CallState.Connected ) { screensharingButton = ( - ); } @@ -518,6 +533,7 @@ export default class CallView extends React.Component { ); } @@ -526,22 +542,28 @@ export default class CallView extends React.Component { let contextMenuButton; if (this.state.callState === CallState.Connected) { contextMenuButton = ( - ); } let dialpadButton; if (this.state.callState === CallState.Connected && this.props.call.opponentSupportsDTMF()) { dialpadButton = ( - ); } @@ -583,9 +605,12 @@ export default class CallView extends React.Component { { dialPad } { contextMenu } { dialpadButton } - { vidMuteButton }
@@ -593,9 +618,12 @@ export default class CallView extends React.Component { { screensharingButton } { sidebarButton } { contextMenuButton } -
); @@ -820,7 +848,7 @@ export default class CallView extends React.Component { let fullScreenButton; if (!this.props.pipMode) { fullScreenButton = ( -
{ let expandButton; if (this.props.pipMode) { - expandButton =
Date: Thu, 5 Aug 2021 13:50:50 +0100 Subject: [PATCH 094/100] i18n --- src/i18n/strings/en_EN.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9664b2ca14..41ef0cc04f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -906,8 +906,12 @@ "sends space invaders": "sends space invaders", "Start the camera": "Start the camera", "Stop the camera": "Stop the camera", - "Dialpad": "Dialpad", + "Stop sharing your screen": "Stop sharing your screen", + "Start sharing your screen": "Start sharing your screen", + "Hide sidebar": "Hide sidebar", + "Show sidebar": "Show sidebar", "More": "More", + "Dialpad": "Dialpad", "Unmute the microphone": "Unmute the microphone", "Mute the microphone": "Mute the microphone", "Hangup": "Hangup", From e1b62ae38b15b503468f212f247dd9470e18c47a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 14:20:31 +0100 Subject: [PATCH 095/100] Increase yOffset by 4px away --- src/components/views/voip/CallView.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 3178566e68..945cc55ed3 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -490,7 +490,7 @@ export default class CallView extends React.Component { onClick={this.onVidMuteClick} title={this.state.vidMuted ? _t("Start the camera") : _t("Stop the camera")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> ); } @@ -513,7 +513,7 @@ export default class CallView extends React.Component { : _t("Start sharing your screen") } alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> ); } @@ -549,7 +549,7 @@ export default class CallView extends React.Component { isExpanded={this.state.showMoreMenu} title={_t("More")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> ); } @@ -563,7 +563,7 @@ export default class CallView extends React.Component { isExpanded={this.state.showDialpad} title={_t("Dialpad")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> ); } @@ -610,7 +610,7 @@ export default class CallView extends React.Component { onClick={this.onMicMuteClick} title={this.state.micMuted ? _t("Unmute the microphone") : _t("Mute the microphone")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> { vidMuteButton }
@@ -623,7 +623,7 @@ export default class CallView extends React.Component { onClick={this.onHangupClick} title={_t("Hangup")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} />
); From b860acca8043ea9c8c937380650339566439f775 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 14:43:44 +0100 Subject: [PATCH 096/100] Extract tooltipYOffset to a const --- src/components/views/voip/CallView.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 945cc55ed3..d301a1e43b 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -82,6 +82,8 @@ interface IState { sidebarShown: boolean; } +const tooltipYOffset = -24; + function getFullScreenElement() { return ( document.fullscreenElement || @@ -490,7 +492,7 @@ export default class CallView extends React.Component { onClick={this.onVidMuteClick} title={this.state.vidMuted ? _t("Start the camera") : _t("Stop the camera")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> ); } @@ -513,7 +515,7 @@ export default class CallView extends React.Component { : _t("Start sharing your screen") } alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> ); } @@ -549,7 +551,7 @@ export default class CallView extends React.Component { isExpanded={this.state.showMoreMenu} title={_t("More")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> ); } @@ -563,7 +565,7 @@ export default class CallView extends React.Component { isExpanded={this.state.showDialpad} title={_t("Dialpad")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> ); } @@ -610,7 +612,7 @@ export default class CallView extends React.Component { onClick={this.onMicMuteClick} title={this.state.micMuted ? _t("Unmute the microphone") : _t("Mute the microphone")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> { vidMuteButton }
@@ -623,7 +625,7 @@ export default class CallView extends React.Component { onClick={this.onHangupClick} title={_t("Hangup")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} />
); From df888a1886aeb9783091caf506d7b4216495a533 Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Thu, 5 Aug 2021 16:33:22 +0100 Subject: [PATCH 097/100] Fix in-call context menus when in PiP mode (#6552) Mounting them as children when in PiP mode doesn't work. Condition mounting the context menus as children of the current component based on whether PiP mode is active. --- src/components/views/voip/CallView.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 356e642d65..d5ec2e71d3 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -554,7 +554,11 @@ export default class CallView extends React.Component { ChevronFace.None, CONTEXT_MENU_VPADDING, )} - mountAsChild={true} + // We mount the context menus as a as a child typically in order to include the + // context menus when fullscreening the call content. + // However, this does not work as well when the call is embedded in a + // picture-in-picture frame. Thus, only mount as child when we are *not* in PiP. + mountAsChild={!this.props.pipMode} onFinished={this.closeDialpad} call={this.props.call} />; @@ -568,7 +572,7 @@ export default class CallView extends React.Component { ChevronFace.None, CONTEXT_MENU_VPADDING, )} - mountAsChild={true} + mountAsChild={!this.props.pipMode} onFinished={this.closeContextMenu} call={this.props.call} />; From 81ddaf2efaf0eda2c296b0e2fb43834b7aae5575 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 5 Aug 2021 11:21:22 -0600 Subject: [PATCH 098/100] Properly set style attribute on shared usercontent iframe Fixes https://github.com/vector-im/element-web/issues/18414 --- src/utils/FileDownloader.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils/FileDownloader.ts b/src/utils/FileDownloader.ts index a22ff506de..5ec91d71cc 100644 --- a/src/utils/FileDownloader.ts +++ b/src/utils/FileDownloader.ts @@ -43,9 +43,8 @@ function getManagedIframe(): { iframe: HTMLIFrameElement, onLoadPromise: Promise // Dev note: the reassignment warnings are entirely incorrect here. - // @ts-ignore - // noinspection JSConstantReassignment - managedIframe.style = { display: "none" }; + managedIframe.style.display = "none"; + // @ts-ignore // noinspection JSConstantReassignment managedIframe.sandbox = "allow-scripts allow-downloads allow-downloads-without-user-activation"; From 17a3dc5e6dc79ccd9e926c49a9c24f0caae0e0f5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 5 Aug 2021 12:44:12 -0600 Subject: [PATCH 099/100] Stop voice messages that are playing when starting a recording Fixes https://github.com/vector-im/element-web/issues/18410 --- src/components/views/rooms/VoiceRecordComposerTile.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 019e39d415..1b583444a3 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -35,6 +35,7 @@ import NotificationBadge from "./NotificationBadge"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import InlineSpinner from "../elements/InlineSpinner"; +import { PlaybackManager } from "../../../audio/PlaybackManager"; interface IProps { room: Room; @@ -177,6 +178,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent Date: Thu, 5 Aug 2021 17:56:16 -0400 Subject: [PATCH 100/100] Add comments to isRegionalIndicator Signed-off-by: Robin Townsend --- src/emoji.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/emoji.ts b/src/emoji.ts index e871e0bb58..ee84583fc9 100644 --- a/src/emoji.ts +++ b/src/emoji.ts @@ -35,8 +35,15 @@ export const EMOTICON_TO_EMOJI = new Map(); export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode)); -const isRegionalIndicator = (x: string): boolean => - Array.from(x).length === 1 && x >= '\u{1f1e6}' && x <= '\u{1f1ff}'; +const isRegionalIndicator = (x: string): boolean => { + // First verify that the string is a single character. We use Array.from + // to make sure we count by characters, not UTF-8 code units. + return Array.from(x).length === 1 && + // Next verify that the character is within the code point range for + // regional indicators. + // http://unicode.org/charts/PDF/Unicode-6.0/U60-1F100.pdf + x >= '\u{1f1e6}' && x <= '\u{1f1ff}'; +}; const EMOJIBASE_GROUP_ID_TO_CATEGORY = [ "people", // smileys