diff --git a/.eslintrc.js b/.eslintrc.js index 7885cfd88d2..8cc5890ca46 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,12 +1,6 @@ module.exports = { - plugins: [ - "matrix-org", - ], - extends: [ - "plugin:matrix-org/babel", - "plugin:matrix-org/react", - "plugin:matrix-org/a11y", - ], + plugins: ["matrix-org"], + extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"], env: { browser: true, node: true, @@ -19,7 +13,6 @@ module.exports = { "no-constant-condition": "off", "prefer-promise-reject-errors": "off", "no-async-promise-executor": "off", - "quotes": "off", "no-extra-boolean-cast": "off", // Bind or arrow functions in props causes performance issues (but we @@ -41,34 +34,47 @@ module.exports = { ], // Ban matrix-js-sdk/src imports in favour of matrix-js-sdk/src/matrix imports to prevent unleashing hell. - "no-restricted-imports": ["error", { - "paths": [{ - "name": "matrix-js-sdk", - "message": "Please use matrix-js-sdk/src/matrix instead", - }, { - "name": "matrix-js-sdk/", - "message": "Please use matrix-js-sdk/src/matrix instead", - }, { - "name": "matrix-js-sdk/src", - "message": "Please use matrix-js-sdk/src/matrix instead", - }, { - "name": "matrix-js-sdk/src/", - "message": "Please use matrix-js-sdk/src/matrix instead", - }, { - "name": "matrix-js-sdk/src/index", - "message": "Please use matrix-js-sdk/src/matrix instead", - }, { - "name": "matrix-react-sdk", - "message": "Please use matrix-react-sdk/src/index instead", - }, { - "name": "matrix-react-sdk/", - "message": "Please use matrix-react-sdk/src/index instead", - }], - "patterns": [{ - "group": ["matrix-js-sdk/lib", "matrix-js-sdk/lib/", "matrix-js-sdk/lib/**"], - "message": "Please use matrix-js-sdk/src/* instead", - }], - }], + "no-restricted-imports": [ + "error", + { + paths: [ + { + name: "matrix-js-sdk", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "matrix-js-sdk/", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "matrix-js-sdk/src", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "matrix-js-sdk/src/", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "matrix-js-sdk/src/index", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "matrix-react-sdk", + message: "Please use matrix-react-sdk/src/index instead", + }, + { + name: "matrix-react-sdk/", + message: "Please use matrix-react-sdk/src/index instead", + }, + ], + patterns: [ + { + group: ["matrix-js-sdk/lib", "matrix-js-sdk/lib/", "matrix-js-sdk/lib/**"], + message: "Please use matrix-js-sdk/src/* instead", + }, + ], + }, + ], // There are too many a11y violations to fix at once // Turn violated rules off until they are fixed @@ -91,19 +97,15 @@ module.exports = { }, overrides: [ { - files: [ - "src/**/*.{ts,tsx}", - "test/**/*.{ts,tsx}", - "cypress/**/*.ts", - ], - extends: [ - "plugin:matrix-org/typescript", - "plugin:matrix-org/react", - ], + files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}", "cypress/**/*.ts"], + extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"], rules: { + // temporary disabled + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-member-accessibility": "off", + // Things we do that break the ideal style "prefer-promise-reject-errors": "off", - "quotes": "off", "no-extra-boolean-cast": "off", // Remove Babel things manually due to override limitations @@ -117,10 +119,6 @@ module.exports = { "@typescript-eslint/ban-ts-comment": "off", // We're okay with assertion errors when we ask for them "@typescript-eslint/no-non-null-assertion": "off", - - // The non-TypeScript rule produces false positives - "func-call-spacing": "off", - "@typescript-eslint/func-call-spacing": ["error"], }, }, // temporary override for offending icon require files @@ -153,12 +151,12 @@ module.exports = { "src/components/views/rooms/MessageComposer.tsx", "src/components/views/rooms/ReplyPreview.tsx", "src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx", - "src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx" + "src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx", ], rules: { "@typescript-eslint/no-var-requires": "off", }, - } + }, ], settings: { react: { @@ -168,7 +166,7 @@ module.exports = { }; function buildRestrictedPropertiesOptions(properties, message) { - return properties.map(prop => { + return properties.map((prop) => { let [object, property] = prop.split("."); if (object === "*") { object = undefined; diff --git a/.github/renovate.json b/.github/renovate.json index 91ed4799766..76320426d9e 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,6 +1,4 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "github>matrix-org/renovate-config-element-web" - ] + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>matrix-org/renovate-config-element-web"] } diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 10bda8e205f..32deb2f2b57 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,30 +1,30 @@ name: Backport on: - pull_request_target: - types: - - closed - - labeled - branches: - - develop + pull_request_target: + types: + - closed + - labeled + branches: + - develop jobs: - backport: - name: Backport - runs-on: ubuntu-latest - # Only react to merged PRs for security reasons. - # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. - if: > - github.event.pull_request.merged - && ( - github.event.action == 'closed' - || ( - github.event.action == 'labeled' - && contains(github.event.label.name, 'backport') - ) - ) - steps: - - uses: tibdex/backport@v2 - with: - labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>" - # We can't use GITHUB_TOKEN here or CI won't run on the new PR - github_token: ${{ secrets.ELEMENT_BOT_TOKEN }} + backport: + name: Backport + runs-on: ubuntu-latest + # Only react to merged PRs for security reasons. + # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. + if: > + github.event.pull_request.merged + && ( + github.event.action == 'closed' + || ( + github.event.action == 'labeled' + && contains(github.event.label.name, 'backport') + ) + ) + steps: + - uses: tibdex/backport@v2 + with: + labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>" + # We can't use GITHUB_TOKEN here or CI won't run on the new PR + github_token: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba870619dc0..c8b44ef47d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,11 @@ name: Release Process on: - release: - types: [ published ] + release: + types: [published] concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: - npm: - name: Publish - uses: matrix-org/matrix-js-sdk/.github/workflows/release-npm.yml@develop - secrets: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + npm: + name: Publish + uses: matrix-org/matrix-js-sdk/.github/workflows/release-npm.yml@develop + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index 4b31454e92e..0b8fcfa5c82 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -1,142 +1,148 @@ name: Static Analysis on: - pull_request: { } - push: - branches: [ develop, master ] - repository_dispatch: - types: [ upstream-sdk-notify ] + pull_request: {} + push: + branches: [develop, master] + repository_dispatch: + types: [upstream-sdk-notify] env: - # These must be set for fetchdep.sh to get the right branch - REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} + # These must be set for fetchdep.sh to get the right branch + REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} jobs: - ts_lint: - name: "Typescript Syntax Check" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - uses: actions/setup-node@v3 - with: - cache: 'yarn' - - - name: Install Deps - run: "./scripts/ci/install-deps.sh --ignore-scripts" - - - name: Typecheck - run: "yarn run lint:types" - - - name: Switch js-sdk to release mode - working-directory: node_modules/matrix-js-sdk - run: | - scripts/switch_package_to_release.js - yarn install - yarn run build:compile - yarn run build:types - - - name: Typecheck (release mode) - run: "yarn run lint:types" - - tsc-strict: - name: Typescript Strict Error Checker - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - permissions: - pull-requests: read - checks: write - steps: - - uses: actions/checkout@v3 - - - name: Install Deps - run: "scripts/ci/layered.sh" - - - name: Get diff lines - id: diff - uses: Equip-Collaboration/diff-line-numbers@v1.0.0 - with: - include: '["\\.tsx?$"]' - - - name: Detecting files changed - id: files - uses: futuratrepadeira/changed-files@v4.0.0 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - pattern: '^.*\.tsx?$' - - - uses: t3chguy/typescript-check-action@main - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - use-check: false - check-fail-mode: added - output-behaviour: annotate - ts-extra-args: '--strict --noImplicitAny' - files-changed: ${{ steps.files.outputs.files_updated }} - files-added: ${{ steps.files.outputs.files_created }} - files-deleted: ${{ steps.files.outputs.files_deleted }} - line-numbers: ${{ steps.diff.outputs.lineNumbers }} - - i18n_lint: - name: "i18n Check" - uses: matrix-org/matrix-react-sdk/.github/workflows/i18n_check.yml@develop - - rethemendex_lint: - name: "Rethemendex Check" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - run: ./res/css/rethemendex.sh - - - run: git diff --exit-code - - js_lint: - name: "ESLint" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - uses: actions/setup-node@v3 - with: - cache: 'yarn' - - # Does not need branch matching as only analyses this layer - - name: Install Deps - run: "yarn install" - - - name: Run Linter - run: "yarn run lint:js" - - style_lint: - name: "Style Lint" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - uses: actions/setup-node@v3 - with: - cache: 'yarn' - - # Does not need branch matching as only analyses this layer - - name: Install Deps - run: "yarn install" - - - name: Run Linter - run: "yarn run lint:style" - - analyse_dead_code: - name: "Analyse Dead Code" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - uses: actions/setup-node@v3 - with: - cache: 'yarn' - - - name: Install Deps - run: "scripts/ci/layered.sh" - - - name: Dead Code Analysis - run: | - cd element-web - yarn run analyse:unused-exports + ts_lint: + name: "Typescript Syntax Check" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + cache: "yarn" + + - name: Install Deps + run: "./scripts/ci/install-deps.sh --ignore-scripts" + + - name: Typecheck + run: "yarn run lint:types" + + - name: Switch js-sdk to release mode + working-directory: node_modules/matrix-js-sdk + run: | + scripts/switch_package_to_release.js + yarn install + yarn run build:compile + yarn run build:types + + - name: Typecheck (release mode) + run: "yarn run lint:types" + + tsc-strict: + name: Typescript Strict Error Checker + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + pull-requests: read + checks: write + strategy: + fail-fast: false + matrix: + args: + - "--strict --noImplicitAny" + - "--noImplicitAny" + steps: + - uses: actions/checkout@v3 + + - name: Install Deps + run: "scripts/ci/layered.sh" + + - name: Get diff lines + id: diff + uses: Equip-Collaboration/diff-line-numbers@v1.0.0 + with: + include: '["\\.tsx?$"]' + + - name: Detecting files changed + id: files + uses: futuratrepadeira/changed-files@v4.0.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + pattern: '^.*\.tsx?$' + + - uses: t3chguy/typescript-check-action@main + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + use-check: false + check-fail-mode: added + output-behaviour: annotate + ts-extra-args: ${{ matrix.args }} + files-changed: ${{ steps.files.outputs.files_updated }} + files-added: ${{ steps.files.outputs.files_created }} + files-deleted: ${{ steps.files.outputs.files_deleted }} + line-numbers: ${{ steps.diff.outputs.lineNumbers }} + + i18n_lint: + name: "i18n Check" + uses: matrix-org/matrix-react-sdk/.github/workflows/i18n_check.yml@develop + + rethemendex_lint: + name: "Rethemendex Check" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - run: ./res/css/rethemendex.sh + + - run: git diff --exit-code + + js_lint: + name: "ESLint" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + cache: "yarn" + + # Does not need branch matching as only analyses this layer + - name: Install Deps + run: "yarn install" + + - name: Run Linter + run: "yarn run lint:js" + + style_lint: + name: "Style Lint" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + cache: "yarn" + + # Does not need branch matching as only analyses this layer + - name: Install Deps + run: "yarn install" + + - name: Run Linter + run: "yarn run lint:style" + + analyse_dead_code: + name: "Analyse Dead Code" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + cache: "yarn" + + - name: Install Deps + run: "scripts/ci/layered.sh" + + - name: Dead Code Analysis + run: | + cd element-web + yarn run analyse:unused-exports diff --git a/.percy.yml b/.percy.yml index deca7f58f7c..78238924233 100644 --- a/.percy.yml +++ b/.percy.yml @@ -1,7 +1,7 @@ version: 2 snapshot: - widths: - - 1024 - - 1920 + widths: + - 1024 + - 1920 percy: - defer-uploads: true + defer-uploads: true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000000..072361a37de --- /dev/null +++ b/.prettierignore @@ -0,0 +1,19 @@ +/coverage +/lib + +/.idea +.vscode +.vscode/ + +# Legacy skinning file that some people might still have +/src/component-index.js + +/.npmrc +/*.log +package-lock.json +yarn.lock + +/src/i18n/strings + +# This file is owned, parsed, and generated by allchange, which doesn't comply with prettier +/CHANGELOG.md diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000000..6a17910f1a0 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1 @@ +module.exports = require("eslint-plugin-matrix-org/.prettierrc.js"); diff --git a/.stylelintrc.js b/.stylelintrc.js index ab5c7968375..e23a18353a8 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -1,12 +1,9 @@ module.exports = { - "extends": "stylelint-config-standard", - customSyntax: require('postcss-scss'), - "plugins": [ - "stylelint-scss", - ], - "rules": { + extends: ["stylelint-config-standard", "stylelint-config-prettier"], + customSyntax: require("postcss-scss"), + plugins: ["stylelint-scss"], + rules: { "color-hex-case": null, - "indentation": 4, "comment-empty-line-before": null, "declaration-empty-line-before": null, "length-zero-no-unit": null, @@ -20,15 +17,18 @@ module.exports = { "at-rule-no-unknown": null, "no-descending-specificity": null, "no-empty-first-line": true, - "scss/at-rule-no-unknown": [true, { - // https://github.com/vector-im/element-web/issues/10544 - "ignoreAtRules": ["define-mixin"], - }], + "scss/at-rule-no-unknown": [ + true, + { + // https://github.com/vector-im/element-web/issues/10544 + ignoreAtRules: ["define-mixin"], + }, + ], // Disable `&_kind`-style selectors while our unused CSS approach is "Find & Replace All" // rather than a CI thing. Shorthand selectors are harder to detect when searching for a // class name. This regex is trying to *allow* anything except `&words`, such as `&::before`, // `&.mx_Class`, etc. - "selector-nested-pattern": "^((&[ :.\\\[,])|([^&]))", + "selector-nested-pattern": "^((&[ :.\\[,])|([^&]))", "declaration-colon-space-after": "always-single-line", // Disable some defaults "selector-class-pattern": null, @@ -50,4 +50,4 @@ module.exports = { "number-max-precision": null, "no-invalid-double-slash-comments": true, }, -} +}; diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d46f19fb4f..efb16fb300c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +Changes in [3.63.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.63.0) (2022-12-21) +===================================================================================================== + +## ✨ Features + * Prevent unnecessary m.direct updates ([\#9805](https://github.com/matrix-org/matrix-react-sdk/pull/9805)). Fixes vector-im/element-web#24059. + * Fix checkForPreJoinUISI for thread roots ([\#9803](https://github.com/matrix-org/matrix-react-sdk/pull/9803)). Fixes vector-im/element-web#24054. + * Add inline code formatting to rich text editor ([\#9720](https://github.com/matrix-org/matrix-react-sdk/pull/9720)). + * Add emoji handling for plain text mode of the new rich text editor ([\#9727](https://github.com/matrix-org/matrix-react-sdk/pull/9727)). + * Overlay virtual room call events into main timeline ([\#9626](https://github.com/matrix-org/matrix-react-sdk/pull/9626)). Fixes vector-im/element-web#22929. + * Adds a new section under "Room Settings" > "Roles & Permissions" which adds the possibility to multiselect users from this room and grant them more permissions. ([\#9596](https://github.com/matrix-org/matrix-react-sdk/pull/9596)). Contributed by @GoodGuyMarco. + * Add emoji handling for rich text mode ([\#9661](https://github.com/matrix-org/matrix-react-sdk/pull/9661)). + * Add setting to hide bold notifications ([\#9705](https://github.com/matrix-org/matrix-react-sdk/pull/9705)). + * Further password reset flow enhancements ([\#9662](https://github.com/matrix-org/matrix-react-sdk/pull/9662)). + * Snooze the bulk unverified sessions reminder on dismiss ([\#9706](https://github.com/matrix-org/matrix-react-sdk/pull/9706)). + * Honor advanced audio processing settings when recording voice messages ([\#9610](https://github.com/matrix-org/matrix-react-sdk/pull/9610)). Contributed by @MrAnno. + * Improve the visual balance of bubble layout ([\#9704](https://github.com/matrix-org/matrix-react-sdk/pull/9704)). + * Add config setting to disable bulk unverified sessions nag ([\#9657](https://github.com/matrix-org/matrix-react-sdk/pull/9657)). + * Only display bulk unverified sessions nag when current sessions is verified ([\#9656](https://github.com/matrix-org/matrix-react-sdk/pull/9656)). + * Separate labs and betas more clearly ([\#8969](https://github.com/matrix-org/matrix-react-sdk/pull/8969)). Fixes vector-im/element-web#22706. + * Show user an error if we fail to create a DM for verification. ([\#9624](https://github.com/matrix-org/matrix-react-sdk/pull/9624)). + +## 🐛 Bug Fixes + * Fix issue where thread panel did not update correctly ([\#9746](https://github.com/matrix-org/matrix-react-sdk/pull/9746)). Fixes vector-im/element-web#23971. + * Remove async call to get virtual room from room load ([\#9743](https://github.com/matrix-org/matrix-react-sdk/pull/9743)). Fixes vector-im/element-web#23968. + * Check each thread for unread messages. ([\#9723](https://github.com/matrix-org/matrix-react-sdk/pull/9723)). + * Device manage - handle sessions that don't support encryption ([\#9717](https://github.com/matrix-org/matrix-react-sdk/pull/9717)). Fixes vector-im/element-web#23722. + * Fix hover state for formatting buttons (Rich text editor) (fix vector-im/element-web/issues/23832) ([\#9715](https://github.com/matrix-org/matrix-react-sdk/pull/9715)). + * Don't allow group calls to be unterminated ([\#9710](https://github.com/matrix-org/matrix-react-sdk/pull/9710)). + * Fix replies to emotes not showing as inline ([\#9707](https://github.com/matrix-org/matrix-react-sdk/pull/9707)). Fixes vector-im/element-web#23903. + * Update copy of 'Change layout' button to match Element Call ([\#9703](https://github.com/matrix-org/matrix-react-sdk/pull/9703)). + * Fix call splitbrains when switching between rooms ([\#9692](https://github.com/matrix-org/matrix-react-sdk/pull/9692)). + * bugfix: fix an issue where the Notifier would incorrectly fire for non-timeline events ([\#9664](https://github.com/matrix-org/matrix-react-sdk/pull/9664)). Fixes vector-im/element-web#17263. + * Fix power selector being wrongly disabled for admins themselves ([\#9681](https://github.com/matrix-org/matrix-react-sdk/pull/9681)). Fixes vector-im/element-web#23882. + * Show day counts in call durations ([\#9641](https://github.com/matrix-org/matrix-react-sdk/pull/9641)). + Changes in [3.62.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.62.0) (2022-12-06) ===================================================================================================== diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dbe74182b1f..a6497b1d40f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,3 @@ -Contributing code to matrix-react-sdk -===================================== +# Contributing code to matrix-react-sdk matrix-react-sdk follows the same pattern as https://github.com/vector-im/element-web/blob/develop/CONTRIBUTING.md diff --git a/README.md b/README.md index 16f3d1f5acf..832020fb611 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,18 @@ `-- matrix-js-sdk (Matrix client js sdk) -matrix-react-sdk -================ +# matrix-react-sdk This is a react-based SDK for inserting a Matrix chat/voip client into a web page. This package provides the React components needed to build a Matrix web client -using React. It is not useable in isolation, and instead must be used from +using React. It is not useable in isolation, and instead must be used from a 'skin'. A skin provides: - * Customised implementations of presentation components. - * Custom CSS - * The containing application - * Zero or more 'modules' containing non-UI functionality + +- Customised implementations of presentation components. +- Custom CSS +- The containing application +- Zero or more 'modules' containing non-UI functionality As of Aug 2018, the only skin that exists is [`vector-im/element-web`](https://github.com/vector-im/element-web/); it and @@ -26,19 +26,19 @@ As of Aug 2018, the only skin that exists is be considered as a single project (for instance, matrix-react-sdk bugs are currently filed against vector-im/element-web rather than this project). -Translation Status ------------------- +## Translation Status + [![Translation status](https://translate.element.io/widgets/element-web/-/multi-auto.svg)](https://translate.element.io/engage/element-web/?utm_source=widget) -Developer Guide ---------------- +## Developer Guide Platform Targets: - * Chrome, Firefox and Safari. - * WebRTC features (VoIP and Video calling) are only available in Chrome & Firefox. - * Mobile Web is not currently a target platform - instead please use the native - iOS (https://github.com/matrix-org/matrix-ios-kit) and Android - (https://github.com/matrix-org/matrix-android-sdk2) SDKs. + +- Chrome, Firefox and Safari. +- WebRTC features (VoIP and Video calling) are only available in Chrome & Firefox. +- Mobile Web is not currently a target platform - instead please use the native + iOS (https://github.com/matrix-org/matrix-ios-kit) and Android + (https://github.com/matrix-org/matrix-android-sdk2) SDKs. All code lands on the `develop` branch - `master` is only used for stable releases. **Please file PRs against `develop`!!** @@ -50,22 +50,23 @@ Our code style is also the same as Element's: https://github.com/vector-im/element-web/blob/develop/code_style.md Code should be committed as follows: - * All new components: - https://github.com/matrix-org/matrix-react-sdk/tree/master/src/components - * Element-specific components: - https://github.com/vector-im/element-web/tree/master/src/components - * In practice, `matrix-react-sdk` is still evolving so fast that the - maintenance burden of customising and overriding these components for - Element can seriously impede development. So right now, there should be - very few (if any) customisations for Element. - * CSS: https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css - * Theme specific CSS & resources: - https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes + +- All new components: + https://github.com/matrix-org/matrix-react-sdk/tree/master/src/components +- Element-specific components: + https://github.com/vector-im/element-web/tree/master/src/components + - In practice, `matrix-react-sdk` is still evolving so fast that the + maintenance burden of customising and overriding these components for + Element can seriously impede development. So right now, there should be + very few (if any) customisations for Element. +- CSS: https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css +- Theme specific CSS & resources: + https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes React components in matrix-react-sdk come in two different flavours: -'structures' and 'views'. Structures are stateful components which handle the +'structures' and 'views'. Structures are stateful components which handle the more complicated business logic of the app, delegating their actual presentation -rendering to stateless 'view' components. For instance, the RoomView component +rendering to stateless 'view' components. For instance, the RoomView component that orchestrates the act of visualising the contents of a given Matrix chat room tracks lots of state for its child components which it passes into them for visual rendering via props. @@ -73,74 +74,72 @@ visual rendering via props. Good separation between the components is maintained by adopting various best practices that anyone working with the SDK needs to be aware of and uphold: - * Components are named with upper camel case (e.g. views/rooms/EventTile.js) +- Components are named with upper camel case (e.g. views/rooms/EventTile.js) - * They are organised in a typically two-level hierarchy - first whether the +- They are organised in a typically two-level hierarchy - first whether the component is a view or a structure, and then a broad functional grouping (e.g. 'rooms' here) - * The view's CSS file MUST have the same name (e.g. view/rooms/MessageTile.css). +- The view's CSS file MUST have the same name (e.g. view/rooms/MessageTile.css). CSS for matrix-react-sdk currently resides in https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css. - * Per-view CSS is optional - it could choose to inherit all its styling from +- Per-view CSS is optional - it could choose to inherit all its styling from the context of the rest of the app, although this is unusual for any but - * Theme specific CSS & resources: - https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes - structural components (lacking presentation logic) and the simplest view - components. +- Theme specific CSS & resources: + https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes + structural components (lacking presentation logic) and the simplest view + components. - * The view MUST *only* refer to the CSS rules defined in its own CSS file. +- The view MUST _only_ refer to the CSS rules defined in its own CSS file. 'Stealing' styling information from other components (including parents) is not cool, as it breaks the independence of the components. - * CSS classes are named with an app-specific name-spacing prefix to try to - avoid CSS collisions. The base skin shipped by Matrix.org with the - matrix-react-sdk uses the naming prefix "mx_". A company called Yoyodyne - Inc might use a prefix like "yy_" for its app-specific classes. +- CSS classes are named with an app-specific name-spacing prefix to try to + avoid CSS collisions. The base skin shipped by Matrix.org with the + matrix-react-sdk uses the naming prefix "mx*". A company called Yoyodyne + Inc might use a prefix like "yy*" for its app-specific classes. - * CSS classes use upper camel case when they describe React components - e.g. +- CSS classes use upper camel case when they describe React components - e.g. .mx_MessageTile is the selector for the CSS applied to a MessageTile view. - * CSS classes for DOM elements within a view which aren't components are named +- CSS classes for DOM elements within a view which aren't components are named by appending a lower camel case identifier to the view's class name - e.g. .mx_MessageTile_randomDiv is how you'd name the class of an arbitrary div within the MessageTile view. - * We deliberately use vanilla CSS 3.0 to avoid adding any more magic - dependencies into the mix than we already have. App developers are welcome - to use whatever floats their boat however. In future we'll start using +- We deliberately use vanilla CSS 3.0 to avoid adding any more magic + dependencies into the mix than we already have. App developers are welcome + to use whatever floats their boat however. In future we'll start using css-next to pull in features like CSS variable support. - * The CSS for a component can override the rules for child components. - For instance, .mx_RoomList .mx_RoomTile {} would be the selector to override +- The CSS for a component can override the rules for child components. + For instance, .mx*RoomList .mx_RoomTile {} would be the selector to override styles of RoomTiles when viewed in the context of a RoomList view. - Overrides *must* be scoped to the View's CSS class - i.e. don't just define + Overrides \_must* be scoped to the View's CSS class - i.e. don't just define .mx_RoomTile {} in RoomList.css - only RoomTile.css is allowed to define its - own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override - only to the context of RoomList views. N.B. overrides should be relatively + own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override + only to the context of RoomList views. N.B. overrides should be relatively rare as in general CSS inheritance should be enough. - * Components should render only within the bounding box of their outermost DOM +- Components should render only within the bounding box of their outermost DOM element. Page-absolute positioning and negative CSS margins and similar are generally not cool and stop the component from being reused easily in different places. Originally `matrix-react-sdk` followed the Atomic design pattern as per -http://patternlab.io to try to encourage a modular architecture. However, we +http://patternlab.io to try to encourage a modular architecture. However, we found that the grouping of components into atoms/molecules/organisms made them harder to find relative to a functional split, and didn't emphasise the distinction between 'structural' and 'view' components, so we backed away from it. -Github Issues -------------- +## Github Issues All issues should be filed under https://github.com/vector-im/element-web/issues for now. -Development ------------ +## Development Ensure you have the latest LTS version of Node.js installed. diff --git a/__mocks__/languages.json b/__mocks__/languages.json index f62fe9b9b43..36ec89561b2 100644 --- a/__mocks__/languages.json +++ b/__mocks__/languages.json @@ -1,10 +1,10 @@ { - "en": { - "fileName": "en_EN.json", - "label": "English" - }, - "en-us": { - "fileName": "en_US.json", - "label": "English (US)" - } + "en": { + "fileName": "en_EN.json", + "label": "English" + }, + "en-us": { + "fileName": "en_US.json", + "label": "English (US)" + } } diff --git a/__mocks__/maplibre-gl.js b/__mocks__/maplibre-gl.js index fe6ec9139e6..0398a1df798 100644 --- a/__mocks__/maplibre-gl.js +++ b/__mocks__/maplibre-gl.js @@ -15,7 +15,7 @@ limitations under the License. */ const EventEmitter = require("events"); -const { LngLat, NavigationControl, LngLatBounds, AttributionControl } = require('maplibre-gl'); +const { LngLat, NavigationControl, LngLatBounds, AttributionControl } = require("maplibre-gl"); class MockMap extends EventEmitter { addControl = jest.fn(); @@ -32,7 +32,7 @@ class MockGeolocateControl extends EventEmitter { trigger = jest.fn(); } const MockGeolocateInstance = new MockGeolocateControl(); -const MockMarker = {} +const MockMarker = {}; MockMarker.setLngLat = jest.fn().mockReturnValue(MockMarker); MockMarker.addTo = jest.fn().mockReturnValue(MockMarker); MockMarker.remove = jest.fn().mockReturnValue(MockMarker); diff --git a/__mocks__/svg.js b/__mocks__/svg.js index c2dc9d9aa73..ee2ab11a014 100644 --- a/__mocks__/svg.js +++ b/__mocks__/svg.js @@ -1,2 +1,2 @@ -export const Icon = 'div'; +export const Icon = "div"; export default "image-file-stub"; diff --git a/babel.config.js b/babel.config.js index ac94a29559e..1c4adcb7f70 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,18 +1,21 @@ module.exports = { - "sourceMaps": "inline", - "presets": [ - ["@babel/preset-env", { - "targets": [ - "last 2 Chrome versions", - "last 2 Firefox versions", - "last 2 Safari versions", - "last 2 Edge versions", - ], - }], + sourceMaps: "inline", + presets: [ + [ + "@babel/preset-env", + { + targets: [ + "last 2 Chrome versions", + "last 2 Firefox versions", + "last 2 Safari versions", + "last 2 Edge versions", + ], + }, + ], "@babel/preset-typescript", "@babel/preset-react", ], - "plugins": [ + plugins: [ "@babel/plugin-proposal-export-default-from", "@babel/plugin-proposal-numeric-separator", "@babel/plugin-proposal-class-properties", diff --git a/cypress.config.ts b/cypress.config.ts index 87f8952dbcc..3aca5da28dd 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -14,21 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { defineConfig } from 'cypress'; +import { defineConfig } from "cypress"; export default defineConfig({ videoUploadOnPasses: false, - projectId: 'ppvnzg', + projectId: "ppvnzg", experimentalInteractiveRunEvents: true, defaultCommandTimeout: 10000, chromeWebSecurity: false, e2e: { setupNodeEvents(on, config) { - return require('./cypress/plugins/index.ts').default(on, config); + return require("./cypress/plugins/index.ts").default(on, config); }, - baseUrl: 'http://localhost:8080', + baseUrl: "http://localhost:8080", experimentalSessionAndOrigin: true, - specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}', + specPattern: "cypress/e2e/**/*.{js,jsx,ts,tsx}", }, env: { // Docker tag to use for `ghcr.io/matrix-org/sliding-sync-proxy` image. diff --git a/cypress/e2e/composer/composer.spec.ts b/cypress/e2e/composer/composer.spec.ts index 6fe562e12a7..0b042ccefe4 100644 --- a/cypress/e2e/composer/composer.spec.ts +++ b/cypress/e2e/composer/composer.spec.ts @@ -23,7 +23,7 @@ describe("Composer", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; }); }); @@ -42,26 +42,26 @@ describe("Composer", () => { it("sends a message when you click send or press Enter", () => { // Type a message - cy.get('div[contenteditable=true]').type('my message 0'); + cy.get("div[contenteditable=true]").type("my message 0"); // It has not been sent yet - cy.contains('.mx_EventTile_body', 'my message 0').should('not.exist'); + cy.contains(".mx_EventTile_body", "my message 0").should("not.exist"); // Click send cy.get('div[aria-label="Send message"]').click(); // It has been sent - cy.contains('.mx_EventTile_body', 'my message 0'); + cy.contains(".mx_EventTile_body", "my message 0"); // Type another and press Enter afterwards - cy.get('div[contenteditable=true]').type('my message 1{enter}'); + cy.get("div[contenteditable=true]").type("my message 1{enter}"); // It was sent - cy.contains('.mx_EventTile_body', 'my message 1'); + cy.contains(".mx_EventTile_body", "my message 1"); }); it("can write formatted text", () => { - cy.get('div[contenteditable=true]').type('my bold{ctrl+b} message'); + cy.get("div[contenteditable=true]").type("my bold{ctrl+b} message"); cy.get('div[aria-label="Send message"]').click(); // Note: both "bold" and "message" are bold, which is probably surprising - cy.contains('.mx_EventTile_body strong', 'bold message'); + cy.contains(".mx_EventTile_body strong", "bold message"); }); it("should allow user to input emoji via graphical picker", () => { @@ -74,7 +74,7 @@ describe("Composer", () => { }); cy.get(".mx_ContextualMenu_background").click(); // Close emoji picker - cy.get('div[contenteditable=true]').type("{enter}"); // Send message + cy.get("div[contenteditable=true]").type("{enter}"); // Send message cy.contains(".mx_EventTile_body", "😇"); }); @@ -86,14 +86,14 @@ describe("Composer", () => { it("only sends when you press Ctrl+Enter", () => { // Type a message and press Enter - cy.get('div[contenteditable=true]').type('my message 3{enter}'); + cy.get("div[contenteditable=true]").type("my message 3{enter}"); // It has not been sent yet - cy.contains('.mx_EventTile_body', 'my message 3').should('not.exist'); + cy.contains(".mx_EventTile_body", "my message 3").should("not.exist"); // Press Ctrl+Enter - cy.get('div[contenteditable=true]').type('{ctrl+enter}'); + cy.get("div[contenteditable=true]").type("{ctrl+enter}"); // It was sent - cy.contains('.mx_EventTile_body', 'my message 3'); + cy.contains(".mx_EventTile_body", "my message 3"); }); }); }); @@ -109,28 +109,28 @@ describe("Composer", () => { it("sends a message when you click send or press Enter", () => { // Type a message - cy.get('div[contenteditable=true]').type('my message 0'); + cy.get("div[contenteditable=true]").type("my message 0"); // It has not been sent yet - cy.contains('.mx_EventTile_body', 'my message 0').should('not.exist'); + cy.contains(".mx_EventTile_body", "my message 0").should("not.exist"); // Click send cy.get('div[aria-label="Send message"]').click(); // It has been sent - cy.contains('.mx_EventTile_body', 'my message 0'); + cy.contains(".mx_EventTile_body", "my message 0"); // Type another - cy.get('div[contenteditable=true]').type('my message 1'); + cy.get("div[contenteditable=true]").type("my message 1"); // Press enter. Would be nice to just use {enter} but we can't because Cypress // does not trigger an insertParagraph when you do that. - cy.get('div[contenteditable=true]').trigger('input', { inputType: "insertParagraph" }); + cy.get("div[contenteditable=true]").trigger("input", { inputType: "insertParagraph" }); // It was sent - cy.contains('.mx_EventTile_body', 'my message 1'); + cy.contains(".mx_EventTile_body", "my message 1"); }); it("can write formatted text", () => { - cy.get('div[contenteditable=true]').type('my {ctrl+b}bold{ctrl+b} message'); + cy.get("div[contenteditable=true]").type("my {ctrl+b}bold{ctrl+b} message"); cy.get('div[aria-label="Send message"]').click(); - cy.contains('.mx_EventTile_body strong', 'bold'); + cy.contains(".mx_EventTile_body strong", "bold"); }); describe("when Ctrl+Enter is required to send", () => { @@ -140,15 +140,15 @@ describe("Composer", () => { it("only sends when you press Ctrl+Enter", () => { // Type a message and press Enter - cy.get('div[contenteditable=true]').type('my message 3'); - cy.get('div[contenteditable=true]').trigger('input', { inputType: "insertParagraph" }); + cy.get("div[contenteditable=true]").type("my message 3"); + cy.get("div[contenteditable=true]").trigger("input", { inputType: "insertParagraph" }); // It has not been sent yet - cy.contains('.mx_EventTile_body', 'my message 3').should('not.exist'); + cy.contains(".mx_EventTile_body", "my message 3").should("not.exist"); // Press Ctrl+Enter - cy.get('div[contenteditable=true]').type('{ctrl+enter}'); + cy.get("div[contenteditable=true]").type("{ctrl+enter}"); // It was sent - cy.contains('.mx_EventTile_body', 'my message 3'); + cy.contains(".mx_EventTile_body", "my message 3"); }); }); }); diff --git a/cypress/e2e/create-room/create-room.spec.ts b/cypress/e2e/create-room/create-room.spec.ts index 1217c917b64..704fa4a9817 100644 --- a/cypress/e2e/create-room/create-room.spec.ts +++ b/cypress/e2e/create-room/create-room.spec.ts @@ -29,7 +29,7 @@ describe("Create Room", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Jim"); diff --git a/cypress/e2e/crypto/crypto.spec.ts b/cypress/e2e/crypto/crypto.spec.ts index 650f8d585c3..2a55c21b05e 100644 --- a/cypress/e2e/crypto/crypto.spec.ts +++ b/cypress/e2e/crypto/crypto.spec.ts @@ -28,7 +28,7 @@ interface CryptoTestContext extends Mocha.Context { } const waitForVerificationRequest = (cli: MatrixClient): Promise => { - return new Promise(resolve => { + return new Promise((resolve) => { const onVerificationRequestEvent = (request: VerificationRequest) => { // @ts-ignore CryptoEvent is not exported to window.matrixcs; using the string value here cli.off("crypto.verification.request", onVerificationRequestEvent); @@ -49,7 +49,7 @@ const checkDMRoom = () => { cy.contains(".mx_RoomView_body .mx_cryptoEvent", "Encryption enabled").should("exist"); }; -const startDMWithBob = function(this: CryptoTestContext) { +const startDMWithBob = function (this: CryptoTestContext) { cy.get('.mx_RoomList [aria-label="Start chat"]').click(); cy.get('[data-testid="invite-dialog-input"]').type(this.bob.getUserId()); cy.contains(".mx_InviteDialog_tile_nameStack_name", "Bob").click(); @@ -57,11 +57,13 @@ const startDMWithBob = function(this: CryptoTestContext) { cy.get(".mx_InviteDialog_goButton").click(); }; -const testMessages = function(this: CryptoTestContext) { +const testMessages = function (this: CryptoTestContext) { // check the invite message - cy.contains(".mx_EventTile_body", "Hey!").closest(".mx_EventTile").within(() => { - cy.get(".mx_EventTile_e2eIcon_warning").should("not.exist"); - }); + cy.contains(".mx_EventTile_body", "Hey!") + .closest(".mx_EventTile") + .within(() => { + cy.get(".mx_EventTile_e2eIcon_warning").should("not.exist"); + }); // Bob sends a response cy.get("@bobsRoom").then((room) => { @@ -72,41 +74,56 @@ const testMessages = function(this: CryptoTestContext) { .should("not.have.descendants", ".mx_EventTile_e2eIcon_warning"); }; -const bobJoin = function(this: CryptoTestContext) { - cy.window({ log: false }).then(async win => { - const bobRooms = this.bob.getRooms(); - if (!bobRooms.length) { - await new Promise(resolve => { - const onMembership = (_event) => { - this.bob.off(win.matrixcs.RoomMemberEvent.Membership, onMembership); - resolve(); - }; - this.bob.on(win.matrixcs.RoomMemberEvent.Membership, onMembership); - }); - } - }).then(() => { - cy.botJoinRoomByName(this.bob, "Alice").as("bobsRoom"); - }); +const bobJoin = function (this: CryptoTestContext) { + cy.window({ log: false }) + .then(async (win) => { + const bobRooms = this.bob.getRooms(); + if (!bobRooms.length) { + await new Promise((resolve) => { + const onMembership = (_event) => { + this.bob.off(win.matrixcs.RoomMemberEvent.Membership, onMembership); + resolve(); + }; + this.bob.on(win.matrixcs.RoomMemberEvent.Membership, onMembership); + }); + } + }) + .then(() => { + cy.botJoinRoomByName(this.bob, "Alice").as("bobsRoom"); + }); cy.contains(".mx_TextualEvent", "Bob joined the room").should("exist"); }; -const handleVerificationRequest = (request: VerificationRequest): Chainable => { - return cy.wrap(new Promise((resolve) => { - const onShowSas = (event: ISasEvent) => { - verifier.off("show_sas", onShowSas); - event.confirm(); - verifier.done(); - resolve(event.sas.emoji); - }; +/** configure the given MatrixClient to auto-accept any invites */ +function autoJoin(client: MatrixClient) { + cy.window({ log: false }).then(async (win) => { + client.on(win.matrixcs.RoomMemberEvent.Membership, (event, member) => { + if (member.membership === "invite" && member.userId === client.getUserId()) { + client.joinRoom(member.roomId); + } + }); + }); +} - const verifier = request.beginKeyVerification("m.sas.v1"); - verifier.on("show_sas", onShowSas); - verifier.verify(); - })); +const handleVerificationRequest = (request: VerificationRequest): Chainable => { + return cy.wrap( + new Promise((resolve) => { + const onShowSas = (event: ISasEvent) => { + verifier.off("show_sas", onShowSas); + event.confirm(); + verifier.done(); + resolve(event.sas.emoji); + }; + + const verifier = request.beginKeyVerification("m.sas.v1"); + verifier.on("show_sas", onShowSas); + verifier.verify(); + }), + ); }; -const verify = function(this: CryptoTestContext) { +const verify = function (this: CryptoTestContext) { const bobsVerificationRequestPromise = waitForVerificationRequest(this.bob); openRoomInfo().within(() => { @@ -114,14 +131,16 @@ const verify = function(this: CryptoTestContext) { cy.contains(".mx_EntityTile_name", "Bob").click(); cy.contains(".mx_UserInfo_verifyButton", "Verify").click(); cy.contains(".mx_AccessibleButton", "Start Verification").click(); - cy.wrap(bobsVerificationRequestPromise).then((verificationRequest: VerificationRequest) => { - verificationRequest.accept(); - return verificationRequest; - }).as("bobsVerificationRequest"); + cy.wrap(bobsVerificationRequestPromise) + .then((verificationRequest: VerificationRequest) => { + verificationRequest.accept(); + return verificationRequest; + }) + .as("bobsVerificationRequest"); cy.contains(".mx_AccessibleButton", "Verify by emoji").click(); cy.get("@bobsVerificationRequest").then((request: VerificationRequest) => { return handleVerificationRequest(request).then((emojis: EmojiMapping[]) => { - cy.get('.mx_VerificationShowSas_emojiSas_block').then((emojiBlocks) => { + cy.get(".mx_VerificationShowSas_emojiSas_block").then((emojiBlocks) => { emojis.forEach((emoji: EmojiMapping, index: number) => { expect(emojiBlocks[index].textContent.toLowerCase()).to.eq(emoji[0] + emoji[1]); }); @@ -134,15 +153,17 @@ const verify = function(this: CryptoTestContext) { }); }; -describe("Cryptography", function() { - beforeEach(function() { - cy.startSynapse("default").as("synapse").then((synapse: SynapseInstance) => { - cy.initTestUser(synapse, "Alice"); - cy.getBot(synapse, { displayName: "Bob", autoAcceptInvites: false }).as("bob"); - }); +describe("Cryptography", function () { + beforeEach(function () { + cy.startSynapse("default") + .as("synapse") + .then((synapse: SynapseInstance) => { + cy.initTestUser(synapse, "Alice"); + cy.getBot(synapse, { displayName: "Bob", autoAcceptInvites: false }).as("bob"); + }); }); - afterEach(function(this: CryptoTestContext) { + afterEach(function (this: CryptoTestContext) { cy.stopSynapse(this.synapse); }); @@ -161,17 +182,32 @@ describe("Cryptography", function() { return; }); - it("creating a DM should work, being e2e-encrypted / user verification", function(this: CryptoTestContext) { + it("creating a DM should work, being e2e-encrypted / user verification", function (this: CryptoTestContext) { cy.bootstrapCrossSigning(); startDMWithBob.call(this); // send first message - cy.get(".mx_BasicMessageComposer_input") - .click() - .should("have.focus") - .type("Hey!{enter}"); + cy.get(".mx_BasicMessageComposer_input").click().should("have.focus").type("Hey!{enter}"); checkDMRoom(); bobJoin.call(this); testMessages.call(this); verify.call(this); }); + + it("should allow verification when there is no existing DM", function (this: CryptoTestContext) { + cy.bootstrapCrossSigning(); + autoJoin(this.bob); + + /* we need to have a room with the other user present, so we can open the verification panel */ + let roomId: string; + cy.createRoom({ name: "TestRoom", invite: [this.bob.getUserId()] }).then((_room1Id) => { + roomId = _room1Id; + cy.log(`Created test room ${roomId}`); + cy.visit(`/#/room/${roomId}`); + // wait for Bob to join the room, otherwise our attempt to open his user details may race + // with his join. + cy.contains(".mx_TextualEvent", "Bob joined the room").should("exist"); + }); + + verify.call(this); + }); }); diff --git a/cypress/e2e/editing/editing.spec.ts b/cypress/e2e/editing/editing.spec.ts index f08466ab306..6f16a6a4cd9 100644 --- a/cypress/e2e/editing/editing.spec.ts +++ b/cypress/e2e/editing/editing.spec.ts @@ -24,19 +24,14 @@ import { SynapseInstance } from "../../plugins/synapsedocker"; import Chainable = Cypress.Chainable; const sendEvent = (roomId: string): Chainable => { - return cy.sendEvent( - roomId, - null, - "m.room.message" as EventType, - MessageEvent.from("Message").serialize().content, - ); + return cy.sendEvent(roomId, null, "m.room.message" as EventType, MessageEvent.from("Message").serialize().content); }; describe("Editing", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Edith").then(() => { cy.injectAxe(); @@ -50,7 +45,7 @@ describe("Editing", () => { }); it("should close the composer when clicking save after making a change and undoing it", () => { - cy.get("@roomId").then(roomId => { + cy.get("@roomId").then((roomId) => { sendEvent(roomId); cy.visit("/#/room/" + roomId); }); diff --git a/cypress/e2e/integration-manager/get-openid-token.spec.ts b/cypress/e2e/integration-manager/get-openid-token.spec.ts index 6f4f977c36f..f799437862c 100644 --- a/cypress/e2e/integration-manager/get-openid-token.spec.ts +++ b/cypress/e2e/integration-manager/get-openid-token.spec.ts @@ -77,18 +77,18 @@ describe("Integration Manager: Get OpenID Token", () => { let integrationManagerUrl: string; beforeEach(() => { - cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then(url => { + cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => { integrationManagerUrl = url; }); - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, USER_DISPLAY_NAME, () => { - cy.window().then(win => { + cy.window().then((win) => { win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN); win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN); }); - }).then(user => { + }).then((user) => { testUser = user; }); @@ -107,8 +107,8 @@ describe("Integration Manager: Get OpenID Token", () => { }).as("integrationManager"); // Succeed when checking the token is valid - cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, req => { - req.continue(res => { + cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, (req) => { + req.continue((res) => { return res.send(200, { user_id: testUser.userId, }); @@ -127,16 +127,14 @@ describe("Integration Manager: Get OpenID Token", () => { }); it("should successfully obtain an openID token", () => { - cy.all([ - cy.get<{}>("@integrationManager"), - ]).then(() => { + cy.all([cy.get<{}>("@integrationManager")]).then(() => { cy.viewRoomByName(ROOM_NAME); openIntegrationManager(); sendActionFromIntegrationManager(integrationManagerUrl); cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => { - cy.get("#message-response").should('include.text', 'access_token'); + cy.get("#message-response").should("include.text", "access_token"); }); }); }); diff --git a/cypress/e2e/integration-manager/kick.spec.ts b/cypress/e2e/integration-manager/kick.spec.ts index f31c4032fdb..e182d840de3 100644 --- a/cypress/e2e/integration-manager/kick.spec.ts +++ b/cypress/e2e/integration-manager/kick.spec.ts @@ -87,8 +87,9 @@ function expectKickedMessage(shouldExist: boolean) { cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click({ multiple: true }); // Check for the event message (or lack thereof) - cy.contains(".mx_EventTile_line", `${USER_DISPLAY_NAME} removed ${BOT_DISPLAY_NAME}: ${KICK_REASON}`) - .should(shouldExist ? "exist" : "not.exist"); + cy.contains(".mx_EventTile_line", `${USER_DISPLAY_NAME} removed ${BOT_DISPLAY_NAME}: ${KICK_REASON}`).should( + shouldExist ? "exist" : "not.exist", + ); } describe("Integration Manager: Kick", () => { @@ -97,18 +98,18 @@ describe("Integration Manager: Kick", () => { let integrationManagerUrl: string; beforeEach(() => { - cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then(url => { + cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => { integrationManagerUrl = url; }); - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, USER_DISPLAY_NAME, () => { - cy.window().then(win => { + cy.window().then((win) => { win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN); win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN); }); - }).then(user => { + }).then((user) => { testUser = user; }); @@ -127,8 +128,8 @@ describe("Integration Manager: Kick", () => { }).as("integrationManager"); // Succeed when checking the token is valid - cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, req => { - req.continue(res => { + cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, (req) => { + req.continue((res) => { return res.send(200, { user_id: testUser.userId, }); @@ -149,103 +150,100 @@ describe("Integration Manager: Kick", () => { }); it("should kick the target", () => { - cy.all([ - cy.get("@bob"), - cy.get("@roomId"), - cy.get<{}>("@integrationManager"), - ]).then(([targetUser, roomId]) => { - const targetUserId = targetUser.getUserId(); - cy.viewRoomByName(ROOM_NAME); - cy.inviteUser(roomId, targetUserId); - cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist'); - - openIntegrationManager(); - sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId); - closeIntegrationManager(integrationManagerUrl); - expectKickedMessage(true); - }); - }); + cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then( + ([targetUser, roomId]) => { + const targetUserId = targetUser.getUserId(); + cy.viewRoomByName(ROOM_NAME); + cy.inviteUser(roomId, targetUserId); + cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should("exist"); - it("should not kick the target if lacking permissions", () => { - cy.all([ - cy.get("@bob"), - cy.get("@roomId"), - cy.get<{}>("@integrationManager"), - ]).then(([targetUser, roomId]) => { - const targetUserId = targetUser.getUserId(); - cy.viewRoomByName(ROOM_NAME); - cy.inviteUser(roomId, targetUserId); - cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist'); - cy.getClient().then(async client => { - await client.sendStateEvent(roomId, 'm.room.power_levels', { - kick: 50, - users: { - [testUser.userId]: 0, - }, - }); - }).then(() => { openIntegrationManager(); sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId); closeIntegrationManager(integrationManagerUrl); - expectKickedMessage(false); - }); - }); + expectKickedMessage(true); + }, + ); + }); + + it("should not kick the target if lacking permissions", () => { + cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then( + ([targetUser, roomId]) => { + const targetUserId = targetUser.getUserId(); + cy.viewRoomByName(ROOM_NAME); + cy.inviteUser(roomId, targetUserId); + cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should("exist"); + cy.getClient() + .then(async (client) => { + await client.sendStateEvent(roomId, "m.room.power_levels", { + kick: 50, + users: { + [testUser.userId]: 0, + }, + }); + }) + .then(() => { + openIntegrationManager(); + sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId); + closeIntegrationManager(integrationManagerUrl); + expectKickedMessage(false); + }); + }, + ); }); it("should no-op if the target already left", () => { - cy.all([ - cy.get("@bob"), - cy.get("@roomId"), - cy.get<{}>("@integrationManager"), - ]).then(([targetUser, roomId]) => { - const targetUserId = targetUser.getUserId(); - cy.viewRoomByName(ROOM_NAME); - cy.inviteUser(roomId, targetUserId); - cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist').then(async () => { - await targetUser.leave(roomId); - }).then(() => { - openIntegrationManager(); - sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId); - closeIntegrationManager(integrationManagerUrl); - expectKickedMessage(false); - }); - }); + cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then( + ([targetUser, roomId]) => { + const targetUserId = targetUser.getUserId(); + cy.viewRoomByName(ROOM_NAME); + cy.inviteUser(roomId, targetUserId); + cy.contains(`${BOT_DISPLAY_NAME} joined the room`) + .should("exist") + .then(async () => { + await targetUser.leave(roomId); + }) + .then(() => { + openIntegrationManager(); + sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId); + closeIntegrationManager(integrationManagerUrl); + expectKickedMessage(false); + }); + }, + ); }); it("should no-op if the target was banned", () => { - cy.all([ - cy.get("@bob"), - cy.get("@roomId"), - cy.get<{}>("@integrationManager"), - ]).then(([targetUser, roomId]) => { - const targetUserId = targetUser.getUserId(); - cy.viewRoomByName(ROOM_NAME); - cy.inviteUser(roomId, targetUserId); - cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist'); - cy.getClient().then(async client => { - await client.ban(roomId, targetUserId); - }).then(() => { + cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then( + ([targetUser, roomId]) => { + const targetUserId = targetUser.getUserId(); + cy.viewRoomByName(ROOM_NAME); + cy.inviteUser(roomId, targetUserId); + cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should("exist"); + cy.getClient() + .then(async (client) => { + await client.ban(roomId, targetUserId); + }) + .then(() => { + openIntegrationManager(); + sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId); + closeIntegrationManager(integrationManagerUrl); + expectKickedMessage(false); + }); + }, + ); + }); + + it("should no-op if the target was never a room member", () => { + cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then( + ([targetUser, roomId]) => { + const targetUserId = targetUser.getUserId(); + cy.viewRoomByName(ROOM_NAME); + openIntegrationManager(); sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId); closeIntegrationManager(integrationManagerUrl); expectKickedMessage(false); - }); - }); - }); - - it("should no-op if the target was never a room member", () => { - cy.all([ - cy.get("@bob"), - cy.get("@roomId"), - cy.get<{}>("@integrationManager"), - ]).then(([targetUser, roomId]) => { - const targetUserId = targetUser.getUserId(); - cy.viewRoomByName(ROOM_NAME); - - openIntegrationManager(); - sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId); - closeIntegrationManager(integrationManagerUrl); - expectKickedMessage(false); - }); + }, + ); }); }); diff --git a/cypress/e2e/lazy-loading/lazy-loading.spec.ts b/cypress/e2e/lazy-loading/lazy-loading.spec.ts index be27c193463..4c6ce14dfea 100644 --- a/cypress/e2e/lazy-loading/lazy-loading.spec.ts +++ b/cypress/e2e/lazy-loading/lazy-loading.spec.ts @@ -31,11 +31,11 @@ describe("Lazy Loading", () => { const charlies: Charly[] = []; beforeEach(() => { - cy.window().then(win => { + cy.window().then((win) => { win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests }); - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Alice"); @@ -44,7 +44,7 @@ describe("Lazy Loading", () => { displayName: "Bob", startClient: false, autoAcceptInvites: false, - }).then(_bob => { + }).then((_bob) => { bob = _bob; }); @@ -54,7 +54,7 @@ describe("Lazy Loading", () => { displayName, startClient: false, autoAcceptInvites: false, - }).then(client => { + }).then((client) => { charlies[i - 1] = { displayName, client }; }); } @@ -71,15 +71,22 @@ describe("Lazy Loading", () => { const charlyMsg2 = "how's it going??"; function setupRoomWithBobAliceAndCharlies(charlies: Charly[]) { - cy.window({ log: false }).then(win => { - return cy.wrap(bob.createRoom({ - name, - room_alias_name: "lltest", - visibility: win.matrixcs.Visibility.Public, - }).then(r => r.room_id), { log: false }).as("roomId"); + cy.window({ log: false }).then((win) => { + return cy + .wrap( + bob + .createRoom({ + name, + room_alias_name: "lltest", + visibility: win.matrixcs.Visibility.Public, + }) + .then((r) => r.room_id), + { log: false }, + ) + .as("roomId"); }); - cy.get("@roomId").then(async roomId => { + cy.get("@roomId").then(async (roomId) => { for (const charly of charlies) { await charly.client.joinRoom(alias); } @@ -122,13 +129,13 @@ describe("Lazy Loading", () => { function checkMemberList(charlies: Charly[]) { getMemberInMemberlist("Alice").should("exist"); getMemberInMemberlist("Bob").should("exist"); - charlies.forEach(charly => { + charlies.forEach((charly) => { getMemberInMemberlist(charly.displayName).should("exist"); }); } function checkMemberListLacksCharlies(charlies: Charly[]) { - charlies.forEach(charly => { + charlies.forEach((charly) => { getMemberInMemberlist(charly.displayName).should("not.exist"); }); } @@ -136,7 +143,7 @@ describe("Lazy Loading", () => { function joinCharliesWhileAliceIsOffline(charlies: Charly[]) { cy.goOffline(); - cy.get("@roomId").then(async roomId => { + cy.get("@roomId").then(async (roomId) => { for (const charly of charlies) { await charly.client.joinRoom(alias); } @@ -163,7 +170,7 @@ describe("Lazy Loading", () => { joinCharliesWhileAliceIsOffline(charly6to10); checkMemberList(charly6to10); - cy.get("@roomId").then(async roomId => { + cy.get("@roomId").then(async (roomId) => { for (const charly of charlies) { await charly.client.leave(roomId); } diff --git a/cypress/e2e/location/location.spec.ts b/cypress/e2e/location/location.spec.ts index b0cc5e555c1..614c88cf8b5 100644 --- a/cypress/e2e/location/location.spec.ts +++ b/cypress/e2e/location/location.spec.ts @@ -31,10 +31,10 @@ describe("Location sharing", () => { }; beforeEach(() => { - cy.window().then(win => { + cy.window().then((win) => { win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests }); - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Tom"); @@ -47,31 +47,28 @@ describe("Location sharing", () => { it("sends and displays pin drop location message successfully", () => { let roomId: string; - cy.createRoom({}).then(_roomId => { + cy.createRoom({}).then((_roomId) => { roomId = _roomId; - cy.visit('/#/room/' + roomId); + cy.visit("/#/room/" + roomId); }); cy.openMessageComposerOptions().within(() => { cy.get('[aria-label="Location"]').click(); }); - selectLocationShareTypeOption('Pin').click(); + selectLocationShareTypeOption("Pin").click(); - cy.get('#mx_LocationPicker_map').click('center'); + cy.get("#mx_LocationPicker_map").click("center"); submitShareLocation(); - cy.get(".mx_RoomView_body .mx_EventTile .mx_MLocationBody", { timeout: 10000 }) - .should('exist') - .click(); + cy.get(".mx_RoomView_body .mx_EventTile .mx_MLocationBody", { timeout: 10000 }).should("exist").click(); // clicking location tile opens maximised map - cy.get('.mx_LocationViewDialog_wrapper').should('exist'); + cy.get(".mx_LocationViewDialog_wrapper").should("exist"); cy.get('[aria-label="Close dialog"]').click(); - cy.get('.mx_Marker') - .should('exist'); + cy.get(".mx_Marker").should("exist"); }); }); diff --git a/cypress/e2e/login/consent.spec.ts b/cypress/e2e/login/consent.spec.ts index c6af9eab22c..dc62ca60607 100644 --- a/cypress/e2e/login/consent.spec.ts +++ b/cypress/e2e/login/consent.spec.ts @@ -24,7 +24,7 @@ describe("Consent", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("consent").then(data => { + cy.startSynapse("consent").then((data) => { synapse = data; cy.initTestUser(synapse, "Bob"); @@ -37,7 +37,7 @@ describe("Consent", () => { it("should prompt the user to consent to terms when server deems it necessary", () => { // Attempt to create a room using the js-sdk which should return an error with `M_CONSENT_NOT_GIVEN` - cy.window().then(win => { + cy.window().then((win) => { win.mxMatrixClientPeg.matrixClient.createRoom({}).catch(() => {}); // Stub `window.open` - clicking the primary button below will call it @@ -50,7 +50,7 @@ describe("Consent", () => { cy.get(".mx_Dialog_primary").click(); }); - cy.get("@windowOpen").then(stub => { + cy.get("@windowOpen").then((stub) => { const url = stub.getCall(0).args[0]; // Go to Synapse's consent page and accept it diff --git a/cypress/e2e/login/login.spec.ts b/cypress/e2e/login/login.spec.ts index 10582870102..de75c28a2e8 100644 --- a/cypress/e2e/login/login.spec.ts +++ b/cypress/e2e/login/login.spec.ts @@ -21,6 +21,10 @@ import { SynapseInstance } from "../../plugins/synapsedocker"; describe("Login", () => { let synapse: SynapseInstance; + beforeEach(() => { + cy.stubDefaultServer(); + }); + afterEach(() => { cy.stopSynapse(synapse); }); @@ -30,7 +34,7 @@ describe("Login", () => { const password = "p4s5W0rD"; beforeEach(() => { - cy.startSynapse("consent").then(data => { + cy.startSynapse("consent").then((data) => { synapse = data; cy.registerUser(synapse, username, password); cy.visit("/#/login"); @@ -48,19 +52,19 @@ describe("Login", () => { cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl); cy.get(".mx_ServerPickerDialog_continue").click(); // wait for the dialog to go away - cy.get('.mx_ServerPickerDialog').should('not.exist'); + cy.get(".mx_ServerPickerDialog").should("not.exist"); cy.get("#mx_LoginForm_username").type(username); cy.get("#mx_LoginForm_password").type(password); cy.get(".mx_Login_submit").click(); - cy.url().should('contain', '/#/home', { timeout: 30000 }); + cy.url().should("contain", "/#/home", { timeout: 30000 }); }); }); describe("logout", () => { beforeEach(() => { - cy.startSynapse("consent").then(data => { + cy.startSynapse("consent").then((data) => { synapse = data; cy.initTestUser(synapse, "Erin"); }); diff --git a/cypress/e2e/polls/polls.spec.ts b/cypress/e2e/polls/polls.spec.ts index 00b944ce9d9..ce0962410c7 100644 --- a/cypress/e2e/polls/polls.spec.ts +++ b/cypress/e2e/polls/polls.spec.ts @@ -33,17 +33,17 @@ describe("Polls", () => { }; const createPoll = ({ title, options }: CreatePollOptions) => { if (options.length < 2) { - throw new Error('Poll must have at least two options'); + throw new Error("Poll must have at least two options"); } - cy.get('.mx_PollCreateDialog').within((pollCreateDialog) => { - cy.get('#poll-topic-input').type(title); + cy.get(".mx_PollCreateDialog").within((pollCreateDialog) => { + cy.get("#poll-topic-input").type(title); options.forEach((option, index) => { const optionId = `#pollcreate_option_${index}`; // click 'add option' button if needed if (pollCreateDialog.find(optionId).length === 0) { - cy.get('.mx_PollCreateDialog_addOption').scrollIntoView().click(); + cy.get(".mx_PollCreateDialog_addOption").scrollIntoView().click(); } cy.get(optionId).scrollIntoView().type(option); }); @@ -56,34 +56,32 @@ describe("Polls", () => { }; const getPollOption = (pollId: string, optionText: string): Chainable => { - return getPollTile(pollId).contains('.mx_MPollBody_option .mx_StyledRadioButton', optionText); + return getPollTile(pollId).contains(".mx_MPollBody_option .mx_StyledRadioButton", optionText); }; const expectPollOptionVoteCount = (pollId: string, optionText: string, votes: number): void => { getPollOption(pollId, optionText).within(() => { - cy.get('.mx_MPollBody_optionVoteCount').should('contain', `${votes} vote`); + cy.get(".mx_MPollBody_optionVoteCount").should("contain", `${votes} vote`); }); }; const botVoteForOption = (bot: MatrixClient, roomId: string, pollId: string, optionText: string): void => { - getPollOption(pollId, optionText).within(ref => { - cy.get('input[type="radio"]').invoke('attr', 'value').then(optionId => { - const pollVote = PollResponseEvent.from([optionId], pollId).serialize(); - bot.sendEvent( - roomId, - pollVote.type, - pollVote.content, - ); - }); + getPollOption(pollId, optionText).within((ref) => { + cy.get('input[type="radio"]') + .invoke("attr", "value") + .then((optionId) => { + const pollVote = PollResponseEvent.from([optionId], pollId).serialize(); + bot.sendEvent(roomId, pollVote.type, pollVote.content); + }); }); }; beforeEach(() => { - cy.enableLabsFeature("feature_thread"); - cy.window().then(win => { + cy.enableLabsFeature("feature_threadstable"); + cy.window().then((win) => { win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests }); - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Tom"); @@ -96,15 +94,15 @@ describe("Polls", () => { it("should be creatable and votable", () => { let bot: MatrixClient; - cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => { + cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => { bot = _bot; }); let roomId: string; - cy.createRoom({}).then(_roomId => { + cy.createRoom({}).then((_roomId) => { roomId = _roomId; cy.inviteUser(roomId, bot.getUserId()); - cy.visit('/#/room/' + roomId); + cy.visit("/#/room/" + roomId); // wait until Bob joined cy.contains(".mx_TextualEvent", "BotBob joined the room").should("exist"); }); @@ -113,34 +111,35 @@ describe("Polls", () => { cy.get('[aria-label="Poll"]').click(); }); - cy.get('.mx_CompoundDialog').percySnapshotElement('Polls Composer'); + cy.get(".mx_CompoundDialog").percySnapshotElement("Polls Composer"); const pollParams = { - title: 'Does the polls feature work?', - options: ['Yes', 'No', 'Maybe'], + title: "Does the polls feature work?", + options: ["Yes", "No", "Maybe"], }; createPoll(pollParams); // Wait for message to send, get its ID and save as @pollId cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title) - .invoke("attr", "data-scroll-tokens").as("pollId"); + .invoke("attr", "data-scroll-tokens") + .as("pollId"); - cy.get("@pollId").then(pollId => { - getPollTile(pollId).percySnapshotElement('Polls Timeline tile - no votes', { percyCSS: hideTimestampCSS }); + cy.get("@pollId").then((pollId) => { + getPollTile(pollId).percySnapshotElement("Polls Timeline tile - no votes", { percyCSS: hideTimestampCSS }); // Bot votes 'Maybe' in the poll botVoteForOption(bot, roomId, pollId, pollParams.options[2]); // no votes shown until I vote, check bots vote has arrived - cy.get('.mx_MPollBody_totalVotes').should('contain', '1 vote cast'); + cy.get(".mx_MPollBody_totalVotes").should("contain", "1 vote cast"); // vote 'Maybe' - getPollOption(pollId, pollParams.options[2]).click('topLeft'); + getPollOption(pollId, pollParams.options[2]).click("topLeft"); // both me and bot have voted Maybe expectPollOptionVoteCount(pollId, pollParams.options[2], 2); // change my vote to 'Yes' - getPollOption(pollId, pollParams.options[0]).click('topLeft'); + getPollOption(pollId, pollParams.options[0]).click("topLeft"); // 1 vote for yes expectPollOptionVoteCount(pollId, pollParams.options[0], 1); @@ -161,15 +160,15 @@ describe("Polls", () => { it("should be editable from context menu if no votes have been cast", () => { let bot: MatrixClient; - cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => { + cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => { bot = _bot; }); let roomId: string; - cy.createRoom({}).then(_roomId => { + cy.createRoom({}).then((_roomId) => { roomId = _roomId; cy.inviteUser(roomId, bot.getUserId()); - cy.visit('/#/room/' + roomId); + cy.visit("/#/room/" + roomId); }); cy.openMessageComposerOptions().within(() => { @@ -177,40 +176,42 @@ describe("Polls", () => { }); const pollParams = { - title: 'Does the polls feature work?', - options: ['Yes', 'No', 'Maybe'], + title: "Does the polls feature work?", + options: ["Yes", "No", "Maybe"], }; createPoll(pollParams); // Wait for message to send, get its ID and save as @pollId - cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", pollParams.title) - .invoke("attr", "data-scroll-tokens").as("pollId"); + cy.get(".mx_RoomView_body .mx_EventTile") + .contains(".mx_EventTile[data-scroll-tokens]", pollParams.title) + .invoke("attr", "data-scroll-tokens") + .as("pollId"); - cy.get("@pollId").then(pollId => { + cy.get("@pollId").then((pollId) => { // Open context menu getPollTile(pollId).rightclick(); // Select edit item - cy.get('.mx_ContextualMenu').within(() => { + cy.get(".mx_ContextualMenu").within(() => { cy.get('[aria-label="Edit"]').click(); }); // Expect poll editing dialog - cy.get('.mx_PollCreateDialog'); + cy.get(".mx_PollCreateDialog"); }); }); it("should not be editable from context menu if votes have been cast", () => { let bot: MatrixClient; - cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => { + cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => { bot = _bot; }); let roomId: string; - cy.createRoom({}).then(_roomId => { + cy.createRoom({}).then((_roomId) => { roomId = _roomId; cy.inviteUser(roomId, bot.getUserId()); - cy.visit('/#/room/' + roomId); + cy.visit("/#/room/" + roomId); }); cy.openMessageComposerOptions().within(() => { @@ -218,51 +219,53 @@ describe("Polls", () => { }); const pollParams = { - title: 'Does the polls feature work?', - options: ['Yes', 'No', 'Maybe'], + title: "Does the polls feature work?", + options: ["Yes", "No", "Maybe"], }; createPoll(pollParams); // Wait for message to send, get its ID and save as @pollId - cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", pollParams.title) - .invoke("attr", "data-scroll-tokens").as("pollId"); + cy.get(".mx_RoomView_body .mx_EventTile") + .contains(".mx_EventTile[data-scroll-tokens]", pollParams.title) + .invoke("attr", "data-scroll-tokens") + .as("pollId"); - cy.get("@pollId").then(pollId => { + cy.get("@pollId").then((pollId) => { // Bot votes 'Maybe' in the poll botVoteForOption(bot, roomId, pollId, pollParams.options[2]); // wait for bot's vote to arrive - cy.get('.mx_MPollBody_totalVotes').should('contain', '1 vote cast'); + cy.get(".mx_MPollBody_totalVotes").should("contain", "1 vote cast"); // Open context menu getPollTile(pollId).rightclick(); // Select edit item - cy.get('.mx_ContextualMenu').within(() => { + cy.get(".mx_ContextualMenu").within(() => { cy.get('[aria-label="Edit"]').click(); }); // Expect error dialog - cy.get('.mx_ErrorDialog'); + cy.get(".mx_ErrorDialog"); }); }); it("should be displayed correctly in thread panel", () => { let botBob: MatrixClient; let botCharlie: MatrixClient; - cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => { + cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => { botBob = _bot; }); - cy.getBot(synapse, { displayName: "BotCharlie" }).then(_bot => { + cy.getBot(synapse, { displayName: "BotCharlie" }).then((_bot) => { botCharlie = _bot; }); let roomId: string; - cy.createRoom({}).then(_roomId => { + cy.createRoom({}).then((_roomId) => { roomId = _roomId; cy.inviteUser(roomId, botBob.getUserId()); cy.inviteUser(roomId, botCharlie.getUserId()); - cy.visit('/#/room/' + roomId); + cy.visit("/#/room/" + roomId); // wait until the bots joined cy.contains(".mx_TextualEvent", "and one other were invited and joined").should("exist"); }); @@ -272,16 +275,17 @@ describe("Polls", () => { }); const pollParams = { - title: 'Does the polls feature work?', - options: ['Yes', 'No', 'Maybe'], + title: "Does the polls feature work?", + options: ["Yes", "No", "Maybe"], }; createPoll(pollParams); // Wait for message to send, get its ID and save as @pollId cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title) - .invoke("attr", "data-scroll-tokens").as("pollId"); + .invoke("attr", "data-scroll-tokens") + .as("pollId"); - cy.get("@pollId").then(pollId => { + cy.get("@pollId").then((pollId) => { // Bob starts thread on the poll botBob.sendMessage(roomId, pollId, { body: "Hello there", @@ -297,22 +301,22 @@ describe("Polls", () => { botVoteForOption(botCharlie, roomId, pollId, pollParams.options[1]); // no votes shown until I vote, check votes have arrived in main tl - cy.get('.mx_RoomView_body .mx_MPollBody_totalVotes').should('contain', '2 votes cast'); + cy.get(".mx_RoomView_body .mx_MPollBody_totalVotes").should("contain", "2 votes cast"); // and thread view - cy.get('.mx_ThreadView .mx_MPollBody_totalVotes').should('contain', '2 votes cast'); + cy.get(".mx_ThreadView .mx_MPollBody_totalVotes").should("contain", "2 votes cast"); - cy.get('.mx_RoomView_body').within(() => { + cy.get(".mx_RoomView_body").within(() => { // vote 'Maybe' in the main timeline poll - getPollOption(pollId, pollParams.options[2]).click('topLeft'); + getPollOption(pollId, pollParams.options[2]).click("topLeft"); // both me and bob have voted Maybe expectPollOptionVoteCount(pollId, pollParams.options[2], 2); }); - cy.get('.mx_ThreadView').within(() => { + cy.get(".mx_ThreadView").within(() => { // votes updated in thread view too expectPollOptionVoteCount(pollId, pollParams.options[2], 2); // change my vote to 'Yes' - getPollOption(pollId, pollParams.options[0]).click('topLeft'); + getPollOption(pollId, pollParams.options[0]).click("topLeft"); }); // Bob updates vote to 'No' @@ -329,11 +333,11 @@ describe("Polls", () => { }; // check counts are correct in main timeline tile - cy.get('.mx_RoomView_body').within(() => { + cy.get(".mx_RoomView_body").within(() => { expectVoteCounts(); }); // and in thread view tile - cy.get('.mx_ThreadView').within(() => { + cy.get(".mx_ThreadView").within(() => { expectVoteCounts(); }); }); diff --git a/cypress/e2e/register/register.spec.ts b/cypress/e2e/register/register.spec.ts index 98ef2bd7290..df5eafb6a06 100644 --- a/cypress/e2e/register/register.spec.ts +++ b/cypress/e2e/register/register.spec.ts @@ -22,8 +22,9 @@ describe("Registration", () => { let synapse: SynapseInstance; beforeEach(() => { + cy.stubDefaultServer(); cy.visit("/#/register"); - cy.startSynapse("consent").then(data => { + cy.startSynapse("consent").then((data) => { synapse = data; }); }); @@ -44,7 +45,7 @@ describe("Registration", () => { cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl); cy.get(".mx_ServerPickerDialog_continue").click(); // wait for the dialog to go away - cy.get('.mx_ServerPickerDialog').should('not.exist'); + cy.get(".mx_ServerPickerDialog").should("not.exist"); cy.get("#mx_RegistrationForm_username").should("be.visible"); // Hide the server text as it contains the randomly allocated Synapse port @@ -74,12 +75,14 @@ describe("Registration", () => { cy.checkA11y(); cy.get(".mx_UseCaseSelection_skip .mx_AccessibleButton").click(); - cy.url().should('contain', '/#/home'); + cy.url().should("contain", "/#/home"); cy.get('[aria-label="User menu"]').click(); cy.get('[aria-label="Security & Privacy"]').click(); - cy.get(".mx_DevicesPanel_myDevice .mx_DevicesPanel_deviceTrust .mx_E2EIcon") - .should("have.class", "mx_E2EIcon_verified"); + cy.get(".mx_DevicesPanel_myDevice .mx_DevicesPanel_deviceTrust .mx_E2EIcon").should( + "have.class", + "mx_E2EIcon_verified", + ); }); it("should require username to fulfil requirements and be available", () => { @@ -88,7 +91,7 @@ describe("Registration", () => { cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl); cy.get(".mx_ServerPickerDialog_continue").click(); // wait for the dialog to go away - cy.get('.mx_ServerPickerDialog').should('not.exist'); + cy.get(".mx_ServerPickerDialog").should("not.exist"); cy.get("#mx_RegistrationForm_username").should("be.visible"); diff --git a/cypress/e2e/regression-tests/pills-click-in-app.spec.ts b/cypress/e2e/regression-tests/pills-click-in-app.spec.ts index cebbc86ed8b..4e484269974 100644 --- a/cypress/e2e/regression-tests/pills-click-in-app.spec.ts +++ b/cypress/e2e/regression-tests/pills-click-in-app.spec.ts @@ -22,7 +22,7 @@ describe("Pills", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Sally"); @@ -33,7 +33,7 @@ describe("Pills", () => { cy.stopSynapse(synapse); }); - it('should navigate clicks internally to the app', () => { + it("should navigate clicks internally to the app", () => { const messageRoom = "Send Messages Here"; const targetLocalpart = "aliasssssssssssss"; cy.createRoom({ @@ -43,34 +43,35 @@ describe("Pills", () => { cy.createRoom({ name: messageRoom, }).as("messageRoomId"); - cy.all([ - cy.get("@targetRoomId"), - cy.get("@messageRoomId"), - ]).then(([targetRoomId, messageRoomId]) => { // discard the target room ID - we don't need it - cy.viewRoomByName(messageRoom); - cy.url().should("contain", `/#/room/${messageRoomId}`); + cy.all([cy.get("@targetRoomId"), cy.get("@messageRoomId")]).then( + ([targetRoomId, messageRoomId]) => { + // discard the target room ID - we don't need it + cy.viewRoomByName(messageRoom); + cy.url().should("contain", `/#/room/${messageRoomId}`); - // send a message using the built-in room mention functionality (autocomplete) - cy.get(".mx_SendMessageComposer .mx_BasicMessageComposer_input") - .type(`Hello world! Join here: #${targetLocalpart.substring(0, 3)}`); - cy.get(".mx_Autocomplete_Completion_title").click(); - cy.get(".mx_MessageComposer_sendMessage").click(); + // send a message using the built-in room mention functionality (autocomplete) + cy.get(".mx_SendMessageComposer .mx_BasicMessageComposer_input").type( + `Hello world! Join here: #${targetLocalpart.substring(0, 3)}`, + ); + cy.get(".mx_Autocomplete_Completion_title").click(); + cy.get(".mx_MessageComposer_sendMessage").click(); - // find the pill in the timeline and click it - cy.get(".mx_EventTile_body .mx_Pill").click(); + // find the pill in the timeline and click it + cy.get(".mx_EventTile_body .mx_Pill").click(); - const localUrl = `/#/room/#${targetLocalpart}:`; - // verify we landed at a sane place - cy.url().should("contain", localUrl); + const localUrl = `/#/room/#${targetLocalpart}:`; + // verify we landed at a sane place + cy.url().should("contain", localUrl); - cy.wait(250); // let the room list settle + cy.wait(250); // let the room list settle - // go back to the message room and try to click on the pill text, as a user would - cy.viewRoomByName(messageRoom); - cy.get(".mx_EventTile_body .mx_Pill .mx_Pill_linkText") - .should("have.css", "pointer-events", "none") - .click({ force: true }); // force is to ensure we bypass pointer-events - cy.url().should("contain", localUrl); - }); + // go back to the message room and try to click on the pill text, as a user would + cy.viewRoomByName(messageRoom); + cy.get(".mx_EventTile_body .mx_Pill .mx_Pill_linkText") + .should("have.css", "pointer-events", "none") + .click({ force: true }); // force is to ensure we bypass pointer-events + cy.url().should("contain", localUrl); + }, + ); }); }); diff --git a/cypress/e2e/right-panel/right-panel.spec.ts b/cypress/e2e/right-panel/right-panel.spec.ts index 604fa0049d6..84e9db9ac6a 100644 --- a/cypress/e2e/right-panel/right-panel.spec.ts +++ b/cypress/e2e/right-panel/right-panel.spec.ts @@ -46,7 +46,7 @@ describe("RightPanel", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, NAME).then(() => cy.window({ log: false }).then(() => { diff --git a/cypress/e2e/room-directory/room-directory.spec.ts b/cypress/e2e/room-directory/room-directory.spec.ts index 9e2ee10c960..b0fec151a7d 100644 --- a/cypress/e2e/room-directory/room-directory.spec.ts +++ b/cypress/e2e/room-directory/room-directory.spec.ts @@ -23,7 +23,7 @@ describe("Room Directory", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Ray"); @@ -36,7 +36,7 @@ describe("Room Directory", () => { }); it("should allow admin to add alias & publish room to directory", () => { - cy.window({ log: false }).then(win => { + cy.window({ log: false }).then((win) => { cy.createRoom({ name: "Gaming", preset: win.matrixcs.Preset.PublicChat, @@ -56,16 +56,14 @@ describe("Room Directory", () => { // Publish into the public rooms directory cy.contains(".mx_SettingsFieldset", "Published Addresses").within(() => { cy.get("#canonicalAlias").find(":selected").should("contain", "#gaming:localhost"); - cy.get(`[aria-label="Publish this room to the public in localhost's room directory?"]`).click() + cy.get(`[aria-label="Publish this room to the public in localhost's room directory?"]`) + .click() .should("have.attr", "aria-checked", "true"); }); cy.closeDialog(); - cy.all([ - cy.get("@bot"), - cy.get("@roomId"), - ]).then(async ([bot, roomId]) => { + cy.all([cy.get("@bot"), cy.get("@roomId")]).then(async ([bot, roomId]) => { const resp = await bot.publicRooms({}); expect(resp.total_room_count_estimate).to.equal(1); expect(resp.chunk).to.have.length(1); @@ -75,10 +73,7 @@ describe("Room Directory", () => { it("should allow finding published rooms in directory", () => { const name = "This is a public room"; - cy.all([ - cy.window({ log: false }), - cy.get("@bot"), - ]).then(([win, bot]) => { + cy.all([cy.window({ log: false }), cy.get("@bot")]).then(([win, bot]) => { bot.createRoom({ visibility: win.matrixcs.Visibility.Public, name, @@ -89,16 +84,17 @@ describe("Room Directory", () => { cy.get('[role="button"][aria-label="Explore rooms"]').click(); cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("Unknown Room"); - cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_otherSearches_messageSearchText") - .should("contain", "can't find the room you're looking for"); + cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_otherSearches_messageSearchText").should( + "contain", + "can't find the room you're looking for", + ); cy.get(".mx_SpotlightDialog_wrapper").percySnapshotElement("Room Directory - filtered no results"); cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("{selectAll}{backspace}test1234"); - cy.contains(".mx_SpotlightDialog .mx_SpotlightDialog_result_publicRoomName", name) - .should("exist"); + cy.contains(".mx_SpotlightDialog .mx_SpotlightDialog_result_publicRoomName", name).should("exist"); cy.get(".mx_SpotlightDialog_wrapper").percySnapshotElement("Room Directory - filtered one result"); cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_option").find(".mx_AccessibleButton").contains("Join").click(); - cy.url().should('contain', `/#/room/#test1234:localhost`); + cy.url().should("contain", `/#/room/#test1234:localhost`); }); }); diff --git a/cypress/e2e/settings/device-management.spec.ts b/cypress/e2e/settings/device-management.spec.ts index ba88db48612..ca3385fcb25 100644 --- a/cypress/e2e/settings/device-management.spec.ts +++ b/cypress/e2e/settings/device-management.spec.ts @@ -25,17 +25,20 @@ describe("Device manager", () => { beforeEach(() => { cy.enableLabsFeature("feature_new_device_manager"); - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; - cy.initTestUser(synapse, "Alice").then(credentials => { - user = credentials; - }).then(() => { - // create some extra sessions to manage - return cy.loginUser(synapse, user.username, user.password); - }).then(() => { - return cy.loginUser(synapse, user.username, user.password); - }); + cy.initTestUser(synapse, "Alice") + .then((credentials) => { + user = credentials; + }) + .then(() => { + // create some extra sessions to manage + return cy.loginUser(synapse, user.username, user.password); + }) + .then(() => { + return cy.loginUser(synapse, user.username, user.password); + }); }); }); @@ -45,73 +48,74 @@ describe("Device manager", () => { it("should display sessions", () => { cy.openUserSettings("Sessions"); - cy.contains('Current session').should('exist'); + cy.contains("Current session").should("exist"); cy.get('[data-testid="current-session-section"]').within(() => { - cy.contains('Unverified session').should('exist'); - cy.get('.mx_DeviceSecurityCard_actions [role="button"]').should('exist'); + cy.contains("Unverified session").should("exist"); }); // current session details opened cy.get('[data-testid="current-session-toggle-details"]').click(); - cy.contains('Session details').should('exist'); + cy.contains("Session details").should("exist"); // close current session details cy.get('[data-testid="current-session-toggle-details"]').click(); - cy.contains('Session details').should('not.exist'); + cy.contains("Session details").should("not.exist"); cy.get('[data-testid="security-recommendations-section"]').within(() => { - cy.contains('Security recommendations').should('exist'); - cy.get('[data-testid="unverified-devices-cta"]').should('have.text', 'View all (3)').click(); + cy.contains("Security recommendations").should("exist"); + cy.get('[data-testid="unverified-devices-cta"]').should("have.text", "View all (3)").click(); }); /** * Other sessions section */ - cy.contains('Other sessions').should('exist'); + cy.contains("Other sessions").should("exist"); // filter applied after clicking through from security recommendations - cy.get('[aria-label="Filter devices"]').should('have.text', 'Show: Unverified'); - cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 3); + cy.get('[aria-label="Filter devices"]').should("have.text", "Show: Unverified"); + cy.get(".mx_FilteredDeviceList_list").find(".mx_FilteredDeviceList_listItem").should("have.length", 3); // select two sessions - cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox').first().click(); - cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox').last().click(); + cy.get(".mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox").first().click(); + cy.get(".mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox").last().click(); // sign out from list selection action buttons cy.get('[data-testid="sign-out-selection-cta"]').click(); cy.get('[data-testid="dialog-primary-button"]').click(); // list updated after sign out - cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 1); + cy.get(".mx_FilteredDeviceList_list").find(".mx_FilteredDeviceList_listItem").should("have.length", 1); // security recommendation count updated - cy.get('[data-testid="unverified-devices-cta"]').should('have.text', 'View all (1)'); + cy.get('[data-testid="unverified-devices-cta"]').should("have.text", "View all (1)"); const sessionName = `Alice's device`; // open the first session - cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem').first().within(() => { - cy.get('[aria-label="Show details"]').click(); - - cy.contains('Session details').should('exist'); - - cy.get('[data-testid="device-heading-rename-cta"]').click(); - cy.get('[data-testid="device-rename-input"]').type(sessionName); - cy.get('[data-testid="device-rename-submit-cta"]').click(); - // there should be a spinner while device updates - cy.get(".mx_Spinner").should("exist"); - // wait for spinner to complete - cy.get(".mx_Spinner").should("not.exist"); - - // session name updated in details - cy.get('.mx_DeviceDetailHeading h3').should('have.text', sessionName); - // and main list item - cy.get('.mx_DeviceTile h4').should('have.text', sessionName); - - // sign out using the device details sign out - cy.get('[data-testid="device-detail-sign-out-cta"]').click(); - }); + cy.get(".mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem") + .first() + .within(() => { + cy.get('[aria-label="Show details"]').click(); + + cy.contains("Session details").should("exist"); + + cy.get('[data-testid="device-heading-rename-cta"]').click(); + cy.get('[data-testid="device-rename-input"]').type(sessionName); + cy.get('[data-testid="device-rename-submit-cta"]').click(); + // there should be a spinner while device updates + cy.get(".mx_Spinner").should("exist"); + // wait for spinner to complete + cy.get(".mx_Spinner").should("not.exist"); + + // session name updated in details + cy.get(".mx_DeviceDetailHeading h3").should("have.text", sessionName); + // and main list item + cy.get(".mx_DeviceTile h4").should("have.text", sessionName); + + // sign out using the device details sign out + cy.get('[data-testid="device-detail-sign-out-cta"]').click(); + }); // confirm the signout cy.get('[data-testid="dialog-primary-button"]').click(); // no other sessions or security recommendations sections when only one session - cy.contains('Other sessions').should('not.exist'); - cy.get('[data-testid="security-recommendations-section"]').should('not.exist'); + cy.contains("Other sessions").should("not.exist"); + cy.get('[data-testid="security-recommendations-section"]').should("not.exist"); }); }); diff --git a/cypress/e2e/settings/hidden-rr-migration.spec.ts b/cypress/e2e/settings/hidden-rr-migration.spec.ts index 1d5419af339..8fd94823100 100644 --- a/cypress/e2e/settings/hidden-rr-migration.spec.ts +++ b/cypress/e2e/settings/hidden-rr-migration.spec.ts @@ -21,7 +21,7 @@ import { SynapseInstance } from "../../plugins/synapsedocker"; function seedLabs(synapse: SynapseInstance, labsVal: boolean | null): void { cy.initTestUser(synapse, "Sally", () => { // seed labs flag - cy.window({ log: false }).then(win => { + cy.window({ log: false }).then((win) => { if (typeof labsVal === "boolean") { // stringify boolean win.localStorage.setItem("mx_labs_feature_feature_hidden_read_receipts", `${labsVal}`); @@ -64,7 +64,7 @@ describe("Hidden Read Receipts Setting Migration", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; }); }); @@ -73,17 +73,17 @@ describe("Hidden Read Receipts Setting Migration", () => { cy.stopSynapse(synapse); }); - it('should not migrate the lack of a labs flag', () => { + it("should not migrate the lack of a labs flag", () => { seedLabs(synapse, null); testForVal(null); }); - it('should migrate labsHiddenRR=false as sendRR=true', () => { + it("should migrate labsHiddenRR=false as sendRR=true", () => { seedLabs(synapse, false); testForVal(true); }); - it('should migrate labsHiddenRR=true as sendRR=false', () => { + it("should migrate labsHiddenRR=true as sendRR=false", () => { seedLabs(synapse, true); testForVal(false); }); diff --git a/cypress/e2e/sliding-sync/sliding-sync.ts b/cypress/e2e/sliding-sync/sliding-sync.ts index b7cd7ffc888..73d6a07d083 100644 --- a/cypress/e2e/sliding-sync/sliding-sync.ts +++ b/cypress/e2e/sliding-sync/sliding-sync.ts @@ -26,18 +26,17 @@ import { ProxyInstance } from "../../plugins/sliding-sync"; describe("Sliding Sync", () => { beforeEach(() => { - cy.startSynapse("default").as("synapse").then(synapse => { - cy.startProxy(synapse).as("proxy"); - }); + cy.startSynapse("default") + .as("synapse") + .then((synapse) => { + cy.startProxy(synapse).as("proxy"); + }); - cy.all([ - cy.get("@synapse"), - cy.get("@proxy"), - ]).then(([synapse, proxy]) => { + cy.all([cy.get("@synapse"), cy.get("@proxy")]).then(([synapse, proxy]) => { cy.enableLabsFeature("feature_sliding_sync"); - cy.intercept("/config.json?cachebuster=*", req => { - return req.continue(res => { + cy.intercept("/config.json?cachebuster=*", (req) => { + return req.continue((res) => { res.send(200, { ...res.body, setting_defaults: { @@ -62,11 +61,16 @@ describe("Sliding Sync", () => { // assert order const checkOrder = (wantOrder: string[]) => { - cy.contains(".mx_RoomSublist", "Rooms").find(".mx_RoomTile_title").should((elements) => { - expect(_.map(elements, (e) => { - return e.textContent; - }), "rooms are sorted").to.deep.equal(wantOrder); - }); + cy.contains(".mx_RoomSublist", "Rooms") + .find(".mx_RoomTile_title") + .should((elements) => { + expect( + _.map(elements, (e) => { + return e.textContent; + }), + "rooms are sorted", + ).to.deep.equal(wantOrder); + }); }; const bumpRoom = (alias: string) => { // Send a message into the given room, this should bump the room to the top @@ -80,9 +84,11 @@ describe("Sliding Sync", () => { const createAndJoinBob = () => { // create a Bob user cy.get("@synapse").then((synapse) => { - return cy.getBot(synapse, { - displayName: "Bob", - }).as("bob"); + return cy + .getBot(synapse, { + displayName: "Bob", + }) + .as("bob"); }); // invite Bob to Test Room and accept then send a message. @@ -95,7 +101,7 @@ describe("Sliding Sync", () => { // sanity check everything works it("should correctly render expected messages", () => { - cy.get("@roomId").then(roomId => cy.visit("/#/room/" + roomId)); + cy.get("@roomId").then((roomId) => cy.visit("/#/room/" + roomId)); cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC); // Wait until configuration is finished @@ -114,54 +120,52 @@ describe("Sliding Sync", () => { cy.createRoom({ name: "Pineapple" }).then(() => cy.contains(".mx_RoomSublist", "Pineapple")); cy.createRoom({ name: "Orange" }).then(() => cy.contains(".mx_RoomSublist", "Orange")); // check the rooms are in the right order - cy.get(".mx_RoomTile").should('have.length', 4); // due to the Test Room in beforeEach - checkOrder([ - "Orange", "Pineapple", "Apple", "Test Room", - ]); + cy.get(".mx_RoomTile").should("have.length", 4); // due to the Test Room in beforeEach + checkOrder(["Orange", "Pineapple", "Apple", "Test Room"]); cy.contains(".mx_RoomSublist", "Rooms").find(".mx_RoomSublist_menuButton").click({ force: true }); cy.contains("A-Z").click(); - cy.get('.mx_StyledRadioButton_checked').should("contain.text", "A-Z"); - checkOrder([ - "Apple", "Orange", "Pineapple", "Test Room", - ]); + cy.get(".mx_StyledRadioButton_checked").should("contain.text", "A-Z"); + checkOrder(["Apple", "Orange", "Pineapple", "Test Room"]); }); it("should move rooms around as new events arrive", () => { // create rooms and check room names are correct - cy.createRoom({ name: "Apple" }).as("roomA").then(() => cy.contains(".mx_RoomSublist", "Apple")); - cy.createRoom({ name: "Pineapple" }).as("roomP").then(() => cy.contains(".mx_RoomSublist", "Pineapple")); - cy.createRoom({ name: "Orange" }).as("roomO").then(() => cy.contains(".mx_RoomSublist", "Orange")); + cy.createRoom({ name: "Apple" }) + .as("roomA") + .then(() => cy.contains(".mx_RoomSublist", "Apple")); + cy.createRoom({ name: "Pineapple" }) + .as("roomP") + .then(() => cy.contains(".mx_RoomSublist", "Pineapple")); + cy.createRoom({ name: "Orange" }) + .as("roomO") + .then(() => cy.contains(".mx_RoomSublist", "Orange")); // Select the Test Room cy.contains(".mx_RoomTile", "Test Room").click(); - checkOrder([ - "Orange", "Pineapple", "Apple", "Test Room", - ]); + checkOrder(["Orange", "Pineapple", "Apple", "Test Room"]); bumpRoom("@roomA"); - checkOrder([ - "Apple", "Orange", "Pineapple", "Test Room", - ]); + checkOrder(["Apple", "Orange", "Pineapple", "Test Room"]); bumpRoom("@roomO"); - checkOrder([ - "Orange", "Apple", "Pineapple", "Test Room", - ]); + checkOrder(["Orange", "Apple", "Pineapple", "Test Room"]); bumpRoom("@roomO"); - checkOrder([ - "Orange", "Apple", "Pineapple", "Test Room", - ]); + checkOrder(["Orange", "Apple", "Pineapple", "Test Room"]); bumpRoom("@roomP"); - checkOrder([ - "Pineapple", "Orange", "Apple", "Test Room", - ]); + checkOrder(["Pineapple", "Orange", "Apple", "Test Room"]); }); it("should not move the selected room: it should be sticky", () => { // create rooms and check room names are correct - cy.createRoom({ name: "Apple" }).as("roomA").then(() => cy.contains(".mx_RoomSublist", "Apple")); - cy.createRoom({ name: "Pineapple" }).as("roomP").then(() => cy.contains(".mx_RoomSublist", "Pineapple")); - cy.createRoom({ name: "Orange" }).as("roomO").then(() => cy.contains(".mx_RoomSublist", "Orange")); + cy.createRoom({ name: "Apple" }) + .as("roomA") + .then(() => cy.contains(".mx_RoomSublist", "Apple")); + cy.createRoom({ name: "Pineapple" }) + .as("roomP") + .then(() => cy.contains(".mx_RoomSublist", "Pineapple")); + cy.createRoom({ name: "Orange" }) + .as("roomO") + .then(() => cy.contains(".mx_RoomSublist", "Orange")); // Given a list of Orange, Pineapple, Apple - if Pineapple is active and a message is sent in Apple, the list should // turn into Apple, Pineapple, Orange - the index position of Pineapple never changes even though the list should technically @@ -169,23 +173,17 @@ describe("Sliding Sync", () => { // Select the Pineapple room cy.contains(".mx_RoomTile", "Pineapple").click(); - checkOrder([ - "Orange", "Pineapple", "Apple", "Test Room", - ]); + checkOrder(["Orange", "Pineapple", "Apple", "Test Room"]); // Move Apple bumpRoom("@roomA"); - checkOrder([ - "Apple", "Pineapple", "Orange", "Test Room", - ]); + checkOrder(["Apple", "Pineapple", "Orange", "Test Room"]); // Select the Test Room cy.contains(".mx_RoomTile", "Test Room").click(); // the rooms reshuffle to match reality - checkOrder([ - "Apple", "Orange", "Pineapple", "Test Room", - ]); + checkOrder(["Apple", "Orange", "Pineapple", "Test Room"]); }); it("should show the right unread notifications", () => { @@ -212,7 +210,8 @@ describe("Sliding Sync", () => { cy.contains(".mx_RoomTile", "Test Room").should("not.have.class", "mx_NotificationBadge_count"); }); - it("should not show unread indicators", () => { // TODO: for now. Later we should. + it("should not show unread indicators", () => { + // TODO: for now. Later we should. createAndJoinBob(); // disable notifs in this room (TODO: CS API call?) @@ -223,17 +222,13 @@ describe("Sliding Sync", () => { cy.createRoom({ name: "Dummy", }); - checkOrder([ - "Dummy", "Test Room", - ]); + checkOrder(["Dummy", "Test Room"]); cy.all([cy.get("@roomId"), cy.get("@bob")]).then(([roomId, bob]) => { return bob.sendTextMessage(roomId, "Do you read me?"); }); // wait for this message to arrive, tell by the room list resorting - checkOrder([ - "Test Room", "Dummy", - ]); + checkOrder(["Test Room", "Dummy"]); cy.contains(".mx_RoomTile", "Test Room").get(".mx_NotificationBadge").should("not.exist"); }); @@ -242,13 +237,18 @@ describe("Sliding Sync", () => { cy.get(".mx_UserMenu_userAvatar").click(); cy.contains("All settings").click(); cy.contains("Preferences").click(); - cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format").should("exist").find( - ".mx_ToggleSwitch_on").should("not.exist"); - cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format").should("exist").find( - ".mx_ToggleSwitch_ball").click(); - cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format", { timeout: 2000 }).should("exist").find( - ".mx_ToggleSwitch_on", { timeout: 2000 }, - ).should("exist"); + cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format") + .should("exist") + .find(".mx_ToggleSwitch_on") + .should("not.exist"); + cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format") + .should("exist") + .find(".mx_ToggleSwitch_ball") + .click(); + cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format", { timeout: 2000 }) + .should("exist") + .find(".mx_ToggleSwitch_on", { timeout: 2000 }) + .should("exist"); }); it("should show and be able to accept/reject/rescind invites", () => { @@ -263,50 +263,56 @@ describe("Sliding Sync", () => { // - roomJoin: will join this room // - roomReject: will reject the invite // - roomRescind: will make Bob rescind the invite - let roomJoin; let roomReject; let roomRescind; let bobClient; - cy.get("@bob").then((bob) => { - bobClient = bob; - return Promise.all([ - bob.createRoom({ name: "Join" }), - bob.createRoom({ name: "Reject" }), - bob.createRoom({ name: "Rescind" }), - ]); - }).then(([join, reject, rescind]) => { - roomJoin = join.room_id; - roomReject = reject.room_id; - roomRescind = rescind.room_id; - return Promise.all([ - bobClient.invite(roomJoin, clientUserId), - bobClient.invite(roomReject, clientUserId), - bobClient.invite(roomRescind, clientUserId), - ]); - }); + let roomJoin; + let roomReject; + let roomRescind; + let bobClient; + cy.get("@bob") + .then((bob) => { + bobClient = bob; + return Promise.all([ + bob.createRoom({ name: "Join" }), + bob.createRoom({ name: "Reject" }), + bob.createRoom({ name: "Rescind" }), + ]); + }) + .then(([join, reject, rescind]) => { + roomJoin = join.room_id; + roomReject = reject.room_id; + roomRescind = rescind.room_id; + return Promise.all([ + bobClient.invite(roomJoin, clientUserId), + bobClient.invite(roomReject, clientUserId), + bobClient.invite(roomRescind, clientUserId), + ]); + }); // wait for them all to be on the UI - cy.get(".mx_RoomTile").should('have.length', 4); // due to the Test Room in beforeEach + cy.get(".mx_RoomTile").should("have.length", 4); // due to the Test Room in beforeEach cy.contains(".mx_RoomTile", "Join").click(); cy.contains(".mx_AccessibleButton", "Accept").click(); - checkOrder([ - "Join", "Test Room", - ]); + checkOrder(["Join", "Test Room"]); cy.contains(".mx_RoomTile", "Reject").click(); cy.contains(".mx_RoomView .mx_AccessibleButton", "Reject").click(); // wait for the rejected room to disappear - cy.get(".mx_RoomTile").should('have.length', 3); + cy.get(".mx_RoomTile").should("have.length", 3); // check the lists are correct - checkOrder([ - "Join", "Test Room", - ]); - cy.contains(".mx_RoomSublist", "Invites").find(".mx_RoomTile_title").should((elements) => { - expect(_.map(elements, (e) => { - return e.textContent; - }), "rooms are sorted").to.deep.equal(["Rescind"]); - }); + checkOrder(["Join", "Test Room"]); + cy.contains(".mx_RoomSublist", "Invites") + .find(".mx_RoomTile_title") + .should((elements) => { + expect( + _.map(elements, (e) => { + return e.textContent; + }), + "rooms are sorted", + ).to.deep.equal(["Rescind"]); + }); // now rescind the invite cy.get("@bob").then((bob) => { @@ -314,19 +320,19 @@ describe("Sliding Sync", () => { }); // wait for the rescind to take effect and check the joined list once more - cy.get(".mx_RoomTile").should('have.length', 2); - checkOrder([ - "Join", "Test Room", - ]); + cy.get(".mx_RoomTile").should("have.length", 2); + checkOrder(["Join", "Test Room"]); }); it("should show a favourite DM only in the favourite sublist", () => { cy.createRoom({ name: "Favourite DM", is_direct: true, - }).as("room").then(roomId => { - cy.getClient().then(cli => cli.setRoomTag(roomId, "m.favourite", { order: 0.5 })); - }); + }) + .as("room") + .then((roomId) => { + cy.getClient().then((cli) => cli.setRoomTag(roomId, "m.favourite", { order: 0.5 })); + }); cy.contains('.mx_RoomSublist[aria-label="Favourites"] .mx_RoomTile', "Favourite DM").should("exist"); cy.contains('.mx_RoomSublist[aria-label="People"] .mx_RoomTile', "Favourite DM").should("not.exist"); @@ -335,7 +341,9 @@ describe("Sliding Sync", () => { // Regression test for a bug in SS mode, but would be useful to have in non-SS mode too. // This ensures we are setting RoomViewStore state correctly. it("should clear the reply to field when swapping rooms", () => { - cy.createRoom({ name: "Other Room" }).as("roomA").then(() => cy.contains(".mx_RoomSublist", "Other Room")); + cy.createRoom({ name: "Other Room" }) + .as("roomA") + .then(() => cy.contains(".mx_RoomSublist", "Other Room")); cy.get("@roomId").then((roomId) => { return cy.sendEvent(roomId, null, "m.room.message", { body: "Hello world", @@ -346,9 +354,9 @@ describe("Sliding Sync", () => { cy.contains(".mx_RoomTile", "Test Room").click(); cy.get(".mx_ReplyPreview").should("not.exist"); // click reply-to on the Hello World message - cy.contains(".mx_EventTile", "Hello world").find('.mx_AccessibleButton[aria-label="Reply"]').click( - { force: true }, - ); + cy.contains(".mx_EventTile", "Hello world") + .find('.mx_AccessibleButton[aria-label="Reply"]') + .click({ force: true }); // check it's visible cy.get(".mx_ReplyPreview").should("exist"); // now click Other Room @@ -365,28 +373,31 @@ describe("Sliding Sync", () => { it("should not cancel replies when permalinks are clicked ", () => { cy.get("@roomId").then((roomId) => { // we require a first message as you cannot click the permalink text with the avatar in the way - return cy.sendEvent(roomId, null, "m.room.message", { - body: "First message", - msgtype: "m.text", - }).then(() => { - return cy.sendEvent(roomId, null, "m.room.message", { - body: "Permalink me", - msgtype: "m.text", - }); - }).then(() => { - cy.sendEvent(roomId, null, "m.room.message", { - body: "Reply to me", + return cy + .sendEvent(roomId, null, "m.room.message", { + body: "First message", msgtype: "m.text", + }) + .then(() => { + return cy.sendEvent(roomId, null, "m.room.message", { + body: "Permalink me", + msgtype: "m.text", + }); + }) + .then(() => { + cy.sendEvent(roomId, null, "m.room.message", { + body: "Reply to me", + msgtype: "m.text", + }); }); - }); }); // select the room cy.contains(".mx_RoomTile", "Test Room").click(); cy.get(".mx_ReplyPreview").should("not.exist"); // click reply-to on the Reply to me message - cy.contains(".mx_EventTile", "Reply to me").find('.mx_AccessibleButton[aria-label="Reply"]').click( - { force: true }, - ); + cy.contains(".mx_EventTile", "Reply to me") + .find('.mx_AccessibleButton[aria-label="Reply"]') + .click({ force: true }); // check it's visible cy.get(".mx_ReplyPreview").should("exist"); // now click on the permalink for Permalink me diff --git a/cypress/e2e/spaces/spaces.spec.ts b/cypress/e2e/spaces/spaces.spec.ts index 893f48239b4..2518ffebda8 100644 --- a/cypress/e2e/spaces/spaces.spec.ts +++ b/cypress/e2e/spaces/spaces.spec.ts @@ -37,12 +37,14 @@ function spaceCreateOptions(spaceName: string): ICreateRoomOpts { creation_content: { type: "m.space", }, - initial_state: [{ - type: "m.room.name", - content: { - name: spaceName, + initial_state: [ + { + type: "m.room.name", + content: { + name: spaceName, + }, }, - }], + ], }; } @@ -61,10 +63,10 @@ describe("Spaces", () => { let user: UserCredentials; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; - cy.initTestUser(synapse, "Sue").then(_user => { + cy.initTestUser(synapse, "Sue").then((_user) => { user = _user; cy.mockClipboard(); }); @@ -78,8 +80,10 @@ describe("Spaces", () => { it("should allow user to create public space", () => { openSpaceCreateMenu().within(() => { cy.get(".mx_SpaceCreateMenuType_public").click(); - cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]') - .selectFile("cypress/fixtures/riot.png", { force: true }); + cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]').selectFile( + "cypress/fixtures/riot.png", + { force: true }, + ); cy.get('input[label="Name"]').type("Let's have a Riot"); cy.get('input[label="Address"]').should("have.value", "lets-have-a-riot"); cy.get('textarea[label="Description"]').type("This is a space to reminisce Riot.im!"); @@ -108,8 +112,10 @@ describe("Spaces", () => { it("should allow user to create private space", () => { openSpaceCreateMenu().within(() => { cy.get(".mx_SpaceCreateMenuType_private").click(); - cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]') - .selectFile("cypress/fixtures/riot.png", { force: true }); + cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]').selectFile( + "cypress/fixtures/riot.png", + { force: true }, + ); cy.get('input[label="Name"]').type("This is not a Riot"); cy.get('input[label="Address"]').should("not.exist"); cy.get('textarea[label="Description"]').type("This is a private space of mourning Riot.im..."); @@ -145,8 +151,10 @@ describe("Spaces", () => { openSpaceCreateMenu().within(() => { cy.get(".mx_SpaceCreateMenuType_private").click(); - cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]') - .selectFile("cypress/fixtures/riot.png", { force: true }); + cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]').selectFile( + "cypress/fixtures/riot.png", + { force: true }, + ); cy.get('input[label="Address"]').should("not.exist"); cy.get('textarea[label="Description"]').type("This is a personal space to mourn Riot.im..."); cy.get('input[label="Name"]').type("This is my Riot{enter}"); @@ -163,7 +171,7 @@ describe("Spaces", () => { it("should allow user to invite another to a space", () => { let bot: MatrixClient; - cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => { + cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => { bot = _bot; }); @@ -198,13 +206,17 @@ describe("Spaces", () => { }); cy.getSpacePanelButton("My Space").should("exist"); - cy.getBot(synapse, { displayName: "BotBob" }).then({ timeout: 10000 }, async bot => { + cy.getBot(synapse, { displayName: "BotBob" }).then({ timeout: 10000 }, async (bot) => { const { room_id: roomId } = await bot.createRoom(spaceCreateOptions("Space Space")); await bot.invite(roomId, user.userId); }); // Assert that `Space Space` is above `My Space` due to it being an invite - cy.getSpacePanelButton("Space Space").should("exist") - .parent().next().find('.mx_SpaceButton[aria-label="My Space"]').should("exist"); + cy.getSpacePanelButton("Space Space") + .should("exist") + .parent() + .next() + .find('.mx_SpaceButton[aria-label="My Space"]') + .should("exist"); }); it("should include rooms in space home", () => { @@ -216,16 +228,10 @@ describe("Spaces", () => { }).as("roomId2"); const spaceName = "Spacey Mc. Space Space"; - cy.all([ - cy.get("@roomId1"), - cy.get("@roomId2"), - ]).then(([roomId1, roomId2]) => { + cy.all([cy.get("@roomId1"), cy.get("@roomId2")]).then(([roomId1, roomId2]) => { cy.createSpace({ name: spaceName, - initial_state: [ - spaceChildInitialState(roomId1), - spaceChildInitialState(roomId2), - ], + initial_state: [spaceChildInitialState(roomId1), spaceChildInitialState(roomId2)], }).as("spaceId"); }); @@ -244,12 +250,10 @@ describe("Spaces", () => { cy.createSpace({ name: "Child Space", initial_state: [], - }).then(spaceId => { + }).then((spaceId) => { cy.createSpace({ name: "Root Space", - initial_state: [ - spaceChildInitialState(spaceId), - ], + initial_state: [spaceChildInitialState(spaceId)], }).as("spaceId"); }); cy.get('.mx_SpacePanel .mx_SpaceButton[aria-label="Root Space"]').should("exist"); @@ -258,7 +262,7 @@ describe("Spaces", () => { const axeOptions = { rules: { // Disable this check as it triggers on nested roving tab index elements which are in practice fine - 'nested-interactive': { + "nested-interactive": { enabled: false, }, }, @@ -269,8 +273,10 @@ describe("Spaces", () => { cy.get(".mx_SpaceButton_toggleCollapse").click({ force: true }); cy.get(".mx_SpacePanel:not(.collapsed)").should("exist"); - cy.contains(".mx_SpaceItem", "Root Space").should("exist") - .contains(".mx_SpaceItem", "Child Space").should("exist"); + cy.contains(".mx_SpaceItem", "Root Space") + .should("exist") + .contains(".mx_SpaceItem", "Child Space") + .should("exist"); cy.checkA11y(undefined, axeOptions); cy.get(".mx_SpacePanel").percySnapshotElement("Space panel expanded", { widths: [258] }); diff --git a/cypress/e2e/spotlight/spotlight.spec.ts b/cypress/e2e/spotlight/spotlight.spec.ts index adcc141deb6..9b89365714d 100644 --- a/cypress/e2e/spotlight/spotlight.spec.ts +++ b/cypress/e2e/spotlight/spotlight.spec.ts @@ -26,7 +26,7 @@ import Shadow = Cypress.Shadow; export enum Filter { People = "people", - PublicRooms = "public_rooms" + PublicRooms = "public_rooms", } declare global { @@ -37,78 +37,86 @@ declare global { * Opens the spotlight dialog */ openSpotlightDialog( - options?: Partial + options?: Partial, ): Chainable>; spotlightDialog( - options?: Partial + options?: Partial, ): Chainable>; spotlightFilter( filter: Filter | null, - options?: Partial + options?: Partial, ): Chainable>; spotlightSearch( - options?: Partial + options?: Partial, ): Chainable>; spotlightResults( - options?: Partial + options?: Partial, ): Chainable>; roomHeaderName( - options?: Partial + options?: Partial, ): Chainable>; startDM(name: string): Chainable; } } } -Cypress.Commands.add("openSpotlightDialog", ( - options?: Partial, -): Chainable> => { - cy.get('.mx_RoomSearch_spotlightTrigger', options).click({ force: true }); - return cy.spotlightDialog(options); -}); - -Cypress.Commands.add("spotlightDialog", ( - options?: Partial, -): Chainable> => { - return cy.get('[role=dialog][aria-label="Search Dialog"]', options); -}); - -Cypress.Commands.add("spotlightFilter", ( - filter: Filter | null, - options?: Partial, -): Chainable> => { - let selector: string; - switch (filter) { - case Filter.People: - selector = "#mx_SpotlightDialog_button_startChat"; - break; - case Filter.PublicRooms: - selector = "#mx_SpotlightDialog_button_explorePublicRooms"; - break; - default: - selector = ".mx_SpotlightDialog_filter"; - break; - } - return cy.get(selector, options).click(); -}); - -Cypress.Commands.add("spotlightSearch", ( - options?: Partial, -): Chainable> => { - return cy.get(".mx_SpotlightDialog_searchBox input", options); -}); - -Cypress.Commands.add("spotlightResults", ( - options?: Partial, -): Chainable> => { - return cy.get(".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option", options); -}); - -Cypress.Commands.add("roomHeaderName", ( - options?: Partial, -): Chainable> => { - return cy.get(".mx_RoomHeader_nametext", options); -}); +Cypress.Commands.add( + "openSpotlightDialog", + (options?: Partial): Chainable> => { + cy.get(".mx_RoomSearch_spotlightTrigger", options).click({ force: true }); + return cy.spotlightDialog(options); + }, +); + +Cypress.Commands.add( + "spotlightDialog", + (options?: Partial): Chainable> => { + return cy.get('[role=dialog][aria-label="Search Dialog"]', options); + }, +); + +Cypress.Commands.add( + "spotlightFilter", + ( + filter: Filter | null, + options?: Partial, + ): Chainable> => { + let selector: string; + switch (filter) { + case Filter.People: + selector = "#mx_SpotlightDialog_button_startChat"; + break; + case Filter.PublicRooms: + selector = "#mx_SpotlightDialog_button_explorePublicRooms"; + break; + default: + selector = ".mx_SpotlightDialog_filter"; + break; + } + return cy.get(selector, options).click(); + }, +); + +Cypress.Commands.add( + "spotlightSearch", + (options?: Partial): Chainable> => { + return cy.get(".mx_SpotlightDialog_searchBox input", options); + }, +); + +Cypress.Commands.add( + "spotlightResults", + (options?: Partial): Chainable> => { + return cy.get(".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option", options); + }, +); + +Cypress.Commands.add( + "roomHeaderName", + (options?: Partial): Chainable> => { + return cy.get(".mx_RoomHeader_nametext", options); + }, +); Cypress.Commands.add("startDM", (name: string) => { cy.openSpotlightDialog().within(() => { @@ -121,9 +129,7 @@ Cypress.Commands.add("startDM", (name: string) => { cy.spotlightResults().eq(0).click(); }); // send first message to start DM - cy.get(".mx_BasicMessageComposer_input") - .should("have.focus") - .type("Hey!{enter}"); + cy.get(".mx_BasicMessageComposer_input").should("have.focus").type("Hey!{enter}"); // The DM room is created at this point, this can take a little bit of time cy.contains(".mx_EventTile_body", "Hey!", { timeout: 30000 }); cy.contains(".mx_RoomSublist[aria-label=People]", name); @@ -148,46 +154,52 @@ describe("Spotlight", () => { let room3Id: string; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; - cy.initTestUser(synapse, "Jim").then(() => - cy.getBot(synapse, { displayName: bot1Name }).then(_bot1 => { - bot1 = _bot1; - }), - ).then(() => - cy.getBot(synapse, { displayName: bot2Name }).then(_bot2 => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - bot2 = _bot2; - }), - ).then(() => - cy.window({ log: false }).then(({ matrixcs: { Visibility } }) => { - cy.createRoom({ name: room1Name, visibility: Visibility.Public }).then(_room1Id => { - room1Id = _room1Id; - bot1.joinRoom(room1Id); - cy.visit("/#/room/" + room1Id); - }); - bot2.createRoom({ name: room2Name, visibility: Visibility.Public }) - .then(({ room_id: _room2Id }) => { - room2Id = _room2Id; - bot2.invite(room2Id, bot1.getUserId()); + cy.initTestUser(synapse, "Jim") + .then(() => + cy.getBot(synapse, { displayName: bot1Name }).then((_bot1) => { + bot1 = _bot1; + }), + ) + .then(() => + cy.getBot(synapse, { displayName: bot2Name }).then((_bot2) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + bot2 = _bot2; + }), + ) + .then(() => + cy.window({ log: false }).then(({ matrixcs: { Visibility } }) => { + cy.createRoom({ name: room1Name, visibility: Visibility.Public }).then((_room1Id) => { + room1Id = _room1Id; + bot1.joinRoom(room1Id); + cy.visit("/#/room/" + room1Id); }); - bot2.createRoom({ - name: room3Name, - visibility: Visibility.Public, initial_state: [{ - type: "m.room.history_visibility", - state_key: "", - content: { - history_visibility: "world_readable", + bot2.createRoom({ name: room2Name, visibility: Visibility.Public }).then( + ({ room_id: _room2Id }) => { + room2Id = _room2Id; + bot2.invite(room2Id, bot1.getUserId()); }, - }], - }).then(({ room_id: _room3Id }) => { - room3Id = _room3Id; - bot2.invite(room3Id, bot1.getUserId()); - }); - }), - ).then(() => - cy.get('.mx_RoomSublist_skeletonUI').should('not.exist'), - ); + ); + bot2.createRoom({ + name: room3Name, + visibility: Visibility.Public, + initial_state: [ + { + type: "m.room.history_visibility", + state_key: "", + content: { + history_visibility: "world_readable", + }, + }, + ], + }).then(({ room_id: _room3Id }) => { + room3Id = _room3Id; + bot2.invite(room3Id, bot1.getUserId()); + }); + }), + ) + .then(() => cy.get(".mx_RoomSublist_skeletonUI").should("not.exist")); }); }); @@ -216,63 +228,71 @@ describe("Spotlight", () => { }); it("should find joined rooms", () => { - cy.openSpotlightDialog().within(() => { - cy.spotlightSearch().clear().type(room1Name); - cy.wait(3000); // wait for the dialog code to settle - cy.spotlightResults().should("have.length", 1); - cy.spotlightResults().eq(0).should("contain", room1Name); - cy.spotlightResults().eq(0).click(); - cy.url().should("contain", room1Id); - }).then(() => { - cy.roomHeaderName().should("contain", room1Name); - }); + cy.openSpotlightDialog() + .within(() => { + cy.spotlightSearch().clear().type(room1Name); + cy.wait(3000); // wait for the dialog code to settle + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", room1Name); + cy.spotlightResults().eq(0).click(); + cy.url().should("contain", room1Id); + }) + .then(() => { + cy.roomHeaderName().should("contain", room1Name); + }); }); it("should find known public rooms", () => { - cy.openSpotlightDialog().within(() => { - cy.spotlightFilter(Filter.PublicRooms); - cy.spotlightSearch().clear().type(room1Name); - cy.wait(3000); // wait for the dialog code to settle - cy.spotlightResults().should("have.length", 1); - cy.spotlightResults().eq(0).should("contain", room1Name); - cy.spotlightResults().eq(0).should("contain", "View"); - cy.spotlightResults().eq(0).click(); - cy.url().should("contain", room1Id); - }).then(() => { - cy.roomHeaderName().should("contain", room1Name); - }); + cy.openSpotlightDialog() + .within(() => { + cy.spotlightFilter(Filter.PublicRooms); + cy.spotlightSearch().clear().type(room1Name); + cy.wait(3000); // wait for the dialog code to settle + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", room1Name); + cy.spotlightResults().eq(0).should("contain", "View"); + cy.spotlightResults().eq(0).click(); + cy.url().should("contain", room1Id); + }) + .then(() => { + cy.roomHeaderName().should("contain", room1Name); + }); }); it("should find unknown public rooms", () => { - cy.openSpotlightDialog().within(() => { - cy.spotlightFilter(Filter.PublicRooms); - cy.spotlightSearch().clear().type(room2Name); - cy.wait(3000); // wait for the dialog code to settle - cy.spotlightResults().should("have.length", 1); - cy.spotlightResults().eq(0).should("contain", room2Name); - cy.spotlightResults().eq(0).should("contain", "Join"); - cy.spotlightResults().eq(0).click(); - cy.url().should("contain", room2Id); - }).then(() => { - cy.get(".mx_RoomView_MessageList").should("have.length", 1); - cy.roomHeaderName().should("contain", room2Name); - }); + cy.openSpotlightDialog() + .within(() => { + cy.spotlightFilter(Filter.PublicRooms); + cy.spotlightSearch().clear().type(room2Name); + cy.wait(3000); // wait for the dialog code to settle + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", room2Name); + cy.spotlightResults().eq(0).should("contain", "Join"); + cy.spotlightResults().eq(0).click(); + cy.url().should("contain", room2Id); + }) + .then(() => { + cy.get(".mx_RoomView_MessageList").should("have.length", 1); + cy.roomHeaderName().should("contain", room2Name); + }); }); it("should find unknown public world readable rooms", () => { - cy.openSpotlightDialog().within(() => { - cy.spotlightFilter(Filter.PublicRooms); - cy.spotlightSearch().clear().type(room3Name); - cy.wait(3000); // wait for the dialog code to settle - cy.spotlightResults().should("have.length", 1); - cy.spotlightResults().eq(0).should("contain", room3Name); - cy.spotlightResults().eq(0).should("contain", "View"); - cy.spotlightResults().eq(0).click(); - cy.url().should("contain", room3Id); - }).then(() => { - cy.get(".mx_RoomPreviewBar_actions .mx_AccessibleButton").click(); - cy.roomHeaderName().should("contain", room3Name); - }); + cy.openSpotlightDialog() + .within(() => { + cy.spotlightFilter(Filter.PublicRooms); + cy.spotlightSearch().clear().type(room3Name); + cy.wait(3000); // wait for the dialog code to settle + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", room3Name); + cy.spotlightResults().eq(0).should("contain", "View"); + cy.spotlightResults().eq(0).click(); + cy.url().should("contain", room3Id); + }) + .then(() => { + cy.get(".mx_RoomPreviewBar_actions .mx_AccessibleButton").click(); + cy.roomHeaderName().should("contain", room3Name); + }); }); // TODO: We currently can’t test finding rooms on other homeservers/other protocols @@ -299,29 +319,33 @@ describe("Spotlight", () => { }); */ it("should find known people", () => { - cy.openSpotlightDialog().within(() => { - cy.spotlightFilter(Filter.People); - cy.spotlightSearch().clear().type(bot1Name); - cy.wait(3000); // wait for the dialog code to settle - cy.spotlightResults().should("have.length", 1); - cy.spotlightResults().eq(0).should("contain", bot1Name); - cy.spotlightResults().eq(0).click(); - }).then(() => { - cy.roomHeaderName().should("contain", bot1Name); - }); + cy.openSpotlightDialog() + .within(() => { + cy.spotlightFilter(Filter.People); + cy.spotlightSearch().clear().type(bot1Name); + cy.wait(3000); // wait for the dialog code to settle + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", bot1Name); + cy.spotlightResults().eq(0).click(); + }) + .then(() => { + cy.roomHeaderName().should("contain", bot1Name); + }); }); it("should find unknown people", () => { - cy.openSpotlightDialog().within(() => { - cy.spotlightFilter(Filter.People); - cy.spotlightSearch().clear().type(bot2Name); - cy.wait(3000); // wait for the dialog code to settle - cy.spotlightResults().should("have.length", 1); - cy.spotlightResults().eq(0).should("contain", bot2Name); - cy.spotlightResults().eq(0).click(); - }).then(() => { - cy.roomHeaderName().should("contain", bot2Name); - }); + cy.openSpotlightDialog() + .within(() => { + cy.spotlightFilter(Filter.People); + cy.spotlightSearch().clear().type(bot2Name); + cy.wait(3000); // wait for the dialog code to settle + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", bot2Name); + cy.spotlightResults().eq(0).click(); + }) + .then(() => { + cy.roomHeaderName().should("contain", bot2Name); + }); }); it("should find group DMs by usernames or user ids", () => { @@ -340,10 +364,7 @@ describe("Spotlight", () => { // Send first message to actually start DM cy.roomHeaderName().should("contain", bot2Name); - cy.get(".mx_BasicMessageComposer_input") - .click() - .should("have.focus") - .type("Hey!{enter}"); + cy.get(".mx_BasicMessageComposer_input").click().should("have.focus").type("Hey!{enter}"); // Assert DM exists by checking for the first message and the room being in the room list cy.contains(".mx_EventTile_body", "Hey!", { timeout: 30000 }); @@ -352,13 +373,13 @@ describe("Spotlight", () => { // Invite BotBob into existing DM with ByteBot cy.getDmRooms(bot2.getUserId()) .should("have.length", 1) - .then(dmRooms => cy.getClient().then(client => client.getRoom(dmRooms[0]))) - .then(groupDm => { + .then((dmRooms) => cy.getClient().then((client) => client.getRoom(dmRooms[0]))) + .then((groupDm) => { cy.inviteUser(groupDm.roomId, bot1.getUserId()); - cy.roomHeaderName().should(($element) => - expect($element.get(0).innerText).contains(groupDm.name)); + cy.roomHeaderName().should(($element) => expect($element.get(0).innerText).contains(groupDm.name)); cy.get(".mx_RoomSublist[aria-label=People]").should(($element) => - expect($element.get(0).innerText).contains(groupDm.name)); + expect($element.get(0).innerText).contains(groupDm.name), + ); // Search for BotBob by id, should return group DM and user cy.openSpotlightDialog().within(() => { @@ -407,17 +428,19 @@ describe("Spotlight", () => { }); it("should allow opening group chat dialog", () => { - cy.openSpotlightDialog().within(() => { - cy.spotlightFilter(Filter.People); - cy.spotlightSearch().clear().type(bot2Name); - cy.wait(3000); // wait for the dialog code to settle - cy.spotlightResults().should("have.length", 1); - cy.spotlightResults().eq(0).should("contain", bot2Name); - cy.get(".mx_SpotlightDialog_startGroupChat").should("contain", "Start a group chat"); - cy.get(".mx_SpotlightDialog_startGroupChat").click(); - }).then(() => { - cy.get('[role=dialog]').should("contain", "Direct Messages"); - }); + cy.openSpotlightDialog() + .within(() => { + cy.spotlightFilter(Filter.People); + cy.spotlightSearch().clear().type(bot2Name); + cy.wait(3000); // wait for the dialog code to settle + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", bot2Name); + cy.get(".mx_SpotlightDialog_startGroupChat").should("contain", "Start a group chat"); + cy.get(".mx_SpotlightDialog_startGroupChat").click(); + }) + .then(() => { + cy.get("[role=dialog]").should("contain", "Direct Messages"); + }); }); it("should close spotlight after starting a DM", () => { @@ -445,38 +468,40 @@ describe("Spotlight", () => { // our debouncing logic only starts the search after a short timeout, // so we wait a few milliseconds. cy.wait(1000); - cy.get(".mx_Spinner").should("not.exist").then(() => { - cy.spotlightResults().should("have.length", 2).then(() => { - cy.spotlightResults().eq(0) - .should("have.attr", "aria-selected", "true"); - cy.spotlightResults().eq(1) - .should("have.attr", "aria-selected", "false"); - }); - cy.spotlightSearch().type("{downArrow}").then(() => { - cy.spotlightResults().eq(0) - .should("have.attr", "aria-selected", "false"); - cy.spotlightResults().eq(1) - .should("have.attr", "aria-selected", "true"); - }); - cy.spotlightSearch().type("{downArrow}").then(() => { - cy.spotlightResults().eq(0) - .should("have.attr", "aria-selected", "false"); - cy.spotlightResults().eq(1) - .should("have.attr", "aria-selected", "false"); - }); - cy.spotlightSearch().type("{upArrow}").then(() => { - cy.spotlightResults().eq(0) - .should("have.attr", "aria-selected", "false"); - cy.spotlightResults().eq(1) - .should("have.attr", "aria-selected", "true"); - }); - cy.spotlightSearch().type("{upArrow}").then(() => { - cy.spotlightResults().eq(0) - .should("have.attr", "aria-selected", "true"); - cy.spotlightResults().eq(1) - .should("have.attr", "aria-selected", "false"); + cy.get(".mx_Spinner") + .should("not.exist") + .then(() => { + cy.spotlightResults() + .should("have.length", 2) + .then(() => { + cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "true"); + cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false"); + }); + cy.spotlightSearch() + .type("{downArrow}") + .then(() => { + cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false"); + cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "true"); + }); + cy.spotlightSearch() + .type("{downArrow}") + .then(() => { + cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false"); + cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false"); + }); + cy.spotlightSearch() + .type("{upArrow}") + .then(() => { + cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false"); + cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "true"); + }); + cy.spotlightSearch() + .type("{upArrow}") + .then(() => { + cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "true"); + cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false"); + }); }); - }); }); }); }); diff --git a/cypress/e2e/threads/threads.spec.ts b/cypress/e2e/threads/threads.spec.ts index 6aea5815e5c..7b616bd13f2 100644 --- a/cypress/e2e/threads/threads.spec.ts +++ b/cypress/e2e/threads/threads.spec.ts @@ -21,7 +21,7 @@ import { MatrixClient } from "../../global"; function markWindowBeforeReload(): void { // mark our window object to "know" when it gets reloaded - cy.window().then(w => w.beforeReload = true); + cy.window().then((w) => (w.beforeReload = true)); } describe("Threads", () => { @@ -29,11 +29,11 @@ describe("Threads", () => { beforeEach(() => { // Default threads to ON for this spec - cy.enableLabsFeature("feature_thread"); - cy.window().then(win => { + cy.enableLabsFeature("feature_threadstable"); + cy.window().then((win) => { win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests }); - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Tom"); @@ -52,7 +52,7 @@ describe("Threads", () => { // initially the new property is there cy.window().should("have.prop", "beforeReload", true); - cy.leaveBeta("Threads"); + cy.leaveBeta("Threaded messages"); cy.wait(1000); // after reload the property should be gone cy.window().should("not.have.prop", "beforeReload"); @@ -66,7 +66,7 @@ describe("Threads", () => { // initially the new property is there cy.window().should("have.prop", "beforeReload", true); - cy.joinBeta("Threads"); + cy.joinBeta("Threaded messages"); cy.wait(1000); // after reload the property should be gone cy.window().should("not.have.prop", "beforeReload"); @@ -78,12 +78,12 @@ describe("Threads", () => { cy.getBot(synapse, { displayName: "BotBob", autoAcceptInvites: false, - }).then(_bot => { + }).then((_bot) => { bot = _bot; }); let roomId: string; - cy.createRoom({}).then(_roomId => { + cy.createRoom({}).then((_roomId) => { roomId = _roomId; cy.inviteUser(roomId, bot.getUserId()); bot.joinRoom(roomId); @@ -95,10 +95,11 @@ describe("Threads", () => { // Wait for message to send, get its ID and save as @threadId cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot") - .invoke("attr", "data-scroll-tokens").as("threadId"); + .invoke("attr", "data-scroll-tokens") + .as("threadId"); // Bot starts thread - cy.get("@threadId").then(threadId => { + cy.get("@threadId").then((threadId) => { bot.sendMessage(roomId, threadId, { body: "Hello there", msgtype: "m.text", @@ -119,7 +120,8 @@ describe("Threads", () => { // User reacts to message instead cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Hello there") - .find('[aria-label="React"]').click({ force: true }); // Cypress has no ability to hover + .find('[aria-label="React"]') + .click({ force: true }); // Cypress has no ability to hover cy.get(".mx_EmojiPicker").within(() => { cy.get('input[type="text"]').type("wave"); cy.contains('[role="menuitem"]', "👋").click(); @@ -127,7 +129,8 @@ describe("Threads", () => { // User redacts their prior response cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Test") - .find('[aria-label="Options"]').click({ force: true }); // Cypress has no ability to hover + .find('[aria-label="Options"]') + .click({ force: true }); // Cypress has no ability to hover cy.get(".mx_IconizedContextMenu").within(() => { cy.contains('[role="menuitem"]', "Remove").click(); }); @@ -144,7 +147,7 @@ describe("Threads", () => { cy.get(".mx_ThreadPanel .mx_BaseCard_close").click(); // Bot responds to thread - cy.get("@threadId").then(threadId => { + cy.get("@threadId").then((threadId) => { bot.sendMessage(roomId, threadId, { body: "How are things?", msgtype: "m.text", @@ -178,45 +181,55 @@ describe("Threads", () => { cy.get(".mx_BasicMessageComposer_input").type(" How about yourself?{enter}"); }); cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "Tom"); - cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content") - .should("contain", "Great! How about yourself?"); + cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should( + "contain", + "Great! How about yourself?", + ); // User closes right panel cy.get(".mx_ThreadView .mx_BaseCard_close").click(); // Bot responds to thread and saves the id of their message to @eventId - cy.get("@threadId").then(threadId => { - cy.wrap(bot.sendMessage(roomId, threadId, { - body: "I'm very good thanks", - msgtype: "m.text", - }).then(res => res.event_id)).as("eventId"); + cy.get("@threadId").then((threadId) => { + cy.wrap( + bot + .sendMessage(roomId, threadId, { + body: "I'm very good thanks", + msgtype: "m.text", + }) + .then((res) => res.event_id), + ).as("eventId"); }); // User asserts cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob"); - cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content") - .should("contain", "I'm very good thanks"); + cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should( + "contain", + "I'm very good thanks", + ); // Bot edits their latest event - cy.get("@eventId").then(eventId => { + cy.get("@eventId").then((eventId) => { bot.sendMessage(roomId, { "body": "* I'm very good thanks :)", "msgtype": "m.text", "m.new_content": { - "body": "I'm very good thanks :)", - "msgtype": "m.text", + body: "I'm very good thanks :)", + msgtype: "m.text", }, "m.relates_to": { - "rel_type": "m.replace", - "event_id": eventId, + rel_type: "m.replace", + event_id: eventId, }, }); }); // User asserts cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob"); - cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content") - .should("contain", "I'm very good thanks :)"); + cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should( + "contain", + "I'm very good thanks :)", + ); }); it("can send voice messages", () => { @@ -227,7 +240,7 @@ describe("Threads", () => { }); let roomId: string; - cy.createRoom({}).then(_roomId => { + cy.createRoom({}).then((_roomId) => { roomId = _roomId; cy.visit("/#/room/" + roomId); }); @@ -237,7 +250,9 @@ describe("Threads", () => { // Create thread cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot") - .realHover().find(".mx_MessageActionBar_threadButton").click(); + .realHover() + .find(".mx_MessageActionBar_threadButton") + .click(); cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1); cy.openMessageComposerOptions(true).find(`[aria-label="Voice Message"]`).click(); @@ -250,7 +265,7 @@ describe("Threads", () => { it("right panel behaves correctly", () => { // Create room let roomId: string; - cy.createRoom({}).then(_roomId => { + cy.createRoom({}).then((_roomId) => { roomId = _roomId; cy.visit("/#/room/" + roomId); }); @@ -259,7 +274,9 @@ describe("Threads", () => { // Create thread cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot") - .realHover().find(".mx_MessageActionBar_threadButton").click(); + .realHover() + .find(".mx_MessageActionBar_threadButton") + .click(); cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1); // Send message to thread @@ -271,7 +288,9 @@ describe("Threads", () => { // Open existing thread cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot") - .realHover().find(".mx_MessageActionBar_threadButton").click(); + .realHover() + .find(".mx_MessageActionBar_threadButton") + .click(); cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1); cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. Bot"); cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. User"); diff --git a/cypress/e2e/timeline/timeline.spec.ts b/cypress/e2e/timeline/timeline.spec.ts index 68e0300ce35..9eea2a5567d 100644 --- a/cypress/e2e/timeline/timeline.spec.ts +++ b/cypress/e2e/timeline/timeline.spec.ts @@ -45,10 +45,7 @@ const expectDisplayName = (e: JQuery, displayName: string): void => }; const expectAvatar = (e: JQuery, avatarUrl: string): void => { - cy.all([ - cy.window({ log: false }), - cy.getClient(), - ]).then(([win, cli]) => { + cy.all([cy.window({ log: false }), cy.getClient()]).then(([win, cli]) => { const size = AVATAR_SIZE * win.devicePixelRatio; expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal( // eslint-disable-next-line no-restricted-properties @@ -75,10 +72,10 @@ describe("Timeline", () => { let newAvatarUrl: string; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, OLD_NAME).then(() => - cy.createRoom({ name: ROOM_NAME }).then(_room1Id => { + cy.createRoom({ name: ROOM_NAME }).then((_room1Id) => { roomId = _room1Id; }), ); @@ -154,8 +151,11 @@ describe("Timeline", () => { it("should create and configure a room on IRC layout", () => { cy.visit("/#/room/" + roomId); cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC); - cy.contains(".mx_RoomView_body .mx_GenericEventListSummary[data-layout=irc] " + - ".mx_GenericEventListSummary_summary", "created and configured the room.").should("exist"); + cy.contains( + ".mx_RoomView_body .mx_GenericEventListSummary[data-layout=irc] " + + ".mx_GenericEventListSummary_summary", + "created and configured the room.", + ).should("exist"); cy.get(".mx_Spinner").should("not.exist"); cy.percySnapshot("Configured room on IRC layout"); }); @@ -165,8 +165,10 @@ describe("Timeline", () => { cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC); // Wait until configuration is finished - cy.contains(".mx_RoomView_body .mx_GenericEventListSummary " + - ".mx_GenericEventListSummary_summary", "created and configured the room.").should("exist"); + cy.contains( + ".mx_RoomView_body .mx_GenericEventListSummary " + ".mx_GenericEventListSummary_summary", + "created and configured the room.", + ).should("exist"); // Click "expand" link button cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click(); @@ -177,13 +179,13 @@ describe("Timeline", () => { // = calc(var(--name-width) + 10px + var(--icon-width)) // = 80 + 10 + 14 = 104px cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info:first-of-type .mx_EventTile_line") - .should('have.css', "margin-inline-start", "104px") - .should('have.css', "inset-inline-start", "0px"); + .should("have.css", "margin-inline-start", "104px") + .should("have.css", "inset-inline-start", "0px"); cy.get(".mx_Spinner").should("not.exist"); // Exclude timestamp from snapshot - const percyCSS = ".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp " - + "{ visibility: hidden !important; }"; + const percyCSS = + ".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp " + "{ visibility: hidden !important; }"; cy.percySnapshot("Event line with inline start margin on IRC layout", { percyCSS }); cy.checkA11y(); }); @@ -192,8 +194,10 @@ describe("Timeline", () => { sendEvent(roomId); cy.visit("/#/room/" + roomId); cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true); - cy.contains(".mx_RoomView_body .mx_GenericEventListSummary .mx_GenericEventListSummary_summary", - "created and configured the room.").should("exist"); + cy.contains( + ".mx_RoomView_body .mx_GenericEventListSummary .mx_GenericEventListSummary_summary", + "created and configured the room.", + ).should("exist"); // Edit message cy.contains(".mx_RoomView_body .mx_EventTile .mx_EventTile_line", "Message").within(() => { @@ -206,20 +210,23 @@ describe("Timeline", () => { cy.get(".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp").click(); // Exclude timestamp from snapshot - const percyCSS = ".mx_RoomView_body .mx_EventTile .mx_MessageTimestamp " - + "{ visibility: hidden !important; }"; + const percyCSS = + ".mx_RoomView_body .mx_EventTile .mx_MessageTimestamp " + "{ visibility: hidden !important; }"; // should not add inline start padding to a hidden event line on IRC layout cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC); - cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info .mx_EventTile_line") - .should('have.css', 'padding-inline-start', '0px'); + cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info .mx_EventTile_line").should( + "have.css", + "padding-inline-start", + "0px", + ); cy.percySnapshot("Hidden event line with zero padding on IRC layout", { percyCSS }); // should add inline start padding to a hidden event line on modern layout cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group); cy.get(".mx_EventTile[data-layout=group].mx_EventTile_info .mx_EventTile_line") // calc(var(--EventTile_group_line-spacing-inline-start) + 20px) = 64 + 20 = 84px - .should('have.css', 'padding-inline-start', '84px'); + .should("have.css", "padding-inline-start", "84px"); cy.percySnapshot("Hidden event line with padding on modern layout", { percyCSS }); }); @@ -227,8 +234,10 @@ describe("Timeline", () => { sendEvent(roomId); cy.visit("/#/room/" + roomId); cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true); - cy.contains(".mx_RoomView_body .mx_GenericEventListSummary " + - ".mx_GenericEventListSummary_summary", "created and configured the room.").should("exist"); + cy.contains( + ".mx_RoomView_body .mx_GenericEventListSummary " + ".mx_GenericEventListSummary_summary", + "created and configured the room.", + ).should("exist"); // Edit message cy.contains(".mx_RoomView_body .mx_EventTile .mx_EventTile_line", "Message").within(() => { @@ -238,9 +247,12 @@ describe("Timeline", () => { cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "MessageEdit").should("exist"); // Click top left of the event toggle, which should not be covered by MessageActionBar's safe area - cy.get(".mx_EventTile .mx_ViewSourceEvent").should("exist").realHover().within(() => { - cy.get(".mx_ViewSourceEvent_toggle").click('topLeft', { force: false }); - }); + cy.get(".mx_EventTile .mx_ViewSourceEvent") + .should("exist") + .realHover() + .within(() => { + cy.get(".mx_ViewSourceEvent_toggle").click("topLeft", { force: false }); + }); // Make sure the expand toggle worked cy.get(".mx_EventTile .mx_ViewSourceEvent_expanded .mx_ViewSourceEvent_toggle").should("be.visible"); @@ -249,8 +261,11 @@ describe("Timeline", () => { it("should click 'collapse' link button on the first hovered info event line on bubble layout", () => { cy.visit("/#/room/" + roomId); cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); - cy.contains(".mx_RoomView_body .mx_GenericEventListSummary[data-layout=bubble] " + - ".mx_GenericEventListSummary_summary", "created and configured the room.").should("exist"); + cy.contains( + ".mx_RoomView_body .mx_GenericEventListSummary[data-layout=bubble] " + + ".mx_GenericEventListSummary_summary", + "created and configured the room.", + ).should("exist"); // Click "expand" link button cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click(); @@ -340,10 +355,14 @@ describe("Timeline", () => { cy.getComposer().type(`${reply}{enter}`); - cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line .mx_ReplyTile .mx_MTextBody") - .should("contain", MESSAGE); - cy.contains(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MTextBody", reply) - .should("have.length", 1); + cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line .mx_ReplyTile .mx_MTextBody").should( + "contain", + MESSAGE, + ); + cy.contains(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MTextBody", reply).should( + "have.length", + 1, + ); }); it("can reply with a voice message", () => { @@ -355,10 +374,14 @@ describe("Timeline", () => { cy.wait(3000); cy.get(".mx_RoomView_body .mx_MessageComposer .mx_MessageComposer_sendMessage").click(); - cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line .mx_ReplyTile .mx_MTextBody") - .should("contain", MESSAGE); - cy.get(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MVoiceMessageBody") - .should("have.length", 1); + cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line .mx_ReplyTile .mx_MTextBody").should( + "contain", + MESSAGE, + ); + cy.get(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MVoiceMessageBody").should( + "have.length", + 1, + ); }); }); }); diff --git a/cypress/e2e/toasts/analytics-toast.ts b/cypress/e2e/toasts/analytics-toast.ts index 518a544a1cb..4c9cbed02fa 100644 --- a/cypress/e2e/toasts/analytics-toast.ts +++ b/cypress/e2e/toasts/analytics-toast.ts @@ -47,15 +47,15 @@ describe("Analytics Toast", () => { }); it("should not show an analytics toast if config has nothing about posthog", () => { - cy.intercept("/config.json?cachebuster=*", req => { - req.continue(res => { + cy.intercept("/config.json?cachebuster=*", (req) => { + req.continue((res) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { posthog, ...body } = res.body; res.send(200, body); }); }); - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Tod"); }); @@ -66,8 +66,8 @@ describe("Analytics Toast", () => { describe("with posthog enabled", () => { beforeEach(() => { - cy.intercept("/config.json?cachebuster=*", req => { - req.continue(res => { + cy.intercept("/config.json?cachebuster=*", (req) => { + req.continue((res) => { res.send(200, { ...res.body, posthog: { @@ -78,7 +78,7 @@ describe("Analytics Toast", () => { }); }); - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Tod"); rejectToast("Notifications"); diff --git a/cypress/e2e/update/update.spec.ts b/cypress/e2e/update/update.spec.ts index 75977763cc9..eaf039b6ea8 100644 --- a/cypress/e2e/update/update.spec.ts +++ b/cypress/e2e/update/update.spec.ts @@ -22,7 +22,7 @@ describe("Update", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; }); }); @@ -45,9 +45,11 @@ describe("Update", () => { cy.initTestUser(synapse, "Ursa"); cy.wait("@version"); - cy.url().should("contain", "updated=" + NEW_VERSION).then(href => { - const url = new URL(href); - expect(url.searchParams.get("updated")).to.equal(NEW_VERSION); - }); + cy.url() + .should("contain", "updated=" + NEW_VERSION) + .then((href) => { + const url = new URL(href); + expect(url.searchParams.get("updated")).to.equal(NEW_VERSION); + }); }); }); diff --git a/cypress/e2e/user-menu/user-menu.spec.ts b/cypress/e2e/user-menu/user-menu.spec.ts index 40b4a0cd74c..841dc82e85f 100644 --- a/cypress/e2e/user-menu/user-menu.spec.ts +++ b/cypress/e2e/user-menu/user-menu.spec.ts @@ -24,10 +24,10 @@ describe("User Menu", () => { let user: UserCredentials; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; - cy.initTestUser(synapse, "Jeff").then(credentials => { + cy.initTestUser(synapse, "Jeff").then((credentials) => { user = credentials; }); }); diff --git a/cypress/e2e/user-onboarding/user-onboarding-new.ts b/cypress/e2e/user-onboarding/user-onboarding-new.ts index c6eac8ce27d..a3975cb5ace 100644 --- a/cypress/e2e/user-onboarding/user-onboarding-new.ts +++ b/cypress/e2e/user-onboarding/user-onboarding-new.ts @@ -26,23 +26,23 @@ describe("User Onboarding (new user)", () => { let bot1: MatrixClient; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Jane Doe"); - cy.window({ log: false }).then(win => { + cy.window({ log: false }).then((win) => { win.localStorage.setItem("mx_registration_time", "1656633601"); }); cy.reload().then(() => { // wait for the app to load return cy.get(".mx_MatrixChat", { timeout: 15000 }); }); - cy.getBot(synapse, { displayName: bot1Name }).then(_bot1 => { + cy.getBot(synapse, { displayName: bot1Name }).then((_bot1) => { bot1 = _bot1; }); - cy.get('.mx_UserOnboardingPage').should('exist'); - cy.get('.mx_UserOnboardingButton').should('exist'); - cy.get('.mx_UserOnboardingList') - .should('exist') + cy.get(".mx_UserOnboardingPage").should("exist"); + cy.get(".mx_UserOnboardingButton").should("exist"); + cy.get(".mx_UserOnboardingList") + .should("exist") .should(($list) => { const list = $list.get(0); expect(getComputedStyle(list).opacity).to.be.eq("1"); @@ -55,44 +55,42 @@ describe("User Onboarding (new user)", () => { }); it("page is shown and preference exists", () => { - cy.get('.mx_UserOnboardingPage') - .percySnapshotElement("User onboarding page"); + cy.get(".mx_UserOnboardingPage").percySnapshotElement("User onboarding page"); cy.openUserSettings("Preferences"); cy.contains("Show shortcut to welcome checklist above the room list").should("exist"); }); it("app download dialog", () => { cy.contains(".mx_UserOnboardingTask_action", "Download apps").click(); - cy.get('[role=dialog]') - .contains("#mx_BaseDialog_title", "Download Element") - .should("exist"); - cy.get('[role=dialog]') - .percySnapshotElement("App download dialog", { - widths: [640], - }); + cy.get("[role=dialog]").contains("#mx_BaseDialog_title", "Download Element").should("exist"); + cy.get("[role=dialog]").percySnapshotElement("App download dialog", { + widths: [640], + }); }); it("using find friends action should increase progress", () => { - cy.get(".mx_ProgressBar").invoke("val").then((oldProgress) => { - const findPeopleAction = cy.contains(".mx_UserOnboardingTask_action", "Find friends"); - expect(findPeopleAction).to.exist; - findPeopleAction.click(); - cy.get(".mx_InviteDialog_editor input").type(bot1.getUserId()); - cy.get(".mx_InviteDialog_buttonAndSpinner").click(); - cy.get(".mx_InviteDialog_buttonAndSpinner").should("not.exist"); - const message = "Hi!"; - cy.get(".mx_SendMessageComposer").type(`${message}!{enter}`); - cy.contains(".mx_MTextBody.mx_EventTile_content", message); - cy.visit("/#/home"); - cy.get('.mx_UserOnboardingPage').should('exist'); - cy.get('.mx_UserOnboardingButton').should('exist'); - cy.get('.mx_UserOnboardingList') - .should('exist') - .should(($list) => { - const list = $list.get(0); - expect(getComputedStyle(list).opacity).to.be.eq("1"); - }); - cy.get(".mx_ProgressBar").invoke("val").should("be.greaterThan", oldProgress); - }); + cy.get(".mx_ProgressBar") + .invoke("val") + .then((oldProgress) => { + const findPeopleAction = cy.contains(".mx_UserOnboardingTask_action", "Find friends"); + expect(findPeopleAction).to.exist; + findPeopleAction.click(); + cy.get(".mx_InviteDialog_editor input").type(bot1.getUserId()); + cy.get(".mx_InviteDialog_buttonAndSpinner").click(); + cy.get(".mx_InviteDialog_buttonAndSpinner").should("not.exist"); + const message = "Hi!"; + cy.get(".mx_SendMessageComposer").type(`${message}!{enter}`); + cy.contains(".mx_MTextBody.mx_EventTile_content", message); + cy.visit("/#/home"); + cy.get(".mx_UserOnboardingPage").should("exist"); + cy.get(".mx_UserOnboardingButton").should("exist"); + cy.get(".mx_UserOnboardingList") + .should("exist") + .should(($list) => { + const list = $list.get(0); + expect(getComputedStyle(list).opacity).to.be.eq("1"); + }); + cy.get(".mx_ProgressBar").invoke("val").should("be.greaterThan", oldProgress); + }); }); }); diff --git a/cypress/e2e/user-onboarding/user-onboarding-old.ts b/cypress/e2e/user-onboarding/user-onboarding-old.ts index f079ed9a4c3..90ae73b2577 100644 --- a/cypress/e2e/user-onboarding/user-onboarding-old.ts +++ b/cypress/e2e/user-onboarding/user-onboarding-old.ts @@ -22,10 +22,10 @@ describe("User Onboarding (old user)", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Jane Doe"); - cy.window({ log: false }).then(win => { + cy.window({ log: false }).then((win) => { win.localStorage.setItem("mx_registration_time", "2"); }); cy.reload().then(() => { @@ -41,8 +41,8 @@ describe("User Onboarding (old user)", () => { }); it("page and preference are hidden", () => { - cy.get('.mx_UserOnboardingPage').should('not.exist'); - cy.get('.mx_UserOnboardingButton').should('not.exist'); + cy.get(".mx_UserOnboardingPage").should("not.exist"); + cy.get(".mx_UserOnboardingButton").should("not.exist"); cy.openUserSettings("Preferences"); cy.contains("Show shortcut to welcome page above the room list").should("not.exist"); }); diff --git a/cypress/e2e/user-view/user-view.spec.ts b/cypress/e2e/user-view/user-view.spec.ts index 30c3ff23ca9..df7bd933ae1 100644 --- a/cypress/e2e/user-view/user-view.spec.ts +++ b/cypress/e2e/user-view/user-view.spec.ts @@ -23,7 +23,7 @@ describe("UserView", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Violet"); @@ -36,7 +36,7 @@ describe("UserView", () => { }); it("should render the user view as expected", () => { - cy.get("@bot").then(bot => { + cy.get("@bot").then((bot) => { cy.visit(`/#/user/${bot.getUserId()}`); }); diff --git a/cypress/e2e/widgets/layout.spec.ts b/cypress/e2e/widgets/layout.spec.ts index 25264c66224..886b3062c81 100644 --- a/cypress/e2e/widgets/layout.spec.ts +++ b/cypress/e2e/widgets/layout.spec.ts @@ -19,7 +19,7 @@ import { IWidget } from "matrix-widget-api"; import { SynapseInstance } from "../../plugins/synapsedocker"; -const ROOM_NAME = 'Test Room'; +const ROOM_NAME = "Test Room"; const WIDGET_ID = "fake-widget"; const WIDGET_HTML = ` @@ -32,18 +32,18 @@ const WIDGET_HTML = ` `; -describe('Widget Layout', () => { +describe("Widget Layout", () => { let widgetUrl: string; let synapse: SynapseInstance; let roomId: string; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Sally"); }); - cy.serveHtmlFile(WIDGET_HTML).then(url => { + cy.serveHtmlFile(WIDGET_HTML).then((url) => { widgetUrl = url; }); @@ -53,34 +53,38 @@ describe('Widget Layout', () => { roomId = id; // setup widget via state event - cy.getClient().then(async matrixClient => { - const content: IWidget = { - id: WIDGET_ID, - creatorUserId: 'somebody', - type: 'widget', - name: 'widget', - url: widgetUrl, - }; - await matrixClient.sendStateEvent(roomId, 'im.vector.modular.widgets', content, WIDGET_ID); - }).as('widgetEventSent'); + cy.getClient() + .then(async (matrixClient) => { + const content: IWidget = { + id: WIDGET_ID, + creatorUserId: "somebody", + type: "widget", + name: "widget", + url: widgetUrl, + }; + await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, WIDGET_ID); + }) + .as("widgetEventSent"); // set initial layout - cy.getClient().then(async matrixClient => { - const content = { - widgets: { - [WIDGET_ID]: { - container: 'top', index: 1, width: 100, height: 0, + cy.getClient() + .then(async (matrixClient) => { + const content = { + widgets: { + [WIDGET_ID]: { + container: "top", + index: 1, + width: 100, + height: 0, + }, }, - }, - }; - await matrixClient.sendStateEvent(roomId, 'io.element.widgets.layout', content, ""); - }).as('layoutEventSent'); + }; + await matrixClient.sendStateEvent(roomId, "io.element.widgets.layout", content, ""); + }) + .as("layoutEventSent"); }); - cy.all([ - cy.get("@widgetEventSent"), - cy.get("@layoutEventSent"), - ]).then(() => { + cy.all([cy.get("@widgetEventSent"), cy.get("@layoutEventSent")]).then(() => { // open the room cy.viewRoomByName(ROOM_NAME); }); @@ -91,31 +95,34 @@ describe('Widget Layout', () => { cy.stopWebServers(); }); - it('manually resize the height of the top container layout', () => { - cy.get('iframe[title="widget"]').invoke('height').should('be.lessThan', 250); + it("manually resize the height of the top container layout", () => { + cy.get('iframe[title="widget"]').invoke("height").should("be.lessThan", 250); - cy.get('.mx_AppsContainer_resizerHandle') - .trigger('mousedown') - .trigger('mousemove', { clientX: 0, clientY: 550, force: true }) - .trigger('mouseup', { clientX: 0, clientY: 550, force: true }); + cy.get(".mx_AppsContainer_resizerHandle") + .trigger("mousedown") + .trigger("mousemove", { clientX: 0, clientY: 550, force: true }) + .trigger("mouseup", { clientX: 0, clientY: 550, force: true }); - cy.get('iframe[title="widget"]').invoke('height').should('be.greaterThan', 400); + cy.get('iframe[title="widget"]').invoke("height").should("be.greaterThan", 400); }); - it('programatically resize the height of the top container layout', () => { - cy.get('iframe[title="widget"]').invoke('height').should('be.lessThan', 250); + it("programatically resize the height of the top container layout", () => { + cy.get('iframe[title="widget"]').invoke("height").should("be.lessThan", 250); - cy.getClient().then(async matrixClient => { + cy.getClient().then(async (matrixClient) => { const content = { widgets: { [WIDGET_ID]: { - container: 'top', index: 1, width: 100, height: 100, + container: "top", + index: 1, + width: 100, + height: 100, }, }, }; - await matrixClient.sendStateEvent(roomId, 'io.element.widgets.layout', content, ""); + await matrixClient.sendStateEvent(roomId, "io.element.widgets.layout", content, ""); }); - cy.get('iframe[title="widget"]').invoke('height').should('be.greaterThan', 400); + cy.get('iframe[title="widget"]').invoke("height").should("be.greaterThan", 400); }); }); diff --git a/cypress/e2e/widgets/stickers.spec.ts b/cypress/e2e/widgets/stickers.spec.ts index 0d0fee8776f..c714b84416c 100644 --- a/cypress/e2e/widgets/stickers.spec.ts +++ b/cypress/e2e/widgets/stickers.spec.ts @@ -67,8 +67,8 @@ const WIDGET_HTML = ` `; function openStickerPicker() { - cy.get('.mx_MessageComposer_buttonMenu').click(); - cy.get('#stickersButton').click(); + cy.get(".mx_MessageComposer_buttonMenu").click(); + cy.get("#stickersButton").click(); } function sendStickerFromPicker() { @@ -76,18 +76,16 @@ function sendStickerFromPicker() { // to use `chromeWebSecurity: false` in our cypress config. Not even cy.origin() can // break into the iframe for us :( cy.accessIframe(`iframe[title="${STICKER_PICKER_WIDGET_NAME}"]`).within({}, () => { - cy.get("#sendsticker").should('exist').click(); + cy.get("#sendsticker").should("exist").click(); }); // Sticker picker should close itself after sending. - cy.get(".mx_AppTileFullWidth#stickers").should('not.exist'); + cy.get(".mx_AppTileFullWidth#stickers").should("not.exist"); } function expectTimelineSticker(roomId: string) { // Make sure it's in the right room - cy.get('.mx_EventTile_sticker > a') - .should("have.attr", "href") - .and("include", `/${roomId}/`); + cy.get(".mx_EventTile_sticker > a").should("have.attr", "href").and("include", `/${roomId}/`); // Make sure the image points at the sticker image cy.get(`img[alt="${STICKER_NAME}"]`) @@ -107,12 +105,12 @@ describe("Stickers", () => { let synapse: SynapseInstance; beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; cy.initTestUser(synapse, "Sally"); }); - cy.serveHtmlFile(WIDGET_HTML).then(url => { + cy.serveHtmlFile(WIDGET_HTML).then((url) => { stickerPickerUrl = url; }); }); @@ -122,7 +120,7 @@ describe("Stickers", () => { cy.stopWebServers(); }); - it('should send a sticker to multiple rooms', () => { + it("should send a sticker to multiple rooms", () => { cy.createRoom({ name: ROOM_NAME_1, }).as("roomId1"); diff --git a/cypress/e2e/widgets/widget-pip-close.spec.ts b/cypress/e2e/widgets/widget-pip-close.spec.ts index df574a68b0c..a88fac9ca50 100644 --- a/cypress/e2e/widgets/widget-pip-close.spec.ts +++ b/cypress/e2e/widgets/widget-pip-close.spec.ts @@ -57,7 +57,7 @@ function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: str return new Promise((resolve, reject) => { function eventsInIntendedState(evList) { const widgetPresent = evList.some((ev) => { - return ev.getContent() && ev.getContent()['id'] === widgetId; + return ev.getContent() && ev.getContent()["id"] === widgetId; }); if (add) { return widgetPresent; @@ -68,7 +68,7 @@ function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: str const room = matrixClient.getRoom(roomId); - const startingWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets'); + const startingWidgetEvents = room.currentState.getStateEvents("im.vector.modular.widgets"); if (eventsInIntendedState(startingWidgetEvents)) { resolve(); return; @@ -77,7 +77,7 @@ function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: str function onRoomStateEvents(ev: MatrixEvent) { if (ev.getRoomId() !== roomId || ev.getType() !== "im.vector.modular.widgets") return; - const currentWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets'); + const currentWidgetEvents = room.currentState.getStateEvents("im.vector.modular.widgets"); if (eventsInIntendedState(currentWidgetEvents)) { matrixClient.removeListener(win.matrixcs.RoomStateEvent.Events, onRoomStateEvents); @@ -95,35 +95,39 @@ describe("Widget PIP", () => { let bot: MatrixClient; let demoWidgetUrl: string; - function roomCreateAddWidgetPip(userRemove: 'leave' | 'kick' | 'ban') { + function roomCreateAddWidgetPip(userRemove: "leave" | "kick" | "ban") { cy.createRoom({ name: ROOM_NAME, invite: [bot.getUserId()], - }).then(roomId => { + }).then((roomId) => { // sets bot to Admin and user to Moderator - cy.getClient().then(matrixClient => { - return matrixClient.sendStateEvent(roomId, 'm.room.power_levels', { - users: { - [user.userId]: 50, - [bot.getUserId()]: 100, - }, - }); - }).as('powerLevelsChanged'); + cy.getClient() + .then((matrixClient) => { + return matrixClient.sendStateEvent(roomId, "m.room.power_levels", { + users: { + [user.userId]: 50, + [bot.getUserId()]: 100, + }, + }); + }) + .as("powerLevelsChanged"); // bot joins the room - cy.botJoinRoom(bot, roomId).as('botJoined'); + cy.botJoinRoom(bot, roomId).as("botJoined"); // setup widget via state event - cy.getClient().then(async matrixClient => { - const content: IWidget = { - id: DEMO_WIDGET_ID, - creatorUserId: 'somebody', - type: DEMO_WIDGET_TYPE, - name: DEMO_WIDGET_NAME, - url: demoWidgetUrl, - }; - await matrixClient.sendStateEvent(roomId, 'im.vector.modular.widgets', content, DEMO_WIDGET_ID); - }).as('widgetEventSent'); + cy.getClient() + .then(async (matrixClient) => { + const content: IWidget = { + id: DEMO_WIDGET_ID, + creatorUserId: "somebody", + type: DEMO_WIDGET_TYPE, + name: DEMO_WIDGET_NAME, + url: demoWidgetUrl, + }; + await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, DEMO_WIDGET_ID); + }) + .as("widgetEventSent"); // open the room cy.viewRoomByName(ROOM_NAME); @@ -133,7 +137,7 @@ describe("Widget PIP", () => { cy.get("@botJoined"), cy.get("@widgetEventSent"), ]).then(() => { - cy.window().then(async win => { + cy.window().then(async (win) => { // wait for widget state event await waitForRoomWidget(win, DEMO_WIDGET_ID, roomId, true); @@ -145,21 +149,23 @@ describe("Widget PIP", () => { // checks that widget is opened in pip cy.accessIframe(`iframe[title="${DEMO_WIDGET_NAME}"]`).within({}, () => { - cy.get("#demo").should('exist').then(async () => { - const userId = user.userId; - if (userRemove == 'leave') { - cy.getClient().then(async matrixClient => { - await matrixClient.leave(roomId); - }); - } else if (userRemove == 'kick') { - await bot.kick(roomId, userId); - } else if (userRemove == 'ban') { - await bot.ban(roomId, userId); - } - - // checks that pip window is closed - cy.get(".mx_LegacyCallView_pip").should("not.exist"); - }); + cy.get("#demo") + .should("exist") + .then(async () => { + const userId = user.userId; + if (userRemove == "leave") { + cy.getClient().then(async (matrixClient) => { + await matrixClient.leave(roomId); + }); + } else if (userRemove == "kick") { + await bot.kick(roomId, userId); + } else if (userRemove == "ban") { + await bot.ban(roomId, userId); + } + + // checks that pip window is closed + cy.get(".mx_LegacyCallView_pip").should("not.exist"); + }); }); }); }); @@ -167,17 +173,17 @@ describe("Widget PIP", () => { } beforeEach(() => { - cy.startSynapse("default").then(data => { + cy.startSynapse("default").then((data) => { synapse = data; - cy.initTestUser(synapse, "Mike").then(_user => { + cy.initTestUser(synapse, "Mike").then((_user) => { user = _user; }); - cy.getBot(synapse, { displayName: "Bot", autoAcceptInvites: false }).then(_bot => { + cy.getBot(synapse, { displayName: "Bot", autoAcceptInvites: false }).then((_bot) => { bot = _bot; }); }); - cy.serveHtmlFile(DEMO_WIDGET_HTML).then(url => { + cy.serveHtmlFile(DEMO_WIDGET_HTML).then((url) => { demoWidgetUrl = url; }); }); @@ -187,15 +193,15 @@ describe("Widget PIP", () => { cy.stopWebServers(); }); - it('should be closed on leave', () => { - roomCreateAddWidgetPip('leave'); + it("should be closed on leave", () => { + roomCreateAddWidgetPip("leave"); }); - it('should be closed on kick', () => { - roomCreateAddWidgetPip('kick'); + it("should be closed on kick", () => { + roomCreateAddWidgetPip("kick"); }); - it('should be closed on ban', () => { - roomCreateAddWidgetPip('ban'); + it("should be closed on ban", () => { + roomCreateAddWidgetPip("ban"); }); }); diff --git a/cypress/fixtures/matrix-org-client-login.json b/cypress/fixtures/matrix-org-client-login.json new file mode 100644 index 00000000000..d7c4fde1e5b --- /dev/null +++ b/cypress/fixtures/matrix-org-client-login.json @@ -0,0 +1,48 @@ +{ + "flows": [ + { + "type": "m.login.sso", + "identity_providers": [ + { + "id": "oidc-github", + "name": "GitHub", + "icon": "mxc://matrix.org/sVesTtrFDTpXRbYfpahuJsKP", + "brand": "github" + }, + { + "id": "oidc-google", + "name": "Google", + "icon": "mxc://matrix.org/ZlnaaZNPxtUuQemvgQzlOlkz", + "brand": "google" + }, + { + "id": "oidc-gitlab", + "name": "GitLab", + "icon": "mxc://matrix.org/MCVOEmFgVieKFshPxmnejWOq", + "brand": "gitlab" + }, + { + "id": "oidc-facebook", + "name": "Facebook", + "icon": "mxc://matrix.org/nsyeLIgzxazZmJadflMAsAWG", + "brand": "facebook" + }, + { + "id": "oidc-apple", + "name": "Apple", + "icon": "mxc://matrix.org/QQKNSOdLiMHtJhzeAObmkFiU", + "brand": "apple" + } + ] + }, + { + "type": "m.login.token" + }, + { + "type": "m.login.password" + }, + { + "type": "m.login.application_service" + } + ] +} diff --git a/cypress/fixtures/matrix-org-client-versions.json b/cypress/fixtures/matrix-org-client-versions.json new file mode 100644 index 00000000000..51f2ab09386 --- /dev/null +++ b/cypress/fixtures/matrix-org-client-versions.json @@ -0,0 +1,39 @@ +{ + "versions": [ + "r0.0.1", + "r0.1.0", + "r0.2.0", + "r0.3.0", + "r0.4.0", + "r0.5.0", + "r0.6.0", + "r0.6.1", + "v1.1", + "v1.2", + "v1.3", + "v1.4" + ], + "unstable_features": { + "org.matrix.label_based_filtering": true, + "org.matrix.e2e_cross_signing": true, + "org.matrix.msc2432": true, + "uk.half-shot.msc2666.mutual_rooms": true, + "io.element.e2ee_forced.public": false, + "io.element.e2ee_forced.private": false, + "io.element.e2ee_forced.trusted_private": false, + "org.matrix.msc3026.busy_presence": false, + "org.matrix.msc2285.stable": true, + "org.matrix.msc3827.stable": true, + "org.matrix.msc2716": false, + "org.matrix.msc3030": false, + "org.matrix.msc3440.stable": true, + "org.matrix.msc3771": true, + "org.matrix.msc3773": false, + "fi.mau.msc2815": false, + "org.matrix.msc3882": false, + "org.matrix.msc3881": false, + "org.matrix.msc3874": false, + "org.matrix.msc3886": false, + "org.matrix.msc3912": false + } +} diff --git a/cypress/fixtures/matrix-org-client-well-known.json b/cypress/fixtures/matrix-org-client-well-known.json new file mode 100644 index 00000000000..ed726e2421b --- /dev/null +++ b/cypress/fixtures/matrix-org-client-well-known.json @@ -0,0 +1,8 @@ +{ + "m.homeserver": { + "base_url": "https://matrix-client.matrix.org" + }, + "m.identity_server": { + "base_url": "https://vector.im" + } +} diff --git a/cypress/fixtures/vector-im-identity-v1.json b/cypress/fixtures/vector-im-identity-v1.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/cypress/fixtures/vector-im-identity-v1.json @@ -0,0 +1 @@ +{} diff --git a/cypress/plugins/docker/index.ts b/cypress/plugins/docker/index.ts index cf1290e0e87..7c5fa1555ac 100644 --- a/cypress/plugins/docker/index.ts +++ b/cypress/plugins/docker/index.ts @@ -42,7 +42,8 @@ export function dockerRun(opts: { const args = [ "run", - "--name", `${opts.containerName}-${crypto.randomBytes(4).toString("hex")}`, + "--name", + `${opts.containerName}-${crypto.randomBytes(4).toString("hex")}`, "-d", ...params, opts.image, @@ -58,23 +59,22 @@ export function dockerRun(opts: { }); } -export function dockerExec(args: { - containerId: string; - params: string[]; -}): Promise { +export function dockerExec(args: { containerId: string; params: string[] }): Promise { return new Promise((resolve, reject) => { - childProcess.execFile("docker", [ - "exec", args.containerId, - ...args.params, - ], { encoding: 'utf8' }, (err, stdout, stderr) => { - if (err) { - console.log(stdout); - console.log(stderr); - reject(err); - return; - } - resolve(); - }); + childProcess.execFile( + "docker", + ["exec", args.containerId, ...args.params], + { encoding: "utf8" }, + (err, stdout, stderr) => { + if (err) { + console.log(stdout); + console.log(stderr); + reject(err); + return; + } + resolve(); + }, + ); }); } @@ -87,58 +87,45 @@ export async function dockerLogs(args: { const stderrFile = args.stderrFile ? await fse.open(args.stderrFile, "w") : "ignore"; await new Promise((resolve) => { - childProcess.spawn("docker", [ - "logs", - args.containerId, - ], { - stdio: ["ignore", stdoutFile, stderrFile], - }).once('close', resolve); + childProcess + .spawn("docker", ["logs", args.containerId], { + stdio: ["ignore", stdoutFile, stderrFile], + }) + .once("close", resolve); }); if (args.stdoutFile) await fse.close(stdoutFile); if (args.stderrFile) await fse.close(stderrFile); } -export function dockerStop(args: { - containerId: string; -}): Promise { +export function dockerStop(args: { containerId: string }): Promise { return new Promise((resolve, reject) => { - childProcess.execFile('docker', [ - "stop", - args.containerId, - ], err => { + childProcess.execFile("docker", ["stop", args.containerId], (err) => { if (err) reject(err); resolve(); }); }); } -export function dockerRm(args: { - containerId: string; -}): Promise { +export function dockerRm(args: { containerId: string }): Promise { return new Promise((resolve, reject) => { - childProcess.execFile('docker', [ - "rm", - args.containerId, - ], err => { + childProcess.execFile("docker", ["rm", args.containerId], (err) => { if (err) reject(err); resolve(); }); }); } -export function dockerIp(args: { - containerId: string; -}): Promise { +export function dockerIp(args: { containerId: string }): Promise { return new Promise((resolve, reject) => { - childProcess.execFile('docker', [ - "inspect", - "-f", "{{ .NetworkSettings.IPAddress }}", - args.containerId, - ], (err, stdout) => { - if (err) reject(err); - else resolve(stdout.trim()); - }); + childProcess.execFile( + "docker", + ["inspect", "-f", "{{ .NetworkSettings.IPAddress }}", args.containerId], + (err, stdout) => { + if (err) reject(err); + else resolve(stdout.trim()); + }, + ); }); } diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts index ce154ee0bcd..603f31ed09d 100644 --- a/cypress/plugins/index.ts +++ b/cypress/plugins/index.ts @@ -27,7 +27,7 @@ import { log } from "./log"; /** * @type {Cypress.PluginConfig} */ -export default function(on: PluginEvents, config: PluginConfigOptions) { +export default function (on: PluginEvents, config: PluginConfigOptions) { docker(on, config); synapseDocker(on, config); slidingSyncProxyDocker(on, config); diff --git a/cypress/plugins/sliding-sync/index.ts b/cypress/plugins/sliding-sync/index.ts index 44dcaecfcba..c2495325e07 100644 --- a/cypress/plugins/sliding-sync/index.ts +++ b/cypress/plugins/sliding-sync/index.ts @@ -41,10 +41,7 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise< const postgresId = await dockerRun({ image: "postgres", containerName: "react-sdk-cypress-sliding-sync-postgres", - params: [ - "--rm", - "-e", `POSTGRES_PASSWORD=${PG_PASSWORD}`, - ], + params: ["--rm", "-e", `POSTGRES_PASSWORD=${PG_PASSWORD}`], }); const postgresIp = await dockerIp({ containerId: postgresId }); @@ -54,14 +51,11 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise< const waitTimeMillis = 30000; const startTime = new Date().getTime(); let lastErr: Error; - while ((new Date().getTime() - startTime) < waitTimeMillis) { + while (new Date().getTime() - startTime < waitTimeMillis) { try { await dockerExec({ containerId: postgresId, - params: [ - "pg_isready", - "-U", "postgres", - ], + params: ["pg_isready", "-U", "postgres"], }); lastErr = null; break; @@ -82,10 +76,14 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise< containerName: "react-sdk-cypress-sliding-sync-proxy", params: [ "--rm", - "-p", `${port}:8008/tcp`, - "-e", "SYNCV3_SECRET=bwahahaha", - "-e", `SYNCV3_SERVER=http://${synapseIp}:8008`, - "-e", `SYNCV3_DB=user=postgres dbname=postgres password=${PG_PASSWORD} host=${postgresIp} sslmode=disable`, + "-p", + `${port}:8008/tcp`, + "-e", + "SYNCV3_SECRET=bwahahaha", + "-e", + `SYNCV3_SERVER=http://${synapseIp}:8008`, + "-e", + `SYNCV3_DB=user=postgres dbname=postgres password=${PG_PASSWORD} host=${postgresIp} sslmode=disable`, ], }); console.log(new Date(), "started!"); diff --git a/cypress/plugins/synapsedocker/index.ts b/cypress/plugins/synapsedocker/index.ts index bfbda1e3607..4a864eb56dc 100644 --- a/cypress/plugins/synapsedocker/index.ts +++ b/cypress/plugins/synapsedocker/index.ts @@ -54,11 +54,11 @@ async function cfgDirFromTemplate(template: string): Promise { if (!stats?.isDirectory) { throw new Error(`No such template: ${template}`); } - const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), 'react-sdk-synapsedocker-')); + const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-synapsedocker-")); // copy the contents of the template dir, omitting homeserver.yaml as we'll template that console.log(`Copy ${templateDir} -> ${tempDir}`); - await fse.copy(templateDir, tempDir, { filter: f => path.basename(f) !== 'homeserver.yaml' }); + await fse.copy(templateDir, tempDir, { filter: (f) => path.basename(f) !== "homeserver.yaml" }); const registrationSecret = randB64Bytes(16); const macaroonSecret = randB64Bytes(16); @@ -102,11 +102,7 @@ async function synapseStart(template: string): Promise { const synapseId = await dockerRun({ image: "matrixdotorg/synapse:develop", containerName: `react-sdk-cypress-synapse`, - params: [ - "--rm", - "-v", `${synCfg.configDir}:/data`, - "-p", `${synCfg.port}:8008/tcp`, - ], + params: ["--rm", "-v", `${synCfg.configDir}:/data`, "-p", `${synCfg.port}:8008/tcp`], cmd: "run", }); @@ -117,9 +113,12 @@ async function synapseStart(template: string): Promise { containerId: synapseId, params: [ "curl", - "--connect-timeout", "30", - "--retry", "30", - "--retry-delay", "1", + "--connect-timeout", + "30", + "--retry", + "30", + "--retry-delay", + "1", "--retry-all-errors", "--silent", "http://localhost:8008/health", diff --git a/cypress/plugins/synapsedocker/templates/COPYME/homeserver.yaml b/cypress/plugins/synapsedocker/templates/COPYME/homeserver.yaml index fab1bc1c451..cb58dc86615 100644 --- a/cypress/plugins/synapsedocker/templates/COPYME/homeserver.yaml +++ b/cypress/plugins/synapsedocker/templates/COPYME/homeserver.yaml @@ -5,21 +5,21 @@ pid_file: /data/homeserver.pid public_baseurl: http://localhost:8008/ # Listener is always port 8008 (configured in the container) listeners: - - port: 8008 - tls: false - bind_addresses: ['::'] - type: http - x_forwarded: true + - port: 8008 + tls: false + bind_addresses: ["::"] + type: http + x_forwarded: true - resources: - - names: [client, federation, consent] - compress: false + resources: + - names: [client, federation, consent] + compress: false # An sqlite in-memory database is fast & automatically wipes each time database: - name: "sqlite3" - args: - database: ":memory:" + name: "sqlite3" + args: + database: ":memory:" # Needs to be configured to log to the console like a good docker process log_config: "/data/log.config" @@ -27,20 +27,20 @@ log_config: "/data/log.config" rc_messages_per_second: 10000 rc_message_burst_count: 10000 rc_registration: - per_second: 10000 - burst_count: 10000 - -rc_login: - address: - per_second: 10000 - burst_count: 10000 - account: - per_second: 10000 - burst_count: 10000 - failed_attempts: per_second: 10000 burst_count: 10000 +rc_login: + address: + per_second: 10000 + burst_count: 10000 + account: + per_second: 10000 + burst_count: 10000 + failed_attempts: + per_second: 10000 + burst_count: 10000 + media_store_path: "/data/media_store" uploads_path: "/data/uploads" enable_registration: true @@ -54,19 +54,19 @@ form_secret: "{{FORM_SECRET}}" # Signing key must be here: it will be generated to this file signing_key_path: "/data/localhost.signing.key" email: - enable_notifs: false - smtp_host: "localhost" - smtp_port: 25 - smtp_user: "exampleusername" - smtp_pass: "examplepassword" - require_transport_security: False - notif_from: "Your Friendly %(app)s homeserver " - app_name: Matrix - notif_template_html: notif_mail.html - notif_template_text: notif_mail.txt - notif_for_new_users: True - client_base_url: "http://localhost/element" + enable_notifs: false + smtp_host: "localhost" + smtp_port: 25 + smtp_user: "exampleusername" + smtp_pass: "examplepassword" + require_transport_security: False + notif_from: "Your Friendly %(app)s homeserver " + app_name: Matrix + notif_template_html: notif_mail.html + notif_template_text: notif_mail.txt + notif_for_new_users: True + client_base_url: "http://localhost/element" trusted_key_servers: - - server_name: "matrix.org" + - server_name: "matrix.org" suppress_key_server_warning: true diff --git a/cypress/plugins/synapsedocker/templates/consent/homeserver.yaml b/cypress/plugins/synapsedocker/templates/consent/homeserver.yaml index 6decaeb5a0b..d3a4fa520ca 100644 --- a/cypress/plugins/synapsedocker/templates/consent/homeserver.yaml +++ b/cypress/plugins/synapsedocker/templates/consent/homeserver.yaml @@ -2,40 +2,40 @@ server_name: "localhost" pid_file: /data/homeserver.pid public_baseurl: "{{PUBLIC_BASEURL}}" listeners: - - port: 8008 - tls: false - bind_addresses: ['::'] - type: http - x_forwarded: true + - port: 8008 + tls: false + bind_addresses: ["::"] + type: http + x_forwarded: true - resources: - - names: [client, federation, consent] - compress: false + resources: + - names: [client, federation, consent] + compress: false database: - name: "sqlite3" - args: - database: ":memory:" + name: "sqlite3" + args: + database: ":memory:" log_config: "/data/log.config" rc_messages_per_second: 10000 rc_message_burst_count: 10000 rc_registration: - per_second: 10000 - burst_count: 10000 - -rc_login: - address: - per_second: 10000 - burst_count: 10000 - account: - per_second: 10000 - burst_count: 10000 - failed_attempts: per_second: 10000 burst_count: 10000 +rc_login: + address: + per_second: 10000 + burst_count: 10000 + account: + per_second: 10000 + burst_count: 10000 + failed_attempts: + per_second: 10000 + burst_count: 10000 + media_store_path: "/data/media_store" uploads_path: "/data/uploads" enable_registration: true @@ -47,38 +47,38 @@ macaroon_secret_key: "{{MACAROON_SECRET_KEY}}" form_secret: "{{FORM_SECRET}}" signing_key_path: "/data/localhost.signing.key" email: - enable_notifs: false - smtp_host: "localhost" - smtp_port: 25 - smtp_user: "exampleusername" - smtp_pass: "examplepassword" - require_transport_security: False - notif_from: "Your Friendly %(app)s homeserver " - app_name: Matrix - notif_template_html: notif_mail.html - notif_template_text: notif_mail.txt - notif_for_new_users: True - client_base_url: "http://localhost/element" + enable_notifs: false + smtp_host: "localhost" + smtp_port: 25 + smtp_user: "exampleusername" + smtp_pass: "examplepassword" + require_transport_security: False + notif_from: "Your Friendly %(app)s homeserver " + app_name: Matrix + notif_template_html: notif_mail.html + notif_template_text: notif_mail.txt + notif_for_new_users: True + client_base_url: "http://localhost/element" user_consent: - template_dir: /data/res/templates/privacy - version: 1.0 - server_notice_content: - msgtype: m.text - body: >- - To continue using this homeserver you must review and agree to the - terms and conditions at %(consent_uri)s - send_server_notice_to_guests: True - block_events_error: >- - To continue using this homeserver you must review and agree to the - terms and conditions at %(consent_uri)s - require_at_registration: true + template_dir: /data/res/templates/privacy + version: 1.0 + server_notice_content: + msgtype: m.text + body: >- + To continue using this homeserver you must review and agree to the + terms and conditions at %(consent_uri)s + send_server_notice_to_guests: True + block_events_error: >- + To continue using this homeserver you must review and agree to the + terms and conditions at %(consent_uri)s + require_at_registration: true server_notices: - system_mxid_localpart: notices - system_mxid_display_name: "Server Notices" - system_mxid_avatar_url: "mxc://localhost:5005/oumMVlgDnLYFaPVkExemNVVZ" - room_name: "Server Notices" + system_mxid_localpart: notices + system_mxid_display_name: "Server Notices" + system_mxid_avatar_url: "mxc://localhost:5005/oumMVlgDnLYFaPVkExemNVVZ" + room_name: "Server Notices" trusted_key_servers: - - server_name: "matrix.org" + - server_name: "matrix.org" suppress_key_server_warning: true diff --git a/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/1.0.html b/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/1.0.html index d4959b4bcb3..8ee888518ab 100644 --- a/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/1.0.html +++ b/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/1.0.html @@ -1,23 +1,19 @@ - + - - Test Privacy policy - - - {% if has_consented %} -

- Thank you, you've already accepted the license. -

- {% else %} -

- Please accept the license! -

-
- - - - -
- {% endif %} - - \ No newline at end of file + + Test Privacy policy + + + {% if has_consented %} +

Thank you, you've already accepted the license.

+ {% else %} +

Please accept the license!

+
+ + + + +
+ {% endif %} + + diff --git a/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/success.html b/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/success.html index abe27d87ca1..0345001e758 100644 --- a/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/success.html +++ b/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/success.html @@ -1,9 +1,9 @@ - + - - Test Privacy policy - - -

Danke schon

- - \ No newline at end of file + + Test Privacy policy + + +

Danke schon

+ + diff --git a/cypress/plugins/synapsedocker/templates/default/homeserver.yaml b/cypress/plugins/synapsedocker/templates/default/homeserver.yaml index 347dadc88f4..aaad3420b99 100644 --- a/cypress/plugins/synapsedocker/templates/default/homeserver.yaml +++ b/cypress/plugins/synapsedocker/templates/default/homeserver.yaml @@ -2,60 +2,60 @@ server_name: "localhost" pid_file: /data/homeserver.pid public_baseurl: "{{PUBLIC_BASEURL}}" listeners: - - port: 8008 - tls: false - bind_addresses: ['::'] - type: http - x_forwarded: true + - port: 8008 + tls: false + bind_addresses: ["::"] + type: http + x_forwarded: true - resources: - - names: [client] - compress: false + resources: + - names: [client] + compress: false database: - name: "sqlite3" - args: - database: ":memory:" + name: "sqlite3" + args: + database: ":memory:" log_config: "/data/log.config" rc_messages_per_second: 10000 rc_message_burst_count: 10000 rc_registration: - per_second: 10000 - burst_count: 10000 + per_second: 10000 + burst_count: 10000 rc_joins: - local: - per_second: 9999 - burst_count: 9999 - remote: + local: + per_second: 9999 + burst_count: 9999 + remote: + per_second: 9999 + burst_count: 9999 +rc_joins_per_room: per_second: 9999 burst_count: 9999 -rc_joins_per_room: - per_second: 9999 - burst_count: 9999 rc_3pid_validation: - per_second: 1000 - burst_count: 1000 - -rc_invites: - per_room: - per_second: 1000 - burst_count: 1000 - per_user: per_second: 1000 burst_count: 1000 +rc_invites: + per_room: + per_second: 1000 + burst_count: 1000 + per_user: + per_second: 1000 + burst_count: 1000 + rc_login: - address: - per_second: 10000 - burst_count: 10000 - account: - per_second: 10000 - burst_count: 10000 - failed_attempts: - per_second: 10000 - burst_count: 10000 + address: + per_second: 10000 + burst_count: 10000 + account: + per_second: 10000 + burst_count: 10000 + failed_attempts: + per_second: 10000 + burst_count: 10000 media_store_path: "/data/media_store" uploads_path: "/data/uploads" @@ -69,8 +69,8 @@ form_secret: "{{FORM_SECRET}}" signing_key_path: "/data/localhost.signing.key" trusted_key_servers: - - server_name: "matrix.org" + - server_name: "matrix.org" suppress_key_server_warning: true ui_auth: - session_timeout: "300s" + session_timeout: "300s" diff --git a/cypress/plugins/utils/port.ts b/cypress/plugins/utils/port.ts index 064ccc7cf94..156ba866d5e 100644 --- a/cypress/plugins/utils/port.ts +++ b/cypress/plugins/utils/port.ts @@ -17,7 +17,7 @@ limitations under the License. import * as net from "net"; export async function getFreePort(): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { const srv = net.createServer(); srv.listen(0, () => { const port = (srv.address()).port; diff --git a/cypress/support/app.ts b/cypress/support/app.ts index 322b9785f36..3e9d75173a2 100644 --- a/cypress/support/app.ts +++ b/cypress/support/app.ts @@ -32,7 +32,7 @@ declare global { } Cypress.Commands.add("tweakConfig", (tweaks: Record): Chainable => { - return cy.window().then(win => { + return cy.window().then((win) => { // note: we can't *set* the object because the window version is effectively a pointer. for (const [k, v] of Object.entries(tweaks)) { // @ts-ignore - for some reason it's not picking up on global.d.ts types. @@ -42,4 +42,4 @@ Cypress.Commands.add("tweakConfig", (tweaks: Record): Chainable void) | undefined, - skipFailures?: boolean, -): void => { - return originalFn(context, { - ...options, - rules: { - // Disable contrast checking for now as we have too many issues with it - 'color-contrast': { - enabled: false, +Cypress.Commands.overwrite( + "checkA11y", + ( + originalFn: Chainable["checkA11y"], + context?: string | Node | axe.ContextObject | undefined, + options: Options = {}, + violationCallback?: ((violations: axe.Result[]) => void) | undefined, + skipFailures?: boolean, + ): void => { + return originalFn( + context, + { + ...options, + rules: { + // Disable contrast checking for now as we have too many issues with it + "color-contrast": { + enabled: false, + }, + ...options.rules, + }, }, - ...options.rules, - }, - }, violationCallback ?? terminalLog, skipFailures); -}); + violationCallback ?? terminalLog, + skipFailures, + ); + }, +); diff --git a/cypress/support/bot.ts b/cypress/support/bot.ts index 26f0aa497e4..d02a0f857b5 100644 --- a/cypress/support/bot.ts +++ b/cypress/support/bot.ts @@ -77,8 +77,9 @@ Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts): opts = Object.assign({}, defaultCreateBotOptions, opts); const username = Cypress._.uniqueId("userId_"); const password = Cypress._.uniqueId("password_"); - return cy.registerUser(synapse, username, password, opts.displayName).then(credentials => { - return cy.window({ log: false }).then(win => { + return cy.registerUser(synapse, username, password, opts.displayName).then((credentials) => { + cy.log(`Registered bot user ${username} with displayname ${opts.displayName}`); + return cy.window({ log: false }).then((win) => { const cli = new win.matrixcs.MatrixClient({ baseUrl: synapse.baseUrl, userId: credentials.userId, @@ -102,12 +103,17 @@ Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts): } return cy.wrap( - cli.initCrypto() + cli + .initCrypto() .then(() => cli.setGlobalErrorOnUnknownDevices(false)) .then(() => cli.startClient()) - .then(() => cli.bootstrapCrossSigning({ - authUploadDeviceSigningKeys: async func => { await func({}); }, - })) + .then(() => + cli.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: async (func) => { + await func({}); + }, + }), + ) .then(() => cli), ); }); @@ -128,13 +134,15 @@ Cypress.Commands.add("botJoinRoomByName", (cli: MatrixClient, roomName: string): return cy.wrap(Promise.reject(`Bot room join failed. Cannot find room '${roomName}'`)); }); -Cypress.Commands.add("botSendMessage", ( - cli: MatrixClient, - roomId: string, - message: string, -): Chainable => { - return cy.wrap(cli.sendMessage(roomId, { - msgtype: "m.text", - body: message, - }), { log: false }); -}); +Cypress.Commands.add( + "botSendMessage", + (cli: MatrixClient, roomId: string, message: string): Chainable => { + return cy.wrap( + cli.sendMessage(roomId, { + msgtype: "m.text", + body: message, + }), + { log: false }, + ); + }, +); diff --git a/cypress/support/client.ts b/cypress/support/client.ts index e20c08a8139..1195a6f303c 100644 --- a/cypress/support/client.ts +++ b/cypress/support/client.ts @@ -66,7 +66,7 @@ declare global { roomId: string, threadId: string | null, eventType: string, - content: IContent + content: IContent, ): Chainable; /** * @param {string} name @@ -89,10 +89,7 @@ declare global { * can be sent to XMLHttpRequest.send (typically a File). Under node.js, * a a Buffer, String or ReadStream. */ - uploadContent( - file: FileType, - opts?: UploadOpts, - ): Chainable>; + uploadContent(file: FileType, opts?: UploadOpts): Chainable>; /** * Turn an MXC URL into an HTTP one. This method is experimental and * may change. @@ -133,23 +130,24 @@ declare global { } Cypress.Commands.add("getClient", (): Chainable => { - return cy.window({ log: false }).then(win => win.mxMatrixClientPeg.matrixClient); + return cy.window({ log: false }).then((win) => win.mxMatrixClientPeg.matrixClient); }); Cypress.Commands.add("getDmRooms", (userId: string): Chainable => { - return cy.getClient() - .then(cli => cli.getAccountData("m.direct")?.getContent>()) - .then(dmRoomMap => dmRoomMap[userId] ?? []); + return cy + .getClient() + .then((cli) => cli.getAccountData("m.direct")?.getContent>()) + .then((dmRoomMap) => dmRoomMap[userId] ?? []); }); Cypress.Commands.add("createRoom", (options: ICreateRoomOpts): Chainable => { - return cy.window({ log: false }).then(async win => { + return cy.window({ log: false }).then(async (win) => { const cli = win.mxMatrixClientPeg.matrixClient; const resp = await cli.createRoom(options); const roomId = resp.room_id; if (!cli.getRoom(roomId)) { - await new Promise(resolve => { + await new Promise((resolve) => { const onRoom = (room: Room) => { if (room.roomId === roomId) { cli.off(win.matrixcs.ClientEvent.Room, onRoom); @@ -168,7 +166,7 @@ Cypress.Commands.add("createSpace", (options: ICreateRoomOpts): Chainable => { - return cy.getClient().then(async (cli: MatrixClient) => { - return cli.sendEvent(roomId, threadId, eventType, content); - }); -}); +Cypress.Commands.add( + "sendEvent", + (roomId: string, threadId: string | null, eventType: string, content: IContent): Chainable => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.sendEvent(roomId, threadId, eventType, content); + }); + }, +); Cypress.Commands.add("setDisplayName", (name: string): Chainable<{}> => { return cy.getClient().then(async (cli: MatrixClient) => { @@ -215,13 +211,15 @@ Cypress.Commands.add("setAvatarUrl", (url: string): Chainable<{}> => { }); Cypress.Commands.add("bootstrapCrossSigning", () => { - cy.window({ log: false }).then(win => { + cy.window({ log: false }).then((win) => { win.mxMatrixClientPeg.matrixClient.bootstrapCrossSigning({ - authUploadDeviceSigningKeys: async func => { await func({}); }, + authUploadDeviceSigningKeys: async (func) => { + await func({}); + }, }); }); }); Cypress.Commands.add("joinRoom", (roomIdOrAlias: string): Chainable => { - return cy.getClient().then(cli => cli.joinRoom(roomIdOrAlias)); + return cy.getClient().then((cli) => cli.joinRoom(roomIdOrAlias)); }); diff --git a/cypress/support/clipboard.ts b/cypress/support/clipboard.ts index 5e80ed8361d..795caee595c 100644 --- a/cypress/support/clipboard.ts +++ b/cypress/support/clipboard.ts @@ -41,7 +41,7 @@ declare global { } Cypress.Commands.add("mockClipboard", () => { - cy.window({ log: false }).then(win => { + cy.window({ log: false }).then((win) => { win.navigator.clipboard.writeText = (text) => { copyText = text; return Promise.resolve(); @@ -54,4 +54,4 @@ Cypress.Commands.add("getClipboardText", (): Chainable => { }); // Needed to make this file a module -export { }; +export {}; diff --git a/cypress/support/composer.ts b/cypress/support/composer.ts index ae6c8bef871..347c581a477 100644 --- a/cypress/support/composer.ts +++ b/cypress/support/composer.ts @@ -33,7 +33,7 @@ declare global { } Cypress.Commands.add("getComposer", (isRightPanel?: boolean): Chainable => { - const panelClass = isRightPanel ? '.mx_RightPanel' : '.mx_RoomView_body'; + const panelClass = isRightPanel ? ".mx_RightPanel" : ".mx_RoomView_body"; return cy.get(`${panelClass} .mx_MessageComposer`); }); @@ -41,8 +41,8 @@ Cypress.Commands.add("openMessageComposerOptions", (isRightPanel?: boolean): Cha cy.getComposer(isRightPanel).within(() => { cy.get('[aria-label="More options"]').click(); }); - return cy.get('.mx_MessageComposer_Menu'); + return cy.get(".mx_MessageComposer_Menu"); }); // Needed to make this file a module -export { }; +export {}; diff --git a/cypress/support/iframes.ts b/cypress/support/iframes.ts index 27bd5e0b8ee..03bd653e3b8 100644 --- a/cypress/support/iframes.ts +++ b/cypress/support/iframes.ts @@ -35,11 +35,15 @@ declare global { // Inspired by https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/ Cypress.Commands.add("accessIframe", (selector: string): Chainable> => { - return cy.get(selector) - .its("0.contentDocument.body").should("not.be.empty") - // Cypress loses types in the mess of wrapping, so force cast - .then(cy.wrap) as Chainable>; + return ( + cy + .get(selector) + .its("0.contentDocument.body") + .should("not.be.empty") + // Cypress loses types in the mess of wrapping, so force cast + .then(cy.wrap) as Chainable> + ); }); // Needed to make this file a module -export { }; +export {}; diff --git a/cypress/support/labs.ts b/cypress/support/labs.ts index 3fff154e140..f0695891735 100644 --- a/cypress/support/labs.ts +++ b/cypress/support/labs.ts @@ -33,10 +33,13 @@ declare global { } Cypress.Commands.add("enableLabsFeature", (feature: string): Chainable => { - return cy.window({ log: false }).then(win => { - win.localStorage.setItem(`mx_labs_feature_${feature}`, "true"); - }).then(() => null); + return cy + .window({ log: false }) + .then((win) => { + win.localStorage.setItem(`mx_labs_feature_${feature}`, "true"); + }) + .then(() => null); }); // Needed to make this file a module -export { }; +export {}; diff --git a/cypress/support/login.ts b/cypress/support/login.ts index 6c441589415..73300f79053 100644 --- a/cypress/support/login.ts +++ b/cypress/support/login.ts @@ -49,86 +49,97 @@ declare global { * @param username login username * @param password login password */ - loginUser( - synapse: SynapseInstance, - username: string, - password: string, - ): Chainable; + loginUser(synapse: SynapseInstance, username: string, password: string): Chainable; } } } // eslint-disable-next-line max-len -Cypress.Commands.add("loginUser", (synapse: SynapseInstance, username: string, password: string): Chainable => { - const url = `${synapse.baseUrl}/_matrix/client/r0/login`; - return cy.request<{ - access_token: string; - user_id: string; - device_id: string; - home_server: string; - }>({ - url, - method: "POST", - body: { - "type": "m.login.password", - "identifier": { - "type": "m.id.user", - "user": username, +Cypress.Commands.add( + "loginUser", + (synapse: SynapseInstance, username: string, password: string): Chainable => { + const url = `${synapse.baseUrl}/_matrix/client/r0/login`; + return cy + .request<{ + access_token: string; + user_id: string; + device_id: string; + home_server: string; + }>({ + url, + method: "POST", + body: { + type: "m.login.password", + identifier: { + type: "m.id.user", + user: username, + }, + password: password, }, - "password": password, - }, - }).then(response => ({ - password, - username, - accessToken: response.body.access_token, - userId: response.body.user_id, - deviceId: response.body.device_id, - homeServer: response.body.home_server, - })); -}); + }) + .then((response) => ({ + password, + username, + accessToken: response.body.access_token, + userId: response.body.user_id, + deviceId: response.body.device_id, + homeServer: response.body.home_server, + })); + }, +); // eslint-disable-next-line max-len -Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: string, prelaunchFn?: () => void): Chainable => { - // XXX: work around Cypress not clearing IDB between tests - cy.window({ log: false }).then(win => { - win.indexedDB.databases()?.then(databases => { - databases.forEach(database => { - win.indexedDB.deleteDatabase(database.name); +Cypress.Commands.add( + "initTestUser", + (synapse: SynapseInstance, displayName: string, prelaunchFn?: () => void): Chainable => { + // XXX: work around Cypress not clearing IDB between tests + cy.window({ log: false }).then((win) => { + win.indexedDB.databases()?.then((databases) => { + databases.forEach((database) => { + win.indexedDB.deleteDatabase(database.name); + }); }); }); - }); - const username = Cypress._.uniqueId("userId_"); - const password = Cypress._.uniqueId("password_"); - return cy.registerUser(synapse, username, password, displayName).then(() => { - return cy.loginUser(synapse, username, password); - }).then(response => { - cy.window({ log: false }).then(win => { - // Seed the localStorage with the required credentials - win.localStorage.setItem("mx_hs_url", synapse.baseUrl); - win.localStorage.setItem("mx_user_id", response.userId); - win.localStorage.setItem("mx_access_token", response.accessToken); - win.localStorage.setItem("mx_device_id", response.deviceId); - win.localStorage.setItem("mx_is_guest", "false"); - win.localStorage.setItem("mx_has_pickle_key", "false"); - win.localStorage.setItem("mx_has_access_token", "true"); + const username = Cypress._.uniqueId("userId_"); + const password = Cypress._.uniqueId("password_"); + return cy + .registerUser(synapse, username, password, displayName) + .then(() => { + return cy.loginUser(synapse, username, password); + }) + .then((response) => { + cy.log(`Registered test user ${username} with displayname ${displayName}`); + cy.window({ log: false }).then((win) => { + // Seed the localStorage with the required credentials + win.localStorage.setItem("mx_hs_url", synapse.baseUrl); + win.localStorage.setItem("mx_user_id", response.userId); + win.localStorage.setItem("mx_access_token", response.accessToken); + win.localStorage.setItem("mx_device_id", response.deviceId); + win.localStorage.setItem("mx_is_guest", "false"); + win.localStorage.setItem("mx_has_pickle_key", "false"); + win.localStorage.setItem("mx_has_access_token", "true"); - // Ensure the language is set to a consistent value - win.localStorage.setItem("mx_local_settings", '{"language":"en"}'); - }); + // Ensure the language is set to a consistent value + win.localStorage.setItem("mx_local_settings", '{"language":"en"}'); + }); - prelaunchFn?.(); + prelaunchFn?.(); - return cy.visit("/").then(() => { - // wait for the app to load - return cy.get(".mx_MatrixChat", { timeout: 30000 }); - }).then(() => ({ - password, - username, - accessToken: response.accessToken, - userId: response.userId, - deviceId: response.deviceId, - homeServer: response.homeServer, - })); - }); -}); + return cy + .visit("/") + .then(() => { + // wait for the app to load + return cy.get(".mx_MatrixChat", { timeout: 30000 }); + }) + .then(() => ({ + password, + username, + accessToken: response.accessToken, + userId: response.userId, + deviceId: response.deviceId, + homeServer: response.homeServer, + })); + }); + }, +); diff --git a/cypress/support/network.ts b/cypress/support/network.ts index 73df049c6c4..93e5ca3c279 100644 --- a/cypress/support/network.ts +++ b/cypress/support/network.ts @@ -20,10 +20,12 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { - // Intercept all /_matrix/ networking requests for the logged in user and fail them + // Intercept all /_matrix/ networking requests for the logged-in user and fail them goOffline(): void; // Remove intercept on all /_matrix/ networking requests goOnline(): void; + // Intercept calls to vector.im/matrix.org so a login page can be shown offline + stubDefaultServer(): void; } } } @@ -33,30 +35,62 @@ declare global { Cypress.Commands.add("goOffline", (): void => { cy.log("Going offline"); - cy.window({ log: false }).then(win => { - cy.intercept("**/_matrix/**", { - headers: { - "Authorization": "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(), + cy.window({ log: false }).then((win) => { + cy.intercept( + "**/_matrix/**", + { + headers: { + Authorization: "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(), + }, }, - }, req => { - req.destroy(); - }); + (req) => { + req.destroy(); + }, + ); }); }); Cypress.Commands.add("goOnline", (): void => { cy.log("Going online"); - cy.window({ log: false }).then(win => { - cy.intercept("**/_matrix/**", { - headers: { - "Authorization": "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(), + cy.window({ log: false }).then((win) => { + cy.intercept( + "**/_matrix/**", + { + headers: { + Authorization: "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(), + }, + }, + (req) => { + req.continue(); }, - }, req => { - req.continue(); - }); + ); win.dispatchEvent(new Event("online")); }); }); +Cypress.Commands.add("stubDefaultServer", (): void => { + cy.log("Stubbing vector.im and matrix.org network calls"); + // We intercept vector.im & matrix.org calls so that tests don't fail when it has issues + cy.intercept("GET", "https://vector.im/_matrix/identity/api/v1", { + fixture: "vector-im-identity-v1.json", + }); + cy.intercept("GET", "https://matrix.org/.well-known/matrix/client", { + fixture: "matrix-org-client-well-known.json", + }); + cy.intercept("GET", "https://matrix-client.matrix.org/_matrix/client/versions", { + fixture: "matrix-org-client-versions.json", + }); + cy.intercept("GET", "https://matrix-client.matrix.org/_matrix/client/r0/login", { + fixture: "matrix-org-client-login.json", + }); + cy.intercept("POST", "https://matrix-client.matrix.org/_matrix/client/r0/register?kind=guest", { + statusCode: 403, + body: { + errcode: "M_FORBIDDEN", + error: "Registration is not enabled on this homeserver.", + }, + }); +}); + // Needed to make this file a module -export { }; +export {}; diff --git a/cypress/support/percy.ts b/cypress/support/percy.ts index f190e7a5148..f5e30a58fcb 100644 --- a/cypress/support/percy.ts +++ b/cypress/support/percy.ts @@ -15,7 +15,7 @@ limitations under the License. */ /// -import { SnapshotOptions as PercySnapshotOptions } from '@percy/core'; +import { SnapshotOptions as PercySnapshotOptions } from "@percy/core"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -39,16 +39,16 @@ declare global { Cypress.Commands.add("percySnapshotElement", { prevSubject: "element" }, (subject, name, options) => { cy.percySnapshot(name, { - domTransformation: documentClone => scope(documentClone, subject.selector), + domTransformation: (documentClone) => scope(documentClone, subject.selector), ...options, }); }); function scope(documentClone: Document, selector: string): Document { const element = documentClone.querySelector(selector); - documentClone.querySelector('body').innerHTML = element.outerHTML; + documentClone.querySelector("body").innerHTML = element.outerHTML; return documentClone; } -export { }; +export {}; diff --git a/cypress/support/proxy.ts b/cypress/support/proxy.ts index e6b1ae96f1d..97480bdbe1f 100644 --- a/cypress/support/proxy.ts +++ b/cypress/support/proxy.ts @@ -18,7 +18,7 @@ limitations under the License. import Chainable = Cypress.Chainable; import AUTWindow = Cypress.AUTWindow; -import { ProxyInstance } from '../plugins/sliding-sync'; +import { ProxyInstance } from "../plugins/sliding-sync"; import { SynapseInstance } from "../plugins/synapsedocker"; declare global { @@ -49,7 +49,7 @@ function stopProxy(proxy?: ProxyInstance): Chainable { if (!proxy) return; // Navigate away from app to stop the background network requests which will race with Synapse shutting down return cy.window({ log: false }).then((win) => { - win.location.href = 'about:blank'; + win.location.href = "about:blank"; cy.task("proxyStop", proxy); }); } diff --git a/cypress/support/settings.ts b/cypress/support/settings.ts index 42a78792a0f..78c3f68878c 100644 --- a/cypress/support/settings.ts +++ b/cypress/support/settings.ts @@ -102,26 +102,27 @@ declare global { } Cypress.Commands.add("getSettingsStore", (): Chainable => { - return cy.window({ log: false }).then(win => win.mxSettingsStore); + return cy.window({ log: false }).then((win) => win.mxSettingsStore); }); -Cypress.Commands.add("setSettingValue", ( - name: string, - roomId: string, - level: SettingLevel, - value: any, -): Chainable => { - return cy.getSettingsStore().then((store: typeof SettingsStore) => { - return cy.wrap(store.setValue(name, roomId, level, value)); - }); -}); +Cypress.Commands.add( + "setSettingValue", + (name: string, roomId: string, level: SettingLevel, value: any): Chainable => { + return cy.getSettingsStore().then((store: typeof SettingsStore) => { + return cy.wrap(store.setValue(name, roomId, level, value)); + }); + }, +); // eslint-disable-next-line max-len -Cypress.Commands.add("getSettingValue", (name: string, roomId?: string, excludeDefault?: boolean): Chainable => { - return cy.getSettingsStore().then((store: typeof SettingsStore) => { - return store.getValue(name, roomId, excludeDefault); - }); -}); +Cypress.Commands.add( + "getSettingValue", + (name: string, roomId?: string, excludeDefault?: boolean): Chainable => { + return cy.getSettingsStore().then((store: typeof SettingsStore) => { + return store.getValue(name, roomId, excludeDefault); + }); + }, +); Cypress.Commands.add("openUserMenu", (): Chainable> => { cy.get('[aria-label="User menu"]').click(); @@ -162,16 +163,22 @@ Cypress.Commands.add("closeDialog", (): Chainable> => { }); Cypress.Commands.add("joinBeta", (name: string): Chainable> => { - return cy.contains(".mx_BetaCard_title", name).closest(".mx_BetaCard").within(() => { - return cy.get(".mx_BetaCard_buttons").contains("Join the beta").click(); - }); + return cy + .contains(".mx_BetaCard_title", name) + .closest(".mx_BetaCard") + .within(() => { + return cy.get(".mx_BetaCard_buttons").contains("Join the beta").click(); + }); }); Cypress.Commands.add("leaveBeta", (name: string): Chainable> => { - return cy.contains(".mx_BetaCard_title", name).closest(".mx_BetaCard").within(() => { - return cy.get(".mx_BetaCard_buttons").contains("Leave the beta").click(); - }); + return cy + .contains(".mx_BetaCard_title", name) + .closest(".mx_BetaCard") + .within(() => { + return cy.get(".mx_BetaCard_buttons").contains("Leave the beta").click(); + }); }); // Needed to make this file a module -export { }; +export {}; diff --git a/cypress/support/synapse.ts b/cypress/support/synapse.ts index 5696e8c015f..c8af95c5775 100644 --- a/cypress/support/synapse.ts +++ b/cypress/support/synapse.ts @@ -16,7 +16,7 @@ limitations under the License. /// -import * as crypto from 'crypto'; +import * as crypto from "crypto"; import Chainable = Cypress.Chainable; import AUTWindow = Cypress.AUTWindow; @@ -64,7 +64,7 @@ function stopSynapse(synapse?: SynapseInstance): Chainable { if (!synapse) return; // Navigate away from app to stop the background network requests which will race with Synapse shutting down return cy.window({ log: false }).then((win) => { - win.location.href = 'about:blank'; + win.location.href = "about:blank"; cy.task("synapseStop", synapse.synapseId); }); } @@ -83,38 +83,42 @@ function registerUser( displayName?: string, ): Chainable { const url = `${synapse.baseUrl}/_synapse/admin/v1/register`; - return cy.then(() => { - // get a nonce - return cy.request<{ nonce: string }>({ url }); - }).then(response => { - const { nonce } = response.body; - const mac = crypto.createHmac('sha1', synapse.registrationSecret).update( - `${nonce}\0${username}\0${password}\0notadmin`, - ).digest('hex'); + return cy + .then(() => { + // get a nonce + return cy.request<{ nonce: string }>({ url }); + }) + .then((response) => { + const { nonce } = response.body; + const mac = crypto + .createHmac("sha1", synapse.registrationSecret) + .update(`${nonce}\0${username}\0${password}\0notadmin`) + .digest("hex"); - return cy.request<{ - access_token: string; - user_id: string; - home_server: string; - device_id: string; - }>({ - url, - method: "POST", - body: { - nonce, - username, - password, - mac, - admin: false, - displayname: displayName, - }, - }); - }).then(response => ({ - homeServer: response.body.home_server, - accessToken: response.body.access_token, - userId: response.body.user_id, - deviceId: response.body.device_id, - })); + return cy.request<{ + access_token: string; + user_id: string; + home_server: string; + device_id: string; + }>({ + url, + method: "POST", + body: { + nonce, + username, + password, + mac, + admin: false, + displayname: displayName, + }, + }); + }) + .then((response) => ({ + homeServer: response.body.home_server, + accessToken: response.body.access_token, + userId: response.body.user_id, + deviceId: response.body.device_id, + })); } Cypress.Commands.add("startSynapse", startSynapse); diff --git a/cypress/support/timeline.ts b/cypress/support/timeline.ts index 28a9705fdb3..1c4bcdc05fc 100644 --- a/cypress/support/timeline.ts +++ b/cypress/support/timeline.ts @@ -38,17 +38,19 @@ export interface Message { } Cypress.Commands.add("scrollToTop", (): void => { - cy.get(".mx_RoomView_timeline .mx_ScrollPanel").scrollTo("top", { duration: 100 }).then(ref => { - if (ref.scrollTop() > 0) { - return cy.scrollToTop(); - } - }); + cy.get(".mx_RoomView_timeline .mx_ScrollPanel") + .scrollTo("top", { duration: 100 }) + .then((ref) => { + if (ref.scrollTop() > 0) { + return cy.scrollToTop(); + } + }); }); Cypress.Commands.add("findEventTile", (sender: string, body: string): Chainable => { // We can't just use a bunch of `.contains` here due to continuations meaning that the events don't // have their own rendered sender displayname so we have to walk the list to keep track of the sender. - return cy.get(".mx_RoomView_MessageList .mx_EventTile").then(refs => { + return cy.get(".mx_RoomView_MessageList .mx_EventTile").then((refs) => { let latestSender: string; for (let i = 0; i < refs.length; i++) { const ref = refs.eq(i); @@ -65,4 +67,4 @@ Cypress.Commands.add("findEventTile", (sender: string, body: string): Chainable< }); // Needed to make this file a module -export { }; +export {}; diff --git a/cypress/support/util.ts b/cypress/support/util.ts index e8f48b4bcc1..b86bbc27d5a 100644 --- a/cypress/support/util.ts +++ b/cypress/support/util.ts @@ -29,7 +29,7 @@ declare global { interface cy { all( - commands: T + commands: T, ): Cypress.Chainable<{ [P in keyof T]: ChainableValue }>; queue: any; } @@ -59,16 +59,16 @@ cy.all = function all(commands): Cypress.Chainable { return cy.wrap( // @see https://lodash.com/docs/4.17.15#lodash Cypress._(commands) - .map(cmd => { + .map((cmd) => { return cmd[chainStart] ? cmd[chainStart].attributes : Cypress._.find(cy.queue.get(), { - attributes: { chainerId: cmd.chainerId }, - }).attributes; + attributes: { chainerId: cmd.chainerId }, + }).attributes; }) .concat(stopCommand.attributes) .slice(1) - .map(cmd => { + .map((cmd) => { return cmd.prev.get("subject"); }) .value(), @@ -79,4 +79,4 @@ cy.all = function all(commands): Cypress.Chainable { }; // Needed to make this file a module -export { }; +export {}; diff --git a/cypress/support/views.ts b/cypress/support/views.ts index 6b853e47221..45337cd558a 100644 --- a/cypress/support/views.ts +++ b/cypress/support/views.ts @@ -70,4 +70,4 @@ Cypress.Commands.add("viewSpaceHomeByName", (name: string): Chainable { +cy.startSynapse("consent").then((result) => { synapse = result; }); ``` @@ -96,32 +102,38 @@ Synapse instance for each test suite, i.e. in `before()`, and then tear it down To later destroy your Synapse you should call `stopSynapse`, passing the SynapseInstance object you received when starting it. + ```javascript cy.stopSynapse(synapse); ``` ### Synapse Config Templates + When a Synapse instance is started, it's given a config generated from one of the config templates in `cypress/plugins/synapsedocker/templates`. There are a couple of special files in these templates: - * `homeserver.yaml`: - Template substitution happens in this file. Template variables are: - * `REGISTRATION_SECRET`: The secret used to register users via the REST API. - * `MACAROON_SECRET_KEY`: Generated each time for security - * `FORM_SECRET`: Generated each time for security - * `PUBLIC_BASEURL`: The localhost url + port combination the synapse is accessible at - * `localhost.signing.key`: A signing key is auto-generated and saved to this file. - Config templates should not contain a signing key and instead assume that one will exist - in this file. + +- `homeserver.yaml`: + Template substitution happens in this file. Template variables are: + - `REGISTRATION_SECRET`: The secret used to register users via the REST API. + - `MACAROON_SECRET_KEY`: Generated each time for security + - `FORM_SECRET`: Generated each time for security + - `PUBLIC_BASEURL`: The localhost url + port combination the synapse is accessible at +- `localhost.signing.key`: A signing key is auto-generated and saved to this file. + Config templates should not contain a signing key and instead assume that one will exist + in this file. All other files in the template are copied recursively to `/data/`, so the file `foo.html` in a template can be referenced in the config as `/data/foo.html`. ### Logging In + There exists a basic utility to start the app with a random user already logged in: + ```javascript cy.initTestUser(synapse, "Jeff"); ``` + It takes the SynapseInstance you received from `startSynapse` and a display name for your test user. This custom command will register a random userId using the registrationSecret with a random password and the given display name. The returned Chainable will contain details about the credentials for if @@ -132,20 +144,24 @@ The internals of how this custom command run may be swapped out later, but the signature can be maintained for simpler maintenance. ### Joining a Room + Many tests will also want to start with the client in a room, ready to send & receive messages. Best way to do this may be to get an access token for the user and use this to create a room with the REST API before logging the user in. You can make use of `cy.getBot(synapse)` and `cy.getClient()` to do this. ### Convenience APIs + We should probably end up with convenience APIs that wrap the synapse creation, logging in and room creation that can be called to set up tests. ### Using matrix-js-sdk + Due to the way we run the Cypress tests in CI, at this time you can only use the matrix-js-sdk module exposed on `window.matrixcs`. This has the limitation that it is only accessible with the app loaded. This may be revisited in the future. ## Good Test Hygiene + This section mostly summarises general good Cypress testing practice, and should not be news to anyone already familiar with Cypress. @@ -158,11 +174,11 @@ already familiar with Cypress. 1. Avoid explicit waits. `cy.get()` will implicitly wait for the specified element to appear and all assertions are retired until they either pass or time out, so you should never need to manually wait for an element. - * For example, for asserting about editing an already-edited message, you can't wait for the + - For example, for asserting about editing an already-edited message, you can't wait for the 'edited' element to appear as there was already one there, but you can assert that the body of the message is what is should be after the second edit and this assertion will pass once it becomes true. You can then assert that the 'edited' element is still in the DOM. - * You can also wait for other things like network requests in the + - You can also wait for other things like network requests in the browser to complete (https://docs.cypress.io/guides/guides/network-requests#Waiting). Needing to wait for things can also be because of race conditions in the app itself, which ideally shouldn't be there! @@ -171,6 +187,7 @@ This is a small selection - the Cypress best practices guide, linked above, has should generally try to adhere to them. ## Percy Visual Testing + We also support visual testing via Percy, this extracts the DOM from Cypress and renders it using custom renderers for Safari, Firefox, Chrome & Edge, allowing us to spot visual regressions before they become release regressions. Each `cy.percySnapshot()` call results in 8 screenshots (4 browsers, 2 sizes) this can quickly be exhausted and @@ -178,4 +195,3 @@ so we only run Percy testing on `develop` and PRs which are labelled `X-Needs-Pe To record a snapshot use `cy.percySnapshot()`, you may have to pass `percyCSS` into the 2nd argument to hide certain elements which contain dynamic/generated data to avoid them cause false positives in the Percy screenshot diffs. - diff --git a/docs/features/composer.md b/docs/features/composer.md index 112a597001b..408c78a8d9c 100644 --- a/docs/features/composer.md +++ b/docs/features/composer.md @@ -1,37 +1,38 @@ # Composer Features + ## Auto Complete - - Hitting tab tries to auto-complete the word before the caret as a room member - - If no matching name is found, a visual bell is shown - - @ + a letter opens auto complete for members starting with the given letter - - When inserting a user pill at the start in the composer, a colon and space is appended to the pill - - When inserting a user pill anywhere else in composer, only a space is appended to the pill - - # + a letter opens auto complete for rooms starting with the given letter - - : open auto complete for emoji - - Pressing arrow-up/arrow-down while the autocomplete is open navigates between auto complete options - - Pressing tab while the autocomplete is open goes to the next autocomplete option, +- Hitting tab tries to auto-complete the word before the caret as a room member + - If no matching name is found, a visual bell is shown +- @ + a letter opens auto complete for members starting with the given letter + - When inserting a user pill at the start in the composer, a colon and space is appended to the pill + - When inserting a user pill anywhere else in composer, only a space is appended to the pill +- # + a letter opens auto complete for rooms starting with the given letter +- : open auto complete for emoji +- Pressing arrow-up/arrow-down while the autocomplete is open navigates between auto complete options +- Pressing tab while the autocomplete is open goes to the next autocomplete option, wrapping around at the end after reverting to the typed text first. ## Formatting - - When selecting text, a formatting bar appears above the selection. - - The formatting bar allows to format the selected test as: +- When selecting text, a formatting bar appears above the selection. +- The formatting bar allows to format the selected test as: bold, italic, strikethrough, a block quote, and a code block (inline if no linebreak is selected). - - Formatting is applied as markdown syntax. - - Hitting ctrl/cmd+B also marks the selected text as bold - - Hitting ctrl/cmd+I also marks the selected text as italic - - Hitting ctrl/cmd+> also marks the selected text as a blockquote +- Formatting is applied as markdown syntax. +- Hitting ctrl/cmd+B also marks the selected text as bold +- Hitting ctrl/cmd+I also marks the selected text as italic +- Hitting ctrl/cmd+> also marks the selected text as a blockquote ## Misc - - When hitting the arrow-up button while having the caret at the start in the composer, +- When hitting the arrow-up button while having the caret at the start in the composer, the last message sent by the syncing user is edited. - - Clicking a display name on an event in the timeline inserts a user pill into the composer - - Emoticons (like :-), >:-), :-/, ...) are replaced by emojis while typing if the relevant setting is enabled - - Typing in the composer sends typing notifications in the room - - Pressing ctrl/mod+z and ctrl/mod+y undoes/redoes modifications - - Pressing shift+enter inserts a line break - - Pressing enter sends the message. - - Choosing "Quote" in the context menu of an event inserts a quote of the event body in the composer. - - Choosing "Reply" in the context menu of an event shows a preview above the composer to reply to. - - Pressing alt+arrow up/arrow down navigates in previously sent messages, putting them in the composer. \ No newline at end of file +- Clicking a display name on an event in the timeline inserts a user pill into the composer +- Emoticons (like :-), >:-), :-/, ...) are replaced by emojis while typing if the relevant setting is enabled +- Typing in the composer sends typing notifications in the room +- Pressing ctrl/mod+z and ctrl/mod+y undoes/redoes modifications +- Pressing shift+enter inserts a line break +- Pressing enter sends the message. +- Choosing "Quote" in the context menu of an event inserts a quote of the event body in the composer. +- Choosing "Reply" in the context menu of an event shows a preview above the composer to reply to. +- Pressing alt+arrow up/arrow down navigates in previously sent messages, putting them in the composer. diff --git a/docs/features/keyboardShortcuts.md b/docs/features/keyboardShortcuts.md index 349535918b3..8d5f73d96b7 100644 --- a/docs/features/keyboardShortcuts.md +++ b/docs/features/keyboardShortcuts.md @@ -17,7 +17,7 @@ Let's say we want to close a menu when the correct keys were pressed: ```ts const onKeyDown = (ev: KeyboardEvent): void => { let handled = true; - const action = getKeyBindingManager().getAccessibilityAction(ev) + const action = getKeyBindingManager().getAccessibilityAction(ev); switch (action) { case KeyBindingAction.Escape: closeMenu(); @@ -26,12 +26,12 @@ const onKeyDown = (ev: KeyboardEvent): void => { handled = false; break; } - + if (handled) { ev.preventDefault(); ev.stopPropagation(); } -} +}; ``` ## Managing keyboard shortcuts diff --git a/docs/icons.md b/docs/icons.md index 36ea54c5505..ef02e681a29 100644 --- a/docs/icons.md +++ b/docs/icons.md @@ -6,6 +6,7 @@ Each .svg exports a `ReactComponent` at the named export `Icon`. Icons have `role="presentation"` and `aria-hidden` automatically applied. These can be overriden by passing props to the icon component. eg + ``` import { Icon as FavoriteIcon } from 'res/img/element-icons/favorite.svg'; diff --git a/docs/jitsi.md b/docs/jitsi.md index 2b63ce0f726..a931ffe6ef6 100644 --- a/docs/jitsi.md +++ b/docs/jitsi.md @@ -6,23 +6,25 @@ instructions on setting up Jitsi. The react-sdk wraps all Jitsi call widgets in a local wrapper called `jitsi.html` which takes several parameters: -*Query string*: -* `widgetId`: The ID of the widget. This is needed for communication back to the - react-sdk. -* `parentUrl`: The URL of the parent window. This is also needed for - communication back to the react-sdk. - -*Hash/fragment (formatted as a query string)*: -* `conferenceDomain`: The domain to connect Jitsi Meet to. -* `conferenceId`: The room or conference ID to connect Jitsi Meet to. -* `isAudioOnly`: Boolean for whether this is a voice-only conference. May not - be present, should default to `false`. -* `displayName`: The display name of the user viewing the widget. May not - be present or could be null. -* `avatarUrl`: The HTTP(S) URL for the avatar of the user viewing the widget. May - not be present or could be null. -* `userId`: The MXID of the user viewing the widget. May not be present or could - be null. +_Query string_: + +- `widgetId`: The ID of the widget. This is needed for communication back to the + react-sdk. +- `parentUrl`: The URL of the parent window. This is also needed for + communication back to the react-sdk. + +_Hash/fragment (formatted as a query string)_: + +- `conferenceDomain`: The domain to connect Jitsi Meet to. +- `conferenceId`: The room or conference ID to connect Jitsi Meet to. +- `isAudioOnly`: Boolean for whether this is a voice-only conference. May not + be present, should default to `false`. +- `displayName`: The display name of the user viewing the widget. May not + be present or could be null. +- `avatarUrl`: The HTTP(S) URL for the avatar of the user viewing the widget. May + not be present or could be null. +- `userId`: The MXID of the user viewing the widget. May not be present or could + be null. The react-sdk will assume that `jitsi.html` is at the path of wherever it is currently being served. For example, `https://develop.element.io/jitsi.html` or `vector://webapp/jitsi.html`. diff --git a/docs/local-echo-dev.md b/docs/local-echo-dev.md index e4725a9b071..c880ea70836 100644 --- a/docs/local-echo-dev.md +++ b/docs/local-echo-dev.md @@ -10,7 +10,7 @@ implementation, such as the `RoomEchoChamber` (which handles echoable details of Anything that can be locally echoed will be provided by the `GenericEchoChamber` implementation. The echo chamber will also need to deal with external changes, and has full control over whether -or not something has successfully been echoed. +or not something has successfully been echoed. An `EchoContext` is provided to echo chambers (usually with a matching type: `RoomEchoContext` gets provided to a `RoomEchoChamber` for example) with details about their intended area of @@ -21,7 +21,7 @@ The `EchoStore` manages echo chamber instances, builds contexts, and is generall accessible than the `EchoChamber` class. For separation of concerns, and to try and keep things tidy, this is an intentional design decision. -**Note**: The local echo stack uses a "whenable" pattern, which is similar to thenables and +**Note**: The local echo stack uses a "whenable" pattern, which is similar to thenables and `EventEmitter`. Whenables are ways of actioning a changing condition without having to deal with listeners being torn down. Once the reference count of the Whenable causes garbage collection, the Whenable's listeners will also be torn down. This is accelerated by the `IDestroyable` interface @@ -36,4 +36,3 @@ mechanisms. The `EchoStore` is responsible for ensuring that the appropriate non-urgent toast (lower left) is set up, where the dialog then drives through the contexts and transactions. - diff --git a/docs/room-list-store.md b/docs/room-list-store.md index 6fc5f711247..b87bf5f7bd2 100644 --- a/docs/room-list-store.md +++ b/docs/room-list-store.md @@ -5,11 +5,12 @@ It's so complicated it needs its own README. ![](img/RoomListStore2.png) Legend: -* Orange = External event. -* Purple = Deterministic flow. -* Green = Algorithm definition. -* Red = Exit condition/point. -* Blue = Process definition. + +- Orange = External event. +- Purple = Deterministic flow. +- Green = Algorithm definition. +- Red = Exit condition/point. +- Blue = Process definition. ## Algorithms involved @@ -22,7 +23,6 @@ Behaviour of the overall room list (sticky rooms, etc) are determined by the gen class. Here is where much of the coordination from the room list store is done to figure out which list algorithm to call, instead of having all the logic in the room list store itself. - Tag sorting is effectively the comparator supplied to the list algorithm. This gives the list algorithm the power to decide when and how to apply the tag sorting, if at all. For example, the importance algorithm, later described in this document, heavily uses the list ordering behaviour to break the tag into categories. @@ -68,14 +68,14 @@ simply get the manual sorting algorithm applied to them with no further involvem algorithm. There are 4 categories: Red, Grey, Bold, and Idle. Each has their own definition based off relative (perceived) importance to the user: -* **Red**: The room has unread mentions waiting for the user. -* **Grey**: The room has unread notifications waiting for the user. Notifications are simply unread - messages which cause a push notification or badge count. Typically, this is the default as rooms get - set to 'All Messages'. -* **Bold**: The room has unread messages waiting for the user. Essentially this is a grey room without - a badge/notification count (or 'Mentions Only'/'Muted'). -* **Idle**: No useful (see definition of useful above) activity has occurred in the room since the user - last read it. +- **Red**: The room has unread mentions waiting for the user. +- **Grey**: The room has unread notifications waiting for the user. Notifications are simply unread + messages which cause a push notification or badge count. Typically, this is the default as rooms get + set to 'All Messages'. +- **Bold**: The room has unread messages waiting for the user. Essentially this is a grey room without + a badge/notification count (or 'Mentions Only'/'Muted'). +- **Idle**: No useful (see definition of useful above) activity has occurred in the room since the user + last read it. Conveniently, each tag gets ordered by those categories as presented: red rooms appear above grey, grey above bold, etc. diff --git a/docs/scrolling.md b/docs/scrolling.md index 9c5edc5f345..5d88fb8e58f 100644 --- a/docs/scrolling.md +++ b/docs/scrolling.md @@ -8,7 +8,6 @@ During an onscroll event, we check whether we're getting close to the top or bot ScrollPanel supports a mode to prevent it shrinking. This is used to prevent a jump when at the bottom of the timeline and people start and stop typing. It gets cleared automatically when 200px above the bottom of the timeline. - ## BACAT (Bottom-Aligned, Clipped-At-Top) scrolling BACAT scrolling implements a different way of restoring the scroll position in the timeline while tiles out of view are changing height or tiles are being added or removed. It was added in https://github.com/matrix-org/matrix-react-sdk/pull/2842. diff --git a/docs/settings.md b/docs/settings.md index 884e4c8350a..4222c86e064 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -5,23 +5,22 @@ different values for a setting at particular levels of interest. For example, a they want URL previews off, but in all other rooms they want them enabled. The `SettingsStore` helps mask the complexity of dealing with the different levels and exposes easy to use getters and setters. - ## Levels Granular Settings rely on a series of known levels in order to use the correct value for the scenario. These levels, in order of priority, are: -* `device` - The current user's device -* `room-device` - The current user's device, but only when in a specific room -* `room-account` - The current user's account, but only when in a specific room -* `account` - The current user's account -* `room` - A specific room (setting for all members of the room) -* `config` - Values are defined by the `setting_defaults` key (usually) in `config.json` -* `default` - The hardcoded default for the settings + +- `device` - The current user's device +- `room-device` - The current user's device, but only when in a specific room +- `room-account` - The current user's account, but only when in a specific room +- `account` - The current user's account +- `room` - A specific room (setting for all members of the room) +- `config` - Values are defined by the `setting_defaults` key (usually) in `config.json` +- `default` - The hardcoded default for the settings Individual settings may control which levels are appropriate for them as part of the defaults. This is often to ensure that room administrators cannot force account-only settings upon participants. - ## Settings Settings are the different options a user may set or experience in the application. These are pre-defined in @@ -29,6 +28,7 @@ Settings are the different options a user may set or experience in the applicati Settings that support the config level can be set in the config file under the `setting_defaults` key (note that some settings, like the "theme" setting, are special cased in the config file): + ```json5 { ... @@ -56,13 +56,14 @@ target level. Values are defined at particular levels and should be done in a safe manner. There are two checks to perform to ensure a clean save: is the level supported and can the user actually set the value. In most cases, neither should be an issue although there are circumstances where this changes. An example of a safe call is: + ```javascript const isSupported = SettingsStore.isLevelSupported(SettingLevel.ROOM); if (isSupported) { - const canSetValue = SettingsStore.canSetValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM); - if (canSetValue) { - SettingsStore.setValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM, newValue); - } + const canSetValue = SettingsStore.canSetValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM); + if (canSetValue) { + SettingsStore.setValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM, newValue); + } } ``` @@ -73,19 +74,14 @@ instance, the component which allows changing the setting may be hidden conditio Where possible, the `SettingsFlag` component should be used to set simple "flip-a-bit" (true/false) settings. The `SettingsFlag` also supports simple radio button options, such as the theme the user would like to use. + ```html - + ``` ### Getting the display name for a setting @@ -93,16 +89,16 @@ Where possible, the `SettingsFlag` component should be used to set simple "flip- Simply call `SettingsStore.getDisplayName`. The appropriate display name will be returned and automatically translated for you. If a display name cannot be found, it will return `null`. - ## Features Feature flags are just like regular settings with some underlying semantics for how they are meant to be used. Usually a feature flag is used when a portion of the application is under development or not ready for full release yet, such -as new functionality or experimental ideas. In these cases, the feature name *should* be named with the `feature_*` +as new functionality or experimental ideas. In these cases, the feature name _should_ be named with the `feature_*` convention and must be tagged with `isFeature: true` in the setting definition. By doing so, the feature will automatically appear in the "labs" section of the user's settings. Features can be controlled at the config level using the following structure: + ```json "features": { "feature_lazyloading": true @@ -144,7 +140,6 @@ additional steps to actually enable notifications. For more information, see `src/settings/controllers/SettingController.ts`. - ## Local echo `SettingsStore` will perform local echo on all settings to ensure that immediately getting values does not cause a @@ -160,7 +155,6 @@ SettingsStore.setValue(...).then(() => { SettingsStore.getValue(...); // this will return the value set in `setValue` above. ``` - ## Watching for changes Most use cases do not need to set up a watcher because they are able to react to changes as they are made, or the @@ -174,12 +168,11 @@ An example of a watcher in action would be: ```javascript class MyComponent extends React.Component { - settingWatcherRef = null; componentWillMount() { const callback = (settingName, roomId, level, newValAtLevel, newVal) => { - this.setState({color: newVal}); + this.setState({ color: newVal }); }; this.settingWatcherRef = SettingsStore.watchSetting("roomColor", "!example:matrix.org", callback); } @@ -190,7 +183,6 @@ class MyComponent extends React.Component { } ``` - # Maintainers Reference The granular settings system has a few complex parts to power it. This section is to document how the `SettingsStore` is diff --git a/docs/usercontent.md b/docs/usercontent.md index db0e34e5faa..cba488b1152 100644 --- a/docs/usercontent.md +++ b/docs/usercontent.md @@ -13,12 +13,12 @@ It exposes a function over a postMessage API, when sent an object with the match ```json5 { - "imgSrc": "", // the src of the image to display in the download link - "imgStyle": "", // the style to apply to the image - "style": "", // the style to apply to the download link - "download": "", // download attribute to pass to the tag - "textContent": "", // the text to put inside the download link - "blob": "", // the data blob to wrap in an object url and allow the user to download + imgSrc: "", // the src of the image to display in the download link + imgStyle: "", // the style to apply to the image + style: "", // the style to apply to the download link + download: "", // download attribute to pass to the tag + textContent: "", // the text to put inside the download link + blob: "", // the data blob to wrap in an object url and allow the user to download } ``` diff --git a/docs/widget-layouts.md b/docs/widget-layouts.md index e7f72e2001e..f2a837c2a84 100644 --- a/docs/widget-layouts.md +++ b/docs/widget-layouts.md @@ -4,24 +4,25 @@ Rooms can have a default widget layout to auto-pin certain widgets, make the con sizes, etc. These are defined through the `io.element.widgets.layout` state event (empty state key). Full example content: + ```json5 { - "widgets": { - "first-widget-id": { - "container": "top", - "index": 0, - "width": 60, - "height": 40 + widgets: { + "first-widget-id": { + container: "top", + index: 0, + width: 60, + height: 40, + }, + "second-widget-id": { + container: "right", + }, }, - "second-widget-id": { - "container": "right" - } - } } ``` As shown, there are two containers possible for widgets. These containers have different behaviour -and interpret the other options differently. +and interpret the other options differently. ## `top` container @@ -32,7 +33,7 @@ therefore fewer messages can be shown). The `index` for a widget determines which order the widgets show up in from left to right. Widgets without an `index` will show up as the rightmost widgets. Tiebreaks (same `index` or multiple defined without an `index`) are resolved by comparing widget IDs. A maximum of 3 widgets can be in the top -container - any which exceed this will be ignored (placed into the `right` container). Smaller numbers +container - any which exceed this will be ignored (placed into the `right` container). Smaller numbers represent leftmost widgets. The `width` is relative width within the container in percentage points. This will be clamped to a @@ -43,7 +44,7 @@ attempt to show them at 33% width each. Note that the client may impose minimum widths on the widgets, such as a 10% minimum to avoid pinning hidden widgets. In general, widgets defined in the 30-70% range each will be free of these restrictions. -The `height` is not in fact applied per-widget but is recorded per-widget for potential future +The `height` is not in fact applied per-widget but is recorded per-widget for potential future capabilities in future containers. The top container will take the tallest `height` and use that for the height of the whole container, and thus all widgets in that container. The `height` is relative to the container, like with `width`, meaning that 100% will consume as much space as the client is diff --git a/package.json b/package.json index 9ce03a5fafb..2860ef98270 100644 --- a/package.json +++ b/package.json @@ -1,263 +1,262 @@ { - "name": "matrix-react-sdk", - "version": "3.62.0", - "description": "SDK for matrix.org using React", - "author": "matrix.org", - "repository": { - "type": "git", - "url": "https://github.com/matrix-org/matrix-react-sdk" - }, - "license": "Apache-2.0", - "files": [ - "lib", - "res", - "src", - "scripts", - "git-revision.txt", - "docs", - "header", - "CHANGELOG.md", - "CONTRIBUTING.rst", - "LICENSE", - "README.md", - "package.json", - ".stylelintrc.js" - ], - "main": "./lib/index.ts", - "matrix_src_main": "./src/index.ts", - "matrix_lib_main": "./lib/index.ts", - "matrix_lib_typings": "./lib/index.d.ts", - "matrix_i18n_extra_translation_funcs": [ - "newTranslatableError" - ], - "scripts": { - "prepublishOnly": "yarn build", - "i18n": "matrix-gen-i18n", - "prunei18n": "matrix-prune-i18n", - "diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && matrix-gen-i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json", - "make-component": "node scripts/make-react-component.js", - "rethemendex": "res/css/rethemendex.sh", - "clean": "rimraf lib", - "build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types", - "build:compile": "babel -d lib --verbose --extensions \".ts,.js,.tsx\" src", - "build:types": "tsc --emitDeclarationOnly --jsx react", - "start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all", - "start:all": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:build", - "start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"", - "lint": "yarn lint:types && yarn lint:js && yarn lint:style", - "lint:js": "eslint --max-warnings 0 src test cypress", - "lint:js-fix": "eslint --fix src test cypress", - "lint:types": "tsc --noEmit --jsx react && tsc --noEmit --jsx react -p cypress", - "lint:style": "stylelint \"res/css/**/*.pcss\"", - "test": "jest", - "test:cypress": "cypress run", - "test:cypress:open": "cypress open", - "coverage": "yarn test --coverage" - }, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@matrix-org/analytics-events": "^0.3.0", - "@matrix-org/matrix-wysiwyg": "^0.6.0", - "@matrix-org/react-sdk-module-api": "^0.0.3", - "@sentry/browser": "^6.11.0", - "@sentry/tracing": "^6.11.0", - "@testing-library/react-hooks": "^8.0.1", - "@types/geojson": "^7946.0.8", - "@types/ua-parser-js": "^0.7.36", - "await-lock": "^2.1.0", - "blurhash": "^1.1.3", - "cheerio": "^1.0.0-rc.9", - "classnames": "^2.2.6", - "commonmark": "^0.29.3", - "counterpart": "^0.18.6", - "diff-dom": "^4.2.2", - "diff-match-patch": "^1.0.5", - "emojibase": "6.0.2", - "emojibase-data": "7.0.0", - "emojibase-regex": "6.0.0", - "escape-html": "^1.0.3", - "file-saver": "^2.0.5", - "filesize": "6.1.0", - "flux": "2.1.1", - "focus-visible": "^5.2.0", - "gfm.css": "^1.1.2", - "glob-to-regexp": "^0.4.1", - "highlight.js": "^11.3.1", - "html-entities": "^1.4.0", - "is-ip": "^3.1.0", - "jszip": "^3.7.0", - "katex": "^0.12.0", - "linkify-element": "4.0.0-beta.4", - "linkify-string": "4.0.0-beta.4", - "linkifyjs": "4.0.0-beta.4", - "lodash": "^4.17.20", - "maplibre-gl": "^1.15.2", - "matrix-encrypt-attachment": "^1.0.3", - "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "22.0.0", - "matrix-widget-api": "^1.1.1", - "minimist": "^1.2.5", - "opus-recorder": "^8.0.3", - "pako": "^2.0.3", - "parse5": "^6.0.1", - "png-chunks-extract": "^1.0.0", - "posthog-js": "1.12.2", - "prop-types": "^15.7.2", - "qrcode": "1.4.4", - "re-resizable": "^6.9.0", - "react": "17.0.2", - "react-beautiful-dnd": "^13.1.0", - "react-blurhash": "^0.1.3", - "react-dom": "17.0.2", - "react-focus-lock": "^2.5.1", - "react-lite-youtube-embed": "^2.2.1-a", - "react-transition-group": "^4.4.1", - "rfc4648": "^1.4.0", - "sanitize-filename": "^1.6.3", - "sanitize-html": "^2.3.2", - "tar-js": "^0.3.0", - "ua-parser-js": "^1.0.2", - "url": "^0.11.0", - "what-input": "^5.2.10", - "zxcvbn": "^4.4.2" - }, - "devDependencies": { - "@babel/cli": "^7.12.10", - "@babel/core": "^7.12.10", - "@babel/eslint-parser": "^7.12.10", - "@babel/eslint-plugin": "^7.12.10", - "@babel/parser": "^7.12.11", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-export-default-from": "^7.12.1", - "@babel/plugin-proposal-numeric-separator": "^7.12.7", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-transform-runtime": "^7.12.10", - "@babel/preset-env": "^7.12.11", - "@babel/preset-react": "^7.12.10", - "@babel/preset-typescript": "^7.12.7", - "@babel/register": "^7.12.10", - "@babel/traverse": "^7.12.12", - "@casualbot/jest-sonar-reporter": "^2.2.5", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz", - "@peculiar/webcrypto": "^1.4.1", - "@percy/cli": "^1.11.0", - "@percy/cypress": "^3.1.2", - "@sentry/types": "^6.10.0", - "@sinonjs/fake-timers": "^9.1.2", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^12.1.5", - "@testing-library/user-event": "^14.4.3", - "@types/classnames": "^2.2.11", - "@types/commonmark": "^0.27.4", - "@types/counterpart": "^0.18.1", - "@types/css-font-loading-module": "^0.0.6", - "@types/diff-match-patch": "^1.0.32", - "@types/enzyme": "^3.10.9", - "@types/escape-html": "^1.0.1", - "@types/file-saver": "^2.0.3", - "@types/flux": "^3.1.9", - "@types/fs-extra": "^9.0.13", - "@types/jest": "^29.2.1", - "@types/katex": "^0.14.0", - "@types/lodash": "^4.14.168", - "@types/modernizr": "^3.5.3", - "@types/node": "^14.18.28", - "@types/pako": "^1.0.1", - "@types/parse5": "^6.0.0", - "@types/qrcode": "^1.3.5", - "@types/react": "^17.0.49", - "@types/react-beautiful-dnd": "^13.0.0", - "@types/react-dom": "^17.0.17", - "@types/react-test-renderer": "^17.0.1", - "@types/react-transition-group": "^4.4.0", - "@types/sanitize-html": "^2.3.1", - "@types/zxcvbn": "^4.4.0", - "@typescript-eslint/eslint-plugin": "^5.35.1", - "@typescript-eslint/parser": "^5.6.0", - "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", - "allchange": "^1.1.0", - "axe-core": "^4.4.3", - "babel-jest": "^26.6.3", - "blob-polyfill": "^6.0.20211015", - "chokidar": "^3.5.1", - "cypress": "^10.3.0", - "cypress-axe": "^1.0.0", - "cypress-real-events": "^1.7.1", - "enzyme": "^3.11.0", - "enzyme-to-json": "^3.6.2", - "eslint": "8.9.0", - "eslint-config-google": "^0.14.0", - "eslint-plugin-deprecate": "^0.7.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-matrix-org": "^0.7.0", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-unicorn": "^44.0.2", - "fetch-mock-jest": "^1.5.1", - "fs-extra": "^10.0.1", - "glob": "^7.1.6", - "jest": "^29.2.2", - "jest-canvas-mock": "^2.3.0", - "jest-environment-jsdom": "^29.2.2", - "jest-mock": "^29.2.2", - "jest-raw-loader": "^1.0.1", - "matrix-mock-request": "^2.5.0", - "matrix-web-i18n": "^1.3.0", - "node-fetch": "2", - "postcss-scss": "^4.0.4", - "raw-loader": "^4.0.2", - "react-test-renderer": "^17.0.2", - "rimraf": "^3.0.2", - "rrweb-snapshot": "1.1.7", - "stylelint": "^14.9.1", - "stylelint-config-standard": "^26.0.0", - "stylelint-scss": "^4.2.0", - "typescript": "4.8.4", - "walk": "^2.3.14" - }, - "jest": { - "snapshotSerializers": [ - "enzyme-to-json/serializer" - ], - "testEnvironment": "jsdom", - "testMatch": [ - "/test/**/*-test.[jt]s?(x)" - ], - "globalSetup": "/test/globalSetup.js", - "setupFiles": [ - "jest-canvas-mock" - ], - "setupFilesAfterEnv": [ - "/test/setupTests.js" - ], - "moduleNameMapper": { - "\\.(gif|png|ttf|woff2)$": "/__mocks__/imageMock.js", - "\\.svg$": "/__mocks__/svg.js", - "\\$webapp/i18n/languages.json": "/__mocks__/languages.json", - "decoderWorker\\.min\\.js": "/__mocks__/empty.js", - "decoderWorker\\.min\\.wasm": "/__mocks__/empty.js", - "waveWorker\\.min\\.js": "/__mocks__/empty.js", - "workers/(.+)\\.worker\\.ts": "/__mocks__/workerMock.js", - "^!!raw-loader!.*": "jest-raw-loader", - "RecorderWorklet": "/__mocks__/empty.js" + "name": "matrix-react-sdk", + "version": "3.63.0", + "description": "SDK for matrix.org using React", + "author": "matrix.org", + "repository": { + "type": "git", + "url": "https://github.com/matrix-org/matrix-react-sdk" }, - "transformIgnorePatterns": [ - "/node_modules/(?!matrix-js-sdk).+$" - ], - "collectCoverageFrom": [ - "/src/**/*.{js,ts,tsx}" + "license": "Apache-2.0", + "files": [ + "lib", + "res", + "src", + "scripts", + "git-revision.txt", + "docs", + "header", + "CHANGELOG.md", + "CONTRIBUTING.rst", + "LICENSE", + "README.md", + "package.json", + ".stylelintrc.js" ], - "coverageReporters": [ - "text-summary", - "lcov" + "main": "./lib/index.ts", + "matrix_src_main": "./src/index.ts", + "matrix_lib_main": "./lib/index.ts", + "matrix_lib_typings": "./lib/index.d.ts", + "matrix_i18n_extra_translation_funcs": [ + "newTranslatableError" ], - "testResultsProcessor": "@casualbot/jest-sonar-reporter" - }, - "@casualbot/jest-sonar-reporter": { - "outputDirectory": "coverage", - "outputName": "jest-sonar-report.xml", - "relativePaths": true - }, - "typings": "./lib/index.d.ts" + "scripts": { + "prepublishOnly": "yarn build", + "i18n": "matrix-gen-i18n", + "prunei18n": "matrix-prune-i18n", + "diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && matrix-gen-i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json", + "make-component": "node scripts/make-react-component.js", + "rethemendex": "res/css/rethemendex.sh", + "clean": "rimraf lib", + "build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types", + "build:compile": "babel -d lib --verbose --extensions \".ts,.js,.tsx\" src", + "build:types": "tsc --emitDeclarationOnly --jsx react", + "start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all", + "start:all": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:build", + "start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"", + "lint": "yarn lint:types && yarn lint:js && yarn lint:style", + "lint:js": "eslint --max-warnings 0 src test cypress && prettier --check .", + "lint:js-fix": "prettier --loglevel=warn --write . && eslint --fix src test cypress", + "lint:types": "tsc --noEmit --jsx react && tsc --noEmit --jsx react -p cypress", + "lint:style": "stylelint \"res/css/**/*.pcss\"", + "test": "jest", + "test:cypress": "cypress run", + "test:cypress:open": "cypress open", + "coverage": "yarn test --coverage" + }, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@matrix-org/analytics-events": "^0.3.0", + "@matrix-org/matrix-wysiwyg": "^0.9.0", + "@matrix-org/react-sdk-module-api": "^0.0.3", + "@sentry/browser": "^7.0.0", + "@sentry/tracing": "^7.0.0", + "@testing-library/react-hooks": "^8.0.1", + "await-lock": "^2.1.0", + "blurhash": "^1.1.3", + "cheerio": "^1.0.0-rc.9", + "classnames": "^2.2.6", + "commonmark": "^0.30.0", + "counterpart": "^0.18.6", + "diff-dom": "^4.2.2", + "diff-match-patch": "^1.0.5", + "emojibase": "6.1.0", + "emojibase-data": "7.0.1", + "emojibase-regex": "6.0.1", + "escape-html": "^1.0.3", + "file-saver": "^2.0.5", + "filesize": "10.0.5", + "flux": "4.0.3", + "focus-visible": "^5.2.0", + "gfm.css": "^1.1.2", + "glob-to-regexp": "^0.4.1", + "highlight.js": "^11.3.1", + "html-entities": "^2.0.0", + "is-ip": "^3.1.0", + "jszip": "^3.7.0", + "katex": "^0.16.0", + "linkify-element": "4.0.0-beta.4", + "linkify-string": "4.0.0-beta.4", + "linkifyjs": "4.0.0-beta.4", + "lodash": "^4.17.20", + "maplibre-gl": "^1.15.2", + "matrix-encrypt-attachment": "^1.0.3", + "matrix-events-sdk": "0.0.1", + "matrix-js-sdk": "23.0.0", + "matrix-widget-api": "^1.1.1", + "minimist": "^1.2.5", + "opus-recorder": "^8.0.3", + "pako": "^2.0.3", + "parse5": "^6.0.1", + "png-chunks-extract": "^1.0.0", + "posthog-js": "1.36.0", + "qrcode": "1.5.1", + "re-resizable": "^6.9.0", + "react": "17.0.2", + "react-beautiful-dnd": "^13.1.0", + "react-blurhash": "^0.2.0", + "react-dom": "17.0.2", + "react-focus-lock": "^2.5.1", + "react-transition-group": "^4.4.1", + "rfc4648": "^1.4.0", + "sanitize-filename": "^1.6.3", + "sanitize-html": "^2.3.2", + "tar-js": "^0.3.0", + "ua-parser-js": "^1.0.2", + "url": "^0.11.0", + "what-input": "^5.2.10", + "zxcvbn": "^4.4.2" + }, + "devDependencies": { + "@babel/cli": "^7.12.10", + "@babel/core": "^7.12.10", + "@babel/eslint-parser": "^7.12.10", + "@babel/eslint-plugin": "^7.12.10", + "@babel/parser": "^7.12.11", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-export-default-from": "^7.12.1", + "@babel/plugin-proposal-numeric-separator": "^7.12.7", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-transform-runtime": "^7.12.10", + "@babel/preset-env": "^7.12.11", + "@babel/preset-react": "^7.12.10", + "@babel/preset-typescript": "^7.12.7", + "@babel/register": "^7.12.10", + "@babel/traverse": "^7.12.12", + "@casualbot/jest-sonar-reporter": "^2.2.5", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz", + "@peculiar/webcrypto": "^1.4.1", + "@percy/cli": "^1.11.0", + "@percy/cypress": "^3.1.2", + "@sinonjs/fake-timers": "^9.1.2", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^14.4.3", + "@types/classnames": "^2.2.11", + "@types/commonmark": "^0.27.4", + "@types/counterpart": "^0.18.1", + "@types/css-font-loading-module": "^0.0.7", + "@types/diff-match-patch": "^1.0.32", + "@types/enzyme": "^3.10.9", + "@types/escape-html": "^1.0.1", + "@types/file-saver": "^2.0.3", + "@types/flux": "^3.1.9", + "@types/fs-extra": "^9.0.13", + "@types/geojson": "^7946.0.8", + "@types/jest": "^29.2.1", + "@types/katex": "^0.14.0", + "@types/lodash": "^4.14.168", + "@types/modernizr": "^3.5.3", + "@types/node": "^16", + "@types/pako": "^2.0.0", + "@types/parse5": "^6.0.0", + "@types/qrcode": "^1.3.5", + "@types/react": "17.0.49", + "@types/react-beautiful-dnd": "^13.0.0", + "@types/react-dom": "17.0.17", + "@types/react-test-renderer": "^17.0.1", + "@types/react-transition-group": "^4.4.0", + "@types/sanitize-html": "^2.3.1", + "@types/ua-parser-js": "^0.7.36", + "@types/zxcvbn": "^4.4.0", + "@typescript-eslint/eslint-plugin": "^5.35.1", + "@typescript-eslint/parser": "^5.6.0", + "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", + "allchange": "^1.1.0", + "axe-core": "4.4.3", + "babel-jest": "^29.0.0", + "blob-polyfill": "^7.0.0", + "chokidar": "^3.5.1", + "cypress": "^11.0.0", + "cypress-axe": "^1.0.0", + "cypress-real-events": "^1.7.1", + "enzyme": "^3.11.0", + "enzyme-to-json": "^3.6.2", + "eslint": "8.28.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-jsx-a11y": "^6.5.1", + "eslint-plugin-matrix-org": "0.9.0", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-unicorn": "^45.0.0", + "fetch-mock-jest": "^1.5.1", + "fs-extra": "^11.0.0", + "glob": "^8.0.0", + "jest": "^29.2.2", + "jest-canvas-mock": "^2.3.0", + "jest-environment-jsdom": "^29.2.2", + "jest-mock": "^29.2.2", + "jest-raw-loader": "^1.0.1", + "matrix-mock-request": "^2.5.0", + "matrix-web-i18n": "^1.3.0", + "node-fetch": "2", + "postcss-scss": "^4.0.4", + "prettier": "2.8.0", + "raw-loader": "^4.0.2", + "react-test-renderer": "^17.0.2", + "rimraf": "^3.0.2", + "stylelint": "^14.9.1", + "stylelint-config-prettier": "^9.0.4", + "stylelint-config-standard": "^29.0.0", + "stylelint-scss": "^4.2.0", + "typescript": "4.9.3", + "walk": "^2.3.14" + }, + "jest": { + "snapshotSerializers": [ + "enzyme-to-json/serializer" + ], + "testEnvironment": "jsdom", + "testMatch": [ + "/test/**/*-test.[jt]s?(x)" + ], + "globalSetup": "/test/globalSetup.js", + "setupFiles": [ + "jest-canvas-mock" + ], + "setupFilesAfterEnv": [ + "/test/setupTests.js" + ], + "moduleNameMapper": { + "\\.(gif|png|ttf|woff2)$": "/__mocks__/imageMock.js", + "\\.svg$": "/__mocks__/svg.js", + "\\$webapp/i18n/languages.json": "/__mocks__/languages.json", + "decoderWorker\\.min\\.js": "/__mocks__/empty.js", + "decoderWorker\\.min\\.wasm": "/__mocks__/empty.js", + "waveWorker\\.min\\.js": "/__mocks__/empty.js", + "workers/(.+)\\.worker\\.ts": "/__mocks__/workerMock.js", + "^!!raw-loader!.*": "jest-raw-loader", + "RecorderWorklet": "/__mocks__/empty.js" + }, + "transformIgnorePatterns": [ + "/node_modules/(?!matrix-js-sdk).+$" + ], + "collectCoverageFrom": [ + "/src/**/*.{js,ts,tsx}" + ], + "coverageReporters": [ + "text-summary", + "lcov" + ], + "testResultsProcessor": "@casualbot/jest-sonar-reporter" + }, + "@casualbot/jest-sonar-reporter": { + "outputDirectory": "coverage", + "outputName": "jest-sonar-report.xml", + "relativePaths": true + }, + "typings": "./lib/index.d.ts" } diff --git a/release_config.yaml b/release_config.yaml index 12e857cbdd1..b8ff1049424 100644 --- a/release_config.yaml +++ b/release_config.yaml @@ -1,4 +1,3 @@ subprojects: matrix-js-sdk: includeByDefault: false - diff --git a/res/css/_animations.pcss b/res/css/_animations.pcss index 01b05b5153c..fb4ce1eb57e 100644 --- a/res/css/_animations.pcss +++ b/res/css/_animations.pcss @@ -35,9 +35,15 @@ limitations under the License. } @keyframes mx--anim-pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } + 0% { + opacity: 1; + } + 50% { + opacity: 0.7; + } + 100% { + opacity: 1; + } } @keyframes mx_Dialog_lightbox_background_keyframes { diff --git a/res/css/_common.pcss b/res/css/_common.pcss index fe669ae1489..edf6aa39851 100644 --- a/res/css/_common.pcss +++ b/res/css/_common.pcss @@ -24,7 +24,7 @@ limitations under the License. @import "./_spacing.pcss"; @import url("maplibre-gl/dist/maplibre-gl.css"); -$hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); /* quadratic */ +$hover-transition: 0.08s cubic-bezier(0.46, 0.03, 0.52, 0.96); /* quadratic */ $selected-message-border-width: 4px; @@ -41,8 +41,8 @@ $timeline-image-border-radius: $border-radius-8px; :root { font-size: 10px; - --transition-short: .1s; - --transition-standard: .3s; + --transition-short: 0.1s; + --transition-standard: 0.3s; } @media (prefers-reduced-motion) { @@ -75,13 +75,16 @@ body { -moz-osx-font-smoothing: grayscale; } -pre, code { +pre, +code { font-family: $monospace-font-family; font-size: 100% !important; } -.error, .warning, -.text-error, .text-warning { +.error, +.warning, +.text-error, +.text-warning { color: $alert; } @@ -133,7 +136,7 @@ input[type="search"].mx_textinput_icon { /* FIXME THEME - Tint by CSS rather than referencing a duplicate asset */ input[type="text"].mx_textinput_icon.mx_textinput_search, input[type="search"].mx_textinput_icon.mx_textinput_search { - background-image: url('$(res)/img/feather-customised/search-input.svg'); + background-image: url("$(res)/img/feather-customised/search-input.svg"); } /* dont search UI as not all browsers support it, */ @@ -151,7 +154,9 @@ textarea::placeholder { opacity: initial; } -input[type="text"], input[type="password"], textarea { +input[type="text"], +input[type="password"], +textarea { background-color: transparent; color: $primary-content; } @@ -161,7 +166,9 @@ textarea { color: $primary-content; } -input[type="text"]:focus, input[type="password"]:focus, textarea:focus { +input[type="text"]:focus, +input[type="password"]:focus, +textarea:focus { outline: none; box-shadow: none; } @@ -198,7 +205,8 @@ legend { /* it has the appearance of a text box so the controls */ /* appear to be part of the input */ -.mx_Dialog, .mx_MatrixChat_wrapper { +.mx_Dialog, +.mx_MatrixChat_wrapper { .mx_textinput > input[type="text"], .mx_textinput > input[type="search"] { border: none; @@ -214,7 +222,7 @@ legend { background-color: transparent; color: $input-darker-fg-color; border-radius: $border-radius-4px; - border: 1px solid rgba($primary-content, .1); + border: 1px solid rgba($primary-content, 0.1); /* these things should probably not be defined globally */ margin: 9px; } @@ -227,7 +235,7 @@ legend { :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="text"]::placeholder, :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="search"]::placeholder, .mx_textinput input::placeholder { - color: rgba($input-darker-fg-color, .75); + color: rgba($input-darker-fg-color, 0.75); } } @@ -330,7 +338,7 @@ legend { pre code { white-space: pre; /* we want code blocks to be scrollable and not wrap */ - >* { + > * { display: inline; } } @@ -458,7 +466,7 @@ legend { } @define-mixin customisedCancelButton { - mask: url('$(res)/img/cancel.svg'); + mask: url("$(res)/img/cancel.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: cover; @@ -801,7 +809,7 @@ legend { align-items: center; &::before { - content: ''; + content: ""; display: inline-block; background-color: $button-fg-color; mask-position: center; @@ -822,7 +830,7 @@ legend { @define-mixin ThreadSummaryIcon { content: ""; display: inline-block; - mask-image: url('$(res)/img/element-icons/thread-summary.svg'); + mask-image: url("$(res)/img/element-icons/thread-summary.svg"); mask-position: center; mask-repeat: no-repeat; mask-size: contain; @@ -839,7 +847,7 @@ legend { } } -@define-mixin composerButton $border-radius,$hover-color { +@define-mixin composerButton $border-radius, $hover-color { --size: 26px; position: relative; cursor: pointer; @@ -850,7 +858,7 @@ legend { border-radius: $border-radius; &::before { - content: ''; + content: ""; position: absolute; top: 3px; left: 3px; @@ -863,7 +871,7 @@ legend { } &::after { - content: ''; + content: ""; position: absolute; left: 0; top: 0; diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 7594a327879..069c3aee693 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -46,6 +46,7 @@ @import "./components/views/typography/_Caption.pcss"; @import "./compound/_Icon.pcss"; @import "./structures/_AutoHideScrollbar.pcss"; +@import "./structures/_AutocompleteInput.pcss"; @import "./structures/_BackdropPanel.pcss"; @import "./structures/_CompatibilityPage.pcss"; @import "./structures/_ContextualMenu.pcss"; @@ -375,4 +376,5 @@ @import "./voice-broadcast/atoms/_PlaybackControlButton.pcss"; @import "./voice-broadcast/atoms/_VoiceBroadcastControl.pcss"; @import "./voice-broadcast/atoms/_VoiceBroadcastHeader.pcss"; +@import "./voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss"; @import "./voice-broadcast/molecules/_VoiceBroadcastBody.pcss"; diff --git a/res/css/_font-sizes.pcss b/res/css/_font-sizes.pcss index caa3a452b03..5c977a740f4 100644 --- a/res/css/_font-sizes.pcss +++ b/res/css/_font-sizes.pcss @@ -24,7 +24,7 @@ $font-6px: 0.6rem; $font-7px: 0.7rem; $font-8px: 0.8rem; $font-9px: 0.9rem; -$font-10px: 1.0rem; +$font-10px: 1rem; $font-10-4px: 1.04rem; $font-11px: 1.1rem; $font-12px: 1.2rem; @@ -35,7 +35,7 @@ $font-16px: 1.6rem; $font-17px: 1.7rem; $font-18px: 1.8rem; $font-19px: 1.9rem; -$font-20px: 2.0rem; +$font-20px: 2rem; $font-21px: 2.1rem; $font-22px: 2.2rem; $font-23px: 2.3rem; @@ -45,7 +45,7 @@ $font-26px: 2.6rem; $font-27px: 2.7rem; $font-28px: 2.8rem; $font-29px: 2.9rem; -$font-30px: 3.0rem; +$font-30px: 3rem; $font-31px: 3.1rem; $font-32px: 3.2rem; $font-33px: 3.3rem; @@ -55,7 +55,7 @@ $font-36px: 3.6rem; $font-37px: 3.7rem; $font-38px: 3.8rem; $font-39px: 3.9rem; -$font-40px: 4.0rem; +$font-40px: 4rem; $font-41px: 4.1rem; $font-42px: 4.2rem; $font-43px: 4.3rem; @@ -65,7 +65,7 @@ $font-46px: 4.6rem; $font-47px: 4.7rem; $font-48px: 4.8rem; $font-49px: 4.9rem; -$font-50px: 5.0rem; +$font-50px: 5rem; $font-51px: 5.1rem; $font-52px: 5.2rem; $font-78px: 7.8rem; diff --git a/res/css/components/views/location/_Marker.pcss b/res/css/components/views/location/_Marker.pcss index f1e0e38b558..e4fefd789c4 100644 --- a/res/css/components/views/location/_Marker.pcss +++ b/res/css/components/views/location/_Marker.pcss @@ -31,7 +31,7 @@ limitations under the License. /* caret down */ &::before { - content: ''; + content: ""; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid currentColor; diff --git a/res/css/components/views/location/_ShareDialogButtons.pcss b/res/css/components/views/location/_ShareDialogButtons.pcss index c6d77d2da84..5278e68020e 100644 --- a/res/css/components/views/location/_ShareDialogButtons.pcss +++ b/res/css/components/views/location/_ShareDialogButtons.pcss @@ -33,7 +33,8 @@ limitations under the License. position: absolute; top: $spacing-16; - &:hover, &:focus { + &:hover, + &:focus { opacity: 1; } diff --git a/res/css/structures/_AutoHideScrollbar.pcss b/res/css/structures/_AutoHideScrollbar.pcss index 88655b8b5c6..b99c0db75e6 100644 --- a/res/css/structures/_AutoHideScrollbar.pcss +++ b/res/css/structures/_AutoHideScrollbar.pcss @@ -47,7 +47,7 @@ html { .mx_AutoHideScrollbar { overflow-x: hidden; overflow-y: auto; - overflow-y: overlay; /* where supported */ + overflow-y: overlay; /* where supported */ -ms-overflow-style: -ms-autohiding-scrollbar; &::-webkit-scrollbar { diff --git a/res/css/structures/_AutocompleteInput.pcss b/res/css/structures/_AutocompleteInput.pcss new file mode 100644 index 00000000000..754c8ae1944 --- /dev/null +++ b/res/css/structures/_AutocompleteInput.pcss @@ -0,0 +1,129 @@ +/* +Copyright 2022 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. +*/ + +.mx_AutocompleteInput { + position: relative; +} + +.mx_AutocompleteInput_search_icon { + margin-left: $spacing-8; + fill: $secondary-content; +} + +.mx_AutocompleteInput_editor { + flex: 1; + display: flex; + flex-wrap: wrap; + align-items: center; + overflow-x: hidden; + overflow-y: auto; + border: 1px solid $input-border-color; + border-radius: 4px; + transition: border-color 0.25s; + + > input { + flex: 1; + min-width: 40%; + resize: none; + // `!important` is required to bypass global input styles. + margin: 0 !important; + padding: $spacing-8 9px; + border: none !important; + color: $primary-content !important; + font-weight: normal !important; + + &::placeholder { + color: $primary-content !important; + font-weight: normal !important; + } + } +} + +.mx_AutocompleteInput_editor--focused { + border-color: $links; +} + +.mx_AutocompleteInput_editor--has-suggestions { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.mx_AutocompleteInput_editor_selection { + display: flex; + margin-left: $spacing-8; +} + +.mx_AutocompleteInput_editor_selection_pill { + display: flex; + align-items: center; + border-radius: 12px; + padding-left: $spacing-8; + padding-right: $spacing-8; + background-color: $username-variant1-color; + color: #ffffff; + font-size: $font-12px; +} + +.mx_AutocompleteInput_editor_selection_remove_button { + padding: 0 $spacing-4; +} + +.mx_AutocompleteInput_matches { + position: absolute; + left: 0; + right: 0; + background-color: $background; + border: 1px solid $links; + border-top-color: $input-border-color; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + z-index: 1000; +} + +.mx_AutocompleteInput_suggestion { + display: flex; + align-items: center; + padding: $spacing-8; + cursor: pointer; + + > * { + user-select: none; + } + + &:hover { + background-color: $quinary-content; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + } +} + +.mx_AutocompleteInput_suggestion--selected { + background-color: $quinary-content; + + &:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + } +} + +.mx_AutocompleteInput_suggestion_title { + margin-right: $spacing-8; +} + +.mx_AutocompleteInput_suggestion_description { + color: $secondary-content; + font-size: $font-12px; +} diff --git a/res/css/structures/_ContextualMenu.pcss b/res/css/structures/_ContextualMenu.pcss index 07eeff1f34a..0530c967ecf 100644 --- a/res/css/structures/_ContextualMenu.pcss +++ b/res/css/structures/_ContextualMenu.pcss @@ -26,7 +26,7 @@ limitations under the License. left: 0; width: 100%; height: 100%; - opacity: 1.0; + opacity: 1; } .mx_ContextualMenu { diff --git a/res/css/structures/_FilePanel.pcss b/res/css/structures/_FilePanel.pcss index 6fe99e77600..b7fbf23d954 100644 --- a/res/css/structures/_FilePanel.pcss +++ b/res/css/structures/_FilePanel.pcss @@ -119,5 +119,5 @@ limitations under the License. } .mx_FilePanel_empty::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); + mask-image: url("$(res)/img/element-icons/room/files.svg"); } diff --git a/res/css/structures/_HomePage.pcss b/res/css/structures/_HomePage.pcss index a3cc194548f..f06fa2e35de 100644 --- a/res/css/structures/_HomePage.pcss +++ b/res/css/structures/_HomePage.pcss @@ -85,7 +85,7 @@ limitations under the License. width: 40px; height: 40px; - content: ''; + content: ""; position: absolute; background-color: #fff; /* on all themes */ mask-repeat: no-repeat; @@ -93,15 +93,15 @@ limitations under the License. } &.mx_HomePage_button_sendDm::before { - mask-image: url('$(res)/img/element-icons/feedback.svg'); + mask-image: url("$(res)/img/element-icons/feedback.svg"); } &.mx_HomePage_button_explore::before { - mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/explore.svg"); } &.mx_HomePage_button_createGroup::before { - mask-image: url('$(res)/img/element-icons/group-members.svg'); + mask-image: url("$(res)/img/element-icons/group-members.svg"); } } } diff --git a/res/css/structures/_LeftPanel.pcss b/res/css/structures/_LeftPanel.pcss index 524ed90248e..1cb123804a7 100644 --- a/res/css/structures/_LeftPanel.pcss +++ b/res/css/structures/_LeftPanel.pcss @@ -131,13 +131,13 @@ $roomListCollapsedWidth: 68px; margin-left: 8px; &::before { - content: ''; + content: ""; position: absolute; top: 8px; left: 8px; width: 16px; height: 16px; - mask-image: url('$(res)/img/element-icons/call/dialpad.svg'); + mask-image: url("$(res)/img/element-icons/call/dialpad.svg"); mask-position: center; mask-size: contain; mask-repeat: no-repeat; @@ -155,7 +155,7 @@ $roomListCollapsedWidth: 68px; margin-left: 8px; &::before { - content: ''; + content: ""; position: absolute; top: 8px; left: 8px; @@ -177,11 +177,11 @@ $roomListCollapsedWidth: 68px; } .mx_LeftPanel_exploreButton::before { - mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/explore.svg"); } .mx_LeftPanel_recentsButton::before { - mask-image: url('$(res)/img/element-icons/clock.svg'); + mask-image: url("$(res)/img/element-icons/clock.svg"); } } diff --git a/res/css/structures/_MainSplit.pcss b/res/css/structures/_MainSplit.pcss index 048a2407881..81466f6207e 100644 --- a/res/css/structures/_MainSplit.pcss +++ b/res/css/structures/_MainSplit.pcss @@ -39,7 +39,7 @@ limitations under the License. width: 4px; border-radius: $border-radius-4px; - content: ''; + content: ""; background-color: $primary-content; opacity: 0.8; diff --git a/res/css/structures/_MatrixChat.pcss b/res/css/structures/_MatrixChat.pcss index f663ecd6e26..bf917a67d99 100644 --- a/res/css/structures/_MatrixChat.pcss +++ b/res/css/structures/_MatrixChat.pcss @@ -52,7 +52,7 @@ limitations under the License. .mx_MatrixChat_syncError { color: $accent-fg-color; - background-color: #DF2A8B; /* Only used here */ + background-color: #df2a8b; /* Only used here */ border-radius: $border-radius-5px; display: table; padding: 30px; @@ -107,7 +107,7 @@ limitations under the License. width: 4px; border-radius: $border-radius-4px; - content: ' '; + content: " "; background-color: $primary-content; opacity: 0.8; diff --git a/res/css/structures/_NotificationPanel.pcss b/res/css/structures/_NotificationPanel.pcss index be5fd3f3691..2ff3335280b 100644 --- a/res/css/structures/_NotificationPanel.pcss +++ b/res/css/structures/_NotificationPanel.pcss @@ -80,7 +80,7 @@ limitations under the License. background-color: $tertiary-content; height: 1px; opacity: 0.4; - content: ''; + content: ""; } } @@ -109,5 +109,5 @@ limitations under the License. } .mx_NotificationPanel_empty::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); } diff --git a/res/css/structures/_QuickSettingsButton.pcss b/res/css/structures/_QuickSettingsButton.pcss index 532ddc867e5..d6686938ad4 100644 --- a/res/css/structures/_QuickSettingsButton.pcss +++ b/res/css/structures/_QuickSettingsButton.pcss @@ -36,7 +36,7 @@ limitations under the License. width: 32px; height: 32px; left: 0; - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: 16px; diff --git a/res/css/structures/_RightPanel.pcss b/res/css/structures/_RightPanel.pcss index 8916541be32..434d8629e9b 100644 --- a/res/css/structures/_RightPanel.pcss +++ b/res/css/structures/_RightPanel.pcss @@ -59,7 +59,7 @@ limitations under the License. border-radius: 100%; &::before { - content: ''; + content: ""; position: absolute; top: 4px; /* center with parent of 32px */ left: 4px; /* center with parent of 32px */ @@ -80,16 +80,16 @@ limitations under the License. } .mx_RightPanel_threadsButton::before { - mask-image: url('$(res)/img/element-icons/room/thread.svg'); + mask-image: url("$(res)/img/element-icons/room/thread.svg"); } .mx_RightPanel_notifsButton::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); mask-position: center; } .mx_RightPanel_roomSummaryButton::before { - mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-image: url("$(res)/img/element-icons/room/room-summary.svg"); mask-position: center; } @@ -99,7 +99,7 @@ $pulse-color: $alert; .mx_RightPanel_pinnedMessagesButton { &::before { - mask-image: url('$(res)/img/element-icons/room/pin.svg'); + mask-image: url("$(res)/img/element-icons/room/pin.svg"); mask-position: center; } } @@ -141,7 +141,7 @@ $pulse-color: $alert; .mx_RightPanel_timelineCardButton { &::before { - mask-image: url('$(res)/img/element-icons/feedback.svg'); + mask-image: url("$(res)/img/element-icons/feedback.svg"); mask-position: center; } } @@ -233,7 +233,7 @@ $pulse-color: $alert; } &::before { - content: ''; + content: ""; display: block; margin: 11px auto 29px auto; height: 42px; diff --git a/res/css/structures/_RoomSearch.pcss b/res/css/structures/_RoomSearch.pcss index 05372367c07..4425e87f7ad 100644 --- a/res/css/structures/_RoomSearch.pcss +++ b/res/css/structures/_RoomSearch.pcss @@ -34,7 +34,7 @@ limitations under the License. .mx_RoomSearch_icon { width: 16px; height: 16px; - mask: url('$(res)/img/element-icons/roomlist/search.svg'); + mask: url("$(res)/img/element-icons/roomlist/search.svg"); mask-repeat: no-repeat; background-color: $secondary-content; margin-left: 7px; diff --git a/res/css/structures/_RoomStatusBar.pcss b/res/css/structures/_RoomStatusBar.pcss index ea94c4439c7..40969bf0c2c 100644 --- a/res/css/structures/_RoomStatusBar.pcss +++ b/res/css/structures/_RoomStatusBar.pcss @@ -121,7 +121,7 @@ limitations under the License. } &::before { - content: ''; + content: ""; position: absolute; left: 10px; /* inset for regular button padding */ background-color: $muted-fg-color; @@ -135,14 +135,14 @@ limitations under the License. } &.mx_RoomStatusBar_unsentCancelAllBtn::before { - mask-image: url('$(res)/img/element-icons/trashcan.svg'); + mask-image: url("$(res)/img/element-icons/trashcan.svg"); } &.mx_RoomStatusBar_unsentRetry { padding-left: 34px; /* 28px from above, but +6px to account for the wider icon */ &::before { - mask-image: url('$(res)/img/element-icons/retry.svg'); + mask-image: url("$(res)/img/element-icons/retry.svg"); } } } diff --git a/res/css/structures/_RoomView.pcss b/res/css/structures/_RoomView.pcss index 85953fe1dd3..755ec98ae26 100644 --- a/res/css/structures/_RoomView.pcss +++ b/res/css/structures/_RoomView.pcss @@ -74,7 +74,7 @@ limitations under the License. .mx_RoomView_messagePanelSearchSpinner { flex: 1; - background-image: url('$(res)/img/typing-indicator-2x.gif'); + background-image: url("$(res)/img/typing-indicator-2x.gif"); background-position: center 367px; background-size: 25px; background-repeat: no-repeat; @@ -83,11 +83,11 @@ limitations under the License. .mx_RoomView_messagePanelSearchSpinner::before { background-color: $info-plinth-fg-color; - mask: url('$(res)/img/feather-customised/search-input.svg'); + mask: url("$(res)/img/feather-customised/search-input.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: 50px; - content: ''; + content: ""; position: absolute; top: 286px; left: 0; @@ -126,7 +126,7 @@ limitations under the License. z-index: 1000; overflow: hidden; - transition: all .2s ease-out; + transition: all 0.2s ease-out; } .mx_RoomView_statusArea_expanded { @@ -155,7 +155,7 @@ limitations under the License. .mx_RoomView_messageListWrapper { justify-content: flex-start; - >.mx_RoomView_MessageList > li > ol { + > .mx_RoomView_MessageList > li > ol { list-style-type: none; } } @@ -222,7 +222,7 @@ hr.mx_RoomView_myReadMarker { .mx_RoomView_callStatusBar .mx_UploadBar_uploadFilename { color: $accent-fg-color; - opacity: 1.0; + opacity: 1; } .mx_RoomView_inCall .mx_RoomView_statusAreaBox_line { diff --git a/res/css/structures/_SearchBox.pcss b/res/css/structures/_SearchBox.pcss index 23ee06f7b39..1323041791d 100644 --- a/res/css/structures/_SearchBox.pcss +++ b/res/css/structures/_SearchBox.pcss @@ -24,7 +24,7 @@ limitations under the License. .mx_SearchBox_closeButton { cursor: pointer; - background-image: url('$(res)/img/icons-close.svg'); + background-image: url("$(res)/img/icons-close.svg"); background-repeat: no-repeat; width: 16px; height: 16px; diff --git a/res/css/structures/_SpaceHierarchy.pcss b/res/css/structures/_SpaceHierarchy.pcss index 2f14473df28..e178f766609 100644 --- a/res/css/structures/_SpaceHierarchy.pcss +++ b/res/css/structures/_SpaceHierarchy.pcss @@ -124,7 +124,7 @@ limitations under the License. background-color: $background; &::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; @@ -135,7 +135,7 @@ limitations under the License. background-color: $tertiary-content; mask-size: 16px; transform: rotate(270deg); - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); } &.mx_SpaceHierarchy_subspace_toggle_shown::before { @@ -215,7 +215,7 @@ limitations under the License. padding-left: 16px; &::before { - content: ''; + content: ""; width: 20px; height: 20px; top: -2px; @@ -225,7 +225,7 @@ limitations under the License. mask-size: contain; mask-repeat: no-repeat; background-color: $accent; - mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/checkmark.svg"); } } } @@ -272,7 +272,8 @@ limitations under the License. } } - &:hover, &:focus-within { + &:hover, + &:focus-within { background-color: $spacePanel-bg-color; .mx_AccessibleButton { diff --git a/res/css/structures/_SpacePanel.pcss b/res/css/structures/_SpacePanel.pcss index f142a45a990..f52a222afa4 100644 --- a/res/css/structures/_SpacePanel.pcss +++ b/res/css/structures/_SpacePanel.pcss @@ -53,7 +53,7 @@ $activeBorderColor: $primary-content; mask-size: contain; mask-repeat: no-repeat; background-color: $background; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); transform: rotate(270deg); } @@ -173,7 +173,7 @@ $activeBorderColor: $primary-content; mask-size: 20px; mask-repeat: no-repeat; background-color: $tertiary-content; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); } .mx_SpaceButton_icon { @@ -185,7 +185,7 @@ $activeBorderColor: $primary-content; &::before { position: absolute; - content: ''; + content: ""; width: $topLevelHeight; height: $topLevelHeight; top: 0; @@ -210,26 +210,26 @@ $activeBorderColor: $primary-content; } &.mx_SpaceButton_home .mx_SpaceButton_icon::before { - mask-image: url('$(res)/img/element-icons/home.svg'); + mask-image: url("$(res)/img/element-icons/home.svg"); } &.mx_SpaceButton_favourites .mx_SpaceButton_icon::before { - mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/favorite.svg"); } &.mx_SpaceButton_people .mx_SpaceButton_icon::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-image: url("$(res)/img/element-icons/room/members.svg"); } &.mx_SpaceButton_orphans .mx_SpaceButton_icon::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-circle.svg"); } &.mx_SpaceButton_new .mx_SpaceButton_icon { &::before { background-color: $primary-content; - mask-image: url('$(res)/img/element-icons/plus.svg'); - transition: all .2s ease-in-out; /* TODO transition */ + mask-image: url("$(res)/img/element-icons/plus.svg"); + transition: all 0.2s ease-in-out; /* TODO transition */ } } @@ -257,14 +257,14 @@ $activeBorderColor: $primary-content; &::before { top: 3px; left: 2px; - content: ''; + content: ""; width: 16px; height: 16px; position: absolute; mask-position: center; mask-size: contain; mask-repeat: no-repeat; - mask-image: url('$(res)/img/element-icons/context-menu.svg'); + mask-image: url("$(res)/img/element-icons/context-menu.svg"); background: $primary-content; } } @@ -405,35 +405,35 @@ $activeBorderColor: $primary-content; } .mx_SpacePanel_iconHome::before { - mask-image: url('$(res)/img/element-icons/home.svg'); + mask-image: url("$(res)/img/element-icons/home.svg"); } .mx_SpacePanel_iconInvite::before { - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); } .mx_SpacePanel_iconSettings::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); } .mx_SpacePanel_iconLeave::before { - mask-image: url('$(res)/img/element-icons/leave.svg'); + mask-image: url("$(res)/img/element-icons/leave.svg"); } .mx_SpacePanel_iconMembers::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-image: url("$(res)/img/element-icons/room/members.svg"); } .mx_SpacePanel_iconPlus::before { - mask-image: url('$(res)/img/element-icons/plus.svg'); + mask-image: url("$(res)/img/element-icons/plus.svg"); } .mx_SpacePanel_iconExplore::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-search.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg"); } .mx_SpacePanel_iconPreferences::before { - mask-image: url('$(res)/img/element-icons/settings/preference.svg'); + mask-image: url("$(res)/img/element-icons/settings/preference.svg"); } .mx_SpacePanel_noIcon { diff --git a/res/css/structures/_SpaceRoomView.pcss b/res/css/structures/_SpaceRoomView.pcss index 2664549b170..f5d567d93fc 100644 --- a/res/css/structures/_SpaceRoomView.pcss +++ b/res/css/structures/_SpaceRoomView.pcss @@ -37,7 +37,7 @@ $SpaceRoomViewInnerWidth: 428px; &::before { position: absolute; - content: ''; + content: ""; width: 32px; height: 32px; top: 24px; @@ -157,7 +157,7 @@ $SpaceRoomViewInnerWidth: 428px; /* XXX: Temporary for the Spaces release only */ .mx_SpaceFeedbackPrompt { padding: 7px; /* 8px - 1px border */ - border: 1px solid rgba($primary-content, .1); + border: 1px solid rgba($primary-content, 0.1); border-radius: 8px; width: max-content; height: fit-content; @@ -236,7 +236,7 @@ $SpaceRoomViewInnerWidth: 428px; width: 16px; background: #fff; /* white icon fill */ mask-size: 16px; - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); } } @@ -251,7 +251,7 @@ $SpaceRoomViewInnerWidth: 428px; width: 24px; background: $tertiary-content; mask-size: contain; - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); } } } @@ -277,11 +277,11 @@ $SpaceRoomViewInnerWidth: 428px; } .mx_SpaceRoomView_privateScope_justMeButton::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-image: url("$(res)/img/element-icons/room/members.svg"); } .mx_SpaceRoomView_privateScope_meAndMyTeammatesButton::before { - mask-image: url('$(res)/img/element-icons/group-members.svg'); + mask-image: url("$(res)/img/element-icons/group-members.svg"); } } @@ -335,7 +335,7 @@ $SpaceRoomViewInnerWidth: 428px; color: $accent; &::before { - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); background-color: $accent; } } diff --git a/res/css/structures/_SplashPage.pcss b/res/css/structures/_SplashPage.pcss index cd918ed596d..be87b04c78d 100644 --- a/res/css/structures/_SplashPage.pcss +++ b/res/css/structures/_SplashPage.pcss @@ -24,40 +24,24 @@ limitations under the License. position: absolute; z-index: -1; opacity: 0.6; - background-image: - radial-gradient( + background-image: radial-gradient( 53.85% 66.75% at 87.55% 0%, hsla(250deg, 76%, 71%, 0.261) 0%, hsla(250deg, 100%, 88%, 0) 100% ), - radial-gradient( - 41.93% 41.93% at 0% 0%, - hsla(222deg, 29%, 20%, 0.28) 0%, - hsla(250deg, 100%, 88%, 0) 100% - ), - radial-gradient( - 100% 100% at 0% 0%, - hsla(250deg, 100%, 88%, 0.174) 0%, - hsla(0deg, 100%, 86%, 0) 100% - ), - radial-gradient( - 106.35% 96.26% at 100% 0%, - hsla(250deg, 100%, 88%, 0.4) 0%, - hsla(167deg, 76%, 82%, 0) 100% - ); + radial-gradient(41.93% 41.93% at 0% 0%, hsla(222deg, 29%, 20%, 0.28) 0%, hsla(250deg, 100%, 88%, 0) 100%), + radial-gradient(100% 100% at 0% 0%, hsla(250deg, 100%, 88%, 0.174) 0%, hsla(0deg, 100%, 86%, 0) 100%), + radial-gradient(106.35% 96.26% at 100% 0%, hsla(250deg, 100%, 88%, 0.4) 0%, hsla(167deg, 76%, 82%, 0) 100%); /* blur to reduce color banding issues due to alpha-blending multiple gradients */ filter: blur(8px); inset: -9px; mask: - /* mask to dither resulting combined gradient */ - url("$(res)/img/noise.png"), + /* mask to dither resulting combined gradient */ url("$(res)/img/noise.png"), /* gradient to apply different amounts of dithering to different parts of the gradient */ - linear-gradient( - to bottom, - /* 10% dithering at the top */ - rgba(0, 0, 0, 0.9) 20%, - /* 80% dithering at the bottom */ - rgba(0, 0, 0, 0.2) 100% - ); + linear-gradient( + to bottom, + /* 10% dithering at the top */ rgba(0, 0, 0, 0.9) 20%, + /* 80% dithering at the bottom */ rgba(0, 0, 0, 0.2) 100% + ); } } diff --git a/res/css/structures/_TabbedView.pcss b/res/css/structures/_TabbedView.pcss index 3f31227caa7..4e453226105 100644 --- a/res/css/structures/_TabbedView.pcss +++ b/res/css/structures/_TabbedView.pcss @@ -136,7 +136,7 @@ limitations under the License. background-color: $icon-button-color; mask-repeat: no-repeat; mask-position: center; - content: ''; + content: ""; } .mx_TabbedView_tabLabel_text { diff --git a/res/css/structures/_ToastContainer.pcss b/res/css/structures/_ToastContainer.pcss index 30d4629440b..9627349c3a1 100644 --- a/res/css/structures/_ToastContainer.pcss +++ b/res/css/structures/_ToastContainer.pcss @@ -48,7 +48,8 @@ limitations under the License. padding: 8px; &.mx_Toast_hasIcon { - &::before, &::after { + &::before, + &::after { content: ""; width: 22px; height: 22px; @@ -70,7 +71,7 @@ limitations under the License. /* white infill for the hollow svg mask */ &::before { background-color: #ffffff; - mask-image: url('$(res)/img/e2e/normal.svg'); + mask-image: url("$(res)/img/e2e/normal.svg"); mask-size: 80%; } @@ -81,16 +82,17 @@ limitations under the License. } &.mx_Toast_icon_secure_backup::after { - mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); + mask-image: url("$(res)/img/feather-customised/secure-backup.svg"); background-color: $primary-content; } &.mx_Toast_icon_labs::after { - mask-image: url('$(res)/img/element-icons/flask.svg'); + mask-image: url("$(res)/img/element-icons/flask.svg"); background-color: $secondary-content; } - .mx_Toast_title, .mx_Toast_body { + .mx_Toast_title, + .mx_Toast_body { grid-column: 2; } } diff --git a/res/css/structures/_UploadBar.pcss b/res/css/structures/_UploadBar.pcss index f3dfcf9aa42..a7dfc8b74fd 100644 --- a/res/css/structures/_UploadBar.pcss +++ b/res/css/structures/_UploadBar.pcss @@ -47,7 +47,7 @@ limitations under the License. mask-repeat: no-repeat; mask-position: center; background-color: $muted-fg-color; - mask-image: url('$(res)/img/element-icons/upload.svg'); + mask-image: url("$(res)/img/element-icons/upload.svg"); } } @@ -61,5 +61,5 @@ limitations under the License. mask-repeat: no-repeat; mask-position: center; background-color: $muted-fg-color; - mask-image: url('$(res)/img/icons-close.svg'); + mask-image: url("$(res)/img/icons-close.svg"); } diff --git a/res/css/structures/_UserMenu.pcss b/res/css/structures/_UserMenu.pcss index 5c862e3d2e1..126d0857047 100644 --- a/res/css/structures/_UserMenu.pcss +++ b/res/css/structures/_UserMenu.pcss @@ -69,7 +69,7 @@ limitations under the License. border-radius: 50%; &::before { - content: ''; + content: ""; width: 16px; height: 16px; position: absolute; @@ -77,7 +77,7 @@ limitations under the License. mask-size: contain; mask-repeat: no-repeat; background-color: $alert; - mask-image: url('$(res)/img/element-icons/roomlist/dnd.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/dnd.svg"); } } } @@ -174,7 +174,7 @@ limitations under the License. display: block; &::before { - content: ''; + content: ""; width: 16px; height: 16px; display: block; @@ -186,39 +186,39 @@ limitations under the License. } .mx_UserMenu_iconHome::before { - mask-image: url('$(res)/img/element-icons/home.svg'); + mask-image: url("$(res)/img/element-icons/home.svg"); } .mx_UserMenu_iconDnd::before { - mask-image: url('$(res)/img/element-icons/roomlist/dnd.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/dnd.svg"); } .mx_UserMenu_iconDndOff::before { - mask-image: url('$(res)/img/element-icons/roomlist/dnd-cross.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/dnd-cross.svg"); } .mx_UserMenu_iconHosting::before { - mask-image: url('$(res)/img/element-icons/brands/element.svg'); + mask-image: url("$(res)/img/element-icons/brands/element.svg"); } .mx_UserMenu_iconBell::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); } .mx_UserMenu_iconLock::before { - mask-image: url('$(res)/img/element-icons/security.svg'); + mask-image: url("$(res)/img/element-icons/security.svg"); } .mx_UserMenu_iconSettings::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); } .mx_UserMenu_iconMessage::before { - mask-image: url('$(res)/img/element-icons/feedback.svg'); + mask-image: url("$(res)/img/element-icons/feedback.svg"); } .mx_UserMenu_iconSignOut::before { - mask-image: url('$(res)/img/element-icons/leave.svg'); + mask-image: url("$(res)/img/element-icons/leave.svg"); } } @@ -264,7 +264,7 @@ limitations under the License. position: absolute; width: inherit; height: inherit; - mask-image: url('$(res)/img/feather-customised/x.svg'); + mask-image: url("$(res)/img/feather-customised/x.svg"); mask-position: center; mask-size: 12px; mask-repeat: no-repeat; diff --git a/res/css/views/audio_messages/_PlayPauseButton.pcss b/res/css/views/audio_messages/_PlayPauseButton.pcss index 0c2573002e1..1093fcd3ae2 100644 --- a/res/css/views/audio_messages/_PlayPauseButton.pcss +++ b/res/css/views/audio_messages/_PlayPauseButton.pcss @@ -24,7 +24,7 @@ limitations under the License. background-color: $system; &::before { - content: ''; + content: ""; position: absolute; /* sizing varies by icon */ background-color: $secondary-content; mask-repeat: no-repeat; @@ -40,7 +40,7 @@ limitations under the License. height: 16px; top: 8px; /* center */ left: 12px; /* center */ - mask-image: url('$(res)/img/element-icons/play.svg'); + mask-image: url("$(res)/img/element-icons/play.svg"); } &.mx_PlayPauseButton_pause::before { @@ -48,6 +48,6 @@ limitations under the License. height: 12px; top: 10px; /* center */ left: 11px; /* center */ - mask-image: url('$(res)/img/element-icons/pause.svg'); + mask-image: url("$(res)/img/element-icons/pause.svg"); } } diff --git a/res/css/views/audio_messages/_SeekBar.pcss b/res/css/views/audio_messages/_SeekBar.pcss index a61876e6314..45e825bc97b 100644 --- a/res/css/views/audio_messages/_SeekBar.pcss +++ b/res/css/views/audio_messages/_SeekBar.pcss @@ -61,8 +61,9 @@ limitations under the License. /* browsers. Firefox responds to webkit-prefixed values now, which means we can't use media */ /* or support queries to selectively apply the rule. An upside is that this CSS doesn't work */ /* in firefox, so it's just wasted CPU/GPU time. */ - &::before { /* ::before to ensure it ends up under the thumb */ - content: ''; + &::before { + /* ::before to ensure it ends up under the thumb */ + content: ""; background-color: $tertiary-content; /* Absolute positioning to ensure it overlaps with the existing bar */ @@ -93,7 +94,7 @@ limitations under the License. /* We do it this way to keep the same padding and margins of the element, avoiding margin math. */ /* Source: https://front-back.com/expand-clickable-areas-for-a-better-touch-experience/ */ &::after { - content: ''; + content: ""; position: absolute; top: -6px; bottom: -6px; diff --git a/res/css/views/auth/_AuthBody.pcss b/res/css/views/auth/_AuthBody.pcss index c3d2bb11f64..c7104d70eec 100644 --- a/res/css/views/auth/_AuthBody.pcss +++ b/res/css/views/auth/_AuthBody.pcss @@ -137,15 +137,50 @@ limitations under the License. } /* specialisation for password reset views */ -.mx_AuthBody_forgot-password { +.mx_AuthBody.mx_AuthBody_forgot-password { font-size: $font-14px; color: $primary-content; padding: 50px 32px; min-height: 600px; h1 { - margin-bottom: $spacing-20; - margin-top: $spacing-24; + margin: $spacing-24 0; + } + + .mx_AuthBody_button-container { + display: flex; + justify-content: center; + } + + .mx_Login_submit { + font-weight: $font-semi-bold; + margin: 0 0 $spacing-16; + } + + .mx_AuthBody_text { + margin-bottom: $spacing-32; + + p { + margin: 0 0 $spacing-8; + } + } + + .mx_AuthBody_sign-in-instead-button { + font-weight: $font-semi-bold; + padding: $spacing-4; + } + + .mx_AuthBody_fieldRow { + margin-bottom: $spacing-24; + } + + .mx_AccessibleButton.mx_AccessibleButton_hasKind { + background: none; + + &:disabled { + cursor: default; + opacity: 0.4; + } } } @@ -154,12 +189,6 @@ limitations under the License. color: $secondary-content; display: flex; gap: $spacing-8; - margin-bottom: 10px; - margin-top: $spacing-24; -} - -.mx_AuthBody_did-not-receive--centered { - justify-content: center; } .mx_AuthBody_resend-button { @@ -168,7 +197,7 @@ limitations under the License. color: $accent; display: flex; gap: $spacing-4; - padding: 4px; + padding: $spacing-4; &:hover { background-color: $system; @@ -209,7 +238,7 @@ limitations under the License. text-align: center; .mx_AuthBody_paddedFooter_title { - margin-top: 16px; + margin-top: $spacing-16; font-size: $font-15px; line-height: $font-24px; @@ -220,7 +249,7 @@ limitations under the License. } .mx_AuthBody_paddedFooter_subtitle { - margin-top: 8px; + margin-top: $spacing-8; font-size: $font-10px; line-height: $font-14px; } @@ -236,7 +265,7 @@ limitations under the License. } .mx_SSOButtons + .mx_AuthBody_changeFlow { - margin-top: 24px; + margin-top: $spacing-24; } .mx_AuthBody_spinner { diff --git a/res/css/views/auth/_LoginWithQR.pcss b/res/css/views/auth/_LoginWithQR.pcss index 390cf8311d0..f1259fde0fa 100644 --- a/res/css/views/auth/_LoginWithQR.pcss +++ b/res/css/views/auth/_LoginWithQR.pcss @@ -32,8 +32,9 @@ limitations under the License. align-items: center; text-align: center; - &::before, &::after { - content: ''; + &::before, + &::after { + content: ""; flex: 1; border-bottom: 1px solid $quinary-content; } diff --git a/res/css/views/auth/_PassphraseField.pcss b/res/css/views/auth/_PassphraseField.pcss index a197ebbe038..2b909a0d647 100644 --- a/res/css/views/auth/_PassphraseField.pcss +++ b/res/css/views/auth/_PassphraseField.pcss @@ -28,7 +28,8 @@ progress.mx_PassphraseField_progress { @mixin ProgressBarBorderRadius "2px"; @mixin ProgressBarColour $PassphraseStrengthLow; - &[value="2"], &[value="3"] { + &[value="2"], + &[value="3"] { @mixin ProgressBarColour $PassphraseStrengthMedium; } &[value="4"] { diff --git a/res/css/views/avatars/_BaseAvatar.pcss b/res/css/views/avatars/_BaseAvatar.pcss index d51dabd48ef..a6a4b0b74b2 100644 --- a/res/css/views/avatars/_BaseAvatar.pcss +++ b/res/css/views/avatars/_BaseAvatar.pcss @@ -29,7 +29,8 @@ limitations under the License. user-select: none; &.mx_RoomAvatar_isSpaceRoom { - &.mx_BaseAvatar_image, .mx_BaseAvatar_image { + &.mx_BaseAvatar_image, + .mx_BaseAvatar_image { border-radius: 8px; } } diff --git a/res/css/views/avatars/_DecoratedRoomAvatar.pcss b/res/css/views/avatars/_DecoratedRoomAvatar.pcss index 43fd6bb5fd5..e15e8264224 100644 --- a/res/css/views/avatars/_DecoratedRoomAvatar.pcss +++ b/res/css/views/avatars/_DecoratedRoomAvatar.pcss @@ -14,12 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_DecoratedRoomAvatar, .mx_ExtraTile { +.mx_DecoratedRoomAvatar, +.mx_ExtraTile { position: relative; contain: content; &.mx_DecoratedRoomAvatar_cutout .mx_BaseAvatar { - mask-image: url('$(res)/img/element-icons/roomlist/decorated-avatar-mask.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/decorated-avatar-mask.svg"); mask-position: center; mask-size: contain; mask-repeat: no-repeat; @@ -37,7 +38,7 @@ limitations under the License. } .mx_DecoratedRoomAvatar_icon::before { - content: ''; + content: ""; width: 100%; height: 100%; right: 0; @@ -50,7 +51,7 @@ limitations under the License. mask-size: contain; mask-repeat: no-repeat; background: $secondary-content; - mask-image: url('$(res)/img/globe.svg'); + mask-image: url("$(res)/img/globe.svg"); } .mx_DecoratedRoomAvatar_icon_offline::before { @@ -69,7 +70,8 @@ limitations under the License. background-color: $presence-busy; } - .mx_NotificationBadge, .mx_RoomTile_badgeContainer { + .mx_NotificationBadge, + .mx_RoomTile_badgeContainer { position: absolute; top: 0; right: 0; diff --git a/res/css/views/beta/_BetaCard.pcss b/res/css/views/beta/_BetaCard.pcss index b47e7ca1b6c..0f8d8a66e73 100644 --- a/res/css/views/beta/_BetaCard.pcss +++ b/res/css/views/beta/_BetaCard.pcss @@ -114,6 +114,10 @@ limitations under the License. } } } + + &:last-child { + margin-bottom: 0; + } } .mx_BetaCard_betaPill { diff --git a/res/css/views/context_menus/_IconizedContextMenu.pcss b/res/css/views/context_menus/_IconizedContextMenu.pcss index 1de0219cb6d..13310eb19fd 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.pcss +++ b/res/css/views/context_menus/_IconizedContextMenu.pcss @@ -31,7 +31,8 @@ limitations under the License. } /* the notFirst class is for cases where the optionList might be under a header of sorts. */ - &:nth-child(n + 2), .mx_IconizedContextMenu_optionList_notFirst { + &:nth-child(n + 2), + .mx_IconizedContextMenu_optionList_notFirst { /* This is a bit of a hack when we could just use a simple border-top property, */ /* however we have a (kinda) good reason for doing it this way: we need opacity. */ /* To get the right color, we need an opacity modifier which means we have to work */ @@ -44,7 +45,7 @@ limitations under the License. &::before { border-top: 1px solid $primary-content; opacity: 0.1; - content: ''; + content: ""; /* Counteract the padding problems (width: 100% ignores the 40px padding, */ /* unless we position it absolutely then it does the right thing). */ @@ -92,13 +93,16 @@ limitations under the License. cursor: not-allowed; } - img, .mx_IconizedContextMenu_icon { /* icons */ + img, + .mx_IconizedContextMenu_icon { + /* icons */ width: 16px; min-width: 16px; max-width: 16px; } - span.mx_IconizedContextMenu_label { /* labels */ + span.mx_IconizedContextMenu_label { + /* labels */ width: 100%; flex: 1; @@ -124,7 +128,7 @@ limitations under the License. height: 16px; &::before { - content: ''; + content: ""; width: inherit; height: inherit; position: absolute; @@ -154,7 +158,8 @@ limitations under the License. } .mx_IconizedContextMenu_active { - &.mx_IconizedContextMenu_item, .mx_IconizedContextMenu_item { + &.mx_IconizedContextMenu_item, + .mx_IconizedContextMenu_item { color: $accent !important; } @@ -176,7 +181,7 @@ limitations under the License. } .mx_IconizedContextMenu_checked::before { - mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/checkmark.svg"); } .mx_IconizedContextMenu_unchecked::before { diff --git a/res/css/views/context_menus/_MessageContextMenu.pcss b/res/css/views/context_menus/_MessageContextMenu.pcss index 27f3966db2b..4531ee1709b 100644 --- a/res/css/views/context_menus/_MessageContextMenu.pcss +++ b/res/css/views/context_menus/_MessageContextMenu.pcss @@ -16,14 +16,13 @@ limitations under the License. */ .mx_MessageContextMenu { - .mx_IconizedContextMenu_icon { width: 16px; height: 16px; display: block; &::before { - content: ''; + content: ""; width: 16px; height: 16px; display: block; @@ -35,59 +34,59 @@ limitations under the License. } .mx_MessageContextMenu_iconCollapse::before { - mask-image: url('$(res)/img/element-icons/message/chevron-up.svg'); + mask-image: url("$(res)/img/element-icons/message/chevron-up.svg"); } .mx_MessageContextMenu_iconReport::before { - mask-image: url('$(res)/img/element-icons/warning-badge.svg'); + mask-image: url("$(res)/img/element-icons/warning-badge.svg"); } .mx_MessageContextMenu_iconLink::before { - mask-image: url('$(res)/img/element-icons/link.svg'); + mask-image: url("$(res)/img/element-icons/link.svg"); } .mx_MessageContextMenu_iconPermalink::before { - mask-image: url('$(res)/img/element-icons/room/share.svg'); + mask-image: url("$(res)/img/element-icons/room/share.svg"); } .mx_MessageContextMenu_iconUnhidePreview::before { - mask-image: url('$(res)/img/element-icons/settings/appearance.svg'); + mask-image: url("$(res)/img/element-icons/settings/appearance.svg"); } .mx_MessageContextMenu_iconOpenInMapSite::before { - mask-image: url('$(res)/img/external-link.svg'); + mask-image: url("$(res)/img/external-link.svg"); } .mx_MessageContextMenu_iconEndPoll::before { - mask-image: url('$(res)/img/element-icons/check-white.svg'); + mask-image: url("$(res)/img/element-icons/check-white.svg"); } .mx_MessageContextMenu_iconForward::before { - mask-image: url('$(res)/img/element-icons/message/fwd.svg'); + mask-image: url("$(res)/img/element-icons/message/fwd.svg"); } .mx_MessageContextMenu_iconRedact::before { - mask-image: url('$(res)/img/element-icons/trashcan.svg'); + mask-image: url("$(res)/img/element-icons/trashcan.svg"); } .mx_MessageContextMenu_iconResend::before { - mask-image: url('$(res)/img/element-icons/retry.svg'); + mask-image: url("$(res)/img/element-icons/retry.svg"); } .mx_MessageContextMenu_iconSource::before { - mask-image: url('$(res)/img/element-icons/room/format-bar/code.svg'); + mask-image: url("$(res)/img/element-icons/room/format-bar/code.svg"); } .mx_MessageContextMenu_iconQuote::before { - mask-image: url('$(res)/img/element-icons/room/format-bar/quote.svg'); + mask-image: url("$(res)/img/element-icons/room/format-bar/quote.svg"); } .mx_MessageContextMenu_iconPin::before { - mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); } .mx_MessageContextMenu_iconUnpin::before { - mask-image: url('$(res)/img/element-icons/room/pin.svg'); + mask-image: url("$(res)/img/element-icons/room/pin.svg"); } .mx_MessageContextMenu_iconCopy::before { @@ -100,26 +99,26 @@ limitations under the License. } .mx_MessageContextMenu_iconEdit::before { - mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg'); + mask-image: url("$(res)/img/element-icons/room/message-bar/edit.svg"); } .mx_MessageContextMenu_iconReply::before { - mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); + mask-image: url("$(res)/img/element-icons/room/message-bar/reply.svg"); } .mx_MessageContextMenu_iconReplyInThread::before { - mask-image: url('$(res)/img/element-icons/message/thread.svg'); + mask-image: url("$(res)/img/element-icons/message/thread.svg"); } .mx_MessageContextMenu_iconReact::before { - mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); + mask-image: url("$(res)/img/element-icons/room/message-bar/emoji.svg"); } .mx_MessageContextMenu_iconViewInRoom::before { - mask-image: url('$(res)/img/element-icons/view-in-room.svg'); + mask-image: url("$(res)/img/element-icons/view-in-room.svg"); } .mx_MessageContextMenu_jumpToEvent::before { - mask-image: url('$(res)/img/element-icons/child-relationship.svg'); + mask-image: url("$(res)/img/element-icons/child-relationship.svg"); } } diff --git a/res/css/views/context_menus/_RoomGeneralContextMenu.pcss b/res/css/views/context_menus/_RoomGeneralContextMenu.pcss index b1277a39d4b..a0271bbb7a7 100644 --- a/res/css/views/context_menus/_RoomGeneralContextMenu.pcss +++ b/res/css/views/context_menus/_RoomGeneralContextMenu.pcss @@ -1,65 +1,65 @@ .mx_RoomGeneralContextMenu_iconStar::before { - mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/favorite.svg"); } .mx_RoomGeneralContextMenu_iconArrowDown::before { - mask-image: url('$(res)/img/element-icons/roomlist/low-priority.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/low-priority.svg"); } .mx_RoomGeneralContextMenu_iconNotificationsDefault::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); } .mx_RoomGeneralContextMenu_iconNotificationsAllMessages::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-default.svg"); } .mx_RoomGeneralContextMenu_iconNotificationsMentionsKeywords::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-dm.svg"); } .mx_RoomGeneralContextMenu_iconNotificationsNone::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-off.svg"); } .mx_RoomGeneralContextMenu_iconPeople::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-image: url("$(res)/img/element-icons/room/members.svg"); } .mx_RoomGeneralContextMenu_iconFiles::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); + mask-image: url("$(res)/img/element-icons/room/files.svg"); } .mx_RoomGeneralContextMenu_iconPins::before { - mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); } .mx_RoomGeneralContextMenu_iconWidgets::before { - mask-image: url('$(res)/img/element-icons/room/apps.svg'); + mask-image: url("$(res)/img/element-icons/room/apps.svg"); } .mx_RoomGeneralContextMenu_iconSettings::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); } .mx_RoomGeneralContextMenu_iconExport::before { - mask-image: url('$(res)/img/element-icons/export.svg'); + mask-image: url("$(res)/img/element-icons/export.svg"); } .mx_RoomGeneralContextMenu_iconDeveloperTools::before { - mask-image: url('$(res)/img/element-icons/settings/flask.svg'); + mask-image: url("$(res)/img/element-icons/settings/flask.svg"); } .mx_RoomGeneralContextMenu_iconCopyLink::before { - mask-image: url('$(res)/img/element-icons/link.svg'); + mask-image: url("$(res)/img/element-icons/link.svg"); } .mx_RoomGeneralContextMenu_iconInvite::before { - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); } .mx_RoomGeneralContextMenu_iconSignOut::before { - mask-image: url('$(res)/img/element-icons/leave.svg'); + mask-image: url("$(res)/img/element-icons/leave.svg"); } .mx_RoomGeneralContextMenu_markAsUnread::before { diff --git a/res/css/views/context_menus/_RoomNotificationContextMenu.pcss b/res/css/views/context_menus/_RoomNotificationContextMenu.pcss index 43bd5a2dbea..baf440fd435 100644 --- a/res/css/views/context_menus/_RoomNotificationContextMenu.pcss +++ b/res/css/views/context_menus/_RoomNotificationContextMenu.pcss @@ -1,12 +1,12 @@ .mx_RoomNotificationContextMenu_iconBell::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); } .mx_RoomNotificationContextMenu_iconBellDot::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-default.svg"); } .mx_RoomNotificationContextMenu_iconBellMentions::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-dm.svg"); } .mx_RoomNotificationContextMenu_iconBellCrossed::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-off.svg"); } diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss b/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss index 98df86871c5..cda642f610f 100644 --- a/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss +++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss @@ -123,13 +123,13 @@ limitations under the License. position: relative; &::before { - content: ''; + content: ""; position: absolute; background-color: $primary-content; mask-repeat: no-repeat; mask-position: center; mask-size: contain; - mask-image: url('$(res)/img/element-icons/retry.svg'); + mask-image: url("$(res)/img/element-icons/retry.svg"); width: 18px; height: 18px; left: 0; @@ -199,7 +199,7 @@ limitations under the License. position: relative; &::before { - content: ''; + content: ""; width: 20px; height: 20px; top: 8px; @@ -209,7 +209,7 @@ limitations under the License. mask-size: contain; mask-repeat: no-repeat; background-color: $accent; - mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/checkmark.svg"); } } } diff --git a/res/css/views/dialogs/_AnalyticsLearnMoreDialog.pcss b/res/css/views/dialogs/_AnalyticsLearnMoreDialog.pcss index 7a3edd5f7d9..7545994ca71 100644 --- a/res/css/views/dialogs/_AnalyticsLearnMoreDialog.pcss +++ b/res/css/views/dialogs/_AnalyticsLearnMoreDialog.pcss @@ -17,7 +17,7 @@ limitations under the License. .mx_AnalyticsLearnMoreDialog { max-width: 500px; .mx_AnalyticsLearnMore_image_holder { - background-image: url('$(res)/img/element-shiny.svg'); + background-image: url("$(res)/img/element-shiny.svg"); background-repeat: no-repeat; background-position: center top; height: 112px; @@ -41,7 +41,7 @@ limitations under the License. .mx_AnalyticsPolicyLink { display: inline-block; - mask-image: url('$(res)/img/external-link.svg'); + mask-image: url("$(res)/img/external-link.svg"); background-color: $accent; mask-repeat: no-repeat; mask-size: contain; @@ -56,7 +56,7 @@ limitations under the License. } .mx_AnalyticsLearnMore_bullets li { - background: url('$(res)/img/tick-circle.svg') no-repeat; + background: url("$(res)/img/tick-circle.svg") no-repeat; list-style-type: none; padding: 2px 0px 20px 32px; vertical-align: middle; diff --git a/res/css/views/dialogs/_BulkRedactDialog.pcss b/res/css/views/dialogs/_BulkRedactDialog.pcss index c6b2adff874..4891b758eed 100644 --- a/res/css/views/dialogs/_BulkRedactDialog.pcss +++ b/res/css/views/dialogs/_BulkRedactDialog.pcss @@ -15,7 +15,8 @@ limitations under the License. */ .mx_BulkRedactDialog { - .mx_Checkbox, .mx_BulkRedactDialog_checkboxMicrocopy { + .mx_Checkbox, + .mx_BulkRedactDialog_checkboxMicrocopy { line-height: $font-20px; } diff --git a/res/css/views/dialogs/_ConfirmSpaceUserActionDialog.pcss b/res/css/views/dialogs/_ConfirmSpaceUserActionDialog.pcss index 09d80a3f2fa..a7ef9e1c88b 100644 --- a/res/css/views/dialogs/_ConfirmSpaceUserActionDialog.pcss +++ b/res/css/views/dialogs/_ConfirmSpaceUserActionDialog.pcss @@ -50,7 +50,7 @@ limitations under the License. color: $secondary-content; &::before { - content: ''; + content: ""; position: absolute; left: 10px; top: calc(50% - 8px); /* vertical centering */ @@ -59,7 +59,7 @@ limitations under the License. background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; - mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-image: url("$(res)/img/element-icons/room/room-summary.svg"); mask-position: center; } } diff --git a/res/css/views/dialogs/_DevtoolsDialog.pcss b/res/css/views/dialogs/_DevtoolsDialog.pcss index 9daa787a62f..5447a3fc178 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.pcss +++ b/res/css/views/dialogs/_DevtoolsDialog.pcss @@ -138,7 +138,8 @@ limitations under the License. text-align: left; } - td, th { + td, + th { width: 360px; /* "feels right" number */ text-overflow: ellipsis; @@ -146,7 +147,8 @@ limitations under the License. white-space: nowrap; } - td + td, th + th { + td + td, + th + th { width: auto; } diff --git a/res/css/views/dialogs/_ExportDialog.pcss b/res/css/views/dialogs/_ExportDialog.pcss index 5c9daeffbf0..ef96ed63818 100644 --- a/res/css/views/dialogs/_ExportDialog.pcss +++ b/res/css/views/dialogs/_ExportDialog.pcss @@ -47,7 +47,8 @@ limitations under the License. color: unset; } - .mx_Field_valid.mx_Field, .mx_Field_valid.mx_Field:focus-within { + .mx_Field_valid.mx_Field, + .mx_Field_valid.mx_Field:focus-within { border-color: $input-border-color; } diff --git a/res/css/views/dialogs/_FeedbackDialog.pcss b/res/css/views/dialogs/_FeedbackDialog.pcss index d16401f09ce..ef7bce0cf27 100644 --- a/res/css/views/dialogs/_FeedbackDialog.pcss +++ b/res/css/views/dialogs/_FeedbackDialog.pcss @@ -58,12 +58,14 @@ limitations under the License. line-height: $font-15px; } - a, .mx_AccessibleButton_kind_link { + a, + .mx_AccessibleButton_kind_link { color: $accent; text-decoration: underline; } - &::before, &::after { + &::before, + &::after { content: ""; position: absolute; width: 40px; @@ -87,7 +89,7 @@ limitations under the License. .mx_FeedbackDialog_reportBug { &::after { - mask-image: url('$(res)/img/feather-customised/bug.svg'); + mask-image: url("$(res)/img/feather-customised/bug.svg"); } } @@ -95,7 +97,7 @@ limitations under the License. .mx_StyledRadioButton { display: inline-flex; font-size: 20px; - transition: font-size 1s, border .5s; + transition: font-size 1s, border 0.5s; border-radius: 50%; border: 2px solid transparent; margin-top: 12px; @@ -132,7 +134,7 @@ limitations under the License. } &::after { - mask-image: url('$(res)/img/element-icons/feedback.svg'); + mask-image: url("$(res)/img/element-icons/feedback.svg"); } } } diff --git a/res/css/views/dialogs/_ForwardDialog.pcss b/res/css/views/dialogs/_ForwardDialog.pcss index bfb9626c9eb..f1818721f13 100644 --- a/res/css/views/dialogs/_ForwardDialog.pcss +++ b/res/css/views/dialogs/_ForwardDialog.pcss @@ -137,7 +137,8 @@ limitations under the License. visibility: hidden; } - .mx_ForwardList_sendIcon, .mx_NotificationBadge { + .mx_ForwardList_sendIcon, + .mx_NotificationBadge { position: absolute; } @@ -148,7 +149,7 @@ limitations under the License. &.mx_ForwardList_sending .mx_ForwardList_sendIcon { background-color: $accent; - mask-image: url('$(res)/img/element-icons/circle-sending.svg'); + mask-image: url("$(res)/img/element-icons/circle-sending.svg"); mask-position: center; mask-repeat: no-repeat; mask-size: 14px; @@ -158,7 +159,7 @@ limitations under the License. &.mx_ForwardList_sent .mx_ForwardList_sendIcon { background-color: $accent; - mask-image: url('$(res)/img/element-icons/circle-sent.svg'); + mask-image: url("$(res)/img/element-icons/circle-sent.svg"); mask-position: center; mask-repeat: no-repeat; mask-size: 14px; diff --git a/res/css/views/dialogs/_InviteDialog.pcss b/res/css/views/dialogs/_InviteDialog.pcss index 3a9c4a7a046..581d4f1de03 100644 --- a/res/css/views/dialogs/_InviteDialog.pcss +++ b/res/css/views/dialogs/_InviteDialog.pcss @@ -313,11 +313,11 @@ limitations under the License. } .mx_InviteDialog_userDirectoryIcon::before { - mask-image: url('$(res)/img/voip/tab-userdirectory.svg'); + mask-image: url("$(res)/img/voip/tab-userdirectory.svg"); } .mx_InviteDialog_dialPadIcon::before { - mask-image: url('$(res)/img/voip/tab-dialpad.svg'); + mask-image: url("$(res)/img/voip/tab-dialpad.svg"); } .mx_InviteDialog_tile { diff --git a/res/css/views/dialogs/_JoinRuleDropdown.pcss b/res/css/views/dialogs/_JoinRuleDropdown.pcss index b4d13909a77..ddb16c31df7 100644 --- a/res/css/views/dialogs/_JoinRuleDropdown.pcss +++ b/res/css/views/dialogs/_JoinRuleDropdown.pcss @@ -50,17 +50,17 @@ limitations under the License. } .mx_JoinRuleDropdown_invite::before { - mask-image: url('$(res)/img/element-icons/lock.svg'); + mask-image: url("$(res)/img/element-icons/lock.svg"); mask-size: contain; } .mx_JoinRuleDropdown_public::before { - mask-image: url('$(res)/img/globe.svg'); + mask-image: url("$(res)/img/globe.svg"); mask-size: 12px; } .mx_JoinRuleDropdown_restricted::before { - mask-image: url('$(res)/img/element-icons/group-members.svg'); + mask-image: url("$(res)/img/element-icons/group-members.svg"); mask-size: contain; } } diff --git a/res/css/views/dialogs/_LeaveSpaceDialog.pcss b/res/css/views/dialogs/_LeaveSpaceDialog.pcss index d63b29f3c1b..f06cc9a46c7 100644 --- a/res/css/views/dialogs/_LeaveSpaceDialog.pcss +++ b/res/css/views/dialogs/_LeaveSpaceDialog.pcss @@ -44,7 +44,7 @@ limitations under the License. color: $secondary-content; &::before { - content: ''; + content: ""; position: absolute; left: 10px; top: calc(50% - 8px); /* vertical centering */ @@ -53,7 +53,7 @@ limitations under the License. background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; - mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-image: url("$(res)/img/element-icons/room/room-summary.svg"); mask-position: center; } } diff --git a/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss b/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss index 6ad2dc4d082..d99072648f6 100644 --- a/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss +++ b/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss @@ -116,7 +116,7 @@ limitations under the License. color: $secondary-content; &::before { - content: ''; + content: ""; position: absolute; left: 10px; top: calc(50% - 8px); /* vertical centering */ @@ -125,7 +125,7 @@ limitations under the License. background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; - mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-image: url("$(res)/img/element-icons/room/room-summary.svg"); mask-position: center; } } diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.pcss b/res/css/views/dialogs/_MessageEditHistoryDialog.pcss index c92ce13e2ef..ff7c1002c42 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.pcss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.pcss @@ -35,7 +35,8 @@ limitations under the License. padding: 0; color: $primary-content; - span.mx_EditHistoryMessage_deletion, span.mx_EditHistoryMessage_insertion { + span.mx_EditHistoryMessage_deletion, + span.mx_EditHistoryMessage_insertion { padding: 0px 2px; } diff --git a/res/css/views/dialogs/_PollCreateDialog.pcss b/res/css/views/dialogs/_PollCreateDialog.pcss index b49fda4db06..e50af35a41a 100644 --- a/res/css/views/dialogs/_PollCreateDialog.pcss +++ b/res/css/views/dialogs/_PollCreateDialog.pcss @@ -63,7 +63,7 @@ limitations under the License. &::before { content: ""; - mask: url('$(res)/img/element-icons/x-8px.svg'); + mask: url("$(res)/img/element-icons/x-8px.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: cover; diff --git a/res/css/views/dialogs/_RoomSettingsDialog.pcss b/res/css/views/dialogs/_RoomSettingsDialog.pcss index 8631ec5d7d5..143cb6fa3c7 100644 --- a/res/css/views/dialogs/_RoomSettingsDialog.pcss +++ b/res/css/views/dialogs/_RoomSettingsDialog.pcss @@ -18,32 +18,32 @@ limitations under the License. /* ========================================================== */ .mx_RoomSettingsDialog_settingsIcon::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); } .mx_RoomSettingsDialog_voiceIcon::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + mask-image: url("$(res)/img/element-icons/call/voice-call.svg"); } .mx_RoomSettingsDialog_securityIcon::before { - mask-image: url('$(res)/img/element-icons/security.svg'); + mask-image: url("$(res)/img/element-icons/security.svg"); } .mx_RoomSettingsDialog_rolesIcon::before { - mask-image: url('$(res)/img/element-icons/room/settings/roles.svg'); + mask-image: url("$(res)/img/element-icons/room/settings/roles.svg"); } .mx_RoomSettingsDialog_notificationsIcon::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); } .mx_RoomSettingsDialog_bridgesIcon::before { /* This icon is pants, please improve :) */ - mask-image: url('$(res)/img/feather-customised/bridge.svg'); + mask-image: url("$(res)/img/feather-customised/bridge.svg"); } .mx_RoomSettingsDialog_warningIcon::before { - mask-image: url('$(res)/img/element-icons/room/settings/advanced.svg'); + mask-image: url("$(res)/img/element-icons/room/settings/advanced.svg"); } .mx_RoomSettingsDialog .mx_Dialog_title { diff --git a/res/css/views/dialogs/_SpacePreferencesDialog.pcss b/res/css/views/dialogs/_SpacePreferencesDialog.pcss index ce36c2d3396..3cdb08cf928 100644 --- a/res/css/views/dialogs/_SpacePreferencesDialog.pcss +++ b/res/css/views/dialogs/_SpacePreferencesDialog.pcss @@ -46,5 +46,5 @@ limitations under the License. } .mx_SpacePreferencesDialog_appearanceIcon::before { - mask-image: url('$(res)/img/element-icons/settings/appearance.svg'); + mask-image: url("$(res)/img/element-icons/settings/appearance.svg"); } diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.pcss b/res/css/views/dialogs/_SpaceSettingsDialog.pcss index ead082dba68..e0887d1b76f 100644 --- a/res/css/views/dialogs/_SpaceSettingsDialog.pcss +++ b/res/css/views/dialogs/_SpaceSettingsDialog.pcss @@ -88,11 +88,11 @@ limitations under the License. .mx_TabbedView_tabLabel { .mx_SpaceSettingsDialog_generalIcon::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); } .mx_SpaceSettingsDialog_visibilityIcon::before { - mask-image: url('$(res)/img/element-icons/eye.svg'); + mask-image: url("$(res)/img/element-icons/eye.svg"); } } } diff --git a/res/css/views/dialogs/_SpotlightDialog.pcss b/res/css/views/dialogs/_SpotlightDialog.pcss index 4edd00bace7..7ee64942cbe 100644 --- a/res/css/views/dialogs/_SpotlightDialog.pcss +++ b/res/css/views/dialogs/_SpotlightDialog.pcss @@ -93,11 +93,11 @@ limitations under the License. } &.mx_SpotlightDialog_filterPeople::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-image: url("$(res)/img/element-icons/room/members.svg"); } &.mx_SpotlightDialog_filterPublicRooms::before { - mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/explore.svg"); } .mx_SpotlightDialog_filter--close { @@ -156,7 +156,8 @@ limitations under the License. padding: $spacing-16; .mx_SpotlightDialog_section { - > h4, > .mx_SpotlightDialog_sectionHeader > h4 { + > h4, + > .mx_SpotlightDialog_sectionHeader > h4 { font-weight: $font-semi-bold; font-size: $font-12px; line-height: $font-15px; @@ -216,7 +217,8 @@ limitations under the License. margin-left: $spacing-16; } - &:hover, &[aria-selected="true"] { + &:hover, + &[aria-selected="true"] { background-color: $quinary-content; } } @@ -329,7 +331,7 @@ limitations under the License. &::before { top: 2px; left: 2px; - content: ''; + content: ""; width: 16px; height: 16px; position: absolute; @@ -339,16 +341,18 @@ limitations under the License. background: $tertiary-content; } - &:hover::before, &[aria-selected="true"]::before { + &:hover::before, + &[aria-selected="true"]::before { background-color: $secondary-content; } } .mx_SpotlightDialog_option--menu::before { - mask-image: url('$(res)/img/element-icons/context-menu.svg'); + mask-image: url("$(res)/img/element-icons/context-menu.svg"); } - &:hover, &[aria-selected="true"] { + &:hover, + &[aria-selected="true"] { background-color: $system; .mx_SpotlightDialog_option--menu, @@ -419,19 +423,19 @@ limitations under the License. } .mx_SpotlightDialog_startChat::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-image: url("$(res)/img/element-icons/room/members.svg"); } .mx_SpotlightDialog_joinRoomAlias::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-circle.svg"); } .mx_SpotlightDialog_explorePublicRooms::before { - mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/explore.svg"); } .mx_SpotlightDialog_startGroupChat::before { - mask-image: url('$(res)/img/element-icons/group-members.svg'); + mask-image: url("$(res)/img/element-icons/group-members.svg"); } .mx_SpotlightDialog_otherSearches_messageSearchText { @@ -448,7 +452,7 @@ limitations under the License. mask-repeat: no-repeat; mask-position: center; mask-size: contain; - mask-image: url('$(res)/img/element-icons/room/search-inset.svg'); + mask-image: url("$(res)/img/element-icons/room/search-inset.svg"); } } @@ -491,19 +495,19 @@ limitations under the License. mask-size: contain; &.mx_SpotlightDialog_metaspaceResult_home-space { - mask-image: url('$(res)/img/element-icons/home.svg'); + mask-image: url("$(res)/img/element-icons/home.svg"); } &.mx_SpotlightDialog_metaspaceResult_favourites-space { - mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/favorite.svg"); } &.mx_SpotlightDialog_metaspaceResult_people-space { - mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-image: url("$(res)/img/element-icons/room/members.svg"); } &.mx_SpotlightDialog_metaspaceResult_orphans-space { - mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-circle.svg"); } } } diff --git a/res/css/views/dialogs/_TermsDialog.pcss b/res/css/views/dialogs/_TermsDialog.pcss index 18daf8514d9..c5834b29871 100644 --- a/res/css/views/dialogs/_TermsDialog.pcss +++ b/res/css/views/dialogs/_TermsDialog.pcss @@ -35,13 +35,14 @@ limitations under the License. width: 100%; } -.mx_TermsDialog_service, .mx_TermsDialog_summary { +.mx_TermsDialog_service, +.mx_TermsDialog_summary { padding-right: 10px; } .mx_TermsDialog_link { display: inline-block; - mask-image: url('$(res)/img/external-link.svg'); + mask-image: url("$(res)/img/external-link.svg"); background-color: $accent; width: 10px; height: 10px; diff --git a/res/css/views/dialogs/_UserSettingsDialog.pcss b/res/css/views/dialogs/_UserSettingsDialog.pcss index c778ad3dd2a..118c057b83b 100644 --- a/res/css/views/dialogs/_UserSettingsDialog.pcss +++ b/res/css/views/dialogs/_UserSettingsDialog.pcss @@ -18,45 +18,45 @@ limitations under the License. /* ========================================================== */ .mx_UserSettingsDialog_settingsIcon::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); } .mx_UserSettingsDialog_appearanceIcon::before { - mask-image: url('$(res)/img/element-icons/settings/appearance.svg'); + mask-image: url("$(res)/img/element-icons/settings/appearance.svg"); } .mx_UserSettingsDialog_voiceIcon::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + mask-image: url("$(res)/img/element-icons/call/voice-call.svg"); } .mx_UserSettingsDialog_bellIcon::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); } .mx_UserSettingsDialog_preferencesIcon::before { - mask-image: url('$(res)/img/element-icons/settings/preference.svg'); + mask-image: url("$(res)/img/element-icons/settings/preference.svg"); } .mx_UserSettingsDialog_keyboardIcon::before { - mask-image: url('$(res)/img/element-icons/settings/keyboard.svg'); + mask-image: url("$(res)/img/element-icons/settings/keyboard.svg"); } .mx_UserSettingsDialog_sidebarIcon::before { - mask-image: url('$(res)/img/element-icons/settings/sidebar.svg'); + mask-image: url("$(res)/img/element-icons/settings/sidebar.svg"); } .mx_UserSettingsDialog_securityIcon::before { - mask-image: url('$(res)/img/element-icons/security.svg'); + mask-image: url("$(res)/img/element-icons/security.svg"); } .mx_UserSettingsDialog_helpIcon::before { - mask-image: url('$(res)/img/element-icons/settings/help.svg'); + mask-image: url("$(res)/img/element-icons/settings/help.svg"); } .mx_UserSettingsDialog_labsIcon::before { - mask-image: url('$(res)/img/element-icons/settings/flask.svg'); + mask-image: url("$(res)/img/element-icons/settings/flask.svg"); } .mx_UserSettingsDialog_mjolnirIcon::before { - mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/emoji.svg"); } diff --git a/res/css/views/dialogs/_VerifyEMailDialog.pcss b/res/css/views/dialogs/_VerifyEMailDialog.pcss index fa36f0e114f..47541dc452a 100644 --- a/res/css/views/dialogs/_VerifyEMailDialog.pcss +++ b/res/css/views/dialogs/_VerifyEMailDialog.pcss @@ -20,8 +20,8 @@ limitations under the License. .mx_Dialog { color: $primary-content; - font-size: 14px; - padding: 16px; + font-size: $font-14px; + padding: $spacing-24 $spacing-24 $spacing-16; text-align: center; width: 485px; @@ -34,5 +34,14 @@ limitations under the License. color: $secondary-content; line-height: 20px; } + + .mx_AuthBody_did-not-receive { + justify-content: center; + margin-bottom: $spacing-8; + } + + .mx_Dialog_cancelButton { + right: 10px; + } } } diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss index 11fd44207ae..6b802197dc7 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss @@ -17,7 +17,7 @@ limitations under the License. .mx_AccessSecretStorageDialog { .mx_AccessSecretStorageDialog_titleWithIcon { &::before { - content: ''; + content: ""; display: inline-block; width: 24px; height: 24px; @@ -35,11 +35,11 @@ limitations under the License. } &.mx_AccessSecretStorageDialog_secureBackupTitle::before { - mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); + mask-image: url("$(res)/img/feather-customised/secure-backup.svg"); } &.mx_AccessSecretStorageDialog_securePhraseTitle::before { - mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); + mask-image: url("$(res)/img/feather-customised/secure-phrase.svg"); } } @@ -88,7 +88,7 @@ limitations under the License. color: $accent; &::before { - mask-image: url('$(res)/img/feather-customised/check.svg'); + mask-image: url("$(res)/img/feather-customised/check.svg"); background-color: $accent; } } @@ -97,19 +97,19 @@ limitations under the License. color: $alert; &::before { - mask-image: url('$(res)/img/feather-customised/x.svg'); + mask-image: url("$(res)/img/feather-customised/x.svg"); background-color: $alert; } } } .mx_Dialog_buttons { - $spacingStart: $spacing-24; /* 16px icon + 8px padding */ + $spacingStart: $spacing-24; /* 16px icon + 8px padding */ text-align: initial; display: flex; flex-flow: column; - gap: 14px; /* TODO: spacing variable */ + gap: 14px; /* TODO: spacing variable */ .mx_Dialog_buttons_additive { float: none; diff --git a/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss b/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss index f9a6a24516f..2c624e835a2 100644 --- a/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss +++ b/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss @@ -49,7 +49,7 @@ limitations under the License. } .mx_CreateSecretStorageDialog_titleWithIcon::before { - content: ''; + content: ""; display: inline-block; width: 24px; height: 24px; @@ -60,14 +60,15 @@ limitations under the License. } .mx_CreateSecretStorageDialog_secureBackupTitle::before { - mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); + mask-image: url("$(res)/img/feather-customised/secure-backup.svg"); } .mx_CreateSecretStorageDialog_securePhraseTitle::before { - mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); + mask-image: url("$(res)/img/feather-customised/secure-phrase.svg"); } -.mx_CreateSecretStorageDialog_centeredTitle, .mx_CreateSecretStorageDialog_centeredBody { +.mx_CreateSecretStorageDialog_centeredTitle, +.mx_CreateSecretStorageDialog_centeredBody { text-align: center; } @@ -110,11 +111,11 @@ limitations under the License. } .mx_CreateSecretStorageDialog_optionIcon_securePhrase { - mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); + mask-image: url("$(res)/img/feather-customised/secure-phrase.svg"); } .mx_CreateSecretStorageDialog_optionIcon_secureBackup { - mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); + mask-image: url("$(res)/img/feather-customised/secure-backup.svg"); } .mx_CreateSecretStorageDialog_passPhraseContainer { diff --git a/res/css/views/elements/_AccessibleButton.pcss b/res/css/views/elements/_AccessibleButton.pcss index a9dd3307a9b..38ab7b8eb6b 100644 --- a/res/css/views/elements/_AccessibleButton.pcss +++ b/res/css/views/elements/_AccessibleButton.pcss @@ -68,7 +68,7 @@ limitations under the License. background-color: $accent; &::before { - mask-image: url('$(res)/img/feather-customised/check.svg'); + mask-image: url("$(res)/img/feather-customised/check.svg"); } } @@ -76,7 +76,7 @@ limitations under the License. background-color: $alert; &::before { - mask-image: url('$(res)/img/feather-customised/x.svg'); + mask-image: url("$(res)/img/feather-customised/x.svg"); } } diff --git a/res/css/views/elements/_CopyableText.pcss b/res/css/views/elements/_CopyableText.pcss index 4ba88b62a87..e6b3b1ebf92 100644 --- a/res/css/views/elements/_CopyableText.pcss +++ b/res/css/views/elements/_CopyableText.pcss @@ -40,7 +40,7 @@ limitations under the License. display: block; /* center to first line */ position: relative; - top: .15em; + top: 0.15em; &::before { content: ""; diff --git a/res/css/views/elements/_DialPadBackspaceButton.pcss b/res/css/views/elements/_DialPadBackspaceButton.pcss index 40e4af7025d..b3dbf06d36d 100644 --- a/res/css/views/elements/_DialPadBackspaceButton.pcss +++ b/res/css/views/elements/_DialPadBackspaceButton.pcss @@ -23,7 +23,7 @@ limitations under the License. /* force this element to appear on the DOM */ content: ""; - background-color: #8D97A5; + background-color: #8d97a5; width: inherit; height: inherit; top: 0px; @@ -32,7 +32,7 @@ limitations under the License. display: inline-block; vertical-align: middle; - mask-image: url('$(res)/img/element-icons/call/delete.svg'); + mask-image: url("$(res)/img/element-icons/call/delete.svg"); mask-position: 8px; mask-size: 20px; mask-repeat: no-repeat; diff --git a/res/css/views/elements/_Dropdown.pcss b/res/css/views/elements/_Dropdown.pcss index 6317aa7503d..0bda660c41f 100644 --- a/res/css/views/elements/_Dropdown.pcss +++ b/res/css/views/elements/_Dropdown.pcss @@ -50,7 +50,7 @@ limitations under the License. width: 10px; height: 6px; padding-right: 9px; - mask: url('$(res)/img/feather-customised/dropdown-arrow.svg'); + mask: url("$(res)/img/feather-customised/dropdown-arrow.svg"); mask-repeat: no-repeat; background: $primary-content; } diff --git a/res/css/views/elements/_ExternalLink.pcss b/res/css/views/elements/_ExternalLink.pcss index 3a2e9830341..0867b088b58 100644 --- a/res/css/views/elements/_ExternalLink.pcss +++ b/res/css/views/elements/_ExternalLink.pcss @@ -20,7 +20,7 @@ limitations under the License. .mx_ExternalLink_icon { display: inline-block; - mask-image: url('$(res)/img/external-link.svg'); + mask-image: url("$(res)/img/external-link.svg"); background-color: currentColor; mask-repeat: no-repeat; mask-size: contain; diff --git a/res/css/views/elements/_FacePile.pcss b/res/css/views/elements/_FacePile.pcss index 7f890a0bb73..4b2a732aa08 100644 --- a/res/css/views/elements/_FacePile.pcss +++ b/res/css/views/elements/_FacePile.pcss @@ -56,7 +56,7 @@ limitations under the License. mask-position: center; mask-size: 20px; mask-repeat: no-repeat; - mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); + mask-image: url("$(res)/img/element-icons/room/ellipsis.svg"); } } } diff --git a/res/css/views/elements/_Field.pcss b/res/css/views/elements/_Field.pcss index 19aec150d15..48ad979fd8b 100644 --- a/res/css/views/elements/_Field.pcss +++ b/res/css/views/elements/_Field.pcss @@ -65,7 +65,7 @@ limitations under the License. right: 10px; width: 10px; height: 6px; - mask: url('$(res)/img/feather-customised/dropdown-arrow.svg'); + mask: url("$(res)/img/feather-customised/dropdown-arrow.svg"); mask-repeat: no-repeat; background-color: $primary-content; z-index: 1; @@ -96,10 +96,7 @@ limitations under the License. } .mx_Field label { - transition: - font-size 0.25s ease-out 0.1s, - color 0.25s ease-out 0.1s, - transform 0.25s ease-out 0.1s, + transition: font-size 0.25s ease-out 0.1s, color 0.25s ease-out 0.1s, transform 0.25s ease-out 0.1s, background-color 0.25s ease-out 0.1s; background-color: transparent; font-size: $font-14px; @@ -121,10 +118,7 @@ limitations under the License. .mx_Field input:not(:placeholder-shown) + label, .mx_Field textarea:focus + label, .mx_Field textarea:not(:placeholder-shown) + label { - transition: - font-size 0.25s ease-out 0s, - color 0.25s ease-out 0s, - transform 0.25s ease-out 0s, + transition: font-size 0.25s ease-out 0s, color 0.25s ease-out 0s, transform 0.25s ease-out 0s, background-color 0.25s ease-out 0s; font-size: $font-10px; transform: translateY(-13px); diff --git a/res/css/views/elements/_ImageView.pcss b/res/css/views/elements/_ImageView.pcss index c2dbbf4fd4b..969e34924bf 100644 --- a/res/css/views/elements/_ImageView.pcss +++ b/res/css/views/elements/_ImageView.pcss @@ -97,7 +97,7 @@ $button-gap: 24px; display: block; &::before { - content: ''; + content: ""; height: $icon-size; width: $icon-size; mask-repeat: no-repeat; @@ -109,27 +109,27 @@ $button-gap: 24px; } .mx_ImageView_button_rotateCW::before { - mask-image: url('$(res)/img/image-view/rotate-cw.svg'); + mask-image: url("$(res)/img/image-view/rotate-cw.svg"); } .mx_ImageView_button_rotateCCW::before { - mask-image: url('$(res)/img/image-view/rotate-ccw.svg'); + mask-image: url("$(res)/img/image-view/rotate-ccw.svg"); } .mx_ImageView_button_zoomOut::before { - mask-image: url('$(res)/img/image-view/zoom-out.svg'); + mask-image: url("$(res)/img/image-view/zoom-out.svg"); } .mx_ImageView_button_zoomIn::before { - mask-image: url('$(res)/img/image-view/zoom-in.svg'); + mask-image: url("$(res)/img/image-view/zoom-in.svg"); } .mx_ImageView_button_download::before { - mask-image: url('$(res)/img/image-view/download.svg'); + mask-image: url("$(res)/img/image-view/download.svg"); } .mx_ImageView_button_more::before { - mask-image: url('$(res)/img/image-view/more.svg'); + mask-image: url("$(res)/img/image-view/more.svg"); } .mx_ImageView_button_close { @@ -139,7 +139,7 @@ $button-gap: 24px; &::before { width: $button-size; height: $button-size; - mask-image: url('$(res)/img/image-view/close.svg'); + mask-image: url("$(res)/img/image-view/close.svg"); mask-size: 40%; } } diff --git a/res/css/views/elements/_InfoTooltip.pcss b/res/css/views/elements/_InfoTooltip.pcss index 5329e7f1f84..566abea29cd 100644 --- a/res/css/views/elements/_InfoTooltip.pcss +++ b/res/css/views/elements/_InfoTooltip.pcss @@ -28,14 +28,14 @@ limitations under the License. width: 16px; height: 16px; mask-position: center; - content: ''; + content: ""; vertical-align: middle; } .mx_InfoTooltip_icon_info::before { - mask-image: url('$(res)/img/element-icons/info.svg'); + mask-image: url("$(res)/img/element-icons/info.svg"); } .mx_InfoTooltip_icon_warning::before { - mask-image: url('$(res)/img/element-icons/warning.svg'); + mask-image: url("$(res)/img/element-icons/warning.svg"); } diff --git a/res/css/views/elements/_InlineSpinner.pcss b/res/css/views/elements/_InlineSpinner.pcss index e5a6601a757..52130416df4 100644 --- a/res/css/views/elements/_InlineSpinner.pcss +++ b/res/css/views/elements/_InlineSpinner.pcss @@ -19,7 +19,8 @@ limitations under the License. vertical-align: middle; } -.mx_InlineSpinner img, .mx_InlineSpinner_icon { +.mx_InlineSpinner img, +.mx_InlineSpinner_icon { margin: -6px 6px; vertical-align: middle; } diff --git a/res/css/views/elements/_InviteReason.pcss b/res/css/views/elements/_InviteReason.pcss index 8024ed59a33..b635ee07e74 100644 --- a/res/css/views/elements/_InviteReason.pcss +++ b/res/css/views/elements/_InviteReason.pcss @@ -38,7 +38,7 @@ limitations under the License. content: ""; margin-right: 8px; background-color: $secondary-content; - mask-image: url('$(res)/img/feather-customised/eye.svg'); + mask-image: url("$(res)/img/feather-customised/eye.svg"); display: inline-block; width: 18px; height: 14px; diff --git a/res/css/views/elements/_MiniAvatarUploader.pcss b/res/css/views/elements/_MiniAvatarUploader.pcss index e0949c63659..b28886a103b 100644 --- a/res/css/views/elements/_MiniAvatarUploader.pcss +++ b/res/css/views/elements/_MiniAvatarUploader.pcss @@ -50,7 +50,7 @@ limitations under the License. background-color: $secondary-content; mask-position: center; mask-repeat: no-repeat; - mask-image: url('$(res)/img/element-icons/camera.svg'); + mask-image: url("$(res)/img/element-icons/camera.svg"); mask-size: 16px; z-index: 2; } diff --git a/res/css/views/elements/_RichText.pcss b/res/css/views/elements/_RichText.pcss index 603ab24e8f9..ddb08ae9e15 100644 --- a/res/css/views/elements/_RichText.pcss +++ b/res/css/views/elements/_RichText.pcss @@ -20,7 +20,7 @@ } .mx_Markdown_CODE { - padding: .2em 0; + padding: 0.2em 0; margin: 0; font-size: 85%; background-color: $rte-code-bg-color; diff --git a/res/css/views/elements/_RoomAliasField.pcss b/res/css/views/elements/_RoomAliasField.pcss index 96222caca4c..94f6c12a143 100644 --- a/res/css/views/elements/_RoomAliasField.pcss +++ b/res/css/views/elements/_RoomAliasField.pcss @@ -34,7 +34,8 @@ limitations under the License. font-weight: normal; } - .mx_Field_prefix, .mx_Field_postfix { + .mx_Field_prefix, + .mx_Field_postfix { color: $info-plinth-fg-color; border-left: none; border-right: none; diff --git a/res/css/views/elements/_ServerPicker.pcss b/res/css/views/elements/_ServerPicker.pcss index a273c6431c5..b993f8bba4e 100644 --- a/res/css/views/elements/_ServerPicker.pcss +++ b/res/css/views/elements/_ServerPicker.pcss @@ -45,7 +45,7 @@ limitations under the License. position: relative; &::before { - content: ''; + content: ""; width: 24px; height: 24px; position: absolute; @@ -54,7 +54,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - mask-image: url('$(res)/img/element-icons/i.svg'); + mask-image: url("$(res)/img/element-icons/i.svg"); background: #ffffff; } } diff --git a/res/css/views/elements/_SettingsFlag.pcss b/res/css/views/elements/_SettingsFlag.pcss index a581edae67d..b3908a158ad 100644 --- a/res/css/views/elements/_SettingsFlag.pcss +++ b/res/css/views/elements/_SettingsFlag.pcss @@ -56,8 +56,13 @@ limitations under the License. color: $secondary-content; /* Support code/pre elements in settings flag descriptions */ - pre, code { + pre, + code { font-family: $monospace-font-family !important; background-color: $rte-code-bg-color; } + + .mx_SettingsTab_microcopy_warning::before { + content: "⚠️ "; + } } diff --git a/res/css/views/elements/_StyledCheckbox.pcss b/res/css/views/elements/_StyledCheckbox.pcss index a67e32ec828..2a4cb3635ec 100644 --- a/res/css/views/elements/_StyledCheckbox.pcss +++ b/res/css/views/elements/_StyledCheckbox.pcss @@ -53,7 +53,7 @@ limitations under the License. height: 100%; width: 100%; - mask-image: url('$(res)/img/feather-customised/check.svg'); + mask-image: url("$(res)/img/feather-customised/check.svg"); mask-position: center; mask-size: 100%; mask-repeat: no-repeat; diff --git a/res/css/views/elements/_TagComposer.pcss b/res/css/views/elements/_TagComposer.pcss index ca073ae6bdb..3468a70eb66 100644 --- a/res/css/views/elements/_TagComposer.pcss +++ b/res/css/views/elements/_TagComposer.pcss @@ -50,7 +50,6 @@ limitations under the License. } .mx_Tag { - font-size: $font-15px; display: inline-flex; @@ -63,7 +62,7 @@ limitations under the License. color: $primary-content; background: $quinary-content; - >svg:first-child { + > svg:first-child { width: 1em; color: $secondary-content; transform: scale(1.25); @@ -81,7 +80,7 @@ limitations under the License. position: relative; svg { - width: .5em; + width: 0.5em; vertical-align: middle; } } diff --git a/res/css/views/elements/_ToggleSwitch.pcss b/res/css/views/elements/_ToggleSwitch.pcss index c4ad3e0a28c..4dc7e294366 100644 --- a/res/css/views/elements/_ToggleSwitch.pcss +++ b/res/css/views/elements/_ToggleSwitch.pcss @@ -17,7 +17,7 @@ limitations under the License. .mx_ToggleSwitch { --ToggleSwitch-min-width: $font-44px; - transition: background-color 0.20s ease-out 0.1s; + transition: background-color 0.2s ease-out 0.1s; width: $font-44px; height: $font-20px; diff --git a/res/css/views/elements/_Tooltip.pcss b/res/css/views/elements/_Tooltip.pcss index 1c28e795205..9ed51010062 100644 --- a/res/css/views/elements/_Tooltip.pcss +++ b/res/css/views/elements/_Tooltip.pcss @@ -47,7 +47,7 @@ limitations under the License. } .mx_Tooltip_chevron::after { - content: ''; + content: ""; width: 0; height: 0; border-top: 6px solid transparent; @@ -71,7 +71,7 @@ limitations under the License. max-width: 300px; word-break: break-word; - background-color: #21262C; /* Same on both themes */ + background-color: #21262c; /* Same on both themes */ color: $accent-fg-color; border: 0; text-align: center; diff --git a/res/css/views/elements/_TooltipButton.pcss b/res/css/views/elements/_TooltipButton.pcss index 5b7c0ce14d6..cc91c6d112c 100644 --- a/res/css/views/elements/_TooltipButton.pcss +++ b/res/css/views/elements/_TooltipButton.pcss @@ -35,7 +35,7 @@ limitations under the License. } .mx_TooltipButton:hover { - opacity: 1.0; + opacity: 1; } .mx_TooltipButton_container { diff --git a/res/css/views/elements/_UseCaseSelection.pcss b/res/css/views/elements/_UseCaseSelection.pcss index 2b907e7b67d..26b2c5652fc 100644 --- a/res/css/views/elements/_UseCaseSelection.pcss +++ b/res/css/views/elements/_UseCaseSelection.pcss @@ -88,7 +88,8 @@ limitations under the License. } .mx_UseCaseSelection_selected { - .mx_UseCaseSelection_slideIn, .mx_UseCaseSelection_slideInDelayed { + .mx_UseCaseSelection_slideIn, + .mx_UseCaseSelection_slideInDelayed { animation-delay: 800ms; animation-duration: 300ms; animation-fill-mode: forwards; diff --git a/res/css/views/elements/_UseCaseSelectionButton.pcss b/res/css/views/elements/_UseCaseSelectionButton.pcss index 7a44e793473..2ca7f6c9cf9 100644 --- a/res/css/views/elements/_UseCaseSelectionButton.pcss +++ b/res/css/views/elements/_UseCaseSelectionButton.pcss @@ -29,7 +29,7 @@ limitations under the License. .mx_UseCaseSelectionButton_icon { /* workaround: design expects a layering of two colors */ - background: linear-gradient(0deg, rgba(172, 59, 168, 0.15), rgba(172, 59, 168, 0.15)), #FFFFFF; + background: linear-gradient(0deg, rgba(172, 59, 168, 0.15), rgba(172, 59, 168, 0.15)), #ffffff; border-radius: 14px; padding: $spacing-8; margin-bottom: $spacing-16; @@ -39,7 +39,7 @@ limitations under the License. display: block; /* this has to remain the same color across all themes, as its background has a fixed color as well */ - background: #1E1E1E; + background: #1e1e1e; mask-position: center; mask-repeat: no-repeat; mask-size: contain; @@ -48,19 +48,20 @@ limitations under the License. } &.mx_UseCaseSelectionButton_messaging::before { - mask-image: url('$(res)/img/element-icons/chat-bubble.svg'); + mask-image: url("$(res)/img/element-icons/chat-bubble.svg"); } &.mx_UseCaseSelectionButton_work::before { - mask-image: url('$(res)/img/element-icons/view-community.svg'); + mask-image: url("$(res)/img/element-icons/view-community.svg"); } &.mx_UseCaseSelectionButton_community::before { - mask-image: url('$(res)/img/globe.svg'); + mask-image: url("$(res)/img/globe.svg"); } } - &:hover, &:focus { + &:hover, + &:focus { box-shadow: 0 $spacing-4 $spacing-8 rgba(0, 0, 0, 0.08); transform: translate(0, -$spacing-8); } @@ -87,7 +88,7 @@ limitations under the License. width: 12px; height: 12px; - mask-image: url('$(res)/img/element-icons/check-white.svg'); + mask-image: url("$(res)/img/element-icons/check-white.svg"); } } diff --git a/res/css/views/elements/_Validation.pcss b/res/css/views/elements/_Validation.pcss index 5546515d942..298c75da335 100644 --- a/res/css/views/elements/_Validation.pcss +++ b/res/css/views/elements/_Validation.pcss @@ -53,7 +53,7 @@ limitations under the License. color: $accent; &::before { - mask-image: url('$(res)/img/feather-customised/check.svg'); + mask-image: url("$(res)/img/feather-customised/check.svg"); background-color: $accent; } } @@ -62,7 +62,7 @@ limitations under the License. color: $alert; &::before { - mask-image: url('$(res)/img/feather-customised/x.svg'); + mask-image: url("$(res)/img/feather-customised/x.svg"); background-color: $alert; } } diff --git a/res/css/views/emojipicker/_EmojiPicker.pcss b/res/css/views/emojipicker/_EmojiPicker.pcss index 99bdc011cc8..e613b81eee3 100644 --- a/res/css/views/emojipicker/_EmojiPicker.pcss +++ b/res/css/views/emojipicker/_EmojiPicker.pcss @@ -61,7 +61,7 @@ limitations under the License. .mx_EmojiPicker_anchor:not(.mx_CustomEmojiCategory)::before { background-color: $primary-content; - content: ''; + content: ""; display: inline-block; mask-size: 100%; mask-repeat: no-repeat; @@ -73,16 +73,36 @@ limitations under the License. background-color: $focus-bg-color; } -.mx_EmojiPicker_anchor_activity::before { mask-image: url('$(res)/img/emojipicker/activity.svg'); } -.mx_EmojiPicker_anchor_custom::before { mask-image: url('$(res)/img/emojipicker/custom.svg'); } -.mx_EmojiPicker_anchor_flags::before { mask-image: url('$(res)/img/emojipicker/flags.svg'); } -.mx_EmojiPicker_anchor_foods::before { mask-image: url('$(res)/img/emojipicker/foods.svg'); } -.mx_EmojiPicker_anchor_nature::before { mask-image: url('$(res)/img/emojipicker/nature.svg'); } -.mx_EmojiPicker_anchor_objects::before { mask-image: url('$(res)/img/emojipicker/objects.svg'); } -.mx_EmojiPicker_anchor_people::before { mask-image: url('$(res)/img/emojipicker/people.svg'); } -.mx_EmojiPicker_anchor_places::before { mask-image: url('$(res)/img/emojipicker/places.svg'); } -.mx_EmojiPicker_anchor_recent::before { mask-image: url('$(res)/img/emojipicker/recent.svg'); } -.mx_EmojiPicker_anchor_symbols::before { mask-image: url('$(res)/img/emojipicker/symbols.svg'); } +.mx_EmojiPicker_anchor_activity::before { + mask-image: url("$(res)/img/emojipicker/activity.svg"); +} +.mx_EmojiPicker_anchor_custom::before { + mask-image: url("$(res)/img/emojipicker/custom.svg"); +} +.mx_EmojiPicker_anchor_flags::before { + mask-image: url("$(res)/img/emojipicker/flags.svg"); +} +.mx_EmojiPicker_anchor_foods::before { + mask-image: url("$(res)/img/emojipicker/foods.svg"); +} +.mx_EmojiPicker_anchor_nature::before { + mask-image: url("$(res)/img/emojipicker/nature.svg"); +} +.mx_EmojiPicker_anchor_objects::before { + mask-image: url("$(res)/img/emojipicker/objects.svg"); +} +.mx_EmojiPicker_anchor_people::before { + mask-image: url("$(res)/img/emojipicker/people.svg"); +} +.mx_EmojiPicker_anchor_places::before { + mask-image: url("$(res)/img/emojipicker/places.svg"); +} +.mx_EmojiPicker_anchor_recent::before { + mask-image: url("$(res)/img/emojipicker/recent.svg"); +} +.mx_EmojiPicker_anchor_symbols::before { + mask-image: url("$(res)/img/emojipicker/symbols.svg"); +} .mx_EmojiPicker_anchor_visible { border-bottom: 2px solid $accent; @@ -127,17 +147,17 @@ limitations under the License. } .mx_EmojiPicker_search_icon::after { - mask: url('$(res)/img/emojipicker/search.svg') no-repeat; + mask: url("$(res)/img/emojipicker/search.svg") no-repeat; mask-size: 100%; background-color: $primary-content; - content: ''; + content: ""; display: inline-block; width: 100%; height: 100%; } .mx_EmojiPicker_search_clear::after { - mask-image: url('$(res)/img/emojipicker/delete.svg'); + mask-image: url("$(res)/img/emojipicker/delete.svg"); } .mx_EmojiPicker_category { @@ -181,12 +201,13 @@ limitations under the License. } .mx_EmojiPicker_item_selected { - color: rgba(0, 0, 0, .5); + color: rgba(0, 0, 0, 0.5); border: 1px solid $accent; padding: 4px; } -.mx_EmojiPicker_category_label, .mx_EmojiPicker_preview_name { +.mx_EmojiPicker_category_label, +.mx_EmojiPicker_preview_name { font-size: $font-16px; font-weight: 600; margin: 0; @@ -218,7 +239,8 @@ limitations under the License. color: $light-fg-color; font-size: $font-14px; - &::before, &::after { + &::before, + &::after { content: ":"; } } diff --git a/res/css/views/location/_LocationPicker.pcss b/res/css/views/location/_LocationPicker.pcss index c131d4cd6a0..6598237b089 100644 --- a/res/css/views/location/_LocationPicker.pcss +++ b/res/css/views/location/_LocationPicker.pcss @@ -27,7 +27,8 @@ limitations under the License. /* and can overlap error message/close buttons */ /* hide it */ &.mx_LocationPicker_hasError { - .maplibregl-canvas-container, .maplibregl-control-container { + .maplibregl-canvas-container, + .maplibregl-control-container { display: none; } } diff --git a/res/css/views/messages/_CallEvent.pcss b/res/css/views/messages/_CallEvent.pcss index 1a11beaa4d4..b9f41e88253 100644 --- a/res/css/views/messages/_CallEvent.pcss +++ b/res/css/views/messages/_CallEvent.pcss @@ -41,9 +41,9 @@ limitations under the License. &.mx_CallEvent_inactive .mx_CallEvent_title::before { display: inline-block; vertical-align: middle; - content: ''; + content: ""; background-color: $secondary-content; - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url("$(res)/img/element-icons/call/video-call.svg"); mask-size: 16px; width: 16px; height: 16px; diff --git a/res/css/views/messages/_CreateEvent.pcss b/res/css/views/messages/_CreateEvent.pcss index db6e0244425..e616742f7c9 100644 --- a/res/css/views/messages/_CreateEvent.pcss +++ b/res/css/views/messages/_CreateEvent.pcss @@ -19,6 +19,6 @@ limitations under the License. &::before { background-color: $header-panel-text-primary-color; - mask-image: url('$(res)/img/element-icons/chat-bubbles.svg'); + mask-image: url("$(res)/img/element-icons/chat-bubbles.svg"); } } diff --git a/res/css/views/messages/_DateSeparator.pcss b/res/css/views/messages/_DateSeparator.pcss index 339531ddc1e..002da026e8b 100644 --- a/res/css/views/messages/_DateSeparator.pcss +++ b/res/css/views/messages/_DateSeparator.pcss @@ -50,6 +50,6 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); background-color: $tertiary-content; } diff --git a/res/css/views/messages/_HiddenBody.pcss b/res/css/views/messages/_HiddenBody.pcss index 14d003e669b..79eb7de6a4c 100644 --- a/res/css/views/messages/_HiddenBody.pcss +++ b/res/css/views/messages/_HiddenBody.pcss @@ -24,12 +24,12 @@ limitations under the License. height: 14px; width: 14px; background-color: $muted-fg-color; - mask-image: url('$(res)/img/element-icons/hide.svg'); + mask-image: url("$(res)/img/element-icons/hide.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: contain; - content: ''; + content: ""; position: absolute; top: 1px; left: 0; diff --git a/res/css/views/messages/_JumpToDatePicker.pcss b/res/css/views/messages/_JumpToDatePicker.pcss index d7ff865d284..e31fc383680 100644 --- a/res/css/views/messages/_JumpToDatePicker.pcss +++ b/res/css/views/messages/_JumpToDatePicker.pcss @@ -31,7 +31,8 @@ limitations under the License. margin: 0; margin-left: 8px; - &, & > input { + &, + & > input { border-radius: 8px; } } diff --git a/res/css/views/messages/_LegacyCallEvent.pcss b/res/css/views/messages/_LegacyCallEvent.pcss index 2b8126910a2..c51b486e669 100644 --- a/res/css/views/messages/_LegacyCallEvent.pcss +++ b/res/css/views/messages/_LegacyCallEvent.pcss @@ -39,7 +39,7 @@ limitations under the License. display: inline-flex; &::before { - content: ''; + content: ""; height: 16px; width: 16px; @@ -51,24 +51,24 @@ limitations under the License. } .mx_LegacyCallEvent_silence::before { - mask-image: url('$(res)/img/voip/silence.svg'); + mask-image: url("$(res)/img/voip/silence.svg"); } .mx_LegacyCallEvent_unSilence::before { - mask-image: url('$(res)/img/voip/un-silence.svg'); + mask-image: url("$(res)/img/voip/un-silence.svg"); } &.mx_LegacyCallEvent_voice { .mx_LegacyCallEvent_type_icon::before, .mx_LegacyCallEvent_content_button_callBack span::before, .mx_LegacyCallEvent_content_button_answer span::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + mask-image: url("$(res)/img/element-icons/call/voice-call.svg"); } &.mx_LegacyCallEvent_rejected, &.mx_LegacyCallEvent_noAnswer { .mx_LegacyCallEvent_type_icon::before { - mask-image: url('$(res)/img/voip/declined-voice.svg'); + mask-image: url("$(res)/img/voip/declined-voice.svg"); } } } @@ -77,13 +77,13 @@ limitations under the License. .mx_LegacyCallEvent_type_icon::before, .mx_LegacyCallEvent_content_button_callBack span::before, .mx_LegacyCallEvent_content_button_answer span::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url("$(res)/img/element-icons/call/video-call.svg"); } &.mx_LegacyCallEvent_rejected, &.mx_LegacyCallEvent_noAnswer { .mx_LegacyCallEvent_type_icon::before { - mask-image: url('$(res)/img/voip/declined-video.svg'); + mask-image: url("$(res)/img/voip/declined-video.svg"); } } } @@ -91,13 +91,13 @@ limitations under the License. &.mx_LegacyCallEvent_missed { &.mx_LegacyCallEvent_voice { .mx_LegacyCallEvent_type_icon::before { - mask-image: url('$(res)/img/voip/missed-voice.svg'); + mask-image: url("$(res)/img/voip/missed-voice.svg"); } } &.mx_LegacyCallEvent_video { .mx_LegacyCallEvent_type_icon::before { - mask-image: url('$(res)/img/voip/missed-video.svg'); + mask-image: url("$(res)/img/voip/missed-video.svg"); } } } @@ -142,7 +142,7 @@ limitations under the License. margin-right: 5px; &::before { - content: ''; + content: ""; position: absolute; height: 13px; width: 13px; @@ -179,7 +179,7 @@ limitations under the License. .mx_LegacyCallEvent_content_button_reject { span::before { - mask-image: url('$(res)/img/element-icons/call/hangup.svg'); + mask-image: url("$(res)/img/element-icons/call/hangup.svg"); } } diff --git a/res/css/views/messages/_MFileBody.pcss b/res/css/views/messages/_MFileBody.pcss index c0fe75229a1..0b7c90143d4 100644 --- a/res/css/views/messages/_MFileBody.pcss +++ b/res/css/views/messages/_MFileBody.pcss @@ -73,11 +73,11 @@ limitations under the License. margin-right: 12px; &::before { - content: ''; + content: ""; mask-repeat: no-repeat; mask-position: center; mask-size: cover; - mask-image: url('$(res)/img/element-icons/room/composer/attach.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/attach.svg"); background-color: $secondary-content; width: 15px; height: 15px; diff --git a/res/css/views/messages/_MImageBody.pcss b/res/css/views/messages/_MImageBody.pcss index 3c71dc1a32e..b8dd3a636ab 100644 --- a/res/css/views/messages/_MImageBody.pcss +++ b/res/css/views/messages/_MImageBody.pcss @@ -50,7 +50,7 @@ $timeline-image-border-radius: $border-radius-8px; background-color: $background; .mx_Blurhash > canvas { - animation: mx--anim-pulse 1.75s infinite cubic-bezier(.4, 0, .6, 1); + animation: mx--anim-pulse 1.75s infinite cubic-bezier(0.4, 0, 0.6, 1); } } @@ -113,7 +113,7 @@ $timeline-image-border-radius: $border-radius-8px; margin-right: 8px; background-color: $accent; - mask-image: url('$(res)/img/feather-customised/eye.svg'); + mask-image: url("$(res)/img/feather-customised/eye.svg"); display: inline-block; width: 18px; height: 14px; diff --git a/res/css/views/messages/_MJitsiWidgetEvent.pcss b/res/css/views/messages/_MJitsiWidgetEvent.pcss index 8bda80e7412..50cb9c34a2e 100644 --- a/res/css/views/messages/_MJitsiWidgetEvent.pcss +++ b/res/css/views/messages/_MJitsiWidgetEvent.pcss @@ -17,6 +17,6 @@ limitations under the License. .mx_EventTileBubble.mx_MJitsiWidgetEvent { &::before { background-color: $header-panel-text-primary-color; /* XXX: Variable abuse */ - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url("$(res)/img/element-icons/call/video-call.svg"); } } diff --git a/res/css/views/messages/_MPollBody.pcss b/res/css/views/messages/_MPollBody.pcss index bc6b66ca653..64c6ff3bf5b 100644 --- a/res/css/views/messages/_MPollBody.pcss +++ b/res/css/views/messages/_MPollBody.pcss @@ -32,7 +32,7 @@ limitations under the License. } h2::before { - content: ''; + content: ""; position: relative; display: inline-block; margin-right: 12px; @@ -44,7 +44,7 @@ limitations under the License. mask-repeat: no-repeat; mask-size: contain; mask-position: center; - mask-image: url('$(res)/img/element-icons/room/composer/poll.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/poll.svg"); } .mx_MPollBody_option { @@ -55,11 +55,13 @@ limitations under the License. max-width: 550px; background-color: $background; - .mx_StyledRadioButton, .mx_MPollBody_endedOption { + .mx_StyledRadioButton, + .mx_MPollBody_endedOption { margin-bottom: 8px; } - .mx_StyledRadioButton_content, .mx_MPollBody_endedOption { + .mx_StyledRadioButton_content, + .mx_MPollBody_endedOption { padding-top: 2px; margin-right: 0px; } @@ -111,16 +113,18 @@ limitations under the License. } /* options not actionable in these states */ - .mx_MPollBody_option_checked, .mx_MPollBody_option_ended { + .mx_MPollBody_option_checked, + .mx_MPollBody_option_ended { pointer-events: none; } - .mx_StyledRadioButton_checked, .mx_MPollBody_endedOptionWinner { + .mx_StyledRadioButton_checked, + .mx_MPollBody_endedOptionWinner { input[type="radio"] + div { border-width: 2px; border-color: $accent; background-color: $accent; - background-image: url('$(res)/img/element-icons/check-white.svg'); + background-image: url("$(res)/img/element-icons/check-white.svg"); background-size: 12px; background-repeat: no-repeat; background-position: center; @@ -132,7 +136,7 @@ limitations under the License. } .mx_MPollBody_endedOptionWinner .mx_MPollBody_optionDescription .mx_MPollBody_optionVoteCount::before { - content: ''; + content: ""; position: relative; display: inline-block; margin-right: 4px; @@ -143,7 +147,7 @@ limitations under the License. mask-repeat: no-repeat; mask-size: contain; mask-position: center; - mask-image: url('$(res)/img/element-icons/trophy.svg'); + mask-image: url("$(res)/img/element-icons/trophy.svg"); } .mx_MPollBody_totalVotes { diff --git a/res/css/views/messages/_MessageActionBar.pcss b/res/css/views/messages/_MessageActionBar.pcss index c97a2c673d8..8dd22b84bc8 100644 --- a/res/css/views/messages/_MessageActionBar.pcss +++ b/res/css/views/messages/_MessageActionBar.pcss @@ -43,7 +43,7 @@ limitations under the License. /* previous event while trying to mouse into the action bar or from the */ /* react button to its tooltip. */ &::before { - content: ''; + content: ""; position: absolute; /* tooltip safe mousing area + tooltip overhang + */ /* action bar + action bar offset from event */ @@ -56,7 +56,11 @@ limitations under the License. cursor: initial; /* stylelint-disable-next-line max-line-length */ - .mx_GenericEventListSummary[data-layout="bubble"] .mx_GenericEventListSummary_toggle ~ .mx_GenericEventListSummary_unstyledList .mx_EventTile_info:first-of-type & { + .mx_GenericEventListSummary[data-layout="bubble"] + .mx_GenericEventListSummary_toggle + ~ .mx_GenericEventListSummary_unstyledList + .mx_EventTile_info:first-of-type + & { /* improve clickability of "collapse" link button on bubble layout by reducing width and height values */ /* mx_GenericEventListSummary_toggle ~: to apply rules to action bar when "collapse" button is available */ /* mx_EventTile_info:first-of-type: to apply rules to the info event tile just under "collapse" button */ @@ -76,7 +80,7 @@ limitations under the License. } } - >* { + > * { white-space: nowrap; display: inline-block; position: relative; @@ -107,21 +111,13 @@ limitations under the License. &:disabled, &[disabled] { cursor: not-allowed; - opacity: .75; + opacity: 0.75; } &:hover { color: $primary-content; } - &.mx_MessageActionBar_threadButton { - - .mx_Indicator { - background: $links; - animation-iteration-count: infinite; - } - } - &.mx_MessageActionBar_favouriteButton_fillstar { color: var(--MessageActionBar-star-button-color); } diff --git a/res/css/views/messages/_ReactionsRow.pcss b/res/css/views/messages/_ReactionsRow.pcss index dbf5ab99649..7d54e3ce206 100644 --- a/res/css/views/messages/_ReactionsRow.pcss +++ b/res/css/views/messages/_ReactionsRow.pcss @@ -29,7 +29,7 @@ limitations under the License. margin-right: 4px; &::before { - content: ''; + content: ""; position: absolute; height: 100%; width: 100%; @@ -37,14 +37,15 @@ limitations under the License. mask-repeat: no-repeat; mask-position: center; background-color: $tertiary-content; - mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); + mask-image: url("$(res)/img/element-icons/room/message-bar/emoji.svg"); } &.mx_ReactionsRow_addReactionButton_active { visibility: visible; /* keep showing whilst the context menu is shown */ } - &:hover, &.mx_ReactionsRow_addReactionButton_active { + &:hover, + &.mx_ReactionsRow_addReactionButton_active { &::before { background-color: $primary-content; } diff --git a/res/css/views/messages/_RedactedBody.pcss b/res/css/views/messages/_RedactedBody.pcss index 600ac0c6b70..a85e97411af 100644 --- a/res/css/views/messages/_RedactedBody.pcss +++ b/res/css/views/messages/_RedactedBody.pcss @@ -24,11 +24,11 @@ limitations under the License. height: 14px; width: 14px; background-color: $muted-fg-color; - mask-image: url('$(res)/img/feather-customised/trash.custom.svg'); + mask-image: url("$(res)/img/feather-customised/trash.custom.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: contain; - content: ''; + content: ""; position: absolute; top: 1px; left: 0; diff --git a/res/css/views/messages/_common_CryptoEvent.pcss b/res/css/views/messages/_common_CryptoEvent.pcss index 33710125097..681c117b22b 100644 --- a/res/css/views/messages/_common_CryptoEvent.pcss +++ b/res/css/views/messages/_common_CryptoEvent.pcss @@ -20,14 +20,14 @@ limitations under the License. /* white infill for the transparency */ &.mx_cryptoEvent_icon::before { background-color: #ffffff; - mask-image: url('$(res)/img/e2e/normal.svg'); + mask-image: url("$(res)/img/e2e/normal.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: 80%; } &.mx_cryptoEvent_icon::after { - mask-image: url('$(res)/img/e2e/normal.svg'); + mask-image: url("$(res)/img/e2e/normal.svg"); background-color: $header-panel-text-primary-color; } @@ -41,7 +41,8 @@ limitations under the License. background-color: $alert; } - .mx_cryptoEvent_state, .mx_cryptoEvent_buttons { + .mx_cryptoEvent_state, + .mx_cryptoEvent_buttons { grid-column: 3; grid-row: 1 / 3; } diff --git a/res/css/views/right_panel/_BaseCard.pcss b/res/css/views/right_panel/_BaseCard.pcss index f0d8ce946a7..229b4685945 100644 --- a/res/css/views/right_panel/_BaseCard.pcss +++ b/res/css/views/right_panel/_BaseCard.pcss @@ -70,7 +70,7 @@ limitations under the License. &::before { transform: rotate(90deg); mask-size: 22px; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); } /* Header title with the back button */ @@ -89,7 +89,7 @@ limitations under the License. margin-inline-end: calc(var(--BaseCard_header_button-margin) - $spacing-4); &::before { - mask-image: url('$(res)/img/icons-close.svg'); + mask-image: url("$(res)/img/icons-close.svg"); mask-size: 8px; } } @@ -115,7 +115,7 @@ limitations under the License. height: var(--BaseCard_header-button-size); &::after { - content: ''; + content: ""; position: absolute; inset-block-start: 0; inset-inline-start: 0; diff --git a/res/css/views/right_panel/_RoomSummaryCard.pcss b/res/css/views/right_panel/_RoomSummaryCard.pcss index 0b2fb69c6bb..a6621b642db 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.pcss +++ b/res/css/views/right_panel/_RoomSummaryCard.pcss @@ -30,7 +30,8 @@ limitations under the License. text-overflow: ellipsis; } - h2, .mx_RoomSummaryCard_alias { + h2, + .mx_RoomSummaryCard_alias { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; @@ -52,7 +53,7 @@ limitations under the License. border: 3px solid $dark-panel-bg-color; &::before { - content: ''; + content: ""; position: absolute; top: 13px; left: 13px; @@ -61,7 +62,7 @@ limitations under the License. mask-size: cover; mask-repeat: no-repeat; mask-position: center; - mask-image: url('$(res)/img/e2e/disabled.svg'); + mask-image: url("$(res)/img/e2e/disabled.svg"); background-color: #ffffff; } } @@ -69,21 +70,21 @@ limitations under the License. .mx_RoomSummaryCard_e2ee_normal { background-color: #424446; &::before { - mask-image: url('$(res)/img/e2e/normal.svg'); + mask-image: url("$(res)/img/e2e/normal.svg"); } } .mx_RoomSummaryCard_e2ee_verified { background-color: #8BC34A; &::before { - mask-image: url('$(res)/img/e2e/verified.svg'); + mask-image: url("$(res)/img/e2e/verified.svg"); } } .mx_RoomSummaryCard_e2ee_warning { background-color: #E53935; &::before { - mask-image: url('$(res)/img/e2e/warning.svg'); + mask-image: url("$(res)/img/e2e/warning.svg"); } } } @@ -94,7 +95,7 @@ limitations under the License. padding-left: 44px; &::before { - content: ''; + content: ""; position: absolute; top: 8px; left: 10px; @@ -143,7 +144,7 @@ limitations under the License. &:hover { &::after { - content: ''; + content: ""; position: absolute; height: 24px; width: 24px; @@ -155,7 +156,7 @@ limitations under the License. } &::before { - content: ''; + content: ""; position: absolute; height: 16px; width: 16px; @@ -170,7 +171,7 @@ limitations under the License. right: 8px; &::before { - mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); } } .mx_RoomSummaryCard_app_maximiseToggle { @@ -186,7 +187,7 @@ limitations under the License. right: 56px; /* 2*24 + 8 */ display: none; &::before { - mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); + mask-image: url("$(res)/img/element-icons/room/ellipsis.svg"); } } @@ -252,25 +253,25 @@ limitations under the License. } .mx_RoomSummaryCard_icon_files::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); + mask-image: url("$(res)/img/element-icons/room/files.svg"); } .mx_RoomSummaryCard_icon_pins::before { - mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); } .mx_RoomSummaryCard_icon_threads::before { - mask-image: url('$(res)/img/element-icons/message/thread.svg'); + mask-image: url("$(res)/img/element-icons/message/thread.svg"); } .mx_RoomSummaryCard_icon_share::before { - mask-image: url('$(res)/img/element-icons/room/share.svg'); + mask-image: url("$(res)/img/element-icons/room/share.svg"); } .mx_RoomSummaryCard_icon_settings::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); } .mx_RoomSummaryCard_icon_export::before { - mask-image: url('$(res)/img/element-icons/export.svg'); + mask-image: url("$(res)/img/element-icons/export.svg"); } diff --git a/res/css/views/right_panel/_ThreadPanel.pcss b/res/css/views/right_panel/_ThreadPanel.pcss index 8ad44f5a406..aa3282cf420 100644 --- a/res/css/views/right_panel/_ThreadPanel.pcss +++ b/res/css/views/right_panel/_ThreadPanel.pcss @@ -94,7 +94,8 @@ limitations under the License. } } - .mx_RoomView_messagePanel { /* To avoid the rule from being applied to .mx_ThreadPanel_empty */ + .mx_RoomView_messagePanel { + /* To avoid the rule from being applied to .mx_ThreadPanel_empty */ .mx_RoomView_messageListWrapper { width: calc(100% + 6px); /* 8px - 2px */ } @@ -149,11 +150,11 @@ limitations under the License. } .mx_ThreadPanel_viewInRoom::before { - mask-image: url('$(res)/img/element-icons/view-in-room.svg'); + mask-image: url("$(res)/img/element-icons/view-in-room.svg"); } .mx_ThreadPanel_copyLinkToThread::before { - mask-image: url('$(res)/img/element-icons/link.svg'); + mask-image: url("$(res)/img/element-icons/link.svg"); } .mx_ThreadPanel_empty { @@ -202,7 +203,7 @@ limitations under the License. font-size: $font-12px; line-height: $font-15px; - >b { + > b { font-weight: $font-semi-bold; } } diff --git a/res/css/views/right_panel/_TimelineCard.pcss b/res/css/views/right_panel/_TimelineCard.pcss index 7e50457ae80..13f949b39e5 100644 --- a/res/css/views/right_panel/_TimelineCard.pcss +++ b/res/css/views/right_panel/_TimelineCard.pcss @@ -37,7 +37,7 @@ limitations under the License. padding-right: 11px; &::after { - content: ''; + content: ""; display: block; position: absolute; left: 0; @@ -171,13 +171,15 @@ limitations under the License. } .mx_GenericEventListSummary_unstyledList, /* RR next to a message on the event list summary */ - .mx_RoomView_MessageList { /* RR next to a message on the messsge list */ + .mx_RoomView_MessageList { + /* RR next to a message on the messsge list */ .mx_EventTile[data-layout="bubble"] { .mx_ReadReceiptGroup { /* 6px: scroll bar width (magic number) */ /* stylelint-disable-next-line declaration-colon-space-after */ - inset-inline-end: - calc(-1 * var(--ReadReceiptGroup_EventBubbleTile-spacing-end) + $container-gap-width + 6px); + inset-inline-end: calc( + -1 * var(--ReadReceiptGroup_EventBubbleTile-spacing-end) + $container-gap-width + 6px + ); } &.mx_EventTile_info { diff --git a/res/css/views/right_panel/_UserInfo.pcss b/res/css/views/right_panel/_UserInfo.pcss index e851e07c0f7..df452aff4a4 100644 --- a/res/css/views/right_panel/_UserInfo.pcss +++ b/res/css/views/right_panel/_UserInfo.pcss @@ -34,7 +34,7 @@ limitations under the License. height: 16px; width: 16px; padding: 4px; - mask-image: url('$(res)/img/minimise.svg'); + mask-image: url("$(res)/img/minimise.svg"); mask-repeat: no-repeat; mask-position: 7px center; background-color: $header-panel-text-primary-color; @@ -69,7 +69,7 @@ limitations under the License. } .mx_UserInfo_separator { - border-bottom: 1px solid rgba($primary-content, .1); + border-bottom: 1px solid rgba($primary-content, 0.1); } .mx_UserInfo_memberDetailsContainer { diff --git a/res/css/views/right_panel/_VerificationPanel.pcss b/res/css/views/right_panel/_VerificationPanel.pcss index d3475ecce01..9ab665b8fca 100644 --- a/res/css/views/right_panel/_VerificationPanel.pcss +++ b/res/css/views/right_panel/_VerificationPanel.pcss @@ -25,7 +25,6 @@ limitations under the License. .mx_UserInfo.mx_BaseCard { .mx_UserInfo_container:not(.mx_UserInfo_separator) { - > div > p { margin-top: 0; margin-bottom: 0; @@ -88,7 +87,8 @@ limitations under the License. } /* Special case styling for EncryptionPanel in a Modal dialog */ -.mx_Dialog, .mx_CompleteSecurity_body { +.mx_Dialog, +.mx_CompleteSecurity_body { .mx_VerificationPanel_QRPhase_startOptions { display: flex; margin-top: 10px; @@ -117,7 +117,8 @@ limitations under the License. max-width: 310px; justify-content: space-between; - canvas, .mx_VerificationPanel_QRPhase_noQR { + canvas, + .mx_VerificationPanel_QRPhase_noQR { width: 220px !important; height: 220px !important; background-color: #fff; diff --git a/res/css/views/rooms/_AppsDrawer.pcss b/res/css/views/rooms/_AppsDrawer.pcss index 25cfd58d520..5c86c4fc360 100644 --- a/res/css/views/rooms/_AppsDrawer.pcss +++ b/res/css/views/rooms/_AppsDrawer.pcss @@ -55,7 +55,7 @@ $MinWidth: 240px; /* We then render the pill handle in an ::after to keep it in the handle's */ /* area without being a massive line across the screen */ &::after { - content: ''; + content: ""; position: absolute; border-radius: $border-radius-3px; @@ -85,7 +85,7 @@ $MinWidth: 240px; width: 4px; border-radius: $border-radius-4px; - content: ''; + content: ""; background-color: $primary-content; opacity: 0.8; @@ -231,7 +231,7 @@ $MinWidth: 240px; &::before { background-color: $muted-fg-color; - content: ''; + content: ""; height: 24px; mask-position: center; mask-repeat: no-repeat; @@ -243,7 +243,7 @@ $MinWidth: 240px; &:hover::after { background-color: $panel-actions; border-radius: 50%; - content: ''; + content: ""; height: 24px; left: 0; position: absolute; @@ -264,11 +264,11 @@ $MinWidth: 240px; } &.mx_AppTileMenuBar_iconButton_popout::before { - mask-image: url('$(res)/img/feather-customised/widget/external-link.svg'); + mask-image: url("$(res)/img/feather-customised/widget/external-link.svg"); } &.mx_AppTileMenuBar_iconButton_menu::before { - mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); + mask-image: url("$(res)/img/element-icons/room/ellipsis.svg"); } } @@ -353,9 +353,9 @@ $MinWidth: 240px; width: 12px; height: 12px; mask-position: center; - content: ''; + content: ""; vertical-align: middle; - mask-image: url('$(res)/img/feather-customised/help-circle.svg'); + mask-image: url("$(res)/img/feather-customised/help-circle.svg"); } .mx_AppPermissionWarning_tooltip { @@ -398,8 +398,12 @@ $MinWidth: 240px; } @keyframes mx_AppLoading_spinner_fadeIn_animation { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + to { + opacity: 1; + } } .mx_AppLoading iframe { diff --git a/res/css/views/rooms/_BasicMessageComposer.pcss b/res/css/views/rooms/_BasicMessageComposer.pcss index 51f32ef4e54..b495e303a87 100644 --- a/res/css/views/rooms/_BasicMessageComposer.pcss +++ b/res/css/views/rooms/_BasicMessageComposer.pcss @@ -30,8 +30,12 @@ limitations under the License. } @keyframes visualbell { - from { background-color: $visual-bell-bg-color; } - to { background-color: $background; } + from { + background-color: $visual-bell-bg-color; + } + to { + background-color: $background; + } } &.mx_BasicMessageComposer_input_error { diff --git a/res/css/views/rooms/_E2EIcon.pcss b/res/css/views/rooms/_E2EIcon.pcss index a6cbea6246e..5ae6de07f5b 100644 --- a/res/css/views/rooms/_E2EIcon.pcss +++ b/res/css/views/rooms/_E2EIcon.pcss @@ -25,7 +25,8 @@ limitations under the License. .mx_E2EIcon_warning, .mx_E2EIcon_normal, .mx_E2EIcon_verified { - &::before, &::after { + &::before, + &::after { content: ""; display: block; position: absolute; @@ -42,7 +43,7 @@ limitations under the License. /* white infill for the transparency */ .mx_E2EIcon::before { background-color: #ffffff; - mask-image: url('$(res)/img/e2e/normal.svg'); + mask-image: url("$(res)/img/e2e/normal.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: 80%; @@ -50,7 +51,7 @@ limitations under the License. /* transparent-looking border surrounding the shield for when overlain over avatars */ .mx_E2EIcon_bordered { - mask-image: url('$(res)/img/e2e/normal.svg'); + mask-image: url("$(res)/img/e2e/normal.svg"); background-color: $header-panel-bg-color; /* shrink the actual badge */ @@ -64,16 +65,16 @@ limitations under the License. } .mx_E2EIcon_warning::after { - mask-image: url('$(res)/img/e2e/warning.svg'); + mask-image: url("$(res)/img/e2e/warning.svg"); background-color: $alert; } .mx_E2EIcon_normal::after { - mask-image: url('$(res)/img/e2e/normal.svg'); + mask-image: url("$(res)/img/e2e/normal.svg"); background-color: $header-panel-text-primary-color; } .mx_E2EIcon_verified::after { - mask-image: url('$(res)/img/e2e/verified.svg'); + mask-image: url("$(res)/img/e2e/verified.svg"); background-color: $accent; } diff --git a/res/css/views/rooms/_EmojiButton.pcss b/res/css/views/rooms/_EmojiButton.pcss index aadce683d4a..3f130ee3dcb 100644 --- a/res/css/views/rooms/_EmojiButton.pcss +++ b/res/css/views/rooms/_EmojiButton.pcss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_EmojiButton { - @mixin composerButton 50%,$accent; + @mixin composerButton 50%, $accent; } .mx_EmojiButton_highlight { @@ -23,11 +23,11 @@ limitations under the License. } .mx_EmojiButton_icon::before { - mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/emoji.svg"); } .mx_MessageComposer_wysiwyg { .mx_EmojiButton { - @mixin composerButton 5px,$tertiary-content; + @mixin composerButton 5px, $tertiary-content; } } diff --git a/res/css/views/rooms/_EntityTile.pcss b/res/css/views/rooms/_EntityTile.pcss index ff42d3c9c93..1ff925467a4 100644 --- a/res/css/views/rooms/_EntityTile.pcss +++ b/res/css/views/rooms/_EntityTile.pcss @@ -39,7 +39,7 @@ limitations under the License. position: absolute; top: calc(50% - 8px); /* center */ right: -8px; - mask: url('$(res)/img/member_chevron.png'); + mask: url("$(res)/img/member_chevron.png"); mask-repeat: no-repeat; width: 16px; height: 16px; diff --git a/res/css/views/rooms/_EventBubbleTile.pcss b/res/css/views/rooms/_EventBubbleTile.pcss index ca9ec513f87..2a0087871a9 100644 --- a/res/css/views/rooms/_EventBubbleTile.pcss +++ b/res/css/views/rooms/_EventBubbleTile.pcss @@ -15,7 +15,9 @@ limitations under the License. */ .mx_RoomView_body[data-layout="bubble"] { - .mx_RoomView_timeline, .mx_RoomView_statusArea, .mx_MessageComposer { + .mx_RoomView_timeline, + .mx_RoomView_statusArea, + .mx_MessageComposer { width: 100%; max-width: 1200px; margin: 0 auto; @@ -43,7 +45,9 @@ limitations under the License. --EventTile_bubble_gap-inline: 5px; position: relative; - margin-top: var(--gutterSize); + /* Other half of the gutter is provided by margin-bottom on the last tile + of the section */ + margin-top: calc(var(--gutterSize) / 2); margin-left: var(--EventTile_bubble-margin-inline-start); font-size: $font-14px; @@ -73,7 +77,7 @@ limitations under the License. } &::before { - content: ''; + content: ""; position: absolute; top: -1px; bottom: -1px; @@ -85,7 +89,6 @@ limitations under the License. &:hover, &.mx_EventTile_selected { - &::before { background: $eventbubble-bg-hover; } @@ -476,7 +479,7 @@ limitations under the License. "reply reply" auto "shield body" auto "shield link" auto - / auto 1fr; + / auto 1fr; .mx_UnknownBody, .mx_EventTile_keyRequestInfo, @@ -510,7 +513,7 @@ limitations under the License. grid-template: "shield source" auto "shield link" auto - / auto 1fr; + / auto 1fr; .mx_ViewSourceEvent { grid-area: source; @@ -536,7 +539,8 @@ limitations under the License. padding-right: 60px; /* align with bubbles text */ font-style: italic; - > a { /* timestamp anchor wrapper */ + > a { + /* timestamp anchor wrapper */ align-self: center; bottom: unset; top: unset; @@ -599,7 +603,8 @@ limitations under the License. bottom: unset; align-self: center; - .mx_MessageTimestamp, &.mx_MessageTimestamp { + .mx_MessageTimestamp, + &.mx_MessageTimestamp { vertical-align: middle; } } diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss index bd7685f139d..5aae6eee107 100644 --- a/res/css/views/rooms/_EventTile.pcss +++ b/res/css/views/rooms/_EventTile.pcss @@ -50,7 +50,7 @@ $left-gutter: 64px; mask-size: 16px; width: 16px; height: 16px; - content: ''; + content: ""; position: absolute; top: 0; left: 0; @@ -59,11 +59,11 @@ $left-gutter: 64px; } .mx_EventTile_receiptSent::before { - mask-image: url('$(res)/img/element-icons/circle-sent.svg'); + mask-image: url("$(res)/img/element-icons/circle-sent.svg"); } .mx_EventTile_receiptSending::before { - mask-image: url('$(res)/img/element-icons/circle-sending.svg'); + mask-image: url("$(res)/img/element-icons/circle-sending.svg"); } .mx_EventTile_content { @@ -225,9 +225,8 @@ $left-gutter: 64px; &.mx_EventTile_selected { > .mx_EventTile_line { /* TODO: ultimately we probably want some transition on here. */ - box-shadow: - inset var(--EventTile-box-shadow-offset-x) 0 0 - var(--EventTile-box-shadow-spread-radius) $accent; + box-shadow: inset var(--EventTile-box-shadow-offset-x) 0 0 var(--EventTile-box-shadow-spread-radius) + $accent; } } @@ -247,21 +246,18 @@ $left-gutter: 64px; } &.mx_EventTile_verified .mx_EventTile_line { - box-shadow: - inset var(--EventTile-box-shadow-offset-x) 0 0 - var(--EventTile-box-shadow-spread-radius) $e2e-verified-color; + box-shadow: inset var(--EventTile-box-shadow-offset-x) 0 0 var(--EventTile-box-shadow-spread-radius) + $e2e-verified-color; } &.mx_EventTile_unverified .mx_EventTile_line { - box-shadow: - inset var(--EventTile-box-shadow-offset-x) 0 0 - var(--EventTile-box-shadow-spread-radius) $e2e-unverified-color; + box-shadow: inset var(--EventTile-box-shadow-offset-x) 0 0 var(--EventTile-box-shadow-spread-radius) + $e2e-unverified-color; } &.mx_EventTile_unknown .mx_EventTile_line { - box-shadow: - inset var(--EventTile-box-shadow-offset-x) 0 0 - var(--EventTile-box-shadow-spread-radius) $e2e-unknown-color; + box-shadow: inset var(--EventTile-box-shadow-offset-x) 0 0 var(--EventTile-box-shadow-spread-radius) + $e2e-unknown-color; } } } @@ -462,6 +458,11 @@ $left-gutter: 64px; &.mx_EventTile_continuation { margin-top: 2px; } + &.mx_EventTile_lastInSection { + /* Other half of the gutter is provided by margin-top on the first + tile of the section */ + margin-bottom: calc(var(--gutterSize) / 2); + } } } @@ -494,8 +495,9 @@ $left-gutter: 64px; > .mx_EventTile_line { /* 15 px of padding */ /* stylelint-disable-next-line declaration-colon-space-after */ - padding-left: - calc(var(--name-width) + var(--icon-width) + $MessageTimestamp_width + 3 * var(--right-padding)); + padding-left: calc( + var(--name-width) + var(--icon-width) + $MessageTimestamp_width + 3 * var(--right-padding) + ); } } @@ -615,7 +617,8 @@ $left-gutter: 64px; */ /* Override nested lists being lower-roman */ - ol ol, ul ol { + ol ol, + ul ol { list-style-type: revert; } } @@ -686,12 +689,12 @@ $left-gutter: 64px; } &.mx_EventTile_e2eIcon_warning::after { - mask-image: url('$(res)/img/e2e/warning.svg'); + mask-image: url("$(res)/img/e2e/warning.svg"); background-color: $alert; } &.mx_EventTile_e2eIcon_normal::after { - mask-image: url('$(res)/img/e2e/normal.svg'); + mask-image: url("$(res)/img/e2e/normal.svg"); background-color: $header-panel-text-primary-color; } } @@ -852,7 +855,7 @@ $left-gutter: 64px; .mx_EventTile:hover .mx_MessageActionBar, .mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar, -[data-whatinput='keyboard'] .mx_EventTile:focus-within .mx_MessageActionBar, +[data-whatinput="keyboard"] .mx_EventTile:focus-within .mx_MessageActionBar, .mx_EventTile.focus-visible:focus-within .mx_MessageActionBar { visibility: visible; } @@ -861,7 +864,7 @@ $left-gutter: 64px; /* is less pretty, but is easier to target because otherwise we need to define the */ /* animation for when it's shown which means duplicating the style definition in */ /* multiple places. */ -.mx_EventTile:not(:hover):not(.mx_EventTile_actionBarFocused):not([data-whatinput='keyboard'] :focus-within) { +.mx_EventTile:not(:hover):not(.mx_EventTile_actionBarFocused):not([data-whatinput="keyboard"] :focus-within) { &:not(.focus-visible:focus-within) .mx_MessageActionBar .mx_Indicator { animation: none; } @@ -1137,7 +1140,8 @@ $left-gutter: 64px; .mx_ReplyChain_wrapper { .mx_MLocationBody, - .mx_UnknownBody { /* Error message inside ReplyTile */ + .mx_UnknownBody { + /* Error message inside ReplyTile */ margin-inline: unset; } } @@ -1323,8 +1327,9 @@ $left-gutter: 64px; &[data-shape="ThreadsList"][data-notification]::before, .mx_NotificationBadge { /* stylelint-disable-next-line declaration-colon-space-after */ - inset-block-start: - calc($notification-inset-block-start - var(--MatrixChat_useCompactLayout_group-padding-top)); + inset-block-start: calc( + $notification-inset-block-start - var(--MatrixChat_useCompactLayout_group-padding-top) + ); } } } diff --git a/res/css/views/rooms/_HistoryTile.pcss b/res/css/views/rooms/_HistoryTile.pcss index 1cd9dd062f3..5f656381c30 100644 --- a/res/css/views/rooms/_HistoryTile.pcss +++ b/res/css/views/rooms/_HistoryTile.pcss @@ -19,6 +19,6 @@ limitations under the License. &::before { background-color: $header-panel-text-primary-color; - mask-image: url('$(res)/img/element-icons/hide.svg'); + mask-image: url("$(res)/img/element-icons/hide.svg"); } } diff --git a/res/css/views/rooms/_IRCLayout.pcss b/res/css/views/rooms/_IRCLayout.pcss index 65ee7d371e5..02ae02fa5eb 100644 --- a/res/css/views/rooms/_IRCLayout.pcss +++ b/res/css/views/rooms/_IRCLayout.pcss @@ -65,7 +65,8 @@ $irc-line-height: $font-18px; align-items: center; /* Need to use important to override the js provided height and width values. */ - > .mx_BaseAvatar, > .mx_BaseAvatar > * { + > .mx_BaseAvatar, + > .mx_BaseAvatar > * { height: $font-14px !important; width: $font-14px !important; font-size: $font-10px !important; diff --git a/res/css/views/rooms/_JumpToBottomButton.pcss b/res/css/views/rooms/_JumpToBottomButton.pcss index 0cb51cd8f7a..7436d9feb5e 100644 --- a/res/css/views/rooms/_JumpToBottomButton.pcss +++ b/res/css/views/rooms/_JumpToBottomButton.pcss @@ -70,7 +70,7 @@ limitations under the License. bottom: 0; left: 0; right: 0; - mask-image: url('$(res)/img/element-icons/message/chevron-up.svg'); + mask-image: url("$(res)/img/element-icons/message/chevron-up.svg"); mask-repeat: no-repeat; mask-size: 28px; mask-position: center 4px; diff --git a/res/css/views/rooms/_LiveContentSummary.pcss b/res/css/views/rooms/_LiveContentSummary.pcss index c56026a829b..2cd6230d7d0 100644 --- a/res/css/views/rooms/_LiveContentSummary.pcss +++ b/res/css/views/rooms/_LiveContentSummary.pcss @@ -21,7 +21,7 @@ limitations under the License. &::before { display: inline-block; vertical-align: text-bottom; - content: ''; + content: ""; background-color: $secondary-content; mask-size: 16px; width: 16px; @@ -30,7 +30,7 @@ limitations under the License. } &.mx_LiveContentSummary_text_video::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url("$(res)/img/element-icons/call/video-call.svg"); } &.mx_LiveContentSummary_text_active { @@ -45,9 +45,9 @@ limitations under the License. .mx_LiveContentSummary_participants::before { display: inline-block; vertical-align: text-bottom; - content: ''; + content: ""; background-color: $secondary-content; - mask-image: url('$(res)/img/element-icons/group-members.svg'); + mask-image: url("$(res)/img/element-icons/group-members.svg"); mask-size: 16px; width: 16px; height: 16px; diff --git a/res/css/views/rooms/_MemberInfo.pcss b/res/css/views/rooms/_MemberInfo.pcss index c29bb28689d..6fc2ff072b6 100644 --- a/res/css/views/rooms/_MemberInfo.pcss +++ b/res/css/views/rooms/_MemberInfo.pcss @@ -36,7 +36,7 @@ limitations under the License. width: 16px; padding: 10px 0 10px 10px; cursor: pointer; - mask-image: url('$(res)/img/minimise.svg'); + mask-image: url("$(res)/img/minimise.svg"); mask-repeat: no-repeat; mask-position: 16px center; background-color: $header-panel-text-primary-color; diff --git a/res/css/views/rooms/_MemberList.pcss b/res/css/views/rooms/_MemberList.pcss index 230149bdf84..16a107ad301 100644 --- a/res/css/views/rooms/_MemberList.pcss +++ b/res/css/views/rooms/_MemberList.pcss @@ -98,10 +98,10 @@ limitations under the License. display: inline-flex; &::before { - content: ''; + content: ""; display: inline-block; background-color: $button-fg-color; - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); mask-position: center; mask-repeat: no-repeat; mask-size: 20px; diff --git a/res/css/views/rooms/_MessageComposer.pcss b/res/css/views/rooms/_MessageComposer.pcss index f00b9ace783..4f3c406d3b3 100644 --- a/res/css/views/rooms/_MessageComposer.pcss +++ b/res/css/views/rooms/_MessageComposer.pcss @@ -153,8 +153,12 @@ limitations under the License. } @keyframes visualbell { - from { background-color: $visual-bell-bg-color; } - to { background-color: $background; } + from { + background-color: $visual-bell-bg-color; + } + to { + background-color: $background; + } } .mx_MessageComposer_input_error { @@ -188,7 +192,7 @@ limitations under the License. .mx_MessageComposer_input textarea::-moz-placeholder { line-height: 100%; color: $accent; - opacity: 1.0; + opacity: 1; } .mx_MessageComposer_input textarea::-webkit-input-placeholder { color: $accent; @@ -199,7 +203,7 @@ limitations under the License. } .mx_MessageComposer_button { - @mixin composerButton 50%,$accent; + @mixin composerButton 50%, $accent; &:last-child { margin-right: auto; @@ -241,7 +245,7 @@ limitations under the License. } .mx_MessageComposer_button { - @mixin composerButton 5px,$tertiary-content; + @mixin composerButton 5px, $tertiary-content; &.mx_MessageComposer_closeButtonMenu { &::after { @@ -260,39 +264,39 @@ limitations under the License. } .mx_MessageComposer_upload::before { - mask-image: url('$(res)/img/element-icons/room/composer/attach.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/attach.svg"); } .mx_MessageComposer_poll::before { - mask-image: url('$(res)/img/element-icons/room/composer/poll.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/poll.svg"); } .mx_MessageComposer_voiceMessage::before { - mask-image: url('$(res)/img/voip/mic-on-mask.svg'); + mask-image: url("$(res)/img/voip/mic-on-mask.svg"); } .mx_MessageComposer_voiceBroadcast::before { - mask-image: url('$(res)/img/element-icons/live.svg'); + mask-image: url("$(res)/img/element-icons/live.svg"); } .mx_MessageComposer_plain_text::before { - mask-image: url('$(res)/img/element-icons/room/composer/plain_text.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/plain_text.svg"); } .mx_MessageComposer_rich_text::before { - mask-image: url('$(res)/img/element-icons/room/composer/rich_text.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/rich_text.svg"); } .mx_MessageComposer_location::before { - mask-image: url('$(res)/img/element-icons/room/composer/location.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/location.svg"); } .mx_MessageComposer_stickers::before { - mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg'); + mask-image: url("$(res)/img/element-icons/room/composer/sticker.svg"); } .mx_MessageComposer_buttonMenu::before { - mask-image: url('$(res)/img/image-view/more.svg'); + mask-image: url("$(res)/img/image-view/more.svg"); } .mx_MessageComposer_sendMessage { @@ -313,13 +317,13 @@ limitations under the License. top: 8px; left: 9px; - mask-image: url('$(res)/img/element-icons/send-message.svg'); + mask-image: url("$(res)/img/element-icons/send-message.svg"); mask-repeat: no-repeat; mask-size: contain; mask-position: center; background-color: $button-fg-color; - content: ''; + content: ""; } &.mx_MessageComposer_sendMessage_enabled { @@ -385,7 +389,7 @@ limitations under the License. .mx_MessageComposer_formatbar_markdown, .mx_MessageComposer_input_markdownIndicator { cursor: pointer; - mask-image: url('$(res)/img/markdown.svg'); + mask-image: url("$(res)/img/markdown.svg"); mask-size: contain; mask-position: center; mask-repeat: no-repeat; diff --git a/res/css/views/rooms/_MessageComposerFormatBar.pcss b/res/css/views/rooms/_MessageComposerFormatBar.pcss index 83e7cd1198b..1f9474ce372 100644 --- a/res/css/views/rooms/_MessageComposerFormatBar.pcss +++ b/res/css/views/rooms/_MessageComposerFormatBar.pcss @@ -55,7 +55,7 @@ limitations under the License. } .mx_MessageComposerFormatBar_button::after { - content: ''; + content: ""; position: absolute; top: 0; left: 0; @@ -71,27 +71,27 @@ limitations under the License. } .mx_MessageComposerFormatBar_buttonIconBold::after { - mask-image: url('$(res)/img/element-icons/room/format-bar/bold.svg'); + mask-image: url("$(res)/img/element-icons/room/format-bar/bold.svg"); } .mx_MessageComposerFormatBar_buttonIconItalic::after { - mask-image: url('$(res)/img/element-icons/room/format-bar/italic.svg'); + mask-image: url("$(res)/img/element-icons/room/format-bar/italic.svg"); } .mx_MessageComposerFormatBar_buttonIconStrikethrough::after { - mask-image: url('$(res)/img/element-icons/room/format-bar/strikethrough.svg'); + mask-image: url("$(res)/img/element-icons/room/format-bar/strikethrough.svg"); } .mx_MessageComposerFormatBar_buttonIconQuote::after { - mask-image: url('$(res)/img/element-icons/room/format-bar/quote.svg'); + mask-image: url("$(res)/img/element-icons/room/format-bar/quote.svg"); } .mx_MessageComposerFormatBar_buttonIconCode::after { - mask-image: url('$(res)/img/element-icons/room/format-bar/code.svg'); + mask-image: url("$(res)/img/element-icons/room/format-bar/code.svg"); } .mx_MessageComposerFormatBar_buttonIconInsertLink::after { - mask-image: url('$(res)/img/element-icons/link.svg'); + mask-image: url("$(res)/img/element-icons/link.svg"); mask-size: 18px; } } diff --git a/res/css/views/rooms/_NewRoomIntro.pcss b/res/css/views/rooms/_NewRoomIntro.pcss index 1b7e298ef6b..d6eaa84fd00 100644 --- a/res/css/views/rooms/_NewRoomIntro.pcss +++ b/res/css/views/rooms/_NewRoomIntro.pcss @@ -34,7 +34,7 @@ limitations under the License. display: inline-block; &:not(.mx_AccessibleButton_kind_primary_outline)::before { - content: ''; + content: ""; display: inline-block; background-color: $button-fg-color; mask-position: center; @@ -48,7 +48,7 @@ limitations under the License. } .mx_NewRoomIntro_inviteButton::before { - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); } } diff --git a/res/css/views/rooms/_PinnedEventTile.pcss b/res/css/views/rooms/_PinnedEventTile.pcss index 6b981a5e457..aa4fe7c02dd 100644 --- a/res/css/views/rooms/_PinnedEventTile.pcss +++ b/res/css/views/rooms/_PinnedEventTile.pcss @@ -77,7 +77,7 @@ limitations under the License. mask-position: center; mask-size: 8px; mask-repeat: no-repeat; - mask-image: url('$(res)/img/image-view/close.svg'); + mask-image: url("$(res)/img/image-view/close.svg"); } } diff --git a/res/css/views/rooms/_ReadReceiptGroup.pcss b/res/css/views/rooms/_ReadReceiptGroup.pcss index 05ec2e5f2de..337991047a4 100644 --- a/res/css/views/rooms/_ReadReceiptGroup.pcss +++ b/res/css/views/rooms/_ReadReceiptGroup.pcss @@ -57,9 +57,7 @@ limitations under the License. border-radius: 100%; will-change: left, top; - transition: - left var(--transition-short) ease-out, - top var(--transition-standard) ease-out; + transition: left var(--transition-short) ease-out, top var(--transition-standard) ease-out; } } } diff --git a/res/css/views/rooms/_RecentlyViewedButton.pcss b/res/css/views/rooms/_RecentlyViewedButton.pcss index 0d5b4597440..f4e9206c67f 100644 --- a/res/css/views/rooms/_RecentlyViewedButton.pcss +++ b/res/css/views/rooms/_RecentlyViewedButton.pcss @@ -19,7 +19,7 @@ limitations under the License. width: max-content; max-width: 240px; max-height: 400px; - border: 1px solid rgba($primary-content, .1); + border: 1px solid rgba($primary-content, 0.1); border-radius: 8px; box-shadow: 0 8px 4px rgba(0, 0, 0, 0.08); display: flex; diff --git a/res/css/views/rooms/_ReplyPreview.pcss b/res/css/views/rooms/_ReplyPreview.pcss index 1795bc8aa24..75b3f8a672e 100644 --- a/res/css/views/rooms/_ReplyPreview.pcss +++ b/res/css/views/rooms/_ReplyPreview.pcss @@ -39,7 +39,7 @@ limitations under the License. .mx_ReplyPreview_header_cancel { background-color: $primary-content; - mask: url('$(res)/img/cancel.svg'); + mask: url("$(res)/img/cancel.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: 18px; diff --git a/res/css/views/rooms/_ReplyTile.pcss b/res/css/views/rooms/_ReplyTile.pcss index 616f1b181fe..1e70b479563 100644 --- a/res/css/views/rooms/_ReplyTile.pcss +++ b/res/css/views/rooms/_ReplyTile.pcss @@ -30,7 +30,7 @@ limitations under the License. > a { display: grid; grid-template: - "sender" auto + "sender" auto "message" auto / auto; text-decoration: none; @@ -49,7 +49,6 @@ limitations under the License. .mx_RedactedBody, .mx_HiddenBody { - padding: 4px 0 2px 20px; &::before { @@ -76,7 +75,8 @@ limitations under the License. } // Hide line numbers and edited indicator - .mx_EventTile_lineNumbers, .mx_EventTile_edited { + .mx_EventTile_lineNumbers, + .mx_EventTile_edited { display: none; } @@ -110,7 +110,7 @@ limitations under the License. /* Render replies to emotes inline with the sender avatar */ grid-template: "sender message" auto - / max-content auto; + / max-content auto; gap: 4px; // increase spacing } diff --git a/res/css/views/rooms/_RoomHeader.pcss b/res/css/views/rooms/_RoomHeader.pcss index bd99655a222..3e8adf0e93c 100644 --- a/res/css/views/rooms/_RoomHeader.pcss +++ b/res/css/views/rooms/_RoomHeader.pcss @@ -27,7 +27,7 @@ limitations under the License. height: 14px; width: 14px; background-color: $secondary-content; - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url("$(res)/img/element-icons/call/video-call.svg"); mask-size: 100%; } @@ -126,7 +126,7 @@ limitations under the License. mask-position: center; mask-size: 20px; mask-repeat: no-repeat; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); background-color: $tertiary-content; } @@ -209,7 +209,7 @@ limitations under the License. border-radius: 100%; &::before { - content: ''; + content: ""; position: absolute; top: 4px; /* center with parent of 32px */ left: 4px; /* center with parent of 32px */ @@ -230,12 +230,12 @@ limitations under the License. } .mx_RoomHeader_forgetButton::before { - mask-image: url('$(res)/img/element-icons/leave.svg'); + mask-image: url("$(res)/img/element-icons/leave.svg"); width: 26px; } .mx_RoomHeader_appsButton::before { - mask-image: url('$(res)/img/element-icons/room/apps.svg'); + mask-image: url("$(res)/img/element-icons/room/apps.svg"); } .mx_RoomHeader_appsButton_highlight::before { @@ -243,15 +243,15 @@ limitations under the License. } .mx_RoomHeader_searchButton::before { - mask-image: url('$(res)/img/element-icons/room/search-inset.svg'); + mask-image: url("$(res)/img/element-icons/room/search-inset.svg"); } .mx_RoomHeader_inviteButton::before { - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); } .mx_RoomHeader_voiceCallButton::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + mask-image: url("$(res)/img/element-icons/call/voice-call.svg"); /* The call button SVG is padded slightly differently, so match it up to the size */ /* of the other icons */ @@ -260,31 +260,31 @@ limitations under the License. } .mx_RoomHeader_videoCallButton::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url("$(res)/img/element-icons/call/video-call.svg"); } .mx_RoomHeader_layoutButton--freedom::before, .mx_RoomHeader_freedomIcon::before { - mask-image: url('$(res)/img/element-icons/call/freedom.svg'); + mask-image: url("$(res)/img/element-icons/call/freedom.svg"); } .mx_RoomHeader_layoutButton--spotlight::before, .mx_RoomHeader_spotlightIcon::before { - mask-image: url('$(res)/img/element-icons/call/spotlight.svg'); + mask-image: url("$(res)/img/element-icons/call/spotlight.svg"); } .mx_RoomHeader_closeButton::before { - mask-image: url('$(res)/img/cancel.svg'); + mask-image: url("$(res)/img/cancel.svg"); mask-size: 20px; mask-position: center; } .mx_RoomHeader_minimiseButton::before { - mask-image: url('$(res)/img/element-icons/reduce.svg'); + mask-image: url("$(res)/img/element-icons/reduce.svg"); } .mx_RoomHeader_layoutMenu .mx_IconizedContextMenu_icon::before { - content: ''; + content: ""; width: 16px; height: 16px; display: block; diff --git a/res/css/views/rooms/_RoomList.pcss b/res/css/views/rooms/_RoomList.pcss index 37c3dd3a48c..eb2cc9c4ee7 100644 --- a/res/css/views/rooms/_RoomList.pcss +++ b/res/css/views/rooms/_RoomList.pcss @@ -19,28 +19,28 @@ limitations under the License. } .mx_RoomList_iconPlus::before { - mask-image: url('$(res)/img/element-icons/roomlist/plus-circle.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/plus-circle.svg"); } .mx_RoomList_iconNewRoom::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-plus.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg"); } .mx_RoomList_iconNewVideoRoom::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-video.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg"); } .mx_RoomList_iconAddExistingRoom::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash.svg"); } .mx_RoomList_iconExplore::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-search.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg"); } .mx_RoomList_iconDialpad::before { - mask-image: url('$(res)/img/element-icons/roomlist/dialpad.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/dialpad.svg"); } .mx_RoomList_iconStartChat::before { - mask-image: url('$(res)/img/element-icons/roomlist/member-plus.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/member-plus.svg"); } .mx_RoomList_iconInvite::before { - mask-image: url('$(res)/img/element-icons/room/share.svg'); + mask-image: url("$(res)/img/element-icons/room/share.svg"); } .mx_RoomList_explorePrompt { @@ -67,7 +67,7 @@ limitations under the License. border-radius: 4px; &::before { - content: ''; + content: ""; width: 16px; height: 16px; position: absolute; @@ -80,11 +80,11 @@ limitations under the License. } &.mx_RoomList_explorePrompt_startChat::before { - mask-image: url('$(res)/img/element-icons/feedback.svg'); + mask-image: url("$(res)/img/element-icons/feedback.svg"); } &.mx_RoomList_explorePrompt_explore::before { - mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/explore.svg"); } } } diff --git a/res/css/views/rooms/_RoomListHeader.pcss b/res/css/views/rooms/_RoomListHeader.pcss index 1c6579cf6fb..d2b58bf83e6 100644 --- a/res/css/views/rooms/_RoomListHeader.pcss +++ b/res/css/views/rooms/_RoomListHeader.pcss @@ -41,7 +41,7 @@ limitations under the License. } &::before { - content: ''; + content: ""; width: 20px; height: 20px; top: 3px; @@ -51,7 +51,7 @@ limitations under the License. mask-size: contain; mask-repeat: no-repeat; background-color: $tertiary-content; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); } &[aria-expanded="true"] { @@ -76,7 +76,7 @@ limitations under the License. flex-shrink: 0; &::before { - content: ''; + content: ""; width: 16px; height: 16px; position: absolute; @@ -84,7 +84,7 @@ limitations under the License. mask-size: contain; mask-repeat: no-repeat; background-color: $secondary-content; - mask-image: url('$(res)/img/element-icons/roomlist/plus.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/plus.svg"); } &:hover { @@ -106,20 +106,20 @@ limitations under the License. } .mx_RoomListHeader_iconInvite::before { - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); } .mx_RoomListHeader_iconStartChat::before { - mask-image: url('$(res)/img/element-icons/roomlist/member-plus.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/member-plus.svg"); } .mx_RoomListHeader_iconNewRoom::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-plus.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg"); } .mx_RoomListHeader_iconNewVideoRoom::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-video.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg"); } .mx_RoomListHeader_iconExplore::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-search.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg"); } .mx_RoomListHeader_iconPlus::before { - mask-image: url('$(res)/img/element-icons/plus.svg'); + mask-image: url("$(res)/img/element-icons/plus.svg"); } diff --git a/res/css/views/rooms/_RoomPreviewCard.pcss b/res/css/views/rooms/_RoomPreviewCard.pcss index 5f93f735902..b7acfb1a321 100644 --- a/res/css/views/rooms/_RoomPreviewCard.pcss +++ b/res/css/views/rooms/_RoomPreviewCard.pcss @@ -42,7 +42,7 @@ limitations under the License. mask-repeat: no-repeat; mask-position: center; mask-size: contain; - mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-image: url("$(res)/img/element-icons/room/room-summary.svg"); background-color: $secondary-content; } } @@ -71,7 +71,8 @@ limitations under the License. align-items: center; .mx_RoomAvatar_isSpaceRoom { - &.mx_BaseAvatar_image, .mx_BaseAvatar_image { + &.mx_BaseAvatar_image, + .mx_BaseAvatar_image { border-radius: 12px; } } @@ -95,7 +96,7 @@ limitations under the License. mask-size: 22px; mask-position: center; mask-repeat: no-repeat; - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url("$(res)/img/element-icons/call/video-call.svg"); } } diff --git a/res/css/views/rooms/_RoomSublist.pcss b/res/css/views/rooms/_RoomSublist.pcss index 197ce295144..905471d2556 100644 --- a/res/css/views/rooms/_RoomSublist.pcss +++ b/res/css/views/rooms/_RoomSublist.pcss @@ -109,7 +109,7 @@ limitations under the License. border-radius: 500px; &::before { - content: ''; + content: ""; width: 16px; height: 16px; position: absolute; @@ -135,7 +135,7 @@ limitations under the License. } */ .mx_RoomSublist_auxButton::before { - mask-image: url('$(res)/img/element-icons/roomlist/plus.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/plus.svg"); } .mx_RoomSublist_auxGroupButton, .mx_RoomSublist_auxDmButton { @@ -153,7 +153,7 @@ limitations under the License. } .mx_RoomSublist_menuButton::before { - mask-image: url('$(res)/img/element-icons/context-menu.svg'); + mask-image: url("$(res)/img/element-icons/context-menu.svg"); } .mx_RoomSublist_headerText { @@ -176,7 +176,7 @@ limitations under the License. margin-right: 6px; &::before { - content: ''; + content: ""; width: 18px; height: 18px; position: absolute; @@ -184,7 +184,7 @@ limitations under the License. mask-size: contain; mask-repeat: no-repeat; background-color: $secondary-content; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); } &.mx_RoomSublist_collapseBtn_collapsed::before { @@ -261,7 +261,8 @@ limitations under the License. bottom: 0 !important; /* override from library */ } - &:hover, &.mx_RoomSublist_hasMenuOpen { + &:hover, + &.mx_RoomSublist_hasMenuOpen { .mx_RoomSublist_resizerHandle { opacity: 0.8; background-color: $primary-content; @@ -299,7 +300,7 @@ limitations under the License. .mx_RoomSublist_showMoreButtonChevron, .mx_RoomSublist_showLessButtonChevron { - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); } .mx_RoomSublist_showLessButtonChevron { @@ -416,7 +417,8 @@ limitations under the License. margin-bottom: 4px; } - .mx_StyledRadioButton, .mx_Checkbox { + .mx_StyledRadioButton, + .mx_Checkbox { margin-top: 8px; } } @@ -432,11 +434,11 @@ limitations under the License. width: 100%; height: 100%; - content: ''; + content: ""; position: absolute; mask-repeat: repeat-y; mask-size: auto 48px; - mask-image: url('$(res)/img/element-icons/roomlist/skeleton-ui.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/skeleton-ui.svg"); } } diff --git a/res/css/views/rooms/_RoomTile.pcss b/res/css/views/rooms/_RoomTile.pcss index e7902ec4827..ebf4aa24ed6 100644 --- a/res/css/views/rooms/_RoomTile.pcss +++ b/res/css/views/rooms/_RoomTile.pcss @@ -35,7 +35,8 @@ limitations under the License. border-radius: $border-radius-8px; } - .mx_DecoratedRoomAvatar, .mx_RoomTile_avatarContainer { + .mx_DecoratedRoomAvatar, + .mx_RoomTile_avatarContainer { margin-right: 1rem; .mx_BaseAvatar_initial { @@ -65,7 +66,8 @@ limitations under the License. flex-direction: column; justify-content: center; - .mx_RoomTile_title, .mx_RoomTile_subtitle { + .mx_RoomTile_title, + .mx_RoomTile_subtitle { width: 100%; /* Ellipsize any text overflow */ @@ -141,7 +143,7 @@ limitations under the License. &::before { top: .2rem; left: .2rem; - content: ''; + content: ""; width: 1.6rem; height: 1.6rem; position: absolute; @@ -158,7 +160,7 @@ limitations under the License. } .mx_RoomTile_menuButton::before { - mask-image: url('$(res)/img/element-icons/context-menu.svg'); + mask-image: url("$(res)/img/element-icons/context-menu.svg"); } &:not(.mx_RoomTile_minimized) { @@ -182,7 +184,8 @@ limitations under the License. align-items: center; position: relative; - .mx_DecoratedRoomAvatar, .mx_RoomTile_avatarContainer { + .mx_DecoratedRoomAvatar, + .mx_RoomTile_avatarContainer { margin-right: 0; } } @@ -308,80 +311,80 @@ limitations under the License. /* We use these both in context menus and the room tiles */ .mx_RoomTile_iconBell::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); } .mx_RoomTile_iconBellDot::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-default.svg"); } .mx_RoomTile_iconBellCrossed::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-off.svg"); } .mx_RoomTile_iconBellMentions::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-dm.svg"); } .mx_RoomTile_contextMenu { .mx_RoomTile_iconStar::before { - mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/favorite.svg"); } .mx_RoomTile_iconArrowDown::before { - mask-image: url('$(res)/img/element-icons/roomlist/low-priority.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/low-priority.svg"); } .mx_RoomTile_iconNotificationsDefault::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); } .mx_RoomTile_iconNotificationsAllMessages::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-default.svg"); } .mx_RoomTile_iconNotificationsMentionsKeywords::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-dm.svg"); } .mx_RoomTile_iconNotificationsNone::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-off.svg"); } .mx_RoomTile_iconPeople::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-image: url("$(res)/img/element-icons/room/members.svg"); } .mx_RoomTile_iconFiles::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); + mask-image: url("$(res)/img/element-icons/room/files.svg"); } .mx_RoomTile_iconPins::before { - mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); } .mx_RoomTile_iconWidgets::before { - mask-image: url('$(res)/img/element-icons/room/apps.svg'); + mask-image: url("$(res)/img/element-icons/room/apps.svg"); } .mx_RoomTile_iconSettings::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); + mask-image: url("$(res)/img/element-icons/settings.svg"); } .mx_RoomTile_iconExport::before { - mask-image: url('$(res)/img/element-icons/export.svg'); + mask-image: url("$(res)/img/element-icons/export.svg"); } .mx_RoomTile_iconDeveloperTools::before { - mask-image: url('$(res)/img/element-icons/settings/flask.svg'); + mask-image: url("$(res)/img/element-icons/settings/flask.svg"); } .mx_RoomTile_iconCopyLink::before { - mask-image: url('$(res)/img/element-icons/link.svg'); + mask-image: url("$(res)/img/element-icons/link.svg"); } .mx_RoomTile_iconInvite::before { - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); } .mx_RoomTile_iconSignOut::before { - mask-image: url('$(res)/img/element-icons/leave.svg'); + mask-image: url("$(res)/img/element-icons/leave.svg"); } } diff --git a/res/css/views/rooms/_SearchBar.pcss b/res/css/views/rooms/_SearchBar.pcss index de2940691a1..95aa57df0e8 100644 --- a/res/css/views/rooms/_SearchBar.pcss +++ b/res/css/views/rooms/_SearchBar.pcss @@ -32,7 +32,7 @@ limitations under the License. width: 37px; height: 37px; background-color: $accent; - mask: url('$(res)/img/feather-customised/search-input.svg'); + mask: url("$(res)/img/feather-customised/search-input.svg"); mask-repeat: no-repeat; mask-position: center; } @@ -59,7 +59,7 @@ limitations under the License. .mx_SearchBar_cancel { background-color: $alert; - mask: url('$(res)/img/cancel.svg'); + mask: url("$(res)/img/cancel.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: 14px; diff --git a/res/css/views/rooms/_ThreadSummary.pcss b/res/css/views/rooms/_ThreadSummary.pcss index f444c23770f..e71c487a068 100644 --- a/res/css/views/rooms/_ThreadSummary.pcss +++ b/res/css/views/rooms/_ThreadSummary.pcss @@ -60,17 +60,17 @@ limitations under the License. opacity: 0; transform: translateX(60px); - transition: all .1s ease-in-out; + transition: all 0.1s ease-in-out; &::before { - content: ''; + content: ""; position: absolute; top: 50%; right: $spacing-12; transform: translateY(-50%); width: 12px; height: 12px; - mask-image: url('$(res)/img/compound/chevron-right-12px.svg'); + mask-image: url("$(res)/img/compound/chevron-right-12px.svg"); mask-position: center; mask-size: contain; mask-repeat: no-repeat; diff --git a/res/css/views/rooms/_TopUnreadMessagesBar.pcss b/res/css/views/rooms/_TopUnreadMessagesBar.pcss index 09f842e5114..b3555738c9f 100644 --- a/res/css/views/rooms/_TopUnreadMessagesBar.pcss +++ b/res/css/views/rooms/_TopUnreadMessagesBar.pcss @@ -52,7 +52,7 @@ limitations under the License. position: absolute; width: 36px; height: 36px; - mask-image: url('$(res)/img/element-icons/message/chevron-up.svg'); + mask-image: url("$(res)/img/element-icons/message/chevron-up.svg"); mask-repeat: no-repeat; mask-size: 28px; mask-position: center 4px; @@ -75,7 +75,7 @@ limitations under the License. position: absolute; width: 18px; height: 18px; - mask-image: url('$(res)/img/icons-close.svg'); + mask-image: url("$(res)/img/icons-close.svg"); mask-repeat: no-repeat; mask-size: 9px; mask-position: 4.5px 4.5px; diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.pcss b/res/css/views/rooms/_VoiceRecordComposerTile.pcss index 26f206f9181..936aee0f331 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.pcss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.pcss @@ -24,7 +24,7 @@ limitations under the License. position: relative; &::after { - content: ''; + content: ""; width: 14px; height: 14px; position: absolute; @@ -43,7 +43,7 @@ limitations under the License. background-color: $voice-record-icon-color; mask-repeat: no-repeat; mask-size: contain; - mask-image: url('$(res)/img/element-icons/trashcan.svg'); + mask-image: url("$(res)/img/element-icons/trashcan.svg"); } .mx_VoiceRecordComposerTile_uploadingState { @@ -81,7 +81,7 @@ limitations under the License. &::before { animation: recording-pulse 2s infinite; - content: ''; + content: ""; background-color: $voice-record-live-circle-color; width: 10px; height: 10px; diff --git a/res/css/views/rooms/_WhoIsTypingTile.pcss b/res/css/views/rooms/_WhoIsTypingTile.pcss index 2d469fa531b..7157d18f2e5 100644 --- a/res/css/views/rooms/_WhoIsTypingTile.pcss +++ b/res/css/views/rooms/_WhoIsTypingTile.pcss @@ -63,7 +63,7 @@ limitations under the License. } .mx_WhoIsTypingTile_label > span { - background-image: url('$(res)/img/typing-indicator-2x.gif'); + background-image: url("$(res)/img/typing-indicator-2x.gif"); background-size: 25px; background-position: left bottom; background-repeat: no-repeat; @@ -72,7 +72,6 @@ limitations under the License. } .mx_MatrixChat_useCompactLayout { - .mx_WhoIsTypingTile { padding-top: 4px; } diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss index b4abee12eb9..d48476cfd7f 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -16,8 +16,12 @@ limitations under the License. .mx_WysiwygComposer_Editor_container { @keyframes visualbell { - from { background-color: $visual-bell-bg-color; } - to { background-color: $background; } + from { + background-color: $visual-bell-bg-color; + } + to { + background-color: $background; + } } .mx_WysiwygComposer_Editor_content { diff --git a/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss index 76026ff9381..fa8078279f5 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss @@ -21,74 +21,40 @@ limitations under the License. .mx_FormattingButtons_Button { --size: 28px; - position: relative; cursor: pointer; height: var(--size); - line-height: var(--size); - width: auto; - padding-left: 22px; + width: var(--size); background-color: transparent; border: none; + display: flex; + align-items: center; + justify-content: center; + border-radius: 5px; + } - &::before { - content: ''; - position: absolute; - top: 6px; - left: 6px; - height: 16px; - width: 16px; - background-color: $tertiary-content; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - } - - &::after { - content: ''; - position: absolute; - left: 0; - top: 0; - z-index: 0; - width: var(--size); - height: var(--size); - border-radius: 5px; - } - + .mx_FormattingButtons_Button_hover { &:hover { - &::after { - background: rgba($secondary-content, 0.1); - } + background: rgba($secondary-content, 0.1); - &::before { - background-color: $secondary-content; + .mx_FormattingButtons_Icon { + color: $secondary-content; } } } .mx_FormattingButtons_active { - &::after { - background: rgba($accent, 0.1); - } + background: rgba($accent, 0.1); - &::before { - background-color: $accent; + .mx_FormattingButtons_Icon { + color: $accent; } } - .mx_FormattingButtons_Button_bold::before { - mask-image: url('$(res)/img/element-icons/room/composer/bold.svg'); - } - - .mx_FormattingButtons_Button_italic::before { - mask-image: url('$(res)/img/element-icons/room/composer/italic.svg'); - } - - .mx_FormattingButtons_Button_underline::before { - mask-image: url('$(res)/img/element-icons/room/composer/underline.svg'); - } - - .mx_FormattingButtons_Button_strikethrough::before { - mask-image: url('$(res)/img/element-icons/room/composer/strikethrough.svg'); + .mx_FormattingButtons_Icon { + --size: 16px; + height: var(--size); + width: var(--size); + color: $tertiary-content; } } diff --git a/res/css/views/settings/_AvatarSetting.pcss b/res/css/views/settings/_AvatarSetting.pcss index fb348723ef3..04312fdb8d3 100644 --- a/res/css/views/settings/_AvatarSetting.pcss +++ b/res/css/views/settings/_AvatarSetting.pcss @@ -96,7 +96,7 @@ limitations under the License. mask-repeat: no-repeat; mask-size: 36px; mask-position: center; - content: ''; + content: ""; position: absolute; top: 0; bottom: 0; @@ -124,7 +124,7 @@ limitations under the License. mask-position: center; mask-size: 55%; background-color: $quinary-content; - mask-image: url('$(res)/img/feather-customised/edit.svg'); + mask-image: url("$(res)/img/feather-customised/edit.svg"); } } diff --git a/res/css/views/settings/_LayoutSwitcher.pcss b/res/css/views/settings/_LayoutSwitcher.pcss index 992d702ab38..e07d87a2cca 100644 --- a/res/css/views/settings/_LayoutSwitcher.pcss +++ b/res/css/views/settings/_LayoutSwitcher.pcss @@ -16,6 +16,8 @@ limitations under the License. */ .mx_LayoutSwitcher { + margin-top: -16px; + .mx_LayoutSwitcher_RadioButtons { display: flex; flex-direction: row; diff --git a/res/css/views/settings/_Notifications.pcss b/res/css/views/settings/_Notifications.pcss index 2725fb4a99a..7357ee1c68b 100644 --- a/res/css/views/settings/_Notifications.pcss +++ b/res/css/views/settings/_Notifications.pcss @@ -68,7 +68,8 @@ limitations under the License. .mx_UserNotifSettings_floatingSection { margin-top: 40px; - & > div:first-child { /* section header */ + & > div:first-child { + /* section header */ font-size: $font-18px; font-weight: $font-semi-bold; } diff --git a/res/css/views/settings/_SecureBackupPanel.pcss b/res/css/views/settings/_SecureBackupPanel.pcss index e0cf898f436..86f7b2036d0 100644 --- a/res/css/views/settings/_SecureBackupPanel.pcss +++ b/res/css/views/settings/_SecureBackupPanel.pcss @@ -15,16 +15,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_sigInvalid, -.mx_SecureBackupPanel_deviceVerified, .mx_SecureBackupPanel_deviceNotVerified { +.mx_SecureBackupPanel_sigValid, +.mx_SecureBackupPanel_sigInvalid, +.mx_SecureBackupPanel_deviceVerified, +.mx_SecureBackupPanel_deviceNotVerified { font-weight: bold; } -.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_deviceVerified { +.mx_SecureBackupPanel_sigValid, +.mx_SecureBackupPanel_deviceVerified { color: $e2e-verified-color; } -.mx_SecureBackupPanel_sigInvalid, .mx_SecureBackupPanel_deviceNotVerified { +.mx_SecureBackupPanel_sigInvalid, +.mx_SecureBackupPanel_deviceNotVerified { color: $e2e-warning-color; } diff --git a/res/css/views/settings/tabs/room/_NotificationSettingsTab.pcss b/res/css/views/settings/tabs/room/_NotificationSettingsTab.pcss index 036a3a5389f..7d978cb5f76 100644 --- a/res/css/views/settings/tabs/room/_NotificationSettingsTab.pcss +++ b/res/css/views/settings/tabs/room/_NotificationSettingsTab.pcss @@ -54,18 +54,18 @@ limitations under the License. } .mx_NotificationSettingsTab_defaultEntry::before { - mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-image: url("$(res)/img/element-icons/notifications.svg"); } .mx_NotificationSettingsTab_allMessagesEntry::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-default.svg"); } .mx_NotificationSettingsTab_mentionsKeywordsEntry::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-dm.svg"); } .mx_NotificationSettingsTab_noneEntry::before { - mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/notifications-off.svg"); } } diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss index 3dad2a49a16..81777dc2a68 100644 --- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss @@ -50,7 +50,7 @@ limitations under the License. top: 0; left: 0; background-color: $alert; - mask-image: url('$(res)/img/feather-customised/alert-triangle.svg'); + mask-image: url("$(res)/img/feather-customised/alert-triangle.svg"); } } } diff --git a/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.pcss index f4c248a584a..5bcb6b16029 100644 --- a/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.pcss +++ b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.pcss @@ -63,18 +63,18 @@ limitations under the License. } .mx_SidebarUserSettingsTab_homeCheckbox .mx_Checkbox_background + div::before { - mask-image: url('$(res)/img/element-icons/home.svg'); + mask-image: url("$(res)/img/element-icons/home.svg"); } .mx_SidebarUserSettingsTab_favouritesCheckbox .mx_Checkbox_background + div::before { - mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/favorite.svg"); } .mx_SidebarUserSettingsTab_peopleCheckbox .mx_Checkbox_background + div::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-image: url("$(res)/img/element-icons/room/members.svg"); } .mx_SidebarUserSettingsTab_orphansCheckbox .mx_Checkbox_background + div::before { - mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); + mask-image: url("$(res)/img/element-icons/roomlist/hash-circle.svg"); } } diff --git a/res/css/views/spaces/_SpaceBasicSettings.pcss b/res/css/views/spaces/_SpaceBasicSettings.pcss index 1ec33f62de8..1fa2436285a 100644 --- a/res/css/views/spaces/_SpaceBasicSettings.pcss +++ b/res/css/views/spaces/_SpaceBasicSettings.pcss @@ -53,7 +53,7 @@ limitations under the License. mask-repeat: no-repeat; mask-position: center; mask-size: 20px; - mask-image: url('$(res)/img/element-icons/camera.svg'); + mask-image: url("$(res)/img/element-icons/camera.svg"); } } diff --git a/res/css/views/spaces/_SpaceCreateMenu.pcss b/res/css/views/spaces/_SpaceCreateMenu.pcss index 3c495e2863f..f1697dacb04 100644 --- a/res/css/views/spaces/_SpaceCreateMenu.pcss +++ b/res/css/views/spaces/_SpaceCreateMenu.pcss @@ -56,10 +56,10 @@ $spacePanelWidth: 68px; } .mx_SpaceCreateMenuType_public::before { - mask-image: url('$(res)/img/globe.svg'); + mask-image: url("$(res)/img/globe.svg"); } .mx_SpaceCreateMenuType_private::before { - mask-image: url('$(res)/img/element-icons/lock.svg'); + mask-image: url("$(res)/img/element-icons/lock.svg"); } .mx_SpaceCreateMenu_back { @@ -82,7 +82,7 @@ $spacePanelWidth: 68px; mask-repeat: no-repeat; mask-position: 2px 3px; mask-size: 24px; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); } } diff --git a/res/css/views/spaces/_SpacePublicShare.pcss b/res/css/views/spaces/_SpacePublicShare.pcss index 373fa94e004..f993d22361c 100644 --- a/res/css/views/spaces/_SpacePublicShare.pcss +++ b/res/css/views/spaces/_SpacePublicShare.pcss @@ -19,11 +19,11 @@ limitations under the License. @mixin SpacePillButton; &.mx_SpacePublicShare_shareButton::before { - mask-image: url('$(res)/img/element-icons/link.svg'); + mask-image: url("$(res)/img/element-icons/link.svg"); } &.mx_SpacePublicShare_inviteButton::before { - mask-image: url('$(res)/img/element-icons/room/invite.svg'); + mask-image: url("$(res)/img/element-icons/room/invite.svg"); } } } diff --git a/res/css/views/terms/_InlineTermsAgreement.pcss b/res/css/views/terms/_InlineTermsAgreement.pcss index 432f39a6934..2ea2c56d819 100644 --- a/res/css/views/terms/_InlineTermsAgreement.pcss +++ b/res/css/views/terms/_InlineTermsAgreement.pcss @@ -34,7 +34,7 @@ limitations under the License. .mx_InlineTermsAgreement_link { display: inline-block; - mask-image: url('$(res)/img/external-link.svg'); + mask-image: url("$(res)/img/external-link.svg"); background-color: $accent; mask-repeat: no-repeat; mask-size: contain; diff --git a/res/css/views/toasts/_IncomingCallToast.pcss b/res/css/views/toasts/_IncomingCallToast.pcss index f2d074f94b5..4d879f4f958 100644 --- a/res/css/views/toasts/_IncomingCallToast.pcss +++ b/res/css/views/toasts/_IncomingCallToast.pcss @@ -95,9 +95,9 @@ limitations under the License. width: $closeButtonSize; &::before { - content: ''; + content: ""; - mask-image: url('$(res)/img/cancel.svg'); + mask-image: url("$(res)/img/cancel.svg"); height: inherit; width: inherit; diff --git a/res/css/views/toasts/_IncomingLegacyCallToast.pcss b/res/css/views/toasts/_IncomingLegacyCallToast.pcss index 2fdaabf2437..e2092ef0064 100644 --- a/res/css/views/toasts/_IncomingLegacyCallToast.pcss +++ b/res/css/views/toasts/_IncomingLegacyCallToast.pcss @@ -58,7 +58,7 @@ limitations under the License. margin-right: 6px; &::before { - content: ''; + content: ""; position: absolute; height: inherit; width: inherit; @@ -72,14 +72,14 @@ limitations under the License. &.mx_IncomingLegacyCallToast_content_voice { .mx_LegacyCallEvent_type .mx_LegacyCallEvent_type_icon::before, .mx_IncomingLegacyCallToast_buttons .mx_IncomingLegacyCallToast_button_accept span::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + mask-image: url("$(res)/img/element-icons/call/voice-call.svg"); } } &.mx_IncomingLegacyCallToast_content_video { .mx_LegacyCallEvent_type .mx_LegacyCallEvent_type_icon::before, .mx_IncomingLegacyCallToast_buttons .mx_IncomingLegacyCallToast_button_accept span::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url("$(res)/img/element-icons/call/video-call.svg"); } } @@ -107,7 +107,7 @@ limitations under the License. } &.mx_IncomingLegacyCallToast_button_decline span::before { - mask-image: url('$(res)/img/element-icons/call/hangup.svg'); + mask-image: url("$(res)/img/element-icons/call/hangup.svg"); mask-size: 16px; width: 16px; height: 16px; @@ -122,7 +122,7 @@ limitations under the License. width: 20px; &::before { - content: ''; + content: ""; height: inherit; width: inherit; @@ -134,10 +134,10 @@ limitations under the License. } .mx_IncomingLegacyCallToast_silence::before { - mask-image: url('$(res)/img/voip/silence.svg'); + mask-image: url("$(res)/img/voip/silence.svg"); } .mx_IncomingLegacyCallToast_unSilence::before { - mask-image: url('$(res)/img/voip/un-silence.svg'); + mask-image: url("$(res)/img/voip/un-silence.svg"); } } diff --git a/res/css/views/toasts/_NonUrgentEchoFailureToast.pcss b/res/css/views/toasts/_NonUrgentEchoFailureToast.pcss index 4b21384e9ce..ddf25aa4018 100644 --- a/res/css/views/toasts/_NonUrgentEchoFailureToast.pcss +++ b/res/css/views/toasts/_NonUrgentEchoFailureToast.pcss @@ -23,11 +23,12 @@ limitations under the License. mask-size: contain; mask-repeat: no-repeat; background-color: #fff; /* we know that non-urgent toasts are always styled the same */ - mask-image: url('$(res)/img/element-icons/cloud-off.svg'); + mask-image: url("$(res)/img/element-icons/cloud-off.svg"); margin-right: 8px; } - span { /* includes the i18n block */ + span { + /* includes the i18n block */ vertical-align: middle; } } diff --git a/res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss b/res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss index 412bc112f8a..8127c163fef 100644 --- a/res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss +++ b/res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss @@ -53,7 +53,7 @@ limitations under the License. box-shadow: 0px 4px 4px 0px #00000026; /* Same on both themes */ &::before { - content: ''; + content: ""; display: inline-block; mask-repeat: no-repeat; @@ -77,7 +77,7 @@ limitations under the License. &::before { width: 14px; height: 14px; - mask-image: url('$(res)/img/element-icons/message/chevron-up.svg'); + mask-image: url("$(res)/img/element-icons/message/chevron-up.svg"); } &.mx_LegacyCallViewButtons_dropdownButton_collapsed::before { @@ -94,24 +94,24 @@ limitations under the License. } &.mx_LegacyCallViewButtons_button_mic::before { - mask-image: url('$(res)/img/voip/call-view/mic-on.svg'); + mask-image: url("$(res)/img/voip/call-view/mic-on.svg"); } &.mx_LegacyCallViewButtons_button_vid::before { - mask-image: url('$(res)/img/voip/call-view/cam-on.svg'); + mask-image: url("$(res)/img/voip/call-view/cam-on.svg"); } &.mx_LegacyCallViewButtons_button_screensharing { background-color: $accent; &::before { - mask-image: url('$(res)/img/voip/call-view/screensharing.svg'); + mask-image: url("$(res)/img/voip/call-view/screensharing.svg"); background-color: white; /* Same on both themes */ } } &.mx_LegacyCallViewButtons_button_sidebar::before { - mask-image: url('$(res)/img/voip/call-view/sidebar-on.svg'); + mask-image: url("$(res)/img/voip/call-view/sidebar-on.svg"); } } @@ -123,18 +123,18 @@ limitations under the License. } &.mx_LegacyCallViewButtons_button_mic::before { - mask-image: url('$(res)/img/voip/call-view/mic-off.svg'); + mask-image: url("$(res)/img/voip/call-view/mic-off.svg"); } &.mx_LegacyCallViewButtons_button_vid::before { - mask-image: url('$(res)/img/voip/call-view/cam-off.svg'); + mask-image: url("$(res)/img/voip/call-view/cam-off.svg"); } &.mx_LegacyCallViewButtons_button_screensharing { background-color: $call-view-button-on-background; &::before { - mask-image: url('$(res)/img/voip/call-view/screensharing.svg'); + mask-image: url("$(res)/img/voip/call-view/screensharing.svg"); background-color: $call-view-button-on-foreground; } } @@ -143,7 +143,7 @@ limitations under the License. background-color: $call-view-button-on-background; &::before { - mask-image: url('$(res)/img/voip/call-view/sidebar-off.svg'); + mask-image: url("$(res)/img/voip/call-view/sidebar-off.svg"); background-color: $call-view-button-on-foreground; } } @@ -152,20 +152,20 @@ limitations under the License. /* Stateless buttons */ &.mx_LegacyCallViewButtons_dialpad::before { - mask-image: url('$(res)/img/voip/call-view/dialpad.svg'); + mask-image: url("$(res)/img/voip/call-view/dialpad.svg"); } &.mx_LegacyCallViewButtons_button_hangup { background-color: $alert; &::before { - mask-image: url('$(res)/img/voip/call-view/hangup.svg'); + mask-image: url("$(res)/img/voip/call-view/hangup.svg"); background-color: white; /* Same on both themes */ } } &.mx_LegacyCallViewButtons_button_more::before { - mask-image: url('$(res)/img/voip/call-view/more.svg'); + mask-image: url("$(res)/img/voip/call-view/more.svg"); } /* Stateless buttons */ diff --git a/res/css/views/voip/_CallView.pcss b/res/css/views/voip/_CallView.pcss index 1fac95db82e..fcdbce7351a 100644 --- a/res/css/views/voip/_CallView.pcss +++ b/res/css/views/voip/_CallView.pcss @@ -126,7 +126,7 @@ limitations under the License. border-radius: calc($size / 2); &::before { - content: ''; + content: ""; display: inline-block; mask-repeat: no-repeat; mask-size: 20px; @@ -137,11 +137,11 @@ limitations under the License. } &.mx_CallView_deviceButton_audio::before { - mask-image: url('$(res)/img/voip/call-view/mic-on.svg'); + mask-image: url("$(res)/img/voip/call-view/mic-on.svg"); } &.mx_CallView_deviceButton_video::before { - mask-image: url('$(res)/img/voip/call-view/cam-on.svg'); + mask-image: url("$(res)/img/voip/call-view/cam-on.svg"); } } @@ -158,9 +158,9 @@ limitations under the License. border-radius: calc($size / 2); &::before { - content: ''; + content: ""; display: inline-block; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); mask-size: $size; mask-position: center; background-color: $call-lobby-primary-content; @@ -181,11 +181,11 @@ limitations under the License. .mx_CallView_deviceButton { &.mx_CallView_deviceButton_audio::before { - mask-image: url('$(res)/img/voip/call-view/mic-off.svg'); + mask-image: url("$(res)/img/voip/call-view/mic-off.svg"); } &.mx_CallView_deviceButton_video::before { - mask-image: url('$(res)/img/voip/call-view/cam-off.svg'); + mask-image: url("$(res)/img/voip/call-view/cam-off.svg"); } } } diff --git a/res/css/views/voip/_DialPad.pcss b/res/css/views/voip/_DialPad.pcss index e58bb493850..05d1b57eeba 100644 --- a/res/css/views/voip/_DialPad.pcss +++ b/res/css/views/voip/_DialPad.pcss @@ -53,7 +53,7 @@ limitations under the License. background-color: $accent; &::before { - content: ''; + content: ""; display: inline-block; height: 40px; width: 40px; @@ -61,7 +61,7 @@ limitations under the License. mask-repeat: no-repeat; mask-size: 20px; mask-position: center; - background-color: #FFF; /* on all themes */ - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + background-color: #fff; /* on all themes */ + mask-image: url("$(res)/img/element-icons/call/voice-call.svg"); } } diff --git a/res/css/views/voip/_LegacyCallViewForRoom.pcss b/res/css/views/voip/_LegacyCallViewForRoom.pcss index e88116017b4..ae6ade8b825 100644 --- a/res/css/views/voip/_LegacyCallViewForRoom.pcss +++ b/res/css/views/voip/_LegacyCallViewForRoom.pcss @@ -29,7 +29,7 @@ limitations under the License. justify-content: center; &::after { - content: ''; + content: ""; border-radius: 4px; height: 4px; diff --git a/res/css/views/voip/_LegacyCallViewHeader.pcss b/res/css/views/voip/_LegacyCallViewHeader.pcss index 9849cd1430e..39c52ba6348 100644 --- a/res/css/views/voip/_LegacyCallViewHeader.pcss +++ b/res/css/views/voip/_LegacyCallViewHeader.pcss @@ -37,7 +37,7 @@ limitations under the License. .mx_LegacyCallViewHeader_secondaryCallInfo { &::before { - content: '·'; + content: "·"; margin-left: 6px; margin-right: 6px; } @@ -55,7 +55,7 @@ limitations under the License. cursor: pointer; &::before { - content: ''; + content: ""; display: inline-block; height: 20px; width: 20px; @@ -68,19 +68,19 @@ limitations under the License. &.mx_LegacyCallViewHeader_button_fullscreen { &::before { - mask-image: url('$(res)/img/element-icons/call/fullscreen.svg'); + mask-image: url("$(res)/img/element-icons/call/fullscreen.svg"); } } &.mx_LegacyCallViewHeader_button_pin { &::before { - mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); } } &.mx_LegacyCallViewHeader_button_expand { &::before { - mask-image: url('$(res)/img/element-icons/call/expand.svg'); + mask-image: url("$(res)/img/element-icons/call/expand.svg"); } } } @@ -114,7 +114,7 @@ limitations under the License. vertical-align: middle; &::before { - content: ''; + content: ""; display: inline-block; vertical-align: top; @@ -124,6 +124,6 @@ limitations under the License. mask-repeat: no-repeat; mask-size: contain; mask-position: center; - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + mask-image: url("$(res)/img/element-icons/call/voice-call.svg"); } } diff --git a/res/css/views/voip/_VideoFeed.pcss b/res/css/views/voip/_VideoFeed.pcss index 3d08e19a6e2..6a9810e07be 100644 --- a/res/css/views/voip/_VideoFeed.pcss +++ b/res/css/views/voip/_VideoFeed.pcss @@ -80,11 +80,11 @@ limitations under the License. } &.mx_VideoFeed_mic_muted::before { - mask-image: url('$(res)/img/voip/mic-muted.svg'); + mask-image: url("$(res)/img/voip/mic-muted.svg"); } &.mx_VideoFeed_mic_unmuted::before { - mask-image: url('$(res)/img/voip/mic-unmuted.svg'); + mask-image: url("$(res)/img/voip/mic-unmuted.svg"); } } } diff --git a/res/css/voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss b/res/css/voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss new file mode 100644 index 00000000000..570a30e6f6f --- /dev/null +++ b/res/css/voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss @@ -0,0 +1,22 @@ +/* +Copyright 2022 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. +*/ + +.mx_RoomTile .mx_RoomTile_titleContainer .mx_RoomTile_subtitle.mx_RoomTile_subtitle--voice-broadcast { + align-items: center; + color: $alert; + display: flex; + gap: $spacing-4; +} diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss index bf4118b806b..3d463cbc9b5 100644 --- a/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss +++ b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss @@ -21,6 +21,10 @@ limitations under the License. display: inline-block; font-size: $font-12px; padding: $spacing-12; + + .mx_Clock { + line-height: 1; + } } .mx_VoiceBroadcastBody--pip { @@ -44,9 +48,8 @@ limitations under the License. } .mx_VoiceBroadcastBody_timerow { - align-items: center; display: flex; - gap: $spacing-4; + justify-content: space-between; } .mx_AccessibleButton.mx_VoiceBroadcastBody_blockButton { diff --git a/res/img/betas/threads.png b/res/img/betas/threads.png deleted file mode 100644 index f34fb5f8958..00000000000 Binary files a/res/img/betas/threads.png and /dev/null differ diff --git a/res/img/element-icons/room/composer/bold.svg b/res/img/element-icons/room/composer/bold.svg index 63fb9c97957..043f9c064cf 100644 --- a/res/img/element-icons/room/composer/bold.svg +++ b/res/img/element-icons/room/composer/bold.svg @@ -1,3 +1,3 @@ - + diff --git a/res/img/element-icons/room/composer/inline_code.svg b/res/img/element-icons/room/composer/inline_code.svg new file mode 100644 index 00000000000..d9f75fde0c7 --- /dev/null +++ b/res/img/element-icons/room/composer/inline_code.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/composer/italic.svg b/res/img/element-icons/room/composer/italic.svg index b669860c949..c6cd755d947 100644 --- a/res/img/element-icons/room/composer/italic.svg +++ b/res/img/element-icons/room/composer/italic.svg @@ -1,3 +1,3 @@ - + diff --git a/res/img/element-icons/room/composer/strikethrough.svg b/res/img/element-icons/room/composer/strikethrough.svg index a7ba639bc84..9a9761729b9 100644 --- a/res/img/element-icons/room/composer/strikethrough.svg +++ b/res/img/element-icons/room/composer/strikethrough.svg @@ -1,4 +1,4 @@ - - + + diff --git a/res/img/element-icons/room/composer/underline.svg b/res/img/element-icons/room/composer/underline.svg index 848d670f88c..f253c874ea8 100644 --- a/res/img/element-icons/room/composer/underline.svg +++ b/res/img/element-icons/room/composer/underline.svg @@ -1,3 +1,3 @@ - + diff --git a/res/img/element-icons/roomlist/search.svg b/res/img/element-icons/roomlist/search.svg index b706092a5cd..b6a1ad100f5 100644 --- a/res/img/element-icons/roomlist/search.svg +++ b/res/img/element-icons/roomlist/search.svg @@ -1,3 +1,3 @@ - + diff --git a/res/themes/dark/css/_dark.pcss b/res/themes/dark/css/_dark.pcss index cc9dc939565..7932949e852 100644 --- a/res/themes/dark/css/_dark.pcss +++ b/res/themes/dark/css/_dark.pcss @@ -1,17 +1,17 @@ /* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A741 */ /* ******************** */ $primary-content: #ffffff; -$secondary-content: #b3b3b3; -$tertiary-content: #808080; -$quaternary-content: #757575; -$quinary-content: #424242; - -$system: #383838; -$system-transparent: rgba($system, 0.0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ -$background: #212121; +$secondary-content: #a9b2bc; +$tertiary-content: #8e99a4; +$quaternary-content: #6f7882; +$quinary-content: #394049; + +$system: #21262c; +$system-transparent: rgba($system, 0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ +$background: #15191e; $overlay-background: rgba($background, 0.85); -$panel-base: #808080; /* This color is not intended for use in the app */ +$panel-base: #8d97a5; /* This color is not intended for use in the app */ $panels: rgba($system, 0.9); $panel-selected: rgba($panel-base, 0.3); $panel-hover: rgba($panel-base, 0.1); @@ -22,7 +22,7 @@ $space-nav: rgba($panel-base, 0.1); /* Reused Figma non-compound colors */ /* ******************** */ $inverted-bg-color: $background; -$header-panel-bg-color: #303030; +$header-panel-bg-color: #20252b; /* ******************** */ /* Theme specific colors */ @@ -32,9 +32,9 @@ $icon-button-color: $tertiary-content; /* Colors that aren't in Figma and are theme specific - we need to get rid of these */ /* ******************** */ -$header-panel-text-secondary-color: #cccccc; -$room-highlight-color: #424242; -$text-secondary-color: #b3b3b3; +$header-panel-text-secondary-color: #c8c8cd; +$room-highlight-color: #343a46; +$text-secondary-color: #b9bec6; /* ******************** */ /* Colors that aren't in Figma - we need to get rid of these */ @@ -50,7 +50,7 @@ $roomtopic-color: $text-secondary-color; $spacePanel-bg-color: rgba(48, 48, 48, 0.82); $panel-gradient: rgba(48, 48, 48, 0), rgba(48, 48, 48, 1); $h3-color: $primary-content; -$event-highlight-bg-color: transparent; +$event-highlight-bg-color: #25271f; $header-panel-text-primary-color: $text-secondary-color; /* ******************** */ @@ -115,9 +115,9 @@ $dialog-close-external-color: $primary-content; /* RoomList */ /* ******************** */ -$roomlist-bg-color: rgba($system, 0.90); -$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #424242 0%, #42424200 100%); -$roomtile-default-badge-bg-color: #8BC34A; +$roomlist-bg-color: rgba($system, 0.9); +$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%); +$roomtile-default-badge-bg-color: $input-darker-fg-color; /* ******************** */ /* Tabbed views */ @@ -166,9 +166,10 @@ $voice-record-icon-color: $quaternary-content; /* Message bubbles */ /* ******************** */ -$message-bubble-background: #424242; -$message-bubble-background-self: #303030; -$message-bubble-background-selected: #3F4931; +$eventbubble-self-bg: #133a34; +$eventbubble-others-bg: #21262c; +$eventbubble-bg-hover: #1c2026; +$eventbubble-reply-color: #c1c6cd; /* ******************** */ /* Lightbox */ @@ -317,25 +318,12 @@ body { /* Splash Page Gradient */ .mx_SplashPage::before { - background-image: - radial-gradient( + background-image: radial-gradient( 53.85% 66.75% at 87.55% 0%, hsla(0, 0%, 11%, 0.15) 0%, hsla(250, 100%, 88%, 0) 100% ), - radial-gradient( - 41.93% 41.93% at 0% 0%, - hsla(0, 0%, 38%, 0.28) 0%, - hsla(250, 100%, 88%, 0) 100% - ), - radial-gradient( - 100% 100% at 0% 0%, - hsla(250, 100%, 88%, 0.3) 0%, - hsla(0, 100%, 86%, 0) 100% - ), - radial-gradient( - 106.35% 96.26% at 100% 0%, - hsla(25, 100%, 88%, 0.4) 0%, - hsla(167, 76%, 82%, 0) 100% - ) !important; + radial-gradient(41.93% 41.93% at 0% 0%, hsla(0, 0%, 38%, 0.28) 0%, hsla(250, 100%, 88%, 0) 100%), + radial-gradient(100% 100% at 0% 0%, hsla(250, 100%, 88%, 0.3) 0%, hsla(0, 100%, 86%, 0) 100%), + radial-gradient(106.35% 96.26% at 100% 0%, hsla(25, 100%, 88%, 0.4) 0%, hsla(167, 76%, 82%, 0) 100%) !important; } diff --git a/res/themes/legacy-dark/css/_legacy-dark.pcss b/res/themes/legacy-dark/css/_legacy-dark.pcss index fb088e290cd..17082c6efca 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.pcss +++ b/res/themes/legacy-dark/css/_legacy-dark.pcss @@ -1,6 +1,6 @@ /* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A741 */ -$system: #383838; -$system-transparent: rgba($system, 0.0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ +$system: #21262c; +$system-transparent: rgba($system, 0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ /* unified palette */ /* try to use these colors when possible */ @@ -87,21 +87,21 @@ $roomtopic-color: $text-secondary-color; $room-icon-unread-color: #fff; /* Legacy theme backports */ -$accent: #8BC34A; -$alert: #E53935; +$accent: #0dbd8b; +$alert: #ff5b55; $links: #0086e6; $primary-content: $primary-fg-color; $secondary-content: $secondary-fg-color; $tertiary-content: $tertiary-fg-color; -$quaternary-content: #757575; +$quaternary-content: #6f7882; $quinary-content: $quaternary-content; -$system: #383838; -$system-transparent: rgba($system, 0.0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ +$system: #21262c; +$system-transparent: rgba($system, 0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ $background: $primary-bg-color; $overlay-background: rgba($background, 0.85); $panels: rgba($system, 0.9); -$panel-base: #808080; /* This color is not intended for use in the app */ +$panel-base: #8d97a5; /* This color is not intended for use in the app */ $panel-selected: rgba($panel-base, 0.3); $panel-hover: rgba($panel-base, 0.1); $panel-actions: $roomtile-selected-bg-color; @@ -130,15 +130,15 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #424242 0%, #42424200 100%) $spacePanel-divider-color: $tertiary-content; -$roomtile-default-badge-bg-color: #8BC34A; -$roomtile-selected-bg-color: #303030; +$roomtile-default-badge-bg-color: #61708b; +$roomtile-selected-bg-color: #1a1d23; /* ******************** */ $widget-menu-bar-bg-color: $header-panel-bg-color; -$widget-body-bg-color: #303030; +$widget-body-bg-color: #1a1d23; -$event-highlight-bg-color: transparent; +$event-highlight-bg-color: #25271f; /* event timestamp */ $event-timestamp-color: $text-secondary-color; @@ -180,8 +180,8 @@ $tooltip-timeline-fg-color: #ffffff; $breadcrumb-placeholder-bg-color: #303030; /* See non-legacy dark for variable information */ -$voice-record-stop-border-color: #757575; -$voice-record-icon-color: #757575; +$voice-record-stop-border-color: #6f7882; +$voice-record-icon-color: #6f7882; /* Appearance tab colors */ $appearance-tab-border-color: $room-highlight-color; @@ -191,10 +191,11 @@ $composer-shadow-color: tranparent; /* Background for code in event tile */ $codeblock-background-color: #121212; -/* Message bubbles */ -$message-bubble-background: #424242; -$message-bubble-background-self: #303030; -$message-bubble-background-selected: #3F4931; +/* Bubble tiles */ +$eventbubble-self-bg: #14322e; +$eventbubble-others-bg: $event-selected-color; +$eventbubble-bg-hover: #1c2026; +$eventbubble-reply-color: #c1c6cd; /* Location sharing */ /* ******************** */ diff --git a/res/themes/legacy-light/css/_fonts.pcss b/res/themes/legacy-light/css/_fonts.pcss index 8aae8951c78..741595a6064 100644 --- a/res/themes/legacy-light/css/_fonts.pcss +++ b/res/themes/legacy-light/css/_fonts.pcss @@ -20,20 +20,20 @@ /* the 'src' links are relative to the bundle.css, which is in a subdirectory. */ @font-face { - font-family: 'Nunito'; + font-family: "Nunito"; font-style: normal; font-weight: 400; - src: url('$(res)/fonts/Nunito/Nunito-Regular.ttf') format('truetype'); + src: url("$(res)/fonts/Nunito/Nunito-Regular.ttf") format("truetype"); } @font-face { - font-family: 'Nunito'; + font-family: "Nunito"; font-style: normal; font-weight: 600; - src: url('$(res)/fonts/Nunito/Nunito-SemiBold.ttf') format('truetype'); + src: url("$(res)/fonts/Nunito/Nunito-SemiBold.ttf") format("truetype"); } @font-face { - font-family: 'Nunito'; + font-family: "Nunito"; font-style: normal; font-weight: 700; - src: url('$(res)/fonts/Nunito/Nunito-Bold.ttf') format('truetype'); + src: url("$(res)/fonts/Nunito/Nunito-Bold.ttf") format("truetype"); } diff --git a/res/themes/legacy-light/css/_legacy-light.pcss b/res/themes/legacy-light/css/_legacy-light.pcss index c22f3ce3363..806f7dcd472 100644 --- a/res/themes/legacy-light/css/_legacy-light.pcss +++ b/res/themes/legacy-light/css/_legacy-light.pcss @@ -142,7 +142,7 @@ $roomheader-addroom-bg-color: #757575; $roomheader-addroom-fg-color: $accent-fg-color; $icon-button-color: #757575; $roomtopic-color: #9e9e9e; -$room-icon-unread-color: #757575; +$room-icon-unread-color: #737d8c; /* ******************** */ @@ -152,25 +152,25 @@ $roomtile-default-badge-bg-color: #8BC34A; $roomtile-selected-bg-color: #fff; $presence-away: #d9b072; -$presence-offline: #bdbdbd; -$presence-busy: #E53935; +$presence-offline: #e3e8f0; +$presence-busy: #ff5b55; /* Legacy theme backports */ -$accent: #8BC34A; -$alert: #E53935; +$accent: #0dbd8b; +$alert: #ff5b55; $links: #0086e6; $primary-content: $primary-fg-color; $secondary-content: $secondary-fg-color; $tertiary-content: $tertiary-fg-color; -$quaternary-content: #757575; +$quaternary-content: #6f7882; $quinary-content: $quaternary-content; -$system: #f5f5f5; -$system-transparent: rgba($system, 0.0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ +$system: #f4f6fa; +$system-transparent: rgba($system, 0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ $background: $primary-bg-color; $overlay-background: rgba($background, 0.85); $panels: rgba($system, 0.9); -$panel-base: #808080; /* This color is not intended for use in the app */ +$panel-base: #8d97a5; /* This color is not intended for use in the app */ $panel-selected: rgba($tertiary-content, 0.3); $panel-hover: rgba($tertiary-content, 0.1); $panel-actions: $roomtile-selected-bg-color; @@ -190,14 +190,14 @@ $call-view-button-on-foreground: $secondary-content; $call-view-button-on-background: $background; $call-view-button-off-foreground: $background; $call-view-button-off-background: $secondary-content; -$call-view-content-background: #383838; +$call-view-content-background: #21262c; $video-feed-secondary-background: #424242; /* XXX: Color from dark theme */ /* All of these are from dark theme */ -$call-lobby-system: #383838; -$call-lobby-background: #212121; -$call-lobby-primary-content: #FFFFFF; +$call-lobby-system: #21262c; +$call-lobby-background: #15191e; +$call-lobby-primary-content: #ffffff; $username-variant1-color: #4189c9; $username-variant2-color: #aa43a7; @@ -297,7 +297,7 @@ $breadcrumb-placeholder-bg-color: #e0e0e0; /* See non-legacy _light for variable information */ $voice-record-stop-symbol-color: #ff4b55; $voice-record-live-circle-color: #ff4b55; -$voice-record-stop-border-color: #e0e0e0; +$voice-record-stop-border-color: #e3e8f0; $voice-record-icon-color: $tertiary-fg-color; /* FontSlider colors */ @@ -308,10 +308,11 @@ $composer-shadow-color: tranparent; /* Background for code in event tile */ $codeblock-background-color: #00000010; -/* Message bubbles */ -$message-bubble-background: #eeeeee; -$message-bubble-background-self: #F1F8E9; -$message-bubble-background-selected: #DBEDC6; +/* Bubble tiles */ +$eventbubble-self-bg: #f0fbf8; +$eventbubble-others-bg: $system; +$eventbubble-bg-hover: #fafbfd; +$eventbubble-reply-color: #c1c6cd; /* pinned events indicator */ $pinned-color: $tertiary-content; diff --git a/res/themes/light-custom/css/_custom.pcss b/res/themes/light-custom/css/_custom.pcss index 4e9207e1f1e..62d18d3cf8c 100644 --- a/res/themes/light-custom/css/_custom.pcss +++ b/res/themes/light-custom/css/_custom.pcss @@ -27,7 +27,7 @@ $tertiary-content: var(--tertiary-content, $tertiary-content); $quaternary-content: var(--quaternary-content, $quaternary-content); $quinary-content: var(--quinary-content, $quinary-content); /* XXX: workaround for Safari 15.3 linear-gradient bug https://github.com/vector-im/element-web/issues/21670 */ -$system-transparent: var(--system-transparent, rgba($system, 0.0)); +$system-transparent: var(--system-transparent, rgba($system, 0)); $system: var(--system, $system); $background: var(--background, $background); $panels: rgba($system, 0.9); @@ -90,7 +90,7 @@ $roomtile-default-badge-bg-color: var(--roomlist-text-secondary-color); /* --roomlist-separator-color */ $input-darker-bg-color: var(--roomlist-separator-color); -$primary-hairline-color: var(--roomlist-separator-color);/* originally #e5e5e5, but close enough */ +$primary-hairline-color: var(--roomlist-separator-color); /* originally #e5e5e5, but close enough */ /* --timeline-text-secondary-color */ $authpage-secondary-color: var(--timeline-text-secondary-color); @@ -138,8 +138,10 @@ $message-bubble-background: var(--eventbubble-others-bg, $message-bubble-backgro $message-bubble-background-self: var(--eventbubble-self-bg, $message-bubble-background-self); $message-bubble-background-selected: var(--eventbubble-selected-bg, $message-bubble-background-selected); -$reaction-row-button-selected-bg-color: - var(--reaction-row-button-selected-bg-color, $reaction-row-button-selected-bg-color); +$reaction-row-button-selected-bg-color: var( + --reaction-row-button-selected-bg-color, + $reaction-row-button-selected-bg-color +); $menu-selected-color: var(--menu-selected-color, $menu-selected-color); $pill-bg-color: var(--other-user-pill-bg-color, $pill-bg-color); diff --git a/res/themes/light-high-contrast/css/_light-high-contrast.pcss b/res/themes/light-high-contrast/css/_light-high-contrast.pcss index 32eec6d8f1a..cb2e6a1e850 100644 --- a/res/themes/light-high-contrast/css/_light-high-contrast.pcss +++ b/res/themes/light-high-contrast/css/_light-high-contrast.pcss @@ -1,21 +1,21 @@ /* Reference: https://www.figma.com/file/RnLKnv09glhxGIZtn8zfmh/UI-Themes-%26-Accessibility?node-id=321%3A65847 */ $accent: #268075; -$alert: #D62C25; -$links: #0A6ECA; -$primary-content: #17191C; -$secondary-content: #5E6266; +$alert: #d62c25; +$links: #0a6eca; +$primary-content: #17191c; +$secondary-content: #5e6266; $tertiary-content: $secondary-content; $quaternary-content: $secondary-content; $quinary-content: $secondary-content; -$username-variant1-color: #0A6ECA; -$username-variant2-color: #AC3BA8; +$username-variant1-color: #0a6eca; +$username-variant2-color: #ac3ba8; $username-variant3-color: #078662; -$username-variant4-color: #CC1449; -$username-variant5-color: #BE4C00; -$username-variant6-color: #1C7274; -$username-variant7-color: #5C56F5; -$username-variant8-color: #3E810A; +$username-variant4-color: #cc1449; +$username-variant5-color: #be4c00; +$username-variant6-color: #1c7274; +$username-variant7-color: #5c56f5; +$username-variant8-color: #3e810a; $accent-alt: $links; $input-border-color: $secondary-content; @@ -60,13 +60,13 @@ $roomtopic-color: $secondary-content; /* Add padding (and remove margin to compensate), so the outline is not */ /* chopped off on the left */ .mx_TabbedView_tabPanel { - margin-left: 236px !important; /* Remove 4 to allow 4 in mx_SettingsTab */ + margin-left: 236px !important; /* Remove 4 to allow 4 in mx_SettingsTab */ } .mx_SettingsTab { padding-left: 4px !important; } .mx_BaseCard { - padding-left: 4px !important; /* Remove 4 to allow 4 in mx_BaseCard_Group */ + padding-left: 4px !important; /* Remove 4 to allow 4 in mx_BaseCard_Group */ } .mx_BaseCard_Group { padding-left: 4px !important; @@ -82,7 +82,8 @@ $roomtopic-color: $secondary-content; opacity: 1 !important; } -.mx_Dialog, .mx_MatrixChat_wrapper { +.mx_Dialog, +.mx_MatrixChat_wrapper { :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="text"]::placeholder, :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="search"]::placeholder, .mx_textinput input::placeholder { @@ -107,7 +108,8 @@ $roomtopic-color: $secondary-content; } .mx_RoomSearch { - &.mx_RoomSearch_focused, &.mx_RoomSearch_hasQuery { + &.mx_RoomSearch_focused, + &.mx_RoomSearch_hasQuery { .mx_RoomSearch_clearButton { &::before { background-color: $background !important; @@ -150,7 +152,8 @@ $roomtopic-color: $secondary-content; #mx_SpotlightDialog_content { .mx_SpotlightDialog_recentlyViewed { .mx_SpotlightDialog_option { - &:hover, &[aria-selected="true"] { + &:hover, + &[aria-selected="true"] { color: $background !important; .mx_DecoratedRoomAvatar_icon::before { @@ -164,7 +167,8 @@ $roomtopic-color: $secondary-content; .mx_SpotlightDialog_otherSearches, .mx_SpotlightDialog_hiddenResults { .mx_SpotlightDialog_option { - &:hover, &[aria-selected="true"] { + &:hover, + &[aria-selected="true"] { background-color: $quinary-content !important; color: $background !important; diff --git a/res/themes/light/css/_fonts.pcss b/res/themes/light/css/_fonts.pcss index 68d9496276c..64a81ce48d3 100644 --- a/res/themes/light/css/_fonts.pcss +++ b/res/themes/light/css/_fonts.pcss @@ -7,116 +7,123 @@ Therefore we define a unicode-range to load which excludes the glyphs (to avoid having to maintain a fork of Inter). */ -$inter-unicode-range: U+0000-20e2,U+20e4-23ce,U+23d0-24c1,U+24c3-259f,U+25c2-2664,U+2666-2763,U+2765-2b05,U+2b07-2b1b,U+2b1d-10FFFF; +$inter-unicode-range: U+0000-20e2, U+20e4-23ce, U+23d0-24c1, U+24c3-259f, U+25c2-2664, U+2666-2763, U+2765-2b05, + U+2b07-2b1b, U+2b1d-10FFFF; @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 400; - font-display: swap; - unicode-range: $inter-unicode-range; - src: url("$(res)/fonts/Inter/Inter-Regular.woff2?v=3.18") format("woff2"), - url("$(res)/fonts/Inter/Inter-Regular.woff?v=3.18") format("woff"); + font-family: "Inter"; + font-style: normal; + font-weight: 400; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-Regular.woff2?v=3.18") format("woff2"), + url("$(res)/fonts/Inter/Inter-Regular.woff?v=3.18") format("woff"); } @font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 400; - font-display: swap; - unicode-range: $inter-unicode-range; - src: url("$(res)/fonts/Inter/Inter-Italic.woff2?v=3.18") format("woff2"), - url("$(res)/fonts/Inter/Inter-Italic.woff?v=3.18") format("woff"); + font-family: "Inter"; + font-style: italic; + font-weight: 400; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-Italic.woff2?v=3.18") format("woff2"), + url("$(res)/fonts/Inter/Inter-Italic.woff?v=3.18") format("woff"); } @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 500; - font-display: swap; - unicode-range: $inter-unicode-range; - src: url("$(res)/fonts/Inter/Inter-Medium.woff2?v=3.18") format("woff2"), - url("$(res)/fonts/Inter/Inter-Medium.woff?v=3.18") format("woff"); + font-family: "Inter"; + font-style: normal; + font-weight: 500; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-Medium.woff2?v=3.18") format("woff2"), + url("$(res)/fonts/Inter/Inter-Medium.woff?v=3.18") format("woff"); } @font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 500; - font-display: swap; - unicode-range: $inter-unicode-range; - src: url("$(res)/fonts/Inter/Inter-MediumItalic.woff2?v=3.18") format("woff2"), - url("$(res)/fonts/Inter/Inter-MediumItalic.woff?v=3.18") format("woff"); + font-family: "Inter"; + font-style: italic; + font-weight: 500; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-MediumItalic.woff2?v=3.18") format("woff2"), + url("$(res)/fonts/Inter/Inter-MediumItalic.woff?v=3.18") format("woff"); } @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 600; - font-display: swap; - unicode-range: $inter-unicode-range; - src: url("$(res)/fonts/Inter/Inter-SemiBold.woff2?v=3.18") format("woff2"), - url("$(res)/fonts/Inter/Inter-SemiBold.woff?v=3.18") format("woff"); + font-family: "Inter"; + font-style: normal; + font-weight: 600; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-SemiBold.woff2?v=3.18") format("woff2"), + url("$(res)/fonts/Inter/Inter-SemiBold.woff?v=3.18") format("woff"); } @font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 600; - font-display: swap; - unicode-range: $inter-unicode-range; - src: url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff2?v=3.18") format("woff2"), - url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff?v=3.18") format("woff"); + font-family: "Inter"; + font-style: italic; + font-weight: 600; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff2?v=3.18") format("woff2"), + url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff?v=3.18") format("woff"); } @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 700; - font-display: swap; - unicode-range: $inter-unicode-range; - src: url("$(res)/fonts/Inter/Inter-Bold.woff2?v=3.18") format("woff2"), - url("$(res)/fonts/Inter/Inter-Bold.woff?v=3.18") format("woff"); + font-family: "Inter"; + font-style: normal; + font-weight: 700; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-Bold.woff2?v=3.18") format("woff2"), + url("$(res)/fonts/Inter/Inter-Bold.woff?v=3.18") format("woff"); } @font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 700; - font-display: swap; - unicode-range: $inter-unicode-range; - src: url("$(res)/fonts/Inter/Inter-BoldItalic.woff2?v=3.18") format("woff2"), - url("$(res)/fonts/Inter/Inter-BoldItalic.woff?v=3.18") format("woff"); + font-family: "Inter"; + font-style: italic; + font-weight: 700; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-BoldItalic.woff2?v=3.18") format("woff2"), + url("$(res)/fonts/Inter/Inter-BoldItalic.woff?v=3.18") format("woff"); } /* latin-ext */ @font-face { - font-family: 'Inconsolata'; + font-family: "Inconsolata"; font-style: normal; font-weight: 400; - src: local('Inconsolata Regular'), local('Inconsolata-Regular'), url('$(res)/fonts/Inconsolata/QldKNThLqRwH-OJ1UHjlKGlX5qhExfHwNJU.woff2') format('woff2'); + src: local("Inconsolata Regular"), local("Inconsolata-Regular"), + url("$(res)/fonts/Inconsolata/QldKNThLqRwH-OJ1UHjlKGlX5qhExfHwNJU.woff2") format("woff2"); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { - font-family: 'Inconsolata'; + font-family: "Inconsolata"; font-style: normal; font-weight: 400; font-display: swap; - src: local('Inconsolata Regular'), local('Inconsolata-Regular'), url('$(res)/fonts/Inconsolata/QldKNThLqRwH-OJ1UHjlKGlZ5qhExfHw.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + src: local("Inconsolata Regular"), local("Inconsolata-Regular"), + url("$(res)/fonts/Inconsolata/QldKNThLqRwH-OJ1UHjlKGlZ5qhExfHw.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, + U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* latin-ext */ @font-face { - font-family: 'Inconsolata'; + font-family: "Inconsolata"; font-style: normal; font-weight: 700; font-display: swap; - src: local('Inconsolata Bold'), local('Inconsolata-Bold'), url('$(res)/fonts/Inconsolata/QldXNThLqRwH-OJ1UHjlKGHiw71n5_zaDpwm80E.woff2') format('woff2'); + src: local("Inconsolata Bold"), local("Inconsolata-Bold"), + url("$(res)/fonts/Inconsolata/QldXNThLqRwH-OJ1UHjlKGHiw71n5_zaDpwm80E.woff2") format("woff2"); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { - font-family: 'Inconsolata'; + font-family: "Inconsolata"; font-style: normal; font-weight: 700; font-display: swap; - src: local('Inconsolata Bold'), local('Inconsolata-Bold'), url('$(res)/fonts/Inconsolata/QldXNThLqRwH-OJ1UHjlKGHiw71p5_zaDpwm.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + src: local("Inconsolata Bold"), local("Inconsolata-Bold"), + url("$(res)/fonts/Inconsolata/QldXNThLqRwH-OJ1UHjlKGHiw71p5_zaDpwm.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, + U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } diff --git a/res/themes/light/css/_light.pcss b/res/themes/light/css/_light.pcss index f37672e545a..b55b8133e94 100644 --- a/res/themes/light/css/_light.pcss +++ b/res/themes/light/css/_light.pcss @@ -10,35 +10,22 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: 'Inter', - 'Twemoji', - 'Apple Color Emoji', - 'Segoe UI Emoji', - 'STIXGeneral', - 'Arial', - 'Helvetica', - sans-serif, - 'Noto Color Emoji'; - -$monospace-font-family: 'Inconsolata', - 'Twemoji', - 'Apple Color Emoji', - 'Segoe UI Emoji', - 'STIXGeneral', - 'Courier', - monospace, - 'Noto Color Emoji'; +$font-family: "Inter", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "STIXGeneral", "Arial", "Helvetica", sans-serif, + "Noto Color Emoji"; + +$monospace-font-family: "Inconsolata", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "STIXGeneral", "Courier", + monospace, "Noto Color Emoji"; /* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120 */ /* ******************** */ -$primary-content: #212121; -$secondary-content: #757575; -$tertiary-content: #808080; -$quaternary-content: #bdbdbd; -$quinary-content: #e0e0e0; +$primary-content: #17191c; +$secondary-content: #737d8c; +$tertiary-content: #8d97a5; +$quaternary-content: #c1c6cd; +$quinary-content: #e3e8f0; -$system: #f5f5f5; -$system-transparent: rgba($system, 0.0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ +$system: #f4f6fa; +$system-transparent: rgba($system, 0); /* XXX: workaround for Safari 15.3 linear-gradient bug */ $background: #ffffff; $overlay-background: rgba($background, 0.85); @@ -92,7 +79,7 @@ $neutral-badge-color: #dbdbdb; $strong-input-border-color: #c7c7c7; $preview-widget-bar-color: #bdbdbd; $accent-fg-color: $background; -$accent-alt: #368bd6; +$accent-alt: #238cf5; $info-plinth-fg-color: #888; /* ******************** */ @@ -107,11 +94,10 @@ $event-selected-color: #f5f5f5; $topleftmenu-color: #212121; $roomtopic-color: #9e9e9e; $spacePanel-bg-color: rgba(232, 232, 232, 0.77); -$panel-gradient: rgba(248, 248, 248, 0), - rgba(248, 248, 248, 1); -$h3-color: #424242; -$event-highlight-bg-color: transparent; -$header-panel-text-primary-color: #757575; +$panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); +$h3-color: #3d3b39; +$event-highlight-bg-color: $yellow-background; +$header-panel-text-primary-color: #91a1c0; /* ******************** */ /* Blockquote */ @@ -199,7 +185,7 @@ $imagebody-giflabel-color: $background; /* RoomList */ /* ******************** */ -$roomlist-bg-color: rgba(245, 245, 245, 0.90); +$roomlist-bg-color: rgba(245, 245, 245, 0.9); $roomsublist-skeleton-ui-bg: linear-gradient(180deg, $background 0%, #ffffff00 100%); $roomtile-default-badge-bg-color: #8BC34A; /* ******************** */ @@ -271,9 +257,10 @@ $voice-record-icon-color: $tertiary-content; /* Message bubbles */ /* ******************** */ -$message-bubble-background: #eeeeee; -$message-bubble-background-self: #F1F8E9; -$message-bubble-background-selected: #DBEDC6; +$eventbubble-self-bg: #e7f8f3; +$eventbubble-others-bg: #e8edf4; +$eventbubble-bg-hover: #f6f7f8; +$eventbubble-reply-color: $quaternary-content; /* ******************** */ /* Lightbox */ @@ -289,15 +276,15 @@ $call-view-button-on-foreground: $secondary-content; $call-view-button-on-background: $background; $call-view-button-off-foreground: $background; $call-view-button-off-background: $secondary-content; -$call-view-content-background: #383838; +$call-view-content-background: #21262c; $video-feed-secondary-background: #424242; /* XXX: Color from dark theme */ $voipcall-plinth-color: $system; /* All of these are from dark theme */ -$call-lobby-system: #383838; -$call-lobby-background: #212121; -$call-lobby-primary-content: #FFFFFF; +$call-lobby-system: #21262c; +$call-lobby-background: #15191e; +$call-lobby-primary-content: #ffffff; /* ******************** */ /* One-off colors */ @@ -370,7 +357,8 @@ $live-badge-color: #ffffff; outline: none; } -@define-mixin mx_DialogButton_hover {} +@define-mixin mx_DialogButton_hover { +} @define-mixin mx_DialogButton_danger { background-color: $accent; diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile deleted file mode 100644 index 08c9153578b..00000000000 --- a/scripts/ci/Dockerfile +++ /dev/null @@ -1,69 +0,0 @@ -# Docker file for end-to-end tests - -# Update on docker hub with the following commands in the directory of this file: -# If you're on linux amd64 -# docker build -t vectorim/element-web-ci-e2etests-env:latest . -# If you're on some other platform, you need to cross-compile -# docker buildx build --platform linux/amd64,linux/arm64 --push -t vectorim/element-web-ci-e2etests-env:latest . -# Then: -# docker push vectorim/element-web-ci-e2etests-env:latest -FROM node:14-buster -RUN apt-get update -RUN apt-get -y install \ - build-essential \ - jq \ - libffi-dev \ - libjpeg-dev \ - libssl-dev \ - libxslt1-dev \ - python3-dev \ - python-pip \ - python-setuptools \ - python-virtualenv \ - sqlite3 \ - uuid-runtime - -# dependencies for chrome (installed by puppeteer) -RUN apt-get -y install \ - ca-certificates \ - fonts-liberation \ - gconf-service \ - libappindicator1 \ - libasound2 \ - libatk1.0-0 \ - libatk-bridge2.0-0 \ - libc6 \ - libcairo2 \ - libcups2 \ - libdbus-1-3 \ - libexpat1 \ - libfontconfig1 \ - libgbm-dev \ - libgcc1 \ - libgconf-2-4 \ - libgdk-pixbuf2.0-0 \ - libglib2.0-0 \ - libgtk-3-0 \ - libnspr4 \ - libnss3 \ - libpango-1.0-0 \ - libpangocairo-1.0-0 \ - libstdc++6 \ - libx11-6 \ - libx11-xcb1 \ - libxcb1 \ - libxcomposite1 \ - libxcursor1 \ - libxdamage1 \ - libxext6 \ - libxfixes3 \ - libxi6 \ - libxrandr2 \ - libxrender1 \ - libxss1 \ - libxtst6 \ - lsb-release \ - wget \ - xdg-utils - -RUN npm install -g typescript diff --git a/scripts/make-react-component.js b/scripts/make-react-component.js index 5fbfecb064f..544a4f4f311 100755 --- a/scripts/make-react-component.js +++ b/scripts/make-react-component.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const fs = require('fs/promises'); -const path = require('path'); +const fs = require("fs/promises"); +const path = require("path"); /** * Unsophisticated script to create a styled, unit-tested react component. @@ -51,18 +51,17 @@ describe('<%%ComponentName%% />', () => { .mx_%%ComponentName%% { } -` -} - +`, +}; const options = { alias: { - filePath: 'f', - withStyle: 's' - } -} + filePath: "f", + withStyle: "s", + }, +}; -const args = require('minimist')(process.argv, options); +const args = require("minimist")(process.argv, options); const ensureDirectoryExists = async (filePath) => { const dirName = path.parse(filePath).dir; @@ -70,54 +69,77 @@ const ensureDirectoryExists = async (filePath) => { try { await fs.access(dirName); return; - } catch (error) { } + } catch (error) {} - await fs.mkdir(dirName, { recursive: true }) -} + await fs.mkdir(dirName, { recursive: true }); +}; -const makeFile = async ({ - filePath, componentName, extension, base, template, prefix, componentFilePath -}) => { - const newFilePath = path.join(base, path.dirname(filePath), `${prefix || ''}${path.basename(filePath)}${extension}`) +const makeFile = async ({ filePath, componentName, extension, base, template, prefix, componentFilePath }) => { + const newFilePath = path.join( + base, + path.dirname(filePath), + `${prefix || ""}${path.basename(filePath)}${extension}`, + ); await ensureDirectoryExists(newFilePath); - const relativePathToComponent = path.parse(path.relative(path.dirname(newFilePath), componentFilePath || '')); + const relativePathToComponent = path.parse(path.relative(path.dirname(newFilePath), componentFilePath || "")); const importComponentPath = path.join(relativePathToComponent.dir, relativePathToComponent.name); try { - await fs.writeFile(newFilePath, fillTemplate(template, componentName, importComponentPath), { flag: 'wx' }); + await fs.writeFile(newFilePath, fillTemplate(template, componentName, importComponentPath), { flag: "wx" }); console.log(`Created ${path.relative(process.cwd(), newFilePath)}`); return newFilePath; } catch (error) { - if (error.code === 'EEXIST') { + if (error.code === "EEXIST") { console.log(`File already exists ${path.relative(process.cwd(), newFilePath)}`); return newFilePath; } else { throw error; } } -} +}; const fillTemplate = (template, componentName, relativeComponentFilePath, skinnedSdkPath) => - template.replace(/%%ComponentName%%/g, componentName) - .replace(/%%RelativeComponentPath%%/g, relativeComponentFilePath) - + template + .replace(/%%ComponentName%%/g, componentName) + .replace(/%%RelativeComponentPath%%/g, relativeComponentFilePath); const makeReactComponent = async () => { const { filePath, withStyle } = args; if (!filePath) { - throw new Error('No file path provided, did you forget -f?') + throw new Error("No file path provided, did you forget -f?"); } - const componentName = filePath.split('/').slice(-1).pop(); + const componentName = filePath.split("/").slice(-1).pop(); - const componentFilePath = await makeFile({ filePath, componentName, base: 'src', extension: '.tsx', template: TEMPLATES.COMPONENT }); - await makeFile({ filePath, componentFilePath, componentName, base: 'test', extension: '-test.tsx', template: TEMPLATES.TEST, componentName }); + const componentFilePath = await makeFile({ + filePath, + componentName, + base: "src", + extension: ".tsx", + template: TEMPLATES.COMPONENT, + }); + await makeFile({ + filePath, + componentFilePath, + componentName, + base: "test", + extension: "-test.tsx", + template: TEMPLATES.TEST, + componentName, + }); if (withStyle) { - await makeFile({ filePath, componentName, base: 'res/css', prefix: '_', extension: '.pcss', template: TEMPLATES.STYLE }); + await makeFile({ + filePath, + componentName, + base: "res/css", + prefix: "_", + extension: ".pcss", + template: TEMPLATES.STYLE, + }); } -} +}; // Wrapper since await at the top level is not well supported yet function run() { @@ -128,4 +150,3 @@ function run() { run(); return; - diff --git a/src/@types/common.ts b/src/@types/common.ts index e054cc1da84..ddcd0bb3dc6 100644 --- a/src/@types/common.ts +++ b/src/@types/common.ts @@ -17,8 +17,8 @@ limitations under the License. import React, { JSXElementConstructor } from "react"; // Based on https://stackoverflow.com/a/53229857/3532235 -export type Without = {[P in Exclude]?: never}; -export type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; +export type Without = { [P in Exclude]?: never }; +export type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; export type Writeable = { -readonly [P in keyof T]: T[P] }; export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor; @@ -26,28 +26,35 @@ export type ReactAnyComponent = React.Component | React.ExoticComponent; // Utility type for string dot notation for accessing nested object properties // Based on https://stackoverflow.com/a/58436959 -type Join = K extends string | number ? - P extends string | number ? - `${K}${"" extends P ? "" : "."}${P}` - : never : never; +type Join = K extends string | number + ? P extends string | number + ? `${K}${"" extends P ? "" : "."}${P}` + : never + : never; type Prev = [never, 0, 1, 2, 3, ...0[]]; -export type Leaves = [D] extends [never] ? never : T extends object ? - { [K in keyof T]-?: Join> }[keyof T] : ""; +export type Leaves = [D] extends [never] + ? never + : T extends object + ? { [K in keyof T]-?: Join> }[keyof T] + : ""; export type RecursivePartial = { - [P in keyof T]?: - T[P] extends (infer U)[] ? RecursivePartial[] : - T[P] extends object ? RecursivePartial : - T[P]; + [P in keyof T]?: T[P] extends (infer U)[] + ? RecursivePartial[] + : T[P] extends object + ? RecursivePartial + : T[P]; }; // Inspired by https://stackoverflow.com/a/60206860 export type KeysWithObjectShape = { [P in keyof Input]: Input[P] extends object - // Arrays are counted as objects - exclude them - ? (Input[P] extends Array ? never : P) + ? // Arrays are counted as objects - exclude them + Input[P] extends Array + ? never + : P : never; }[keyof Input]; diff --git a/src/@types/diff-dom.d.ts b/src/@types/diff-dom.d.ts index 38ff6432cf8..5998b0e4041 100644 --- a/src/@types/diff-dom.d.ts +++ b/src/@types/diff-dom.d.ts @@ -26,8 +26,7 @@ declare module "diff-dom" { newValue: string; } - interface IOpts { - } + interface IOpts {} export class DiffDOM { public constructor(opts?: IOpts); diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index c4971d24f12..f1e72ca2fc4 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -73,7 +73,7 @@ declare global { // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029#issuecomment-869224737 // https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas OffscreenCanvas?: { - new(width: number, height: number): OffscreenCanvas; + new (width: number, height: number): OffscreenCanvas; }; mxContentMessages: ContentMessages; @@ -149,14 +149,7 @@ declare global { // https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas interface OffscreenCanvas { - height: number; - width: number; - getContext: HTMLCanvasElement["getContext"]; - convertToBlob(opts?: { - type?: string; - quality?: number; - }): Promise; - transferToImageBitmap(): ImageBitmap; + convertToBlob(opts?: { type?: string; quality?: number }): Promise; } interface HTMLAudioElement { @@ -205,11 +198,7 @@ declare global { // https://github.com/microsoft/TypeScript/issues/28308#issuecomment-650802278 interface AudioWorkletProcessor { readonly port: MessagePort; - process( - inputs: Float32Array[][], - outputs: Float32Array[][], - parameters: Record - ): boolean; + process(inputs: Float32Array[][], outputs: Float32Array[][], parameters: Record): boolean; } // https://github.com/microsoft/TypeScript/issues/28308#issuecomment-650802278 @@ -226,11 +215,9 @@ declare global { // https://github.com/microsoft/TypeScript/issues/28308#issuecomment-650802278 function registerProcessor( name: string, - processorCtor: (new ( - options?: AudioWorkletNodeOptions - ) => AudioWorkletProcessor) & { + processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & { parameterDescriptors?: AudioParamDescriptor[]; - } + }, ); // eslint-disable-next-line no-var diff --git a/src/@types/polyfill.ts b/src/@types/polyfill.ts index 3ce05d9c2f9..12a201e9c8e 100644 --- a/src/@types/polyfill.ts +++ b/src/@types/polyfill.ts @@ -21,15 +21,33 @@ export function polyfillTouchEvent() { if (!window.TouchEvent) { // We have no intention of actually using this, so just lie. window.TouchEvent = class TouchEvent extends UIEvent { - public get altKey(): boolean { return false; } - public get changedTouches(): any { return []; } - public get ctrlKey(): boolean { return false; } - public get metaKey(): boolean { return false; } - public get shiftKey(): boolean { return false; } - public get targetTouches(): any { return []; } - public get touches(): any { return []; } - public get rotation(): number { return 0.0; } - public get scale(): number { return 0.0; } + public get altKey(): boolean { + return false; + } + public get changedTouches(): any { + return []; + } + public get ctrlKey(): boolean { + return false; + } + public get metaKey(): boolean { + return false; + } + public get shiftKey(): boolean { + return false; + } + public get targetTouches(): any { + return []; + } + public get touches(): any { + return []; + } + public get rotation(): number { + return 0.0; + } + public get scale(): number { + return 0.0; + } constructor(eventType: string, params?: any) { super(eventType, params); } diff --git a/src/@types/raw-loader.d.ts b/src/@types/raw-loader.d.ts index efd825204e9..04663e572de 100644 --- a/src/@types/raw-loader.d.ts +++ b/src/@types/raw-loader.d.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -declare module '!!raw-loader!*' { +declare module "!!raw-loader!*" { const contents: string; export default contents; } diff --git a/src/@types/sanitize-html.d.ts b/src/@types/sanitize-html.d.ts index 4cada298457..373189f22fa 100644 --- a/src/@types/sanitize-html.d.ts +++ b/src/@types/sanitize-html.d.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import sanitizeHtml from 'sanitize-html'; +import sanitizeHtml from "sanitize-html"; export interface IExtendedSanitizeOptions extends sanitizeHtml.IOptions { // This option only exists in 2.x RCs so far, so not yet present in the diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts index 95ebbec0f57..eb06169c090 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -18,10 +18,10 @@ limitations under the License. import { IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix"; -import { MatrixClientPeg } from './MatrixClientPeg'; -import Modal from './Modal'; -import { _t } from './languageHandler'; -import IdentityAuthClient from './IdentityAuthClient'; +import { MatrixClientPeg } from "./MatrixClientPeg"; +import Modal from "./Modal"; +import { _t } from "./languageHandler"; +import IdentityAuthClient from "./IdentityAuthClient"; import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents"; import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog"; @@ -58,17 +58,22 @@ export default class AddThreepid { * @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked(). */ public addEmailAddress(emailAddress: string): Promise { - return MatrixClientPeg.get().requestAdd3pidEmailToken(emailAddress, this.clientSecret, 1).then((res) => { - this.sessionId = res.sid; - return res; - }, function(err) { - if (err.errcode === 'M_THREEPID_IN_USE') { - err.message = _t('This email address is already in use'); - } else if (err.httpStatus) { - err.message = err.message + ` (Status ${err.httpStatus})`; - } - throw err; - }); + return MatrixClientPeg.get() + .requestAdd3pidEmailToken(emailAddress, this.clientSecret, 1) + .then( + (res) => { + this.sessionId = res.sid; + return res; + }, + function (err) { + if (err.errcode === "M_THREEPID_IN_USE") { + err.message = _t("This email address is already in use"); + } else if (err.httpStatus) { + err.message = err.message + ` (Status ${err.httpStatus})`; + } + throw err; + }, + ); } /** @@ -83,20 +88,22 @@ export default class AddThreepid { // For separate bind, request a token directly from the IS. const authClient = new IdentityAuthClient(); const identityAccessToken = await authClient.getAccessToken(); - return MatrixClientPeg.get().requestEmailToken( - emailAddress, this.clientSecret, 1, - undefined, identityAccessToken, - ).then((res) => { - this.sessionId = res.sid; - return res; - }, function(err) { - if (err.errcode === 'M_THREEPID_IN_USE') { - err.message = _t('This email address is already in use'); - } else if (err.httpStatus) { - err.message = err.message + ` (Status ${err.httpStatus})`; - } - throw err; - }); + return MatrixClientPeg.get() + .requestEmailToken(emailAddress, this.clientSecret, 1, undefined, identityAccessToken) + .then( + (res) => { + this.sessionId = res.sid; + return res; + }, + function (err) { + if (err.errcode === "M_THREEPID_IN_USE") { + err.message = _t("This email address is already in use"); + } else if (err.httpStatus) { + err.message = err.message + ` (Status ${err.httpStatus})`; + } + throw err; + }, + ); } else { // For tangled bind, request a token via the HS. return this.addEmailAddress(emailAddress); @@ -111,20 +118,23 @@ export default class AddThreepid { * @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken(). */ public addMsisdn(phoneCountry: string, phoneNumber: string): Promise { - return MatrixClientPeg.get().requestAdd3pidMsisdnToken( - phoneCountry, phoneNumber, this.clientSecret, 1, - ).then((res) => { - this.sessionId = res.sid; - this.submitUrl = res.submit_url; - return res; - }, function(err) { - if (err.errcode === 'M_THREEPID_IN_USE') { - err.message = _t('This phone number is already in use'); - } else if (err.httpStatus) { - err.message = err.message + ` (Status ${err.httpStatus})`; - } - throw err; - }); + return MatrixClientPeg.get() + .requestAdd3pidMsisdnToken(phoneCountry, phoneNumber, this.clientSecret, 1) + .then( + (res) => { + this.sessionId = res.sid; + this.submitUrl = res.submit_url; + return res; + }, + function (err) { + if (err.errcode === "M_THREEPID_IN_USE") { + err.message = _t("This phone number is already in use"); + } else if (err.httpStatus) { + err.message = err.message + ` (Status ${err.httpStatus})`; + } + throw err; + }, + ); } /** @@ -140,20 +150,22 @@ export default class AddThreepid { // For separate bind, request a token directly from the IS. const authClient = new IdentityAuthClient(); const identityAccessToken = await authClient.getAccessToken(); - return MatrixClientPeg.get().requestMsisdnToken( - phoneCountry, phoneNumber, this.clientSecret, 1, - undefined, identityAccessToken, - ).then((res) => { - this.sessionId = res.sid; - return res; - }, function(err) { - if (err.errcode === 'M_THREEPID_IN_USE') { - err.message = _t('This phone number is already in use'); - } else if (err.httpStatus) { - err.message = err.message + ` (Status ${err.httpStatus})`; - } - throw err; - }); + return MatrixClientPeg.get() + .requestMsisdnToken(phoneCountry, phoneNumber, this.clientSecret, 1, undefined, identityAccessToken) + .then( + (res) => { + this.sessionId = res.sid; + return res; + }, + function (err) { + if (err.errcode === "M_THREEPID_IN_USE") { + err.message = _t("This phone number is already in use"); + } else if (err.httpStatus) { + err.message = err.message + ` (Status ${err.httpStatus})`; + } + throw err; + }, + ); } else { // For tangled bind, request a token via the HS. return this.addMsisdn(phoneCountry, phoneNumber); @@ -194,8 +206,10 @@ export default class AddThreepid { const dialogAesthetics = { [SSOAuthEntry.PHASE_PREAUTH]: { title: _t("Use Single Sign On to continue"), - body: _t("Confirm adding this email address by using " + - "Single Sign On to prove your identity."), + body: _t( + "Confirm adding this email address by using " + + "Single Sign On to prove your identity.", + ), continueText: _t("Single Sign On"), continueKind: "primary", }, @@ -220,15 +234,18 @@ export default class AddThreepid { } } } else { - await MatrixClientPeg.get().addThreePid({ - sid: this.sessionId, - client_secret: this.clientSecret, - id_server: getIdServerDomain(), - }, this.bind); + await MatrixClientPeg.get().addThreePid( + { + sid: this.sessionId, + client_secret: this.clientSecret, + id_server: getIdServerDomain(), + }, + this.bind, + ); } } catch (err) { if (err.httpStatus === 401) { - err.message = _t('Failed to verify email address: make sure you clicked the link in the email'); + err.message = _t("Failed to verify email address: make sure you clicked the link in the email"); } else if (err.httpStatus) { err.message += ` (Status ${err.httpStatus})`; } @@ -240,7 +257,7 @@ 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?: { type: string; session?: string }): Promise<{}> => { return MatrixClientPeg.get().addThreePidOnly({ sid: this.sessionId, client_secret: this.clientSecret, @@ -258,8 +275,7 @@ export default class AddThreepid { */ public async haveMsisdnToken(msisdnToken: string): Promise { const authClient = new IdentityAuthClient(); - const supportsSeparateAddAndBind = - await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind(); + const supportsSeparateAddAndBind = await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind(); let result; if (this.submitUrl) { @@ -307,8 +323,9 @@ export default class AddThreepid { const dialogAesthetics = { [SSOAuthEntry.PHASE_PREAUTH]: { title: _t("Use Single Sign On to continue"), - body: _t("Confirm adding this phone number by using " + - "Single Sign On to prove your identity."), + body: _t( + "Confirm adding this phone number by using " + "Single Sign On to prove your identity.", + ), continueText: _t("Single Sign On"), continueKind: "primary", }, @@ -333,11 +350,14 @@ export default class AddThreepid { } } } else { - await MatrixClientPeg.get().addThreePid({ - sid: this.sessionId, - client_secret: this.clientSecret, - id_server: getIdServerDomain(), - }, this.bind); + await MatrixClientPeg.get().addThreePid( + { + sid: this.sessionId, + client_secret: this.clientSecret, + id_server: getIdServerDomain(), + }, + this.bind, + ); } } } diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx index 07a06f20d44..95cbc2c5181 100644 --- a/src/AsyncWrapper.tsx +++ b/src/AsyncWrapper.tsx @@ -17,7 +17,7 @@ limitations under the License. import React, { ComponentType } from "react"; import { logger } from "matrix-js-sdk/src/logger"; -import { _t } from './languageHandler'; +import { _t } from "./languageHandler"; import { IDialogProps } from "./components/views/dialogs/IDialogProps"; import BaseDialog from "./components/views/dialogs/BaseDialog"; import DialogButtons from "./components/views/elements/DialogButtons"; @@ -50,21 +50,23 @@ export default class AsyncWrapper extends React.Component { componentDidMount() { // XXX: temporary logging to try to diagnose // https://github.com/vector-im/element-web/issues/3148 - logger.log('Starting load of AsyncWrapper for modal'); - this.props.prom.then((result) => { - if (this.unmounted) return; - - // Take the 'default' member if it's there, then we support - // passing in just an import()ed module, since ES6 async import - // always returns a module *namespace*. - const component = (result as AsyncImport).default - ? (result as AsyncImport).default - : result as ComponentType; - this.setState({ component }); - }).catch((e) => { - logger.warn('AsyncWrapper promise failed', e); - this.setState({ error: e }); - }); + logger.log("Starting load of AsyncWrapper for modal"); + this.props.prom + .then((result) => { + if (this.unmounted) return; + + // Take the 'default' member if it's there, then we support + // passing in just an import()ed module, since ES6 async import + // always returns a module *namespace*. + const component = (result as AsyncImport).default + ? (result as AsyncImport).default + : (result as ComponentType); + this.setState({ component }); + }) + .catch((e) => { + logger.warn("AsyncWrapper promise failed", e); + this.setState({ error: e }); + }); } componentWillUnmount() { @@ -80,17 +82,19 @@ export default class AsyncWrapper extends React.Component { const Component = this.state.component; return ; } else if (this.state.error) { - return - { _t("Unable to load! Check your network connectivity and try again.") } - - ; + return ( + + {_t("Unable to load! Check your network connectivity and try again.")} + + + ); } else { // show a spinner until the component is loaded. return ; } } } - diff --git a/src/Avatar.ts b/src/Avatar.ts index 1b8eb6cb01e..c61b8b9a205 100644 --- a/src/Avatar.ts +++ b/src/Avatar.ts @@ -20,7 +20,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { ResizeMethod } from "matrix-js-sdk/src/@types/partials"; import { split } from "lodash"; -import DMRoomMap from './utils/DMRoomMap'; +import DMRoomMap from "./utils/DMRoomMap"; import { mediaFromMxc } from "./customisations/Media"; import { isLocalRoom } from "./utils/localRoom/isLocalRoom"; @@ -39,7 +39,7 @@ export function avatarUrlForMember( // member can be null here currently since on invites, the JS SDK // does not have enough info to build a RoomMember object for // the inviter. - url = defaultAvatarUrlForString(member ? member.userId : ''); + url = defaultAvatarUrlForString(member ? member.userId : ""); } return url; } @@ -55,10 +55,15 @@ export function avatarUrlForUser( } function isValidHexColor(color: string): boolean { - return typeof color === "string" && + return ( + typeof color === "string" && (color.length === 7 || color.length === 9) && color.charAt(0) === "#" && - !color.slice(1).split("").some(c => isNaN(parseInt(c, 16))); + !color + .slice(1) + .split("") + .some((c) => isNaN(parseInt(c, 16))) + ); } function urlForColor(color: string): string { @@ -124,7 +129,7 @@ export function getInitialLetter(name: string): string { } const initial = name[0]; - if ((initial === '@' || initial === '#' || initial === '+') && name[1]) { + if ((initial === "@" || initial === "#" || initial === "+") && name[1]) { name = name.substring(1); } @@ -143,10 +148,7 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi if (room.isSpaceRoom()) return null; // If the room is not a DM don't fallback to a member avatar - if ( - !DMRoomMap.shared().getUserIdForRoomId(room.roomId) - && !(isLocalRoom(room)) - ) { + if (!DMRoomMap.shared().getUserIdForRoomId(room.roomId) && !isLocalRoom(room)) { return null; } diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 9efed3b7ea5..ff5d6fafa72 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -23,8 +23,8 @@ import { logger } from "matrix-js-sdk/src/logger"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Room } from "matrix-js-sdk/src/models/room"; -import dis from './dispatcher/dispatcher'; -import BaseEventIndexManager from './indexing/BaseEventIndexManager'; +import dis from "./dispatcher/dispatcher"; +import BaseEventIndexManager from "./indexing/BaseEventIndexManager"; import { ActionPayload } from "./dispatcher/payloads"; import { CheckUpdatesPayload } from "./dispatcher/payloads/CheckUpdatesPayload"; import { Action } from "./dispatcher/actions"; @@ -80,7 +80,7 @@ export default abstract class BasePlatform { protected onAction = (payload: ActionPayload): void => { switch (payload.action) { - case 'on_client_not_viable': + case "on_client_not_viable": case Action.OnLoggedOut: this.setNotificationCount(0); break; @@ -200,7 +200,7 @@ export default abstract class BasePlatform { body: msg, silent: true, // we play our own sounds }; - if (avatarUrl) notifBody['icon'] = avatarUrl; + if (avatarUrl) notifBody["icon"] = avatarUrl; const notification = new window.Notification(title, notifBody); notification.onclick = () => { @@ -376,7 +376,8 @@ export default abstract class BasePlatform { try { const key = await crypto.subtle.decrypt( - { name: "AES-GCM", iv: data.iv, additionalData }, data.cryptoKey, + { name: "AES-GCM", iv: data.iv, additionalData }, + data.cryptoKey, data.encrypted, ); return encodeUnpaddedBase64(key); @@ -400,9 +401,10 @@ export default abstract class BasePlatform { const crypto = window.crypto; const randomArray = new Uint8Array(32); crypto.getRandomValues(randomArray); - const cryptoKey = await crypto.subtle.generateKey( - { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"], - ); + const cryptoKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [ + "encrypt", + "decrypt", + ]); const iv = new Uint8Array(32); crypto.getRandomValues(iv); @@ -415,9 +417,7 @@ export default abstract class BasePlatform { additionalData[userId.length + 1 + i] = deviceId.charCodeAt(i); } - const encrypted = await crypto.subtle.encrypt( - { name: "AES-GCM", iv, additionalData }, cryptoKey, randomArray, - ); + const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, cryptoKey, randomArray); try { await idbSave("pickleKey", [userId, deviceId], { encrypted, iv, cryptoKey }); diff --git a/src/BlurhashEncoder.ts b/src/BlurhashEncoder.ts index 2aee370fe90..d9a7ab85041 100644 --- a/src/BlurhashEncoder.ts +++ b/src/BlurhashEncoder.ts @@ -57,4 +57,3 @@ export class BlurhashEncoder { return deferred.promise; } } - diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts index 8135eaab0ef..47d336dc333 100644 --- a/src/ContentMessages.ts +++ b/src/ContentMessages.ts @@ -27,9 +27,9 @@ import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; import { removeElement } from "matrix-js-sdk/src/utils"; import { IEncryptedFile, IMediaEventContent, IMediaEventInfo } from "./customisations/models/IMediaEventContent"; -import dis from './dispatcher/dispatcher'; -import { _t } from './languageHandler'; -import Modal from './Modal'; +import dis from "./dispatcher/dispatcher"; +import { _t } from "./languageHandler"; +import Modal from "./Modal"; import Spinner from "./components/views/elements/Spinner"; import { Action } from "./dispatcher/actions"; import { @@ -73,11 +73,11 @@ async function loadImageElement(imageFile: File) { const img = new Image(); const objectUrl = URL.createObjectURL(imageFile); const imgPromise = new Promise((resolve, reject) => { - img.onload = function() { + img.onload = function () { URL.revokeObjectURL(objectUrl); resolve(img); }; - img.onerror = function(e) { + img.onerror = function (e) { reject(e); }; }); @@ -92,11 +92,11 @@ async function loadImageElement(imageFile: File) { // Thus we could slice the file down to only sniff the first 0x1000 // bytes (but this makes extractPngChunks choke on the corrupt file) const headers = imageFile; //.slice(0, 0x1000); - parsePromise = readFileAsArrayBuffer(headers).then(arrayBuffer => { + parsePromise = readFileAsArrayBuffer(headers).then((arrayBuffer) => { const buffer = new Uint8Array(arrayBuffer); const chunks = extractPngChunks(buffer); for (const chunk of chunks) { - if (chunk.name === 'pHYs') { + if (chunk.name === "pHYs") { if (chunk.data.byteLength !== PHYS_HIDPI.length) return; return chunk.data.every((val, i) => val === PHYS_HIDPI[i]); } @@ -106,8 +106,8 @@ async function loadImageElement(imageFile: File) { } const [hidpi] = await Promise.all([parsePromise, imgPromise]); - const width = hidpi ? (img.width >> 1) : img.width; - const height = hidpi ? (img.height >> 1) : img.height; + const width = hidpi ? img.width >> 1 : img.width; + const height = hidpi ? img.height >> 1 : img.height; return { width, height, img }; } @@ -154,7 +154,7 @@ async function infoForImageFile( imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL || // thumbnail is not sufficiently smaller than original (sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE && - sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT)) + sizeDifference <= imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT) ) { delete imageInfo["thumbnail_info"]; return imageInfo; @@ -185,13 +185,13 @@ function loadVideoElement(videoFile: File): Promise { const reader = new FileReader(); - reader.onload = function(ev) { + reader.onload = function (ev) { // Wait until we have enough data to thumbnail the first frame. - video.onloadeddata = async function() { + video.onloadeddata = async function () { resolve(video); video.pause(); }; - video.onerror = function(e) { + video.onerror = function (e) { reject(e); }; @@ -206,7 +206,7 @@ function loadVideoElement(videoFile: File): Promise { video.load(); video.play(); }; - reader.onerror = function(e) { + reader.onerror = function (e) { reject(e); }; reader.readAsDataURL(videoFile); @@ -229,16 +229,19 @@ function infoForVideoFile( const thumbnailType = "image/jpeg"; let videoInfo: Partial; - return loadVideoElement(videoFile).then((video) => { - return createThumbnail(video, video.videoWidth, video.videoHeight, thumbnailType); - }).then((result) => { - videoInfo = result.info; - return uploadFile(matrixClient, roomId, result.thumbnail); - }).then((result) => { - videoInfo.thumbnail_url = result.url; - videoInfo.thumbnail_file = result.file; - return videoInfo; - }); + return loadVideoElement(videoFile) + .then((video) => { + return createThumbnail(video, video.videoWidth, video.videoHeight, thumbnailType); + }) + .then((result) => { + videoInfo = result.info; + return uploadFile(matrixClient, roomId, result.thumbnail); + }) + .then((result) => { + videoInfo.thumbnail_url = result.url; + videoInfo.thumbnail_file = result.file; + return videoInfo; + }); } /** @@ -250,10 +253,10 @@ function infoForVideoFile( function readFileAsArrayBuffer(file: File | Blob): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onload = function(e) { + reader.onload = function (e) { resolve(e.target.result as ArrayBuffer); }; - reader.onerror = function(e) { + reader.onerror = function (e) { reject(e); }; reader.readAsArrayBuffer(file); @@ -280,7 +283,7 @@ export async function uploadFile( file: File | Blob, progressHandler?: UploadOpts["progressHandler"], controller?: AbortController, -): Promise<{ url?: string, file?: IEncryptedFile }> { +): Promise<{ url?: string; file?: IEncryptedFile }> { const abortController = controller ?? new AbortController(); // If the room is encrypted then encrypt the file before uploading it. @@ -357,13 +360,14 @@ export default class ContentMessages { context = TimelineRenderingType.Room, ): Promise { if (matrixClient.isGuest()) { - dis.dispatch({ action: 'require_registration' }); + dis.dispatch({ action: "require_registration" }); return; } const replyToEvent = SdkContextClass.instance.roomViewStore.getQuotingEvent(); - if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to - const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner'); + if (!this.mediaConfig) { + // hot-path optimization to not flash a spinner if we don't need to + const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); await this.ensureMediaConfigFetched(matrixClient); modal.close(); } @@ -410,16 +414,8 @@ export default class ContentMessages { } } - promBefore = doMaybeLocalRoomAction( - roomId, - (actualRoomId) => this.sendContentToRoom( - file, - actualRoomId, - relation, - matrixClient, - replyToEvent, - loopPromiseBefore, - ), + promBefore = doMaybeLocalRoomAction(roomId, (actualRoomId) => + this.sendContentToRoom(file, actualRoomId, relation, matrixClient, replyToEvent, loopPromiseBefore), ); } @@ -440,11 +436,13 @@ export default class ContentMessages { } public getCurrentUploads(relation?: IEventRelation): RoomUpload[] { - return this.inprogress.filter(roomUpload => { + return this.inprogress.filter((roomUpload) => { const noRelation = !relation && !roomUpload.relation; - const matchingRelation = relation && roomUpload.relation - && relation.rel_type === roomUpload.relation.rel_type - && relation.event_id === roomUpload.relation.event_id; + const matchingRelation = + relation && + roomUpload.relation && + relation.rel_type === roomUpload.relation.rel_type && + relation.event_id === roomUpload.relation.event_id; return (noRelation || matchingRelation) && !roomUpload.cancelled; }); @@ -498,7 +496,7 @@ export default class ContentMessages { } try { - if (file.type.startsWith('image/')) { + if (file.type.startsWith("image/")) { content.msgtype = MsgType.Image; try { const imageInfo = await infoForImageFile(matrixClient, roomId, file); @@ -508,9 +506,9 @@ export default class ContentMessages { logger.error(e); content.msgtype = MsgType.File; } - } else if (file.type.indexOf('audio/') === 0) { + } else if (file.type.indexOf("audio/") === 0) { content.msgtype = MsgType.Audio; - } else if (file.type.indexOf('video/') === 0) { + } else if (file.type.indexOf("video/") === 0) { content.msgtype = MsgType.Video; try { const videoInfo = await infoForVideoFile(matrixClient, roomId, file); @@ -543,7 +541,7 @@ export default class ContentMessages { } dis.dispatch({ action: Action.UploadFinished, upload }); - dis.dispatch({ action: 'message_sent' }); + dis.dispatch({ action: "message_sent" }); } catch (error) { // 413: File was too big or upset the server in some way: // clear the media size limit so we fetch it again next time we try to upload @@ -554,26 +552,27 @@ export default class ContentMessages { if (!upload.cancelled) { let desc = _t("The file '%(fileName)s' failed to upload.", { fileName: upload.fileName }); if (error.httpStatus === 413) { - desc = _t( - "The file '%(fileName)s' exceeds this homeserver's size limit for uploads", - { fileName: upload.fileName }, - ); + desc = _t("The file '%(fileName)s' exceeds this homeserver's size limit for uploads", { + fileName: upload.fileName, + }); } Modal.createDialog(ErrorDialog, { - title: _t('Upload Failed'), + title: _t("Upload Failed"), description: desc, }); dis.dispatch({ action: Action.UploadFailed, upload, error }); } } finally { - removeElement(this.inprogress, e => e.promise === upload.promise); + removeElement(this.inprogress, (e) => e.promise === upload.promise); } } private isFileSizeAcceptable(file: File) { - if (this.mediaConfig !== null && + if ( + this.mediaConfig !== null && this.mediaConfig["m.upload.size"] !== undefined && - file.size > this.mediaConfig["m.upload.size"]) { + file.size > this.mediaConfig["m.upload.size"] + ) { return false; } return true; @@ -583,16 +582,20 @@ export default class ContentMessages { if (this.mediaConfig !== null) return; logger.log("[Media Config] Fetching"); - return matrixClient.getMediaConfig().then((config) => { - logger.log("[Media Config] Fetched config:", config); - return config; - }).catch(() => { - // Media repo can't or won't report limits, so provide an empty object (no limits). - logger.log("[Media Config] Could not fetch config, so not limiting uploads."); - return {}; - }).then((config) => { - this.mediaConfig = config; - }); + return matrixClient + .getMediaConfig() + .then((config) => { + logger.log("[Media Config] Fetched config:", config); + return config; + }) + .catch(() => { + // Media repo can't or won't report limits, so provide an empty object (no limits). + logger.log("[Media Config] Could not fetch config, so not limiting uploads."); + return {}; + }) + .then((config) => { + this.mediaConfig = config; + }); } static sharedInstance() { diff --git a/src/DateUtils.ts b/src/DateUtils.ts index 25495b0542f..1dab03121ec 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -16,45 +16,37 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { _t } from './languageHandler'; +import { _t } from "./languageHandler"; function getDaysArray(): string[] { - return [ - _t('Sun'), - _t('Mon'), - _t('Tue'), - _t('Wed'), - _t('Thu'), - _t('Fri'), - _t('Sat'), - ]; + return [_t("Sun"), _t("Mon"), _t("Tue"), _t("Wed"), _t("Thu"), _t("Fri"), _t("Sat")]; } function getMonthsArray(): string[] { return [ - _t('Jan'), - _t('Feb'), - _t('Mar'), - _t('Apr'), - _t('May'), - _t('Jun'), - _t('Jul'), - _t('Aug'), - _t('Sep'), - _t('Oct'), - _t('Nov'), - _t('Dec'), + _t("Jan"), + _t("Feb"), + _t("Mar"), + _t("Apr"), + _t("May"), + _t("Jun"), + _t("Jul"), + _t("Aug"), + _t("Sep"), + _t("Oct"), + _t("Nov"), + _t("Dec"), ]; } function pad(n: number): string { - return (n < 10 ? '0' : '') + n; + return (n < 10 ? "0" : "") + n; } function twelveHourTime(date: Date, showSeconds = false): string { let hours = date.getHours() % 12; const minutes = pad(date.getMinutes()); - const ampm = date.getHours() >= 12 ? _t('PM') : _t('AM'); + const ampm = date.getHours() >= 12 ? _t("PM") : _t("AM"); hours = hours ? hours : 12; // convert 0 -> 12 if (showSeconds) { const seconds = pad(date.getSeconds()); @@ -71,13 +63,13 @@ export function formatDate(date: Date, showTwelveHour = false): string { return formatTime(date, showTwelveHour); } else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) { // TODO: use standard date localize function provided in counterpart - return _t('%(weekDayName)s %(time)s', { + return _t("%(weekDayName)s %(time)s", { weekDayName: days[date.getDay()], time: formatTime(date, showTwelveHour), }); } else if (now.getFullYear() === date.getFullYear()) { // TODO: use standard date localize function provided in counterpart - return _t('%(weekDayName)s, %(monthName)s %(day)s %(time)s', { + return _t("%(weekDayName)s, %(monthName)s %(day)s %(time)s", { weekDayName: days[date.getDay()], monthName: months[date.getMonth()], day: date.getDate(), @@ -90,7 +82,7 @@ export function formatDate(date: Date, showTwelveHour = false): string { export function formatFullDateNoTime(date: Date): string { const days = getDaysArray(); const months = getMonthsArray(); - return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s', { + return _t("%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", { weekDayName: days[date.getDay()], monthName: months[date.getMonth()], day: date.getDate(), @@ -101,7 +93,7 @@ export function formatFullDateNoTime(date: Date): string { export function formatFullDate(date: Date, showTwelveHour = false, showSeconds = true): string { const days = getDaysArray(); const months = getMonthsArray(); - return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', { + return _t("%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", { weekDayName: days[date.getDay()], monthName: months[date.getMonth()], day: date.getDate(), @@ -114,45 +106,45 @@ export function formatFullTime(date: Date, showTwelveHour = false): string { if (showTwelveHour) { return twelveHourTime(date, true); } - return pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()); + return pad(date.getHours()) + ":" + pad(date.getMinutes()) + ":" + pad(date.getSeconds()); } export function formatTime(date: Date, showTwelveHour = false): string { if (showTwelveHour) { return twelveHourTime(date); } - return pad(date.getHours()) + ':' + pad(date.getMinutes()); -} - -export function formatCallTime(delta: Date): string { - const hours = delta.getUTCHours(); - const minutes = delta.getUTCMinutes(); - const seconds = delta.getUTCSeconds(); - - let output = ""; - if (hours) output += `${hours}h `; - if (minutes || output) output += `${minutes}m `; - if (seconds || output) output += `${seconds}s`; - - return output; + return pad(date.getHours()) + ":" + pad(date.getMinutes()); } export function formatSeconds(inSeconds: number): string { - const hours = Math.floor(inSeconds / (60 * 60)).toFixed(0).padStart(2, '0'); - const minutes = Math.floor((inSeconds % (60 * 60)) / 60).toFixed(0).padStart(2, '0'); - const seconds = Math.floor(((inSeconds % (60 * 60)) % 60)).toFixed(0).padStart(2, '0'); + const isNegative = inSeconds < 0; + inSeconds = Math.abs(inSeconds); + + const hours = Math.floor(inSeconds / (60 * 60)) + .toFixed(0) + .padStart(2, "0"); + const minutes = Math.floor((inSeconds % (60 * 60)) / 60) + .toFixed(0) + .padStart(2, "0"); + const seconds = Math.floor((inSeconds % (60 * 60)) % 60) + .toFixed(0) + .padStart(2, "0"); let output = ""; if (hours !== "00") output += `${hours}:`; output += `${minutes}:${seconds}`; + if (isNegative) { + output = "-" + output; + } + return output; } export function formatTimeLeft(inSeconds: number): string { const hours = Math.floor(inSeconds / (60 * 60)).toFixed(0); const minutes = Math.floor((inSeconds % (60 * 60)) / 60).toFixed(0); - const seconds = Math.floor(((inSeconds % (60 * 60)) % 60)).toFixed(0); + const seconds = Math.floor((inSeconds % (60 * 60)) % 60).toFixed(0); if (hours !== "0") { return _t("%(hours)sh %(minutes)sm %(seconds)ss left", { @@ -198,8 +190,8 @@ export function wantsDateSeparator(prevEventDate: Date, nextEventDate: Date): bo export function formatFullDateNoDay(date: Date) { return _t("%(date)s at %(time)s", { - date: date.toLocaleDateString().replace(/\//g, '-'), - time: date.toLocaleTimeString().replace(/:/g, '-'), + date: date.toLocaleDateString().replace(/\//g, "-"), + time: date.toLocaleTimeString().replace(/:/g, "-"), }); } @@ -214,13 +206,7 @@ export function formatFullDateNoDayISO(date: Date): string { } export function formatFullDateNoDayNoTime(date: Date) { - return ( - date.getFullYear() + - "/" + - pad(date.getMonth() + 1) + - "/" + - pad(date.getDate()) - ); + return date.getFullYear() + "/" + pad(date.getMonth() + 1) + "/" + pad(date.getDate()); } export function formatRelativeTime(date: Date, showTwelveHour = false): string { @@ -238,24 +224,48 @@ export function formatRelativeTime(date: Date, showTwelveHour = false): string { } } +const MINUTE_MS = 60000; +const HOUR_MS = MINUTE_MS * 60; +const DAY_MS = HOUR_MS * 24; + /** * Formats duration in ms to human readable string * Returns value in biggest possible unit (day, hour, min, second) * Rounds values up until unit threshold * ie. 23:13:57 -> 23h, 24:13:57 -> 1d, 44:56:56 -> 2d */ -const MINUTE_MS = 60000; -const HOUR_MS = MINUTE_MS * 60; -const DAY_MS = HOUR_MS * 24; export function formatDuration(durationMs: number): string { if (durationMs >= DAY_MS) { - return _t('%(value)sd', { value: Math.round(durationMs / DAY_MS) }); + return _t("%(value)sd", { value: Math.round(durationMs / DAY_MS) }); } if (durationMs >= HOUR_MS) { - return _t('%(value)sh', { value: Math.round(durationMs / HOUR_MS) }); + return _t("%(value)sh", { value: Math.round(durationMs / HOUR_MS) }); } if (durationMs >= MINUTE_MS) { - return _t('%(value)sm', { value: Math.round(durationMs / MINUTE_MS) }); + return _t("%(value)sm", { value: Math.round(durationMs / MINUTE_MS) }); + } + return _t("%(value)ss", { value: Math.round(durationMs / 1000) }); +} + +/** + * Formats duration in ms to human readable string + * Returns precise value down to the nearest second + * ie. 23:13:57 -> 23h 13m 57s, 44:56:56 -> 1d 20h 56m 56s + */ +export function formatPreciseDuration(durationMs: number): string { + const days = Math.floor(durationMs / DAY_MS); + const hours = Math.floor((durationMs % DAY_MS) / HOUR_MS); + const minutes = Math.floor((durationMs % HOUR_MS) / MINUTE_MS); + const seconds = Math.floor((durationMs % MINUTE_MS) / 1000); + + if (days > 0) { + return _t("%(days)sd %(hours)sh %(minutes)sm %(seconds)ss", { days, hours, minutes, seconds }); + } + if (hours > 0) { + return _t("%(hours)sh %(minutes)sm %(seconds)ss", { hours, minutes, seconds }); + } + if (minutes > 0) { + return _t("%(minutes)sm %(seconds)ss", { minutes, seconds }); } - return _t('%(value)ss', { value: Math.round(durationMs / 1000) }); + return _t("%(value)ss", { value: seconds }); } diff --git a/src/DecryptionFailureTracker.ts b/src/DecryptionFailureTracker.ts index b0d9b7ef580..9756eb30281 100644 --- a/src/DecryptionFailureTracker.ts +++ b/src/DecryptionFailureTracker.ts @@ -18,7 +18,7 @@ import { DecryptionError } from "matrix-js-sdk/src/crypto/algorithms"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Error as ErrorEvent } from "@matrix-org/analytics-events/types/typescript/Error"; -import { PosthogAnalytics } from './PosthogAnalytics'; +import { PosthogAnalytics } from "./PosthogAnalytics"; export class DecryptionFailure { public readonly ts: number; @@ -35,28 +35,31 @@ type TrackingFn = (count: number, trackedErrCode: ErrorCode, rawError: string) = export type ErrCodeMapFn = (errcode: string) => ErrorCode; export class DecryptionFailureTracker { - private static internalInstance = new DecryptionFailureTracker((total, errorCode, rawError) => { - for (let i = 0; i < total; i++) { - PosthogAnalytics.instance.trackEvent({ - eventName: "Error", - domain: "E2EE", - name: errorCode, - context: `mxc_crypto_error_type_${rawError}`, - }); - } - }, (errorCode) => { - // Map JS-SDK error codes to tracker codes for aggregation - switch (errorCode) { - case 'MEGOLM_UNKNOWN_INBOUND_SESSION_ID': - return 'OlmKeysNotSentError'; - case 'OLM_UNKNOWN_MESSAGE_INDEX': - return 'OlmIndexError'; - case undefined: - return 'OlmUnspecifiedError'; - default: - return 'UnknownError'; - } - }); + private static internalInstance = new DecryptionFailureTracker( + (total, errorCode, rawError) => { + for (let i = 0; i < total; i++) { + PosthogAnalytics.instance.trackEvent({ + eventName: "Error", + domain: "E2EE", + name: errorCode, + context: `mxc_crypto_error_type_${rawError}`, + }); + } + }, + (errorCode) => { + // Map JS-SDK error codes to tracker codes for aggregation + switch (errorCode) { + case "MEGOLM_UNKNOWN_INBOUND_SESSION_ID": + return "OlmKeysNotSentError"; + case "OLM_UNKNOWN_MESSAGE_INDEX": + return "OlmIndexError"; + case undefined: + return "OlmUnspecifiedError"; + default: + return "UnknownError"; + } + }, + ); // Map of event IDs to DecryptionFailure items. public failures: Map = new Map(); @@ -108,12 +111,12 @@ export class DecryptionFailureTracker { * trackedErrorCode. If not provided, the `.code` of errors will be used. */ private constructor(private readonly fn: TrackingFn, private readonly errorCodeMapFn: ErrCodeMapFn) { - if (!fn || typeof fn !== 'function') { - throw new Error('DecryptionFailureTracker requires tracking function'); + if (!fn || typeof fn !== "function") { + throw new Error("DecryptionFailureTracker requires tracking function"); } - if (typeof errorCodeMapFn !== 'function') { - throw new Error('DecryptionFailureTracker second constructor argument should be a function'); + if (typeof errorCodeMapFn !== "function") { + throw new Error("DecryptionFailureTracker second constructor argument should be a function"); } } @@ -145,7 +148,9 @@ export class DecryptionFailureTracker { public addVisibleEvent(e: MatrixEvent): void { const eventId = e.getId(); - if (this.trackedEvents.has(eventId)) { return; } + if (this.trackedEvents.has(eventId)) { + return; + } this.visibleEvents.add(eventId); if (this.failures.has(eventId) && !this.visibleFailures.has(eventId)) { @@ -156,7 +161,9 @@ export class DecryptionFailureTracker { public addDecryptionFailure(failure: DecryptionFailure): void { const eventId = failure.failedEventId; - if (this.trackedEvents.has(eventId)) { return; } + if (this.trackedEvents.has(eventId)) { + return; + } this.failures.set(eventId, failure); if (this.visibleEvents.has(eventId) && !this.visibleFailures.has(eventId)) { @@ -174,15 +181,12 @@ export class DecryptionFailureTracker { * Start checking for and tracking failures. */ public start(): void { - this.checkInterval = setInterval( + this.checkInterval = window.setInterval( () => this.checkFailures(Date.now()), DecryptionFailureTracker.CHECK_INTERVAL_MS, ); - this.trackInterval = setInterval( - () => this.trackFailures(), - DecryptionFailureTracker.TRACK_INTERVAL_MS, - ); + this.trackInterval = window.setInterval(() => this.trackFailures(), DecryptionFailureTracker.TRACK_INTERVAL_MS); } /** diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 1f49c3b34d6..3d33ff0fda8 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -20,7 +20,7 @@ import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { ClientEvent, EventType, RoomStateEvent } from "matrix-js-sdk/src/matrix"; import { SyncState } from "matrix-js-sdk/src/sync"; -import { MatrixClientPeg } from './MatrixClientPeg'; +import { MatrixClientPeg } from "./MatrixClientPeg"; import dis from "./dispatcher/dispatcher"; import { hideToast as hideBulkUnverifiedSessionsToast, @@ -36,17 +36,16 @@ import { showToast as showUnverifiedSessionsToast, } from "./toasts/UnverifiedSessionToast"; import { accessSecretStorage, isSecretStorageBeingAccessed } from "./SecurityManager"; -import { isSecureBackupRequired } from './utils/WellKnownUtils'; +import { isSecureBackupRequired } from "./utils/WellKnownUtils"; import { ActionPayload } from "./dispatcher/payloads"; import { Action } from "./dispatcher/actions"; import { isLoggedIn } from "./utils/login"; import SdkConfig from "./SdkConfig"; import PlatformPeg from "./PlatformPeg"; -import { - recordClientInformation, - removeClientInformation, -} from "./utils/device/clientInformation"; +import { recordClientInformation, removeClientInformation } from "./utils/device/clientInformation"; import SettingsStore, { CallbackFn } from "./settings/SettingsStore"; +import { UIFeature } from "./settings/UIFeature"; +import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder"; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; @@ -68,6 +67,7 @@ export default class DeviceListener { private displayingToastsForDeviceIds = new Set(); private running = false; private shouldRecordClientInformation = false; + private enableBulkUnverifiedSessionsReminder = true; private deviceClientInformationSettingWatcherRef: string | undefined; public static sharedInstance() { @@ -85,9 +85,11 @@ export default class DeviceListener { MatrixClientPeg.get().on(ClientEvent.AccountData, this.onAccountData); MatrixClientPeg.get().on(ClientEvent.Sync, this.onSync); MatrixClientPeg.get().on(RoomStateEvent.Events, this.onRoomStateEvents); - this.shouldRecordClientInformation = SettingsStore.getValue('deviceClientInformationOptIn'); + this.shouldRecordClientInformation = SettingsStore.getValue("deviceClientInformationOptIn"); + // only configurable in config, so we don't need to watch the value + this.enableBulkUnverifiedSessionsReminder = SettingsStore.getValue(UIFeature.BulkUnverifiedSessionsReminder); this.deviceClientInformationSettingWatcherRef = SettingsStore.watchSetting( - 'deviceClientInformationOptIn', + "deviceClientInformationOptIn", null, this.onRecordClientInformationSettingChange, ); @@ -133,7 +135,7 @@ export default class DeviceListener { * @param {String[]} deviceIds List of device IDs to dismiss notifications for */ public async dismissUnverifiedSessions(deviceIds: Iterable) { - logger.log("Dismissing unverified sessions: " + Array.from(deviceIds).join(',')); + logger.log("Dismissing unverified sessions: " + Array.from(deviceIds).join(",")); for (const d of deviceIds) { this.dismissed.add(d); } @@ -149,9 +151,7 @@ export default class DeviceListener { private ensureDeviceIdsAtStartPopulated() { if (this.ourDeviceIdsAtStart === null) { const cli = MatrixClientPeg.get(); - this.ourDeviceIdsAtStart = new Set( - cli.getStoredDevicesForUser(cli.getUserId()).map(d => d.deviceId), - ); + this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()).map((d) => d.deviceId)); } } @@ -194,16 +194,16 @@ export default class DeviceListener { // * completed secret storage creation // which result in account data changes affecting checks below. if ( - ev.getType().startsWith('m.secret_storage.') || - ev.getType().startsWith('m.cross_signing.') || - ev.getType() === 'm.megolm_backup.v1' + ev.getType().startsWith("m.secret_storage.") || + ev.getType().startsWith("m.cross_signing.") || + ev.getType() === "m.megolm_backup.v1" ) { this.recheck(); } }; private onSync = (state: SyncState, prevState?: SyncState) => { - if (state === 'PREPARED' && prevState === null) { + if (state === "PREPARED" && prevState === null) { this.recheck(); } }; @@ -225,7 +225,7 @@ export default class DeviceListener { // The server doesn't tell us when key backup is set up, so we poll // & cache the result private async getKeyBackupInfo() { - const now = (new Date()).getTime(); + const now = new Date().getTime(); if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) { this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); this.keyBackupFetchedAt = now; @@ -239,7 +239,7 @@ export default class DeviceListener { if (isSecretStorageBeingAccessed()) return false; // Show setup toasts once the user is in at least one encrypted room. const cli = MatrixClientPeg.get(); - return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId)); + return cli && cli.getRooms().some((r) => cli.isRoomEncrypted(r.roomId)); } private async recheck() { @@ -267,10 +267,7 @@ export default class DeviceListener { await cli.downloadKeys([cli.getUserId()]); // cross signing isn't enabled - nag to enable it // There are 3 different toasts for: - if ( - !cli.getCrossSigningId() && - cli.getStoredCrossSigningForUser(cli.getUserId()) - ) { + if (!cli.getCrossSigningId() && cli.getStoredCrossSigningForUser(cli.getUserId())) { // Cross-signing on account but this device doesn't trust the master key (verify this session) showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION); this.checkKeyBackupStatus(); @@ -306,6 +303,9 @@ export default class DeviceListener { // Unverified devices that have appeared since then const newUnverifiedDeviceIds = new Set(); + const isCurrentDeviceTrusted = + crossSigningReady && (await cli.checkDeviceTrust(cli.getUserId()!, cli.deviceId!).isCrossSigningVerified()); + // as long as cross-signing isn't ready, // you can't see or dismiss any device toasts if (crossSigningReady) { @@ -313,7 +313,7 @@ export default class DeviceListener { for (const device of devices) { if (device.deviceId === cli.deviceId) continue; - const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId); + const deviceTrust = await cli.checkDeviceTrust(cli.getUserId()!, device.deviceId!); if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) { if (this.ourDeviceIdsAtStart.has(device.deviceId)) { oldUnverifiedDeviceIds.add(device.deviceId); @@ -324,12 +324,20 @@ export default class DeviceListener { } } - logger.debug("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(',')); - logger.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(',')); - logger.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(',')); + logger.debug("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(",")); + logger.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(",")); + logger.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(",")); + + const isBulkUnverifiedSessionsReminderSnoozed = isBulkUnverifiedDeviceReminderSnoozed(); // Display or hide the batch toast for old unverified sessions - if (oldUnverifiedDeviceIds.size > 0) { + // don't show the toast if the current device is unverified + if ( + oldUnverifiedDeviceIds.size > 0 && + isCurrentDeviceTrusted && + this.enableBulkUnverifiedSessionsReminder && + !isBulkUnverifiedSessionsReminderSnoozed + ) { showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds); } else { hideBulkUnverifiedSessionsToast(); @@ -365,7 +373,11 @@ export default class DeviceListener { }; private onRecordClientInformationSettingChange: CallbackFn = ( - _originalSettingName, _roomId, _level, _newLevel, newValue, + _originalSettingName, + _roomId, + _level, + _newLevel, + newValue, ) => { const prevValue = this.shouldRecordClientInformation; @@ -379,18 +391,14 @@ export default class DeviceListener { private updateClientInformation = async () => { try { if (this.shouldRecordClientInformation) { - await recordClientInformation( - MatrixClientPeg.get(), - SdkConfig.get(), - PlatformPeg.get(), - ); + await recordClientInformation(MatrixClientPeg.get(), SdkConfig.get(), PlatformPeg.get()); } else { await removeClientInformation(MatrixClientPeg.get()); } } catch (error) { // this is a best effort operation // log the error without rethrowing - logger.error('Failed to update client information', error); + logger.error("Failed to update client information", error); } }; } diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index eeca2ec7b03..14157575787 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -17,29 +17,29 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from 'react'; -import sanitizeHtml from 'sanitize-html'; -import cheerio from 'cheerio'; -import classNames from 'classnames'; -import EMOJIBASE_REGEX from 'emojibase-regex'; -import { split } from 'lodash'; -import katex from 'katex'; -import { AllHtmlEntities } from 'html-entities'; -import { IContent } from 'matrix-js-sdk/src/models/event'; -import { Optional } from 'matrix-events-sdk'; +import React, { ReactNode } from "react"; +import sanitizeHtml from "sanitize-html"; +import cheerio from "cheerio"; +import classNames from "classnames"; +import EMOJIBASE_REGEX from "emojibase-regex"; +import { split } from "lodash"; +import katex from "katex"; +import { decode } from "html-entities"; +import { IContent } from "matrix-js-sdk/src/models/event"; +import { Optional } from "matrix-events-sdk"; import { _linkifyElement, _linkifyString, ELEMENT_URL_PATTERN, options as linkifyMatrixOptions, -} from './linkify-matrix'; -import { IExtendedSanitizeOptions } from './@types/sanitize-html'; -import SettingsStore from './settings/SettingsStore'; +} from "./linkify-matrix"; +import { IExtendedSanitizeOptions } from "./@types/sanitize-html"; +import SettingsStore from "./settings/SettingsStore"; import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks"; import { getEmojiFromUnicode } from "./emoji"; import { mediaFromMxc } from "./customisations/Media"; -import { stripHTMLReply, stripPlainReply } from './utils/Reply'; +import { stripHTMLReply, stripPlainReply } from "./utils/Reply"; // Anything outside the basic multilingual plane will be a surrogate pair const SURROGATE_PAIR_PATTERN = /([\ud800-\udbff])([\udc00-\udfff])/; @@ -55,7 +55,7 @@ const ZWJ_REGEX = /[\u200D\u2003]/g; // Regex pattern for whitespace characters const WHITESPACE_REGEX = /\s/g; -const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i'); +const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, "i"); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; @@ -107,7 +107,7 @@ function mightContainEmoji(str: string): boolean { */ export function unicodeToShortcode(char: string): string { const shortcodes = getEmojiFromUnicode(char)?.shortcodes; - return shortcodes?.length ? `:${shortcodes[0]}:` : ''; + return shortcodes?.length ? `:${shortcodes[0]}:` : ""; } /* @@ -126,7 +126,7 @@ export function getHtmlText(insaneHtml: string): string { allowedAttributes: {}, selfClosing: [], allowedSchemes: [], - disallowedTagsMode: 'discard', + disallowedTagsMode: "discard", }); } @@ -147,11 +147,12 @@ export function isUrlPermitted(inputUrl: string): boolean { } } -const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to matrix +const transformTags: IExtendedSanitizeOptions["transformTags"] = { + // custom to matrix // add blank targets to all hyperlinks except vector URLs - 'a': function(tagName: string, attribs: sanitizeHtml.Attributes) { + "a": function (tagName: string, attribs: sanitizeHtml.Attributes) { if (attribs.href) { - attribs.target = '_blank'; // by default + attribs.target = "_blank"; // by default const transformed = tryTransformPermalinkToLocalHref(attribs.href); // only used to check if it is a link that can be handled locally if ( @@ -165,10 +166,10 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to delete attribs.href; } - attribs.rel = 'noreferrer noopener'; // https://mathiasbynens.github.io/rel-noopener/ + attribs.rel = "noreferrer noopener"; // https://mathiasbynens.github.io/rel-noopener/ return { tagName, attribs }; }, - 'img': function(tagName: string, attribs: sanitizeHtml.Attributes) { + "img": function (tagName: string, attribs: sanitizeHtml.Attributes) { let src = attribs.src; // Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag // because transformTags is used _before_ we filter by allowedSchemesByTag and @@ -208,18 +209,18 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height); return { tagName, attribs }; }, - 'code': function(tagName: string, attribs: sanitizeHtml.Attributes) { - if (typeof attribs.class !== 'undefined') { + "code": function (tagName: string, attribs: sanitizeHtml.Attributes) { + if (typeof attribs.class !== "undefined") { // Filter out all classes other than ones starting with language- for syntax highlighting. - const classes = attribs.class.split(/\s/).filter(function(cl) { - return cl.startsWith('language-') && !cl.startsWith('language-_'); + const classes = attribs.class.split(/\s/).filter(function (cl) { + return cl.startsWith("language-") && !cl.startsWith("language-_"); }); - attribs.class = classes.join(' '); + attribs.class = classes.join(" "); } return { tagName, attribs }; }, // eslint-disable-next-line @typescript-eslint/naming-convention - '*': function(tagName: string, attribs: sanitizeHtml.Attributes) { + "*": function (tagName: string, attribs: sanitizeHtml.Attributes) { // Delete any style previously assigned, style is an allowedTag for font, span & img, // because attributes are stripped after transforming. // For img this is trusted as it is generated wholly within the img transformation method. @@ -230,8 +231,8 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to // Sanitise and transform data-mx-color and data-mx-bg-color to their CSS // equivalents const customCSSMapper = { - 'data-mx-color': 'color', - 'data-mx-bg-color': 'background-color', + "data-mx-color": "color", + "data-mx-bg-color": "background-color", // $customAttributeKey: $cssAttributeKey }; @@ -239,8 +240,9 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to Object.keys(customCSSMapper).forEach((customAttributeKey) => { const cssAttributeKey = customCSSMapper[customAttributeKey]; const customAttributeValue = attribs[customAttributeKey]; - if (customAttributeValue && - typeof customAttributeValue === 'string' && + if ( + customAttributeValue && + typeof customAttributeValue === "string" && COLOR_REGEX.test(customAttributeValue) ) { style += cssAttributeKey + ":" + customAttributeValue + ";"; @@ -258,28 +260,61 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to const sanitizeHtmlParams: IExtendedSanitizeOptions = { allowedTags: [ - 'font', // custom to matrix for IRC-style font coloring - 'del', // for markdown - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub', - 'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', - 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img', - 'details', 'summary', + "font", // custom to matrix for IRC-style font coloring + "del", // for markdown + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "blockquote", + "p", + "a", + "ul", + "ol", + "sup", + "sub", + "nl", + "li", + "b", + "i", + "u", + "strong", + "em", + "strike", + "code", + "hr", + "br", + "div", + "table", + "thead", + "caption", + "tbody", + "tr", + "th", + "td", + "pre", + "span", + "img", + "details", + "summary", ], allowedAttributes: { // attribute sanitization happens after transformations, so we have to accept `style` for font, span & img // but strip during the transformation. // custom ones first: - font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix - span: ['data-mx-maths', 'data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'style'], // custom to matrix - div: ['data-mx-maths'], - a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix + font: ["color", "data-mx-bg-color", "data-mx-color", "style"], // custom to matrix + span: ["data-mx-maths", "data-mx-bg-color", "data-mx-color", "data-mx-spoiler", "style"], // custom to matrix + div: ["data-mx-maths"], + a: ["href", "name", "target", "rel"], // remote target: custom to matrix // img tags also accept width/height, we just map those to max-width & max-height during transformation - img: ['src', 'alt', 'title', 'style', 'data-mx-emoticon'], - ol: ['start'], - code: ['class'], // We don't actually allow all classes, we filter them in transformTags + img: ["src", "alt", "title", "style", "data-mx-emoticon"], + ol: ["start"], + code: ["class"], // We don't actually allow all classes, we filter them in transformTags }, // Lots of these won't come up by default because we don't allow them - selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'], + selfClosing: ["img", "br", "hr", "area", "base", "basefont", "input", "link", "meta"], // URL schemes we permit allowedSchemes: PERMITTED_URL_SCHEMES, allowProtocolRelative: false, @@ -292,8 +327,8 @@ const sanitizeHtmlParams: IExtendedSanitizeOptions = { const composerSanitizeHtmlParams: IExtendedSanitizeOptions = { ...sanitizeHtmlParams, transformTags: { - 'code': transformTags['code'], - '*': transformTags['*'], + "code": transformTags["code"], + "*": transformTags["*"], }, }; @@ -301,17 +336,25 @@ const composerSanitizeHtmlParams: IExtendedSanitizeOptions = { const topicSanitizeHtmlParams: IExtendedSanitizeOptions = { ...sanitizeHtmlParams, allowedTags: [ - 'font', // custom to matrix for IRC-style font coloring - 'del', // for markdown - 'a', 'sup', 'sub', - 'b', 'i', 'u', 'strong', 'em', 'strike', 'br', 'div', - 'span', + "font", // custom to matrix for IRC-style font coloring + "del", // for markdown + "a", + "sup", + "sub", + "b", + "i", + "u", + "strong", + "em", + "strike", + "br", + "div", + "span", ], }; abstract class BaseHighlighter { - constructor(public highlightClass: string, public highlightLink: string) { - } + constructor(public highlightClass: string, public highlightLink: string) {} /** * apply the highlights to a section of text @@ -408,8 +451,11 @@ export interface IOptsReturnString extends IOpts { const emojiToHtmlSpan = (emoji: string) => `${emoji}`; -const emojiToJsxSpan = (emoji: string, key: number) => - { emoji }; +const emojiToJsxSpan = (emoji: string, key: number) => ( + + {emoji} + +); /** * Wraps emojis in to style them separately from the rest of message. Consecutive emojis (and modifiers) are wrapped @@ -422,15 +468,15 @@ const emojiToJsxSpan = (emoji: string, key: number) => function formatEmojis(message: string, isHtmlMessage: boolean): (JSX.Element | string)[] { const emojiToSpan = isHtmlMessage ? emojiToHtmlSpan : emojiToJsxSpan; const result: (JSX.Element | string)[] = []; - let text = ''; + let text = ""; let key = 0; // We use lodash's grapheme splitter to avoid breaking apart compound emojis - for (const char of split(message, '')) { + for (const char of split(message, "")) { if (EMOJIBASE_REGEX.test(char)) { if (text) { result.push(text); - text = ''; + text = ""; } result.push(emojiToSpan(char, key)); key++; @@ -481,8 +527,8 @@ export function bodyToHtml(content: IContent, highlights: Optional, op ?.filter((highlight: string): boolean => !highlight.includes("<")) .map((highlight: string): string => sanitizeHtml(highlight, sanitizeParams)); - let formattedBody = typeof content.formatted_body === 'string' ? content.formatted_body : null; - const plainBody = typeof content.body === 'string' ? content.body : ""; + let formattedBody = typeof content.formatted_body === "string" ? content.formatted_body : null; + const plainBody = typeof content.body === "string" ? content.body : ""; if (opts.stripReplyFallback && formattedBody) formattedBody = stripHTMLReply(formattedBody); strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody; @@ -499,8 +545,8 @@ export function bodyToHtml(content: IContent, highlights: Optional, op // are interrupted by HTML tags (not that we did before) - e.g. foobar won't get highlighted // by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either // XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure. - sanitizeParams.textFilter = function(safeText) { - return highlighter.applyHighlights(safeText, safeHighlights).join(''); + sanitizeParams.textFilter = function (safeText) { + return highlighter.applyHighlights(safeText, safeHighlights).join(""); }; } @@ -517,15 +563,13 @@ export function bodyToHtml(content: IContent, highlights: Optional, op if (isHtmlMessage && SettingsStore.getValue("feature_latex_maths")) { // @ts-ignore - The types for `replaceWith` wrongly expect // Cheerio instance to be returned. - phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) { - return katex.renderToString( - AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')), - { - throwOnError: false, - // @ts-ignore - `e` can be an Element, not just a Node - displayMode: e.name == 'div', - output: "htmlAndMathml", - }); + phtml('div, span[data-mx-maths!=""]').replaceWith(function (i, e) { + return katex.renderToString(decode(phtml(e).attr("data-mx-maths")), { + throwOnError: false, + // @ts-ignore - `e` can be an Element, not just a Node + displayMode: e.name == "div", + output: "htmlAndMathml", + }); }); safeBody = phtml.html(); } @@ -549,10 +593,10 @@ export function bodyToHtml(content: IContent, highlights: Optional, op }); } if (bodyHasEmoji) { - safeBody = formatEmojis(safeBody, true).join(''); + safeBody = formatEmojis(safeBody, true).join(""); } } else if (highlighter) { - safeBody = highlighter.applyHighlights(plainBody, safeHighlights).join(''); + safeBody = highlighter.applyHighlights(plainBody, safeHighlights).join(""); } } finally { delete sanitizeParams.textFilter; @@ -564,32 +608,35 @@ export function bodyToHtml(content: IContent, highlights: Optional, op } let emojiBody = false; - if (!opts.disableBigEmoji) { - let contentBodyTrimmed = (bodyHasEmoji && contentBody !== undefined) ? contentBody.trim() : ''; + if (!opts.disableBigEmoji && bodyHasEmoji) { + let contentBodyTrimmed = contentBody !== undefined ? contentBody.trim() : ""; // Ignore spaces in body text. Emojis with spaces in between should // still be counted as purely emoji messages. - contentBodyTrimmed = contentBodyTrimmed.replace(WHITESPACE_REGEX, ''); + contentBodyTrimmed = contentBodyTrimmed.replace(WHITESPACE_REGEX, ""); // Remove zero width joiner characters from emoji messages. This ensures // that emojis that are made up of multiple unicode characters are still // presented as large. - contentBodyTrimmed = contentBodyTrimmed.replace(ZWJ_REGEX, ''); + contentBodyTrimmed = contentBodyTrimmed.replace(ZWJ_REGEX, ""); const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed); - const matched = match && match[0] && match[0].length === contentBodyTrimmed.length; - emojiBody = (matched || isAllHtmlEmoji) && ( - strippedBody === safeBody || // replies have the html fallbacks, account for that here - content.formatted_body === undefined || - (!content.formatted_body.includes("http:") && - !content.formatted_body.includes("https:")) - ); + emojiBody = + match && + match[0] && + match[0].length === contentBodyTrimmed.length && + // Prevent user pills expanding for users with only emoji in + // their username. Permalinks (links in pills) can be any URL + // now, so we just check for an HTTP-looking thing. + (strippedBody === safeBody || // replies have the html fallbacks, account for that here + content.formatted_body === undefined || + (!content.formatted_body.includes("http:") && !content.formatted_body.includes("https:"))); } const className = classNames({ - 'mx_EventTile_body': true, - 'mx_EventTile_bigEmoji': emojiBody, - 'markdown-body': isHtmlMessage && !emojiBody, + "mx_EventTile_body": true, + "mx_EventTile_bigEmoji": emojiBody, + "markdown-body": isHtmlMessage && !emojiBody, }); let emojiBodyElements: JSX.Element[]; @@ -597,15 +644,18 @@ export function bodyToHtml(content: IContent, highlights: Optional, op emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[]; } - return safeBody ? + return safeBody ? ( : - { emojiBodyElements || strippedBody } - ; + /> + ) : ( + + {emojiBodyElements || strippedBody} + + ); } /** @@ -636,7 +686,7 @@ export function topicToHtml( if (isFormattedTopic) { safeTopic = sanitizeHtml(htmlTopic, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams); if (topicHasEmoji) { - safeTopic = formatEmojis(safeTopic, true).join(''); + safeTopic = formatEmojis(safeTopic, true).join(""); } } } catch { @@ -648,15 +698,13 @@ export function topicToHtml( emojiBodyElements = formatEmojis(topic, false); } - return isFormattedTopic - ? - : - { emojiBodyElements || topic } - ; + return isFormattedTopic ? ( + + ) : ( + + {emojiBodyElements || topic} + + ); } /** diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts index a7a12e1b1f2..fffa3fbb9ff 100644 --- a/src/IConfigOptions.ts +++ b/src/IConfigOptions.ts @@ -57,7 +57,7 @@ export interface IConfigOptions { branding?: { welcome_background_url?: string | string[]; // chosen at random if array auth_header_logo_url?: string; - auth_footer_links?: {text: string, url: string}[]; + auth_footer_links?: { text: string; url: string }[]; }; map_style_url?: string; // for location-shared maps @@ -163,7 +163,7 @@ export interface IConfigOptions { enable_presence_by_hs_url?: Record; // - terms_and_conditions_links?: { url: string, text: string }[]; + terms_and_conditions_links?: { url: string; text: string }[]; latex_maths_delims?: { inline?: { diff --git a/src/IdentityAuthClient.tsx b/src/IdentityAuthClient.tsx index 45f924af078..8d9040be495 100644 --- a/src/IdentityAuthClient.tsx +++ b/src/IdentityAuthClient.tsx @@ -15,19 +15,19 @@ limitations under the License. */ import React from "react"; -import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types'; -import { createClient, MatrixClient } from 'matrix-js-sdk/src/matrix'; +import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; +import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; -import { MatrixClientPeg } from './MatrixClientPeg'; -import Modal from './Modal'; -import { _t } from './languageHandler'; -import { Service, startTermsFlow, TermsNotSignedError } from './Terms'; +import { MatrixClientPeg } from "./MatrixClientPeg"; +import Modal from "./Modal"; +import { _t } from "./languageHandler"; +import { Service, startTermsFlow, TermsNotSignedError } from "./Terms"; import { doesAccountDataHaveIdentityServer, doesIdentityServerHaveTerms, setToDefaultIdentityServer, -} from './utils/IdentityServerUtils'; +} from "./utils/IdentityServerUtils"; import QuestionDialog from "./components/views/dialogs/QuestionDialog"; import { abbreviateUrl } from "./utils/UrlUtils"; @@ -101,10 +101,7 @@ export default class IdentityAuthClient { try { await this.checkToken(token); } catch (e) { - if ( - e instanceof TermsNotSignedError || - e instanceof AbortedIdentityActionError - ) { + if (e instanceof TermsNotSignedError || e instanceof AbortedIdentityActionError) { // Retrying won't help this throw e; } @@ -128,11 +125,7 @@ export default class IdentityAuthClient { } catch (e) { if (e.errcode === "M_TERMS_NOT_SIGNED") { logger.log("Identity server requires new terms to be agreed to"); - await startTermsFlow([new Service( - SERVICE_TYPES.IS, - identityServerUrl, - token, - )]); + await startTermsFlow([new Service(SERVICE_TYPES.IS, identityServerUrl, token)]); return; } throw e; @@ -147,17 +140,18 @@ export default class IdentityAuthClient { title: _t("Identity server has no terms of service"), description: (
-

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

-

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

+

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

+

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

), button: _t("Trust"), @@ -166,9 +160,7 @@ export default class IdentityAuthClient { if (confirmed) { setToDefaultIdentityServer(); } else { - throw new AbortedIdentityActionError( - "User aborted identity server action without terms", - ); + throw new AbortedIdentityActionError("User aborted identity server action without terms"); } } @@ -182,8 +174,7 @@ export default class IdentityAuthClient { public async registerForToken(check = true): Promise { const hsOpenIdToken = await MatrixClientPeg.get().getOpenIdToken(); // XXX: The spec is `token`, but we used `access_token` for a Sydent release. - const { access_token: accessToken, token } = - await this.matrixClient.registerWithIdentityServer(hsOpenIdToken); + const { access_token: accessToken, token } = await this.matrixClient.registerWithIdentityServer(hsOpenIdToken); const identityAccessToken = token ? token : accessToken; if (check) await this.checkToken(identityAccessToken); return identityAccessToken; diff --git a/src/ImageUtils.ts b/src/ImageUtils.ts index acf8daa607a..1618aa8f4c2 100644 --- a/src/ImageUtils.ts +++ b/src/ImageUtils.ts @@ -48,4 +48,3 @@ export function thumbHeight(fullWidth: number, fullHeight: number, thumbWidth: n return Math.floor(heightMulti * fullHeight); } } - diff --git a/src/KeyBindingsDefaults.ts b/src/KeyBindingsDefaults.ts index 8ce30252f92..271aa04c50b 100644 --- a/src/KeyBindingsDefaults.ts +++ b/src/KeyBindingsDefaults.ts @@ -19,11 +19,7 @@ import { IS_MAC, Key } from "./Keyboard"; import SettingsStore from "./settings/SettingsStore"; import SdkConfig from "./SdkConfig"; import { IKeyBindingsProvider, KeyBinding } from "./KeyBindingsManager"; -import { - CATEGORIES, - CategoryName, - KeyBindingAction, -} from "./accessibility/KeyboardShortcuts"; +import { CATEGORIES, CategoryName, KeyBindingAction } from "./accessibility/KeyboardShortcuts"; import { getKeyboardShortcuts } from "./accessibility/KeyboardShortcutUtils"; export const getBindingsByCategory = (category: CategoryName): KeyBinding[] => { @@ -39,7 +35,7 @@ export const getBindingsByCategory = (category: CategoryName): KeyBinding[] => { const messageComposerBindings = (): KeyBinding[] => { const bindings = getBindingsByCategory(CategoryName.COMPOSER); - if (SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend')) { + if (SettingsStore.getValue("MessageComposerInput.ctrlEnterToSend")) { bindings.push({ action: KeyBindingAction.SendMessage, keyCombo: { @@ -128,7 +124,7 @@ const roomListBindings = (): KeyBinding[] => { const roomBindings = (): KeyBinding[] => { const bindings = getBindingsByCategory(CategoryName.ROOM); - if (SettingsStore.getValue('ctrlFForSearch')) { + if (SettingsStore.getValue("ctrlFForSearch")) { bindings.push({ action: KeyBindingAction.SearchInRoom, keyCombo: { diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts index aee403e31d1..ad02f5413c3 100644 --- a/src/KeyBindingsManager.ts +++ b/src/KeyBindingsManager.ts @@ -16,8 +16,8 @@ limitations under the License. */ import { KeyBindingAction } from "./accessibility/KeyboardShortcuts"; -import { defaultBindingsProvider } from './KeyBindingsDefaults'; -import { IS_MAC } from './Keyboard'; +import { defaultBindingsProvider } from "./KeyBindingsDefaults"; +import { IS_MAC } from "./Keyboard"; /** * Represent a key combination. @@ -72,27 +72,18 @@ export function isKeyComboMatch(ev: KeyboardEvent | React.KeyboardEvent, combo: // When ctrlOrCmd is set, the keys need do evaluated differently on PC and Mac if (combo.ctrlOrCmdKey) { if (onMac) { - if (!evMeta - || evCtrl !== comboCtrl - || evAlt !== comboAlt - || evShift !== comboShift) { + if (!evMeta || evCtrl !== comboCtrl || evAlt !== comboAlt || evShift !== comboShift) { return false; } } else { - if (!evCtrl - || evMeta !== comboMeta - || evAlt !== comboAlt - || evShift !== comboShift) { + if (!evCtrl || evMeta !== comboMeta || evAlt !== comboAlt || evShift !== comboShift) { return false; } } return true; } - if (evMeta !== comboMeta - || evCtrl !== comboCtrl - || evAlt !== comboAlt - || evShift !== comboShift) { + if (evMeta !== comboMeta || evCtrl !== comboCtrl || evAlt !== comboAlt || evShift !== comboShift) { return false; } @@ -114,9 +105,7 @@ export class KeyBindingsManager { * To overwrite the default key bindings add a new providers before the default provider, e.g. a provider for * customized key bindings. */ - bindingsProviders: IKeyBindingsProvider[] = [ - defaultBindingsProvider, - ]; + bindingsProviders: IKeyBindingsProvider[] = [defaultBindingsProvider]; /** * Finds a matching KeyAction for a given KeyboardEvent @@ -127,7 +116,7 @@ export class KeyBindingsManager { ): KeyBindingAction | undefined { for (const getter of getters) { const bindings = getter(); - const binding = bindings.find(it => isKeyComboMatch(ev, it.keyCombo, IS_MAC)); + const binding = bindings.find((it) => isKeyComboMatch(ev, it.keyCombo, IS_MAC)); if (binding) { return binding.action; } @@ -136,35 +125,59 @@ export class KeyBindingsManager { } getMessageComposerAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { - return this.getAction(this.bindingsProviders.map(it => it.getMessageComposerBindings), ev); + return this.getAction( + this.bindingsProviders.map((it) => it.getMessageComposerBindings), + ev, + ); } getAutocompleteAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { - return this.getAction(this.bindingsProviders.map(it => it.getAutocompleteBindings), ev); + return this.getAction( + this.bindingsProviders.map((it) => it.getAutocompleteBindings), + ev, + ); } getRoomListAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { - return this.getAction(this.bindingsProviders.map(it => it.getRoomListBindings), ev); + return this.getAction( + this.bindingsProviders.map((it) => it.getRoomListBindings), + ev, + ); } getRoomAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { - return this.getAction(this.bindingsProviders.map(it => it.getRoomBindings), ev); + return this.getAction( + this.bindingsProviders.map((it) => it.getRoomBindings), + ev, + ); } getNavigationAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { - return this.getAction(this.bindingsProviders.map(it => it.getNavigationBindings), ev); + return this.getAction( + this.bindingsProviders.map((it) => it.getNavigationBindings), + ev, + ); } getAccessibilityAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { - return this.getAction(this.bindingsProviders.map(it => it.getAccessibilityBindings), ev); + return this.getAction( + this.bindingsProviders.map((it) => it.getAccessibilityBindings), + ev, + ); } getCallAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { - return this.getAction(this.bindingsProviders.map(it => it.getCallBindings), ev); + return this.getAction( + this.bindingsProviders.map((it) => it.getCallBindings), + ev, + ); } getLabsAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { - return this.getAction(this.bindingsProviders.map(it => it.getLabsBindings), ev); + return this.getAction( + this.bindingsProviders.map((it) => it.getLabsBindings), + ev, + ); } } diff --git a/src/Keyboard.ts b/src/Keyboard.ts index 7d41f9c77d1..3a425cef31d 100644 --- a/src/Keyboard.ts +++ b/src/Keyboard.ts @@ -74,7 +74,7 @@ export const Key = { Z: "z", }; -export const IS_MAC = navigator.platform.toUpperCase().includes('MAC'); +export const IS_MAC = navigator.platform.toUpperCase().includes("MAC"); export function isOnlyCtrlOrCmdKeyEvent(ev) { if (IS_MAC) { diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index 41098dcb4db..847cd38aefc 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React from "react"; import { CallError, CallErrorCode, @@ -27,19 +27,19 @@ import { CallType, MatrixCall, } from "matrix-js-sdk/src/webrtc/call"; -import { logger } from 'matrix-js-sdk/src/logger'; -import EventEmitter from 'events'; +import { logger } from "matrix-js-sdk/src/logger"; +import EventEmitter from "events"; import { RuleId, TweakName, Tweaks } from "matrix-js-sdk/src/@types/PushRules"; -import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor'; +import { PushProcessor } from "matrix-js-sdk/src/pushprocessor"; import { SyncState } from "matrix-js-sdk/src/sync"; import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler"; -import { MatrixClientPeg } from './MatrixClientPeg'; -import Modal from './Modal'; -import { _t } from './languageHandler'; -import dis from './dispatcher/dispatcher'; -import WidgetUtils from './utils/WidgetUtils'; -import SettingsStore from './settings/SettingsStore'; +import { MatrixClientPeg } from "./MatrixClientPeg"; +import Modal from "./Modal"; +import { _t } from "./languageHandler"; +import dis from "./dispatcher/dispatcher"; +import WidgetUtils from "./utils/WidgetUtils"; +import SettingsStore from "./settings/SettingsStore"; import { WidgetType } from "./widgets/WidgetType"; import { SettingLevel } from "./settings/SettingLevel"; import QuestionDialog from "./components/views/dialogs/QuestionDialog"; @@ -48,36 +48,72 @@ import WidgetStore from "./stores/WidgetStore"; import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; import { UIFeature } from "./settings/UIFeature"; -import { Action } from './dispatcher/actions'; -import VoipUserMapper from './VoipUserMapper'; -import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid'; -import SdkConfig from './SdkConfig'; -import { ensureDMExists } from './createRoom'; -import { Container, WidgetLayoutStore } from './stores/widgets/WidgetLayoutStore'; -import IncomingLegacyCallToast, { getIncomingLegacyCallToastKey } from './toasts/IncomingLegacyCallToast'; -import ToastStore from './stores/ToastStore'; -import Resend from './Resend'; +import { Action } from "./dispatcher/actions"; +import VoipUserMapper from "./VoipUserMapper"; +import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from "./widgets/ManagedHybrid"; +import SdkConfig from "./SdkConfig"; +import { ensureDMExists } from "./createRoom"; +import { Container, WidgetLayoutStore } from "./stores/widgets/WidgetLayoutStore"; +import IncomingLegacyCallToast, { getIncomingLegacyCallToastKey } from "./toasts/IncomingLegacyCallToast"; +import ToastStore from "./stores/ToastStore"; +import Resend from "./Resend"; import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload"; import { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialogTypes"; import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload"; -import { findDMForUser } from './utils/dm/findDMForUser'; -import { getJoinedNonFunctionalMembers } from './utils/room/getJoinedNonFunctionalMembers'; -import { localNotificationsAreSilenced } from './utils/notifications'; +import { findDMForUser } from "./utils/dm/findDMForUser"; +import { getJoinedNonFunctionalMembers } from "./utils/room/getJoinedNonFunctionalMembers"; +import { localNotificationsAreSilenced } from "./utils/notifications"; -export const PROTOCOL_PSTN = 'm.protocol.pstn'; -export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; -export const PROTOCOL_SIP_NATIVE = 'im.vector.protocol.sip_native'; -export const PROTOCOL_SIP_VIRTUAL = 'im.vector.protocol.sip_virtual'; +export const PROTOCOL_PSTN = "m.protocol.pstn"; +export const PROTOCOL_PSTN_PREFIXED = "im.vector.protocol.pstn"; +export const PROTOCOL_SIP_NATIVE = "im.vector.protocol.sip_native"; +export const PROTOCOL_SIP_VIRTUAL = "im.vector.protocol.sip_virtual"; const CHECK_PROTOCOLS_ATTEMPTS = 3; -enum AudioID { - Ring = 'ringAudio', - Ringback = 'ringbackAudio', - CallEnd = 'callendAudio', - Busy = 'busyAudio', +type MediaEventType = keyof HTMLMediaElementEventMap; +const MEDIA_ERROR_EVENT_TYPES: MediaEventType[] = [ + "error", + // The media has become empty; for example, this event is sent if the media has + // already been loaded (or partially loaded), and the HTMLMediaElement.load method + // is called to reload it. + "emptied", + // The user agent is trying to fetch media data, but data is unexpectedly not + // forthcoming. + "stalled", + // Media data loading has been suspended. + "suspend", + // Playback has stopped because of a temporary lack of data + "waiting", +]; +const MEDIA_DEBUG_EVENT_TYPES: MediaEventType[] = [ + "play", + "pause", + "playing", + "ended", + "loadeddata", + "loadedmetadata", + "canplay", + "canplaythrough", + "volumechange", +]; + +const MEDIA_EVENT_TYPES = [...MEDIA_ERROR_EVENT_TYPES, ...MEDIA_DEBUG_EVENT_TYPES]; + +export enum AudioID { + Ring = "ringAudio", + Ringback = "ringbackAudio", + CallEnd = "callendAudio", + Busy = "busyAudio", } +/* istanbul ignore next */ +const debuglog = (...args: any[]): void => { + if (SettingsStore.getValue("debug_legacy_call_handler")) { + logger.log.call(console, "LegacyCallHandler debuglog:", ...args); + } +}; + interface ThirdpartyLookupResponseFields { /* eslint-disable camelcase */ @@ -119,6 +155,7 @@ export default class LegacyCallHandler extends EventEmitter { // call with a different party to this one. private transferees = new Map(); // callId (target) -> call (transferee) private audioPromises = new Map>(); + private audioElementsWithListeners = new Map(); private supportsPstnProtocol = null; private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native @@ -163,12 +200,12 @@ export default class LegacyCallHandler extends EventEmitter { // end up causing the audio elements with our ring/ringback etc // audio clips in to play. if (navigator.mediaSession) { - navigator.mediaSession.setActionHandler('play', function() {}); - navigator.mediaSession.setActionHandler('pause', function() {}); - navigator.mediaSession.setActionHandler('seekbackward', function() {}); - navigator.mediaSession.setActionHandler('seekforward', function() {}); - navigator.mediaSession.setActionHandler('previoustrack', function() {}); - navigator.mediaSession.setActionHandler('nexttrack', function() {}); + navigator.mediaSession.setActionHandler("play", function () {}); + navigator.mediaSession.setActionHandler("pause", function () {}); + navigator.mediaSession.setActionHandler("seekbackward", function () {}); + navigator.mediaSession.setActionHandler("seekforward", function () {}); + navigator.mediaSession.setActionHandler("previoustrack", function () {}); + navigator.mediaSession.setActionHandler("nexttrack", function () {}); } if (SettingsStore.getValue(UIFeature.Voip)) { @@ -176,6 +213,16 @@ export default class LegacyCallHandler extends EventEmitter { } this.checkProtocols(CHECK_PROTOCOLS_ATTEMPTS); + + // Add event listeners for the