From bde51ae5248a89009613b84a71733bedbc76620b Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 27 Jun 2023 13:23:37 +0200 Subject: [PATCH 01/93] Update linkify to 4.1.1 (#11132) * Update linkify to 4.1.1 Fixes: vector-im/element-web#23806 * Empty commit to nudge CI * Remove obsolete `any` types * Allow hyphens in domainpart * Improve test name --- package.json | 8 ++-- src/linkify-matrix.ts | 75 ++++++++++++++++--------------------- test/linkify-matrix-test.ts | 33 ++++++++++++++-- yarn.lock | 38 +++++++++---------- 4 files changed, 84 insertions(+), 70 deletions(-) diff --git a/package.json b/package.json index 02734bd2bb9..ab7f35e56d7 100644 --- a/package.json +++ b/package.json @@ -89,10 +89,10 @@ "is-ip": "^3.1.0", "jszip": "^3.7.0", "katex": "^0.16.0", - "linkify-element": "4.0.0-beta.4", - "linkify-react": "4.0.0-beta.4", - "linkify-string": "4.0.0-beta.4", - "linkifyjs": "4.0.0-beta.4", + "linkify-element": "4.1.1", + "linkify-react": "4.1.1", + "linkify-string": "4.1.1", + "linkifyjs": "4.1.1", "lodash": "^4.17.20", "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", diff --git a/src/linkify-matrix.ts b/src/linkify-matrix.ts index 085143f1982..400a3ba1531 100644 --- a/src/linkify-matrix.ts +++ b/src/linkify-matrix.ts @@ -38,17 +38,14 @@ export enum Type { RoomAlias = "roomalias", } -// Linkify stuff doesn't type scanner/parser/utils properly :/ function matrixOpaqueIdLinkifyParser({ scanner, parser, - utils, token, name, }: { - scanner: any; - parser: any; - utils: any; + scanner: linkifyjs.ScannerInit; + parser: linkifyjs.ParserInit; token: "#" | "+" | "@"; name: Type; }): void { @@ -56,54 +53,48 @@ function matrixOpaqueIdLinkifyParser({ DOT, // IPV4 necessity NUM, - TLD, COLON, SYM, SLASH, EQUALS, HYPHEN, UNDERSCORE, - // because 'localhost' is tokenised to the localhost token, - // usernames @localhost:foo.com are otherwise not matched! - LOCALHOST, - domain, } = scanner.tokens; - const S_START = parser.start; - const matrixSymbol = utils.createTokenClass(name, { isLink: true }); + // Contains NUM, WORD, UWORD, EMOJI, TLD, UTLD, SCHEME, SLASH_SCHEME and LOCALHOST plus custom protocols (e.g. "matrix") + const { domain } = scanner.tokens.groups; - const localpartTokens = [domain, TLD, DOT, LOCALHOST, SYM, SLASH, EQUALS, UNDERSCORE, HYPHEN]; - const domainpartTokens = [domain, TLD, LOCALHOST, HYPHEN]; + // Tokens we need that are not contained in the domain group + const additionalLocalpartTokens = [DOT, SYM, SLASH, EQUALS, UNDERSCORE, HYPHEN]; + const additionalDomainpartTokens = [HYPHEN]; - const INITIAL_STATE = S_START.tt(token); + const matrixToken = linkifyjs.createTokenClass(name, { isLink: true }); + const matrixTokenState = new linkifyjs.State(matrixToken) as any as linkifyjs.State; // linkify doesn't appear to type this correctly - const LOCALPART_STATE = INITIAL_STATE.tt(domain); - for (const token of localpartTokens) { - INITIAL_STATE.tt(token, LOCALPART_STATE); - LOCALPART_STATE.tt(token, LOCALPART_STATE); - } - const LOCALPART_STATE_DOT = LOCALPART_STATE.tt(DOT); - for (const token of localpartTokens) { - LOCALPART_STATE_DOT.tt(token, LOCALPART_STATE); - } + const matrixTokenWithPort = linkifyjs.createTokenClass(name, { isLink: true }); + const matrixTokenWithPortState = new linkifyjs.State( + matrixTokenWithPort, + ) as any as linkifyjs.State; // linkify doesn't appear to type this correctly - const DOMAINPART_STATE_DOT = LOCALPART_STATE.tt(COLON); - const DOMAINPART_STATE = DOMAINPART_STATE_DOT.tt(domain); - DOMAINPART_STATE.tt(DOT, DOMAINPART_STATE_DOT); - for (const token of domainpartTokens) { - DOMAINPART_STATE.tt(token, DOMAINPART_STATE); - // we are done if we have a domain - DOMAINPART_STATE.tt(token, matrixSymbol); - } - - // accept repeated TLDs (e.g .org.uk) but do not accept double dots: .. - for (const token of domainpartTokens) { - DOMAINPART_STATE_DOT.tt(token, DOMAINPART_STATE); - } + const INITIAL_STATE = parser.start.tt(token); - const PORT_STATE = DOMAINPART_STATE.tt(COLON); + // Localpart + const LOCALPART_STATE = new linkifyjs.State(); + INITIAL_STATE.ta(domain, LOCALPART_STATE); + INITIAL_STATE.ta(additionalLocalpartTokens, LOCALPART_STATE); + LOCALPART_STATE.ta(domain, LOCALPART_STATE); + LOCALPART_STATE.ta(additionalLocalpartTokens, LOCALPART_STATE); - PORT_STATE.tt(NUM, matrixSymbol); + // Domainpart + const DOMAINPART_STATE_DOT = LOCALPART_STATE.tt(COLON); + DOMAINPART_STATE_DOT.ta(domain, matrixTokenState); + DOMAINPART_STATE_DOT.ta(additionalDomainpartTokens, matrixTokenState); + matrixTokenState.ta(domain, matrixTokenState); + matrixTokenState.ta(additionalDomainpartTokens, matrixTokenState); + matrixTokenState.tt(DOT, DOMAINPART_STATE_DOT); + + // Port suffixes + matrixTokenState.tt(COLON).tt(NUM, matrixTokenWithPortState); } function onUserClick(event: MouseEvent, userId: string): void { @@ -231,23 +222,21 @@ export const options: Opts = { }; // Run the plugins -registerPlugin(Type.RoomAlias, ({ scanner, parser, utils }: any) => { +registerPlugin(Type.RoomAlias, ({ scanner, parser }) => { const token = scanner.tokens.POUND as "#"; matrixOpaqueIdLinkifyParser({ scanner, parser, - utils, token, name: Type.RoomAlias, }); }); -registerPlugin(Type.UserId, ({ scanner, parser, utils }: any) => { +registerPlugin(Type.UserId, ({ scanner, parser }) => { const token = scanner.tokens.AT as "@"; matrixOpaqueIdLinkifyParser({ scanner, parser, - utils, token, name: Type.UserId, }); diff --git a/test/linkify-matrix-test.ts b/test/linkify-matrix-test.ts index 22694da9db5..4fd2166c2b5 100644 --- a/test/linkify-matrix-test.ts +++ b/test/linkify-matrix-test.ts @@ -138,6 +138,20 @@ describe("linkify-matrix", () => { }, ]); }); + it("properly parses " + char + "localhost:foo.com", () => { + const test = char + "localhost:foo.com"; + const found = linkify.find(test); + expect(found).toEqual([ + { + href: char + "localhost:foo.com", + type, + value: char + "localhost:foo.com", + start: 0, + end: test.length, + isLink: true, + }, + ]); + }); it("properly parses " + char + "foo:localhost", () => { const test = char + "foo:localhost"; const found = linkify.find(test); @@ -162,7 +176,6 @@ describe("linkify-matrix", () => { value: char + "foo:bar.com", start: 0, end: test.length, - isLink: true, }, ]); @@ -219,7 +232,6 @@ describe("linkify-matrix", () => { href: char + "foo:bar.com", start: 0, end: test.length - ":".length, - isLink: true, }, ]); @@ -238,6 +250,20 @@ describe("linkify-matrix", () => { }, ]); }); + it("ignores duplicate :NUM (double port specifier)", () => { + const test = "" + char + "foo:bar.com:2225:1234"; + const found = linkify.find(test); + expect(found).toEqual([ + { + href: char + "foo:bar.com:2225", + type, + value: char + "foo:bar.com:2225", + start: 0, + end: 17, + isLink: true, + }, + ]); + }); it("ignores all the trailing :", () => { const test = "" + char + "foo:bar.com::::"; const found = linkify.find(test); @@ -262,7 +288,6 @@ describe("linkify-matrix", () => { value: char + "foo.asdf:bar.com", start: 0, end: test.length - ":".repeat(4).length, - isLink: true, }, ]); @@ -281,7 +306,7 @@ describe("linkify-matrix", () => { }, ]); }); - it("does not parse multiple room aliases in one string", () => { + it("properly parses room alias with hyphen in domain part", () => { const test = "" + char + "foo:bar.com-baz.com"; const found = linkify.find(test); expect(found).toEqual([ diff --git a/yarn.lock b/yarn.lock index 0971e782f03..d8a45cc6955 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6333,25 +6333,25 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkify-element@4.0.0-beta.4: - version "4.0.0-beta.4" - resolved "https://registry.yarnpkg.com/linkify-element/-/linkify-element-4.0.0-beta.4.tgz#31bb5dff7430c4debc34030466bd8f3e297793a7" - integrity sha512-dsu5qxk6MhQHxXUlPjul33JknQPx7Iv/N8zisH4JtV31qVk0qZg/5gn10Hr76GlMuixcdcxVvGHNfVcvbut13w== - -linkify-react@4.0.0-beta.4: - version "4.0.0-beta.4" - resolved "https://registry.yarnpkg.com/linkify-react/-/linkify-react-4.0.0-beta.4.tgz#75311ade523a52d43054dd841d724d746d43f60d" - integrity sha512-o4vFe28vtk6i8a6tbtkLyusIyhLJSYoHC3gEpmJEVqi6Hy3aguVEenYmtaOjmAQehDrBYeHv9s4qcneZOf7SWQ== - -linkify-string@4.0.0-beta.4: - version "4.0.0-beta.4" - resolved "https://registry.yarnpkg.com/linkify-string/-/linkify-string-4.0.0-beta.4.tgz#0982509bc6ce81c554bff8d7121057193b84ea32" - integrity sha512-1U90tclSloCMAhbcuu4S+BN7ZisZkFB6ggKS1ofdYy1bmtgxdXGDppVUV+qRp5rcAudla7K0LBgOiwCQ0WzrYQ== - -linkifyjs@4.0.0-beta.4: - version "4.0.0-beta.4" - resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.0.0-beta.4.tgz#8a03e7a999ed0b578a14d690585a32706525c45e" - integrity sha512-j8IUYMqyTT0aDrrkA5kf4hn6QurSKjGiQbqjNr4qc8dwEXIniCGp0JrdXmsGcTOEyhKG03GyRnJjp3NDTBBPDQ== +linkify-element@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/linkify-element/-/linkify-element-4.1.1.tgz#049221d53250e67c053cd94dd0ef411cccb87b28" + integrity sha512-G//YNU6WXu1uo/oneLfGE6UPlz5cdk4M43l+WHPezdWUQ/B703g9CtvxtLgfNFU8a/9+c9XjI+d+vfQTiH+KHg== + +linkify-react@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/linkify-react/-/linkify-react-4.1.1.tgz#79cc29c6e5c0fd660be74a6a51d25c1b36977cf7" + integrity sha512-2K9Y1cUdvq40dFWqCJ//X+WP19nlzIVITFGI93RjLnA0M7KbnxQ/ffC3AZIZaEIrLangF9Hjt3i0GQ9/anEG5A== + +linkify-string@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/linkify-string/-/linkify-string-4.1.1.tgz#461eb30b66752dec21f3557ebe55983ae3f5b195" + integrity sha512-9+kj8xr7GLiyNyO9ri7lIxq2ixVYjjqvtomPQpeYNNT56/PxQq6utzXFLm8HxOaGTiMpimj1UAQWwYYPV88L1g== + +linkifyjs@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde" + integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA== listr2@^3.8.3: version "3.14.0" From 3d886de5b016e010889306bed0ad3e0fedaa0f20 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 13:15:21 +0100 Subject: [PATCH 02/93] Update typescript-eslint monorepo to v5.60.0 (#11148) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 108 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/yarn.lock b/yarn.lock index d8a45cc6955..80448bbc616 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2229,11 +2229,16 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" -"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.8": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@^7.0.9": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -2464,14 +2469,14 @@ integrity sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w== "@typescript-eslint/eslint-plugin@^5.35.1": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz#a350faef1baa1e961698240f922d8de1761a9e2b" - integrity sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw== + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz#81382d6ecb92b8dda70e91f9035611cb2fecd1c3" + integrity sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.6" - "@typescript-eslint/type-utils" "5.59.6" - "@typescript-eslint/utils" "5.59.6" + "@typescript-eslint/scope-manager" "5.60.1" + "@typescript-eslint/type-utils" "5.60.1" + "@typescript-eslint/utils" "5.60.1" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -2480,13 +2485,13 @@ tsutils "^3.21.0" "@typescript-eslint/parser@^5.6.0": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.6.tgz#bd36f71f5a529f828e20b627078d3ed6738dbb40" - integrity sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA== + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.60.1.tgz#0f2f58209c0862a73e3d5a56099abfdfa21d0fd3" + integrity sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q== dependencies: - "@typescript-eslint/scope-manager" "5.59.6" - "@typescript-eslint/types" "5.59.6" - "@typescript-eslint/typescript-estree" "5.59.6" + "@typescript-eslint/scope-manager" "5.60.1" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/typescript-estree" "5.60.1" debug "^4.3.4" "@typescript-eslint/scope-manager@5.58.0": @@ -2497,21 +2502,21 @@ "@typescript-eslint/types" "5.58.0" "@typescript-eslint/visitor-keys" "5.58.0" -"@typescript-eslint/scope-manager@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz#d43a3687aa4433868527cfe797eb267c6be35f19" - integrity sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ== +"@typescript-eslint/scope-manager@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz#35abdb47f500c68c08f2f2b4f22c7c79472854bb" + integrity sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ== dependencies: - "@typescript-eslint/types" "5.59.6" - "@typescript-eslint/visitor-keys" "5.59.6" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/visitor-keys" "5.60.1" -"@typescript-eslint/type-utils@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz#37c51d2ae36127d8b81f32a0a4d2efae19277c48" - integrity sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ== +"@typescript-eslint/type-utils@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz#17770540e98d65ab4730c7aac618003f702893f4" + integrity sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A== dependencies: - "@typescript-eslint/typescript-estree" "5.59.6" - "@typescript-eslint/utils" "5.59.6" + "@typescript-eslint/typescript-estree" "5.60.1" + "@typescript-eslint/utils" "5.60.1" debug "^4.3.4" tsutils "^3.21.0" @@ -2520,10 +2525,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.58.0.tgz#54c490b8522c18986004df7674c644ffe2ed77d8" integrity sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g== -"@typescript-eslint/types@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.6.tgz#5a6557a772af044afe890d77c6a07e8c23c2460b" - integrity sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA== +"@typescript-eslint/types@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.60.1.tgz#a17473910f6b8d388ea83c9d7051af89c4eb7561" + integrity sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg== "@typescript-eslint/typescript-estree@5.58.0": version "5.58.0" @@ -2538,30 +2543,30 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz#2fb80522687bd3825504925ea7e1b8de7bb6251b" - integrity sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA== +"@typescript-eslint/typescript-estree@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz#8c71824b7165b64d5ebd7aa42968899525959834" + integrity sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw== dependencies: - "@typescript-eslint/types" "5.59.6" - "@typescript-eslint/visitor-keys" "5.59.6" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/visitor-keys" "5.60.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.6.tgz#82960fe23788113fc3b1f9d4663d6773b7907839" - integrity sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg== +"@typescript-eslint/utils@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.60.1.tgz#6861ebedbefba1ac85482d2bdef6f2ff1eb65b80" + integrity sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.6" - "@typescript-eslint/types" "5.59.6" - "@typescript-eslint/typescript-estree" "5.59.6" + "@typescript-eslint/scope-manager" "5.60.1" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/typescript-estree" "5.60.1" eslint-scope "^5.1.1" semver "^7.3.7" @@ -2587,12 +2592,12 @@ "@typescript-eslint/types" "5.58.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz#673fccabf28943847d0c8e9e8d008e3ada7be6bb" - integrity sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q== +"@typescript-eslint/visitor-keys@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz#19a877358bf96318ec35d90bfe6bd1445cce9434" + integrity sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw== dependencies: - "@typescript-eslint/types" "5.59.6" + "@typescript-eslint/types" "5.60.1" eslint-visitor-keys "^3.3.0" "@vector-im/compound-design-tokens@^0.0.3": @@ -7920,7 +7925,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.7: +semver@^7.3.2, semver@^7.3.4: version "7.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== @@ -7934,6 +7939,13 @@ semver@^7.3.5, semver@^7.3.8: dependencies: lru-cache "^6.0.0" +semver@^7.3.7: + version "7.5.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" + integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== + dependencies: + lru-cache "^6.0.0" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" From e1cad41bc3a06aa9176fb36efa1cf35cc9a2fe02 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 27 Jun 2023 17:39:56 +0100 Subject: [PATCH 03/93] Conform more of the codebase to strictNullChecks (#11134) --- src/MatrixClientPeg.ts | 8 +++----- src/Searching.ts | 10 ++++++---- src/components/structures/RoomView.tsx | 2 +- src/components/structures/ScrollPanel.tsx | 2 +- src/components/structures/TimelinePanel.tsx | 12 ++++++++---- src/components/views/messages/MjolnirBody.tsx | 4 ++-- src/components/views/rooms/RoomListHeader.tsx | 2 +- src/components/views/rooms/RoomPreviewBar.tsx | 2 +- src/stores/ModalWidgetStore.ts | 6 +----- src/stores/right-panel/RightPanelStore.ts | 4 ++-- src/utils/AutoDiscoveryUtils.tsx | 10 +++++++++- src/utils/DecryptFile.ts | 4 ++-- src/utils/StorageManager.ts | 2 +- src/utils/promise.ts | 4 ++-- test/utils/media/requestMediaPermissions-test.tsx | 8 ++++---- 15 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index fabed4f61c3..21820f443a8 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -71,7 +71,7 @@ export interface IMatrixClientPeg { * * @returns {string} The homeserver name, if present. */ - getHomeserverName(): string | null; + getHomeserverName(): string; get(): MatrixClient | null; safeGet(): MatrixClient; @@ -358,10 +358,8 @@ class MatrixClientPegClass implements IMatrixClientPeg { }; } - public getHomeserverName(): string | null { - if (!this.matrixClient) return null; - - const matches = /^@[^:]+:(.+)$/.exec(this.matrixClient.getSafeUserId()); + public getHomeserverName(): string { + const matches = /^@[^:]+:(.+)$/.exec(this.safeGet().getSafeUserId()); if (matches === null || matches.length < 1) { throw new Error("Failed to derive homeserver name from user ID!"); } diff --git a/src/Searching.ts b/src/Searching.ts index 5ec128b0aa4..feb56cb3fe7 100644 --- a/src/Searching.ts +++ b/src/Searching.ts @@ -234,9 +234,11 @@ async function localPagination( ): Promise { const eventIndex = EventIndexPeg.get(); - const searchArgs = searchResult.seshatQuery; + if (!searchResult.seshatQuery) { + throw new Error("localSearchProcess must be called first"); + } - const localResult = await eventIndex!.search(searchArgs); + const localResult = await eventIndex!.search(searchResult.seshatQuery); if (!localResult) { throw new Error("Local search pagination failed"); } @@ -408,7 +410,7 @@ function combineEvents( ): IResultRoomEvents { const response = {} as IResultRoomEvents; - const cachedEvents = previousSearchResult.cachedEvents; + const cachedEvents = previousSearchResult.cachedEvents ?? []; let oldestEventFrom = previousSearchResult.oldestEventFrom; response.highlights = previousSearchResult.highlights; @@ -564,7 +566,7 @@ async function combinedPagination( // Fetch events from the server if we have a token for it and if it's the // local indexes turn or the local index has exhausted its results. if (searchResult.serverSideNextBatch && (oldestEventFrom === "local" || !searchArgs.next_batch)) { - const body = { body: searchResult._query, next_batch: searchResult.serverSideNextBatch }; + const body = { body: searchResult._query!, next_batch: searchResult.serverSideNextBatch }; serverSideResult = await client.search(body); } diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 898f9ff3bb4..2e2dc96b849 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1704,7 +1704,7 @@ export class RoomView extends React.Component { } catch (error) { logger.error(`Failed to reject invite: ${error}`); - const msg = error.message ? error.message : JSON.stringify(error); + const msg = error instanceof Error ? error.message : JSON.stringify(error); Modal.createDialog(ErrorDialog, { title: _t("Failed to reject invite"), description: msg, diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index 0dc31665d2a..fa7da4156fd 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -700,7 +700,7 @@ export default class ScrollPanel extends React.Component { const trackedNode = this.getTrackedNode(); if (trackedNode) { const newBottomOffset = this.topFromBottom(trackedNode); - const bottomDiff = newBottomOffset - scrollState.bottomOffset; + const bottomDiff = newBottomOffset - (scrollState.bottomOffset ?? 0); this.bottomGrowth += bottomDiff; scrollState.bottomOffset = newBottomOffset; const newHeight = `${this.getListHeight()}px`; diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 47e53f18ae2..d9dfa64493c 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -599,7 +599,7 @@ class TimelinePanel extends React.Component { if (!this.timelineWindow?.canPaginate(dir)) { debuglog("can't", dir, "paginate any further"); - this.setState({ [canPaginateKey]: false }); + this.setState({ [canPaginateKey]: false } as Pick); return Promise.resolve(false); } @@ -609,7 +609,7 @@ class TimelinePanel extends React.Component { } debuglog("Initiating paginate; backwards:" + backwards); - this.setState({ [paginatingKey]: true }); + this.setState({ [paginatingKey]: true } as Pick); return this.onPaginationRequest(this.timelineWindow, dir, PAGINATE_SIZE).then(async (r) => { if (this.unmounted) { @@ -2012,7 +2012,7 @@ class TimelinePanel extends React.Component { return receiptStore?.getEventReadUpTo(myUserId, ignoreSynthesized) ?? null; } - private setReadMarker(eventId: string | null, eventTs: number, inhibitSetState = false): void { + private setReadMarker(eventId: string | null, eventTs?: number, inhibitSetState = false): void { const roomId = this.props.timelineSet.room?.roomId; // don't update the state (and cause a re-render) if there is @@ -2023,7 +2023,11 @@ class TimelinePanel extends React.Component { // in order to later figure out if the read marker is // above or below the visible timeline, we stash the timestamp. - TimelinePanel.roomReadMarkerTsMap[roomId ?? ""] = eventTs; + if (eventTs !== undefined) { + TimelinePanel.roomReadMarkerTsMap[roomId ?? ""] = eventTs; + } else { + delete TimelinePanel.roomReadMarkerTsMap[roomId ?? ""]; + } if (inhibitSetState) { return; diff --git a/src/components/views/messages/MjolnirBody.tsx b/src/components/views/messages/MjolnirBody.tsx index 637802b4554..d37c21619b2 100644 --- a/src/components/views/messages/MjolnirBody.tsx +++ b/src/components/views/messages/MjolnirBody.tsx @@ -18,7 +18,7 @@ import React from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t } from "../../../languageHandler"; -import AccessibleButton from "../elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; interface IProps { mxEvent: MatrixEvent; @@ -26,7 +26,7 @@ interface IProps { } export default class MjolnirBody extends React.Component { - private onAllowClick = (e: React.MouseEvent): void => { + private onAllowClick = (e: ButtonEvent): void => { e.preventDefault(); e.stopPropagation(); diff --git a/src/components/views/rooms/RoomListHeader.tsx b/src/components/views/rooms/RoomListHeader.tsx index 1feb201e8a5..d1b7019b4a3 100644 --- a/src/components/views/rooms/RoomListHeader.tsx +++ b/src/components/views/rooms/RoomListHeader.tsx @@ -171,7 +171,7 @@ const RoomListHeader: React.FC = ({ onVisibilityChange }) => { contextMenu = ( diff --git a/src/components/views/rooms/RoomPreviewBar.tsx b/src/components/views/rooms/RoomPreviewBar.tsx index 96cbaa99173..ae06b482944 100644 --- a/src/components/views/rooms/RoomPreviewBar.tsx +++ b/src/components/views/rooms/RoomPreviewBar.tsx @@ -155,7 +155,7 @@ export default class RoomPreviewBar extends React.Component { ); this.setState({ invitedEmailMxid: result.mxid }); } catch (err) { - this.setState({ threePidFetchError: err }); + this.setState({ threePidFetchError: err as MatrixError }); } this.setState({ busy: false }); } diff --git a/src/stores/ModalWidgetStore.ts b/src/stores/ModalWidgetStore.ts index 1126df1f0e6..b561e183f9e 100644 --- a/src/stores/ModalWidgetStore.ts +++ b/src/stores/ModalWidgetStore.ts @@ -70,11 +70,7 @@ export class ModalWidgetStore extends AsyncStoreWithClient { widgetRoomId, sourceWidgetId: sourceWidget.id, onFinished: (success, data) => { - if (!success) { - this.closeModalWidget(sourceWidget, widgetRoomId, { "m.exited": true }); - } else { - this.closeModalWidget(sourceWidget, widgetRoomId, data); - } + this.closeModalWidget(sourceWidget, widgetRoomId, success && data ? data : { "m.exited": true }); this.openSourceWidgetId = null; this.openSourceWidgetRoomId = null; diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 6d827aa171a..e0dc5b1fe35 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -363,7 +363,7 @@ export default class RightPanelStore extends ReadyWatchingStore { const panel = this.byRoom[this.viewedRoomId]; if (panel?.history) { panel.history = panel.history.filter( - (card) => + (card: IRightPanelCard) => card.phase != RightPanelPhases.RoomMemberInfo && card.phase != RightPanelPhases.Room3pidMemberInfo, ); @@ -371,7 +371,7 @@ export default class RightPanelStore extends ReadyWatchingStore { } // when we're switching to a room, clear out thread permalinks to not get you stuck in the middle of the thread // in order to fix https://github.com/matrix-org/matrix-react-sdk/pull/11011 - if (this.currentCard?.phase === RightPanelPhases.ThreadView) { + if (this.currentCard?.phase === RightPanelPhases.ThreadView && this.currentCard.state) { this.currentCard.state.initialEvent = undefined; this.currentCard.state.isInitialEventHighlighted = undefined; this.currentCard.state.initialEventScrollIntoView = undefined; diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index 96fe807a122..213bc5ea986 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -261,7 +261,15 @@ export default class AutoDiscoveryUtils { throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); } - let delegatedAuthentication = undefined; + let delegatedAuthentication: + | { + authorizationEndpoint: string; + registrationEndpoint?: string; + tokenEndpoint: string; + account?: string; + issuer: string; + } + | undefined; if (discoveryResult[M_AUTHENTICATION.stable!]?.state === AutoDiscovery.SUCCESS) { const { authorizationEndpoint, registrationEndpoint, tokenEndpoint, account, issuer } = discoveryResult[ M_AUTHENTICATION.stable! diff --git a/src/utils/DecryptFile.ts b/src/utils/DecryptFile.ts index 506c40441f6..ed0f3953a38 100644 --- a/src/utils/DecryptFile.ts +++ b/src/utils/DecryptFile.ts @@ -60,7 +60,7 @@ export async function decryptFile(file?: IEncryptedFile, info?: IMediaEventInfo) } responseData = await response.arrayBuffer(); } catch (e) { - throw new DownloadError(e); + throw new DownloadError(e as Error); } try { @@ -77,6 +77,6 @@ export async function decryptFile(file?: IEncryptedFile, info?: IMediaEventInfo) return new Blob([dataArray], { type: mimetype }); } catch (e) { - throw new DecryptError(e); + throw new DecryptError(e as Error); } } diff --git a/src/utils/StorageManager.ts b/src/utils/StorageManager.ts index 6cab834855a..a7ed5d76aa8 100644 --- a/src/utils/StorageManager.ts +++ b/src/utils/StorageManager.ts @@ -36,7 +36,7 @@ function log(msg: string): void { logger.log(`StorageManager: ${msg}`); } -function error(msg: string, ...args: string[]): void { +function error(msg: string, ...args: any[]): void { logger.error(`StorageManager: ${msg}`, ...args); } diff --git a/src/utils/promise.ts b/src/utils/promise.ts index 1ae1018b0cb..2d4c3dc0110 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -33,14 +33,14 @@ export async function retry( num: number, predicate?: (e: E) => boolean, ): Promise { - let lastErr!: E; + let lastErr!: any; for (let i = 0; i < num; i++) { try { const v = await fn(); // If `await fn()` throws then we won't reach here return v; } catch (err) { - if (predicate && !predicate(err)) { + if (predicate && !predicate(err as E)) { throw err; } lastErr = err; diff --git a/test/utils/media/requestMediaPermissions-test.tsx b/test/utils/media/requestMediaPermissions-test.tsx index 0239e6c30e0..633f6ab9fac 100644 --- a/test/utils/media/requestMediaPermissions-test.tsx +++ b/test/utils/media/requestMediaPermissions-test.tsx @@ -41,7 +41,7 @@ describe("requestMediaPermissions", () => { describe("when an audio and video device is available", () => { beforeEach(() => { mocked(navigator.mediaDevices.getUserMedia).mockImplementation( - async ({ audio, video }): Promise => { + async ({ audio, video }: MediaStreamConstraints): Promise => { if (audio && video) return audioVideoStream; return audioStream; }, @@ -56,7 +56,7 @@ describe("requestMediaPermissions", () => { describe("when calling with video = false and an audio device is available", () => { beforeEach(() => { mocked(navigator.mediaDevices.getUserMedia).mockImplementation( - async ({ audio, video }): Promise => { + async ({ audio, video }: MediaStreamConstraints): Promise => { if (audio && !video) return audioStream; return audioVideoStream; }, @@ -72,7 +72,7 @@ describe("requestMediaPermissions", () => { beforeEach(() => { error.name = "NotFoundError"; mocked(navigator.mediaDevices.getUserMedia).mockImplementation( - async ({ audio, video }): Promise => { + async ({ audio, video }: MediaStreamConstraints): Promise => { if (audio && video) throw error; if (audio) return audioStream; return audioVideoStream; @@ -103,7 +103,7 @@ describe("requestMediaPermissions", () => { describe("when an Error is raised", () => { beforeEach(async () => { mocked(navigator.mediaDevices.getUserMedia).mockImplementation( - async ({ audio, video }): Promise => { + async ({ audio, video }: MediaStreamConstraints): Promise => { if (audio && video) throw error; return audioVideoStream; }, From 9704c9fc139068065aefc3c44bed6295764a9cfc Mon Sep 17 00:00:00 2001 From: Kerry Date: Wed, 28 Jun 2023 10:20:43 +1200 Subject: [PATCH 04/93] test server switch in cypress login test (#11130) --- cypress/e2e/login/login.spec.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cypress/e2e/login/login.spec.ts b/cypress/e2e/login/login.spec.ts index 9bc6dd3f1b2..2968f0946be 100644 --- a/cypress/e2e/login/login.spec.ts +++ b/cypress/e2e/login/login.spec.ts @@ -47,6 +47,30 @@ describe("Login", () => { // wait for the dialog to go away cy.get(".mx_ServerPickerDialog").should("not.exist"); + cy.get(".mx_Spinner").should("not.exist"); + cy.get(".mx_ServerPicker_server").should("have.text", homeserver.baseUrl); + + cy.findByRole("button", { name: "Edit" }).click(); + + // select the default server again + cy.get(".mx_StyledRadioButton").first().click(); + cy.findByRole("button", { name: "Continue" }).click(); + cy.get(".mx_ServerPickerDialog").should("not.exist"); + cy.get(".mx_Spinner").should("not.exist"); + // name of default server + cy.get(".mx_ServerPicker_server").should("have.text", "server.invalid"); + + // switch back to the custom homeserver + + cy.findByRole("button", { name: "Edit" }).click(); + cy.findByRole("textbox", { name: "Other homeserver" }).type(homeserver.baseUrl); + cy.findByRole("button", { name: "Continue" }).click(); + // wait for the dialog to go away + cy.get(".mx_ServerPickerDialog").should("not.exist"); + + cy.get(".mx_Spinner").should("not.exist"); + cy.get(".mx_ServerPicker_server").should("have.text", homeserver.baseUrl); + cy.findByRole("textbox", { name: "Username", timeout: 15000 }).should("be.visible"); // Disabled because flaky - see https://github.com/vector-im/element-web/issues/24688 //cy.percySnapshot("Login"); From a87362a0487444fb88491db288bd17e2263cbb3f Mon Sep 17 00:00:00 2001 From: Kerry Date: Wed, 28 Jun 2023 11:45:11 +1200 Subject: [PATCH 05/93] Unit test token login flow in `MatrixChat` (#11143) * test tokenlogin * whitespace * tidy * strict --- src/Lifecycle.ts | 2 +- .../components/structures/MatrixChat-test.tsx | 183 +++++++++++++++++- 2 files changed, 178 insertions(+), 7 deletions(-) diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index a1d00d86ad5..9a2f6809508 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -664,7 +664,7 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise", () => { }; const getComponent = (props: Partial> = {}) => render(); - const localStorageSpy = jest.spyOn(localStorage.__proto__, "getItem").mockReturnValue(undefined); + const localStorageSetSpy = jest.spyOn(localStorage.__proto__, "setItem"); + const localStorageGetSpy = jest.spyOn(localStorage.__proto__, "getItem").mockReturnValue(undefined); + const localStorageClearSpy = jest.spyOn(localStorage.__proto__, "clear"); + const sessionStorageSetSpy = jest.spyOn(sessionStorage.__proto__, "setItem"); + + // make test results readable + filterConsole("Failed to parse localStorage object"); beforeEach(async () => { mockClient = getMockClientWithEventEmitter(getMockClientMethods()); @@ -124,10 +130,14 @@ describe("", () => { unstable_features: {}, versions: [], }); - localStorageSpy.mockReset(); + localStorageGetSpy.mockReset(); + localStorageSetSpy.mockReset(); + sessionStorageSetSpy.mockReset(); jest.spyOn(StorageManager, "idbLoad").mockRestore(); jest.spyOn(StorageManager, "idbSave").mockResolvedValue(undefined); jest.spyOn(defaultDispatcher, "dispatch").mockClear(); + + await clearAllModals(); }); it("should render spinner while app is loading", () => { @@ -151,7 +161,7 @@ describe("", () => { }; beforeEach(() => { - localStorageSpy.mockImplementation((key: unknown) => mockLocalStorage[key as string] || ""); + localStorageGetSpy.mockImplementation((key: unknown) => mockLocalStorage[key as string] || ""); jest.spyOn(StorageManager, "idbLoad").mockImplementation(async (table, key) => { const safeKey = Array.isArray(key) ? key[0] : key; @@ -350,9 +360,6 @@ describe("", () => { const userName = "ernie"; const password = "ilovebert"; - // make test results readable - filterConsole("Failed to parse localStorage object"); - const getComponentAndWaitForReady = async (): Promise => { const renderResult = getComponent(); // wait for welcome page chrome render @@ -535,4 +542,168 @@ describe("", () => { }); }); }); + + describe("when query params have a loginToken", () => { + const loginToken = "test-login-token"; + const realQueryParams = { + loginToken, + }; + + const mockLocalStorage: Record = { + mx_sso_hs_url: serverConfig.hsUrl, + mx_sso_is_url: serverConfig.isUrl, + // these are only going to be set during login + mx_hs_url: serverConfig.hsUrl, + mx_is_url: serverConfig.isUrl, + }; + + let loginClient!: ReturnType; + const userId = "@alice:server.org"; + const deviceId = "test-device-id"; + const accessToken = "test-access-token"; + const clientLoginResponse = { + user_id: userId, + device_id: deviceId, + access_token: accessToken, + }; + + beforeEach(() => { + loginClient = getMockClientWithEventEmitter(getMockClientMethods()); + // this is used to create a temporary client during login + jest.spyOn(MatrixJs, "createClient").mockReturnValue(loginClient); + + loginClient.login.mockClear().mockResolvedValue(clientLoginResponse); + + localStorageGetSpy.mockImplementation((key: unknown) => mockLocalStorage[key as string] || ""); + }); + + it("should show an error dialog when no homeserver is found in local storage", async () => { + localStorageGetSpy.mockReturnValue(undefined); + getComponent({ realQueryParams }); + + expect(localStorageGetSpy).toHaveBeenCalledWith("mx_sso_hs_url"); + expect(localStorageGetSpy).toHaveBeenCalledWith("mx_sso_is_url"); + + const dialog = await screen.findByRole("dialog"); + + // warning dialog + expect( + within(dialog).getByText( + "We asked the browser to remember which homeserver you use to let you sign in, " + + "but unfortunately your browser has forgotten it. Go to the sign in page and try again.", + ), + ).toBeInTheDocument(); + }); + + it("should attempt token login", async () => { + getComponent({ realQueryParams }); + + expect(loginClient.login).toHaveBeenCalledWith("m.login.token", { + initial_device_display_name: undefined, + token: loginToken, + }); + }); + + it("should call onTokenLoginCompleted", async () => { + const onTokenLoginCompleted = jest.fn(); + getComponent({ realQueryParams, onTokenLoginCompleted }); + + await flushPromises(); + + expect(onTokenLoginCompleted).toHaveBeenCalled(); + }); + + describe("when login fails", () => { + beforeEach(() => { + loginClient.login.mockRejectedValue(new Error("oups")); + }); + it("should show a dialog", async () => { + getComponent({ realQueryParams }); + + await flushPromises(); + + const dialog = await screen.findByRole("dialog"); + + // warning dialog + expect( + within(dialog).getByText( + "There was a problem communicating with the homeserver, please try again later.", + ), + ).toBeInTheDocument(); + }); + + it("should not clear storage", async () => { + getComponent({ realQueryParams }); + + await flushPromises(); + + expect(loginClient.clearStores).not.toHaveBeenCalled(); + }); + }); + + describe("when login succeeds", () => { + beforeEach(() => { + jest.spyOn(StorageManager, "idbLoad").mockImplementation( + async (_table: string, key: string | string[]) => { + if (key === "mx_access_token") { + return accessToken as any; + } + }, + ); + }); + it("should clear storage", async () => { + getComponent({ realQueryParams }); + + await flushPromises(); + + // just check we called the clearStorage function + expect(loginClient.clearStores).toHaveBeenCalled(); + expect(localStorageClearSpy).toHaveBeenCalled(); + }); + + it("should persist login credentials", async () => { + getComponent({ realQueryParams }); + + await flushPromises(); + + expect(localStorageSetSpy).toHaveBeenCalledWith("mx_hs_url", serverConfig.hsUrl); + expect(localStorageSetSpy).toHaveBeenCalledWith("mx_user_id", userId); + expect(localStorageSetSpy).toHaveBeenCalledWith("mx_has_access_token", "true"); + expect(localStorageSetSpy).toHaveBeenCalledWith("mx_device_id", deviceId); + }); + + it("should set fresh login flag in session storage", async () => { + getComponent({ realQueryParams }); + + await flushPromises(); + + expect(sessionStorageSetSpy).toHaveBeenCalledWith("mx_fresh_login", "true"); + }); + + it("should override hsUrl in creds when login response wellKnown differs from config", async () => { + const hsUrlFromWk = "https://hsfromwk.org"; + const loginResponseWithWellKnown = { + ...clientLoginResponse, + well_known: { + "m.homeserver": { + base_url: hsUrlFromWk, + }, + }, + }; + loginClient.login.mockResolvedValue(loginResponseWithWellKnown); + getComponent({ realQueryParams }); + + await flushPromises(); + + expect(localStorageSetSpy).toHaveBeenCalledWith("mx_hs_url", hsUrlFromWk); + }); + + it("should continue to post login setup when no session is found in local storage", async () => { + getComponent({ realQueryParams }); + + // logged in but waiting for sync screen + await screen.findByText("Logout"); + }); + }); + }); }); From d404e0656ae1d354a9c42c895a7502fb2bf81399 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 28 Jun 2023 11:02:15 +0100 Subject: [PATCH 06/93] Conform more of the codebase to strictNullChecks (#11135) --- src/AddThreepid.ts | 18 ++++++++++----- src/stores/right-panel/RightPanelStore.ts | 27 +++++++++++++---------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts index 42c6068b87f..b6335059cff 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -16,14 +16,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { + IAddThreePidOnlyBody, + IAuthData, + IRequestMsisdnTokenResponse, + IRequestTokenResponse, + MatrixClient, +} from "matrix-js-sdk/src/matrix"; import { MatrixError, HTTPError } from "matrix-js-sdk/src/matrix"; import Modal from "./Modal"; import { _t, UserFriendlyError } from "./languageHandler"; import IdentityAuthClient from "./IdentityAuthClient"; import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents"; -import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog"; +import InteractiveAuthDialog, { InteractiveAuthDialogProps } from "./components/views/dialogs/InteractiveAuthDialog"; function getIdServerDomain(matrixClient: MatrixClient): string { const idBaseUrl = matrixClient.getIdentityServerUrl(true); @@ -239,7 +245,7 @@ export default class AddThreepid { [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, }, - }); + } as InteractiveAuthDialogProps); return finished; } } @@ -270,11 +276,11 @@ export default class AddThreepid { * @param {{type: string, session?: string}} auth UI auth object * @return {Promise} Response from /3pid/add call (in current spec, an empty object) */ - private makeAddThreepidOnlyRequest = (auth?: { type: string; session?: string }): Promise<{}> => { + private makeAddThreepidOnlyRequest = (auth?: IAddThreePidOnlyBody["auth"] | null): Promise<{}> => { return this.matrixClient.addThreePidOnly({ sid: this.sessionId, client_secret: this.clientSecret, - auth, + auth: auth ?? undefined, }); }; @@ -360,7 +366,7 @@ export default class AddThreepid { [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, }, - }); + } as InteractiveAuthDialogProps); return finished; } } diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index e0dc5b1fe35..7f782200a4e 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -90,7 +90,7 @@ export default class RightPanelStore extends ReadyWatchingStore { * during room changes. */ public get isOpen(): boolean { - return this.byRoom[this.viewedRoomId]?.isOpen ?? false; + return this.byRoom[this.viewedRoomId ?? ""]?.isOpen ?? false; } public isOpenForRoom(roomId: string): boolean { @@ -98,7 +98,7 @@ export default class RightPanelStore extends ReadyWatchingStore { } public get roomPhaseHistory(): Array { - return this.byRoom[this.viewedRoomId]?.history ?? []; + return this.byRoom[this.viewedRoomId ?? ""]?.history ?? []; } /** @@ -133,7 +133,7 @@ export default class RightPanelStore extends ReadyWatchingStore { // Setters public setCard(card: IRightPanelCard, allowClose = true, roomId?: string): void { - const rId = roomId ?? this.viewedRoomId; + const rId = roomId ?? this.viewedRoomId ?? ""; // This function behaves as following: // Update state: if the same phase is send but with a state // Set right panel and erase history: if a "different to the current" phase is send (with or without a state) @@ -163,7 +163,7 @@ export default class RightPanelStore extends ReadyWatchingStore { public setCards(cards: IRightPanelCard[], allowClose = true, roomId: string | null = null): void { // This function sets the history of the right panel and shows the right panel if not already visible. - const rId = roomId ?? this.viewedRoomId; + const rId = roomId ?? this.viewedRoomId ?? ""; const history = cards.map((c) => ({ phase: c.phase, state: c.state ?? {} })); this.byRoom[rId] = { history, isOpen: true }; this.show(rId); @@ -172,7 +172,7 @@ export default class RightPanelStore extends ReadyWatchingStore { // Appends a card to the history and shows the right panel if not already visible public pushCard(card: IRightPanelCard, allowClose = true, roomId: string | null = null): void { - const rId = roomId ?? this.viewedRoomId; + const rId = roomId ?? this.viewedRoomId ?? ""; const redirect = this.getVerificationRedirect(card); const targetPhase = redirect?.phase ?? card.phase; const pState = redirect?.state ?? card.state ?? {}; @@ -198,7 +198,7 @@ export default class RightPanelStore extends ReadyWatchingStore { } public popCard(roomId: string | null = null): IRightPanelCard | undefined { - const rId = roomId ?? this.viewedRoomId; + const rId = roomId ?? this.viewedRoomId ?? ""; if (!this.byRoom[rId]) return; const removedCard = this.byRoom[rId].history.pop(); @@ -207,7 +207,7 @@ export default class RightPanelStore extends ReadyWatchingStore { } public togglePanel(roomId: string | null): void { - const rId = roomId ?? this.viewedRoomId; + const rId = roomId ?? this.viewedRoomId ?? ""; if (!this.byRoom[rId]) return; this.byRoom[rId].isOpen = !this.byRoom[rId].isOpen; @@ -215,13 +215,13 @@ export default class RightPanelStore extends ReadyWatchingStore { } public show(roomId: string | null): void { - if (!this.isOpenForRoom(roomId ?? this.viewedRoomId)) { + if (!this.isOpenForRoom(roomId ?? this.viewedRoomId ?? "")) { this.togglePanel(roomId); } } public hide(roomId: string | null): void { - if (this.isOpenForRoom(roomId ?? this.viewedRoomId)) { + if (this.isOpenForRoom(roomId ?? this.viewedRoomId ?? "")) { this.togglePanel(roomId); } } @@ -360,7 +360,7 @@ export default class RightPanelStore extends ReadyWatchingStore { // when we're switching to a room, clear out any stale MemberInfo cards // in order to fix https://github.com/vector-im/element-web/issues/21487 if (this.currentCard?.phase !== RightPanelPhases.EncryptionPanel) { - const panel = this.byRoom[this.viewedRoomId]; + const panel = this.byRoom[this.viewedRoomId ?? ""]; if (panel?.history) { panel.history = panel.history.filter( (card: IRightPanelCard) => @@ -380,13 +380,16 @@ export default class RightPanelStore extends ReadyWatchingStore { // If the right panel stays open mode is used, and the panel was either // closed or never shown for that room, then force it open and display // the room member list. - if (SettingsStore.getValue("feature_right_panel_default_open") && !this.byRoom[this.viewedRoomId]?.isOpen) { + if ( + SettingsStore.getValue("feature_right_panel_default_open") && + !this.byRoom[this.viewedRoomId ?? ""]?.isOpen + ) { const history = [{ phase: RightPanelPhases.RoomMemberList }]; const room = this.viewedRoomId ? this.mxClient?.getRoom(this.viewedRoomId) : undefined; if (!room?.isSpaceRoom()) { history.unshift({ phase: RightPanelPhases.RoomSummary }); } - this.byRoom[this.viewedRoomId] = { + this.byRoom[this.viewedRoomId ?? ""] = { isOpen: true, history, }; From 3ac9066e460c28ee9d1e931b3742e541501fd37d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 28 Jun 2023 11:10:45 +0100 Subject: [PATCH 07/93] Only trap escape key for cancel reply if there is a reply (#11140) fall through (to clear read marker) otherwise Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- .../views/rooms/SendMessageComposer.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index ed2e022b38f..d7326ee913b 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -351,13 +351,15 @@ export class SendMessageComposer extends React.Component Date: Wed, 28 Jun 2023 11:11:18 +0100 Subject: [PATCH 08/93] Use new `CryptoEvent.VerificationRequestReceived` event (#11141) https://github.com/matrix-org/matrix-js-sdk/pull/3514 deprecates `CryptoEvent.VerificationRequest` in favour of `CryptoEvent.VerificationRequestReceived`. Use the new event. --- src/components/structures/MatrixChat.tsx | 2 +- .../views/dialogs/devtools/VerificationExplorer.tsx | 2 +- src/stores/SetupEncryptionStore.ts | 4 ++-- src/stores/right-panel/RightPanelStore.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 40b13eef671..3383e8b0399 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1659,7 +1659,7 @@ export default class MatrixChat extends React.PureComponent { Modal.createDialog(KeySignatureUploadFailedDialog, { failures, source, continuation }); }); - cli.on(CryptoEvent.VerificationRequest, (request) => { + cli.on(CryptoEvent.VerificationRequestReceived, (request) => { if (request.verifier) { Modal.createDialog( IncomingSasDialog, diff --git a/src/components/views/dialogs/devtools/VerificationExplorer.tsx b/src/components/views/dialogs/devtools/VerificationExplorer.tsx index 1ae3030eae1..aaaf02dea4d 100644 --- a/src/components/views/dialogs/devtools/VerificationExplorer.tsx +++ b/src/components/views/dialogs/devtools/VerificationExplorer.tsx @@ -83,7 +83,7 @@ const VerificationExplorer: Tool = ({ onBack }: IDevtoolsProps) => { const cli = useContext(MatrixClientContext); const context = useContext(DevtoolsContext); - const requests = useTypedEventEmitterState(cli, CryptoEvent.VerificationRequest, () => { + const requests = useTypedEventEmitterState(cli, CryptoEvent.VerificationRequestReceived, () => { return ( cli.crypto?.inRoomVerificationRequests["requestsByRoomId"]?.get(context.room.roomId) ?? new Map() diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts index aa37dcb755b..707591dddd2 100644 --- a/src/stores/SetupEncryptionStore.ts +++ b/src/stores/SetupEncryptionStore.ts @@ -64,7 +64,7 @@ export class SetupEncryptionStore extends EventEmitter { this.phase = Phase.Loading; const cli = MatrixClientPeg.safeGet(); - cli.on(CryptoEvent.VerificationRequest, this.onVerificationRequest); + cli.on(CryptoEvent.VerificationRequestReceived, this.onVerificationRequest); cli.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); const requestsInProgress = cli.getCrypto()!.getVerificationRequestsToDeviceInProgress(cli.getUserId()!); @@ -87,7 +87,7 @@ export class SetupEncryptionStore extends EventEmitter { const cli = MatrixClientPeg.get(); if (!!cli) { - cli.removeListener(CryptoEvent.VerificationRequest, this.onVerificationRequest); + cli.removeListener(CryptoEvent.VerificationRequestReceived, this.onVerificationRequest); cli.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); } } diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 7f782200a4e..c004fbf44db 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -66,13 +66,13 @@ export default class RightPanelStore extends ReadyWatchingStore { protected async onReady(): Promise { this.viewedRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); - this.matrixClient?.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); + this.matrixClient?.on(CryptoEvent.VerificationRequestReceived, this.onVerificationRequestUpdate); this.loadCacheFromSettings(); this.emitAndUpdateSettings(); } protected async onNotReady(): Promise { - this.matrixClient?.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); + this.matrixClient?.off(CryptoEvent.VerificationRequestReceived, this.onVerificationRequestUpdate); } protected onDispatcherAction(payload: ActionPayload): void { From 46eb34a55d2872253e188178c65977ff6d3a4ce1 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 28 Jun 2023 13:39:34 +0100 Subject: [PATCH 09/93] Kill off references to deprecated `getStoredDevice` and `getStoredDevicesForUser` (#11152) * Use new `CryptoEvent.VerificationRequestReceived` event https://github.com/matrix-org/matrix-js-sdk/pull/3514 deprecates `CryptoEvent.VerificationRequest` in favour of `CryptoEvent.VerificationRequestReceived`. Use the new event. * Factor out `getDeviceCryptoInfo` function I seem to be writing this logic several times, so let's factor it out. * Factor out `getUserDeviceIds` function Another utility function * VerificationRequestToast: `getStoredDevice` -> `getDeviceCryptoInfo` * SlashCommands: `getStoredDevice` -> `getDeviceCryptoInfo` * MemberTile: `getStoredDevicesForUser` -> `getUserDeviceIds` * Remove redundant mock of `getStoredDevicesForUser` --- src/DeviceListener.ts | 9 +-- src/SlashCommands.tsx | 3 +- .../views/right_panel/VerificationPanel.tsx | 8 +- src/components/views/rooms/MemberTile.tsx | 6 +- .../views/toasts/VerificationRequestToast.tsx | 19 +++-- src/utils/crypto/deviceInfo.ts | 67 ++++++++++++++++ .../views/right_panel/UserInfo-test.tsx | 1 - .../toasts/VerificationRequestToast-test.tsx | 25 ++++-- test/test-utils/client.ts | 3 + test/test-utils/test-utils.ts | 2 + test/utils/crypto/deviceInfo-test.ts | 80 +++++++++++++++++++ 11 files changed, 189 insertions(+), 34 deletions(-) create mode 100644 src/utils/crypto/deviceInfo.ts create mode 100644 test/utils/crypto/deviceInfo-test.ts diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 40ce534ce0c..d7c845943b9 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -46,6 +46,7 @@ import { recordClientInformation, removeClientInformation } from "./utils/device import SettingsStore, { CallbackFn } from "./settings/SettingsStore"; import { UIFeature } from "./settings/UIFeature"; import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder"; +import { getUserDeviceIds } from "./utils/crypto/deviceInfo"; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; @@ -161,12 +162,8 @@ export default class DeviceListener { */ private async getDeviceIds(): Promise> { const cli = this.client; - const crypto = cli?.getCrypto(); - if (crypto === undefined) return new Set(); - - const userId = cli!.getSafeUserId(); - const devices = await crypto.getUserDeviceInfo([userId]); - return new Set(devices.get(userId)?.keys() ?? []); + if (!cli) return new Set(); + return await getUserDeviceIds(cli, cli.getSafeUserId()); } private onWillUpdateDevices = async (users: string[], initialFetch?: boolean): Promise => { diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 524a22a21a5..71e39d13262 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -70,6 +70,7 @@ import { leaveRoomBehaviour } from "./utils/leave-behaviour"; import { isLocalRoom } from "./utils/localRoom/isLocalRoom"; import { SdkContextClass } from "./contexts/SDKContext"; import { MatrixClientPeg } from "./MatrixClientPeg"; +import { getDeviceCryptoInfo } from "./utils/crypto/deviceInfo"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -1031,7 +1032,7 @@ export const Commands = [ return success( (async (): Promise => { - const device = cli.getStoredDevice(userId, deviceId); + const device = await getDeviceCryptoInfo(cli, userId, deviceId); if (!device) { throw new UserFriendlyError( "Unknown (user, session) pair: (%(userId)s, %(deviceId)s)", diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx index 29af0672fbf..d3a2b98ba50 100644 --- a/src/components/views/right_panel/VerificationPanel.tsx +++ b/src/components/views/right_panel/VerificationPanel.tsx @@ -36,6 +36,7 @@ import E2EIcon, { E2EState } from "../rooms/E2EIcon"; import Spinner from "../elements/Spinner"; import AccessibleButton from "../elements/AccessibleButton"; import VerificationShowSas from "../verification/VerificationShowSas"; +import { getDeviceCryptoInfo } from "../../../utils/crypto/deviceInfo"; interface IProps { layout: string; @@ -224,12 +225,7 @@ export default class VerificationPanel extends React.PureComponent { return; } - const devices = cli.getStoredDevicesForUser(userId); - const anyDeviceUnverified = await asyncSome(devices, async (device) => { - const { deviceId } = device; + const deviceIDs = await getUserDeviceIds(cli, userId); + const anyDeviceUnverified = await asyncSome(deviceIDs, async (deviceId) => { // For your own devices, we use the stricter check of cross-signing // verification to encourage everyone to trust their own devices via // cross-signing so that other users can then safely trust you. diff --git a/src/components/views/toasts/VerificationRequestToast.tsx b/src/components/views/toasts/VerificationRequestToast.tsx index 0e9a7781f69..37c1683d811 100644 --- a/src/components/views/toasts/VerificationRequestToast.tsx +++ b/src/components/views/toasts/VerificationRequestToast.tsx @@ -20,8 +20,8 @@ import { VerificationRequest, VerificationRequestEvent, } from "matrix-js-sdk/src/crypto-api"; -import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; import { logger } from "matrix-js-sdk/src/logger"; +import { Device } from "matrix-js-sdk/src/matrix"; import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -35,6 +35,7 @@ import { Action } from "../../../dispatcher/actions"; import VerificationRequestDialog from "../dialogs/VerificationRequestDialog"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { getDeviceCryptoInfo } from "../../../utils/crypto/deviceInfo"; interface IProps { toastKey: string; @@ -44,7 +45,7 @@ interface IProps { interface IState { /** number of seconds left in the timeout counter. Zero if there is no timeout. */ counter: number; - device?: DeviceInfo; + device?: Device; ip?: string; } @@ -74,15 +75,13 @@ export default class VerificationRequestToast extends React.PureComponent { + const crypto = client.getCrypto(); + if (!crypto) { + // no crypto support, no device. + return undefined; + } + + const deviceMap = await crypto.getUserDeviceInfo([userId], downloadUncached); + return deviceMap.get(userId)?.get(deviceId); +} + +/** + * Get the IDs of the given user's devices. + * + * Only devices with Crypto support are returned. If the MatrixClient doesn't support cryptography, an empty Set is + * returned. + * + * @param client - Matrix Client. + * @param userId - ID of the user to query. + */ + +export async function getUserDeviceIds(client: MatrixClient, userId: string): Promise> { + const crypto = client.getCrypto(); + if (!crypto) { + return new Set(); + } + + const deviceMap = await crypto.getUserDeviceInfo([userId]); + return new Set(deviceMap.get(userId)?.keys() ?? []); +} diff --git a/test/components/views/right_panel/UserInfo-test.tsx b/test/components/views/right_panel/UserInfo-test.tsx index 66e75e4ab58..28b072a5d87 100644 --- a/test/components/views/right_panel/UserInfo-test.tsx +++ b/test/components/views/right_panel/UserInfo-test.tsx @@ -160,7 +160,6 @@ beforeEach(() => { credentials: {}, setPowerLevel: jest.fn(), downloadKeys: jest.fn(), - getStoredDevicesForUser: jest.fn(), getCrypto: jest.fn().mockReturnValue(mockCrypto), getStoredCrossSigningForUser: jest.fn(), } as unknown as MatrixClient); diff --git a/test/components/views/toasts/VerificationRequestToast-test.tsx b/test/components/views/toasts/VerificationRequestToast-test.tsx index 6929e992224..f5ba06f571a 100644 --- a/test/components/views/toasts/VerificationRequestToast-test.tsx +++ b/test/components/views/toasts/VerificationRequestToast-test.tsx @@ -15,18 +15,23 @@ limitations under the License. */ import React, { ComponentProps } from "react"; -import { Mocked } from "jest-mock"; +import { mocked, Mocked } from "jest-mock"; import { act, render, RenderResult } from "@testing-library/react"; import { VerificationRequest, VerificationRequestEvent, } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; -import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/client"; import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter"; +import { Device } from "matrix-js-sdk/src/matrix"; import VerificationRequestToast from "../../../../src/components/views/toasts/VerificationRequestToast"; -import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils"; +import { + flushPromises, + getMockClientWithEventEmitter, + mockClientMethodsCrypto, + mockClientMethodsUser, +} from "../../../test-utils"; import ToastStore from "../../../../src/stores/ToastStore"; function renderComponent( @@ -46,7 +51,7 @@ describe("VerificationRequestToast", () => { beforeEach(() => { client = getMockClientWithEventEmitter({ ...mockClientMethodsUser(), - getStoredDevice: jest.fn(), + ...mockClientMethodsCrypto(), getDevice: jest.fn(), }); }); @@ -56,9 +61,15 @@ describe("VerificationRequestToast", () => { const otherIDevice: IMyDevice = { device_id: otherDeviceId, last_seen_ip: "1.1.1.1" }; client.getDevice.mockResolvedValue(otherIDevice); - const otherDeviceInfo = new DeviceInfo(otherDeviceId); - otherDeviceInfo.unsigned = { device_display_name: "my other device" }; - client.getStoredDevice.mockReturnValue(otherDeviceInfo); + const otherDeviceInfo = new Device({ + algorithms: [], + keys: new Map(), + userId: "", + deviceId: otherDeviceId, + displayName: "my other device", + }); + const deviceMap = new Map([[client.getSafeUserId(), new Map([[otherDeviceId, otherDeviceInfo]])]]); + mocked(client.getCrypto()!.getUserDeviceInfo).mockResolvedValue(deviceMap); const request = makeMockVerificationRequest({ isSelfVerification: true, diff --git a/test/test-utils/client.ts b/test/test-utils/client.ts index 582099d6833..056f18eee31 100644 --- a/test/test-utils/client.ts +++ b/test/test-utils/client.ts @@ -64,6 +64,8 @@ export class MockClientWithEventEmitter extends EventEmitter { getUserId: jest.fn().mockReturnValue(aliceId), }); * ``` + * + * See also `stubClient()` which does something similar but uses a more complete mock client. */ export const getMockClientWithEventEmitter = ( mockProperties: Partial>, @@ -158,6 +160,7 @@ export const mockClientMethodsCrypto = (): Partial< getSessionBackupPrivateKey: jest.fn(), }, getCrypto: jest.fn().mockReturnValue({ + getUserDeviceInfo: jest.fn(), getCrossSigningStatus: jest.fn().mockResolvedValue({ publicKeysOnDevice: true, privateKeysInSecretStorage: false, diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 3451f17a59b..1e2fe78ca16 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -60,6 +60,8 @@ import MatrixClientBackedSettingsHandler from "../../src/settings/handlers/Matri * TODO: once the components are updated to get their MatrixClients from * the react context, we can get rid of this and just inject a test client * via the context instead. + * + * See also `getMockClientWithEventEmitter` which does something similar but different. */ export function stubClient(): MatrixClient { const client = createTestClient(); diff --git a/test/utils/crypto/deviceInfo-test.ts b/test/utils/crypto/deviceInfo-test.ts new file mode 100644 index 00000000000..6c2b1a9e38b --- /dev/null +++ b/test/utils/crypto/deviceInfo-test.ts @@ -0,0 +1,80 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Mocked, mocked } from "jest-mock"; +import { Device, MatrixClient } from "matrix-js-sdk/src/matrix"; + +import { getDeviceCryptoInfo, getUserDeviceIds } from "../../../src/utils/crypto/deviceInfo"; +import { getMockClientWithEventEmitter, mockClientMethodsCrypto } from "../../test-utils"; + +describe("getDeviceCryptoInfo()", () => { + let mockClient: Mocked; + + beforeEach(() => { + mockClient = getMockClientWithEventEmitter({ ...mockClientMethodsCrypto() }); + }); + + it("should return undefined on clients with no crypto", async () => { + jest.spyOn(mockClient, "getCrypto").mockReturnValue(undefined); + await expect(getDeviceCryptoInfo(mockClient, "@user:id", "device_id")).resolves.toBeUndefined(); + }); + + it("should return undefined for unknown users", async () => { + mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue(new Map()); + await expect(getDeviceCryptoInfo(mockClient, "@user:id", "device_id")).resolves.toBeUndefined(); + }); + + it("should return undefined for unknown devices", async () => { + mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue(new Map([["@user:id", new Map()]])); + await expect(getDeviceCryptoInfo(mockClient, "@user:id", "device_id")).resolves.toBeUndefined(); + }); + + it("should return the right result for known devices", async () => { + const mockDevice = { deviceId: "device_id" } as Device; + mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue( + new Map([["@user:id", new Map([["device_id", mockDevice]])]]), + ); + await expect(getDeviceCryptoInfo(mockClient, "@user:id", "device_id")).resolves.toBe(mockDevice); + expect(mockClient.getCrypto()!.getUserDeviceInfo).toHaveBeenCalledWith(["@user:id"], undefined); + }); +}); + +describe("getUserDeviceIds", () => { + let mockClient: Mocked; + + beforeEach(() => { + mockClient = getMockClientWithEventEmitter({ ...mockClientMethodsCrypto() }); + }); + + it("should return empty set on clients with no crypto", async () => { + jest.spyOn(mockClient, "getCrypto").mockReturnValue(undefined); + await expect(getUserDeviceIds(mockClient, "@user:id")).resolves.toEqual(new Set()); + }); + + it("should return empty set for unknown users", async () => { + mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue(new Map()); + await expect(getUserDeviceIds(mockClient, "@user:id")).resolves.toEqual(new Set()); + }); + + it("should return the right result for known users", async () => { + const mockDevice = { deviceId: "device_id" } as Device; + mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue( + new Map([["@user:id", new Map([["device_id", mockDevice]])]]), + ); + await expect(getUserDeviceIds(mockClient, "@user:id")).resolves.toEqual(new Set(["device_id"])); + expect(mockClient.getCrypto()!.getUserDeviceInfo).toHaveBeenCalledWith(["@user:id"]); + }); +}); From 6836a5fa7b79114c8cfc20789e37f8c2ef3c3698 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 28 Jun 2023 14:05:36 +0100 Subject: [PATCH 10/93] Conform more code to `strictNullChecks` and `noImplicitAny` (#11156) --- src/Lifecycle.ts | 12 ++++++++-- src/components/structures/RoomView.tsx | 22 ++++++++++++------- src/components/structures/ScrollPanel.tsx | 1 + .../structures/SpaceHierarchy-test.tsx | 2 +- .../structures/TimelinePanel-test.tsx | 2 +- .../right_panel/VerificationPanel-test.tsx | 2 +- .../views/spaces/SpacePanel-test.tsx | 7 +++--- test/utils/AutoDiscoveryUtils-test.tsx | 2 +- test/utils/oidc/registerClient-test.ts | 5 +++-- 9 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 9a2f6809508..c3366cdc5c5 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -686,14 +686,22 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise { }; private injectSticker(url: string, info: object, text: string, threadId: string | null): void { - if (!this.context.client) return; + const roomId = this.getRoomId(); + if (!this.context.client || !roomId) return; if (this.context.client.isGuest()) { dis.dispatch({ action: "require_registration" }); return; } ContentMessages.sharedInstance() - .sendStickerContentToRoom(url, this.getRoomId(), threadId, info, text, this.context.client) + .sendStickerContentToRoom(url, roomId, threadId, info, text, this.context.client) .then(undefined, (error) => { if (error.name === "UnknownDeviceError") { // Let the staus bar handle this @@ -1636,7 +1637,7 @@ export class RoomView extends React.Component { private onSearchUpdate = (inProgress: boolean, searchResults: ISearchResults | null): void => { this.setState({ search: { - ...this.state.search, + ...this.state.search!, count: searchResults?.count, inProgress, }, @@ -1658,10 +1659,12 @@ export class RoomView extends React.Component { }; private onRejectButtonClicked = (): void => { + const roomId = this.getRoomId(); + if (!roomId) return; this.setState({ rejecting: true, }); - this.context.client?.leave(this.getRoomId()).then( + this.context.client?.leave(roomId).then( () => { dis.dispatch({ action: Action.ViewHomePage }); this.setState({ @@ -1896,14 +1899,17 @@ export class RoomView extends React.Component { }); } - private onFileDrop = (dataTransfer: DataTransfer): Promise => - ContentMessages.sharedInstance().sendContentListToRoom( + private onFileDrop = async (dataTransfer: DataTransfer): Promise => { + const roomId = this.getRoomId(); + if (!roomId || !this.context.client) return; + await ContentMessages.sharedInstance().sendContentListToRoom( Array.from(dataTransfer.files), - this.getRoomId(), - null, + roomId, + undefined, this.context.client, TimelineRenderingType.Room, ); + }; private onMeasurement = (narrow: boolean): void => { this.setState({ narrow }); diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index fa7da4156fd..163ee39e03c 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -830,6 +830,7 @@ export default class ScrollPanel extends React.Component { } private topFromBottom(node: HTMLElement): number { + if (!this.itemlist.current) return -1; // current capped height - distance from top = distance from bottom of container to top of tracked element return this.itemlist.current.clientHeight - node.offsetTop; } diff --git a/test/components/structures/SpaceHierarchy-test.tsx b/test/components/structures/SpaceHierarchy-test.tsx index 71584839890..1d41de41ac5 100644 --- a/test/components/structures/SpaceHierarchy-test.tsx +++ b/test/components/structures/SpaceHierarchy-test.tsx @@ -166,7 +166,7 @@ describe("SpaceHierarchy", () => { observe: () => null, unobserve: () => null, disconnect: () => null, - }); + } as ResizeObserver); window.IntersectionObserver = mockIntersectionObserver; }); diff --git a/test/components/structures/TimelinePanel-test.tsx b/test/components/structures/TimelinePanel-test.tsx index 8f98230a5c6..c01109bfa8b 100644 --- a/test/components/structures/TimelinePanel-test.tsx +++ b/test/components/structures/TimelinePanel-test.tsx @@ -70,7 +70,7 @@ const mkTimeline = (room: Room, events: MatrixEvent[]): [EventTimeline, EventTim room: room as Room, getLiveTimeline: () => timeline, getTimelineForEvent: () => timeline, - getPendingEvents: () => [], + getPendingEvents: () => [] as MatrixEvent[], } as unknown as EventTimelineSet; const timeline = new EventTimeline(timelineSet); events.forEach((event) => timeline.addEvent(event, { toStartOfTimeline: false })); diff --git a/test/components/views/right_panel/VerificationPanel-test.tsx b/test/components/views/right_panel/VerificationPanel-test.tsx index bee4611c6df..73f927afcb9 100644 --- a/test/components/views/right_panel/VerificationPanel-test.tsx +++ b/test/components/views/right_panel/VerificationPanel-test.tsx @@ -185,7 +185,7 @@ function renderComponent(props: Partial const defaultProps = { layout: "", member: {} as User, - onClose: () => undefined, + onClose: () => {}, isRoomEncrypted: false, inDialog: false, phase: props.request.phase, diff --git a/test/components/views/spaces/SpacePanel-test.tsx b/test/components/views/spaces/SpacePanel-test.tsx index e474c3f0556..db6b64ecf41 100644 --- a/test/components/views/spaces/SpacePanel-test.tsx +++ b/test/components/views/spaces/SpacePanel-test.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from "react"; import { render, screen, fireEvent, act } from "@testing-library/react"; import { mocked } from "jest-mock"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; import UnwrappedSpacePanel from "../../../../src/components/views/spaces/SpacePanel"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; @@ -28,6 +28,7 @@ import { mkStubRoom, wrapInSdkContext } from "../../../test-utils"; import { SdkContextClass } from "../../../../src/contexts/SDKContext"; import SpaceStore from "../../../../src/stores/spaces/SpaceStore"; import DMRoomMap from "../../../../src/utils/DMRoomMap"; +import { SpaceNotificationState } from "../../../../src/stores/notifications/SpaceNotificationState"; // DND test utilities based on // https://github.com/colinrobertbrooks/react-beautiful-dnd-test-utils/issues/18#issuecomment-1373388693 @@ -98,8 +99,8 @@ jest.mock("../../../../src/stores/spaces/SpaceStore", () => { enabledMetaSpaces: MetaSpace[] = []; spacePanelSpaces: string[] = []; activeSpace: SpaceKey = "!space1"; - getChildSpaces = () => []; - getNotificationState = () => null; + getChildSpaces = () => [] as Room[]; + getNotificationState = () => null as SpaceNotificationState | null; setActiveSpace = jest.fn(); moveRootSpace = jest.fn(); } diff --git a/test/utils/AutoDiscoveryUtils-test.tsx b/test/utils/AutoDiscoveryUtils-test.tsx index a47532179c3..e8e5f87dc1a 100644 --- a/test/utils/AutoDiscoveryUtils-test.tsx +++ b/test/utils/AutoDiscoveryUtils-test.tsx @@ -214,7 +214,7 @@ describe("AutoDiscoveryUtils", () => { registrationEndpoint: "https://test.com/registration", tokenEndpoint: "https://test.com/token", }; - const discoveryResult = { + const discoveryResult: ClientConfig = { ...validIsConfig, ...validHsConfig, [M_AUTHENTICATION.stable!]: { diff --git a/test/utils/oidc/registerClient-test.ts b/test/utils/oidc/registerClient-test.ts index 0a45f2011ba..1e08c0085e1 100644 --- a/test/utils/oidc/registerClient-test.ts +++ b/test/utils/oidc/registerClient-test.ts @@ -18,6 +18,7 @@ import fetchMockJest from "fetch-mock-jest"; import { OidcError } from "matrix-js-sdk/src/oidc/error"; import { getOidcClientId } from "../../../src/utils/oidc/registerClient"; +import { ValidatedDelegatedAuthConfig } from "../../../src/utils/ValidatedServerConfig"; describe("getOidcClientId()", () => { const issuer = "https://auth.com/"; @@ -49,7 +50,7 @@ describe("getOidcClientId()", () => { }); it("should throw when no static clientId is configured and no registration endpoint", async () => { - const authConfigWithoutRegistration = { + const authConfigWithoutRegistration: ValidatedDelegatedAuthConfig = { ...delegatedAuthConfig, issuer: "https://issuerWithoutStaticClientId.org/", registrationEndpoint: undefined, @@ -62,7 +63,7 @@ describe("getOidcClientId()", () => { }); it("should handle when staticOidcClients object is falsy", async () => { - const authConfigWithoutRegistration = { + const authConfigWithoutRegistration: ValidatedDelegatedAuthConfig = { ...delegatedAuthConfig, registrationEndpoint: undefined, }; From 209f5bdf335cb357649267c68c25ae95110ab940 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 28 Jun 2023 15:07:02 +0100 Subject: [PATCH 11/93] Consider the empty push rule actions array equiv to deprecated dont_notify (#11155) * Consider the empty push rule actions array equiv to deprecated dont_notify * Switch primary tests to empty actions, add test for dont_notify * strict types --- src/RoomNotifs.ts | 5 ++++- test/RoomNotifs-test.ts | 7 +++++++ test/test-utils/test-utils.ts | 3 +-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts index c484d68f182..c1f94cd71c0 100644 --- a/src/RoomNotifs.ts +++ b/src/RoomNotifs.ts @@ -219,7 +219,10 @@ function isRuleRoomMuteRuleForRoomId(roomId: string, rule: IPushRule): boolean { } function isMuteRule(rule: IPushRule): boolean { - return rule.actions.length === 1 && rule.actions[0] === PushRuleActionName.DontNotify; + // DontNotify is equivalent to the empty actions array + return ( + rule.actions.length === 0 || (rule.actions.length === 1 && rule.actions[0] === PushRuleActionName.DontNotify) + ); } export function determineUnreadState( diff --git a/test/RoomNotifs-test.ts b/test/RoomNotifs-test.ts index e80e943e3c4..505de622bcd 100644 --- a/test/RoomNotifs-test.ts +++ b/test/RoomNotifs-test.ts @@ -64,6 +64,13 @@ describe("RoomNotifs test", () => { expect(getRoomNotifsState(client, room.roomId)).toBe(RoomNotifState.Mute); }); + it("getRoomNotifsState handles mute state for legacy DontNotify action", () => { + const room = mkRoom(client, "!roomId:server"); + muteRoom(room); + client.pushRules!.global.override![0]!.actions = [PushRuleActionName.DontNotify]; + expect(getRoomNotifsState(client, room.roomId)).toBe(RoomNotifState.Mute); + }); + it("getRoomNotifsState handles mentions only", () => { (client as any).getRoomPushRule = () => ({ rule_id: "!roomId:server", diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 1e2fe78ca16..b4de68df356 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -34,7 +34,6 @@ import { RoomType, KNOWN_SAFE_ROOM_VERSION, ConditionKind, - PushRuleActionName, IPushRules, RelationType, } from "matrix-js-sdk/src/matrix"; @@ -796,7 +795,7 @@ export function muteRoom(room: Room): void { pattern: room.roomId, }, ], - actions: [PushRuleActionName.DontNotify], + actions: [], }, ]; } From c0db739d815ff92fde12048320aa51a37ad00af7 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Wed, 28 Jun 2023 16:39:19 +0200 Subject: [PATCH 12/93] Apply `strictNullChecks` to `src/components/views/spaces/*` (#10517) Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/spaces/QuickSettingsButton.tsx | 10 +- .../views/spaces/SpacePublicShare.tsx | 2 +- .../views/spaces/SpaceTreeLevel.tsx | 11 ++- src/settings/Settings.tsx | 13 ++- src/theme.ts | 25 +++-- .../views/spaces/QuickSettingsButton-test.tsx | 96 +++++++++++++++++++ test/theme-test.ts | 24 ++++- 7 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 test/components/views/spaces/QuickSettingsButton-test.tsx diff --git a/src/components/views/spaces/QuickSettingsButton.tsx b/src/components/views/spaces/QuickSettingsButton.tsx index 458dcaeac49..3966b04c7d0 100644 --- a/src/components/views/spaces/QuickSettingsButton.tsx +++ b/src/components/views/spaces/QuickSettingsButton.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 - 2023 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. @@ -33,7 +33,6 @@ import { Icon as PinUprightIcon } from "../../../../res/img/element-icons/room/p import { Icon as EllipsisIcon } from "../../../../res/img/element-icons/room/ellipsis.svg"; import { Icon as MembersIcon } from "../../../../res/img/element-icons/room/members.svg"; import { Icon as FavoriteIcon } from "../../../../res/img/element-icons/roomlist/favorite.svg"; -import SettingsStore from "../../../settings/SettingsStore"; import Modal from "../../../Modal"; import DevtoolsDialog from "../dialogs/DevtoolsDialog"; import { SdkContextClass } from "../../../contexts/SDKContext"; @@ -46,6 +45,9 @@ const QuickSettingsButton: React.FC<{ const { [MetaSpace.Favourites]: favouritesEnabled, [MetaSpace.People]: peopleEnabled } = useSettingValue>("Spaces.enabledMetaSpaces"); + const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); + const developerModeEnabled = useSettingValue("developerMode"); + let contextMenu: JSX.Element | undefined; if (menuDisplayed && handle.current) { contextMenu = ( @@ -68,14 +70,14 @@ const QuickSettingsButton: React.FC<{ {_t("All settings")} - {SettingsStore.getValue("developerMode") && SdkContextClass.instance.roomViewStore.getRoomId() && ( + {currentRoomId && developerModeEnabled && ( { closeMenu(); Modal.createDialog( DevtoolsDialog, { - roomId: SdkContextClass.instance.roomViewStore.getRoomId()!, + roomId: currentRoomId, }, "mx_DevtoolsDialog_wrapper", ); diff --git a/src/components/views/spaces/SpacePublicShare.tsx b/src/components/views/spaces/SpacePublicShare.tsx index a79cbf0c0b4..236161e3f63 100644 --- a/src/components/views/spaces/SpacePublicShare.tsx +++ b/src/components/views/spaces/SpacePublicShare.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 - 2023 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. diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index 1d31e9d141f..671a15b2c3a 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 - 2023 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. @@ -95,9 +95,9 @@ export const SpaceButton = forwardRef( } let notifBadge; - if (notificationState) { + if (space && notificationState) { let ariaLabel = _t("Jump to first unread room."); - if (space?.getMyMembership() === "invite") { + if (space.getMyMembership() === "invite") { ariaLabel = _t("Jump to first invite."); } @@ -133,8 +133,9 @@ export const SpaceButton = forwardRef( } const viewSpaceHome = (): void => - defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: space.roomId }); - const activateSpace = (): void => SpaceStore.instance.setActiveSpace(spaceKey ?? space.roomId); + // space is set here because of the assignment condition of onClick + defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: space!.roomId }); + const activateSpace = (): void => SpaceStore.instance.setActiveSpace(spaceKey ?? space?.roomId ?? ""); const onClick = props.onClick ?? (selected && space ? viewSpaceHome : activateSpace); return ( diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 52bcac185ad..4b7c7ca5b68 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -46,6 +46,7 @@ import { FontWatcher } from "./watchers/FontWatcher"; import RustCryptoSdkController from "./controllers/RustCryptoSdkController"; import ServerSupportUnstableFeatureController from "./controllers/ServerSupportUnstableFeatureController"; import { WatchManager } from "./WatchManager"; +import { CustomTheme } from "../theme"; export const defaultWatchManager = new WatchManager(); @@ -111,7 +112,15 @@ export const labGroupNames: Record = { [LabGroup.Developer]: _td("Developer"), }; -export type SettingValueType = boolean | number | string | number[] | string[] | Record | null; +export type SettingValueType = + | boolean + | number + | string + | number[] + | string[] + | Record + | Record[] + | null; export interface IBaseSetting { isFeature?: false | undefined; @@ -653,7 +662,7 @@ export const SETTINGS: { [setting: string]: ISetting } = { }, "custom_themes": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, - default: [], + default: [] as CustomTheme[], }, "use_system_theme": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, diff --git a/src/theme.ts b/src/theme.ts index afd31057d8f..9ce8ef405a0 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -16,6 +16,7 @@ limitations under the License. */ import { compare } from "matrix-js-sdk/src/utils"; +import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "./languageHandler"; import SettingsStore from "./settings/SettingsStore"; @@ -34,7 +35,8 @@ interface IFontFaces extends Omit = {}; - for (const { name } of customThemes) { - customThemeNames[`custom-${name}`] = name; + + try { + for (const { name } of customThemes) { + customThemeNames[`custom-${name}`] = name; + } + } catch (err) { + logger.warn("Error loading custom themes", { + err, + customThemes, + }); } + return Object.assign({}, customThemeNames, BUILTIN_THEMES); } @@ -166,7 +177,7 @@ function generateCustomFontFaceCSS(faces: IFontFaces[]): string { .join("\n"); } -function setCustomThemeVars(customTheme: ICustomTheme): void { +function setCustomThemeVars(customTheme: CustomTheme): void { const { style } = document.body; function setCSSColorVariable(name: string, hexColor: string, doPct = true): void { @@ -209,7 +220,7 @@ function setCustomThemeVars(customTheme: ICustomTheme): void { } } -export function getCustomTheme(themeName: string): ICustomTheme { +export function getCustomTheme(themeName: string): CustomTheme { // set css variables const customThemes = SettingsStore.getValue("custom_themes"); if (!customThemes) { diff --git a/test/components/views/spaces/QuickSettingsButton-test.tsx b/test/components/views/spaces/QuickSettingsButton-test.tsx new file mode 100644 index 00000000000..e518a3a10cc --- /dev/null +++ b/test/components/views/spaces/QuickSettingsButton-test.tsx @@ -0,0 +1,96 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { mocked } from "jest-mock"; + +import QuickSettingsButton from "../../../../src/components/views/spaces/QuickSettingsButton"; +import SettingsStore from "../../../../src/settings/SettingsStore"; +import { SdkContextClass } from "../../../../src/contexts/SDKContext"; + +describe("QuickSettingsButton", () => { + const roomId = "!room:example.com"; + + const renderQuickSettingsButton = () => { + render(); + }; + + const getQuickSettingsButton = () => { + return screen.getByRole("button", { name: "Quick settings" }); + }; + + const openQuickSettings = async () => { + await userEvent.click(getQuickSettingsButton()); + await screen.findByText("Quick settings"); + }; + + it("should render the quick settings button", () => { + renderQuickSettingsButton(); + expect(getQuickSettingsButton()).toBeInTheDocument(); + }); + + describe("when the quick settings are open", () => { + beforeEach(async () => { + renderQuickSettingsButton(); + await openQuickSettings(); + }); + + it("should not render the »Developer tools« button", () => { + renderQuickSettingsButton(); + expect(screen.queryByText("Developer tools")).not.toBeInTheDocument(); + }); + }); + + describe("when developer mode is enabled", () => { + beforeEach(() => { + jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => setting === "developerMode"); + renderQuickSettingsButton(); + }); + + afterEach(() => { + mocked(SettingsStore.getValue).mockRestore(); + }); + + describe("and no room is viewed", () => { + it("should not render the »Developer tools« button", () => { + renderQuickSettingsButton(); + expect(screen.queryByText("Developer tools")).not.toBeInTheDocument(); + }); + }); + + describe("and a room is viewed", () => { + beforeEach(() => { + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(roomId); + }); + + afterEach(() => { + mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockRestore(); + }); + + describe("and the quick settings are open", () => { + beforeEach(async () => { + await openQuickSettings(); + }); + + it("should render the »Developer tools« button", () => { + expect(screen.getByRole("button", { name: "Developer tools" })).toBeInTheDocument(); + }); + }); + }); + }); +}); diff --git a/test/theme-test.ts b/test/theme-test.ts index b7d5f5060d2..3789028f81b 100644 --- a/test/theme-test.ts +++ b/test/theme-test.ts @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { setTheme } from "../src/theme"; +import SettingsStore from "../src/settings/SettingsStore"; +import { enumerateThemes, setTheme } from "../src/theme"; describe("theme", () => { describe("setTheme", () => { @@ -124,4 +125,25 @@ describe("theme", () => { }); }); }); + + describe("enumerateThemes", () => { + it("should return a list of themes", () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue([{ name: "pink" }]); + expect(enumerateThemes()).toEqual({ + "light": "Light", + "light-high-contrast": "Light high contrast", + "dark": "Dark", + "custom-pink": "pink", + }); + }); + + it("should be robust to malformed custom_themes values", () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue([23]); + expect(enumerateThemes()).toEqual({ + "light": "Light", + "light-high-contrast": "Light high contrast", + "dark": "Dark", + }); + }); + }); }); From ff2f8692ddc3634a334c514f2566eac877bee3bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:22:23 +0000 Subject: [PATCH 13/93] Update dependency @percy/cli to v1.26.1 (#11146) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 224 +++++++++++++++++++++++++++--------------------------- 1 file changed, 114 insertions(+), 110 deletions(-) diff --git a/yarn.lock b/yarn.lock index 80448bbc616..4374533bb38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -56,7 +56,14 @@ "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" chokidar "^3.4.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4": +"@babel/code-frame@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" + integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== + dependencies: + "@babel/highlight" "^7.22.5" + +"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== @@ -294,11 +301,16 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": +"@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" + integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== + "@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" @@ -323,12 +335,12 @@ "@babel/traverse" "^7.21.5" "@babel/types" "^7.21.5" -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== +"@babel/highlight@^7.18.6", "@babel/highlight@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" + integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== dependencies: - "@babel/helper-validator-identifier" "^7.18.6" + "@babel/helper-validator-identifier" "^7.22.5" chalk "^2.0.0" js-tokens "^4.0.0" @@ -1775,105 +1787,105 @@ tslib "^2.5.0" webcrypto-core "^1.7.7" -"@percy/cli-app@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/cli-app/-/cli-app-1.24.1.tgz#f4296597631f135ec77c3b0d5eab297018461cf2" - integrity sha512-LFgPfVFY487U43D/TkGyF4My6Urym/jQcYYmglzTUFLITan/a9ICiVyyK/CL1dS74CUjWbtFPAfs4glez3qewA== +"@percy/cli-app@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/cli-app/-/cli-app-1.26.1.tgz#f4695078258b81604ed5716bf6ee7dd62f273136" + integrity sha512-HFrw2CxQcOZiXr8P+4f9aS2WYY/k8T3CprkghaA2ZgELOiOKc5nRoo1h5+KT7/LRaJCstk7rD8Sj7F4V+/tMqg== dependencies: - "@percy/cli-command" "1.24.1" - "@percy/cli-exec" "1.24.1" + "@percy/cli-command" "1.26.1" + "@percy/cli-exec" "1.26.1" -"@percy/cli-build@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/cli-build/-/cli-build-1.24.1.tgz#e659ad2b86e1a7c6df2de803d466f1f2c5c9c645" - integrity sha512-iayHaZOdo5eOkjRHkSeplKqxrElZ/FDOtQSj/u5gU9UHqivgeE/kD0i66yWjuc0WR0B2F8PKbLXsJyv8z82xKg== +"@percy/cli-build@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/cli-build/-/cli-build-1.26.1.tgz#4a276bcedfca43e62290fe8ef4d5e4ed4eb9dd36" + integrity sha512-UZoHHPG5weU3Q8MkT0/mcc/A8JgizrNEdlsK2sEPvUcYjhA9kiTqM/slCtr0/JDPurMn8pKrEq15PhGW2cq3sw== dependencies: - "@percy/cli-command" "1.24.1" + "@percy/cli-command" "1.26.1" -"@percy/cli-command@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/cli-command/-/cli-command-1.24.1.tgz#aef22506c3f23e62f2822c185295bb3adbeeb1da" - integrity sha512-R6DAwLYH7o2cSckm3Nn0hIKvyygk5hMMlHenegbGF4Lu7f7p5K1Gtk0feZXw3DitpxEW4UKincr4KDofjRc2cg== +"@percy/cli-command@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/cli-command/-/cli-command-1.26.1.tgz#715096823c41a0be7ab6eae7d659beeb8f65ed51" + integrity sha512-9/fZO6gElX+keM1V78QdRU9H1HZYQsBZeMbzvp9mRF7oZcPTc1BJKClyEW3RwqD4IttQu8reGtYy1Dq9JglXHA== dependencies: - "@percy/config" "1.24.1" - "@percy/core" "1.24.1" - "@percy/logger" "1.24.1" + "@percy/config" "1.26.1" + "@percy/core" "1.26.1" + "@percy/logger" "1.26.1" -"@percy/cli-config@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/cli-config/-/cli-config-1.24.1.tgz#5c9e16cdd6c30eb1527fce8821a6876fc9be9f11" - integrity sha512-8jNxTCRSxKqg1/1vrDw5Xp+5z2G43LNlSDJPM0Eymb5dH6g9yeqxyqkqgxiv1KH5AaQMVJ3Lfcqlzyy5NCI4RQ== +"@percy/cli-config@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/cli-config/-/cli-config-1.26.1.tgz#a91a39d03796d5918f45aa7ba8fbb82689771847" + integrity sha512-haat1i2S663ZsRYJ1BJKtp9hf21eRj8y8IkaRxee3TmpVNHR3QtWbv80t1hlbcy9usX9sIMT/xtXdkgMPPZ/ag== dependencies: - "@percy/cli-command" "1.24.1" + "@percy/cli-command" "1.26.1" -"@percy/cli-exec@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/cli-exec/-/cli-exec-1.24.1.tgz#22432f7ccb1978fd03692266cd7688740484b634" - integrity sha512-3mF0YwVsxrbECBXt2zc54L7AU6W+MSF2zgJtyZaBvyvN57pznvH6ZJGr2ZR4Ck7PiYgF1zZ3N31GlbWmutKCzQ== +"@percy/cli-exec@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/cli-exec/-/cli-exec-1.26.1.tgz#8cc104d873e0aa7ceb04f6e525c0f8245ff41132" + integrity sha512-jHzoSs1Xrv9Hp2qM+3OFR7uBVIyQPjhMbaLyN2uHYg6E53hvWtQoatiU71lxjSoLExPOxqzaEwWfQPOLcWT1Yw== dependencies: - "@percy/cli-command" "1.24.1" + "@percy/cli-command" "1.26.1" cross-spawn "^7.0.3" which "^2.0.2" -"@percy/cli-snapshot@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/cli-snapshot/-/cli-snapshot-1.24.1.tgz#ec9bcc746fedef1ac383903dc7f8a7f2fa0b73a2" - integrity sha512-/CzwvPRzMvQj/MiLY8QSrQE27VJvEc0EaG4qaY9iMXgarXa7+jqWzZuqoFYEIks36K7MyBqt5PGyh/g27XN3Fg== +"@percy/cli-snapshot@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/cli-snapshot/-/cli-snapshot-1.26.1.tgz#b65dd6e8d619d8d24754fc4d73af2a4d1c140a61" + integrity sha512-v0GqzWpj5EG/9ouvXmveI+YoYMZ5kMO3lOjl/vIncwXVsqlDHZXiG7N/zLZFxJhqTnmtCgTUIYZtyUTjtBxiJA== dependencies: - "@percy/cli-command" "1.24.1" + "@percy/cli-command" "1.26.1" yaml "^2.0.0" -"@percy/cli-upload@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/cli-upload/-/cli-upload-1.24.1.tgz#13759b77ed48e0dc0573e580bb49652d946e529b" - integrity sha512-P1ypjUAge01HdsRqrkmIeYsEKqQLQZ5xskKMBGc7mjRSPSYMuGLyHSc0p0nGpclCFp59pDzKfvCGnNJ/ZbEiAQ== +"@percy/cli-upload@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/cli-upload/-/cli-upload-1.26.1.tgz#49f57cdd0792596025196de7baef22aeb720e973" + integrity sha512-odmubJFV/G1WgUgg/gv3lXJ8LGzZCHzP8ltiBg+6IxE4bFgii4SrVGXjQd44K/WKUSBLtUIHwWCqRkk+xJEgMQ== dependencies: - "@percy/cli-command" "1.24.1" + "@percy/cli-command" "1.26.1" fast-glob "^3.2.11" image-size "^1.0.0" "@percy/cli@^1.11.0": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/cli/-/cli-1.24.1.tgz#d4b50b91478f447f84f9e0eee87bdc7ee593ce07" - integrity sha512-sbQUcUW0uz/JDQDcH/UpTAPIt/wx8G855tVufdS6kazWaLMpcFfbXNxh/xuTr4wUgdB0ERLp4P91oCQqNYXR9g== - dependencies: - "@percy/cli-app" "1.24.1" - "@percy/cli-build" "1.24.1" - "@percy/cli-command" "1.24.1" - "@percy/cli-config" "1.24.1" - "@percy/cli-exec" "1.24.1" - "@percy/cli-snapshot" "1.24.1" - "@percy/cli-upload" "1.24.1" - "@percy/client" "1.24.1" - "@percy/logger" "1.24.1" - -"@percy/client@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/client/-/client-1.24.1.tgz#61037142ab44426ac3ec7c9f315294255cf5330c" - integrity sha512-KFxF6JMQKQHmx5cGsAUNDNpIXIwflkAJQb/L+mwbODvT09wkYBNUjw9O5Lde6xKY7tDqOUrbdO81XfvZl6JgRA== - dependencies: - "@percy/env" "1.24.1" - "@percy/logger" "1.24.1" - -"@percy/config@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/config/-/config-1.24.1.tgz#842412d9a38a6a2aeee43cb5b09b6c228010af2e" - integrity sha512-SDM59al8dDE05Ivs87khxsPB0S/rIfoLdTWMlM81GKHf5hklmKKB/I2afzUmjIaG564cguIXNtevKOhmbZ7iPQ== - dependencies: - "@percy/logger" "1.24.1" + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/cli/-/cli-1.26.1.tgz#5da1596242527faf1454f2fbc289fe6f70c1b02f" + integrity sha512-cwli1yBAcIExcT2ELZrKyFJ4+AroDoBt2BmLTGJI+BWKWUBHnJOZiYBMk4CCg+lsh8twxBk8IO43YBHbv25cMg== + dependencies: + "@percy/cli-app" "1.26.1" + "@percy/cli-build" "1.26.1" + "@percy/cli-command" "1.26.1" + "@percy/cli-config" "1.26.1" + "@percy/cli-exec" "1.26.1" + "@percy/cli-snapshot" "1.26.1" + "@percy/cli-upload" "1.26.1" + "@percy/client" "1.26.1" + "@percy/logger" "1.26.1" + +"@percy/client@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/client/-/client-1.26.1.tgz#25a7fa3bcbfd6315015a79e11564f7255343c60d" + integrity sha512-qafZGZNmpHmYW3N94Mi5o1AC9r95smLENrDXvsRTpWXlUAC6kYaAKwijwE93ui9PT0zYPiXCJwkexd5DhVecgg== + dependencies: + "@percy/env" "1.26.1" + "@percy/logger" "1.26.1" + +"@percy/config@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/config/-/config-1.26.1.tgz#790591a436c07cb9287217f381518149b175ff4c" + integrity sha512-hmwHVF5C00VpncDKLPT4wWi9xN/gA5uEDgUdqxRiiY5oxhK5LLY8YQ0NJZiaSD9r5/l3S/sN20GwtfVYdnZVqg== + dependencies: + "@percy/logger" "1.26.1" ajv "^8.6.2" cosmiconfig "^8.0.0" yaml "^2.0.0" -"@percy/core@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/core/-/core-1.24.1.tgz#7b276f8de7d39c4ae59bb4c3236b4a115d1b2f47" - integrity sha512-lByp0765X4wnJFeNqjp8+nZBdVIG7/+cz5gBCwfktuZmsYZje8/HCa0agQTjEx5ETvoXyjPKCpI144nqHqvTFg== +"@percy/core@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/core/-/core-1.26.1.tgz#eac299bdd4ffea962516e5a6fba64e66a749512c" + integrity sha512-I/CB9rgg/LewyO1HvKuHa4L/A5TDkngPzVX0w1qGi+oZa1++Ordb775CcQG7UCwB5KIo41TU+qlCZUZM6C1Jcg== dependencies: - "@percy/client" "1.24.1" - "@percy/config" "1.24.1" - "@percy/dom" "1.24.1" - "@percy/logger" "1.24.1" + "@percy/client" "1.26.1" + "@percy/config" "1.26.1" + "@percy/dom" "1.26.1" + "@percy/logger" "1.26.1" content-disposition "^0.5.4" cross-spawn "^7.0.3" extract-zip "^2.0.1" @@ -1891,20 +1903,22 @@ dependencies: "@percy/sdk-utils" "^1.3.1" -"@percy/dom@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/dom/-/dom-1.24.1.tgz#c5cb46795931ef39e707f301d94a365aa32230d5" - integrity sha512-wdf0hrQerIhla9vpl0CeOhRBXCAkTWXzIqyrcXxje/rwYXw6PfanYnB48yKCbLyaPY8MRIV5JMP8fxzhJF6Rdg== +"@percy/dom@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/dom/-/dom-1.26.1.tgz#481da3fa70cbfceb9f98945cb00d1cb8c2f8ef6a" + integrity sha512-tTAYdMQJxpGmm7/el9zuIclVq/VbmLexrWFnRF0LJtSyoDvihDovp3Bk4TxZgzUyAXr2OrnHK0IpDgjX7EhZ+g== -"@percy/env@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/env/-/env-1.24.1.tgz#eefd53a8d31f8f8409512d47eccb7a7cdaec69fb" - integrity sha512-h/KLpK2z2Cw2dR5vHfuItsAlpUBjNJBmwVmlvb/nbdxAh4slz1a2QGzdFiskpU4YMDy6GebpN+cOkPXQcI8SaQ== +"@percy/env@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/env/-/env-1.26.1.tgz#d1df5f5def4f1414599e9f8b743dc0ecf71ce405" + integrity sha512-sROrXtE9xdrf6+tBgWc2M9ySs8GV+86QjSXa5+MaOL20MSthmSzL0kHQmLo80ZFo456CQc1lfGZCqn39sXDWag== + dependencies: + "@percy/logger" "1.26.1" -"@percy/logger@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@percy/logger/-/logger-1.24.1.tgz#ccb011a5230155766375ffe8ec10b11080c3c2e7" - integrity sha512-dJWGl+tZdPrg4uZglkGaom/1l4Cagruki9ZqsModNvvEN4LoXCbrIBd45GxlNkNhLbUDbvvvpTz1Lqu8wRwB8A== +"@percy/logger@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@percy/logger/-/logger-1.26.1.tgz#766bcb5ac297ea3fb58f949581d5b29205070e20" + integrity sha512-Vk6VWhD32nuoYMBYxIRb8fjK7dTVD3nOwTK+0OAsB5sjcbelBTNLoU+6r2iESBRSA9fmvsmwLJ7xP7V3AgLj2g== "@percy/sdk-utils@^1.3.1": version "1.23.0" @@ -2294,9 +2308,9 @@ form-data "^3.0.0" "@types/node@*": - version "20.2.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb" - integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ== + version "20.3.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898" + integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== "@types/node@^14.14.31": version "14.18.48" @@ -3490,17 +3504,7 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmiconfig@^8.0.0: - version "8.1.3" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.1.3.tgz#0e614a118fcc2d9e5afc2f87d53cd09931015689" - integrity sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw== - dependencies: - import-fresh "^3.2.1" - js-yaml "^4.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - -cosmiconfig@^8.2.0: +cosmiconfig@^8.0.0, cosmiconfig@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd" integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ== @@ -9086,9 +9090,9 @@ yallist@^4.0.0: integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" - integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" + integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== yargs-parser@^18.1.2: version "18.1.3" From bbd1ab0e2748d9068ef8813f22fe403572b8005a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:48:10 +0000 Subject: [PATCH 14/93] Update tj-actions/changed-files action to v37 (#11150) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- .github/workflows/i18n_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/i18n_check.yml b/.github/workflows/i18n_check.yml index 36da56cc8c7..5188cbf0cae 100644 --- a/.github/workflows/i18n_check.yml +++ b/.github/workflows/i18n_check.yml @@ -12,7 +12,7 @@ jobs: - name: "Get modified files" id: changed_files if: github.event_name == 'pull_request' && github.event.pull_request.user.login != 'RiotTranslateBot' - uses: tj-actions/changed-files@b13786805affca18e536ed489687d3d8d1f05d21 # v36 + uses: tj-actions/changed-files@bb3376162b179308a79fc4450262a15a8e1d6888 # v37 with: files: | src/i18n/strings/* From dddab6d54a4c412276f83fb3e52bdcbd1926421a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:53:48 +0000 Subject: [PATCH 15/93] Update sentry-javascript monorepo to v7.56.0 (#11147) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 86 +++++++++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4374533bb38..a1179ad8957 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1930,64 +1930,64 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@sentry-internal/tracing@7.52.1": - version "7.52.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.52.1.tgz#c98823afd2f9814466fa26f24a1a54fe63b27c24" - integrity sha512-6N99rE+Ek0LgbqSzI/XpsKSLUyJjQ9nychViy+MP60p1x+hllukfTsDbNtUNrPlW0Bx+vqUrWKkAqmTFad94TQ== - dependencies: - "@sentry/core" "7.52.1" - "@sentry/types" "7.52.1" - "@sentry/utils" "7.52.1" +"@sentry-internal/tracing@7.56.0": + version "7.56.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.56.0.tgz#ba709258f2f0f3d8a36f9740403088b39212b843" + integrity sha512-OKI4Pz/O13gng8hT9rNc+gRV3+P7nnk1HnHlV8fgaQydS6DsRxoDL1sHa42tZGbh7K9jqNAP3TC6VjBOsr2tXA== + dependencies: + "@sentry/core" "7.56.0" + "@sentry/types" "7.56.0" + "@sentry/utils" "7.56.0" tslib "^1.9.3" "@sentry/browser@^7.0.0": - version "7.52.1" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.52.1.tgz#f112ca4170bb5023f050bdcbcce5621b475a46eb" - integrity sha512-HrCOfieX68t+Wj42VIkraLYwx8kN5311SdBkHccevWs2Y2dZU7R9iLbI87+nb5kpOPQ7jVWW7d6QI/yZmliYgQ== - dependencies: - "@sentry-internal/tracing" "7.52.1" - "@sentry/core" "7.52.1" - "@sentry/replay" "7.52.1" - "@sentry/types" "7.52.1" - "@sentry/utils" "7.52.1" + version "7.56.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.56.0.tgz#6bf3ff21bc2e9b66a72ea0c7dcc3572fdeb3bd8f" + integrity sha512-qpyyw+NM/psbNAYMlTCCdWwxHHcogppEQ+Q40jGE4sKP2VRIjjyteJkUcaEMoGpbJXx9puzTWbpzqlQ8r15Now== + dependencies: + "@sentry-internal/tracing" "7.56.0" + "@sentry/core" "7.56.0" + "@sentry/replay" "7.56.0" + "@sentry/types" "7.56.0" + "@sentry/utils" "7.56.0" tslib "^1.9.3" -"@sentry/core@7.52.1": - version "7.52.1" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.52.1.tgz#4de702937ba8944802bb06eb8dfdf089c39f6bab" - integrity sha512-36clugQu5z/9jrit1gzI7KfKbAUimjRab39JeR0mJ6pMuKLTTK7PhbpUAD4AQBs9qVeXN2c7h9SVZiSA0UDvkg== +"@sentry/core@7.56.0": + version "7.56.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.56.0.tgz#f4253e0d61f55444180a63e5278b62e57303f7cf" + integrity sha512-Nuyyfh09Yz27kPo74fXHlrdmZeK6zrlJVtxQ6LkwuoaTBcNcesNXVaOtr6gjvUGUmsfriVPP3Jero5LXufV7GQ== dependencies: - "@sentry/types" "7.52.1" - "@sentry/utils" "7.52.1" + "@sentry/types" "7.56.0" + "@sentry/utils" "7.56.0" tslib "^1.9.3" -"@sentry/replay@7.52.1": - version "7.52.1" - resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.52.1.tgz#272a0bcb79bb9ffce99b5dcaf864f18d729ce0da" - integrity sha512-A+RaUmpU9/yBHnU3ATemc6wAvobGno0yf5R6fZYkAFoo2FCR2YG6AXxkTazymIf8v2DnLGaSDORYDPdhQClU9A== +"@sentry/replay@7.56.0": + version "7.56.0" + resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.56.0.tgz#8a49dcb45e9ea83bf905cec0d9b42fed4b8085bd" + integrity sha512-bvjiJK1+SM/paLapuL+nEJ8CIF1bJqi0nnFyxUIi2L5L6yb2uMwfyT3IQ+kz0cXJsLdb3HN4WMphVjyiU9pFdg== dependencies: - "@sentry/core" "7.52.1" - "@sentry/types" "7.52.1" - "@sentry/utils" "7.52.1" + "@sentry/core" "7.56.0" + "@sentry/types" "7.56.0" + "@sentry/utils" "7.56.0" "@sentry/tracing@^7.0.0": - version "7.52.1" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.52.1.tgz#65db0404b3d83a268ece9773ddd6aa5fa609d801" - integrity sha512-1afFeb0X6YcMK8mcsGXpO9rNFEF4Kd3mAUF22hXyFNWVoPNQsvdh/WxG2t3U+hLhehQ1ps3iJ0jxFRGF5zSboA== + version "7.56.0" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.56.0.tgz#f119c2b04c06718fa3a0d00b2781c56005e9dd80" + integrity sha512-Qy7lJdC2YBk9T8JFt4da7xHB3pTZH6yUiIwo5edmSBv2cY6MQ0QZgLzsjJurjf47+/WecVYYKdye9q4twsBlDA== dependencies: - "@sentry-internal/tracing" "7.52.1" + "@sentry-internal/tracing" "7.56.0" -"@sentry/types@7.52.1": - version "7.52.1" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.52.1.tgz#bcff6d0462d9b9b7b9ec31c0068fe02d44f25da2" - integrity sha512-OMbGBPrJsw0iEXwZ2bJUYxewI1IEAU2e1aQGc0O6QW5+6hhCh+8HO8Xl4EymqwejjztuwStkl6G1qhK+Q0/Row== +"@sentry/types@7.56.0": + version "7.56.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.56.0.tgz#9042a099cf9e8816d081886d24b88082a5d9f87a" + integrity sha512-5WjhVOQm75ItOytOx2jTx+5yw8/qJ316+g1Di8dS9+kgIi1zniqdMcX00C2yYe3FMUgFB49PegCUYulm9Evapw== -"@sentry/utils@7.52.1": - version "7.52.1" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.52.1.tgz#4a3e49b918f78dba4524c924286210259020cac5" - integrity sha512-MPt1Xu/jluulknW8CmZ2naJ53jEdtdwCBSo6fXJvOTI0SDqwIPbXDVrsnqLAhVJuIN7xbkj96nuY/VBR6S5sWg== +"@sentry/utils@7.56.0": + version "7.56.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.56.0.tgz#e60e4935d17b2197584abf6ce61b522ad055352c" + integrity sha512-wgeX7bufxc//TjjSIE+gCMm8hVId7Jzvc+f441bYrWnNZBuzPIDW2BummCcPrKzSYe5GeYZDTZGV8YZGMLGBjw== dependencies: - "@sentry/types" "7.52.1" + "@sentry/types" "7.56.0" tslib "^1.9.3" "@sinclair/typebox@^0.24.1": From 518c02a90e479a08605a14313e77e6d7afb35728 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:55:40 +0000 Subject: [PATCH 16/93] Update all non-major dependencies (#11084) * Update all non-major dependencies * Hold back typescript & posthog-js * Hold back posthog-js --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- package.json | 6 +- yarn.lock | 169 +++++++++++++++------------------------------------ 2 files changed, 53 insertions(+), 122 deletions(-) diff --git a/package.json b/package.json index ab7f35e56d7..f10b95ea8e7 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "react-transition-group": "^4.4.1", "rfc4648": "^1.4.0", "sanitize-filename": "^1.6.3", - "sanitize-html": "2.10.0", + "sanitize-html": "2.11.0", "tar-js": "^0.3.0", "ua-parser-js": "^1.0.2", "what-input": "^5.2.10", @@ -184,14 +184,14 @@ "cypress-each": "^1.13.3", "cypress-multi-reporters": "^1.6.1", "cypress-real-events": "^1.7.1", - "eslint": "8.42.0", + "eslint": "8.43.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-deprecate": "^0.7.0", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jest": "^27.2.1", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-matrix-org": "1.1.0", + "eslint-plugin-matrix-org": "1.2.0", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-unicorn": "^47.0.0", diff --git a/yarn.lock b/yarn.lock index a1179ad8957..5584fe87c64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1167,9 +1167,9 @@ integrity sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA== "@csstools/media-query-list-parser@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.0.tgz#6e1a5e12e0d103cd13b94bddb88b878bd6866103" - integrity sha512-MXkR+TeaS2q9IkpyO6jVCdtA/bfpABJxIrfkLswThFN8EZZgI2RfAHhm6sDNDuYV25d5+b8Lj1fpTccIcSLPsQ== + version "2.1.1" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.1.tgz#7c92053c957f188c35acb30c0678a90d460a4bb4" + integrity sha512-pUjtFbaKbiFNjJo8pprrIaXLvQvWIlwPiFnRI4sEnc4F0NIGTOsw8kaJSR3CmZAKEvV8QYckovgAnWQC0bgLLQ== "@csstools/selector-specificity@^2.2.0": version "2.2.0" @@ -1235,10 +1235,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.42.0": - version "8.42.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" - integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== +"@eslint/js@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.43.0.tgz#559ca3d9ddbd6bf907ad524320a0d14b85586af0" + integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg== "@humanwhocodes/config-array@^0.11.10": version "0.11.10" @@ -2313,9 +2313,9 @@ integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== "@types/node@^14.14.31": - version "14.18.48" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.48.tgz#ee5c7ac6e38fd2a9e6885f15c003464cf2da343c" - integrity sha512-iL0PIMwejpmuVHgfibHpfDwOdsbmB50wr21X71VnF5d7SsBF7WK+ZvP/SCcFm7Iwb9iiYSap9rlrdhToNAWdxg== + version "14.18.52" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.52.tgz#214674cbff9f86fad4bf0c25f31ab9b9fa31110f" + integrity sha512-DGhiXKOHSFVVm+PJD+9Y0ObxXLeG6qwc0HoOn+ooQKeNNu+T2mEJCM5UBDUREKAggl9MHYjb5E71PAmx6MbzIg== "@types/node@^16": version "16.18.34" @@ -2508,14 +2508,6 @@ "@typescript-eslint/typescript-estree" "5.60.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.58.0": - version "5.58.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz#5e023a48352afc6a87be6ce3c8e763bc9e2f0bc8" - integrity sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA== - dependencies: - "@typescript-eslint/types" "5.58.0" - "@typescript-eslint/visitor-keys" "5.58.0" - "@typescript-eslint/scope-manager@5.60.1": version "5.60.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz#35abdb47f500c68c08f2f2b4f22c7c79472854bb" @@ -2534,29 +2526,11 @@ debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.58.0": - version "5.58.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.58.0.tgz#54c490b8522c18986004df7674c644ffe2ed77d8" - integrity sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g== - "@typescript-eslint/types@5.60.1": version "5.60.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.60.1.tgz#a17473910f6b8d388ea83c9d7051af89c4eb7561" integrity sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg== -"@typescript-eslint/typescript-estree@5.58.0": - version "5.58.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz#4966e6ff57eaf6e0fce2586497edc097e2ab3e61" - integrity sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q== - dependencies: - "@typescript-eslint/types" "5.58.0" - "@typescript-eslint/visitor-keys" "5.58.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - "@typescript-eslint/typescript-estree@5.60.1": version "5.60.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz#8c71824b7165b64d5ebd7aa42968899525959834" @@ -2570,7 +2544,7 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.60.1": +"@typescript-eslint/utils@5.60.1", "@typescript-eslint/utils@^5.10.0": version "5.60.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.60.1.tgz#6861ebedbefba1ac85482d2bdef6f2ff1eb65b80" integrity sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ== @@ -2584,28 +2558,6 @@ eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/utils@^5.10.0": - version "5.58.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.58.0.tgz#430d7c95f23ec457b05be5520c1700a0dfd559d5" - integrity sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.58.0" - "@typescript-eslint/types" "5.58.0" - "@typescript-eslint/typescript-estree" "5.58.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.58.0": - version "5.58.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz#eb9de3a61d2331829e6761ce7fd13061781168b4" - integrity sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA== - dependencies: - "@typescript-eslint/types" "5.58.0" - eslint-visitor-keys "^3.3.0" - "@typescript-eslint/visitor-keys@5.60.1": version "5.60.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz#19a877358bf96318ec35d90bfe6bd1445cce9434" @@ -3666,9 +3618,9 @@ cypress-real-events@^1.7.1: integrity sha512-8fFnA8EzS3EVbAmpSEUf3A8yZCmfU3IPOSGUDVFCdE1ke1gYL1A+gvXXV6HKUbTPRuvKKt2vpaMbUwYLpDRswQ== cypress@^12.0.0: - version "12.13.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.13.0.tgz#725b6617ea19e41e5c59cc509fc3e08097142b01" - integrity sha512-QJlSmdPk+53Zhy69woJMySZQJoWfEWun3X5OOenGsXjRPVfByVTHorxNehbzhZrEzH9RDUDqVcck0ahtlS+N/Q== + version "12.16.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.16.0.tgz#d0dcd0725a96497f4c60cf54742242259847924c" + integrity sha512-mwv1YNe48hm0LVaPgofEhGCtLwNIQEjmj2dJXnAkY1b4n/NE9OtgPph4TyS+tOtYp5CKtRmDvBzWseUXQTjbTg== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" @@ -4281,9 +4233,9 @@ eslint-plugin-import@^2.25.4: tsconfig-paths "^3.14.1" eslint-plugin-jest@^27.2.1: - version "27.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz#b85b4adf41c682ea29f1f01c8b11ccc39b5c672c" - integrity sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg== + version "27.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.2.tgz#be4ded5f91905d9ec89aa8968d39c71f3b072c0c" + integrity sha512-euzbp06F934Z7UDl5ZUaRPLAc9MKjh0rMPERrHT7UhlCEwgb25kBj37TvMgWeHZVkR5I9CayswrpoaqZU1RImw== dependencies: "@typescript-eslint/utils" "^5.10.0" @@ -4309,10 +4261,10 @@ eslint-plugin-jsx-a11y@^6.5.1: object.fromentries "^2.0.6" semver "^6.3.0" -eslint-plugin-matrix-org@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-1.1.0.tgz#cb3c313b58aa84ee0dd52c57f4a614a1795e8744" - integrity sha512-UArLqthBuaCljVajS2TtlPQLXNMZZAPKRt+gA8D0ayzcAj+Ghl50amwGtvLHMzISGv3sqNDBFBMD9cElntE1zA== +eslint-plugin-matrix-org@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-1.2.0.tgz#84b78969c93e6d3d593fe8bf25ee67ec4dcd2883" + integrity sha512-Wp5CeLnyEwGBn8ZfVbSuO2y0Fs51IWonPJ1QRQTntaRxOkEQnnky3gOPwpfGJ8JB0CxYr1zXfeHh8LcYHW4wcg== eslint-plugin-react-hooks@^4.3.0: version "4.6.0" @@ -4393,15 +4345,15 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@8.42.0: - version "8.42.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.42.0.tgz#7bebdc3a55f9ed7167251fe7259f75219cade291" - integrity sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A== +eslint@8.43.0: + version "8.43.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.43.0.tgz#3e8c6066a57097adfd9d390b8fc93075f257a094" + integrity sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" "@eslint/eslintrc" "^2.0.3" - "@eslint/js" "8.42.0" + "@eslint/js" "8.43.0" "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -5166,9 +5118,9 @@ html-encoding-sniffer@^3.0.0: whatwg-encoding "^2.0.0" html-entities@^2.0.0: - version "2.3.3" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" - integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== + version "2.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" + integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== html-escaper@^2.0.0: version "2.0.2" @@ -6266,9 +6218,9 @@ jszip@^3.7.0: setimmediate "^1.0.5" katex@^0.16.0: - version "0.16.7" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.7.tgz#36be1d4ed96e8afdc5863407e70f8fb250aeafd5" - integrity sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA== + version "0.16.8" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.8.tgz#89b453f40e8557f423f31a1009e9298dd99d5ceb" + integrity sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg== dependencies: commander "^8.3.0" @@ -7253,14 +7205,6 @@ postcss-scss@^4.0.4: resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.6.tgz#5d62a574b950a6ae12f2aa89b60d63d9e4432bfd" integrity sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ== -postcss-selector-parser@^6.0.11: - version "6.0.11" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" - integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - postcss-selector-parser@^6.0.13: version "6.0.13" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" @@ -7878,10 +7822,10 @@ sanitize-filename@^1.6.3: dependencies: truncate-utf8-bytes "^1.0.0" -sanitize-html@2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.10.0.tgz#74d28848dfcf72c39693139131895c78900ab452" - integrity sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ== +sanitize-html@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.11.0.tgz#9a6434ee8fcaeddc740d8ae7cd5dd71d3981f8f6" + integrity sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" @@ -7929,10 +7873,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2, semver@^7.3.4: - version "7.5.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" - integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== +semver@^7.3.2, semver@^7.3.4, semver@^7.3.7: + version "7.5.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" + integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== dependencies: lru-cache "^6.0.0" @@ -7943,13 +7887,6 @@ semver@^7.3.5, semver@^7.3.8: dependencies: lru-cache "^6.0.0" -semver@^7.3.7: - version "7.5.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" - integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== - dependencies: - lru-cache "^6.0.0" - set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -8265,19 +8202,19 @@ stylelint-config-standard@^33.0.0: stylelint-config-recommended "^12.0.0" stylelint-scss@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-5.0.0.tgz#2ec6323bac8003fa64871f3fbb75108270e99b83" - integrity sha512-5Ee5kG3JIcP2jk2PMoFMiNmW/815V+wK5o37X5ke90ihWMpPXI9iyqeA6zEWipWSRXeQc0kqbd7hKqiR+wPKNA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-5.0.1.tgz#b33a6580b5734eace083cfc2cc3021225e28547f" + integrity sha512-n87iCRZrr2J7//I/QFsDXxFLnHKw633U4qvWZ+mOW6KDAp/HLj06H+6+f9zOuTYy+MdGdTuCSDROCpQIhw5fvQ== dependencies: postcss-media-query-parser "^0.2.3" postcss-resolve-nested-selector "^0.1.1" - postcss-selector-parser "^6.0.11" + postcss-selector-parser "^6.0.13" postcss-value-parser "^4.2.0" stylelint@^15.0.0: - version "15.7.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.7.0.tgz#945939a2ce9516998a198580e69b1ceef8a7c5f3" - integrity sha512-fQRwHwWuZsDn4ENyE9AsKkOkV9WlD2CmYiVDbdZPdS3iZh0ceypOn1EuwTNuZ8xTrHF+jVeIEzLtFFSlD/nJHg== + version "15.9.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.9.0.tgz#f5522fac58f806b59ebddbca360ed470dc5b419c" + integrity sha512-sXtAZi64CllWr6A+8ymDWnlIaYwuAa7XRmGnJxLQXFNnLjd3Izm4HAD+loKVaZ7cpK6SLxhAUX1lwPJKGCn0mg== dependencies: "@csstools/css-parser-algorithms" "^2.2.0" "@csstools/css-tokenizer" "^2.1.1" @@ -8319,7 +8256,6 @@ stylelint@^15.0.0: supports-hyperlinks "^3.0.0" svg-tags "^1.0.0" table "^6.8.1" - v8-compile-cache "^2.3.0" write-file-atomic "^5.0.1" supercluster@^7.1.5: @@ -8588,9 +8524,9 @@ tslib@^2.0.0, tslib@^2.0.3, tslib@^2.4.0, tslib@^2.5.0: integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== tslib@^2.1.0: - version "2.5.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" - integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== + version "2.6.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" + integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== tsutils@^3.21.0: version "3.21.0" @@ -8815,11 +8751,6 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -v8-compile-cache@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - v8-to-istanbul@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" From ac9dd8307fcbf500bde73322ea6ff1f4f4828534 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:17:40 +0000 Subject: [PATCH 17/93] Update dependency @types/node to v16.18.36 (#11075) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5584fe87c64..3b2b181fe06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2318,9 +2318,9 @@ integrity sha512-DGhiXKOHSFVVm+PJD+9Y0ObxXLeG6qwc0HoOn+ooQKeNNu+T2mEJCM5UBDUREKAggl9MHYjb5E71PAmx6MbzIg== "@types/node@^16": - version "16.18.34" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.34.tgz#62d2099b30339dec4b1b04a14c96266459d7c8b2" - integrity sha512-VmVm7gXwhkUimRfBwVI1CHhwp86jDWR04B5FGebMMyxV90SlCmFujwUHrxTD4oO+SOYU86SoxvhgeRQJY7iXFg== + version "16.18.37" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.37.tgz#a1f8728e4dc30163deb41e9b7aba65d0c2d4eda1" + integrity sha512-ql+4dw4PlPFBP495k8JzUX/oMNRI2Ei4PrMHgj8oT4VhGlYUzF4EYr0qk2fW+XBVGIrq8Zzk13m4cvyXZuv4pA== "@types/normalize-package-data@^2.4.0": version "2.4.1" From 3de2bcdc1ab3aeff8cde1b32f758796aff06db8f Mon Sep 17 00:00:00 2001 From: Kerry Date: Thu, 29 Jun 2023 09:02:58 +1200 Subject: [PATCH 18/93] Fix: cypress `toasts/analytics-toast.spec.ts` failures (#11153) * Revert "skip broken analytics tests (#11144)" This reverts commit 83ee1946ea073cec20d13176a136ef51d222de7c. * reset window notification permission between tets --- cypress/e2e/settings/security-user-settings-tab.spec.ts | 2 +- cypress/e2e/toasts/analytics-toast.spec.ts | 2 +- cypress/support/login.ts | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/settings/security-user-settings-tab.spec.ts b/cypress/e2e/settings/security-user-settings-tab.spec.ts index aed3eeb6893..341624dee30 100644 --- a/cypress/e2e/settings/security-user-settings-tab.spec.ts +++ b/cypress/e2e/settings/security-user-settings-tab.spec.ts @@ -25,7 +25,7 @@ describe("Security user settings tab", () => { cy.stopHomeserver(homeserver); }); - describe.skip("with posthog enabled", () => { + describe("with posthog enabled", () => { beforeEach(() => { // Enable posthog cy.intercept("/config.json?cachebuster=*", (req) => { diff --git a/cypress/e2e/toasts/analytics-toast.spec.ts b/cypress/e2e/toasts/analytics-toast.spec.ts index c1c6edc9020..4cc8baa838e 100644 --- a/cypress/e2e/toasts/analytics-toast.spec.ts +++ b/cypress/e2e/toasts/analytics-toast.spec.ts @@ -39,7 +39,7 @@ function rejectToast(expectedTitle: string): void { }); } -describe.skip("Analytics Toast", () => { +describe("Analytics Toast", () => { let homeserver: HomeserverInstance; afterEach(() => { diff --git a/cypress/support/login.ts b/cypress/support/login.ts index b830c23f1ef..5a65da17617 100644 --- a/cypress/support/login.ts +++ b/cypress/support/login.ts @@ -137,7 +137,14 @@ Cypress.Commands.add( prelaunchFn?.(); return cy - .visit("/") + .visit("/", { + onBeforeLoad(win) { + // reset notification permissions so we have predictable behaviour + // of notifications toast + // @ts-ignore allow setting default + cy.stub(win.Notification, "permission", "default"); + }, + }) .then(() => { // wait for the app to load return cy.get(".mx_MatrixChat", { timeout: 30000 }); From 3f04e41c21e6c48b4620d837ddfbb99cfcb25f14 Mon Sep 17 00:00:00 2001 From: Kerry Date: Thu, 29 Jun 2023 09:08:56 +1200 Subject: [PATCH 19/93] OIDC: navigate to authorization endpoint (#11096) * add delegatedauthentication to validated server config * dynamic client registration functions * test OP registration functions * add stubbed nativeOidc flow setup in Login * cover more error cases in Login * tidy * test dynamic client registration in Login * comment oidc_static_clients * register oidc inside Login.getFlows * strict fixes * remove unused code * and imports * comments * comments 2 * util functions to get static client id * check static client ids in login flow * remove dead code * OidcRegistrationClientMetadata type * navigate to oidc authorize url * navigate to oidc authorize url * test * adjust for js-sdk code * update test for response_mode query * use new types * strict * tidy --- res/css/structures/auth/_Login.pcss | 5 + src/components/structures/auth/Login.tsx | 25 ++++- src/utils/oidc/authorize.ts | 73 +++++++++++++ src/utils/oidc/registerClient.ts | 1 - .../components/structures/auth/Login-test.tsx | 7 +- test/utils/oidc/authorize-test.ts | 102 ++++++++++++++++++ 6 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 src/utils/oidc/authorize.ts create mode 100644 test/utils/oidc/authorize-test.ts diff --git a/res/css/structures/auth/_Login.pcss b/res/css/structures/auth/_Login.pcss index 2eba8cf3d14..eeca1e8e494 100644 --- a/res/css/structures/auth/_Login.pcss +++ b/res/css/structures/auth/_Login.pcss @@ -99,3 +99,8 @@ div.mx_AccessibleButton_kind_link.mx_Login_forgot { align-content: center; padding: 14px; } + +.mx_Login_fullWidthButton { + width: 100%; + margin-bottom: 16px; +} diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index b69c7f3e09f..e4ac7f88ce9 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -20,7 +20,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { ISSOFlow, SSOAction } from "matrix-js-sdk/src/@types/auth"; import { _t, _td, UserFriendlyError } from "../../../languageHandler"; -import Login, { ClientLoginFlow } from "../../../Login"; +import Login, { ClientLoginFlow, OidcNativeFlow } from "../../../Login"; import { messageForConnectionError, messageForLoginError } from "../../../utils/ErrorUtils"; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; import AuthPage from "../../views/auth/AuthPage"; @@ -39,6 +39,7 @@ import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleBu import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig"; import { filterBoolean } from "../../../utils/arrays"; import { Features } from "../../../settings/Settings"; +import { startOidcLogin } from "../../../utils/oidc/authorize"; // These are used in several places, and come from the js-sdk's autodiscovery // stuff. We define them here so that they'll be picked up by i18n. @@ -146,6 +147,7 @@ export default class LoginComponent extends React.PureComponent "m.login.cas": () => this.renderSsoStep("cas"), // eslint-disable-next-line @typescript-eslint/naming-convention "m.login.sso": () => this.renderSsoStep("sso"), + "oidcNativeFlow": () => this.renderOidcNativeStep(), }; } @@ -433,7 +435,7 @@ export default class LoginComponent extends React.PureComponent if (!this.state.flows) return null; // this is the ideal order we want to show the flows in - const order = ["m.login.password", "m.login.sso"]; + const order = ["oidcNativeFlow", "m.login.password", "m.login.sso"]; const flows = filterBoolean(order.map((type) => this.state.flows?.find((flow) => flow.type === type))); return ( @@ -466,6 +468,25 @@ export default class LoginComponent extends React.PureComponent ); }; + private renderOidcNativeStep = (): React.ReactNode => { + const flow = this.state.flows!.find((flow) => flow.type === "oidcNativeFlow")! as OidcNativeFlow; + return ( + { + await startOidcLogin( + this.props.serverConfig.delegatedAuthentication!, + flow.clientId, + this.props.serverConfig.hsUrl, + ); + }} + > + {_t("Continue")} + + ); + }; + private renderSsoStep = (loginType: "cas" | "sso"): JSX.Element => { const flow = this.state.flows?.find((flow) => flow.type === "m.login." + loginType) as ISSOFlow; diff --git a/src/utils/oidc/authorize.ts b/src/utils/oidc/authorize.ts new file mode 100644 index 00000000000..22e7a11bce1 --- /dev/null +++ b/src/utils/oidc/authorize.ts @@ -0,0 +1,73 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { + AuthorizationParams, + generateAuthorizationParams, + generateAuthorizationUrl, +} from "matrix-js-sdk/src/oidc/authorize"; + +import { ValidatedDelegatedAuthConfig } from "../ValidatedServerConfig"; + +/** + * Store authorization params for retrieval when returning from OIDC OP + * @param authorizationParams from `generateAuthorizationParams` + * @param delegatedAuthConfig used for future interactions with OP + * @param clientId this client's id as registered with configured issuer + * @param homeserver target homeserver + */ +const storeAuthorizationParams = ( + { redirectUri, state, nonce, codeVerifier }: AuthorizationParams, + { issuer }: ValidatedDelegatedAuthConfig, + clientId: string, + homeserver: string, +): void => { + window.sessionStorage.setItem(`oidc_${state}_nonce`, nonce); + window.sessionStorage.setItem(`oidc_${state}_redirectUri`, redirectUri); + window.sessionStorage.setItem(`oidc_${state}_codeVerifier`, codeVerifier); + window.sessionStorage.setItem(`oidc_${state}_clientId`, clientId); + window.sessionStorage.setItem(`oidc_${state}_issuer`, issuer); + window.sessionStorage.setItem(`oidc_${state}_homeserver`, homeserver); +}; + +/** + * Start OIDC authorization code flow + * Generates auth params, stores them in session storage and + * Navigates to configured authorization endpoint + * @param delegatedAuthConfig from discovery + * @param clientId this client's id as registered with configured issuer + * @param homeserver target homeserver + * @returns Promise that resolves after we have navigated to auth endpoint + */ +export const startOidcLogin = async ( + delegatedAuthConfig: ValidatedDelegatedAuthConfig, + clientId: string, + homeserver: string, +): Promise => { + // TODO(kerrya) afterloginfragment https://github.com/vector-im/element-web/issues/25656 + const redirectUri = window.location.origin; + const authParams = generateAuthorizationParams({ redirectUri }); + + storeAuthorizationParams(authParams, delegatedAuthConfig, clientId, homeserver); + + const authorizationUrl = await generateAuthorizationUrl( + delegatedAuthConfig.authorizationEndpoint, + clientId, + authParams, + ); + + window.location.href = authorizationUrl; +}; diff --git a/src/utils/oidc/registerClient.ts b/src/utils/oidc/registerClient.ts index f292bf5a80d..4e2df7832c2 100644 --- a/src/utils/oidc/registerClient.ts +++ b/src/utils/oidc/registerClient.ts @@ -44,7 +44,6 @@ const getStaticOidcClientId = (issuer: string, staticOidcClients?: Record, diff --git a/test/components/structures/auth/Login-test.tsx b/test/components/structures/auth/Login-test.tsx index 29a1bfa08ee..dbb5bf0f90e 100644 --- a/test/components/structures/auth/Login-test.tsx +++ b/test/components/structures/auth/Login-test.tsx @@ -409,7 +409,7 @@ describe("Login", function () { }); // short term during active development, UI will be added in next PRs - it("should show error when oidc native flow is correctly configured but not supported by UI", async () => { + it("should show continue button when oidc native flow is correctly configured", async () => { fetchMock.post(delegatedAuth.registrationEndpoint, { client_id: "abc123" }); getComponent(hsUrl, isUrl, delegatedAuth); @@ -417,10 +417,7 @@ describe("Login", function () { // did not continue with matrix login expect(mockClient.loginFlows).not.toHaveBeenCalled(); - // no oidc native UI yet - expect( - screen.getByText("This homeserver doesn't offer any login flows which are supported by this client."), - ).toBeInTheDocument(); + expect(screen.getByText("Continue")).toBeInTheDocument(); }); /** diff --git a/test/utils/oidc/authorize-test.ts b/test/utils/oidc/authorize-test.ts new file mode 100644 index 00000000000..5abdb19862a --- /dev/null +++ b/test/utils/oidc/authorize-test.ts @@ -0,0 +1,102 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import fetchMockJest from "fetch-mock-jest"; +import * as randomStringUtils from "matrix-js-sdk/src/randomstring"; + +import { startOidcLogin } from "../../../src/utils/oidc/authorize"; + +describe("startOidcLogin()", () => { + const issuer = "https://auth.com/"; + const authorizationEndpoint = "https://auth.com/authorization"; + const homeserver = "https://matrix.org"; + const clientId = "xyz789"; + const baseUrl = "https://test.com"; + + const delegatedAuthConfig = { + issuer, + registrationEndpoint: issuer + "registration", + authorizationEndpoint, + tokenEndpoint: issuer + "token", + }; + + const sessionStorageGetSpy = jest.spyOn(sessionStorage.__proto__, "setItem").mockReturnValue(undefined); + const randomStringMockImpl = (length: number) => new Array(length).fill("x").join(""); + + // to restore later + const realWindowLocation = window.location; + + beforeEach(() => { + fetchMockJest.mockClear(); + fetchMockJest.resetBehavior(); + + sessionStorageGetSpy.mockClear(); + + // @ts-ignore allow delete of non-optional prop + delete window.location; + // @ts-ignore ugly mocking + window.location = { + href: baseUrl, + origin: baseUrl, + }; + + jest.spyOn(randomStringUtils, "randomString").mockRestore(); + }); + + afterAll(() => { + window.location = realWindowLocation; + }); + + it("should store authorization params in session storage", async () => { + jest.spyOn(randomStringUtils, "randomString").mockReset().mockImplementation(randomStringMockImpl); + await startOidcLogin(delegatedAuthConfig, clientId, homeserver); + + const state = randomStringUtils.randomString(8); + + expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_nonce`, randomStringUtils.randomString(8)); + expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_redirectUri`, baseUrl); + expect(sessionStorageGetSpy).toHaveBeenCalledWith( + `oidc_${state}_codeVerifier`, + randomStringUtils.randomString(64), + ); + expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_clientId`, clientId); + expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_issuer`, issuer); + expect(sessionStorageGetSpy).toHaveBeenCalledWith(`oidc_${state}_homeserver`, homeserver); + }); + + it("navigates to authorization endpoint with correct parameters", async () => { + await startOidcLogin(delegatedAuthConfig, clientId, homeserver); + + const expectedScopeWithoutDeviceId = `openid urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:`; + + const authUrl = new URL(window.location.href); + + expect(authUrl.searchParams.get("response_mode")).toEqual("query"); + expect(authUrl.searchParams.get("response_type")).toEqual("code"); + expect(authUrl.searchParams.get("client_id")).toEqual(clientId); + expect(authUrl.searchParams.get("code_challenge_method")).toEqual("S256"); + + // scope ends with a 10char randomstring deviceId + const scope = authUrl.searchParams.get("scope")!; + expect(scope.substring(0, scope.length - 10)).toEqual(expectedScopeWithoutDeviceId); + expect(scope.substring(scope.length - 10)).toBeTruthy(); + + // random string, just check they are set + expect(authUrl.searchParams.has("state")).toBeTruthy(); + expect(authUrl.searchParams.has("nonce")).toBeTruthy(); + expect(authUrl.searchParams.has("code_challenge")).toBeTruthy(); + }); +}); From 879832a4eb0bfaaa3c44f2e6293d6cd4a9260877 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 29 Jun 2023 09:19:18 +0100 Subject: [PATCH 20/93] Fix spurious error sending receipt in thread errors (#11157) Trying to send an RR to the first event fails in threads as that event is a thread root and cannot carry a threaded RR so instead target the last event --- src/components/structures/TimelinePanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index d9dfa64493c..061aa9d10e0 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -1135,7 +1135,7 @@ class TimelinePanel extends React.Component { const lastReadEventIndex = this.getLastDisplayedEventIndex({ ignoreOwn: true, }); - const lastReadEvent: MatrixEvent | null = this.state.events[lastReadEventIndex ?? 0] ?? null; + const lastReadEvent = this.state.events[lastReadEventIndex ?? this.state.events.length - 1] ?? null; const shouldSendReadReceipt = this.shouldSendReadReceipt( currentReadReceiptEventId, From ce479c57747ac7a953c166bb4219350c9dc27cf9 Mon Sep 17 00:00:00 2001 From: alunturner <56027671+alunturner@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:20:24 +0100 Subject: [PATCH 21/93] comply with `strict` and `strictNullChecks` (#11161) --- src/Searching.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Searching.ts b/src/Searching.ts index feb56cb3fe7..894be0cf9ca 100644 --- a/src/Searching.ts +++ b/src/Searching.ts @@ -29,6 +29,7 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { ISearchArgs } from "./indexing/BaseEventIndexManager"; import EventIndexPeg from "./indexing/EventIndexPeg"; +import { isNotUndefined } from "./Typeguards"; const SEARCH_LIMIT = 10; @@ -468,8 +469,8 @@ function combineEvents( */ function combineResponses( previousSearchResult: ISeshatSearchResults, - localEvents: IResultRoomEvents, - serverEvents: IResultRoomEvents, + localEvents?: IResultRoomEvents, + serverEvents?: IResultRoomEvents, ): IResultRoomEvents { // Combine our events first. const response = combineEvents(previousSearchResult, localEvents, serverEvents); @@ -480,11 +481,14 @@ function combineResponses( if (previousSearchResult.count) { response.count = previousSearchResult.count; } else { - response.count = localEvents.count + serverEvents.count; + const localEventCount = localEvents?.count ?? 0; + const serverEventCount = serverEvents?.count ?? 0; + + response.count = localEventCount + serverEventCount; } // Update our next batch tokens for the given search sources. - if (localEvents) { + if (localEvents && isNotUndefined(previousSearchResult.seshatQuery)) { previousSearchResult.seshatQuery.next_batch = localEvents.next_batch; } if (serverEvents) { @@ -505,7 +509,11 @@ function combineResponses( // pagination request. // // Provide a fake next batch token for that case. - if (!response.next_batch && previousSearchResult.cachedEvents.length > 0) { + if ( + !response.next_batch && + isNotUndefined(previousSearchResult.cachedEvents) && + previousSearchResult.cachedEvents.length > 0 + ) { response.next_batch = "cached"; } @@ -565,16 +573,12 @@ async function combinedPagination( // Fetch events from the server if we have a token for it and if it's the // local indexes turn or the local index has exhausted its results. - if (searchResult.serverSideNextBatch && (oldestEventFrom === "local" || !searchArgs.next_batch)) { + if (searchResult.serverSideNextBatch && (oldestEventFrom === "local" || !searchArgs?.next_batch)) { const body = { body: searchResult._query!, next_batch: searchResult.serverSideNextBatch }; serverSideResult = await client.search(body); } - let serverEvents: IResultRoomEvents | undefined; - - if (serverSideResult) { - serverEvents = serverSideResult.search_categories.room_events; - } + const serverEvents: IResultRoomEvents | undefined = serverSideResult?.search_categories.room_events; // Combine our events. const combinedResult = combineResponses(searchResult, localResult, serverEvents); From 9c7d935aae8a86c5b4970600b443a12a4a7bd77e Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 29 Jun 2023 11:30:25 +0100 Subject: [PATCH 22/93] Compound Typography pass (#11103) * Integrate compound design tokens The icons should not be included in this repo, and should live in the compound design token repo, but for simplicity sake at this phase of the integration they will be added here * Delete unused or incorrect - sass variables * Typography pass * Deprecate _font-weights.pcss and use Compound instead * lint fix * Fix snapshot * Fix typography pass feedback * Remove unwanted e2e test cypress tests should test functionality not visual output. And we should not test visual output by inspecting CSS properties * lintfix * Migration script for baseFontSize * Updates after design review * Update font scaling panel to use min/max size * Fix custom font * Fix font slider e2e test * Update custom font * Update new baseFontSizeV2 * Disambiguate heading props * Fix appearance test * change max font size * fix e2ee test * fix tests * test baseFontSize migration code * typescript strict * Migrate baseFontSize account setting * Change assertion for font size * Fix font size controller test --- cypress/e2e/right-panel/file-panel.spec.ts | 16 ---- .../appearance-user-settings-tab.spec.ts | 12 ++- cypress/e2e/timeline/timeline.spec.ts | 14 --- res/css/_common.pcss | 34 +++---- res/css/_components.pcss | 1 - res/css/_font-sizes.pcss | 88 ++++++----------- res/css/_font-weights.pcss | 20 ---- .../views/beacon/_BeaconListItem.pcss | 2 +- .../views/beacon/_DialogSidebar.pcss | 2 +- .../views/beacon/_OwnBeaconStatus.pcss | 2 +- .../views/elements/_AppPermission.pcss | 4 +- .../views/elements/_FilterDropdown.pcss | 2 +- .../views/elements/_FilterTabGroup.pcss | 2 +- res/css/components/views/pips/_WidgetPip.pcss | 2 +- .../settings/devices/_DeviceSecurityCard.pcss | 2 +- .../views/spaces/_QuickThemeSwitcher.pcss | 2 +- .../components/views/typography/_Caption.pcss | 2 +- res/css/structures/_ContextualMenu.pcss | 1 - res/css/structures/_FilePanel.pcss | 6 +- res/css/structures/_GenericDropdownMenu.pcss | 2 +- res/css/structures/_HomePage.pcss | 8 +- res/css/structures/_LargeLoader.pcss | 2 +- res/css/structures/_QuickSettingsButton.pcss | 4 +- res/css/structures/_RightPanel.pcss | 4 +- res/css/structures/_RoomSearch.pcss | 6 +- res/css/structures/_RoomStatusBar.pcss | 4 +- res/css/structures/_SpaceHierarchy.pcss | 10 +- res/css/structures/_SpacePanel.pcss | 9 +- res/css/structures/_SpaceRoomView.pcss | 6 +- res/css/structures/_ToastContainer.pcss | 6 +- res/css/structures/_UserMenu.pcss | 13 +-- res/css/structures/auth/_Login.pcss | 2 +- res/css/structures/auth/_Registration.pcss | 2 +- res/css/views/auth/_AuthBody.pcss | 15 ++- res/css/views/auth/_AuthFooter.pcss | 2 +- res/css/views/auth/_CompleteSecurityBody.pcss | 5 +- res/css/views/auth/_LanguageSelector.pcss | 3 +- res/css/views/auth/_LoginWithQR.pcss | 4 +- res/css/views/beta/_BetaCard.pcss | 10 +- .../context_menus/_IconizedContextMenu.pcss | 5 +- .../dialogs/_AddExistingToSpaceDialog.pcss | 6 +- res/css/views/dialogs/_CompoundDialog.pcss | 2 +- .../dialogs/_ConfirmUserActionDialog.pcss | 2 +- res/css/views/dialogs/_CreateRoomDialog.pcss | 4 +- res/css/views/dialogs/_ExportDialog.pcss | 2 +- res/css/views/dialogs/_FeedbackDialog.pcss | 2 +- res/css/views/dialogs/_ForwardDialog.pcss | 2 +- .../_GenericFeatureFeedbackDialog.pcss | 2 +- res/css/views/dialogs/_InviteDialog.pcss | 10 +- res/css/views/dialogs/_JoinRuleDropdown.pcss | 6 +- .../_ManageRestrictedJoinRuleDialog.pcss | 2 +- .../dialogs/_MessageEditHistoryDialog.pcss | 2 +- res/css/views/dialogs/_PollCreateDialog.pcss | 2 +- .../dialogs/_RoomSettingsDialogBridges.pcss | 2 +- .../views/dialogs/_ServerPickerDialog.pcss | 5 +- .../views/dialogs/_SpaceSettingsDialog.pcss | 6 +- res/css/views/dialogs/_SpotlightDialog.pcss | 2 +- res/css/views/dialogs/_VerifyEMailDialog.pcss | 4 +- .../security/_CreateSecretStorageDialog.pcss | 4 +- res/css/views/elements/_AccessibleButton.pcss | 4 +- res/css/views/elements/_ExternalLink.pcss | 2 +- res/css/views/elements/_FacePile.pcss | 2 +- res/css/views/elements/_Field.pcss | 8 +- .../elements/_GenericEventListSummary.pcss | 13 ++- res/css/views/elements/_RoomAliasField.pcss | 2 +- res/css/views/elements/_SSOButtons.pcss | 3 +- res/css/views/elements/_ServerPicker.pcss | 5 +- res/css/views/elements/_SettingsFlag.pcss | 4 +- res/css/views/elements/_Slider.pcss | 2 +- res/css/views/elements/_StyledCheckbox.pcss | 5 +- .../views/elements/_StyledRadioButton.pcss | 2 +- res/css/views/elements/_Tooltip.pcss | 2 +- res/css/views/elements/_UseCaseSelection.pcss | 2 +- res/css/views/emojipicker/_EmojiPicker.pcss | 4 +- res/css/views/messages/_CallEvent.pcss | 2 +- res/css/views/messages/_DateSeparator.pcss | 2 +- .../views/messages/_DisambiguatedProfile.pcss | 5 +- res/css/views/messages/_EventTileBubble.pcss | 2 +- res/css/views/messages/_LegacyCallEvent.pcss | 2 +- res/css/views/messages/_MPollBody.pcss | 2 +- res/css/views/messages/_MediaBody.pcss | 2 +- res/css/views/right_panel/_BaseCard.pcss | 15 ++- .../views/right_panel/_RoomSummaryCard.pcss | 7 +- res/css/views/right_panel/_ThreadPanel.pcss | 4 +- res/css/views/right_panel/_UserInfo.pcss | 15 ++- .../views/right_panel/_VerificationPanel.pcss | 2 +- .../views/room_settings/_AliasSettings.pcss | 2 +- .../views/rooms/_DecryptionFailureBar.pcss | 2 +- res/css/views/rooms/_EntityTile.pcss | 2 +- res/css/views/rooms/_EventTile.pcss | 9 +- res/css/views/rooms/_MemberInfo.pcss | 2 +- res/css/views/rooms/_MemberList.pcss | 4 +- res/css/views/rooms/_MessageComposer.pcss | 4 +- .../rooms/_MessageComposerFormatBar.pcss | 2 +- res/css/views/rooms/_NewRoomIntro.pcss | 2 +- res/css/views/rooms/_PinnedEventTile.pcss | 2 +- res/css/views/rooms/_ReadReceiptGroup.pcss | 2 +- res/css/views/rooms/_ReplyTile.pcss | 4 +- res/css/views/rooms/_RoomBreadcrumbs.pcss | 3 +- res/css/views/rooms/_RoomCallBanner.pcss | 2 +- res/css/views/rooms/_RoomHeader.pcss | 12 +-- res/css/views/rooms/_RoomListHeader.pcss | 5 +- res/css/views/rooms/_RoomPreviewBar.pcss | 4 +- res/css/views/rooms/_RoomPreviewCard.pcss | 4 +- res/css/views/rooms/_RoomSublist.pcss | 6 +- res/css/views/rooms/_RoomTile.pcss | 5 +- res/css/views/rooms/_SearchBar.pcss | 2 +- res/css/views/rooms/_SendMessageComposer.pcss | 2 +- res/css/views/rooms/_ThreadSummary.pcss | 2 +- res/css/views/rooms/_WhoIsTypingTile.pcss | 3 +- .../_SendWysiwygComposer.pcss | 2 +- res/css/views/settings/_JoinRuleSettings.pcss | 7 +- res/css/views/settings/_Notifications.pcss | 5 +- res/css/views/settings/_ProfileSettings.pcss | 2 +- res/css/views/settings/_SettingsFieldset.pcss | 6 +- res/css/views/settings/_ThemeChoicePanel.pcss | 2 +- res/css/views/settings/tabs/_SettingsTab.pcss | 2 +- .../tabs/room/_NotificationSettingsTab.pcss | 2 +- res/css/views/spaces/_SpaceBasicSettings.pcss | 2 +- res/css/views/spaces/_SpaceCreateMenu.pcss | 2 +- .../views/terms/_InlineTermsAgreement.pcss | 2 +- res/css/views/toasts/_AnalyticsToast.pcss | 4 +- res/css/views/toasts/_IncomingCallToast.pcss | 2 +- res/css/views/typography/_Heading.pcss | 34 +++---- .../_UserOnboardingButton.pcss | 2 +- res/css/views/voip/_DialPad.pcss | 2 +- res/css/views/voip/_DialPadContextMenu.pcss | 6 +- res/css/views/voip/_DialPadModal.pcss | 4 +- res/css/voice-broadcast/atoms/_LiveBadge.pcss | 2 +- .../atoms/_VoiceBroadcastHeader.pcss | 2 +- .../structures/NotificationPanel.tsx | 2 +- src/components/structures/ThreadPanel.tsx | 2 +- src/components/structures/ThreadView.tsx | 2 +- src/components/views/beacon/DialogSidebar.tsx | 2 +- .../views/dialogs/AppDownloadDialog.tsx | 6 +- src/components/views/dialogs/BaseDialog.tsx | 3 +- .../views/elements/AppPermission.tsx | 2 +- .../views/location/EnableLiveShare.tsx | 2 +- src/components/views/location/MapError.tsx | 2 +- src/components/views/location/ShareType.tsx | 2 +- .../views/polls/pollHistory/PollHistory.tsx | 2 +- .../views/right_panel/PinnedMessagesCard.tsx | 4 +- .../views/right_panel/TimelineCard.tsx | 2 +- .../views/right_panel/WidgetCard.tsx | 2 +- .../views/settings/FontScalingPanel.tsx | 21 ++-- .../views/settings/IntegrationManager.tsx | 4 +- .../views/settings/SetIntegrationManager.tsx | 4 +- .../settings/devices/DeviceDetailHeading.tsx | 2 +- .../views/settings/devices/DeviceTile.tsx | 2 +- .../views/settings/shared/SettingsSection.tsx | 2 +- .../shared/SettingsSubsectionHeading.tsx | 2 +- .../tabs/room/NotificationSettingsTab.tsx | 2 +- .../tabs/user/GeneralUserSettingsTab.tsx | 2 +- src/components/views/typography/Heading.tsx | 19 +++- .../user-onboarding/UserOnboardingButton.tsx | 2 +- .../user-onboarding/UserOnboardingHeader.tsx | 2 +- .../user-onboarding/UserOnboardingList.tsx | 2 +- .../user-onboarding/UserOnboardingTask.tsx | 2 +- src/dispatcher/actions.ts | 5 + src/models/Call.ts | 2 +- src/settings/Settings.tsx | 12 +++ .../controllers/FontSizeController.ts | 18 +++- src/settings/watchers/FontWatcher.ts | 95 ++++++++++++++----- .../__snapshots__/MatrixChat-test.tsx.snap | 4 +- .../ChangelogDialog-test.tsx.snap | 2 +- .../DevtoolsDialog-test.tsx.snap | 2 +- .../__snapshots__/ExportDialog-test.tsx.snap | 2 +- .../FeedbackDialog-test.tsx.snap | 2 +- ...lDeviceKeyVerificationDialog-test.tsx.snap | 4 +- .../MessageEditHistoryDialog-test.tsx.snap | 4 +- .../ServerPickerDialog-test.tsx.snap | 2 +- .../CreateKeyBackupDialog-test.tsx.snap | 6 +- .../ImportE2eKeysDialog-test.tsx.snap | 2 +- .../views/settings/FontScalingPanel-test.tsx | 4 +- .../FontScalingPanel-test.tsx.snap | 16 ++-- .../ThemeChoicePanel-test.tsx.snap | 2 +- .../CurrentDeviceSection-test.tsx.snap | 6 +- .../LoginWithQRSection-test.tsx.snap | 4 +- .../SecurityRecommendations-test.tsx.snap | 6 +- .../EmailAddresses-test.tsx.snap | 6 +- .../__snapshots__/PhoneNumbers-test.tsx.snap | 6 +- .../SettingsSubsection-test.tsx.snap | 8 +- .../SettingsSubsectionHeading-test.tsx.snap | 4 +- .../AdvancedRoomSettingsTab-test.tsx.snap | 4 +- .../SecurityRoomSettingsTab-test.tsx.snap | 4 +- .../AppearanceUserSettingsTab-test.tsx.snap | 22 ++--- .../GeneralUserSettingsTab-test.tsx.snap | 2 +- .../KeyboardUserSettingsTab-test.tsx.snap | 14 +-- .../MjolnirUserSettingsTab-test.tsx.snap | 4 +- .../PreferencesUserSettingsTab-test.tsx.snap | 22 ++--- .../SecurityUserSettingsTab-test.tsx.snap | 12 +-- .../SessionManagerTab-test.tsx.snap | 4 +- .../SidebarUserSettingsTab-test.tsx.snap | 2 +- .../AddExistingToSpaceDialog-test.tsx.snap | 2 +- .../views/typography/Heading-test.tsx | 14 ++- .../__snapshots__/Heading-test.tsx.snap | 13 +++ test/models/Call-test.ts | 4 +- .../controllers/FontSizeController-test.ts | 7 +- test/settings/watchers/FontWatcher-test.tsx | 55 ++++++++--- 199 files changed, 606 insertions(+), 608 deletions(-) delete mode 100644 res/css/_font-weights.pcss diff --git a/cypress/e2e/right-panel/file-panel.spec.ts b/cypress/e2e/right-panel/file-panel.spec.ts index b36edfb276f..fa436772bab 100644 --- a/cypress/e2e/right-panel/file-panel.spec.ts +++ b/cypress/e2e/right-panel/file-panel.spec.ts @@ -238,22 +238,6 @@ describe("FilePanel", () => { }); }); }); - - it("should not add inline padding to a tile when it is selected with right click", () => { - // Upload a file - uploadFile("cypress/fixtures/1sec.ogg"); - - cy.get(".mx_FilePanel .mx_RoomView_MessageList").within(() => { - // Wait until the spinner of the audio player vanishes - cy.get(".mx_InlineSpinner").should("not.exist"); - - // Right click the uploaded file to select the tile - cy.get(".mx_EventTile").rightclick(); - - // Assert that inline padding is not applied - cy.get(".mx_EventTile_selected .mx_EventTile_line").should("have.css", "padding-inline", "0px"); - }); - }); }); describe("download", () => { diff --git a/cypress/e2e/settings/appearance-user-settings-tab.spec.ts b/cypress/e2e/settings/appearance-user-settings-tab.spec.ts index cb22d26b58b..e57f00a1396 100644 --- a/cypress/e2e/settings/appearance-user-settings-tab.spec.ts +++ b/cypress/e2e/settings/appearance-user-settings-tab.spec.ts @@ -122,9 +122,10 @@ describe("Appearance user settings tab", () => { // Click the left position of the slider cy.get("input").realClick({ position: "left" }); + const MIN_FONT_SIZE = 11; // Assert that the smallest font size is selected - cy.get("input[value='13']").should("exist"); - cy.get("output .mx_Slider_selection_label").findByText("13"); + cy.get(`input[value='${MIN_FONT_SIZE}']`).should("exist"); + cy.get("output .mx_Slider_selection_label").findByText(MIN_FONT_SIZE); }); cy.get(".mx_FontScalingPanel_fontSlider").percySnapshotElement("Font size slider - smallest (13)", { @@ -135,12 +136,13 @@ describe("Appearance user settings tab", () => { // Click the right position of the slider cy.get("input").realClick({ position: "right" }); + const MAX_FONT_SIZE = 21; // Assert that the largest font size is selected - cy.get("input[value='18']").should("exist"); - cy.get("output .mx_Slider_selection_label").findByText("18"); + cy.get(`input[value='${MAX_FONT_SIZE}']`).should("exist"); + cy.get("output .mx_Slider_selection_label").findByText(MAX_FONT_SIZE); }); - cy.get(".mx_FontScalingPanel_fontSlider").percySnapshotElement("Font size slider - largest (18)", { + cy.get(".mx_FontScalingPanel_fontSlider").percySnapshotElement("Font size slider - largest (21)", { widths: [486], }); }); diff --git a/cypress/e2e/timeline/timeline.spec.ts b/cypress/e2e/timeline/timeline.spec.ts index 10650d14f4e..6acf8a19b2f 100644 --- a/cypress/e2e/timeline/timeline.spec.ts +++ b/cypress/e2e/timeline/timeline.spec.ts @@ -204,13 +204,6 @@ describe("Timeline", () => { cy.findByRole("button", { name: "collapse" }).should("exist"); }); - // Check the height of expanded GELS line - cy.get(".mx_GenericEventListSummary[data-layout=irc] .mx_GenericEventListSummary_spacer").should( - "have.css", - "line-height", - "18px", // var(--irc-line-height): $font-18px (See: _IRCLayout.pcss) - ); - cy.get(".mx_MainSplit").percySnapshotElement("Expanded GELS on IRC layout", { percyCSS }); }); @@ -238,13 +231,6 @@ describe("Timeline", () => { cy.findByRole("button", { name: "collapse" }).should("exist"); }); - // Check the height of expanded GELS line - cy.get(".mx_GenericEventListSummary[data-layout=group] .mx_GenericEventListSummary_spacer").should( - "have.css", - "line-height", - "22px", // $font-22px (See: _GenericEventListSummary.pcss) - ); - cy.get(".mx_MainSplit").percySnapshotElement("Expanded GELS on modern layout", { percyCSS }); }); diff --git a/res/css/_common.pcss b/res/css/_common.pcss index b0d2a1d2090..88f7de5e038 100644 --- a/res/css/_common.pcss +++ b/res/css/_common.pcss @@ -2,7 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2017 - 2019 New Vector Ltd -Copyright 2019 - 2021 The Matrix.org Foundation C.I.C +Copyright 2019 - 2023 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. @@ -19,7 +19,6 @@ limitations under the License. @import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css"); @import "./_font-sizes.pcss"; -@import "./_font-weights.pcss"; @import "./_animations.pcss"; @import "./_spacing.pcss"; @import url("maplibre-gl/dist/maplibre-gl.css"); @@ -77,8 +76,9 @@ html { } body { - font-family: $font-family; - font-size: $font-15px; + font: var(--cpd-font-body-md-regular); + letter-spacing: var(--cpd-font-letter-spacing-body-md); + background-color: $background; color: $primary-content; border: 0px; @@ -119,8 +119,8 @@ b { h2 { color: $primary-content; - font-weight: 400; - font-size: $font-18px; + font: var(--cpd-font-heading-lg-regular); + letter-spacing: var(--cpd-font-letter-spacing-heading-lg); margin-top: 16px; margin-bottom: 16px; } @@ -134,10 +134,9 @@ a:visited { input[type="text"], input[type="search"], input[type="password"] { - font-family: inherit; padding: 9px; - font-size: $font-14px; - font-weight: var(--font-semi-bold); + font: var(--cpd-font-body-md-semibold); + font-weight: var(--cpd-font-weight-semibold); min-width: 0; } @@ -337,11 +336,9 @@ legend { /* Styles copied/inspired by GroupLayout, ReplyTile, and EventTile variants. */ .markdown-body { - font-family: inherit !important; - white-space: normal !important; - line-height: inherit !important; + font: var(--cpd-font-body-md-regular) !important; + letter-spacing: var(--cpd-font-letter-spacing-body-md); color: inherit; /* inherit the colour from the dark or light theme by default (but not for code blocks) */ - font-size: $font-14px; pre, code { @@ -498,7 +495,7 @@ legend { .mx_Dialog_content { margin: 24px 0 68px; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); color: $primary-content; word-wrap: break-word; } @@ -535,8 +532,7 @@ legend { vertical-align: middle; border: 0px; border-radius: 8px; - font-family: $font-family; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); color: $button-fg-color; background-color: $accent; width: auto; @@ -570,7 +566,7 @@ legend { margin-bottom: 5px; /* flip colours for the secondary ones */ - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); border: 1px solid $accent; color: $accent; background-color: $button-secondary-bg-color; @@ -758,7 +754,7 @@ legend { @define-mixin LegacyCallButton { box-sizing: border-box; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); height: $font-24px; line-height: $font-24px; margin-right: 0; @@ -780,7 +776,7 @@ legend { @define-mixin ThreadRepliesAmount { color: $secondary-content; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); white-space: nowrap; position: relative; padding: 0 $spacing-12 0 $spacing-8; diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 3e8e4cd80b7..2851313c54e 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -2,7 +2,6 @@ @import "./_animations.pcss"; @import "./_common.pcss"; @import "./_font-sizes.pcss"; -@import "./_font-weights.pcss"; @import "./_spacing.pcss"; @import "./components/views/beacon/_BeaconListItem.pcss"; @import "./components/views/beacon/_BeaconStatus.pcss"; diff --git a/res/css/_font-sizes.pcss b/res/css/_font-sizes.pcss index 5d83ff83df6..bb447ebfa0b 100644 --- a/res/css/_font-sizes.pcss +++ b/res/css/_font-sizes.pcss @@ -21,63 +21,33 @@ limitations under the License. * "Font size" setting). They exist to make the job of converting designs (which tend to be based in pixels) into CSS * easier. * - * That means that, slightly confusingly, `$font-10px` is only *actually* 10px at the default font size: at a base - * `font-size` of 15, it is actually 15px. */ -$font-1px: 0.1rem; -$font-1-5px: 0.15rem; -$font-2px: 0.2rem; -$font-3px: 0.3rem; -$font-4px: 0.4rem; -$font-5px: 0.5rem; -$font-6px: 0.6rem; -$font-7px: 0.7rem; -$font-8px: 0.8rem; -$font-9px: 0.9rem; -$font-10px: 1rem; -$font-10-4px: 1.04rem; -$font-11px: 1.1rem; -$font-12px: 1.2rem; -$font-13px: 1.3rem; -$font-14px: 1.4rem; -$font-15px: 1.5rem; -$font-16px: 1.6rem; -$font-17px: 1.7rem; -$font-18px: 1.8rem; -$font-19px: 1.9rem; -$font-20px: 2rem; -$font-21px: 2.1rem; -$font-22px: 2.2rem; -$font-23px: 2.3rem; -$font-24px: 2.4rem; -$font-25px: 2.5rem; -$font-26px: 2.6rem; -$font-27px: 2.7rem; -$font-28px: 2.8rem; -$font-29px: 2.9rem; -$font-30px: 3rem; -$font-31px: 3.1rem; -$font-32px: 3.2rem; -$font-33px: 3.3rem; -$font-34px: 3.4rem; -$font-35px: 3.5rem; -$font-36px: 3.6rem; -$font-37px: 3.7rem; -$font-38px: 3.8rem; -$font-39px: 3.9rem; -$font-40px: 4rem; -$font-41px: 4.1rem; -$font-42px: 4.2rem; -$font-43px: 4.3rem; -$font-44px: 4.4rem; -$font-45px: 4.5rem; -$font-46px: 4.6rem; -$font-47px: 4.7rem; -$font-48px: 4.8rem; -$font-49px: 4.9rem; -$font-50px: 5rem; -$font-51px: 5.1rem; -$font-52px: 5.2rem; -$font-78px: 7.8rem; -$font-88px: 8.8rem; -$font-400px: 40rem; +$font-1px: 0.0625rem; +$font-8px: 0.5rem; +$font-9px: 0.5625rem; +$font-10px: 0.625rem; +$font-10-4px: 0.6275rem; +$font-11px: 0.6875rem; +$font-12px: 0.75rem; +$font-13px: 0.8125rem; +$font-14px: 0.875rem; +$font-15px: 0.9375rem; +$font-16px: 1rem; +$font-17px: 1.0625rem; +$font-18px: 1.125rem; +$font-20px: 1.25rem; +$font-22px: 1.375rem; +$font-23px: 1.4375rem; +$font-24px: 1.5rem; +$font-25px: 1.5625rem; +$font-26px: 1.625rem; +$font-28px: 1.75rem; +$font-29px: 1.8125rem; +$font-30px: 1.875rem; +$font-32px: 2rem; +$font-34px: 2.125rem; +$font-35px: 2.1875rem; +$font-39px: 2.4375rem; +$font-42px: 2.625rem; +$font-44px: 2.75rem; +$font-48px: 3rem; diff --git a/res/css/_font-weights.pcss b/res/css/_font-weights.pcss deleted file mode 100644 index 7931d6a56a5..00000000000 --- a/res/css/_font-weights.pcss +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -:root { - --font-normal: 400; - --font-semi-bold: 600; -} diff --git a/res/css/components/views/beacon/_BeaconListItem.pcss b/res/css/components/views/beacon/_BeaconListItem.pcss index c9b39bbebf4..3389ccc3a2f 100644 --- a/res/css/components/views/beacon/_BeaconListItem.pcss +++ b/res/css/components/views/beacon/_BeaconListItem.pcss @@ -55,7 +55,7 @@ limitations under the License. margin-bottom: $spacing-8; .mx_BeaconStatus_label { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); } } diff --git a/res/css/components/views/beacon/_DialogSidebar.pcss b/res/css/components/views/beacon/_DialogSidebar.pcss index c33f74e0366..31d3b7b16d5 100644 --- a/res/css/components/views/beacon/_DialogSidebar.pcss +++ b/res/css/components/views/beacon/_DialogSidebar.pcss @@ -57,6 +57,6 @@ limitations under the License. } .mx_DialogSidebar_noResults { - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); color: $secondary-content; } diff --git a/res/css/components/views/beacon/_OwnBeaconStatus.pcss b/res/css/components/views/beacon/_OwnBeaconStatus.pcss index dedf02da7a6..a0776b942aa 100644 --- a/res/css/components/views/beacon/_OwnBeaconStatus.pcss +++ b/res/css/components/views/beacon/_OwnBeaconStatus.pcss @@ -27,5 +27,5 @@ limitations under the License. .mx_OwnBeaconStatus_destructiveButton { /* override button link_inline styles */ color: $alert !important; - font-weight: var(--font-semi-bold) !important; + font-weight: var(--cpd-font-weight-semibold) !important; } diff --git a/res/css/components/views/elements/_AppPermission.pcss b/res/css/components/views/elements/_AppPermission.pcss index 71f282ebeee..4bbe0ac07a7 100644 --- a/res/css/components/views/elements/_AppPermission.pcss +++ b/res/css/components/views/elements/_AppPermission.pcss @@ -19,7 +19,9 @@ limitations under the License. font-size: $font-12px; width: 100%; /* make mx_AppPermission fill width of mx_AppTileBody so that scroll bar appears on the edge */ overflow-y: scroll; - + .mx_AppPermission_bolder { + font-weight: var(--cpd-font-weight-semibold); + } .mx_AppPermission_content { margin-block: auto; /* place at the center */ diff --git a/res/css/components/views/elements/_FilterDropdown.pcss b/res/css/components/views/elements/_FilterDropdown.pcss index a73a45c03ee..dc264ca9226 100644 --- a/res/css/components/views/elements/_FilterDropdown.pcss +++ b/res/css/components/views/elements/_FilterDropdown.pcss @@ -72,7 +72,7 @@ limitations under the License. } .mx_FilterDropdown_optionLabel { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); display: block; } diff --git a/res/css/components/views/elements/_FilterTabGroup.pcss b/res/css/components/views/elements/_FilterTabGroup.pcss index 05329cb7d00..946bd7f5431 100644 --- a/res/css/components/views/elements/_FilterTabGroup.pcss +++ b/res/css/components/views/elements/_FilterTabGroup.pcss @@ -38,7 +38,7 @@ limitations under the License. &:checked + span { color: $accent; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); // underline box-shadow: 0 1.5px 0 0 currentColor; } diff --git a/res/css/components/views/pips/_WidgetPip.pcss b/res/css/components/views/pips/_WidgetPip.pcss index cecc0e1365a..f6bf5a2a63e 100644 --- a/res/css/components/views/pips/_WidgetPip.pcss +++ b/res/css/components/views/pips/_WidgetPip.pcss @@ -42,7 +42,7 @@ limitations under the License. padding: $spacing-12; display: flex; font-size: $font-12px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); background: linear-gradient(rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0)); } diff --git a/res/css/components/views/settings/devices/_DeviceSecurityCard.pcss b/res/css/components/views/settings/devices/_DeviceSecurityCard.pcss index c2a0d5bb780..e161c0b14aa 100644 --- a/res/css/components/views/settings/devices/_DeviceSecurityCard.pcss +++ b/res/css/components/views/settings/devices/_DeviceSecurityCard.pcss @@ -65,7 +65,7 @@ limitations under the License. } .mx_DeviceSecurityCard_description { margin: 0; - font-size: $font-12px; + font: var(--cpd-font-body-sm-regular); color: $secondary-content; } diff --git a/res/css/components/views/spaces/_QuickThemeSwitcher.pcss b/res/css/components/views/spaces/_QuickThemeSwitcher.pcss index a729134c124..66a97313538 100644 --- a/res/css/components/views/spaces/_QuickThemeSwitcher.pcss +++ b/res/css/components/views/spaces/_QuickThemeSwitcher.pcss @@ -30,7 +30,7 @@ limitations under the License. } .mx_QuickThemeSwitcher_heading { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-12px; line-height: $font-15px; color: $secondary-content; diff --git a/res/css/components/views/typography/_Caption.pcss b/res/css/components/views/typography/_Caption.pcss index f51276d9f96..cad93f3881a 100644 --- a/res/css/components/views/typography/_Caption.pcss +++ b/res/css/components/views/typography/_Caption.pcss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_Caption { - font-size: $font-12px; + font: var(--cpd-font-body-sm-regular); color: $secondary-content; &.mx_Caption_error { diff --git a/res/css/structures/_ContextualMenu.pcss b/res/css/structures/_ContextualMenu.pcss index cac926ec724..6aff7738fc7 100644 --- a/res/css/structures/_ContextualMenu.pcss +++ b/res/css/structures/_ContextualMenu.pcss @@ -35,7 +35,6 @@ limitations under the License. background-color: $menu-bg-color; color: $primary-content; position: absolute; - font-size: $font-14px; z-index: 5001; width: max-content; } diff --git a/res/css/structures/_FilePanel.pcss b/res/css/structures/_FilePanel.pcss index 99de3d32965..9a1ebda894e 100644 --- a/res/css/structures/_FilePanel.pcss +++ b/res/css/structures/_FilePanel.pcss @@ -70,11 +70,11 @@ limitations under the License. padding-top: $spacing-8; display: flex; justify-content: space-between; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); color: $event-timestamp-color; .mx_MImageBody_size { - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); text-align: right; white-space: nowrap; } @@ -100,7 +100,7 @@ limitations under the License. .mx_MessageTimestamp { text-align: right; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); } } } diff --git a/res/css/structures/_GenericDropdownMenu.pcss b/res/css/structures/_GenericDropdownMenu.pcss index c3740cc847d..1722c7fd2e1 100644 --- a/res/css/structures/_GenericDropdownMenu.pcss +++ b/res/css/structures/_GenericDropdownMenu.pcss @@ -92,7 +92,7 @@ limitations under the License. span:first-child { color: $primary-content; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); } } diff --git a/res/css/structures/_HomePage.pcss b/res/css/structures/_HomePage.pcss index b2495634357..b2f607f8226 100644 --- a/res/css/structures/_HomePage.pcss +++ b/res/css/structures/_HomePage.pcss @@ -37,15 +37,15 @@ limitations under the License. } h1 { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-32px; - line-height: $font-44px; + line-height: 1.375; margin-bottom: 4px; } h2 { margin-top: 4px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-18px; line-height: $font-25px; color: $muted-fg-color; @@ -73,7 +73,7 @@ limitations under the License. word-break: break-word; box-sizing: border-box; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-15px; line-height: $font-20px; color: #fff; /* on all themes */ diff --git a/res/css/structures/_LargeLoader.pcss b/res/css/structures/_LargeLoader.pcss index 555eb4bee55..55f57b02942 100644 --- a/res/css/structures/_LargeLoader.pcss +++ b/res/css/structures/_LargeLoader.pcss @@ -29,7 +29,7 @@ limitations under the License. .mx_LargeLoader_text { font-size: 24px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); padding: 0 16px; position: relative; text-align: center; diff --git a/res/css/structures/_QuickSettingsButton.pcss b/res/css/structures/_QuickSettingsButton.pcss index 3f26e132504..631b098ad64 100644 --- a/res/css/structures/_QuickSettingsButton.pcss +++ b/res/css/structures/_QuickSettingsButton.pcss @@ -59,7 +59,7 @@ limitations under the License. contain: unset; /* let the dropdown paint beyond the context menu */ > div > h2 { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-15px; line-height: $font-24px; color: $primary-content; @@ -72,7 +72,7 @@ limitations under the License. } > div > h4 { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-12px; line-height: $font-15px; text-transform: uppercase; diff --git a/res/css/structures/_RightPanel.pcss b/res/css/structures/_RightPanel.pcss index 71c98607641..7d39968c11a 100644 --- a/res/css/structures/_RightPanel.pcss +++ b/res/css/structures/_RightPanel.pcss @@ -83,7 +83,7 @@ limitations under the License. h2, p { - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); } &::before { @@ -103,7 +103,7 @@ limitations under the License. .mx_RightPanel_scopeHeader { margin: 24px; text-align: center; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-18px; line-height: $font-22px; diff --git a/res/css/structures/_RoomSearch.pcss b/res/css/structures/_RoomSearch.pcss index 8252d2d9b9a..fea292c8baf 100644 --- a/res/css/structures/_RoomSearch.pcss +++ b/res/css/structures/_RoomSearch.pcss @@ -43,15 +43,13 @@ limitations under the License. } .mx_RoomSearch_spotlightTriggerText { - font-size: $font-12px; - line-height: $font-16px; color: $tertiary-content; flex: 1; min-width: 0; /* the following rules are to match that of a real input field */ overflow: hidden; margin: 9px; - font-weight: var(--font-semi-bold); + font: var(--cpd-font-body-sm-semibold); } .mx_RoomSearch_shortcutPrompt { @@ -62,7 +60,7 @@ limitations under the License. font-size: $font-12px; line-height: $font-15px; font-family: inherit; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); color: $light-fg-color; margin-right: 6px; white-space: nowrap; diff --git a/res/css/structures/_RoomStatusBar.pcss b/res/css/structures/_RoomStatusBar.pcss index 60191c34528..e046e5f7fd7 100644 --- a/res/css/structures/_RoomStatusBar.pcss +++ b/res/css/structures/_RoomStatusBar.pcss @@ -186,7 +186,7 @@ limitations under the License. .mx_RoomStatusBar_typingBar { height: 50px; - line-height: $font-50px; + line-height: 50px; color: $primary-content; opacity: 0.5; @@ -205,6 +205,6 @@ limitations under the License. .mx_RoomStatusBar_typingBar { height: 40px; - line-height: $font-40px; + line-height: 40px; } } diff --git a/res/css/structures/_SpaceHierarchy.pcss b/res/css/structures/_SpaceHierarchy.pcss index 2dedf2099c9..81498af176a 100644 --- a/res/css/structures/_SpaceHierarchy.pcss +++ b/res/css/structures/_SpaceHierarchy.pcss @@ -46,7 +46,7 @@ limitations under the License. .mx_SpaceHierarchy_listHeader_header { grid-column-start: 1; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); margin: 0; } @@ -71,7 +71,7 @@ limitations under the License. .mx_SpaceHierarchy_error { position: relative; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); color: $alert; font-size: $font-15px; line-height: $font-18px; @@ -94,7 +94,7 @@ limitations under the License. .mx_SpaceHierarchy_roomCount { > h3 { display: inline; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-18px; line-height: $font-22px; color: $primary-content; @@ -167,7 +167,7 @@ limitations under the License. gap: 6px 12px; .mx_SpaceHierarchy_roomTile_item { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-15px; line-height: $font-18px; display: grid; @@ -233,7 +233,7 @@ limitations under the License. .mx_SpaceHierarchy_roomTile_info { grid-row: 2; grid-column: 2; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); font-weight: initial; line-height: $font-18px; color: $secondary-content; diff --git a/res/css/structures/_SpacePanel.pcss b/res/css/structures/_SpacePanel.pcss index a149eae6395..e8b8d8eb042 100644 --- a/res/css/structures/_SpacePanel.pcss +++ b/res/css/structures/_SpacePanel.pcss @@ -156,8 +156,7 @@ limitations under the License. display: block; text-overflow: ellipsis; overflow: hidden; - font-size: $font-14px; - line-height: $font-18px; + font: var(--cpd-font-body-md-regular); } .mx_SpaceButton_toggleCollapse { @@ -275,7 +274,7 @@ limitations under the License. border-radius: 8px; background-color: $panel-actions; font-size: $font-15px !important; /* override inline style */ - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); line-height: $font-18px; & + .mx_BaseAvatar_image { @@ -379,7 +378,7 @@ limitations under the License. .mx_SpacePanel_contextMenu_header { margin: 12px 16px 12px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-15px; line-height: $font-18px; overflow: hidden; @@ -431,7 +430,7 @@ limitations under the License. color: $tertiary-content; font-size: $font-10px; line-height: $font-12px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); //margin-left: 8px; } } diff --git a/res/css/structures/_SpaceRoomView.pcss b/res/css/structures/_SpaceRoomView.pcss index a83fe420a71..f1bf0cf2141 100644 --- a/res/css/structures/_SpaceRoomView.pcss +++ b/res/css/structures/_SpaceRoomView.pcss @@ -22,7 +22,7 @@ limitations under the License. border-radius: 8px; border: 1px solid $input-border-color; font-size: $font-17px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); margin: 20px 0; > div { @@ -73,7 +73,7 @@ limitations under the License. h1 { margin: 0; font-size: $font-24px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); color: $primary-content; width: max-content; } @@ -120,7 +120,7 @@ limitations under the License. } .mx_SpaceRoomView_errorText { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-12px; line-height: $font-15px; color: $alert; diff --git a/res/css/structures/_ToastContainer.pcss b/res/css/structures/_ToastContainer.pcss index c33caf27583..a485afe1291 100644 --- a/res/css/structures/_ToastContainer.pcss +++ b/res/css/structures/_ToastContainer.pcss @@ -118,8 +118,8 @@ limitations under the License. h2 { margin: 0; - font-size: $font-15px; - font-weight: var(--font-semi-bold); + font: var(--cpd-font-heading-sm-medium); + font-weight: var(--cpd-font-weight-medium); display: inline; width: auto; } @@ -153,7 +153,7 @@ limitations under the License. overflow: hidden; text-overflow: ellipsis; margin: 4px 0 11px 0; - font-size: $font-12px; + font: var(--cpd-font-body-sm-regular); a { text-decoration: none; diff --git a/res/css/structures/_UserMenu.pcss b/res/css/structures/_UserMenu.pcss index 4c23bf23c0d..f37fbb7f08a 100644 --- a/res/css/structures/_UserMenu.pcss +++ b/res/css/structures/_UserMenu.pcss @@ -47,7 +47,7 @@ limitations under the License. } .mx_UserMenu_name { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-15px; line-height: $font-24px; margin-left: 10px; @@ -104,7 +104,7 @@ limitations under the License. .mx_UserMenu_contextMenu_displayName, .mx_UserMenu_contextMenu_userId { - font-size: $font-15px; + font: var(--cpd-font-heading-sm-regular); /* Automatically grow subelements to fit the container */ flex: 1; @@ -117,12 +117,7 @@ limitations under the License. } .mx_UserMenu_contextMenu_displayName { - font-weight: bold; - line-height: $font-20px; - } - - .mx_UserMenu_contextMenu_userId { - line-height: $font-24px; + font-weight: var(--cpd-font-weight-semibold); } } @@ -147,7 +142,7 @@ limitations under the License. display: inline-block; > span { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); display: block; & + span { diff --git a/res/css/structures/auth/_Login.pcss b/res/css/structures/auth/_Login.pcss index eeca1e8e494..aa4244bcfbd 100644 --- a/res/css/structures/auth/_Login.pcss +++ b/res/css/structures/auth/_Login.pcss @@ -18,7 +18,7 @@ limitations under the License. .mx_Login_submit { @mixin mx_DialogButton; font-size: 15px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); width: 100%; margin-top: 24px; margin-bottom: 24px; diff --git a/res/css/structures/auth/_Registration.pcss b/res/css/structures/auth/_Registration.pcss index b415e78f107..42ac7c0fb4e 100644 --- a/res/css/structures/auth/_Registration.pcss +++ b/res/css/structures/auth/_Registration.pcss @@ -21,7 +21,7 @@ limitations under the License. min-height: 270px; p { - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); color: $authpage-primary-color; &.secondary { diff --git a/res/css/views/auth/_AuthBody.pcss b/res/css/views/auth/_AuthBody.pcss index b5736e644dd..5bce1bbfeaa 100644 --- a/res/css/views/auth/_AuthBody.pcss +++ b/res/css/views/auth/_AuthBody.pcss @@ -25,7 +25,7 @@ limitations under the License. box-sizing: border-box; b { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); } &.mx_AuthBody_flex { @@ -35,14 +35,13 @@ limitations under the License. h1 { font-size: $font-24px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); margin-top: $spacing-8; color: $authpage-primary-color; } h2 { - font-size: $font-14px; - font-weight: var(--font-semi-bold); + font: var(--cpd-font-body-md-semibold); color: $authpage-secondary-color; } @@ -141,7 +140,7 @@ limitations under the License. /* specialisation for password reset views */ .mx_AuthBody.mx_AuthBody_forgot-password { - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); color: $primary-content; padding: 50px 32px; min-height: 600px; @@ -156,7 +155,7 @@ limitations under the License. } .mx_Login_submit { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); margin: 0 0 $spacing-16; } @@ -169,7 +168,7 @@ limitations under the License. } .mx_AuthBody_sign-in-instead-button { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); padding: $spacing-4; } @@ -263,7 +262,7 @@ limitations under the License. text-align: center; > a { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); } } diff --git a/res/css/views/auth/_AuthFooter.pcss b/res/css/views/auth/_AuthFooter.pcss index 0bc2743d544..36349594ecd 100644 --- a/res/css/views/auth/_AuthFooter.pcss +++ b/res/css/views/auth/_AuthFooter.pcss @@ -17,7 +17,7 @@ limitations under the License. .mx_AuthFooter { text-align: center; width: 100%; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); opacity: 0.72; padding: 20px 0; background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8)); diff --git a/res/css/views/auth/_CompleteSecurityBody.pcss b/res/css/views/auth/_CompleteSecurityBody.pcss index 644a9a2d375..53d5988c6dd 100644 --- a/res/css/views/auth/_CompleteSecurityBody.pcss +++ b/res/css/views/auth/_CompleteSecurityBody.pcss @@ -25,13 +25,12 @@ limitations under the License. h2 { font-size: $font-24px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); margin-top: 0; } h3 { - font-size: $font-14px; - font-weight: var(--font-semi-bold); + font: var(--cpd-font-body-md-semibold); } a:link, diff --git a/res/css/views/auth/_LanguageSelector.pcss b/res/css/views/auth/_LanguageSelector.pcss index 8a762e0de3c..b2e179d000c 100644 --- a/res/css/views/auth/_LanguageSelector.pcss +++ b/res/css/views/auth/_LanguageSelector.pcss @@ -20,8 +20,7 @@ limitations under the License. .mx_AuthBody_language .mx_Dropdown_input { border: none; - font-size: $font-14px; - font-weight: var(--font-semi-bold); + font: var(--cpd-font-body-md-semibold); color: $authpage-lang-color; width: auto; } diff --git a/res/css/views/auth/_LoginWithQR.pcss b/res/css/views/auth/_LoginWithQR.pcss index 699d7b0f38e..665c351eb7b 100644 --- a/res/css/views/auth/_LoginWithQR.pcss +++ b/res/css/views/auth/_LoginWithQR.pcss @@ -57,7 +57,7 @@ limitations under the License. margin-left: $spacing-12; } - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); h1 { font-size: $font-24px; @@ -111,7 +111,7 @@ limitations under the License. .mx_LoginWithQR_confirmationDigits { text-align: center; margin: $spacing-48 auto; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-24px; color: $primary-content; } diff --git a/res/css/views/beta/_BetaCard.pcss b/res/css/views/beta/_BetaCard.pcss index 47ab7b005be..42dd3273474 100644 --- a/res/css/views/beta/_BetaCard.pcss +++ b/res/css/views/beta/_BetaCard.pcss @@ -31,7 +31,7 @@ limitations under the License. flex: 1; .mx_BetaCard_title { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-18px; line-height: $font-22px; color: $primary-content; @@ -73,8 +73,7 @@ limitations under the License. .mx_BetaCard_faq { margin-top: $spacing-20; - font-size: $font-12px; - line-height: $font-15px; + font: var(--cpd-font-body-xs-regular); > h4 { margin: $spacing-12 0 0; @@ -108,8 +107,7 @@ limitations under the License. .mx_SettingsFlag_microcopy { margin-top: $spacing-4; - font-size: $font-12px; - line-height: $font-15px; + font: var(--cpd-font-body-sm-regular); } } } @@ -121,7 +119,7 @@ limitations under the License. border-radius: 8px; text-transform: uppercase; font-size: $font-12px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); line-height: 15px; color: $button-primary-fg-color; display: inline-block; diff --git a/res/css/views/context_menus/_IconizedContextMenu.pcss b/res/css/views/context_menus/_IconizedContextMenu.pcss index 581d0ca5e20..126225da5dd 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.pcss +++ b/res/css/views/context_menus/_IconizedContextMenu.pcss @@ -30,7 +30,7 @@ limitations under the License. .mx_IconizedContextMenu_optionList_label { font-size: $font-15px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); } /* the notFirst class is for cases where the optionList might be under a header of sorts. */ @@ -79,8 +79,7 @@ limitations under the License. padding-bottom: 12px; text-decoration: none; color: $primary-content; - font-size: $font-15px; - line-height: $font-24px; + font: var(--cpd-font-body-md-regular); /* Create a flexbox to more easily define the list items */ display: flex; diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss b/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss index f71d43ba0b0..7866bac1c11 100644 --- a/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss +++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss @@ -48,7 +48,7 @@ limitations under the License. margin: 0; color: $secondary-content; font-size: $font-12px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); line-height: $font-15px; } @@ -96,7 +96,7 @@ limitations under the License. } .mx_AddExistingToSpace_errorHeading { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-15px; line-height: $font-18px; color: $alert; @@ -171,7 +171,7 @@ limitations under the License. > div { > h1 { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-18px; line-height: $font-22px; margin: 0; diff --git a/res/css/views/dialogs/_CompoundDialog.pcss b/res/css/views/dialogs/_CompoundDialog.pcss index b9ddf7837a8..6777b4f81d4 100644 --- a/res/css/views/dialogs/_CompoundDialog.pcss +++ b/res/css/views/dialogs/_CompoundDialog.pcss @@ -37,7 +37,7 @@ limitations under the License. h1 { display: inline-block; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-24px; margin: 0; /* managed by header class */ } diff --git a/res/css/views/dialogs/_ConfirmUserActionDialog.pcss b/res/css/views/dialogs/_ConfirmUserActionDialog.pcss index cdcb2b45875..2d6fd430a47 100644 --- a/res/css/views/dialogs/_ConfirmUserActionDialog.pcss +++ b/res/css/views/dialogs/_ConfirmUserActionDialog.pcss @@ -34,7 +34,7 @@ limitations under the License. } .mx_ConfirmUserActionDialog_reasonField { - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); color: $primary-content; background-color: $background; } diff --git a/res/css/views/dialogs/_CreateRoomDialog.pcss b/res/css/views/dialogs/_CreateRoomDialog.pcss index de0dba9a1e0..437044cc8fe 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.pcss +++ b/res/css/views/dialogs/_CreateRoomDialog.pcss @@ -19,7 +19,7 @@ limitations under the License. .mx_CreateRoomDialog_details_summary { list-style: none; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); cursor: pointer; color: $accent; @@ -96,7 +96,7 @@ limitations under the License. .mx_SettingsFlag_label { flex: 1 1 0; min-width: 0; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); } .mx_ToggleSwitch { diff --git a/res/css/views/dialogs/_ExportDialog.pcss b/res/css/views/dialogs/_ExportDialog.pcss index 64599c669c7..ca972aaa723 100644 --- a/res/css/views/dialogs/_ExportDialog.pcss +++ b/res/css/views/dialogs/_ExportDialog.pcss @@ -19,7 +19,7 @@ limitations under the License. font-size: $font-16px; display: block; font-family: $font-family; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); color: $primary-content; margin-top: 18px; margin-bottom: 12px; diff --git a/res/css/views/dialogs/_FeedbackDialog.pcss b/res/css/views/dialogs/_FeedbackDialog.pcss index aa778e1776d..06c18ceddbe 100644 --- a/res/css/views/dialogs/_FeedbackDialog.pcss +++ b/res/css/views/dialogs/_FeedbackDialog.pcss @@ -41,7 +41,7 @@ limitations under the License. > h3 { margin-top: 0; margin-bottom: 8px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-18px; line-height: $font-22px; } diff --git a/res/css/views/dialogs/_ForwardDialog.pcss b/res/css/views/dialogs/_ForwardDialog.pcss index 4190c052e5b..e6c322a77c6 100644 --- a/res/css/views/dialogs/_ForwardDialog.pcss +++ b/res/css/views/dialogs/_ForwardDialog.pcss @@ -27,7 +27,7 @@ limitations under the License. margin: 0 0 6px; color: $secondary-content; font-size: $font-12px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); line-height: $font-15px; } diff --git a/res/css/views/dialogs/_GenericFeatureFeedbackDialog.pcss b/res/css/views/dialogs/_GenericFeatureFeedbackDialog.pcss index 83f93495143..3777a43bc51 100644 --- a/res/css/views/dialogs/_GenericFeatureFeedbackDialog.pcss +++ b/res/css/views/dialogs/_GenericFeatureFeedbackDialog.pcss @@ -17,7 +17,7 @@ limitations under the License. .mx_GenericFeatureFeedbackDialog { .mx_GenericFeatureFeedbackDialog_subheading { color: $primary-content; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); line-height: $font-20px; margin-bottom: 24px; } diff --git a/res/css/views/dialogs/_InviteDialog.pcss b/res/css/views/dialogs/_InviteDialog.pcss index 4ffdb5d6cfa..f13e6b47246 100644 --- a/res/css/views/dialogs/_InviteDialog.pcss +++ b/res/css/views/dialogs/_InviteDialog.pcss @@ -52,8 +52,8 @@ limitations under the License. > input[type="text"] { margin: 6px 0 !important; height: 24px; + font: var(--cpd-font-body-md-regular); line-height: $font-24px; - font-size: $font-14px; padding-inline-start: $spacing-12; border: 0 !important; outline: 0 !important; @@ -110,11 +110,11 @@ limitations under the License. .mx_InviteDialog_section_hidden_suggestions_disclaimer { padding: $spacing-8 0 $spacing-16 0; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); > span { color: $primary-content; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); } > p { @@ -277,7 +277,7 @@ limitations under the License. input { font-size: 18px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); padding-top: 0; } @@ -429,7 +429,7 @@ limitations under the License. .mx_InviteDialog_tile_nameStack_name { font-size: $font-15px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); color: $primary-content; } diff --git a/res/css/views/dialogs/_JoinRuleDropdown.pcss b/res/css/views/dialogs/_JoinRuleDropdown.pcss index 83d73fcea28..5b1b8b7a188 100644 --- a/res/css/views/dialogs/_JoinRuleDropdown.pcss +++ b/res/css/views/dialogs/_JoinRuleDropdown.pcss @@ -16,9 +16,7 @@ limitations under the License. .mx_JoinRuleDropdown { margin-bottom: 8px; - font-weight: normal; - font-family: $font-family; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); color: $primary-content; .mx_Dropdown_input { @@ -26,7 +24,7 @@ limitations under the License. } .mx_Dropdown_option { - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); line-height: $font-32px; height: 32px; min-height: 32px; diff --git a/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss b/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss index 8a2d079399c..1082e500055 100644 --- a/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss +++ b/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss @@ -54,7 +54,7 @@ limitations under the License. margin: 0; color: $secondary-content; font-size: $font-12px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); line-height: $font-15px; } diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.pcss b/res/css/views/dialogs/_MessageEditHistoryDialog.pcss index 69f854d50b9..03bf78e920d 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.pcss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.pcss @@ -31,7 +31,7 @@ limitations under the License. .mx_MessageEditHistoryDialog_edits { list-style-type: none; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); padding: 0; color: $primary-content; diff --git a/res/css/views/dialogs/_PollCreateDialog.pcss b/res/css/views/dialogs/_PollCreateDialog.pcss index 0f9ba92cf14..dd5eb764eda 100644 --- a/res/css/views/dialogs/_PollCreateDialog.pcss +++ b/res/css/views/dialogs/_PollCreateDialog.pcss @@ -23,7 +23,7 @@ limitations under the License. } h2 { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-15px; line-height: $font-24px; margin-top: 0; diff --git a/res/css/views/dialogs/_RoomSettingsDialogBridges.pcss b/res/css/views/dialogs/_RoomSettingsDialogBridges.pcss index 681a76e9e41..aad030bd674 100644 --- a/res/css/views/dialogs/_RoomSettingsDialogBridges.pcss +++ b/res/css/views/dialogs/_RoomSettingsDialogBridges.pcss @@ -94,7 +94,7 @@ limitations under the License. .mx_RoomSettingsDialog_workspace_channel_details { color: $primary-content; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); .mx_RoomSettingsDialog_channel { margin-inline-start: 5px; diff --git a/res/css/views/dialogs/_ServerPickerDialog.pcss b/res/css/views/dialogs/_ServerPickerDialog.pcss index 440ddbf5f62..3089135824b 100644 --- a/res/css/views/dialogs/_ServerPickerDialog.pcss +++ b/res/css/views/dialogs/_ServerPickerDialog.pcss @@ -23,7 +23,8 @@ limitations under the License. > p { color: $secondary-content; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); + margin: 16px 0; &:first-of-type { @@ -37,7 +38,7 @@ limitations under the License. > h2 { font-size: $font-15px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); color: $secondary-content; margin: 16px 0 16px 8px; } diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.pcss b/res/css/views/dialogs/_SpaceSettingsDialog.pcss index 7b7c40e2689..ca224ba5c75 100644 --- a/res/css/views/dialogs/_SpaceSettingsDialog.pcss +++ b/res/css/views/dialogs/_SpaceSettingsDialog.pcss @@ -18,7 +18,7 @@ limitations under the License. color: $primary-content; .mx_SpaceSettings_errorText { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-12px; line-height: $font-15px; color: $alert; @@ -42,7 +42,7 @@ limitations under the License. margin-bottom: 4px; .mx_StyledRadioButton_content { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); line-height: $font-18px; color: $primary-content; } @@ -71,7 +71,7 @@ limitations under the License. .mx_AccessibleButton_hasKind { &.mx_AccessibleButton_kind_link { - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); margin: 7px 18px; &.mx_SettingsTab_showAdvanced { diff --git a/res/css/views/dialogs/_SpotlightDialog.pcss b/res/css/views/dialogs/_SpotlightDialog.pcss index 75b4f48ff5a..4e811ecef10 100644 --- a/res/css/views/dialogs/_SpotlightDialog.pcss +++ b/res/css/views/dialogs/_SpotlightDialog.pcss @@ -163,7 +163,7 @@ limitations under the License. .mx_SpotlightDialog_section { > h4, > .mx_SpotlightDialog_sectionHeader > h4 { - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-12px; line-height: $font-15px; color: $secondary-content; diff --git a/res/css/views/dialogs/_VerifyEMailDialog.pcss b/res/css/views/dialogs/_VerifyEMailDialog.pcss index a8db4a3d0a6..c1430326f72 100644 --- a/res/css/views/dialogs/_VerifyEMailDialog.pcss +++ b/res/css/views/dialogs/_VerifyEMailDialog.pcss @@ -20,14 +20,14 @@ limitations under the License. .mx_Dialog { color: $primary-content; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); padding: $spacing-24 $spacing-24 $spacing-16; text-align: center; width: 485px; h1 { font-size: $font-24px; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); } .mx_VerifyEMailDialog_text-light { diff --git a/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss b/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss index e695992008e..1a4a3f9040d 100644 --- a/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss +++ b/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss @@ -38,7 +38,7 @@ limitations under the License. .mx_SettingsFlag_label { flex: 1 1 0; min-width: 0; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); } .mx_ToggleSwitch { @@ -103,7 +103,7 @@ limitations under the License. .mx_CreateSecretStorageDialog_optionTitle { color: $dialog-title-fg-color; - font-weight: var(--font-semi-bold); + font-weight: var(--cpd-font-weight-semibold); font-size: $font-18px; padding-bottom: 10px; } diff --git a/res/css/views/elements/_AccessibleButton.pcss b/res/css/views/elements/_AccessibleButton.pcss index fbab8bef1bf..e4c00d356f8 100644 --- a/res/css/views/elements/_AccessibleButton.pcss +++ b/res/css/views/elements/_AccessibleButton.pcss @@ -40,7 +40,7 @@ limitations under the License. display: inline-flex; align-items: center; justify-content: center; - font-size: $font-14px; + font: var(--cpd-font-body-md-regular); border: none; /* override default .", + {}, + { button: generalTabButton }, + )} + + + + {threepids + .filter((t) => t.medium === ThreepidMedium.Email) + .map((email) => ( + it.pushkey === email.address) !== undefined} + onChange={(value) => setEmailEnabled(email.address, value)} + /> + ))} + + + {notificationTargets.length > 0 && ( + +
    + {pushers + .filter((it) => it.kind !== "email") + .map((pusher) => ( +
  • {pusher.device_display_name || pusher.app_display_name}
  • + ))} +
+
+ )} + + ); +} diff --git a/src/components/views/settings/notifications/NotificationSettings2.tsx b/src/components/views/settings/notifications/NotificationSettings2.tsx new file mode 100644 index 00000000000..c3ba045602b --- /dev/null +++ b/src/components/views/settings/notifications/NotificationSettings2.tsx @@ -0,0 +1,370 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { useState } from "react"; + +import NewAndImprovedIcon from "../../../../../res/img/element-icons/new-and-improved.svg"; +import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; +import { useNotificationSettings } from "../../../../hooks/useNotificationSettings"; +import { useSettingValue } from "../../../../hooks/useSettings"; +import { _t } from "../../../../languageHandler"; +import { + DefaultNotificationSettings, + NotificationSettings, +} from "../../../../models/notificationsettings/NotificationSettings"; +import { RoomNotifState } from "../../../../RoomNotifs"; +import { SettingLevel } from "../../../../settings/SettingLevel"; +import SettingsStore from "../../../../settings/SettingsStore"; +import { NotificationColor } from "../../../../stores/notifications/NotificationColor"; +import { clearAllNotifications } from "../../../../utils/notifications"; +import AccessibleButton from "../../elements/AccessibleButton"; +import LabelledCheckbox from "../../elements/LabelledCheckbox"; +import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; +import StyledRadioGroup from "../../elements/StyledRadioGroup"; +import TagComposer from "../../elements/TagComposer"; +import { StatelessNotificationBadge } from "../../rooms/NotificationBadge/StatelessNotificationBadge"; +import { SettingsBanner } from "../shared/SettingsBanner"; +import { SettingsSection } from "../shared/SettingsSection"; +import SettingsSubsection from "../shared/SettingsSubsection"; +import { NotificationPusherSettings } from "./NotificationPusherSettings"; + +enum NotificationDefaultLevels { + AllMessages = "all_messages", + PeopleMentionsKeywords = "people_mentions_keywords", + MentionsKeywords = "mentions_keywords", +} + +function toDefaultLevels(levels: NotificationSettings["defaultLevels"]): NotificationDefaultLevels { + if (levels.room === RoomNotifState.AllMessages) { + return NotificationDefaultLevels.AllMessages; + } else if (levels.dm === RoomNotifState.AllMessages) { + return NotificationDefaultLevels.PeopleMentionsKeywords; + } else { + return NotificationDefaultLevels.MentionsKeywords; + } +} + +const NotificationOptions = [ + { + value: NotificationDefaultLevels.AllMessages, + label: _t("All messages"), + }, + { + value: NotificationDefaultLevels.PeopleMentionsKeywords, + label: _t("People, Mentions and Keywords"), + }, + { + value: NotificationDefaultLevels.MentionsKeywords, + label: _t("Mentions and Keywords only"), + }, +]; + +function boldText(text: string): JSX.Element { + return {text}; +} + +function useHasUnreadNotifications(): boolean { + const cli = useMatrixClientContext(); + return cli.getRooms().some((room) => room.getUnreadNotificationCount() > 0); +} + +export default function NotificationSettings2(): JSX.Element { + const cli = useMatrixClientContext(); + + const desktopNotifications = useSettingValue("notificationsEnabled"); + const desktopShowBody = useSettingValue("notificationBodyEnabled"); + const audioNotifications = useSettingValue("audioNotificationsEnabled"); + + const { model, hasPendingChanges, reconcile } = useNotificationSettings(cli); + + const disabled = model === null || hasPendingChanges; + const settings = model ?? DefaultNotificationSettings; + + const [updatingUnread, setUpdatingUnread] = useState(false); + const hasUnreadNotifications = useHasUnreadNotifications(); + + return ( +
+ {hasPendingChanges && model !== null && ( + } + action={_t("Switch now")} + onAction={() => reconcile(model!)} + > + {_t( + "Update: We have updated our notification settings. This won’t affect your previously selected settings.", + {}, + { strong: boldText }, + )} + + )} + +
+ { + reconcile({ + ...model!, + globalMute: !value, + }); + }} + /> + + SettingsStore.setValue("notificationsEnabled", null, SettingLevel.DEVICE, value) + } + /> + + SettingsStore.setValue("notificationBodyEnabled", null, SettingLevel.DEVICE, value) + } + /> + + SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, value) + } + /> +
+ + { + reconcile({ + ...model!, + defaultLevels: { + ...model!.defaultLevels, + dm: + value !== NotificationDefaultLevels.MentionsKeywords + ? RoomNotifState.AllMessages + : RoomNotifState.MentionsOnly, + room: + value === NotificationDefaultLevels.AllMessages + ? RoomNotifState.AllMessages + : RoomNotifState.MentionsOnly, + }, + }); + }} + /> + + + { + reconcile({ + ...model!, + sound: { + ...model!.sound, + people: value ? "default" : undefined, + }, + }); + }} + /> + { + reconcile({ + ...model!, + sound: { + ...model!.sound, + mentions: value ? "default" : undefined, + }, + }); + }} + /> + { + reconcile({ + ...model!, + sound: { + ...model!.sound, + calls: value ? "ring" : undefined, + }, + }); + }} + /> + + + { + reconcile({ + ...model!, + activity: { + ...model!.activity, + invite: value, + }, + }); + }} + /> + { + reconcile({ + ...model!, + activity: { + ...model!.activity, + status_event: value, + }, + }); + }} + /> + { + reconcile({ + ...model!, + activity: { + ...model!.activity, + bot_notices: value, + }, + }); + }} + /> + + when keywords are used in a room.", + {}, + { + badge: , + }, + )} + > + { + reconcile({ + ...model!, + mentions: { + ...model!.mentions, + room: value, + }, + }); + }} + /> + { + reconcile({ + ...model!, + mentions: { + ...model!.mentions, + user: value, + }, + }); + }} + /> + { + reconcile({ + ...model!, + mentions: { + ...model!.mentions, + keywords: value, + }, + }); + }} + /> + { + reconcile({ + ...model!, + keywords: [keyword, ...model!.keywords], + }); + }} + onRemove={(keyword) => { + reconcile({ + ...model!, + keywords: model!.keywords.filter((it) => it !== keyword), + }); + }} + label={_t("Keyword")} + placeholder={_t("New keyword")} + /> + + + + {hasUnreadNotifications && ( + { + setUpdatingUnread(true); + await clearAllNotifications(cli); + setUpdatingUnread(false); + }} + > + {_t("Mark all messages as read")} + + )} + { + reconcile(DefaultNotificationSettings); + }} + > + {_t("Reset to default settings")} + + +
+
+ ); +} diff --git a/src/components/views/settings/shared/SettingsBanner.tsx b/src/components/views/settings/shared/SettingsBanner.tsx new file mode 100644 index 00000000000..85dfb1d89d8 --- /dev/null +++ b/src/components/views/settings/shared/SettingsBanner.tsx @@ -0,0 +1,39 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { PropsWithChildren, ReactNode } from "react"; + +import AccessibleButton from "../../elements/AccessibleButton"; + +interface Props { + icon?: ReactNode; + action?: ReactNode; + onAction?: () => void; +} + +export function SettingsBanner({ children, icon, action, onAction }: PropsWithChildren): JSX.Element { + return ( +
+ {icon} +
{children}
+ {action && ( + + {action} + + )} +
+ ); +} diff --git a/src/components/views/settings/shared/SettingsIndent.tsx b/src/components/views/settings/shared/SettingsIndent.tsx new file mode 100644 index 00000000000..48ddf2fb70e --- /dev/null +++ b/src/components/views/settings/shared/SettingsIndent.tsx @@ -0,0 +1,27 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { HTMLAttributes } from "react"; + +export interface SettingsIndentProps extends HTMLAttributes { + children?: React.ReactNode; +} + +export const SettingsIndent: React.FC = ({ children, ...rest }) => ( +
+ {children} +
+); diff --git a/src/components/views/settings/shared/SettingsSection.tsx b/src/components/views/settings/shared/SettingsSection.tsx index 243346c0473..926ef5e2323 100644 --- a/src/components/views/settings/shared/SettingsSection.tsx +++ b/src/components/views/settings/shared/SettingsSection.tsx @@ -1,5 +1,5 @@ /* -Copyright 2022 The Matrix.org Foundation C.I.C. +Copyright 2022-2023 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 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import classnames from "classnames"; import React, { HTMLAttributes } from "react"; import Heading from "../../typography/Heading"; @@ -40,8 +41,8 @@ export interface SettingsSectionProps extends HTMLAttributes { * * ``` */ -export const SettingsSection: React.FC = ({ heading, children, ...rest }) => ( -
+export const SettingsSection: React.FC = ({ className, heading, children, ...rest }) => ( +
{typeof heading === "string" ? {heading} : <>{heading}}
{children}
diff --git a/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx index 4e95220df1f..50afdf91c93 100644 --- a/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx @@ -1,5 +1,5 @@ /* -Copyright 2019-2021 The Matrix.org Foundation C.I.C. +Copyright 2019-2023 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. @@ -17,17 +17,26 @@ limitations under the License. import React from "react"; import { _t } from "../../../../../languageHandler"; +import { Features } from "../../../../../settings/Settings"; +import SettingsStore from "../../../../../settings/SettingsStore"; import Notifications from "../../Notifications"; +import NotificationSettings2 from "../../notifications/NotificationSettings2"; import { SettingsSection } from "../../shared/SettingsSection"; import SettingsTab from "../SettingsTab"; export default class NotificationUserSettingsTab extends React.Component { public render(): React.ReactNode { + const newNotificationSettingsEnabled = SettingsStore.getValue(Features.NotificationSettings2); + return ( - - - + {newNotificationSettingsEnabled ? ( + + ) : ( + + + + )} ); } diff --git a/src/hooks/useNotificationSettings.tsx b/src/hooks/useNotificationSettings.tsx index b4174b4924e..0e5f26b88f0 100644 --- a/src/hooks/useNotificationSettings.tsx +++ b/src/hooks/useNotificationSettings.tsx @@ -44,6 +44,7 @@ type UseNotificationSettings = { }; export function useNotificationSettings(cli: MatrixClient): UseNotificationSettings { + const run = useLinearisedPromise(); const supportsIntentionalMentions = useMemo(() => cli.supportsIntentionalMentions(), [cli]); const pushRules = useRef(null); @@ -61,21 +62,41 @@ export function useNotificationSettings(cli: MatrixClient): UseNotificationSetti }, [cli, supportsIntentionalMentions]); useEffect(() => { - updatePushRules().catch((err) => console.error(err)); - }, [cli, updatePushRules]); + run(updatePushRules).catch((err) => console.error(err)); + }, [cli, run, updatePushRules]); const reconcile = useCallback( (model: NotificationSettings) => { - if (pushRules.current !== null) { - setModel(model); - const changes = reconcileNotificationSettings(pushRules.current, model, supportsIntentionalMentions); - applyChanges(cli, changes) - .then(updatePushRules) - .catch((err) => console.error(err)); - } + setModel(model); + run(async () => { + if (pushRules.current !== null) { + const changes = reconcileNotificationSettings( + pushRules.current, + model, + supportsIntentionalMentions, + ); + await applyChanges(cli, changes); + await updatePushRules(); + } + }).catch((err) => console.error(err)); }, - [cli, updatePushRules, supportsIntentionalMentions], + [run, supportsIntentionalMentions, cli, updatePushRules], ); return { model, hasPendingChanges, reconcile }; } + +function useLinearisedPromise(): (fun: () => Promise) => Promise { + const lastPromise = useRef | null>(null); + + return useCallback((fun: () => Promise): Promise => { + let next: Promise; + if (lastPromise.current === null) { + next = fun(); + } else { + next = lastPromise.current.then(fun); + } + lastPromise.current = next; + return next; + }, []); +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d9f74d40a56..dff93e43b99 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -953,6 +953,9 @@ "Can I use text chat alongside the video call?": "Can I use text chat alongside the video call?", "Yes, the chat timeline is displayed alongside the video.": "Yes, the chat timeline is displayed alongside the video.", "Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.", + "New Notification Settings": "New Notification Settings", + "Notification Settings": "Notification Settings", + "Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.": "Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.", "Explore public spaces in the new search dialog": "Explore public spaces in the new search dialog", "Requires your server to support the stable version of MSC3827": "Requires your server to support the stable version of MSC3827", "Let moderators hide messages pending moderation.": "Let moderators hide messages pending moderation.", @@ -1767,6 +1770,33 @@ "%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.", "You do not have sufficient permissions to change this.": "You do not have sufficient permissions to change this.", "Call type": "Call type", + "Email Notifications": "Email Notifications", + "Email summary": "Email summary", + "Receive an email summary of missed notifications": "Receive an email summary of missed notifications", + "Select which emails you want to send summaries to. Manage your emails in .": "Select which emails you want to send summaries to. Manage your emails in .", + "People, Mentions and Keywords": "People, Mentions and Keywords", + "Mentions and Keywords only": "Mentions and Keywords only", + "Switch now": "Switch now", + "Update: We have updated our notification settings. This won’t affect your previously selected settings.": "Update: We have updated our notification settings. This won’t affect your previously selected settings.", + "Show message preview in desktop notification": "Show message preview in desktop notification", + "I want to be notified for (Default Setting)": "I want to be notified for (Default Setting)", + "This setting will be applied by default to all your rooms.": "This setting will be applied by default to all your rooms.", + "Play a sound for": "Play a sound for", + "Applied by default to all rooms on all devices.": "Applied by default to all rooms on all devices.", + "Mentions and Keywords": "Mentions and Keywords", + "Audio and Video calls": "Audio and Video calls", + "Other things we think you might be interested in:": "Other things we think you might be interested in:", + "Invited to a room": "Invited to a room", + "New room activity, upgrades and status messages occur": "New room activity, upgrades and status messages occur", + "Messages sent by bots": "Messages sent by bots", + "Show a badge when keywords are used in a room.": "Show a badge when keywords are used in a room.", + "Notify when someone mentions using @room": "Notify when someone mentions using @room", + "Notify when someone mentions using @displayname or %(mxid)s": "Notify when someone mentions using @displayname or %(mxid)s", + "Notify when someone uses a keyword": "Notify when someone uses a keyword", + "Enter keywords here, or use for spelling variations or nicknames": "Enter keywords here, or use for spelling variations or nicknames", + "Quick Actions": "Quick Actions", + "Mark all messages as read": "Mark all messages as read", + "Reset to default settings": "Reset to default settings", "Unable to revoke sharing for email address": "Unable to revoke sharing for email address", "Unable to share email address": "Unable to share email address", "Your email address hasn't been verified yet": "Your email address hasn't been verified yet", diff --git a/src/models/notificationsettings/reconcileNotificationSettings.ts b/src/models/notificationsettings/reconcileNotificationSettings.ts index 7fdd366e633..510e7ca3af1 100644 --- a/src/models/notificationsettings/reconcileNotificationSettings.ts +++ b/src/models/notificationsettings/reconcileNotificationSettings.ts @@ -196,6 +196,11 @@ export function reconcileNotificationSettings( } } + const mentionActions = NotificationUtils.encodeActions({ + notify: true, + sound: model.sound.mentions, + highlight: true, + }); const contentRules = pushRules.global.content?.filter((rule) => !rule.rule_id.startsWith(".")) ?? []; const newKeywords = new Set(model.keywords); for (const rule of contentRules) { @@ -204,12 +209,27 @@ export function reconcileNotificationSettings( rule_id: rule.rule_id, kind: PushRuleKind.ContentSpecific, }); - } else if (rule.enabled !== model.mentions.keywords) { - changes.updated.push({ - rule_id: rule.rule_id, - kind: PushRuleKind.ContentSpecific, - enabled: model.mentions.keywords, - }); + } else { + let changed = false; + if (rule.enabled !== model.mentions.keywords) { + changed = true; + } else if (rule.actions !== undefined) { + const originalActions = NotificationUtils.decodeActions(rule.actions); + const actions = NotificationUtils.decodeActions(mentionActions); + if (originalActions === null || actions === null) { + changed = true; + } else if (!deepCompare(actions, originalActions)) { + changed = true; + } + } + if (changed) { + changes.updated.push({ + rule_id: rule.rule_id, + kind: PushRuleKind.ContentSpecific, + enabled: model.mentions.keywords, + actions: mentionActions, + }); + } } newKeywords.delete(rule.pattern!); } @@ -220,7 +240,7 @@ export function reconcileNotificationSettings( default: false, enabled: model.mentions.keywords, pattern: keyword, - actions: StandardActions.ACTION_NOTIFY, + actions: mentionActions, }); } diff --git a/src/models/notificationsettings/toNotificationSettings.ts b/src/models/notificationsettings/toNotificationSettings.ts index cfb28718c48..9a3f15453d2 100644 --- a/src/models/notificationsettings/toNotificationSettings.ts +++ b/src/models/notificationsettings/toNotificationSettings.ts @@ -37,6 +37,22 @@ function shouldNotify(rules: (IPushRule | null | undefined | false)[]): boolean return false; } +function isMuted(rules: (IPushRule | null | undefined | false)[]): boolean { + if (rules.length === 0) { + return false; + } + for (const rule of rules) { + if (rule === null || rule === undefined || rule === false || !rule.enabled) { + continue; + } + const actions = NotificationUtils.decodeActions(rule.actions); + if (actions !== null && !actions.notify && actions.highlight !== true && actions.sound === undefined) { + return true; + } + } + return false; +} + function determineSound(rules: (IPushRule | null | undefined | false)[]): string | undefined { for (const rule of rules) { if (rule === null || rule === undefined || rule === false || !rule.enabled) { @@ -74,7 +90,7 @@ export function toNotificationSettings( people: determineSound(dmRules), }, activity: { - bot_notices: shouldNotify([standardRules.get(RuleId.SuppressNotices)]), + bot_notices: !isMuted([standardRules.get(RuleId.SuppressNotices)]), invite: shouldNotify([standardRules.get(RuleId.InviteToSelf)]), status_event: shouldNotify([standardRules.get(RuleId.MemberEvent), standardRules.get(RuleId.Tombstone)]), }, diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 7b932eb3b99..5f1cbe8165c 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -94,6 +94,7 @@ export enum LabGroup { export enum Features { VoiceBroadcast = "feature_voice_broadcast", VoiceBroadcastForceSmallChunks = "feature_voice_broadcast_force_small_chunks", + NotificationSettings2 = "feature_notification_settings2", OidcNativeFlow = "feature_oidc_native_flow", } @@ -229,6 +230,28 @@ export const SETTINGS: { [setting: string]: ISetting } = { requiresRefresh: true, }, }, + [Features.NotificationSettings2]: { + isFeature: true, + labsGroup: LabGroup.Experimental, + supportedLevels: LEVELS_FEATURE, + displayName: _td("New Notification Settings"), + default: false, + betaInfo: { + title: _td("Notification Settings"), + caption: () => ( + <> +

+ {_t( + "Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.", + { + brand: SdkConfig.get().brand, + }, + )} +

+ + ), + }, + }, "feature_exploring_public_spaces": { isFeature: true, labsGroup: LabGroup.Spaces, diff --git a/test/components/views/settings/notifications/Notifications2-test.tsx b/test/components/views/settings/notifications/Notifications2-test.tsx new file mode 100644 index 00000000000..11030abc411 --- /dev/null +++ b/test/components/views/settings/notifications/Notifications2-test.tsx @@ -0,0 +1,762 @@ +/* +Copyright 2022-2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { act, findByRole, getByRole, queryByRole, render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { ThreepidMedium } from "matrix-js-sdk/src/@types/threepids"; +import { IPushRules, MatrixClient, NotificationCountType, PushRuleKind, Room, RuleId } from "matrix-js-sdk/src/matrix"; +import React from "react"; + +import NotificationSettings2 from "../../../../../src/components/views/settings/notifications/NotificationSettings2"; +import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; +import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg"; +import { StandardActions } from "../../../../../src/notifications/StandardActions"; +import { PredictableRandom } from "../../../../predictableRandom"; +import { mkMessage, stubClient } from "../../../../test-utils"; +import Mock = jest.Mock; + +const mockRandom = new PredictableRandom(); + +// Fake random strings to give a predictable snapshot for IDs +jest.mock("matrix-js-sdk/src/randomstring", () => ({ + randomString: jest.fn((len): string => { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let ret = ""; + + for (let i = 0; i < len; ++i) { + ret += chars.charAt(Math.floor(mockRandom.get() * chars.length)); + } + + return ret; + }), +})); + +const waitForUpdate = (): Promise => new Promise((resolve) => setTimeout(resolve)); + +const labelGlobalMute = "Enable notifications for this account"; +const labelLevelAllMessage = "All messages"; +const labelLevelMentionsOnly = "Mentions and Keywords only"; +const labelSoundPeople = "People"; +const labelSoundMentions = "Mentions and Keywords"; +const labelSoundCalls = "Audio and Video calls"; +const labelActivityInvites = "Invited to a room"; +const labelActivityStatus = "New room activity, upgrades and status messages occur"; +const labelActivityBots = "Messages sent by bots"; +const labelMentionUser = "Notify when someone mentions using @displayname or @mxid"; +const labelMentionRoom = "Notify when someone mentions using @room"; +const labelMentionKeyword = + "Notify when someone uses a keyword" + "Enter keywords here, or use for spelling variations or nicknames"; +const labelResetDefault = "Reset to default settings"; + +const keywords = ["justjann3", "justj4nn3", "justj4nne", "Janne", "J4nne", "Jann3", "jann3", "j4nne", "janne"]; + +describe("", () => { + let cli: MatrixClient; + let pushRules: IPushRules; + + beforeAll(async () => { + pushRules = (await import("../../../../models/notificationsettings/pushrules_sample.json")) as IPushRules; + }); + + beforeEach(() => { + stubClient(); + cli = MatrixClientPeg.safeGet(); + cli.getPushRules = jest.fn(cli.getPushRules).mockResolvedValue(pushRules); + cli.supportsIntentionalMentions = jest.fn(cli.supportsIntentionalMentions).mockReturnValue(false); + cli.setPushRuleEnabled = jest.fn(cli.setPushRuleEnabled); + cli.setPushRuleActions = jest.fn(cli.setPushRuleActions); + cli.addPushRule = jest.fn(cli.addPushRule).mockResolvedValue({}); + cli.deletePushRule = jest.fn(cli.deletePushRule).mockResolvedValue({}); + cli.removePusher = jest.fn(cli.removePusher).mockResolvedValue({}); + cli.setPusher = jest.fn(cli.setPusher).mockResolvedValue({}); + + mockRandom.reset(); + }); + + it("matches the snapshot", async () => { + cli.getPushers = jest.fn(cli.getPushers).mockResolvedValue({ + pushers: [ + { + app_display_name: "Element", + app_id: "im.vector.app", + data: {}, + device_display_name: "My EyeFon", + kind: "http", + lang: "en", + pushkey: "", + enabled: true, + }, + ], + }); + cli.getThreePids = jest.fn(cli.getThreePids).mockResolvedValue({ + threepids: [ + { + medium: ThreepidMedium.Email, + address: "test@example.tld", + validated_at: 1656633600, + added_at: 1656633600, + }, + ], + }); + + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.container).toMatchSnapshot(); + }); + + it("correctly handles the loading/disabled state", async () => { + (cli.getPushRules as Mock).mockReturnValue(new Promise(() => {})); + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(async () => { + await waitForUpdate(); + expect(screen.container).toMatchSnapshot(); + + const globalMute = screen.getByLabelText(labelGlobalMute); + expect(globalMute).toHaveAttribute("aria-disabled", "true"); + + const levelAllMessages = screen.getByLabelText(labelLevelAllMessage); + expect(levelAllMessages).toBeDisabled(); + + const soundPeople = screen.getByLabelText(labelSoundPeople); + expect(soundPeople).toBeDisabled(); + const soundMentions = screen.getByLabelText(labelSoundMentions); + expect(soundMentions).toBeDisabled(); + const soundCalls = screen.getByLabelText(labelSoundCalls); + expect(soundCalls).toBeDisabled(); + + const activityInvites = screen.getByLabelText(labelActivityInvites); + expect(activityInvites).toBeDisabled(); + const activityStatus = screen.getByLabelText(labelActivityStatus); + expect(activityStatus).toBeDisabled(); + const activityBots = screen.getByLabelText(labelActivityBots); + expect(activityBots).toBeDisabled(); + + const mentionUser = screen.getByLabelText(labelMentionUser.replace("@mxid", cli.getUserId()!)); + expect(mentionUser).toBeDisabled(); + const mentionRoom = screen.getByLabelText(labelMentionRoom); + expect(mentionRoom).toBeDisabled(); + const mentionKeyword = screen.getByLabelText(labelMentionKeyword); + expect(mentionKeyword).toBeDisabled(); + await Promise.all([ + user.click(globalMute), + user.click(levelAllMessages), + user.click(soundPeople), + user.click(soundMentions), + user.click(soundCalls), + user.click(activityInvites), + user.click(activityStatus), + user.click(activityBots), + user.click(mentionUser), + user.click(mentionRoom), + user.click(mentionKeyword), + ]); + }); + + expect(cli.setPushRuleActions).not.toHaveBeenCalled(); + expect(cli.setPushRuleEnabled).not.toHaveBeenCalled(); + expect(cli.addPushRule).not.toHaveBeenCalled(); + expect(cli.deletePushRule).not.toHaveBeenCalled(); + }); + + describe("form elements actually toggle the model value", () => { + it("global mute", async () => { + const label = labelGlobalMute; + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.Master, true); + }); + + it("notification level", async () => { + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(labelLevelAllMessage)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(labelLevelAllMessage)); + await waitForUpdate(); + }); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.EncryptedMessage, + true, + ); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.Message, true); + (cli.setPushRuleEnabled as Mock).mockClear(); + expect(screen.getByLabelText(labelLevelMentionsOnly)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(labelLevelMentionsOnly)); + await waitForUpdate(); + }); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.EncryptedDM, + true, + ); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.DM, true); + }); + + describe("play a sound for", () => { + it("people", async () => { + const label = labelSoundPeople; + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.EncryptedDM, + StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.DM, + StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.InviteToSelf, + StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, + ); + }); + + it("mentions", async () => { + const label = labelSoundMentions; + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.ContainsDisplayName, + StandardActions.ACTION_HIGHLIGHT, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.ContentSpecific, + RuleId.ContainsUserName, + StandardActions.ACTION_HIGHLIGHT, + ); + }); + + it("calls", async () => { + const label = labelSoundCalls; + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.IncomingCall, + StandardActions.ACTION_NOTIFY, + ); + }); + }); + + describe("activity", () => { + it("invite", async () => { + const label = labelActivityInvites; + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.InviteToSelf, + StandardActions.ACTION_NOTIFY, + ); + }); + it("status messages", async () => { + const label = labelActivityStatus; + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.MemberEvent, + StandardActions.ACTION_NOTIFY, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.Tombstone, + StandardActions.ACTION_HIGHLIGHT, + ); + }); + it("notices", async () => { + const label = labelActivityBots; + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.SuppressNotices, + StandardActions.ACTION_DONT_NOTIFY, + ); + }); + }); + describe("mentions", () => { + it("room mentions", async () => { + const label = labelMentionRoom; + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.AtRoomNotification, + StandardActions.ACTION_DONT_NOTIFY, + ); + }); + it("user mentions", async () => { + const label = labelMentionUser.replace("@mxid", cli.getUserId()!); + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.ContainsDisplayName, + StandardActions.ACTION_DONT_NOTIFY, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.ContentSpecific, + RuleId.ContainsUserName, + StandardActions.ACTION_DONT_NOTIFY, + ); + }); + it("keywords", async () => { + const label = labelMentionKeyword; + + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + for (const pattern of keywords) { + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith( + "global", + PushRuleKind.ContentSpecific, + pattern, + false, + ); + } + }); + }); + describe("keywords", () => { + it("allows adding keywords", async () => { + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + const inputField = screen.getByRole("textbox", { name: "Keyword" }); + const addButton = screen.getByRole("button", { name: "Add" }); + expect(inputField).not.toBeDisabled(); + expect(addButton).not.toBeDisabled(); + await act(async () => { + await user.type(inputField, "testkeyword"); + await user.click(addButton); + await waitForUpdate(); + }); + expect(cli.addPushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "testkeyword", { + kind: PushRuleKind.ContentSpecific, + rule_id: "testkeyword", + enabled: true, + default: false, + actions: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND, + pattern: "testkeyword", + }); + }); + + it("allows deleting keywords", async () => { + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + const tag = screen.getByText("justj4nn3"); + const deleteButton = getByRole(tag, "button", { name: "Remove" }); + expect(deleteButton).not.toBeDisabled(); + await act(async () => { + await user.click(deleteButton); + await waitForUpdate(); + }); + expect(cli.deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nn3"); + }); + }); + + it("resets the model correctly", async () => { + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + const button = screen.getByText(labelResetDefault); + expect(button).not.toBeDisabled(); + await act(async () => { + await user.click(button); + await waitForUpdate(); + }); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.EncryptedMessage, + true, + ); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.Message, true); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.EncryptedDM, + true, + ); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.DM, true); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.SuppressNotices, + false, + ); + expect(cli.setPushRuleEnabled).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.InviteToSelf, + true, + ); + + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.EncryptedMessage, + StandardActions.ACTION_NOTIFY, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.Message, + StandardActions.ACTION_NOTIFY, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.EncryptedDM, + StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.DM, + StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.SuppressNotices, + StandardActions.ACTION_DONT_NOTIFY, + ); + expect(cli.setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.InviteToSelf, + StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, + ); + + for (const pattern of keywords) { + expect(cli.deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, pattern); + } + }); + }); + + describe("pusher settings", () => { + it("can create email pushers", async () => { + cli.getPushers = jest.fn(cli.getPushers).mockResolvedValue({ + pushers: [ + { + app_display_name: "Element", + app_id: "im.vector.app", + data: {}, + device_display_name: "My EyeFon", + kind: "http", + lang: "en", + pushkey: "", + enabled: true, + }, + ], + }); + cli.getThreePids = jest.fn(cli.getThreePids).mockResolvedValue({ + threepids: [ + { + medium: ThreepidMedium.Email, + address: "test@example.tld", + validated_at: 1656633600, + added_at: 1656633600, + }, + ], + }); + + const label = "test@example.tld"; + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.setPusher).toHaveBeenCalledWith({ + app_display_name: "Email Notifications", + app_id: "m.email", + append: true, + data: { brand: "Element" }, + device_display_name: "test@example.tld", + kind: "email", + lang: "en-US", + pushkey: "test@example.tld", + }); + }); + + it("can remove email pushers", async () => { + cli.getPushers = jest.fn(cli.getPushers).mockResolvedValue({ + pushers: [ + { + app_display_name: "Element", + app_id: "im.vector.app", + data: {}, + device_display_name: "My EyeFon", + kind: "http", + lang: "en", + pushkey: "abctest", + }, + { + app_display_name: "Email Notifications", + app_id: "m.email", + data: { brand: "Element" }, + device_display_name: "test@example.tld", + kind: "email", + lang: "en-US", + pushkey: "test@example.tld", + }, + ], + }); + cli.getThreePids = jest.fn(cli.getThreePids).mockResolvedValue({ + threepids: [ + { + medium: ThreepidMedium.Email, + address: "test@example.tld", + validated_at: 1656633600, + added_at: 1656633600, + }, + ], + }); + + const label = "test@example.tld"; + const user = userEvent.setup(); + const screen = render( + + + , + ); + await act(waitForUpdate); + expect(screen.getByLabelText(label)).not.toBeDisabled(); + await act(async () => { + await user.click(screen.getByLabelText(label)); + await waitForUpdate(); + }); + expect(cli.removePusher).toHaveBeenCalledWith("test@example.tld", "m.email"); + }); + }); + + describe("clear all notifications", () => { + it("is hidden when no notifications exist", async () => { + const room = new Room("room123", cli, "@alice:example.org"); + cli.getRooms = jest.fn(cli.getRooms).mockReturnValue([room]); + + const { container } = render( + + + , + ); + await waitForUpdate(); + expect( + queryByRole(container, "button", { + name: "Mark all messages as read", + }), + ).not.toBeInTheDocument(); + }); + + it("clears all notifications", async () => { + const room = new Room("room123", cli, "@alice:example.org"); + cli.getRooms = jest.fn(cli.getRooms).mockReturnValue([room]); + + const message = mkMessage({ + event: true, + room: "room123", + user: "@alice:example.org", + ts: 1, + }); + room.addLiveEvents([message]); + room.setUnreadNotificationCount(NotificationCountType.Total, 1); + + const user = userEvent.setup(); + const { container } = render( + + + , + ); + await waitForUpdate(); + const clearNotificationEl = await findByRole(container, "button", { + name: "Mark all messages as read", + }); + + await act(async () => { + await user.click(clearNotificationEl); + await waitForUpdate(); + }); + expect(cli.sendReadReceipt).toHaveBeenCalled(); + + await waitFor(() => { + expect(clearNotificationEl).not.toBeInTheDocument(); + }); + }); + }); +}); diff --git a/test/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap b/test/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap new file mode 100644 index 00000000000..9eef11fbaea --- /dev/null +++ b/test/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap @@ -0,0 +1,1604 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` correctly handles the loading/disabled state 1`] = ` +
+
+
+

+ Notifications +

+
+
+
+ +
+ Enable notifications for this account +
+
+
+
+
+
+
+ +
+ Enable desktop notifications for this session +
+
+
+
+
+
+
+ +
+ Show message preview in desktop notification +
+
+
+
+
+
+
+ +
+ Enable audible notifications for this session +
+
+
+
+
+
+
+
+
+

+ I want to be notified for (Default Setting) +

+
+
+
+ This setting will be applied by default to all your rooms. +
+
+
+