From 111d4adab2b87c5ed019b4aff6bd7df85c61f1b1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 22:38:11 +0100 Subject: [PATCH 01/21] Convert createRoom over to typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{createRoom.js => createRoom.ts} | 68 +++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 7 deletions(-) rename src/{createRoom.js => createRoom.ts} (81%) diff --git a/src/createRoom.js b/src/createRoom.ts similarity index 81% rename from src/createRoom.js rename to src/createRoom.ts index affdf196a7..b863450065 100644 --- a/src/createRoom.js +++ b/src/createRoom.ts @@ -15,6 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {MatrixClient} from "matrix-js-sdk/src/client"; +import {Room} from "matrix-js-sdk/src/models/Room"; + import {MatrixClientPeg} from './MatrixClientPeg'; import Modal from './Modal'; import * as sdk from './index'; @@ -26,6 +29,56 @@ import {getAddressType} from "./UserAddress"; const E2EE_WK_KEY = "im.vector.riot.e2ee"; +// TODO move these interfaces over to js-sdk once it has been typescripted enough to accept them +enum Visibility { + Public = "public", + Private = "private", +} + +enum Preset { + PrivateChat = "private_chat", + TrustedPrivateChat = "trusted_private_chat", + PublicChat = "public_chat", +} + +interface Invite3PID { + id_server: string; + id_access_token?: string; // this gets injected by the js-sdk + medium: string; + address: string; +} + +interface IStateEvent { + type: string; + state_key?: string; // defaults to an empty string + content: object; +} + +interface ICreateOpts { + visibility?: Visibility; + room_alias_name?: string; + name?: string; + topic?: string; + invite?: string[]; + invite_3pid?: Invite3PID[]; + room_version?: string; + creation_content?: object; + initial_state?: IStateEvent[]; + preset?: Preset; + is_direct?: boolean; + power_level_content_override?: object; +} + +interface IOpts { + dmUserId?: string; + createOpts?: ICreateOpts; + spinner?: boolean; + guestAccess?: boolean; + encryption?: boolean; + inlineErrors?: boolean; + andView?: boolean; +} + /** * Create a new room, and switch to it. * @@ -40,11 +93,12 @@ const E2EE_WK_KEY = "im.vector.riot.e2ee"; * Default: False * @param {bool=} opts.inlineErrors True to raise errors off the promise instead of resolving to null. * Default: False + * @param {bool=} opts.andView True to dispatch an action to view the room once it has been created. * * @returns {Promise} which resolves to the room id, or null if the * action was aborted or failed. */ -export default function createRoom(opts) { +export default function createRoom(opts: IOpts): Promise { opts = opts || {}; if (opts.spinner === undefined) opts.spinner = true; if (opts.guestAccess === undefined) opts.guestAccess = true; @@ -59,12 +113,12 @@ export default function createRoom(opts) { return Promise.resolve(null); } - const defaultPreset = opts.dmUserId ? 'trusted_private_chat' : 'private_chat'; + const defaultPreset = opts.dmUserId ? Preset.TrustedPrivateChat : Preset.PrivateChat; // set some defaults for the creation const createOpts = opts.createOpts || {}; createOpts.preset = createOpts.preset || defaultPreset; - createOpts.visibility = createOpts.visibility || 'private'; + createOpts.visibility = createOpts.visibility || Visibility.Private; if (opts.dmUserId && createOpts.invite === undefined) { switch (getAddressType(opts.dmUserId)) { case 'mx-user-id': @@ -166,7 +220,7 @@ export default function createRoom(opts) { }); } -export function findDMForUser(client, userId) { +export function findDMForUser(client: MatrixClient, userId: string): Room { const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId); const rooms = roomIds.map(id => client.getRoom(id)); const suitableDMRooms = rooms.filter(r => { @@ -189,7 +243,7 @@ export function findDMForUser(client, userId) { * NOTE: this assumes you've just created the room and there's not been an opportunity * for other code to run, so we shouldn't miss RoomState.newMember when it comes by. */ -export async function _waitForMember(client, roomId, userId, opts = { timeout: 1500 }) { +export async function _waitForMember(client: MatrixClient, roomId: string, userId: string, opts = { timeout: 1500 }) { const { timeout } = opts; let handler; return new Promise((resolve) => { @@ -212,7 +266,7 @@ export async function _waitForMember(client, roomId, userId, opts = { timeout: 1 * Ensure that for every user in a room, there is at least one device that we * can encrypt to. */ -export async function canEncryptToAllUsers(client, userIds) { +export async function canEncryptToAllUsers(client: MatrixClient, userIds: string[]) { const usersDeviceMap = await client.downloadKeys(userIds); // { "@user:host": { "DEVICE": {...}, ... }, ... } return Object.values(usersDeviceMap).every((userDevices) => @@ -221,7 +275,7 @@ export async function canEncryptToAllUsers(client, userIds) { ); } -export async function ensureDMExists(client, userId) { +export async function ensureDMExists(client: MatrixClient, userId: string): Promise { const existingDMRoom = findDMForUser(client, userId); let roomId; if (existingDMRoom) { From 97711032d8bb94848fabe959edc3ccf7daff2df9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 22:38:21 +0100 Subject: [PATCH 02/21] Fix signature of sleep utility Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/utils/promise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/promise.ts b/src/utils/promise.ts index c5c1cb9a56..d3ae2c3d1b 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -15,7 +15,7 @@ limitations under the License. */ // Returns a promise which resolves with a given value after the given number of ms -export function sleep(ms: number, value: T): Promise { +export function sleep(ms: number, value?: T): Promise { return new Promise((resolve => { setTimeout(resolve, ms, value); })); } From 7322aaf6023784ae2bb1dbd556a911611bcd0af6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 22:42:28 +0100 Subject: [PATCH 03/21] Convert PlatformPeg to Typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/@types/global.d.ts | 2 ++ src/{PlatformPeg.js => PlatformPeg.ts} | 15 ++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) rename src/{PlatformPeg.js => PlatformPeg.ts} (82%) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index ffd3277892..8486016cd0 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -20,6 +20,7 @@ import { IMatrixClientPeg } from "../MatrixClientPeg"; import ToastStore from "../stores/ToastStore"; import DeviceListener from "../DeviceListener"; import { RoomListStore2 } from "../stores/room-list/RoomListStore2"; +import { PlatformPeg } from "../PlatformPeg"; declare global { interface Window { @@ -33,6 +34,7 @@ declare global { mx_ToastStore: ToastStore; mx_DeviceListener: DeviceListener; mx_RoomListStore2: RoomListStore2; + mxPlatformPeg: PlatformPeg; } // workaround for https://github.com/microsoft/TypeScript/issues/30933 diff --git a/src/PlatformPeg.js b/src/PlatformPeg.ts similarity index 82% rename from src/PlatformPeg.js rename to src/PlatformPeg.ts index 34131fde7d..42cb7acaf7 100644 --- a/src/PlatformPeg.js +++ b/src/PlatformPeg.ts @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import BasePlatform from "./BasePlatform"; + /* * Holds the current Platform object used by the code to do anything * specific to the platform we're running on (eg. web, electron) @@ -21,10 +24,8 @@ limitations under the License. * This allows the app layer to set a Platform without necessarily * having to have a MatrixChat object */ -class PlatformPeg { - constructor() { - this.platform = null; - } +export class PlatformPeg { + platform: BasePlatform = null; /** * Returns the current Platform object for the application. @@ -44,7 +45,7 @@ class PlatformPeg { } } -if (!global.mxPlatformPeg) { - global.mxPlatformPeg = new PlatformPeg(); +if (!window.mxPlatformPeg) { + window.mxPlatformPeg = new PlatformPeg(); } -export default global.mxPlatformPeg; +export default window.mxPlatformPeg; From 1ab0a1a1defc1c75c54302ef5ac51c4b71d5ddf0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:14:31 +0100 Subject: [PATCH 04/21] First step towards a11y in the new room list Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.ts | 4 ++++ src/PlatformPeg.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 1d11495e61..acf72a986c 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -53,6 +53,10 @@ export default abstract class BasePlatform { this.startUpdateCheck = this.startUpdateCheck.bind(this); } + abstract async getConfig(): Promise<{}>; + + abstract getDefaultDeviceDisplayName(): string; + protected onAction = (payload: ActionPayload) => { switch (payload.action) { case 'on_client_not_viable': diff --git a/src/PlatformPeg.ts b/src/PlatformPeg.ts index 42cb7acaf7..1d2b813ebc 100644 --- a/src/PlatformPeg.ts +++ b/src/PlatformPeg.ts @@ -40,7 +40,7 @@ export class PlatformPeg { * application. * This should be an instance of a class extending BasePlatform. */ - set(plaf) { + set(plaf: BasePlatform) { this.platform = plaf; } } From 48ce294a497211eff1405b47ca1fc2219857b0db Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:15:08 +0100 Subject: [PATCH 05/21] Transition languageHandler to Typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- package.json | 1 + src/@types/global.d.ts | 4 ++ src/components/views/toasts/GenericToast.tsx | 4 +- ...languageHandler.js => languageHandler.tsx} | 72 ++++++++++++------- yarn.lock | 5 ++ 5 files changed, 57 insertions(+), 29 deletions(-) rename src/{languageHandler.js => languageHandler.tsx} (87%) diff --git a/package.json b/package.json index e8719520c4..a79a0467ff 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "@babel/register": "^7.7.4", "@peculiar/webcrypto": "^1.0.22", "@types/classnames": "^2.2.10", + "@types/counterpart": "^0.18.1", "@types/flux": "^3.1.9", "@types/lodash": "^4.14.152", "@types/modernizr": "^3.5.3", diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 8486016cd0..2c2fec759c 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -47,6 +47,10 @@ declare global { hasStorageAccess?: () => Promise; } + interface Navigator { + userLanguage?: string; + } + interface StorageEstimate { usageDetails?: {[key: string]: number}; } diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx index 9f8885ba47..6cd881b9eb 100644 --- a/src/components/views/toasts/GenericToast.tsx +++ b/src/components/views/toasts/GenericToast.tsx @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {ReactChild} from "react"; +import React, {ReactNode} from "react"; import FormButton from "../elements/FormButton"; import {XOR} from "../../../@types/common"; export interface IProps { - description: ReactChild; + description: ReactNode; acceptLabel: string; onAccept(); diff --git a/src/languageHandler.js b/src/languageHandler.tsx similarity index 87% rename from src/languageHandler.js rename to src/languageHandler.tsx index 79a172015a..91d90d4e6c 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.tsx @@ -1,7 +1,7 @@ /* Copyright 2017 MTRNord and Cooperative EITA Copyright 2017 Vector Creations Ltd. -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +20,11 @@ limitations under the License. import request from 'browser-request'; import counterpart from 'counterpart'; import React from 'react'; + import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; import PlatformPeg from "./PlatformPeg"; -// $webapp is a webpack resolve alias pointing to the output directory, see webpack config +// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config import webpackLangJsonUrl from "$webapp/i18n/languages.json"; const i18nFolder = 'i18n/'; @@ -37,27 +38,31 @@ counterpart.setSeparator('|'); // Fall back to English counterpart.setFallbackLocale('en'); +interface ITranslatableError extends Error { + translatedMessage: string; +} + /** * Helper function to create an error which has an English message * with a translatedMessage property for use by the consumer. * @param {string} message Message to translate. * @returns {Error} The constructed error. */ -export function newTranslatableError(message) { - const error = new Error(message); +export function newTranslatableError(message: string) { + const error = new Error(message) as ITranslatableError; error.translatedMessage = _t(message); return error; } // Function which only purpose is to mark that a string is translatable // Does not actually do anything. It's helpful for automatic extraction of translatable strings -export function _td(s) { +export function _td(s: string): string { return s; } // Wrapper for counterpart's translation function so that it handles nulls and undefineds properly // Takes the same arguments as counterpart.translate() -function safeCounterpartTranslate(text, options) { +function safeCounterpartTranslate(text: string, options?: object) { // Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191 // The interpolation library that counterpart uses does not support undefined/null // values and instead will throw an error. This is a problem since everywhere else @@ -89,6 +94,13 @@ function safeCounterpartTranslate(text, options) { return translated; } +interface IVariables { + count?: number; + [key: string]: number | string; +} + +type Tags = Record React.ReactNode>; + /* * Translates text and optionally also replaces XML-ish elements in the text with e.g. React components * @param {string} text The untranslated text, e.g "click here now to %(foo)s". @@ -105,7 +117,9 @@ function safeCounterpartTranslate(text, options) { * * @return a React component if any non-strings were used in substitutions, otherwise a string */ -export function _t(text, variables, tags) { +export function _t(text: string, variables?: IVariables): string; +export function _t(text: string, variables: IVariables, tags: Tags): React.ReactNode; +export function _t(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode { // Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components // However, still pass the variables to counterpart so that it can choose the correct plural if count is given // It is enough to pass the count variable, but in the future counterpart might make use of other information too @@ -141,23 +155,25 @@ export function _t(text, variables, tags) { * * @return a React component if any non-strings were used in substitutions, otherwise a string */ -export function substitute(text, variables, tags) { - let result = text; +export function substitute(text: string, variables?: IVariables): string; +export function substitute(text: string, variables: IVariables, tags: Tags): string; +export function substitute(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode { + let result: React.ReactNode | string = text; if (variables !== undefined) { - const regexpMapping = {}; + const regexpMapping: IVariables = {}; for (const variable in variables) { regexpMapping[`%\\(${variable}\\)s`] = variables[variable]; } - result = replaceByRegexes(result, regexpMapping); + result = replaceByRegexes(result as string, regexpMapping); } if (tags !== undefined) { - const regexpMapping = {}; + const regexpMapping: Tags = {}; for (const tag in tags) { regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag]; } - result = replaceByRegexes(result, regexpMapping); + result = replaceByRegexes(result as string, regexpMapping); } return result; @@ -172,7 +188,9 @@ export function substitute(text, variables, tags) { * * @return a React component if any non-strings were used in substitutions, otherwise a string */ -export function replaceByRegexes(text, mapping) { +export function replaceByRegexes(text: string, mapping: IVariables): string; +export function replaceByRegexes(text: string, mapping: Tags): React.ReactNode; +export function replaceByRegexes(text: string, mapping: IVariables | Tags): string | React.ReactNode { // We initially store our output as an array of strings and objects (e.g. React components). // This will then be converted to a string or a at the end const output = [text]; @@ -189,7 +207,7 @@ export function replaceByRegexes(text, mapping) { // and everything after the match. Insert all three into the output. We need to do this because we can insert objects. // Otherwise there would be no need for the splitting and we could do simple replacement. let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it - for (const outputIndex in output) { + for (let outputIndex = 0; outputIndex < output.length; outputIndex++) { const inputText = output[outputIndex]; if (typeof inputText !== 'string') { // We might have inserted objects earlier, don't try to replace them continue; @@ -216,7 +234,7 @@ export function replaceByRegexes(text, mapping) { let replaced; // If substitution is a function, call it if (mapping[regexpString] instanceof Function) { - replaced = mapping[regexpString].apply(null, capturedGroups); + replaced = (mapping as Tags)[regexpString].apply(null, capturedGroups); } else { replaced = mapping[regexpString]; } @@ -277,11 +295,11 @@ export function replaceByRegexes(text, mapping) { // Allow overriding the text displayed when no translation exists // Currently only used in unit tests to avoid having to load // the translations in riot-web -export function setMissingEntryGenerator(f) { +export function setMissingEntryGenerator(f: (value: string) => void) { counterpart.setMissingEntryGenerator(f); } -export function setLanguage(preferredLangs) { +export function setLanguage(preferredLangs: string | string[]) { if (!Array.isArray(preferredLangs)) { preferredLangs = [preferredLangs]; } @@ -358,8 +376,8 @@ export function getLanguageFromBrowser() { * @param {string} language The input language string * @return {string[]} List of normalised languages */ -export function getNormalizedLanguageKeys(language) { - const languageKeys = []; +export function getNormalizedLanguageKeys(language: string) { + const languageKeys: string[] = []; const normalizedLanguage = normalizeLanguageKey(language); const languageParts = normalizedLanguage.split('-'); if (languageParts.length === 2 && languageParts[0] === languageParts[1]) { @@ -380,7 +398,7 @@ export function getNormalizedLanguageKeys(language) { * @param {string} language The language string to be normalized * @returns {string} The normalized language string */ -export function normalizeLanguageKey(language) { +export function normalizeLanguageKey(language: string) { return language.toLowerCase().replace("_", "-"); } @@ -396,7 +414,7 @@ export function getCurrentLanguage() { * @param {string[]} langs List of language codes to pick from * @returns {string} The most appropriate language code from langs */ -export function pickBestLanguage(langs) { +export function pickBestLanguage(langs: string[]): string { const currentLang = getCurrentLanguage(); const normalisedLangs = langs.map(normalizeLanguageKey); @@ -408,13 +426,13 @@ export function pickBestLanguage(langs) { { // Failing that, a different dialect of the same language - const closeLangIndex = normalisedLangs.find((l) => l.substr(0, 2) === currentLang.substr(0, 2)); + const closeLangIndex = normalisedLangs.findIndex((l) => l.substr(0, 2) === currentLang.substr(0, 2)); if (closeLangIndex > -1) return langs[closeLangIndex]; } { // Neither of those? Try an english variant. - const enIndex = normalisedLangs.find((l) => l.startsWith('en')); + const enIndex = normalisedLangs.findIndex((l) => l.startsWith('en')); if (enIndex > -1) return langs[enIndex]; } @@ -422,7 +440,7 @@ export function pickBestLanguage(langs) { return langs[0]; } -function getLangsJson() { +function getLangsJson(): Promise { return new Promise(async (resolve, reject) => { let url; if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through @@ -443,7 +461,7 @@ function getLangsJson() { }); } -function weblateToCounterpart(inTrs) { +function weblateToCounterpart(inTrs: object): object { const outTrs = {}; for (const key of Object.keys(inTrs)) { @@ -463,7 +481,7 @@ function weblateToCounterpart(inTrs) { return outTrs; } -function getLanguage(langPath) { +function getLanguage(langPath: string): object { return new Promise((resolve, reject) => { request( { method: "GET", url: langPath }, diff --git a/yarn.lock b/yarn.lock index c20658f014..b51f7af45b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1257,6 +1257,11 @@ resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== +"@types/counterpart@^0.18.1": + version "0.18.1" + resolved "https://registry.yarnpkg.com/@types/counterpart/-/counterpart-0.18.1.tgz#b1b784d9e54d9879f0a8cb12f2caedab65430fe8" + integrity sha512-PRuFlBBkvdDOtxlIASzTmkEFar+S66Ek48NVVTWMUjtJAdn5vyMSN8y6IZIoIymGpR36q2nZbIYazBWyFxL+IQ== + "@types/fbemitter@*": version "2.0.32" resolved "https://registry.yarnpkg.com/@types/fbemitter/-/fbemitter-2.0.32.tgz#8ed204da0f54e9c8eaec31b1eec91e25132d082c" From f69a090d3d438eca7ec09c7da9b2ac2719205fd4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:22:36 +0100 Subject: [PATCH 06/21] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/createRoom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/createRoom.ts b/src/createRoom.ts index b863450065..c436196c27 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -16,7 +16,7 @@ limitations under the License. */ import {MatrixClient} from "matrix-js-sdk/src/client"; -import {Room} from "matrix-js-sdk/src/models/Room"; +import {Room} from "matrix-js-sdk/src/models/room"; import {MatrixClientPeg} from './MatrixClientPeg'; import Modal from './Modal'; From 33612398bed6383501bb1c2327f5fdc41a35c11b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:26:39 +0100 Subject: [PATCH 07/21] fix import Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/groups.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/groups.js b/src/groups.js index 860cf71fff..e73af15c79 100644 --- a/src/groups.js +++ b/src/groups.js @@ -15,7 +15,8 @@ limitations under the License. */ import PropTypes from 'prop-types'; -import { _t } from './languageHandler.js'; + +import { _t } from './languageHandler'; export const GroupMemberType = PropTypes.shape({ userId: PropTypes.string.isRequired, From 96cfd26bd42ea1437e4cc4486e1338ce45becc59 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:32:21 +0100 Subject: [PATCH 08/21] fix imports some more Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/elements/EditableItemList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 50d5a3d10f..34e53906a2 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -16,7 +16,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from '../../../languageHandler.js'; +import {_t} from '../../../languageHandler'; import Field from "./Field"; import AccessibleButton from "./AccessibleButton"; From d725cc338924421c2ef5d2f231cad699a8298a6c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:39:27 +0100 Subject: [PATCH 09/21] convert MatrixClientContext to Typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../{MatrixClientContext.js => MatrixClientContext.ts} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename src/contexts/{MatrixClientContext.js => MatrixClientContext.ts} (85%) diff --git a/src/contexts/MatrixClientContext.js b/src/contexts/MatrixClientContext.ts similarity index 85% rename from src/contexts/MatrixClientContext.js rename to src/contexts/MatrixClientContext.ts index 54a23ca132..7e8a92064d 100644 --- a/src/contexts/MatrixClientContext.js +++ b/src/contexts/MatrixClientContext.ts @@ -15,7 +15,8 @@ limitations under the License. */ import { createContext } from "react"; +import { MatrixClient } from "matrix-js-sdk/src/client"; -const MatrixClientContext = createContext(undefined); +const MatrixClientContext = createContext(undefined); MatrixClientContext.displayName = "MatrixClientContext"; export default MatrixClientContext; From 8233dec72e2ed69df381a38421e639633886aa96 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 6 Jul 2020 21:05:06 +0100 Subject: [PATCH 10/21] Fix some room list sticky header instabilities Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel2.tsx | 1 + src/components/views/rooms/RoomList2.tsx | 6 +++++- src/components/views/rooms/RoomSublist2.tsx | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 23a9e74646..aed0434b7b 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -266,6 +266,7 @@ export default class LeftPanel2 extends React.Component { onFocus={this.onFocus} onBlur={this.onBlur} isMinimized={this.props.isMinimized} + onResize={this.onResize} />; // TODO: Conference handling / calls: https://github.com/vector-im/riot-web/issues/14177 diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index b0bb70c9a0..0c754b2c8d 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -55,6 +55,7 @@ interface IProps { collapsed: boolean; searchFilter: string; isMinimized: boolean; + onResize(); } interface IState { @@ -183,7 +184,9 @@ export default class RoomList2 extends React.Component { layoutMap.set(tagId, new ListLayout(tagId)); } - this.setState({sublists: newLists, layouts: layoutMap}); + this.setState({sublists: newLists, layouts: layoutMap}, () => { + this.props.onResize(); + }); }; private renderCommunityInvites(): React.ReactElement[] { @@ -256,6 +259,7 @@ export default class RoomList2 extends React.Component { isInvite={aesthetics.isInvite} layout={this.state.layouts.get(orderedTagId)} isMinimized={this.props.isMinimized} + onResize={this.props.onResize} extraBadTilesThatShouldntExist={extraTiles} /> ); diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 21e7c581f0..a1abfb8c7c 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -66,6 +66,7 @@ interface IProps { layout: ListLayout; isMinimized: boolean; tagId: TagID; + onResize(); // TODO: Don't use this. It's for community invites, and community invites shouldn't be here. // You should feel bad if you use this. @@ -228,6 +229,7 @@ export default class RoomSublist2 extends React.Component { private toggleCollapsed = () => { this.props.layout.isCollapsed = !this.props.layout.isCollapsed; this.forceUpdate(); // because the layout doesn't trigger an update + setImmediate(() => this.props.onResize()); // needs to happen when the DOM is updated }; private onHeaderKeyDown = (ev: React.KeyboardEvent) => { From c9bc318ca7a4798d04198ec9fe117d7dafed8ba3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 6 Jul 2020 21:32:46 +0100 Subject: [PATCH 11/21] Allow vertical scrolling on the new room list breadcrumbs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel2.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 23a9e74646..26c95a1ad1 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -32,6 +32,7 @@ import ResizeNotifier from "../../utils/ResizeNotifier"; import SettingsStore from "../../settings/SettingsStore"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2"; import {Key} from "../../Keyboard"; +import IndicatorScrollbar from "../structures/IndicatorScrollbar"; // TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 @@ -217,11 +218,14 @@ export default class LeftPanel2 extends React.Component { private renderHeader(): React.ReactNode { let breadcrumbs; - if (this.state.showBreadcrumbs) { + if (this.state.showBreadcrumbs && !this.props.isMinimized) { breadcrumbs = ( -
- {this.props.isMinimized ? null : } -
+ + + ); } From 61a5807fd1f8d4b302f3b72478fc7587e0408f12 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 3 Jul 2020 12:17:54 +0200 Subject: [PATCH 12/21] only show topmost top sticky header --- src/components/structures/LeftPanel2.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 23a9e74646..88446e02f5 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -115,6 +115,7 @@ export default class LeftPanel2 extends React.Component { const headerStickyWidth = rlRect.width - headerRightMargin; let gotBottom = false; + let lastTopHeader; for (const sublist of sublists) { const slRect = sublist.getBoundingClientRect(); @@ -131,6 +132,12 @@ export default class LeftPanel2 extends React.Component { header.classList.add("mx_RoomSublist2_headerContainer_stickyTop"); header.style.width = `${headerStickyWidth}px`; header.style.top = `${rlRect.top}px`; + if (lastTopHeader) { + lastTopHeader.style.display = "none"; + } + // first unset it, if set in last iteration + header.style.removeProperty("display"); + lastTopHeader = header; } else { header.classList.remove("mx_RoomSublist2_headerContainer_sticky"); header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop"); From a2cf641c0e7d69656dcd80fc0662fcea2e0583f6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 3 Jul 2020 16:52:01 +0200 Subject: [PATCH 13/21] don't need to set width with javascript? --- res/css/views/rooms/_RoomSublist2.scss | 1 + src/components/structures/LeftPanel2.tsx | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 0e76152f86..e080f0efb4 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -70,6 +70,7 @@ limitations under the License. z-index: 1; // over top of other elements, but still under the ones in the visible list height: 32px; // to match the header container // width set by JS + width: calc(100% - 22px); } &.mx_RoomSublist2_headerContainer_stickyBottom { diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 88446e02f5..ccd4049c88 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -124,13 +124,11 @@ export default class LeftPanel2 extends React.Component { if (slRect.top + headerHeight > bottom && !gotBottom) { header.classList.add("mx_RoomSublist2_headerContainer_sticky"); header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom"); - header.style.width = `${headerStickyWidth}px`; header.style.top = `unset`; gotBottom = true; } else if (slRect.top < top) { header.classList.add("mx_RoomSublist2_headerContainer_sticky"); header.classList.add("mx_RoomSublist2_headerContainer_stickyTop"); - header.style.width = `${headerStickyWidth}px`; header.style.top = `${rlRect.top}px`; if (lastTopHeader) { lastTopHeader.style.display = "none"; @@ -142,7 +140,6 @@ export default class LeftPanel2 extends React.Component { header.classList.remove("mx_RoomSublist2_headerContainer_sticky"); header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop"); header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom"); - header.style.width = `unset`; header.style.top = `unset`; } } From 201f6ebe83f6c5095e67e1005fef3394c0822ade Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 3 Jul 2020 16:52:28 +0200 Subject: [PATCH 14/21] make stick headers jump in a bit later so the transition is less jumpy --- src/components/structures/LeftPanel2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index ccd4049c88..db58535b50 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -126,7 +126,7 @@ export default class LeftPanel2 extends React.Component { header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom"); header.style.top = `unset`; gotBottom = true; - } else if (slRect.top < top) { + } else if ((slRect.top - (headerHeight / 3)) < top) { header.classList.add("mx_RoomSublist2_headerContainer_sticky"); header.classList.add("mx_RoomSublist2_headerContainer_stickyTop"); header.style.top = `${rlRect.top}px`; From 7647072b05839f03d3c6ae3c3c6e9e534be1258f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 3 Jul 2020 16:52:52 +0200 Subject: [PATCH 15/21] remove prop instead of assigning unset --- src/components/structures/LeftPanel2.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index db58535b50..e31b71e97c 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -124,7 +124,7 @@ export default class LeftPanel2 extends React.Component { if (slRect.top + headerHeight > bottom && !gotBottom) { header.classList.add("mx_RoomSublist2_headerContainer_sticky"); header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom"); - header.style.top = `unset`; + header.style.removeProperty("top"); gotBottom = true; } else if ((slRect.top - (headerHeight / 3)) < top) { header.classList.add("mx_RoomSublist2_headerContainer_sticky"); @@ -140,7 +140,7 @@ export default class LeftPanel2 extends React.Component { header.classList.remove("mx_RoomSublist2_headerContainer_sticky"); header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop"); header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom"); - header.style.top = `unset`; + header.style.removeProperty("top"); } } } From 5bf14d8427094ecf49ee3254e7cae1bde7ccd2f9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 6 Jul 2020 14:37:38 -0600 Subject: [PATCH 16/21] Minor cleanup of sticky header CSS --- res/css/views/rooms/_RoomSublist2.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index e080f0efb4..3c9f21d311 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -69,8 +69,7 @@ limitations under the License. position: fixed; z-index: 1; // over top of other elements, but still under the ones in the visible list height: 32px; // to match the header container - // width set by JS - width: calc(100% - 22px); + width: calc(100% - 22px); // 22px is an offset for aligning the header to the container } &.mx_RoomSublist2_headerContainer_stickyBottom { From f6aa6208ee0495ea6802ac76b2ae02f9fbc858c8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 6 Jul 2020 21:53:20 +0100 Subject: [PATCH 17/21] null-guard against groups with a null name :(( Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomList2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index b0bb70c9a0..82531cb77d 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -190,7 +190,7 @@ export default class RoomList2 extends React.Component { // TODO: Put community invites in a more sensible place (not in the room list) return MatrixClientPeg.get().getGroups().filter(g => { if (g.myMembership !== 'invite') return false; - return !this.searchFilter || this.searchFilter.matches(g.name); + return !this.searchFilter || this.searchFilter.matches(g.name || ""); }).map(g => { const avatar = ( Date: Mon, 6 Jul 2020 21:58:44 +0100 Subject: [PATCH 18/21] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomSublist2.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index a1abfb8c7c..88f0f662c4 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -66,12 +66,13 @@ interface IProps { layout: ListLayout; isMinimized: boolean; tagId: TagID; - onResize(); // TODO: Don't use this. It's for community invites, and community invites shouldn't be here. // You should feel bad if you use this. extraBadTilesThatShouldntExist?: React.ReactElement[]; + onResize(); + // TODO: Account for https://github.com/vector-im/riot-web/issues/14179 } From abfbcf409008c53e642370f1fdb323a388816df2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 6 Jul 2020 22:04:30 +0100 Subject: [PATCH 19/21] use uglier style for props but be consistent :'( Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomList2.tsx | 2 +- src/components/views/rooms/RoomSublist2.tsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index 0c754b2c8d..421e6abd12 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -51,11 +51,11 @@ interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; onFocus: (ev: React.FocusEvent) => void; onBlur: (ev: React.FocusEvent) => void; + onResize: () => void; resizeNotifier: ResizeNotifier; collapsed: boolean; searchFilter: string; isMinimized: boolean; - onResize(); } interface IState { diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 88f0f662c4..2ca9ec5dd1 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -66,13 +66,12 @@ interface IProps { layout: ListLayout; isMinimized: boolean; tagId: TagID; + onResize: () => void; // TODO: Don't use this. It's for community invites, and community invites shouldn't be here. // You should feel bad if you use this. extraBadTilesThatShouldntExist?: React.ReactElement[]; - onResize(); - // TODO: Account for https://github.com/vector-im/riot-web/issues/14179 } From 70eebc978fdf062d0d603a3b4de0332d333d3a53 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 6 Jul 2020 14:37:38 -0600 Subject: [PATCH 20/21] Revert "Minor cleanup of sticky header CSS" This reverts commit 5bf14d8427094ecf49ee3254e7cae1bde7ccd2f9. --- res/css/views/rooms/_RoomSublist2.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 3c9f21d311..e080f0efb4 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -69,7 +69,8 @@ limitations under the License. position: fixed; z-index: 1; // over top of other elements, but still under the ones in the visible list height: 32px; // to match the header container - width: calc(100% - 22px); // 22px is an offset for aligning the header to the container + // width set by JS + width: calc(100% - 22px); } &.mx_RoomSublist2_headerContainer_stickyBottom { From d14dd777b7d4bc1f4838de2908d4c11b08d73569 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 6 Jul 2020 15:23:20 -0600 Subject: [PATCH 21/21] Revert "don't need to set width with javascript?" This reverts commit a2cf641c0e7d69656dcd80fc0662fcea2e0583f6. --- res/css/views/rooms/_RoomSublist2.scss | 1 - src/components/structures/LeftPanel2.tsx | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index e080f0efb4..0e76152f86 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -70,7 +70,6 @@ limitations under the License. z-index: 1; // over top of other elements, but still under the ones in the visible list height: 32px; // to match the header container // width set by JS - width: calc(100% - 22px); } &.mx_RoomSublist2_headerContainer_stickyBottom { diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 44116c0efd..6c790f3318 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -125,11 +125,13 @@ export default class LeftPanel2 extends React.Component { if (slRect.top + headerHeight > bottom && !gotBottom) { header.classList.add("mx_RoomSublist2_headerContainer_sticky"); header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom"); + header.style.width = `${headerStickyWidth}px`; header.style.removeProperty("top"); gotBottom = true; } else if ((slRect.top - (headerHeight / 3)) < top) { header.classList.add("mx_RoomSublist2_headerContainer_sticky"); header.classList.add("mx_RoomSublist2_headerContainer_stickyTop"); + header.style.width = `${headerStickyWidth}px`; header.style.top = `${rlRect.top}px`; if (lastTopHeader) { lastTopHeader.style.display = "none"; @@ -141,6 +143,7 @@ export default class LeftPanel2 extends React.Component { header.classList.remove("mx_RoomSublist2_headerContainer_sticky"); header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop"); header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom"); + header.style.removeProperty("width"); header.style.removeProperty("top"); } }