From e91f4b7eb25622dcee27ac4b608ddddd802d1ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 14 Apr 2021 19:15:19 +0200 Subject: [PATCH 01/23] Add model var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/SendMessageComposer.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 75bc943146..a3e841a0e2 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -328,6 +328,8 @@ export default class SendMessageComposer extends React.Component { } async _sendMessage() { + const model = this.model; + if (this.model.isEmpty) { return; } @@ -336,7 +338,7 @@ export default class SendMessageComposer extends React.Component { let shouldSend = true; let content; - if (!containsEmote(this.model) && this._isSlashCommand()) { + if (!containsEmote(model) && this._isSlashCommand()) { const [cmd, args, commandText] = this._getSlashCommand(); if (cmd) { if (cmd.category === CommandCategories.messages) { @@ -377,7 +379,7 @@ export default class SendMessageComposer extends React.Component { } } - if (isQuickReaction(this.model)) { + if (isQuickReaction(model)) { shouldSend = false; this._sendQuickReaction(); } @@ -386,7 +388,7 @@ export default class SendMessageComposer extends React.Component { const startTime = CountlyAnalytics.getTimestamp(); const {roomId} = this.props.room; if (!content) { - content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent); + content = createMessageContent(model, this.props.permalinkCreator, replyToEvent); } // don't bother sending an empty message if (!content.body.trim()) return; @@ -409,9 +411,9 @@ export default class SendMessageComposer extends React.Component { CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content); } - this.sendHistoryManager.save(this.model, replyToEvent); + this.sendHistoryManager.save(model, replyToEvent); // clear composer - this.model.reset([]); + model.reset([]); this._editorRef.clearUndoHistory(); this._editorRef.focus(); this._clearStoredEditorState(); From 3edf05d38d72ec72522842d7e27a2e298b2e077c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 18 Apr 2021 08:43:00 +0200 Subject: [PATCH 02/23] Replace emoji at the end of a message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/rooms/BasicMessageComposer.tsx | 15 ++++++++++----- src/components/views/rooms/SendMessageComposer.js | 7 ++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 9d9e3a1ba0..f19b5903df 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -51,6 +51,7 @@ import {replaceableComponent} from "../../../utils/replaceableComponent"; // matches emoticons which follow the start of a line or whitespace const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$'); +export const REGEX_EMOTICON = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')$'); const IS_MAC = navigator.platform.indexOf("Mac") !== -1; @@ -150,7 +151,7 @@ export default class BasicMessageEditor extends React.Component } } - private replaceEmoticon = (caretPosition: DocumentPosition) => { + public replaceEmoticon(caretPosition: DocumentPosition, regex: RegExp) { const {model} = this.props; const range = model.startRange(caretPosition); // expand range max 8 characters backwards from caretPosition, @@ -161,7 +162,7 @@ export default class BasicMessageEditor extends React.Component n -= 1; return n >= 0 && (part.type === "plain" || part.type === "pill-candidate"); }); - const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text); + const emoticonMatch = regex.exec(range.text); if (emoticonMatch) { const query = emoticonMatch[1].replace("-", ""); // try both exact match and lower-case, this means that xd won't match xD but :P will match :p @@ -180,7 +181,7 @@ export default class BasicMessageEditor extends React.Component return range.replace([partCreator.plain(data.unicode + " ")]); } } - }; + } private updateEditorState = (selection: Caret, inputType?: string, diff?: IDiff) => { renderModel(this.editorRef.current, this.props.model); @@ -567,8 +568,7 @@ export default class BasicMessageEditor extends React.Component }; private configureEmoticonAutoReplace = () => { - const shouldReplace = SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji'); - this.props.model.setTransformCallback(shouldReplace ? this.replaceEmoticon : null); + this.props.model.setTransformCallback(this.transform); }; private configureShouldShowPillAvatar = () => { @@ -576,6 +576,11 @@ export default class BasicMessageEditor extends React.Component this.setState({ showPillAvatar }); }; + private transform = (documentPosition: DocumentPosition) => { + const shouldReplace = SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji'); + if (shouldReplace) this.replaceEmoticon(documentPosition, REGEX_EMOTICON_WHITESPACE); + } + componentWillUnmount() { document.removeEventListener("selectionchange", this.onSelectionChange); this.editorRef.current.removeEventListener("input", this.onInput, true); diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index a3e841a0e2..703d409b00 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -28,7 +28,7 @@ import { stripPrefix, } from '../../../editor/serialize'; import {CommandPartCreator} from '../../../editor/parts'; -import BasicMessageComposer from "./BasicMessageComposer"; +import BasicMessageComposer, {REGEX_EMOTICON} from "./BasicMessageComposer"; import ReplyThread from "../elements/ReplyThread"; import {parseEvent} from '../../../editor/deserialize'; import {findEditableEvent} from '../../../utils/EventUtils'; @@ -334,6 +334,11 @@ export default class SendMessageComposer extends React.Component { return; } + // Replace emoticon at the end of the message + const caret = this._editorRef.getCaret(); + const position = model.positionForOffset(caret.offset, caret.atNodeEnd); + this._editorRef.replaceEmoticon(position, REGEX_EMOTICON); + const replyToEvent = this.props.replyToEvent; let shouldSend = true; let content; From 609196a240a8783576fea33599c343a68648e53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 18 Apr 2021 10:02:50 +0200 Subject: [PATCH 03/23] Replace emoticon before a newline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/rooms/BasicMessageComposer.tsx | 15 ++++++++++----- src/editor/range.ts | 7 +++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index f19b5903df..e84d8bb9c0 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -50,7 +50,7 @@ import { AutocompleteAction, getKeyBindingsManager, MessageComposerAction } from import {replaceableComponent} from "../../../utils/replaceableComponent"; // matches emoticons which follow the start of a line or whitespace -const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$'); +const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s|:^$'); export const REGEX_EMOTICON = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')$'); const IS_MAC = navigator.platform.indexOf("Mac") !== -1; @@ -160,7 +160,7 @@ export default class BasicMessageEditor extends React.Component range.expandBackwardsWhile((index, offset) => { const part = model.parts[index]; n -= 1; - return n >= 0 && (part.type === "plain" || part.type === "pill-candidate"); + return n >= 0 && ["plain", "pill-candidate", "newline"].includes(part.type); }); const emoticonMatch = regex.exec(range.text); if (emoticonMatch) { @@ -170,15 +170,20 @@ export default class BasicMessageEditor extends React.Component if (data) { const {partCreator} = model; - const hasPrecedingSpace = emoticonMatch[0][0] === " "; + const moveStart = emoticonMatch[0][0] === " " ? 1 : 0; + const moveEnd = emoticonMatch[0].length - emoticonMatch.length - moveStart; + // we need the range to only comprise of the emoticon // because we'll replace the whole range with an emoji, // so move the start forward to the start of the emoticon. // Take + 1 because index is reported without the possible preceding space. - range.moveStart(emoticonMatch.index + (hasPrecedingSpace ? 1 : 0)); + range.moveStart(emoticonMatch.index + moveStart); + // and move end backwards so that we don't replace the trailing space/newline + range.moveEndBackwards(moveEnd); + // this returns the amount of added/removed characters during the replace // so the caret position can be adjusted. - return range.replace([partCreator.plain(data.unicode + " ")]); + return range.replace([partCreator.plain(data.unicode)]); } } } diff --git a/src/editor/range.ts b/src/editor/range.ts index 838dfd8b98..b390ad1d5e 100644 --- a/src/editor/range.ts +++ b/src/editor/range.ts @@ -39,6 +39,13 @@ export default class Range { }); } + moveEndBackwards(delta: number) { + this._end = this._end.backwardsWhile(this.model, () => { + delta -= 1; + return delta >= 0; + }); + } + trim() { this._start = this._start.forwardsWhile(this.model, whitespacePredicate); this._end = this._end.backwardsWhile(this.model, whitespacePredicate); From d36f8ccb95bce378026f7a17d9d49fe99191abcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 18 Apr 2021 10:18:49 +0200 Subject: [PATCH 04/23] Rename moveStart to moveStartForwards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So it it's clear what it does Signed-off-by: Šimon Brandner --- src/components/views/rooms/BasicMessageComposer.tsx | 2 +- src/editor/range.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index e84d8bb9c0..09f43fc9a4 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -177,7 +177,7 @@ export default class BasicMessageEditor extends React.Component // because we'll replace the whole range with an emoji, // so move the start forward to the start of the emoticon. // Take + 1 because index is reported without the possible preceding space. - range.moveStart(emoticonMatch.index + moveStart); + range.moveStartForwards(emoticonMatch.index + moveStart); // and move end backwards so that we don't replace the trailing space/newline range.moveEndBackwards(moveEnd); diff --git a/src/editor/range.ts b/src/editor/range.ts index b390ad1d5e..313a1b9ac8 100644 --- a/src/editor/range.ts +++ b/src/editor/range.ts @@ -32,7 +32,7 @@ export default class Range { this._end = bIsLarger ? positionB : positionA; } - moveStart(delta: number) { + moveStartForwards(delta: number) { this._start = this._start.forwardsWhile(this.model, () => { delta -= 1; return delta >= 0; From 07eafb9e3d7031cf73f2c095e4176d8189b5b662 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Thu, 9 Sep 2021 20:07:51 -0400 Subject: [PATCH 05/23] Fix room list scroll jumps Signed-off-by: Robin Townsend --- res/css/views/rooms/_RoomSublist.scss | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index 3fffbfd64c..6db2185dd5 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -172,14 +172,12 @@ limitations under the License. } } - // In the general case, we leave height of headers alone even if sticky, so - // that the sublists below them do not jump. However, that leaves a gap - // when scrolled to the top above the first sublist (whose header can only - // ever stick to top), so we force height to 0 for only that first header. - // See also https://github.com/vector-im/element-web/issues/14429. - &:first-child .mx_RoomSublist_headerContainer { - height: 0; - padding-bottom: 4px; + // In the general case, we reserve space for each sublist header to prevent + // scroll jumps when they become sticky. However, that leaves a gap when + // scrolled to the top above the first sublist (whose header can only ever + // stick to top), so we make sure to exclude the first visible sublist. + &:not(.mx_RoomSublist_hidden) ~ .mx_RoomSublist .mx_RoomSublist_headerContainer { + height: 24px; } .mx_RoomSublist_resizeBox { From 4c8c64a1cb61cb9a257a38ac4283596edce71862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 29 Aug 2021 09:39:34 +0200 Subject: [PATCH 06/23] Use MediaHandler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/MediaDeviceHandler.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts index 073f24523d..154f167745 100644 --- a/src/MediaDeviceHandler.ts +++ b/src/MediaDeviceHandler.ts @@ -17,8 +17,8 @@ limitations under the License. import SettingsStore from "./settings/SettingsStore"; import { SettingLevel } from "./settings/SettingLevel"; -import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix"; import EventEmitter from 'events'; +import { MatrixClientPeg } from "./MatrixClientPeg"; // XXX: MediaDeviceKind is a union type, so we make our own enum export enum MediaDeviceKindEnum { @@ -74,8 +74,8 @@ export default class MediaDeviceHandler extends EventEmitter { const audioDeviceId = SettingsStore.getValue("webrtc_audioinput"); const videoDeviceId = SettingsStore.getValue("webrtc_videoinput"); - setMatrixCallAudioInput(audioDeviceId); - setMatrixCallVideoInput(videoDeviceId); + MatrixClientPeg.get().getMediaHandler().setAudioInput(audioDeviceId); + MatrixClientPeg.get().getMediaHandler().setVideoInput(videoDeviceId); } public setAudioOutput(deviceId: string): void { @@ -90,7 +90,7 @@ export default class MediaDeviceHandler extends EventEmitter { */ public setAudioInput(deviceId: string): void { SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId); - setMatrixCallAudioInput(deviceId); + MatrixClientPeg.get().getMediaHandler().setAudioInput(deviceId); } /** @@ -100,7 +100,7 @@ export default class MediaDeviceHandler extends EventEmitter { */ public setVideoInput(deviceId: string): void { SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId); - setMatrixCallVideoInput(deviceId); + MatrixClientPeg.get().getMediaHandler().setVideoInput(deviceId); } public setDevice(deviceId: string, kind: MediaDeviceKindEnum): void { From 0defc4b14bf9108d482e570da275fc55c4b71201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 11 Sep 2021 09:03:04 +0200 Subject: [PATCH 07/23] Don't show screensharing dialog on web MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index cec67499ae..17fda93921 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -277,9 +277,13 @@ export default class CallView extends React.Component { if (this.state.screensharing) { isScreensharing = await this.props.call.setScreensharingEnabled(false); } else { - const { finished } = Modal.createDialog(DesktopCapturerSourcePicker); - const [source] = await finished; - isScreensharing = await this.props.call.setScreensharingEnabled(true, source); + if (window.electron?.getDesktopCapturerSources) { + const { finished } = Modal.createDialog(DesktopCapturerSourcePicker); + const [source] = await finished; + isScreensharing = await this.props.call.setScreensharingEnabled(true, source); + } else { + isScreensharing = await this.props.call.setScreensharingEnabled(true); + } } this.setState({ From f4ca073b4ae316400b163492c1fcbbc992bbaf7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 11 Sep 2021 10:26:15 +0200 Subject: [PATCH 08/23] Don't auto replace if not enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/SendMessageComposer.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 0a33af30b9..b2fca33dfe 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -354,9 +354,11 @@ export default class SendMessageComposer extends React.Component { } // Replace emoticon at the end of the message - const caret = this.editorRef.current?.getCaret(); - const position = model.positionForOffset(caret.offset, caret.atNodeEnd); - this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON); + if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) { + const caret = this.editorRef.current?.getCaret(); + const position = model.positionForOffset(caret.offset, caret.atNodeEnd); + this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON); + } const replyToEvent = this.props.replyToEvent; let shouldSend = true; From 8099791b320a4e97c148572348dc4cc7d8590376 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 11 Sep 2021 11:39:44 -0400 Subject: [PATCH 09/23] Fix various message bubble alignment issues Signed-off-by: Robin Townsend --- res/css/views/rooms/_EventBubbleTile.scss | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 41c9dad394..d10353a663 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -26,7 +26,7 @@ limitations under the License. position: relative; margin-top: var(--gutterSize); - margin-left: 50px; + margin-left: 49px; margin-right: 100px; &.mx_EventTile_continuation { @@ -287,6 +287,7 @@ limitations under the License. .mx_EventTile_line, .mx_EventTile_info { min-width: 100%; + margin: 0; } .mx_EventTile_e2eIcon { @@ -295,8 +296,8 @@ limitations under the License. .mx_EventTile_line > a { right: auto; - top: -15px; - left: -68px; + top: -11px; + left: -95px; } } @@ -326,11 +327,9 @@ limitations under the License. } .mx_EventTile_line { - margin: 0 5px; + margin: 0; > a { - left: auto; - right: 0; - transform: translateX(calc(100% + 5px)); + left: -76px; } } @@ -340,7 +339,7 @@ limitations under the License. } .mx_EventListSummary[data-expanded=false][data-layout=bubble] { - padding: 0 34px; + padding: 0 49px; } /* events that do not require bubble layout */ From 465ce0820d132346d7b5b878039fbaaee966619c Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 13 Sep 2021 23:32:01 -0400 Subject: [PATCH 10/23] Make message bubble font size consistent Signed-off-by: Robin Townsend --- res/css/views/rooms/_EventBubbleTile.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 41c9dad394..eb1cf29b0f 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -28,6 +28,7 @@ limitations under the License. margin-top: var(--gutterSize); margin-left: 50px; margin-right: 100px; + font-size: $font-14px; &.mx_EventTile_continuation { margin-top: 2px; @@ -81,6 +82,7 @@ limitations under the License. position: relative; top: -2px; left: 2px; + font-size: $font-15px; } &[data-self=false] { From a43f5507a3a4dd767d5cb71a138f42d89e0e82ff Mon Sep 17 00:00:00 2001 From: James Salter Date: Tue, 14 Sep 2021 14:57:26 +0100 Subject: [PATCH 11/23] Use a UUID instead of hashed user ID for tracking Generate a UUID and save it to account data for cross device tracking. --- src/Lifecycle.ts | 5 +++-- src/PosthogAnalytics.ts | 26 ++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index e48fd52cb1..5f5aeb389f 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -574,11 +574,12 @@ async function doSetLoggedIn( await abortLogin(); } - PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId); - Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl); MatrixClientPeg.replaceUsingCreds(credentials); + + PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId); + const client = MatrixClientPeg.get(); if (credentials.freshLogin && SettingsStore.getValue("feature_dehydration")) { diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index 860a155aff..f01246ec00 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -18,6 +18,7 @@ import posthog, { PostHog } from 'posthog-js'; import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; import SettingsStore from './settings/SettingsStore'; +import { MatrixClientPeg } from "./MatrixClientPeg"; /* Posthog analytics tracking. * @@ -274,9 +275,30 @@ export class PosthogAnalytics { this.anonymity = anonymity; } - public async identifyUser(userId: string): Promise { + private static getUUIDv4(): string { + // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16), + ); + } + + public async identifyUser(): Promise { if (this.anonymity == Anonymity.Pseudonymous) { - this.posthog.identify(await hashHex(userId)); + // Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows + // different devices to send the same ID. + const client = MatrixClientPeg.get(); + const accountData = await client.getAccountDataFromServer("im.vector.web.analytics_id"); + let analyticsID = accountData?.id; + if (!analyticsID) { + // Couldn't retrieve an analytics ID from user settings, so create one and set it on the server. + // Note there's a race condition here - if two devices do these steps at the same time, last write + // wins, and the first writer will send tracking with an ID that doesn't match the one on the server + // until the next time account data is refreshed and this function is called (most likely on next + // page load). This will happen pretty infrequently, so we can tolerate the possibility. + analyticsID = PosthogAnalytics.getUUIDv4(); + await client.setAccountData("im.vector.web.analytics_id", { id: analyticsID }); + } + this.posthog.identify(analyticsID); } } From 2c0835ed9bc5dce77a5851398cbbcfa2d720621d Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 14 Sep 2021 11:27:03 -0400 Subject: [PATCH 12/23] Add comments to message bubble magic numbers Signed-off-by: Robin Townsend --- res/css/views/rooms/_EventBubbleTile.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index d10353a663..262fb89bba 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -23,7 +23,6 @@ limitations under the License. } .mx_EventTile[data-layout=bubble] { - position: relative; margin-top: var(--gutterSize); margin-left: 49px; @@ -287,6 +286,7 @@ limitations under the License. .mx_EventTile_line, .mx_EventTile_info { min-width: 100%; + // Preserve alignment with left edge of text in bubbles margin: 0; } @@ -295,6 +295,7 @@ limitations under the License. } .mx_EventTile_line > a { + // Align timestamps with those of normal bubble tiles right: auto; top: -11px; left: -95px; @@ -329,6 +330,7 @@ limitations under the License. .mx_EventTile_line { margin: 0; > a { + // Align timestamps with those of normal bubble tiles left: -76px; } } @@ -339,6 +341,7 @@ limitations under the License. } .mx_EventListSummary[data-expanded=false][data-layout=bubble] { + // Align with left edge of bubble tiles padding: 0 49px; } From db25147a1991f084dc52fef9e2b410c55978c36d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 14 Sep 2021 17:49:18 +0200 Subject: [PATCH 13/23] Remove message_send_failed as it was unused MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/Resend.ts | 5 ----- src/components/structures/MatrixChat.tsx | 7 +------ 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Resend.ts b/src/Resend.ts index 38b84a28e0..be9fb9550b 100644 --- a/src/Resend.ts +++ b/src/Resend.ts @@ -48,11 +48,6 @@ export default class Resend { // XXX: temporary logging to try to diagnose // https://github.com/vector-im/element-web/issues/3148 console.log('Resend got send failure: ' + err.name + '(' + err + ')'); - - dis.dispatch({ - action: 'message_send_failed', - event: event, - }); }); } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 531dc9fbe9..0156d47d20 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1897,15 +1897,10 @@ export default class MatrixChat extends React.PureComponent { onSendEvent(roomId: string, event: MatrixEvent) { const cli = MatrixClientPeg.get(); - if (!cli) { - dis.dispatch({ action: 'message_send_failed' }); - return; - } + if (!cli) return; cli.sendEvent(roomId, event.getType(), event.getContent()).then(() => { dis.dispatch({ action: 'message_sent' }); - }, (err) => { - dis.dispatch({ action: 'message_send_failed' }); }); } From 7344a177e3fff83e0f49cb6772925cd5d9b316c1 Mon Sep 17 00:00:00 2001 From: James Salter Date: Tue, 14 Sep 2021 15:53:33 +0100 Subject: [PATCH 14/23] Fix tests, swallow errors --- src/PosthogAnalytics.ts | 34 ++++++++++++++++++++-------------- test/PosthogAnalytics-test.ts | 22 ++++++++++++++++++---- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index f01246ec00..e68cae5390 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -19,6 +19,7 @@ import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; import SettingsStore from './settings/SettingsStore'; import { MatrixClientPeg } from "./MatrixClientPeg"; +import { MatrixClient } from "../../matrix-js-sdk"; /* Posthog analytics tracking. * @@ -282,23 +283,28 @@ export class PosthogAnalytics { ); } - public async identifyUser(): Promise { + public async identifyUser(client: MatrixClientPeg, analyticsIdGenerator: () => string): Promise { if (this.anonymity == Anonymity.Pseudonymous) { // Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows // different devices to send the same ID. - const client = MatrixClientPeg.get(); - const accountData = await client.getAccountDataFromServer("im.vector.web.analytics_id"); - let analyticsID = accountData?.id; - if (!analyticsID) { - // Couldn't retrieve an analytics ID from user settings, so create one and set it on the server. - // Note there's a race condition here - if two devices do these steps at the same time, last write - // wins, and the first writer will send tracking with an ID that doesn't match the one on the server - // until the next time account data is refreshed and this function is called (most likely on next - // page load). This will happen pretty infrequently, so we can tolerate the possibility. - analyticsID = PosthogAnalytics.getUUIDv4(); - await client.setAccountData("im.vector.web.analytics_id", { id: analyticsID }); + try { + const accountData = await client.getAccountDataFromServer("im.vector.web.analytics_id"); + let analyticsID = accountData?.id; + if (!analyticsID) { + // Couldn't retrieve an analytics ID from user settings, so create one and set it on the server. + // Note there's a race condition here - if two devices do these steps at the same time, last write + // wins, and the first writer will send tracking with an ID that doesn't match the one on the server + // until the next time account data is refreshed and this function is called (most likely on next + // page load). This will happen pretty infrequently, so we can tolerate the possibility. + analyticsID = analyticsIdGenerator(); + await client.setAccountData("im.vector.web.analytics_id", { id: analyticsID }); + } + this.posthog.identify(analyticsID); + } catch (e) { + // The above could fail due to network requests, but not essential to starting the application, + // so swallow it. + console.log("Unable to identify user for tracking" + e.toString()); } - this.posthog.identify(analyticsID); } } @@ -371,7 +377,7 @@ export class PosthogAnalytics { // 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); + await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getUUIDv4); } } } diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts index 6cb1743051..10dec617fc 100644 --- a/test/PosthogAnalytics-test.ts +++ b/test/PosthogAnalytics-test.ts @@ -24,6 +24,7 @@ import { } from '../src/PosthogAnalytics'; import SdkConfig from '../src/SdkConfig'; +import { MatrixClientPeg } from "../src/MatrixClientPeg"; class FakePosthog { public capture; @@ -218,15 +219,28 @@ bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`); 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"); + class FakeClient { + getAccountDataFromServer = jest.fn().mockResolvedValue(null); + setAccountData = jest.fn().mockResolvedValue({}); + } + await analytics.identifyUser(new FakeClient(), () => "analytics_id" ); + expect(fakePosthog.identify.mock.calls[0][0]).toBe("analytics_id"); }); it("Should not identify the user to posthog if anonymous", async () => { analytics.setAnonymity(Anonymity.Anonymous); - await analytics.identifyUser("foo"); + await analytics.identifyUser(null); expect(fakePosthog.identify.mock.calls.length).toBe(0); }); + + it("Should identify using the server's analytics id if present", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + class FakeClient { + getAccountDataFromServer = jest.fn().mockResolvedValue({ id: "existing_analytics_id" }); + setAccountData = jest.fn().mockResolvedValue({}); + } + await analytics.identifyUser(new FakeClient(), () => "new_analytics_id" ); + expect(fakePosthog.identify.mock.calls[0][0]).toBe("existing_analytics_id"); + }); }); }); From 8824b120649d908fd8e17825d09b223792e9f964 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 14 Sep 2021 12:35:51 -0400 Subject: [PATCH 15/23] Fix alignment of sender name in message bubble replies Signed-off-by: Robin Townsend --- res/css/views/rooms/_EventBubbleTile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 262fb89bba..ee473c4af2 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -76,7 +76,7 @@ limitations under the License. max-width: 70%; } - .mx_SenderProfile { + > .mx_SenderProfile { position: relative; top: -2px; left: 2px; From 9b3da61ae4d79f598ad2583e5be67e4b67139f0d Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 14 Sep 2021 12:37:45 -0400 Subject: [PATCH 16/23] Remove unnecessary unset Signed-off-by: Robin Townsend --- res/css/views/rooms/_EventBubbleTile.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index ee473c4af2..1603777113 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -112,8 +112,6 @@ limitations under the License. .mx_ReplyTile .mx_SenderProfile { display: block; - top: unset; - left: unset; } .mx_ReactionsRow { From 6c1dea09e8b9b6d0370090a06408a34f34771281 Mon Sep 17 00:00:00 2001 From: James Salter Date: Tue, 14 Sep 2021 17:46:56 +0100 Subject: [PATCH 17/23] lint --- src/PosthogAnalytics.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index e68cae5390..d026db1303 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -18,8 +18,7 @@ import posthog, { PostHog } from 'posthog-js'; import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; import SettingsStore from './settings/SettingsStore'; -import { MatrixClientPeg } from "./MatrixClientPeg"; -import { MatrixClient } from "../../matrix-js-sdk"; +import { IMatrixClientPeg, MatrixClientPeg } from "./MatrixClientPeg"; /* Posthog analytics tracking. * @@ -278,12 +277,12 @@ export class PosthogAnalytics { private static getUUIDv4(): string { // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid - return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => + return ("10000000-1000-4000-8000-100000000000").replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16), ); } - public async identifyUser(client: MatrixClientPeg, analyticsIdGenerator: () => string): Promise { + public async identifyUser(client: IMatrixClientPeg, analyticsIdGenerator: () => string): Promise { if (this.anonymity == Anonymity.Pseudonymous) { // Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows // different devices to send the same ID. From c2192a78bce9ae9f328087bb566c98839643e533 Mon Sep 17 00:00:00 2001 From: James Salter Date: Tue, 14 Sep 2021 18:16:48 +0100 Subject: [PATCH 18/23] More lint --- src/PosthogAnalytics.ts | 14 ++++++-------- test/PosthogAnalytics-test.ts | 1 - 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index d026db1303..422fcc475f 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -18,7 +18,8 @@ import posthog, { PostHog } from 'posthog-js'; import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; import SettingsStore from './settings/SettingsStore'; -import { IMatrixClientPeg, MatrixClientPeg } from "./MatrixClientPeg"; +import { MatrixClientPeg } from "./MatrixClientPeg"; +import { MatrixClient } from "../../matrix-js-sdk"; /* Posthog analytics tracking. * @@ -275,14 +276,11 @@ export class PosthogAnalytics { this.anonymity = anonymity; } - private static getUUIDv4(): string { - // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid - return ("10000000-1000-4000-8000-100000000000").replace(/[018]/g, c => - (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16), - ); + private static getRandomAnalyticsId(): string { + return [...crypto.getRandomValues(new Uint8Array(16))].map((c) => c.toString(16)).join(''); } - public async identifyUser(client: IMatrixClientPeg, analyticsIdGenerator: () => string): Promise { + public async identifyUser(client: MatrixClient, analyticsIdGenerator: () => string): Promise { if (this.anonymity == Anonymity.Pseudonymous) { // Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows // different devices to send the same ID. @@ -376,7 +374,7 @@ export class PosthogAnalytics { // 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(MatrixClientPeg.get(), PosthogAnalytics.getUUIDv4); + await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getRandomAnalyticsId); } } } diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts index 10dec617fc..2832fbe92e 100644 --- a/test/PosthogAnalytics-test.ts +++ b/test/PosthogAnalytics-test.ts @@ -24,7 +24,6 @@ import { } from '../src/PosthogAnalytics'; import SdkConfig from '../src/SdkConfig'; -import { MatrixClientPeg } from "../src/MatrixClientPeg"; class FakePosthog { public capture; From 5baaa6b77e7db8318b802ebc169d994a82b7e97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 14 Sep 2021 18:53:31 +0200 Subject: [PATCH 19/23] Convert MemberStatusMessageAvatar to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- ...vatar.js => MemberStatusMessageAvatar.tsx} | 59 ++++++++++--------- .../views/rooms/MessageComposer.tsx | 2 +- 2 files changed, 33 insertions(+), 28 deletions(-) rename src/components/views/avatars/{MemberStatusMessageAvatar.js => MemberStatusMessageAvatar.tsx} (76%) diff --git a/src/components/views/avatars/MemberStatusMessageAvatar.js b/src/components/views/avatars/MemberStatusMessageAvatar.tsx similarity index 76% rename from src/components/views/avatars/MemberStatusMessageAvatar.js rename to src/components/views/avatars/MemberStatusMessageAvatar.tsx index 82b7b8e400..f7965a635e 100644 --- a/src/components/views/avatars/MemberStatusMessageAvatar.js +++ b/src/components/views/avatars/MemberStatusMessageAvatar.tsx @@ -15,43 +15,48 @@ limitations under the License. */ import React, { createRef } from 'react'; -import PropTypes from 'prop-types'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from "../../../languageHandler"; import MemberAvatar from '../avatars/MemberAvatar'; import classNames from 'classnames'; import StatusMessageContextMenu from "../context_menus/StatusMessageContextMenu"; import SettingsStore from "../../../settings/SettingsStore"; -import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; +import { ChevronFace, ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { ResizeMethod } from "matrix-js-sdk/src/@types/partials"; + +interface IProps { + member: RoomMember; + width?: number; + height?: number; + resizeMethod?: ResizeMethod; +} + +interface IState { + hasStatus: boolean; + menuDisplayed: boolean; +} @replaceableComponent("views.avatars.MemberStatusMessageAvatar") -export default class MemberStatusMessageAvatar extends React.Component { - static propTypes = { - member: PropTypes.object.isRequired, - width: PropTypes.number, - height: PropTypes.number, - resizeMethod: PropTypes.string, - }; - - static defaultProps = { +export default class MemberStatusMessageAvatar extends React.Component { + public static defaultProps: Partial = { width: 40, height: 40, resizeMethod: 'crop', }; + private button = createRef(); - constructor(props) { + constructor(props: IProps) { super(props); this.state = { hasStatus: this.hasStatus, menuDisplayed: false, }; - - this._button = createRef(); } - componentDidMount() { + public componentDidMount(): void { if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) { throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user"); } @@ -62,44 +67,44 @@ export default class MemberStatusMessageAvatar extends React.Component { if (!user) { return; } - user.on("User._unstable_statusMessage", this._onStatusMessageCommitted); + user.on("User._unstable_statusMessage", this.onStatusMessageCommitted); } - componentWillUnmount() { + public componentWillUnmount(): void { const { user } = this.props.member; if (!user) { return; } user.removeListener( "User._unstable_statusMessage", - this._onStatusMessageCommitted, + this.onStatusMessageCommitted, ); } - get hasStatus() { + private get hasStatus(): boolean { const { user } = this.props.member; if (!user) { return false; } - return !!user._unstable_statusMessage; + return !!user.unstable_statusMessage; } - _onStatusMessageCommitted = () => { + private onStatusMessageCommitted = (): void => { // The `User` object has observed a status message change. this.setState({ hasStatus: this.hasStatus, }); }; - openMenu = () => { + private openMenu = (): void => { this.setState({ menuDisplayed: true }); }; - closeMenu = () => { + private closeMenu = (): void => { this.setState({ menuDisplayed: false }); }; - render() { + public render(): JSX.Element { const avatar = Date: Tue, 14 Sep 2021 18:58:20 +0200 Subject: [PATCH 20/23] Convert GenericElementContextMenu to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- ...tMenu.js => GenericElementContextMenu.tsx} | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) rename src/components/views/context_menus/{GenericElementContextMenu.js => GenericElementContextMenu.tsx} (67%) diff --git a/src/components/views/context_menus/GenericElementContextMenu.js b/src/components/views/context_menus/GenericElementContextMenu.tsx similarity index 67% rename from src/components/views/context_menus/GenericElementContextMenu.js rename to src/components/views/context_menus/GenericElementContextMenu.tsx index 87d44ef0d3..a0a8c89b37 100644 --- a/src/components/views/context_menus/GenericElementContextMenu.js +++ b/src/components/views/context_menus/GenericElementContextMenu.tsx @@ -15,45 +15,41 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -/* +interface IProps { + element: React.ReactNode; + // Function to be called when the parent window is resized + // This can be used to reposition or close the menu on resize and + // ensure that it is not displayed in a stale position. + onResize?: () => void; +} + +/** * This component can be used to display generic HTML content in a contextual * menu. */ - @replaceableComponent("views.context_menus.GenericElementContextMenu") -export default class GenericElementContextMenu extends React.Component { - static propTypes = { - element: PropTypes.element.isRequired, - // Function to be called when the parent window is resized - // This can be used to reposition or close the menu on resize and - // ensure that it is not displayed in a stale position. - onResize: PropTypes.func, - }; - - constructor(props) { +export default class GenericElementContextMenu extends React.Component { + constructor(props: IProps) { super(props); - this.resize = this.resize.bind(this); } - componentDidMount() { - this.resize = this.resize.bind(this); + public componentDidMount(): void { window.addEventListener("resize", this.resize); } - componentWillUnmount() { + public componentWillUnmount(): void { window.removeEventListener("resize", this.resize); } - resize() { + private resize = (): void => { if (this.props.onResize) { this.props.onResize(); } - } + }; - render() { + public render(): JSX.Element { return
{ this.props.element }
; } } From 11e61075b4a8407b309c2c48439dc64954d7c6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 14 Sep 2021 18:59:54 +0200 Subject: [PATCH 21/23] Convert GenericTextContextMenu to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- ...extContextMenu.js => GenericTextContextMenu.tsx} | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) rename src/components/views/context_menus/{GenericTextContextMenu.js => GenericTextContextMenu.tsx} (86%) diff --git a/src/components/views/context_menus/GenericTextContextMenu.js b/src/components/views/context_menus/GenericTextContextMenu.tsx similarity index 86% rename from src/components/views/context_menus/GenericTextContextMenu.js rename to src/components/views/context_menus/GenericTextContextMenu.tsx index 474732e88b..3ca158dd02 100644 --- a/src/components/views/context_menus/GenericTextContextMenu.js +++ b/src/components/views/context_menus/GenericTextContextMenu.tsx @@ -15,16 +15,15 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -@replaceableComponent("views.context_menus.GenericTextContextMenu") -export default class GenericTextContextMenu extends React.Component { - static propTypes = { - message: PropTypes.string.isRequired, - }; +interface IProps { + message: string; +} - render() { +@replaceableComponent("views.context_menus.GenericTextContextMenu") +export default class GenericTextContextMenu extends React.Component { + public render(): JSX.Element { return
{ this.props.message }
; } } From 0f55fde03a9d8c8764bf97106b55bb955e70e795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 14 Sep 2021 19:09:24 +0200 Subject: [PATCH 22/23] Convert StatusMessageContextMenu to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../avatars/MemberStatusMessageAvatar.tsx | 2 +- ...xtMenu.js => StatusMessageContextMenu.tsx} | 68 ++++++++++--------- 2 files changed, 37 insertions(+), 33 deletions(-) rename src/components/views/context_menus/{StatusMessageContextMenu.js => StatusMessageContextMenu.tsx} (71%) diff --git a/src/components/views/avatars/MemberStatusMessageAvatar.tsx b/src/components/views/avatars/MemberStatusMessageAvatar.tsx index f7965a635e..8c703b3b32 100644 --- a/src/components/views/avatars/MemberStatusMessageAvatar.tsx +++ b/src/components/views/avatars/MemberStatusMessageAvatar.tsx @@ -137,7 +137,7 @@ export default class MemberStatusMessageAvatar extends React.Component - + ); } diff --git a/src/components/views/context_menus/StatusMessageContextMenu.js b/src/components/views/context_menus/StatusMessageContextMenu.tsx similarity index 71% rename from src/components/views/context_menus/StatusMessageContextMenu.js rename to src/components/views/context_menus/StatusMessageContextMenu.tsx index e05b05116c..954dc3f5c0 100644 --- a/src/components/views/context_menus/StatusMessageContextMenu.js +++ b/src/components/views/context_menus/StatusMessageContextMenu.tsx @@ -14,53 +14,59 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { ChangeEvent } from 'react'; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; -import * as sdk from '../../../index'; -import AccessibleButton from '../elements/AccessibleButton'; +import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { User } from "matrix-js-sdk/src/models/user"; +import Spinner from "../elements/Spinner"; + +interface IProps { + // js-sdk User object. Not required because it might not exist. + user?: User; +} + +interface IState { + message: string; + waiting: boolean; +} @replaceableComponent("views.context_menus.StatusMessageContextMenu") -export default class StatusMessageContextMenu extends React.Component { - static propTypes = { - // js-sdk User object. Not required because it might not exist. - user: PropTypes.object, - }; - - constructor(props) { +export default class StatusMessageContextMenu extends React.Component { + constructor(props: IProps) { super(props); this.state = { message: this.comittedStatusMessage, + waiting: false, }; } - componentDidMount() { + public componentDidMount(): void { const { user } = this.props; if (!user) { return; } - user.on("User._unstable_statusMessage", this._onStatusMessageCommitted); + user.on("User._unstable_statusMessage", this.onStatusMessageCommitted); } - componentWillUnmount() { + public componentWillUnmount(): void { const { user } = this.props; if (!user) { return; } user.removeListener( "User._unstable_statusMessage", - this._onStatusMessageCommitted, + this.onStatusMessageCommitted, ); } - get comittedStatusMessage() { - return this.props.user ? this.props.user._unstable_statusMessage : ""; + get comittedStatusMessage(): string { + return this.props.user ? this.props.user.unstable_statusMessage : ""; } - _onStatusMessageCommitted = () => { + private onStatusMessageCommitted = (): void => { // The `User` object has observed a status message change. this.setState({ message: this.comittedStatusMessage, @@ -68,14 +74,14 @@ export default class StatusMessageContextMenu extends React.Component { }); }; - _onClearClick = (e) => { + private onClearClick = (): void=> { MatrixClientPeg.get()._unstable_setStatusMessage(""); this.setState({ waiting: true, }); }; - _onSubmit = (e) => { + private onSubmit = (e: ButtonEvent): void => { e.preventDefault(); MatrixClientPeg.get()._unstable_setStatusMessage(this.state.message); this.setState({ @@ -83,27 +89,25 @@ export default class StatusMessageContextMenu extends React.Component { }); }; - _onStatusChange = (e) => { + private onStatusChange = (e: ChangeEvent): void => { // The input field's value was changed. this.setState({ - message: e.target.value, + message: (e.target as HTMLInputElement).value, }); }; - render() { - const Spinner = sdk.getComponent('views.elements.Spinner'); - + public render(): JSX.Element { let actionButton; if (this.comittedStatusMessage) { if (this.state.message === this.comittedStatusMessage) { actionButton = { _t("Clear status") } ; } else { actionButton = { _t("Update status") } ; @@ -112,7 +116,7 @@ export default class StatusMessageContextMenu extends React.Component { actionButton = { _t("Set status") } ; @@ -120,13 +124,13 @@ export default class StatusMessageContextMenu extends React.Component { let spinner = null; if (this.state.waiting) { - spinner = ; + spinner = ; } const form =
{ actionButton } From 48fbbf2f444f9bd0069e47896945ed9182d93768 Mon Sep 17 00:00:00 2001 From: James Salter Date: Wed, 15 Sep 2021 09:48:48 +0100 Subject: [PATCH 23/23] Fix import, convert event type to constant --- src/PosthogAnalytics.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index 422fcc475f..ca0d321e7c 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -19,7 +19,7 @@ import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; import SettingsStore from './settings/SettingsStore'; import { MatrixClientPeg } from "./MatrixClientPeg"; -import { MatrixClient } from "../../matrix-js-sdk"; +import { MatrixClient } from "matrix-js-sdk/src/client"; /* Posthog analytics tracking. * @@ -143,6 +143,7 @@ export class PosthogAnalytics { private enabled = false; private static _instance = null; private platformSuperProperties = {}; + private static ANALYTICS_ID_EVENT_TYPE = "im.vector.web.analytics_id"; public static get instance(): PosthogAnalytics { if (!this._instance) { @@ -285,7 +286,7 @@ export class PosthogAnalytics { // Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows // different devices to send the same ID. try { - const accountData = await client.getAccountDataFromServer("im.vector.web.analytics_id"); + const accountData = await client.getAccountDataFromServer(PosthogAnalytics.ANALYTICS_ID_EVENT_TYPE); let analyticsID = accountData?.id; if (!analyticsID) { // Couldn't retrieve an analytics ID from user settings, so create one and set it on the server.