diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml new file mode 100644 index 00000000000..c0f6a4ba762 --- /dev/null +++ b/.github/workflows/cypress.yaml @@ -0,0 +1,148 @@ +# Triggers after the layered build has finished, taking the artifact and running cypress on it +name: Cypress End to End Tests +on: + workflow_run: + workflows: [ "Element Web - Build" ] + types: + - completed +jobs: + # This job cannot have a pretty name due to https://github.com/haya14busa/action-workflow_run-status/issues/158 + cypress: + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + permissions: + actions: read + statuses: write + issues: read + pull-requests: read + environment: Cypress + steps: + # Wire up the status check for this workflow_run action + - uses: haya14busa/action-workflow_run-status@967ed83efa565c257675ed70cfe5231f062ddd94 # v1.0.0 + + - id: prdetails + if: github.event.workflow_run.event == 'pull_request' + uses: matrix-org/pr-details-action@v1.1 + with: + owner: ${{ github.event.workflow_run.head_repository.owner.login }} + branch: ${{ github.event.workflow_run.head_branch }} + + - uses: actions/checkout@v2 + with: + # XXX: We're checking out untrusted code in a secure context + # We need to be careful to not trust anything this code outputs/may do + # We need to check this out to access the cypress tests which are on the head branch + repository: ${{ github.event.workflow_run.head_repository.full_name }} + ref: ${{ github.event.workflow_run.head_sha }} + persist-credentials: false + + # There's a 'download artifact' action, but it hasn't been updated for the workflow_run action + # (https://github.com/actions/download-artifact/issues/60) so instead we get this mess: + - name: 📥 Download artifact + uses: dawidd6/action-download-artifact@v2 + with: + workflow: element-build-and-test.yaml + run_id: ${{ github.event.workflow_run.id }} + name: previewbuild + path: webapp + + - name: Get commit details + if: github.event.workflow_run.event == 'pull_request' + uses: actions/github-script@v5 + with: + script: | + const response = await github.rest.git.getCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: "${{ github.event.workflow_run.head_sha }}", + }); + core.exportVariable("COMMIT_INFO_MESSAGE", response.data.message); + core.exportVariable("COMMIT_INFO_AUTHOR", response.data.author.name); + core.exportVariable("COMMIT_INFO_EMAIL", response.data.author.email); + + # Only run Percy when it is demanded or on develop + - name: Disable Percy if not needed + if: | + github.event.workflow_run.event == 'pull_request' && + !contains(fromJSON(steps.prdetails.outputs.data).labels.*.name, 'X-Needs-Percy') + run: | + echo "PERCY_ENABLE=0" >> $GITHUB_ENV + + - name: Run Cypress tests + uses: cypress-io/github-action@v2 + with: + # The built-in Electron runner seems to grind to a halt trying + # to run the tests, so use chrome. + browser: chrome + start: npx serve -p 8080 webapp + wait-on: 'http://localhost:8080' + record: true + command-prefix: 'yarn percy exec --' + env: + # pass the Dashboard record key as an environment variable + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + # tell Cypress more details about the context of this run + COMMIT_INFO_BRANCH: ${{ github.event.workflow_run.head_branch }} + COMMIT_INFO_SHA: ${{ github.event.workflow_run.head_sha }} + COMMIT_INFO_REMOTE: ${{ github.repositoryUrl }} + + # pass the Percy token as an environment variable + PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} + # Use existing chromium rather than downloading another + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + PERCY_BROWSER_EXECUTABLE: /usr/bin/chromium-browser + # tell Percy more details about the context of this run + PERCY_BRANCH: ${{ github.event.workflow_run.head_branch }} + PERCY_COMMIT: ${{ github.event.workflow_run.head_sha }} + PERCY_PULL_REQUEST: ${{ steps.prdetails.outputs.pr_id }} + # pass GitHub token to allow accurately detecting a build vs a re-run build + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # make Node's os.tmpdir() return something where we actually have permissions + TMPDIR: ${{ runner.temp }} + + - name: Upload Artifact + if: failure() + uses: actions/upload-artifact@v2 + with: + name: cypress-results + path: | + cypress/screenshots + cypress/videos + cypress/synapselogs + + - name: Upload Benchmark + uses: actions/upload-artifact@v2 + with: + name: cypress-benchmark + path: cypress/performance/measurements.json + retention-days: 1 + + store-benchmark: + needs: cypress + runs-on: ubuntu-latest + if: | + github.event.workflow_run.event != 'pull_request' && + github.event.workflow_run.head_branch == 'develop' && + github.event.workflow_run.head_repository.full_name == github.repository + permissions: + contents: write + steps: + - uses: actions/checkout@v2 + + - name: Download benchmark result + uses: actions/download-artifact@v3 + with: + name: cypress-benchmark + + - name: Store benchmark result + uses: matrix-org/github-action-benchmark@jsperfentry-6 + with: + name: Cypress measurements + tool: 'jsperformanceentry' + output-file-path: measurements.json + # The dashboard is available at https://matrix-org.github.io/matrix-react-sdk/cypress/bench/ + benchmark-data-dir-path: cypress/bench + fail-on-alert: false + comment-on-alert: false + github-token: ${{ secrets.GITHUB_TOKEN }} + auto-push: ${{ github.event.workflow_run.event != 'pull_request' }} diff --git a/.github/workflows/element-build-and-test.yaml b/.github/workflows/element-build-and-test.yaml deleted file mode 100644 index 9ed06bd8ad9..00000000000 --- a/.github/workflows/element-build-and-test.yaml +++ /dev/null @@ -1,125 +0,0 @@ -# Produce a build of element-web with this version of react-sdk -# and any matching branches of element-web and js-sdk, output it -# as an artifact and run integration tests. -name: Element Web - Build and Test -on: - 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 }} -jobs: - build: - name: "Build Element-Web" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - uses: actions/setup-node@v3 - with: - cache: 'yarn' - - - name: Fetch layered build - id: layered_build - run: | - scripts/ci/layered.sh - JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD) - REACT_SHA=$(git rev-parse --short=12 HEAD) - VECTOR_SHA=$(git -C element-web rev-parse --short=12 HEAD) - echo "::set-output name=VERSION::$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA" - - - name: Copy config - run: cp element.io/develop/config.json config.json - working-directory: ./element-web - - - name: Build - env: - CI_PACKAGE: true - VERSION: "${{ steps.layered_build.outputs.VERSION }}" - run: yarn build - working-directory: ./element-web - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: previewbuild - path: element-web/webapp - # We'll only use this in a triggered job, then we're done with it - retention-days: 1 - - cypress: - name: "Cypress End to End Tests" - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Download build - uses: actions/download-artifact@v3 - with: - name: previewbuild - path: webapp - - - name: Run Cypress tests - uses: cypress-io/github-action@v2 - with: - # The built-in Electron runner seems to grind to a halt trying - # to run the tests, so use chrome. - browser: chrome - start: npx serve -p 8080 webapp - wait-on: 'http://localhost:8080' - record: true - command-prefix: 'yarn percy exec --' - env: - # pass the Dashboard record key as an environment variable - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - # pass the Percy token as an environment variable - PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} - # Use existing chromium rather than downloading another - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - PERCY_BROWSER_EXECUTABLE: /usr/bin/chromium-browser - # pass GitHub token to allow accurately detecting a build vs a re-run build - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # make Node's os.tmpdir() return something where we actually have permissions - TMPDIR: ${{ runner.temp }} - - - name: Upload Artifact - if: failure() - uses: actions/upload-artifact@v2 - with: - name: cypress-results - path: | - cypress/screenshots - cypress/videos - cypress/synapselogs - - - name: Store benchmark result - if: github.ref == 'refs/heads/develop' - uses: matrix-org/github-action-benchmark@jsperfentry-1 - with: - name: Cypress measurements - tool: 'jsperformanceentry' - output-file-path: cypress/performance/measurements.json - # The dashboard is available at https://matrix-org.github.io/matrix-react-sdk/cypress/bench/ - benchmark-data-dir-path: cypress/bench - fail-on-alert: false - comment-on-alert: false - github-token: ${{ secrets.DEPLOY_GH_PAGES }} - auto-push: ${{ github.ref == 'refs/heads/develop' }} - - app-tests: - name: Element Web Integration Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - uses: actions/setup-node@v3 - with: - cache: 'yarn' - - - name: Run tests - run: "./scripts/ci/app-tests.sh" diff --git a/.github/workflows/element-web.yaml b/.github/workflows/element-web.yaml new file mode 100644 index 00000000000..315c8bdcf2d --- /dev/null +++ b/.github/workflows/element-web.yaml @@ -0,0 +1,52 @@ +# Produce a build of element-web with this version of react-sdk +# and any matching branches of element-web and js-sdk, output it +# as an artifact and run integration tests. +name: Element Web - Build +on: + 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 }} +jobs: + build: + name: "Build Element-Web" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-node@v3 + with: + cache: 'yarn' + + - name: Fetch layered build + id: layered_build + run: | + scripts/ci/layered.sh + JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD) + REACT_SHA=$(git rev-parse --short=12 HEAD) + VECTOR_SHA=$(git -C element-web rev-parse --short=12 HEAD) + echo "::set-output name=VERSION::$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA" + + - name: Copy config + run: cp element.io/develop/config.json config.json + working-directory: ./element-web + + - name: Build + env: + CI_PACKAGE: true + VERSION: "${{ steps.layered_build.outputs.VERSION }}" + run: yarn build + working-directory: ./element-web + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: previewbuild + path: element-web/webapp + # We'll only use this in a triggered job, then we're done with it + retention-days: 1 diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index d861ea054e6..2c8613d61ef 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -4,23 +4,9 @@ on: types: [ opened, edited, labeled, unlabeled, synchronize ] concurrency: ${{ github.workflow }}-${{ github.event.pull_request.head.ref }} jobs: - changelog: - name: Preview Changelog - if: github.event.action != 'synchronize' - runs-on: ubuntu-latest - steps: - - uses: matrix-org/allchange@main - with: - ghToken: ${{ secrets.GITHUB_TOKEN }} - - enforce-label: - name: Enforce Labels - runs-on: ubuntu-latest - permissions: - pull-requests: read - steps: - - uses: yogevbd/enforce-label-action@2.1.0 - with: - REQUIRED_LABELS_ANY: "T-Defect,T-Enhancement,T-Task" - BANNED_LABELS: "X-Blocked" - BANNED_LABELS_DESCRIPTION: "Preventing merge whilst PR is marked blocked!" + action: + uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop + with: + labels: "T-Defect,T-Enhancement,T-Task" + secrets: + ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 11660e68ba4..a5360c64fbb 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -8,30 +8,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }} cancel-in-progress: true jobs: - prdetails: - name: ℹ️ PR Details - if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' - uses: matrix-org/matrix-js-sdk/.github/workflows/pr_details.yml@develop - with: - owner: ${{ github.event.workflow_run.head_repository.owner.login }} - branch: ${{ github.event.workflow_run.head_branch }} - sonarqube: name: 🩻 SonarQube - needs: prdetails - # Only wait for prdetails if it isn't skipped - if: | - always() && - (needs.prdetails.result == 'success' || needs.prdetails.result == 'skipped') && - github.event.workflow_run.conclusion == 'success' uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop - with: - repo: ${{ github.event.workflow_run.head_repository.full_name }} - pr_id: ${{ needs.prdetails.outputs.pr_id }} - head_branch: ${{ needs.prdetails.outputs.head_branch || github.event.workflow_run.head_branch }} - base_branch: ${{ needs.prdetails.outputs.base_branch }} - revision: ${{ github.event.workflow_run.head_sha }} - coverage_workflow_name: tests.yml - coverage_run_id: ${{ github.event.workflow_run.id }} secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9fa7a6f7cf1..f2b97bd1897 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,3 +35,16 @@ jobs: path: | coverage !coverage/lcov-report + + app-tests: + name: Element Web Integration Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-node@v3 + with: + cache: 'yarn' + + - name: Run tests + run: "./scripts/ci/app-tests.sh" diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2b3ec2979..e7f94db4214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,108 @@ +Changes in [3.48.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.48.0) (2022-07-05) +===================================================================================================== + +## 🚨 BREAKING CHANGES + * Remove Piwik support ([\#8835](https://github.com/matrix-org/matrix-react-sdk/pull/8835)). + +## ✨ Features + * Move New Search Experience out of beta ([\#8859](https://github.com/matrix-org/matrix-react-sdk/pull/8859)). Contributed by @justjanne. + * Switch video rooms to spotlight layout when in PiP mode ([\#8912](https://github.com/matrix-org/matrix-react-sdk/pull/8912)). Fixes vector-im/element-web#22574. + * Live location sharing - render message deleted tile for redacted beacons ([\#8905](https://github.com/matrix-org/matrix-react-sdk/pull/8905)). Contributed by @kerryarchibald. + * Improve view source dialog style ([\#8883](https://github.com/matrix-org/matrix-react-sdk/pull/8883)). Fixes vector-im/element-web#22636. Contributed by @luixxiul. + * Improve integration manager dialog style ([\#8888](https://github.com/matrix-org/matrix-react-sdk/pull/8888)). Fixes vector-im/element-web#22642. Contributed by @luixxiul. + * Implement MSC3827: Filtering of `/publicRooms` by room type ([\#8866](https://github.com/matrix-org/matrix-react-sdk/pull/8866)). Fixes vector-im/element-web#22578. + * Show chat panel when opening a video room with unread messages ([\#8812](https://github.com/matrix-org/matrix-react-sdk/pull/8812)). Fixes vector-im/element-web#22527. + * Live location share - forward latest location ([\#8860](https://github.com/matrix-org/matrix-react-sdk/pull/8860)). Contributed by @kerryarchibald. + * Allow integration managers to validate user identity after opening ([\#8782](https://github.com/matrix-org/matrix-react-sdk/pull/8782)). Contributed by @Half-Shot. + * Create a common header on right panel cards on BaseCard ([\#8808](https://github.com/matrix-org/matrix-react-sdk/pull/8808)). Contributed by @luixxiul. + * Integrate searching public rooms and people into the new search experience ([\#8707](https://github.com/matrix-org/matrix-react-sdk/pull/8707)). Fixes vector-im/element-web#21354 and vector-im/element-web#19349. Contributed by @justjanne. + * Bring back waveform for voice messages and retain seeking ([\#8843](https://github.com/matrix-org/matrix-react-sdk/pull/8843)). Fixes vector-im/element-web#21904. + * Improve colors in settings ([\#7283](https://github.com/matrix-org/matrix-react-sdk/pull/7283)). + * Keep draft in composer when a slash command syntax errors ([\#8811](https://github.com/matrix-org/matrix-react-sdk/pull/8811)). Fixes vector-im/element-web#22384. + * Release video rooms as a beta feature ([\#8431](https://github.com/matrix-org/matrix-react-sdk/pull/8431)). + * Clarify logout key backup warning dialog. Contributed by @notramo. ([\#8741](https://github.com/matrix-org/matrix-react-sdk/pull/8741)). Fixes vector-im/element-web#15565. Contributed by @MadLittleMods. + * Slightly improve the look of the `Message edits` dialog ([\#8763](https://github.com/matrix-org/matrix-react-sdk/pull/8763)). Fixes vector-im/element-web#22410. + * Add support for MD / HTML in room topics ([\#8215](https://github.com/matrix-org/matrix-react-sdk/pull/8215)). Fixes vector-im/element-web#5180. Contributed by @Johennes. + * Live location share - link to timeline tile from share warning ([\#8752](https://github.com/matrix-org/matrix-react-sdk/pull/8752)). Contributed by @kerryarchibald. + * Improve composer visiblity ([\#8578](https://github.com/matrix-org/matrix-react-sdk/pull/8578)). Fixes vector-im/element-web#22072 and vector-im/element-web#17362. + * Makes the avatar of the user menu non-draggable ([\#8765](https://github.com/matrix-org/matrix-react-sdk/pull/8765)). Contributed by @luixxiul. + * Improve widget buttons behaviour and layout ([\#8734](https://github.com/matrix-org/matrix-react-sdk/pull/8734)). + * Use AccessibleButton for 'Reset All' link button on SetupEncryptionBody ([\#8730](https://github.com/matrix-org/matrix-react-sdk/pull/8730)). Contributed by @luixxiul. + * Adjust message timestamp position on TimelineCard in non-bubble layouts ([\#8745](https://github.com/matrix-org/matrix-react-sdk/pull/8745)). Fixes vector-im/element-web#22426. Contributed by @luixxiul. + * Use AccessibleButton for 'In reply to' link button on ReplyChain ([\#8726](https://github.com/matrix-org/matrix-react-sdk/pull/8726)). Fixes vector-im/element-web#22407. Contributed by @luixxiul. + * Live location share - enable reply and react to tiles ([\#8721](https://github.com/matrix-org/matrix-react-sdk/pull/8721)). Contributed by @kerryarchibald. + * Change dash to em dash issues fixed ([\#8455](https://github.com/matrix-org/matrix-react-sdk/pull/8455)). Fixes vector-im/element-web#21895. Contributed by @goelesha. + +## 🐛 Bug Fixes + * Make invite dialogue fixed height ([\#8945](https://github.com/matrix-org/matrix-react-sdk/pull/8945)). + * Correct issue with tab order in new search experience ([\#8919](https://github.com/matrix-org/matrix-react-sdk/pull/8919)). Fixes vector-im/element-web#22670. Contributed by @justjanne. + * Clicking location replies now redirects to the replied event instead of opening the map ([\#8918](https://github.com/matrix-org/matrix-react-sdk/pull/8918)). Fixes vector-im/element-web#22667. + * Keep clicks on pills within the app ([\#8917](https://github.com/matrix-org/matrix-react-sdk/pull/8917)). Fixes vector-im/element-web#22653. + * Don't overlap tile bubbles with timestamps in modern layout ([\#8908](https://github.com/matrix-org/matrix-react-sdk/pull/8908)). Fixes vector-im/element-web#22425. + * Connect to Jitsi unmuted by default ([\#8909](https://github.com/matrix-org/matrix-react-sdk/pull/8909)). + * Maximize width value of display name on TimelineCard with IRC/modern layout ([\#8904](https://github.com/matrix-org/matrix-react-sdk/pull/8904)). Fixes vector-im/element-web#22651. Contributed by @luixxiul. + * Align the avatar and the display name on TimelineCard ([\#8900](https://github.com/matrix-org/matrix-react-sdk/pull/8900)). Contributed by @luixxiul. + * Remove inline margin from reactions row on IRC layout ([\#8891](https://github.com/matrix-org/matrix-react-sdk/pull/8891)). Fixes vector-im/element-web#22644. Contributed by @luixxiul. + * Align "From a thread" on search result panel on IRC layout ([\#8892](https://github.com/matrix-org/matrix-react-sdk/pull/8892)). Fixes vector-im/element-web#22645. Contributed by @luixxiul. + * Display description of E2E advanced panel as subsection text ([\#8889](https://github.com/matrix-org/matrix-react-sdk/pull/8889)). Contributed by @luixxiul. + * Remove inline end margin from images on file panel ([\#8886](https://github.com/matrix-org/matrix-react-sdk/pull/8886)). Fixes vector-im/element-web#22640. Contributed by @luixxiul. + * Disable option to `Quote` when we don't have sufficient permissions ([\#8893](https://github.com/matrix-org/matrix-react-sdk/pull/8893)). Fixes vector-im/element-web#22643. + * Add padding to font scaling loader for message bubble layout ([\#8875](https://github.com/matrix-org/matrix-react-sdk/pull/8875)). Fixes vector-im/element-web#22626. Contributed by @luixxiul. + * Set 100% max-width to display name on reply tiles ([\#8867](https://github.com/matrix-org/matrix-react-sdk/pull/8867)). Fixes vector-im/element-web#22615. Contributed by @luixxiul. + * Fix alignment of pill letter ([\#8874](https://github.com/matrix-org/matrix-react-sdk/pull/8874)). Fixes vector-im/element-web#22622. Contributed by @luixxiul. + * Move the beta pill to the right side and display the pill on video room only ([\#8873](https://github.com/matrix-org/matrix-react-sdk/pull/8873)). Fixes vector-im/element-web#22619 and vector-im/element-web#22620. Contributed by @luixxiul. + * Stop using absolute property to place beta pill on RoomPreviewCard ([\#8872](https://github.com/matrix-org/matrix-react-sdk/pull/8872)). Fixes vector-im/element-web#22617. Contributed by @luixxiul. + * Make the pill text single line ([\#8744](https://github.com/matrix-org/matrix-react-sdk/pull/8744)). Fixes vector-im/element-web#22427. Contributed by @luixxiul. + * Hide overflow of public room description on spotlight dialog result ([\#8870](https://github.com/matrix-org/matrix-react-sdk/pull/8870)). Contributed by @luixxiul. + * Fix position of message action bar on the info tile on TimelineCard in message bubble layout ([\#8865](https://github.com/matrix-org/matrix-react-sdk/pull/8865)). Fixes vector-im/element-web#22614. Contributed by @luixxiul. + * Remove inline start margin from display name on reply tiles on TimelineCard ([\#8864](https://github.com/matrix-org/matrix-react-sdk/pull/8864)). Fixes vector-im/element-web#22613. Contributed by @luixxiul. + * Improve homeserver dropdown dialog styling ([\#8850](https://github.com/matrix-org/matrix-react-sdk/pull/8850)). Fixes vector-im/element-web#22552. Contributed by @justjanne. + * Fix crash when drawing blurHash for portrait videos PSB-139 ([\#8855](https://github.com/matrix-org/matrix-react-sdk/pull/8855)). Fixes vector-im/element-web#22597. Contributed by @andybalaam. + * Fix grid blowout on pinned event tiles ([\#8816](https://github.com/matrix-org/matrix-react-sdk/pull/8816)). Fixes vector-im/element-web#22543. Contributed by @luixxiul. + * Fix temporary sync errors if there's weird settings stored in account data ([\#8857](https://github.com/matrix-org/matrix-react-sdk/pull/8857)). + * Fix reactions row overflow and gap between reactions ([\#8813](https://github.com/matrix-org/matrix-react-sdk/pull/8813)). Fixes vector-im/element-web#22093. Contributed by @luixxiul. + * Fix issues with the Create new room button in Spotlight ([\#8851](https://github.com/matrix-org/matrix-react-sdk/pull/8851)). Contributed by @justjanne. + * Remove margin from E2E icon between avatar and hidden event ([\#8584](https://github.com/matrix-org/matrix-react-sdk/pull/8584)). Fixes vector-im/element-web#22186. Contributed by @luixxiul. + * Fix waveform on a message bubble ([\#8852](https://github.com/matrix-org/matrix-react-sdk/pull/8852)). Contributed by @luixxiul. + * Location sharing maps are now loaded after reconnection ([\#8848](https://github.com/matrix-org/matrix-react-sdk/pull/8848)). Fixes vector-im/element-web#20993. + * Update the avatar mask so it doesn’t cut off spaces’ avatars anymore ([\#8849](https://github.com/matrix-org/matrix-react-sdk/pull/8849)). Contributed by @justjanne. + * Add a bit of safety around timestamp handling for threads ([\#8845](https://github.com/matrix-org/matrix-react-sdk/pull/8845)). + * Remove top margin from event tile on a narrow viewport ([\#8814](https://github.com/matrix-org/matrix-react-sdk/pull/8814)). Contributed by @luixxiul. + * Fix keyboard shortcuts on settings tab being wrapped ([\#8825](https://github.com/matrix-org/matrix-react-sdk/pull/8825)). Fixes vector-im/element-web#22547. Contributed by @luixxiul. + * Add try-catch around blurhash loading ([\#8830](https://github.com/matrix-org/matrix-react-sdk/pull/8830)). + * Prevent new composer from overflowing from non-breakable text ([\#8829](https://github.com/matrix-org/matrix-react-sdk/pull/8829)). Fixes vector-im/element-web#22507. Contributed by @justjanne. + * Use common subheading on sidebar user settings tab ([\#8823](https://github.com/matrix-org/matrix-react-sdk/pull/8823)). Contributed by @luixxiul. + * Fix clickable area of advanced toggle on appearance user settings tab ([\#8820](https://github.com/matrix-org/matrix-react-sdk/pull/8820)). Fixes vector-im/element-web#22546. Contributed by @luixxiul. + * Disable redacting reactions if we don't have sufficient permissions ([\#8767](https://github.com/matrix-org/matrix-react-sdk/pull/8767)). Fixes vector-im/element-web#22262. + * Update the live timeline when the JS SDK resets it ([\#8806](https://github.com/matrix-org/matrix-react-sdk/pull/8806)). Fixes vector-im/element-web#22421. + * Fix flex blowout on image reply ([\#8809](https://github.com/matrix-org/matrix-react-sdk/pull/8809)). Fixes vector-im/element-web#22509 and vector-im/element-web#22510. Contributed by @luixxiul. + * Enable background color on hover for chat panel and thread panel ([\#8644](https://github.com/matrix-org/matrix-react-sdk/pull/8644)). Fixes vector-im/element-web#22273. Contributed by @luixxiul. + * Fix #20026: send read marker as soon as we change it ([\#8802](https://github.com/matrix-org/matrix-react-sdk/pull/8802)). Fixes vector-im/element-web#20026. Contributed by @andybalaam. + * Allow AppTiles to shrink as much as necessary ([\#8805](https://github.com/matrix-org/matrix-react-sdk/pull/8805)). Fixes vector-im/element-web#22499. + * Make widgets in video rooms immutable again ([\#8803](https://github.com/matrix-org/matrix-react-sdk/pull/8803)). Fixes vector-im/element-web#22497. + * Use MessageActionBar style declarations on pinned message card ([\#8757](https://github.com/matrix-org/matrix-react-sdk/pull/8757)). Fixes vector-im/element-web#22444. Contributed by @luixxiul. + * Expire video member events after 1 hour ([\#8776](https://github.com/matrix-org/matrix-react-sdk/pull/8776)). + * Name lists on invite dialog ([\#8046](https://github.com/matrix-org/matrix-react-sdk/pull/8046)). Fixes vector-im/element-web#21400 and vector-im/element-web#19463. Contributed by @luixxiul. + * Live location share - show loading UI for beacons with start timestamp in the future ([\#8775](https://github.com/matrix-org/matrix-react-sdk/pull/8775)). Fixes vector-im/element-web#22437. Contributed by @kerryarchibald. + * Fix scroll jump issue with the composer ([\#8788](https://github.com/matrix-org/matrix-react-sdk/pull/8788)). Fixes vector-im/element-web#22464. + * Fix the incorrect nesting of download button on MessageActionBar ([\#8785](https://github.com/matrix-org/matrix-react-sdk/pull/8785)). Contributed by @luixxiul. + * Revert link color change in composer ([\#8784](https://github.com/matrix-org/matrix-react-sdk/pull/8784)). Fixes vector-im/element-web#22468. + * Fix 'Logout' inline link on the splash screen ([\#8770](https://github.com/matrix-org/matrix-react-sdk/pull/8770)). Fixes vector-im/element-web#22449. Contributed by @luixxiul. + * Fix disappearing widget poput button when changing the widget layout ([\#8754](https://github.com/matrix-org/matrix-react-sdk/pull/8754)). + * Reduce gutter with the new read receipt UI ([\#8736](https://github.com/matrix-org/matrix-react-sdk/pull/8736)). Fixes vector-im/element-web#21890. + * Add ellipsis effect to hidden beacon status ([\#8755](https://github.com/matrix-org/matrix-react-sdk/pull/8755)). Fixes vector-im/element-web#22441. Contributed by @luixxiul. + * Make the pill on the basic message composer compatible with display name in RTL languages ([\#8758](https://github.com/matrix-org/matrix-react-sdk/pull/8758)). Fixes vector-im/element-web#22445. Contributed by @luixxiul. + * Prevent the banner text from being selected, replacing the spacing values with the variable ([\#8756](https://github.com/matrix-org/matrix-react-sdk/pull/8756)). Fixes vector-im/element-web#22442. Contributed by @luixxiul. + * Ensure the first device on a newly-registered account gets cross-signed properly ([\#8750](https://github.com/matrix-org/matrix-react-sdk/pull/8750)). Fixes vector-im/element-web#21977. Contributed by @duxovni. + * Hide live location option in threads composer ([\#8746](https://github.com/matrix-org/matrix-react-sdk/pull/8746)). Fixes vector-im/element-web#22424. Contributed by @kerryarchibald. + * Make sure MessageTimestamp is not hidden by EventTile_line on TimelineCard ([\#8748](https://github.com/matrix-org/matrix-react-sdk/pull/8748)). Contributed by @luixxiul. + * Make PiP motion smoother and react to window resizes correctly ([\#8747](https://github.com/matrix-org/matrix-react-sdk/pull/8747)). Fixes vector-im/element-web#22292. + * Prevent Invite and DevTools dialogs from being cut off ([\#8646](https://github.com/matrix-org/matrix-react-sdk/pull/8646)). Fixes vector-im/element-web#20911 and undefined/matrix-react-sdk#8165. Contributed by @justjanne. + * Squish event bubble tiles less ([\#8740](https://github.com/matrix-org/matrix-react-sdk/pull/8740)). + * Use random widget IDs for video rooms ([\#8739](https://github.com/matrix-org/matrix-react-sdk/pull/8739)). Fixes vector-im/element-web#22417. + * Fix read avatars overflow from the right chat panel with a maximized widget on bubble message layout ([\#8470](https://github.com/matrix-org/matrix-react-sdk/pull/8470)). Contributed by @luixxiul. + * Fix `CallView` crash ([\#8735](https://github.com/matrix-org/matrix-react-sdk/pull/8735)). Fixes vector-im/element-web#22394. + Changes in [3.47.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.47.0) (2022-06-14) ===================================================================================================== diff --git a/__mocks__/maplibre-gl.js b/__mocks__/maplibre-gl.js index 599cacde13d..fe6ec9139e6 100644 --- a/__mocks__/maplibre-gl.js +++ b/__mocks__/maplibre-gl.js @@ -1,5 +1,21 @@ +/* +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. +*/ + const EventEmitter = require("events"); -const { LngLat, NavigationControl, LngLatBounds } = require('maplibre-gl'); +const { LngLat, NavigationControl, LngLatBounds, AttributionControl } = require('maplibre-gl'); class MockMap extends EventEmitter { addControl = jest.fn(); @@ -27,4 +43,5 @@ module.exports = { LngLat, LngLatBounds, NavigationControl, + AttributionControl, }; diff --git a/code_style.md b/code_style.md index 5747540a766..8071cd264bd 100644 --- a/code_style.md +++ b/code_style.md @@ -208,3 +208,7 @@ React information in component state that could be derived from the model? - Avoid things marked as Legacy or Deprecated in React 16 (e.g string refs and legacy contexts) + +Unit tests +----- +- New tests should use [react testing library](https://testing-library.com/docs/react-testing-library/intro/) \ No newline at end of file diff --git a/cypress.json b/cypress.json index d41cc70dd00..92f7d0f2540 100644 --- a/cypress.json +++ b/cypress.json @@ -3,5 +3,10 @@ "videoUploadOnPasses": false, "projectId": "ppvnzg", "experimentalSessionAndOrigin": true, - "experimentalInteractiveRunEvents": true + "experimentalInteractiveRunEvents": true, + "retries": { + "runMode": 2, + "openMode": 0 + }, + "chromeWebSecurity": false } diff --git a/cypress/global.d.ts b/cypress/global.d.ts index a3e91a2b44c..3d36daf951c 100644 --- a/cypress/global.d.ts +++ b/cypress/global.d.ts @@ -15,10 +15,17 @@ limitations under the License. */ import "matrix-js-sdk/src/@types/global"; -import type { MatrixClient, ClientEvent } from "matrix-js-sdk/src/client"; -import type { MatrixScheduler, MemoryCryptoStore, MemoryStore, RoomStateEvent } from "matrix-js-sdk/src/matrix"; -import type { RoomMemberEvent } from "matrix-js-sdk/src/models/room-member"; -import type { WebStorageSessionStore } from "matrix-js-sdk/src/store/session/webstorage"; +import type { + MatrixClient, + ClientEvent, + MatrixScheduler, + MemoryCryptoStore, + MemoryStore, + Preset, + RoomStateEvent, + Visibility, + RoomMemberEvent, +} from "matrix-js-sdk/src/matrix"; import type { MatrixDispatcher } from "../src/dispatcher/dispatcher"; import type PerformanceMonitor from "../src/performance"; @@ -41,7 +48,8 @@ declare global { MatrixScheduler: typeof MatrixScheduler; MemoryStore: typeof MemoryStore; MemoryCryptoStore: typeof MemoryCryptoStore; - WebStorageSessionStore: typeof WebStorageSessionStore; + Visibility: typeof Visibility; + Preset: typeof Preset; }; } } diff --git a/cypress/integration/1-register/register.spec.ts b/cypress/integration/1-register/register.spec.ts index 3dba78c4904..3d710cd965b 100644 --- a/cypress/integration/1-register/register.spec.ts +++ b/cypress/integration/1-register/register.spec.ts @@ -67,5 +67,10 @@ describe("Registration", () => { cy.url().should('contain', '/#/home'); cy.stopMeasuring("from-submit-to-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"); }); }); diff --git a/cypress/integration/10-user-view/user-view.spec.ts b/cypress/integration/10-user-view/user-view.spec.ts new file mode 100644 index 00000000000..e98a1d47f41 --- /dev/null +++ b/cypress/integration/10-user-view/user-view.spec.ts @@ -0,0 +1,51 @@ +/* +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. +*/ + +/// + +import { SynapseInstance } from "../../plugins/synapsedocker"; +import { MatrixClient } from "../../global"; + +describe("UserView", () => { + let synapse: SynapseInstance; + + beforeEach(() => { + cy.startSynapse("default").then(data => { + synapse = data; + + cy.initTestUser(synapse, "Violet"); + cy.getBot(synapse, "Usman").as("bot"); + }); + }); + + afterEach(() => { + cy.stopSynapse(synapse); + }); + + it("should render the user view as expected", () => { + cy.get("@bot").then(bot => { + cy.visit(`/#/user/${bot.getUserId()}`); + }); + + cy.get("#mx_RightPanel .mx_UserInfo_profile h2").should("contain", "Usman"); + cy.get(".mx_RightPanel .mx_Spinner").should("not.exist"); // wait for spinners to finish + cy.get(".mx_RightPanel").percySnapshotElement("User View", { + // Hide the MXID field as it'll vary on each test + percyCSS: ".mx_UserInfo_profile_mxid { visibility: hidden !important; }", + widths: [260, 500], + }); + }); +}); diff --git a/cypress/integration/11-room-directory/room-directory.spec.ts b/cypress/integration/11-room-directory/room-directory.spec.ts new file mode 100644 index 00000000000..e7e3c5c9c86 --- /dev/null +++ b/cypress/integration/11-room-directory/room-directory.spec.ts @@ -0,0 +1,103 @@ +/* +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. +*/ + +/// + +import { SynapseInstance } from "../../plugins/synapsedocker"; +import { MatrixClient } from "../../global"; + +describe("Room Directory", () => { + let synapse: SynapseInstance; + + beforeEach(() => { + cy.startSynapse("default").then(data => { + synapse = data; + + cy.initTestUser(synapse, "Ray"); + cy.getBot(synapse, "Paul").as("bot"); + }); + }); + + afterEach(() => { + cy.stopSynapse(synapse); + }); + + it("should allow admin to add alias & publish room to directory", () => { + cy.window({ log: false }).then(win => { + cy.createRoom({ + name: "Gaming", + preset: win.matrixcs.Preset.PublicChat, + }).as("roomId"); + }); + + cy.viewRoomByName("Gaming"); + cy.openRoomSettings(); + + // First add a local address `gaming` + cy.contains(".mx_SettingsFieldset", "Local Addresses").within(() => { + cy.get(".mx_Field input").type("gaming"); + cy.contains(".mx_AccessibleButton", "Add").click(); + cy.get(".mx_EditableItem_item").should("contain", "#gaming:localhost"); + }); + + // 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() + .should("have.attr", "aria-checked", "true"); + }); + + cy.closeDialog(); + + 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); + expect(resp.chunk[0].room_id).to.equal(roomId); + }); + }); + + 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]) => { + bot.createRoom({ + visibility: win.matrixcs.Visibility.Public, + name, + room_alias_name: "test1234", + }); + }); + + cy.get('[role="button"][aria-label="Explore rooms"]').click(); + + cy.get('.mx_RoomDirectory_dialogWrapper [name="dirsearch"]').type("Unknown Room"); + cy.get(".mx_RoomDirectory_dialogWrapper h5").should("contain", 'No results for "Unknown Room"'); + cy.get(".mx_RoomDirectory_dialogWrapper").percySnapshotElement("Room Directory - filtered no results"); + + cy.get('.mx_RoomDirectory_dialogWrapper [name="dirsearch"]').type("{selectAll}{backspace}test1234"); + cy.get(".mx_RoomDirectory_dialogWrapper").contains(".mx_RoomDirectory_listItem", name) + .should("exist").as("resultRow"); + cy.get(".mx_RoomDirectory_dialogWrapper").percySnapshotElement("Room Directory - filtered one result"); + cy.get("@resultRow").find(".mx_AccessibleButton").contains("Join").click(); + + cy.url().should('contain', `/#/room/#test1234:localhost`); + }); +}); diff --git a/cypress/integration/12-spotlight/spotlight.spec.ts b/cypress/integration/12-spotlight/spotlight.spec.ts new file mode 100644 index 00000000000..5c0018b499c --- /dev/null +++ b/cypress/integration/12-spotlight/spotlight.spec.ts @@ -0,0 +1,321 @@ +/* +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. +*/ + +/// + +import { MatrixClient } from "../../global"; +import { SynapseInstance } from "../../plugins/synapsedocker"; +import Chainable = Cypress.Chainable; +import Loggable = Cypress.Loggable; +import Timeoutable = Cypress.Timeoutable; +import Withinable = Cypress.Withinable; +import Shadow = Cypress.Shadow; + +export enum Filter { + People = "people", + PublicRooms = "public_rooms" +} + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + /** + * Opens the spotlight dialog + */ + openSpotlightDialog( + options?: Partial + ): Chainable>; + spotlightDialog( + options?: Partial + ): Chainable>; + spotlightFilter( + filter: Filter | null, + options?: Partial + ): Chainable>; + spotlightSearch( + options?: Partial + ): Chainable>; + spotlightResults( + options?: Partial + ): Chainable>; + roomHeaderName( + options?: Partial + ): 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); +}); + +describe("Spotlight", () => { + let synapse: SynapseInstance; + + const bot1Name = "BotBob"; + let bot1: MatrixClient; + + const bot2Name = "ByteBot"; + let bot2: MatrixClient; + + const room1Name = "247"; + let room1Id: string; + + const room2Name = "Lounge"; + let room2Id: string; + + beforeEach(() => { + cy.startSynapse("default").then(data => { + synapse = data; + cy.initTestUser(synapse, "Jim").then(() => + cy.getBot(synapse, bot1Name).then(_bot1 => { + bot1 = _bot1; + }), + ).then(() => + cy.getBot(synapse, 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; + cy.inviteUser(room1Id, bot1.getUserId()); + cy.visit("/#/room/" + room1Id); + }); + bot2.createRoom({ name: room2Name, visibility: Visibility.Public }) + .then(({ room_id: _room2Id }) => { + room2Id = _room2Id; + bot2.invite(room2Id, bot1.getUserId()); + }); + }), + ).then(() => + cy.get('.mx_RoomSublist_skeletonUI').should('not.exist'), + ); + }); + }); + + afterEach(() => { + cy.stopSynapse(synapse); + }); + + it("should be able to add and remove filters via keyboard", () => { + cy.openSpotlightDialog().within(() => { + cy.spotlightSearch().type("{downArrow}"); + cy.get("#mx_SpotlightDialog_button_explorePublicRooms").should("have.attr", "aria-selected", "true"); + cy.spotlightSearch().type("{enter}"); + cy.get(".mx_SpotlightDialog_filter").should("contain", "Public rooms"); + cy.spotlightSearch().type("{backspace}"); + cy.get(".mx_SpotlightDialog_filter").should("not.exist"); + + cy.spotlightSearch().type("{downArrow}"); + cy.spotlightSearch().type("{downArrow}"); + cy.get("#mx_SpotlightDialog_button_startChat").should("have.attr", "aria-selected", "true"); + cy.spotlightSearch().type("{enter}"); + cy.get(".mx_SpotlightDialog_filter").should("contain", "People"); + cy.spotlightSearch().type("{backspace}"); + cy.get(".mx_SpotlightDialog_filter").should("not.exist"); + }); + }); + + it("should find joined rooms", () => { + cy.openSpotlightDialog().within(() => { + cy.spotlightSearch().clear().type(room1Name); + 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.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 unknown public rooms", () => { + cy.openSpotlightDialog().within(() => { + cy.spotlightFilter(Filter.PublicRooms); + cy.spotlightSearch().clear().type(room2Name); + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", room2Name); + cy.spotlightResults().eq(0).click(); + cy.url().should("contain", room2Id); + }).then(() => { + cy.get(".mx_RoomPreviewBar_actions .mx_AccessibleButton").click(); + cy.roomHeaderName().should("contain", room2Name); + }); + }); + + // TODO: We currently can’t test finding rooms on other homeservers/other protocols + // We obviously don’t have federation or bridges in cypress tests + /* + const room3Name = "Matrix HQ"; + const room3Id = "#matrix:matrix.org"; + + it("should find unknown public rooms on other homeservers", () => { + cy.openSpotlightDialog().within(() => { + cy.spotlightFilter(Filter.PublicRooms); + cy.spotlightSearch().clear().type(room3Name); + cy.get("[aria-haspopup=true][role=button]").click(); + }).then(() => { + cy.contains(".mx_GenericDropdownMenu_Option--header", "matrix.org") + .next("[role=menuitemradio]") + .click(); + cy.wait(3_600_000); + }).then(() => cy.spotlightDialog().within(() => { + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", room3Name); + cy.spotlightResults().eq(0).should("contain", room3Id); + })); + }); + */ + it("should find known people", () => { + cy.openSpotlightDialog().within(() => { + cy.spotlightFilter(Filter.People); + cy.spotlightSearch().clear().type(bot1Name); + 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.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 allow opening group chat dialog", () => { + cy.openSpotlightDialog().within(() => { + cy.spotlightFilter(Filter.People); + cy.spotlightSearch().clear().type(bot2Name); + 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 be able to navigate results via keyboard", () => { + cy.openSpotlightDialog().within(() => { + cy.spotlightFilter(Filter.People); + cy.spotlightSearch().clear().type("b"); + // our debouncing logic only starts the search after a short timeout, + // so we wait a few milliseconds. + cy.wait(300); + 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/integration/13-regression-tests/pills-click-in-app.spec.ts b/cypress/integration/13-regression-tests/pills-click-in-app.spec.ts new file mode 100644 index 00000000000..33c86cbf3ac --- /dev/null +++ b/cypress/integration/13-regression-tests/pills-click-in-app.spec.ts @@ -0,0 +1,75 @@ +/* +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. +*/ + +/// + +import { SynapseInstance } from "../../plugins/synapsedocker"; + +describe("Pills", () => { + let synapse: SynapseInstance; + + beforeEach(() => { + cy.startSynapse("default").then(data => { + synapse = data; + + cy.initTestUser(synapse, "Sally"); + }); + }); + + afterEach(() => { + cy.stopSynapse(synapse); + }); + + it('should navigate clicks internally to the app', () => { + const messageRoom = "Send Messages Here"; + const targetLocalpart = "aliasssssssssssss"; + cy.createRoom({ + name: "Target", + room_alias_name: targetLocalpart, + }).as("targetRoomId"); + 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}`); + + // 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(); + + // verify we landed at a sane place + cy.url().should("contain", `/#/room/#${targetLocalpart}:`); + + 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", `https://matrix.to/#/#${targetLocalpart}:`); + }); + }); +}); diff --git a/cypress/integration/5-threads/threads.spec.ts b/cypress/integration/5-threads/threads.spec.ts index 226e63576d8..64269b14574 100644 --- a/cypress/integration/5-threads/threads.spec.ts +++ b/cypress/integration/5-threads/threads.spec.ts @@ -28,9 +28,10 @@ describe("Threads", () => { let synapse: SynapseInstance; beforeEach(() => { + // Default threads to ON for this spec + cy.enableLabsFeature("feature_thread"); cy.window().then(win => { win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests - win.localStorage.setItem("mx_labs_feature_feature_thread", "true"); // Default threads to ON for this spec }); cy.startSynapse("default").then(data => { synapse = data; diff --git a/cypress/integration/7-crypto/crypto.spec.ts b/cypress/integration/7-crypto/crypto.spec.ts index 2446e3bd2bb..6f1f7aa6c89 100644 --- a/cypress/integration/7-crypto/crypto.spec.ts +++ b/cypress/integration/7-crypto/crypto.spec.ts @@ -19,13 +19,17 @@ limitations under the License. import type { MatrixClient } from "matrix-js-sdk/src/matrix"; import { SynapseInstance } from "../../plugins/synapsedocker"; -function waitForEncryption(cli: MatrixClient, roomId: string, win: Cypress.AUTWindow, resolve: () => void) { - cli.crypto.cryptoStore.getEndToEndRooms(null, (result) => { - if (result[roomId]) { - resolve(); - } else { - cli.once(win.matrixcs.RoomStateEvent.Update, () => waitForEncryption(cli, roomId, win, resolve)); - } +function waitForEncryption(cli: MatrixClient, roomId: string, win: Cypress.AUTWindow): Promise { + return new Promise(resolve => { + const onEvent = () => { + cli.crypto.cryptoStore.getEndToEndRooms(null, (result) => { + if (result[roomId]) { + cli.off(win.matrixcs.ClientEvent.Event, onEvent); + resolve(); + } + }); + }; + cli.on(win.matrixcs.ClientEvent.Event, onEvent); }); } @@ -61,15 +65,15 @@ describe("Cryptography", () => { cy.window(), ]).then(([bot, roomId, win]) => { cy.inviteUser(roomId, bot.getUserId()); - cy.visit("/#/room/" + roomId); cy.wrap( - new Promise(resolve => - waitForEncryption(bot, roomId, win, resolve), + waitForEncryption( + bot, roomId, win, ).then(() => bot.sendMessage(roomId, { body: "Top secret message", msgtype: "m.text", })), ); + cy.visit("/#/room/" + roomId); }); cy.get(".mx_RoomView_body .mx_cryptoEvent").should("contain", "Encryption enabled"); diff --git a/cypress/integration/9-widgets/stickers.spec.ts b/cypress/integration/9-widgets/stickers.spec.ts new file mode 100644 index 00000000000..0d0fee8776f --- /dev/null +++ b/cypress/integration/9-widgets/stickers.spec.ts @@ -0,0 +1,163 @@ +/* +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. +*/ + +/// + +import { SynapseInstance } from "../../plugins/synapsedocker"; + +const STICKER_PICKER_WIDGET_ID = "fake-sticker-picker"; +const STICKER_PICKER_WIDGET_NAME = "Fake Stickers"; +const STICKER_NAME = "Test Sticker"; +const ROOM_NAME_1 = "Sticker Test"; +const ROOM_NAME_2 = "Sticker Test Two"; +const STICKER_MESSAGE = JSON.stringify({ + action: "m.sticker", + api: "fromWidget", + data: { + name: "teststicker", + description: STICKER_NAME, + file: "test.png", + content: { + body: STICKER_NAME, + msgtype: "m.sticker", + url: "mxc://somewhere", + }, + }, + requestId: "1", + widgetId: STICKER_PICKER_WIDGET_ID, +}); +const WIDGET_HTML = ` + + + Fake Sticker Picker + + + + + + + +`; + +function openStickerPicker() { + cy.get('.mx_MessageComposer_buttonMenu').click(); + cy.get('#stickersButton').click(); +} + +function sendStickerFromPicker() { + // Note: Until https://github.com/cypress-io/cypress/issues/136 is fixed we will need + // 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(); + }); + + // Sticker picker should close itself after sending. + 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}/`); + + // Make sure the image points at the sticker image + cy.get(`img[alt="${STICKER_NAME}"]`) + .should("have.attr", "src") + .and("match", /thumbnail\/somewhere\?/); +} + +describe("Stickers", () => { + // We spin up a web server for the sticker picker so that we're not testing to see if + // sysadmins can deploy sticker pickers on the same Element domain - we actually want + // to make sure that cross-origin postMessage works properly. This makes it difficult + // to write the test though, as we have to juggle iframe logistics. + // + // See sendStickerFromPicker() for more detail on iframe comms. + + let stickerPickerUrl: string; + let synapse: SynapseInstance; + + beforeEach(() => { + cy.startSynapse("default").then(data => { + synapse = data; + + cy.initTestUser(synapse, "Sally"); + }); + cy.serveHtmlFile(WIDGET_HTML).then(url => { + stickerPickerUrl = url; + }); + }); + + afterEach(() => { + cy.stopSynapse(synapse); + cy.stopWebServers(); + }); + + it('should send a sticker to multiple rooms', () => { + cy.createRoom({ + name: ROOM_NAME_1, + }).as("roomId1"); + cy.createRoom({ + name: ROOM_NAME_2, + }).as("roomId2"); + cy.setAccountData("m.widgets", { + [STICKER_PICKER_WIDGET_ID]: { + content: { + type: "m.stickerpicker", + name: STICKER_PICKER_WIDGET_NAME, + url: stickerPickerUrl, + }, + id: STICKER_PICKER_WIDGET_ID, + }, + }).as("stickers"); + + cy.all([ + cy.get("@roomId1"), + cy.get("@roomId2"), + cy.get<{}>("@stickers"), // just want to wait for it to be set up + ]).then(([roomId1, roomId2]) => { + cy.viewRoomByName(ROOM_NAME_1); + cy.url().should("contain", `/#/room/${roomId1}`); + openStickerPicker(); + sendStickerFromPicker(); + expectTimelineSticker(roomId1); + + // Ensure that when we switch to a different room that the sticker + // goes to the right place + cy.viewRoomByName(ROOM_NAME_2); + cy.url().should("contain", `/#/room/${roomId2}`); + openStickerPicker(); + sendStickerFromPicker(); + expectTimelineSticker(roomId2); + }); + }); +}); diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts index eab5441c203..bc62efb03f9 100644 --- a/cypress/plugins/index.ts +++ b/cypress/plugins/index.ts @@ -20,6 +20,7 @@ import PluginEvents = Cypress.PluginEvents; import PluginConfigOptions = Cypress.PluginConfigOptions; import { performance } from "./performance"; import { synapseDocker } from "./synapsedocker"; +import { webserver } from "./webserver"; /** * @type {Cypress.PluginConfig} @@ -27,4 +28,5 @@ import { synapseDocker } from "./synapsedocker"; export default function(on: PluginEvents, config: PluginConfigOptions) { performance(on, config); synapseDocker(on, config); + webserver(on, config); } diff --git a/cypress/plugins/synapsedocker/index.ts b/cypress/plugins/synapsedocker/index.ts index 7108ade904a..42241ecc7d6 100644 --- a/cypress/plugins/synapsedocker/index.ts +++ b/cypress/plugins/synapsedocker/index.ts @@ -112,6 +112,12 @@ async function synapseStart(template: string): Promise { const containerName = `react-sdk-cypress-synapse-${crypto.randomBytes(4).toString("hex")}`; const userInfo = os.userInfo(); + let userParams: string[] = []; + if (userInfo.uid >= 0) { + // On *nix we run the docker container as our uid:gid otherwise cleaning it up its media_store can be difficult + userParams = ["-u", `${userInfo.uid}:${userInfo.gid}`]; + } + const synapseId = await new Promise((resolve, reject) => { childProcess.execFile('docker', [ "run", @@ -119,8 +125,7 @@ async function synapseStart(template: string): Promise { "-d", "-v", `${synCfg.configDir}:/data`, "-p", `${synCfg.port}:8008/tcp`, - // We run the docker container as our uid:gid otherwise cleaning it up its media_store can be difficult - "-u", `${userInfo.uid}:${userInfo.gid}`, + ...userParams, "matrixdotorg/synapse:develop", "run", ], (err, stdout) => { diff --git a/cypress/plugins/webserver.ts b/cypress/plugins/webserver.ts new file mode 100644 index 00000000000..55a25a313e3 --- /dev/null +++ b/cypress/plugins/webserver.ts @@ -0,0 +1,52 @@ +/* +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. +*/ + +/// + +import * as http from "http"; +import { AddressInfo } from "net"; + +import PluginEvents = Cypress.PluginEvents; +import PluginConfigOptions = Cypress.PluginConfigOptions; + +const servers: http.Server[] = []; + +function serveHtmlFile(html: string): string { + const server = http.createServer((req, res) => { + res.writeHead(200, { + "Content-Type": "text/html", + }); + res.end(html); + }); + server.listen(); + servers.push(server); + + return `http://localhost:${(server.address() as AddressInfo).port}/`; +} + +function stopWebServers(): null { + for (const server of servers) { + server.close(); + } + servers.splice(0, servers.length); // clear + + return null; // tell cypress we did the task successfully (doesn't allow undefined) +} + +export function webserver(on: PluginEvents, config: PluginConfigOptions) { + on("task", { serveHtmlFile, stopWebServers }); + on("after:run", stopWebServers); +} diff --git a/cypress/support/app.ts b/cypress/support/app.ts index 21321e2b56f..322b9785f36 100644 --- a/cypress/support/app.ts +++ b/cypress/support/app.ts @@ -16,7 +16,6 @@ limitations under the License. /// -import "./client"; // XXX: without an (any) import here, types break down import Chainable = Cypress.Chainable; import AUTWindow = Cypress.AUTWindow; @@ -41,3 +40,6 @@ Cypress.Commands.add("tweakConfig", (tweaks: Record): Chainable { diff --git a/cypress/support/client.ts b/cypress/support/client.ts index 6a6a3932711..db27f4d2b1e 100644 --- a/cypress/support/client.ts +++ b/cypress/support/client.ts @@ -47,6 +47,12 @@ declare global { * @param userId the id of the user to invite */ inviteUser(roomId: string, userId: string): Chainable<{}>; + /** + * Sets account data for the user. + * @param type The type of account data. + * @param data The data to store. + */ + setAccountData(type: string, data: object): Chainable<{}>; } } } @@ -91,3 +97,9 @@ Cypress.Commands.add("inviteUser", (roomId: string, userId: string): Chainable<{ return cli.invite(roomId, userId); }); }); + +Cypress.Commands.add("setAccountData", (type: string, data: object): Chainable<{}> => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.setAccountData(type, data); + }); +}); diff --git a/cypress/support/iframes.ts b/cypress/support/iframes.ts new file mode 100644 index 00000000000..27bd5e0b8ee --- /dev/null +++ b/cypress/support/iframes.ts @@ -0,0 +1,45 @@ +/* +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. +*/ + +/// + +import Chainable = Cypress.Chainable; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + /** + * Gets you into the `body` of the selectable iframe. Best to call + * `within({}, () => { ... })` on the returned Chainable to access + * further elements. + * @param selector The jquery selector to find the frame with. + */ + accessIframe(selector: string): Chainable>; + } + } +} + +// 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>; +}); + +// Needed to make this file a module +export { }; diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 06d6efc252b..4a0852c64a6 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -22,9 +22,14 @@ import "cypress-real-events"; import "./performance"; import "./synapse"; import "./login"; +import "./labs"; import "./client"; import "./settings"; import "./bot"; import "./clipboard"; import "./util"; import "./app"; +import "./percy"; +import "./webserver"; +import "./views"; +import "./iframes"; diff --git a/cypress/support/labs.ts b/cypress/support/labs.ts new file mode 100644 index 00000000000..3fff154e140 --- /dev/null +++ b/cypress/support/labs.ts @@ -0,0 +1,42 @@ +/* +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. +*/ + +import Chainable = Cypress.Chainable; + +/// + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + /** + * Enables a labs feature for an element session. + * Has to be called before the session is initialized + * @param feature labsFeature to enable (e.g. "feature_spotlight") + */ + enableLabsFeature(feature: string): Chainable; + } + } +} + +Cypress.Commands.add("enableLabsFeature", (feature: string): Chainable => { + 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 { }; diff --git a/cypress/support/percy.ts b/cypress/support/percy.ts new file mode 100644 index 00000000000..f190e7a5148 --- /dev/null +++ b/cypress/support/percy.ts @@ -0,0 +1,54 @@ +/* +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. +*/ + +/// +import { SnapshotOptions as PercySnapshotOptions } from '@percy/core'; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface SnapshotOptions extends PercySnapshotOptions { + domTransformation?: (documentClone: Document) => void; + } + + interface Chainable { + percySnapshotElement(name?: string, options?: SnapshotOptions); + } + + interface Chainable { + /** + * Takes a Percy snapshot of a given element + */ + percySnapshotElement(name: string, options: SnapshotOptions): Chainable; + } + } +} + +Cypress.Commands.add("percySnapshotElement", { prevSubject: "element" }, (subject, name, options) => { + cy.percySnapshot(name, { + 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; + + return documentClone; +} + +export { }; diff --git a/cypress/support/settings.ts b/cypress/support/settings.ts index 4be44e27117..aed0631354b 100644 --- a/cypress/support/settings.ts +++ b/cypress/support/settings.ts @@ -33,16 +33,22 @@ declare global { */ openUserSettings(tab?: string): Chainable>; + /** + * Open room settings (via room header menu), returning a handle to the resulting dialog. + * @param tab the name of the tab to switch to after opening, optional. + */ + openRoomSettings(tab?: string): Chainable>; + /** * Switch settings tab to the one by the given name, ideally call this in the context of the dialog. * @param tab the name of the tab to switch to. */ - switchTabUserSettings(tab: string): Chainable>; + switchTab(tab: string): Chainable>; /** - * Close user settings, ideally call this in the context of the dialog. + * Close dialog, ideally call this in the context of the dialog. */ - closeUserSettings(): Chainable>; + closeDialog(): Chainable>; /** * Join the given beta, the `Labs` tab must already be opened, @@ -72,18 +78,30 @@ Cypress.Commands.add("openUserSettings", (tab?: string): Chainable { if (tab) { - cy.switchTabUserSettings(tab); + cy.switchTab(tab); + } + }); +}); + +Cypress.Commands.add("openRoomSettings", (tab?: string): Chainable> => { + cy.get(".mx_RoomHeader_name").click(); + cy.get(".mx_RoomTile_contextMenu").within(() => { + cy.get('[aria-label="Settings"]').click(); + }); + return cy.get(".mx_RoomSettingsDialog").within(() => { + if (tab) { + cy.switchTab(tab); } }); }); -Cypress.Commands.add("switchTabUserSettings", (tab: string): Chainable> => { +Cypress.Commands.add("switchTab", (tab: string): Chainable> => { return cy.get(".mx_TabbedView_tabLabels").within(() => { cy.get(".mx_TabbedView_tabLabel").contains(tab).click(); }); }); -Cypress.Commands.add("closeUserSettings", (): Chainable> => { +Cypress.Commands.add("closeDialog", (): Chainable> => { return cy.get('[aria-label="Close dialog"]').click(); }); diff --git a/cypress/support/storage.ts b/cypress/support/storage.ts deleted file mode 100644 index d93e1188351..00000000000 --- a/cypress/support/storage.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* -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. -*/ - -export class MockStorage implements Storage { - private data: Record = {}; - private keys: string[] = []; - public length = 0; - - constructor() {} - - public setItem(k: string, v: string) { - this.data[k] = v; - this.recalc(); - } - - public getItem(k: string): string | null { - return this.data[k] || null; - } - - public removeItem(k: string) { - delete this.data[k]; - this.recalc(); - } - - public clear() { - this.data = {}; - this.recalc(); - } - - public key(index: number): string { - return this.keys[index]; - } - - private recalc() { - const keys = []; - for (const k in this.data) { - if (!this.data.hasOwnProperty(k)) { - continue; - } - keys.push(k); - } - this.keys = keys; - this.length = keys.length; - } -} diff --git a/cypress/support/views.ts b/cypress/support/views.ts new file mode 100644 index 00000000000..c7f55b4ac9c --- /dev/null +++ b/cypress/support/views.ts @@ -0,0 +1,40 @@ +/* +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. +*/ + +/// + +import Chainable = Cypress.Chainable; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + /** + * Opens the given room by name. The room must be visible in the + * room list. + * @param name The room name to find and click on/open. + */ + viewRoomByName(name: string): Chainable>; + } + } +} + +Cypress.Commands.add("viewRoomByName", (name: string): Chainable> => { + return cy.get(`.mx_RoomTile[aria-label="${name}"]`).click(); +}); + +// Needed to make this file a module +export { }; diff --git a/cypress/support/webserver.ts b/cypress/support/webserver.ts new file mode 100644 index 00000000000..a587e1aa8bf --- /dev/null +++ b/cypress/support/webserver.ts @@ -0,0 +1,52 @@ +/* +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. +*/ + +/// + +import Chainable = Cypress.Chainable; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + /** + * Starts a web server which serves the given HTML. + * @param html The HTML to serve + * @returns The URL at which the HTML can be accessed. + */ + serveHtmlFile(html: string): Chainable; + + /** + * Stops all running web servers. + */ + stopWebServers(): Chainable; + } + } +} + +function serveHtmlFile(html: string): Chainable { + return cy.task("serveHtmlFile", html); +} + +function stopWebServers(): Chainable { + return cy.task("stopWebServers"); +} + +Cypress.Commands.add("serveHtmlFile", serveHtmlFile); +Cypress.Commands.add("stopWebServers", stopWebServers); + +// Needed to make this file a module +export { }; diff --git a/docs/cypress.md b/docs/cypress.md index 021f10b215e..1f3882a7e79 100644 --- a/docs/cypress.md +++ b/docs/cypress.md @@ -171,9 +171,8 @@ 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. -Right now we run it as part of the standard Pull Request CI automation but due to only having 25k screenshots/month, -and each `cy.percySnapshot()` call results in 8 screenshots (4 browsers, 2 sizes) this could quickly be exhausted and -at that point we would likely run it on a CRON interval or before releases. +Each `cy.percySnapshot()` call results in 8 screenshots (4 browsers, 2 sizes) this can quickly be exhausted and +so we only run Percy testing on `develop` and PRs which are labelled `X-Needs-Percy`. 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/scrolling.md b/docs/scrolling.md index a5232359a74..9c5edc5f345 100644 --- a/docs/scrolling.md +++ b/docs/scrolling.md @@ -23,6 +23,6 @@ As of https://github.com/matrix-org/matrix-react-sdk/pull/4166, we are scrolling ### How does it work? -`componentDidUpdate` is called when a tile in the timeline is updated (as we rerender the whole timeline) or tiles are added or removed (see Updates section before). From here, `checkScroll` is called, which calls `_restoreSavedScrollState`. Now, we increase the timeline height if something below the viewport grew by adjusting `this._bottomGrowth`. `bottomGrowth` is the height added to the timeline (on top of the height from the number of pages calculated at the last `_updateHeight` run) to compensate for growth below the viewport. This is cleared during the next run of `_updateHeight`. Remember that the tiles in the timeline are aligned to the bottom. +`componentDidUpdate` is called when a tile in the timeline is updated (as we rerender the whole timeline) or tiles are added or removed (see Updates section before). From here, `checkScroll` is called, which calls `restoreSavedScrollState`. Now, we increase the timeline height if something below the viewport grew by adjusting `this.bottomGrowth`. `bottomGrowth` is the height added to the timeline (on top of the height from the number of pages calculated at the last `updateHeight` run) to compensate for growth below the viewport. This is cleared during the next run of `updateHeight`. Remember that the tiles in the timeline are aligned to the bottom. -From `_restoreSavedScrollState` we also call `_updateHeight` which waits until the user stops scrolling for 100ms and then recalculates the amount of pages of 400px the timeline should be sized to, to be able to show all of its (newly added) content. We have to adjust the scroll offset (which is why we wait until scrolling has stopped) now because the space above the viewport has likely changed. +From `restoreSavedScrollState` we also call `updateHeight` which waits until the user stops scrolling for 100ms and then recalculates the amount of pages of 400px the timeline should be sized to, to be able to show all of its (newly added) content. We have to adjust the scroll offset (which is why we wait until scrolling has stopped) now because the space above the viewport has likely changed. diff --git a/package.json b/package.json index ef3e8c950b4..82c824422ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.47.0", + "version": "3.48.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -56,8 +56,10 @@ }, "dependencies": { "@babel/runtime": "^7.12.5", + "@matrix-org/analytics-events": "^0.1.1", "@sentry/browser": "^6.11.0", "@sentry/tracing": "^6.11.0", + "@testing-library/react": "^12.1.5", "@types/geojson": "^7946.0.8", "await-lock": "^2.1.0", "blurhash": "^1.1.3", @@ -88,10 +90,9 @@ "linkifyjs": "4.0.0-beta.4", "lodash": "^4.17.20", "maplibre-gl": "^1.15.2", - "matrix-analytics-events": "github:matrix-org/matrix-analytics-events.git#a0687ca6fbdb7258543d49b99fb88b9201e900b0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "^0.0.1-beta.7", - "matrix-js-sdk": "18.1.0", + "matrix-js-sdk": "19.0.0", "matrix-widget-api": "^0.1.0-beta.18", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", @@ -134,7 +135,7 @@ "@babel/traverse": "^7.12.12", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz", "@peculiar/webcrypto": "^1.1.4", - "@percy/cli": "^1.1.4", + "@percy/cli": "^1.3.0", "@percy/cypress": "^3.1.1", "@sentry/types": "^6.10.0", "@sinonjs/fake-timers": "^9.1.2", @@ -191,7 +192,7 @@ "jest-sonar-reporter": "^2.0.0", "matrix-mock-request": "^2.0.0", "matrix-react-test-utils": "^0.2.3", - "matrix-web-i18n": "^1.2.0", + "matrix-web-i18n": "^1.3.0", "raw-loader": "^4.0.2", "react-test-renderer": "^17.0.2", "rimraf": "^3.0.2", diff --git a/res/css/_common.scss b/res/css/_common.scss index a0c7bbbd9ac..0944ee98b9f 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -305,6 +305,73 @@ legend { overflow-y: auto; } +// Styles copied/inspired by GroupLayout, ReplyTile, and EventTile variants. +.mx_Dialog .markdown-body { + font-family: inherit !important; + white-space: normal !important; + line-height: inherit !important; + color: inherit; // inherit the colour from the dark or light theme by default (but not for code blocks) + font-size: $font-14px; + + pre, + code { + font-family: $monospace-font-family !important; + background-color: $codeblock-background-color; + } + + // this selector wrongly applies to code blocks too but we will unset it in the next one + code { + white-space: pre-wrap; // don't collapse spaces in inline code blocks + } + + pre code { + white-space: pre; // we want code blocks to be scrollable and not wrap + + >* { + display: inline; + } + } + + pre { + // have to use overlay rather than auto otherwise Linux and Windows + // Chrome gets very confused about vertical spacing: + // https://github.com/vector-im/vector-web/issues/754 + overflow-x: overlay; + overflow-y: visible; + + &::-webkit-scrollbar-corner { + background: transparent; + } + } +} + +.mx_Dialog .markdown-body h1, +.mx_Dialog .markdown-body h2, +.mx_Dialog .markdown-body h3, +.mx_Dialog .markdown-body h4, +.mx_Dialog .markdown-body h5, +.mx_Dialog .markdown-body h6 { + font-family: inherit !important; + color: inherit; +} + +/* Make h1 and h2 the same size as h3. */ +.mx_Dialog .markdown-body h1, +.mx_Dialog .markdown-body h2 { + font-size: 1.5em; + border-bottom: none !important; // override GFM +} + +.mx_Dialog .markdown-body a { + color: $accent-alt; +} + +.mx_Dialog .markdown-body blockquote { + border-left: 2px solid $blockquote-bar-color; + border-radius: 2px; + padding: 0 10px; +} + .mx_Dialog_fixedWidth { width: 60vw; max-width: 704px; @@ -667,15 +734,6 @@ legend { } } -@define-mixin mx_Settings_fullWidthField { - margin-right: 100px; -} - -@define-mixin mx_Settings_tooltip { - // So it fits in the space provided by the page - max-width: 120px; -} - @define-mixin ProgressBarColour $colour { color: $colour; &::-moz-progress-bar { @@ -772,9 +830,3 @@ legend { mask-repeat: no-repeat; mask-size: contain; } - -@define-mixin ListResetDefault { - list-style: none; - padding: 0; - margin: 0; -} diff --git a/res/css/_components.scss b/res/css/_components.scss index 5d1a51e0b55..40b26c1d1be 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -33,6 +33,7 @@ @import "./structures/_ContextualMenu.scss"; @import "./structures/_FileDropTarget.scss"; @import "./structures/_FilePanel.scss"; +@import "./structures/_GenericDropdownMenu.scss"; @import "./structures/_GenericErrorPage.scss"; @import "./structures/_HeaderButtons.scss"; @import "./structures/_HomePage.scss"; @@ -88,7 +89,6 @@ @import "./views/context_menus/_IconizedContextMenu.scss"; @import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/dialogs/_AddExistingToSpaceDialog.scss"; -@import "./views/dialogs/_Analytics.scss"; @import "./views/dialogs/_AnalyticsLearnMoreDialog.scss"; @import "./views/dialogs/_BugReportDialog.scss"; @import "./views/dialogs/_BulkRedactDialog.scss"; @@ -239,7 +239,6 @@ @import "./views/rooms/_EditMessageComposer.scss"; @import "./views/rooms/_EntityTile.scss"; @import "./views/rooms/_EventTile.scss"; -@import "./views/rooms/_GroupLayout.scss"; @import "./views/rooms/_BubbleLayout.scss"; @import "./views/rooms/_HistoryTile.scss"; @import "./views/rooms/_IRCLayout.scss"; @@ -274,13 +273,13 @@ @import "./views/rooms/_Stickers.scss"; @import "./views/rooms/_ThreadSummary.scss"; @import "./views/rooms/_TopUnreadMessagesBar.scss"; +@import "./views/rooms/_VideoRoomSummary.scss"; @import "./views/rooms/_VoiceRecordComposerTile.scss"; @import "./views/rooms/_WhoIsTypingTile.scss"; @import "./views/settings/_AvatarSetting.scss"; @import "./views/settings/_CrossSigningPanel.scss"; @import "./views/settings/_CryptographyPanel.scss"; @import "./views/settings/_DevicesPanel.scss"; -@import "./views/settings/_E2eAdvancedPanel.scss"; @import "./views/settings/_EmailAddresses.scss"; @import "./views/settings/_FontScalingPanel.scss"; @import "./views/settings/_ImageSizePanel.scss"; @@ -309,7 +308,6 @@ @import "./views/settings/tabs/user/_KeyboardUserSettingsTab.scss"; @import "./views/settings/tabs/user/_LabsUserSettingsTab.scss"; @import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss"; -@import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss"; @import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss"; @import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss"; @import "./views/settings/tabs/user/_SidebarUserSettingsTab.scss"; diff --git a/res/css/components/views/beacon/_BeaconStatus.scss b/res/css/components/views/beacon/_BeaconStatus.scss index 95c41749111..65e51a934ba 100644 --- a/res/css/components/views/beacon/_BeaconStatus.scss +++ b/res/css/components/views/beacon/_BeaconStatus.scss @@ -55,6 +55,11 @@ limitations under the License. white-space: nowrap; overflow: hidden; + + .mx_BeaconStatus_description_status { + text-overflow: ellipsis; + overflow: hidden; + } } .mx_BeaconStatus_expiryTime { diff --git a/res/css/components/views/beacon/_DialogSidebar.scss b/res/css/components/views/beacon/_DialogSidebar.scss index 1989b57c301..1edf5840e34 100644 --- a/res/css/components/views/beacon/_DialogSidebar.scss +++ b/res/css/components/views/beacon/_DialogSidebar.scss @@ -29,32 +29,29 @@ limitations under the License. background-color: $background; box-shadow: 0px 4px 4px $menu-box-shadow-color; -} - -.mx_DialogSidebar_header { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - - flex: 0 0; - margin-bottom: $spacing-16; - - color: $primary-content; -} - -.mx_DialogSidebar_closeButton { - @mixin ButtonResetDefault; -} - -.mx_DialogSidebar_closeButtonIcon { - color: $tertiary-content; - height: 12px; -} -.mx_DialogSidebar_list { - @mixin ListResetDefault; - flex: 1 1 0; - width: 100%; - overflow: auto; + .mx_DialogSidebar_header { + display: flex; + align-items: center; + justify-content: space-between; + + flex: 0 0; + margin-bottom: $spacing-16; + + color: $primary-content; + + .mx_DialogSidebar_closeButtonIcon { + color: $tertiary-content; + height: 12px; + } + } + + .mx_DialogSidebar_list { + list-style: none; + padding: 0; + margin: 0; + flex: 1 1 0; + width: 100%; + overflow: auto; + } } diff --git a/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss b/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss index 04645c965ed..de7dfa035c8 100644 --- a/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss +++ b/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss @@ -15,7 +15,6 @@ limitations under the License. */ .mx_LeftPanelLiveShareWarning { - @mixin ButtonResetDefault; width: 100%; box-sizing: border-box; diff --git a/res/css/components/views/beacon/_RoomLiveShareWarning.scss b/res/css/components/views/beacon/_RoomLiveShareWarning.scss index f82c7f4de40..f85cebe8bbe 100644 --- a/res/css/components/views/beacon/_RoomLiveShareWarning.scss +++ b/res/css/components/views/beacon/_RoomLiveShareWarning.scss @@ -26,6 +26,7 @@ limitations under the License. color: $primary-content; background-color: $system; + cursor: pointer; } .mx_RoomLiveShareWarning_icon { diff --git a/res/css/components/views/location/_ShareType.scss b/res/css/components/views/location/_ShareType.scss index 6b39f1a80a4..4dd49d4470d 100644 --- a/res/css/components/views/location/_ShareType.scss +++ b/res/css/components/views/location/_ShareType.scss @@ -24,6 +24,35 @@ limitations under the License. padding: 60px $spacing-12 $spacing-32; color: $primary-content; + + .mx_ShareType_wrapper_options { + display: flex; + flex-direction: column; + row-gap: $spacing-12; + width: 100%; + margin-top: $spacing-12; + + .mx_ShareType_option { + display: flex; + align-items: center; + justify-content: flex-start; + padding: $spacing-8 $spacing-20; + background: none; + + border: 1px solid $quinary-content; + border-radius: 8px; + + font-size: $font-15px; + font-family: inherit; + line-height: inherit; + color: $primary-content; + + &:hover, + &:focus { + border-color: $accent; + } + } + } } .mx_ShareType_badge { @@ -43,28 +72,6 @@ limitations under the License. text-align: center; } -.mx_ShareType_option { - @mixin ButtonResetDefault; - - width: 100%; - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - padding: $spacing-8 $spacing-20; - margin-top: $spacing-12; - - color: $primary-content; - border: 1px solid $quinary-content; - border-radius: 8px; - - font-size: $font-15px; - - &:hover, &:focus { - border-color: $accent; - } -} - .mx_ShareType_option-icon { height: 40px; width: 40px; diff --git a/res/css/components/views/location/_ZoomButtons.scss b/res/css/components/views/location/_ZoomButtons.scss index 59d52477f97..d6e40e5a49f 100644 --- a/res/css/components/views/location/_ZoomButtons.scss +++ b/res/css/components/views/location/_ZoomButtons.scss @@ -18,28 +18,28 @@ limitations under the License. position: absolute; bottom: $spacing-32; right: $spacing-24; -} - -.mx_ZoomButtons_button { - @mixin ButtonResetDefault; - - margin-top: $spacing-8; - border-radius: 4px; display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - - height: 24px; - width: 24px; - - background: $background; - box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.25); -} - -.mx_ZoomButtons_icon { - height: 10px; - width: 10px; - - color: $primary-content; + flex-direction: column; + row-gap: $spacing-8; + + .mx_ZoomButtons_button { + $ZoomButtons_button-size: 24px; + + border-radius: 4px; + display: flex; + justify-content: center; + align-items: center; + height: $ZoomButtons_button-size; + width: $ZoomButtons_button-size; + background: $background; + box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.25); + + .mx_ZoomButtons_icon { + $ZoomButtons_icon-size: 10px; + + height: $ZoomButtons_icon-size; + width: $ZoomButtons_icon-size; + color: $primary-content; + } + } } diff --git a/res/css/components/views/messages/_MBeaconBody.scss b/res/css/components/views/messages/_MBeaconBody.scss index 5654f14a057..aed1cb44d3d 100644 --- a/res/css/components/views/messages/_MBeaconBody.scss +++ b/res/css/components/views/messages/_MBeaconBody.scss @@ -17,7 +17,8 @@ limitations under the License. .mx_MBeaconBody { position: relative; height: 220px; - width: 325px; + max-width: 325px; + width: 100%; border-radius: $timeline-image-border-radius; overflow: hidden; @@ -50,3 +51,8 @@ limitations under the License. max-width: 100%; width: 450px; } + +.mx_ReplyTile .mx_MBeaconBody { + // Prevent clicking a beacon within a reply + pointer-events: none; +} diff --git a/res/css/rethemendex.sh b/res/css/rethemendex.sh index 13be73f9a97..e6fb4b80074 100755 --- a/res/css/rethemendex.sh +++ b/res/css/rethemendex.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env sh cd `dirname $0` diff --git a/res/css/structures/_FilePanel.scss b/res/css/structures/_FilePanel.scss index 634ce9604cc..f8d6469568d 100644 --- a/res/css/structures/_FilePanel.scss +++ b/res/css/structures/_FilePanel.scss @@ -55,10 +55,6 @@ limitations under the License. } } -.mx_FilePanel .mx_EventTile .mx_MImageBody { - margin-right: 0px; -} - .mx_FilePanel .mx_EventTile .mx_MFileBody { line-height: 2.4rem; } diff --git a/res/css/structures/_GenericDropdownMenu.scss b/res/css/structures/_GenericDropdownMenu.scss new file mode 100644 index 00000000000..e56ddbcefc8 --- /dev/null +++ b/res/css/structures/_GenericDropdownMenu.scss @@ -0,0 +1,123 @@ +/* +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_GenericDropdownMenu_button { + padding: 3px 4px 3px 8px; + border-radius: 4px; + line-height: 1.5; + user-select: none; + font-size: $font-12px; + color: $secondary-content; +} + +.mx_GenericDropdownMenu_button:hover, +.mx_GenericDropdownMenu_button[aria-expanded=true] { + background: $quinary-content; +} + +.mx_GenericDropdownMenu_button::before { + content: ""; + width: 18px; + height: 18px; + background: currentColor; + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); + mask-size: 100%; + mask-repeat: no-repeat; + float: right; +} + +.mx_ContextualMenu_wrapper.mx_GenericDropdownMenu_wrapper { + .mx_ContextualMenu { + position: initial; + + font-size: $font-12px; + color: $secondary-content; + padding-top: 10px; + padding-bottom: 10px; + + border: 1px solid $quinary-content; + box-shadow: 0 1px 3px rgba(23, 25, 28, 0.05); + } + + .mx_ContextualMenu_chevron_top { + left: auto; + right: 22px; + border-bottom-color: $quinary-content; + + &::after { + content: ""; + border: inherit; + border-bottom-color: $menu-bg-color; + position: absolute; + top: 1px; + left: -8px; + } + } + + .mx_GenericDropdownMenu_divider { + display: block; + height: 0; + margin-left: 4px; + margin-right: 19px; + border-top: 1px solid $quinary-content; + } + + .mx_GenericDropdownMenu_Option { + display: flex; + flex-grow: 1; + flex-direction: row; + align-items: center; + padding: 10px 20px 10px 30px; + position: relative; + + > .mx_GenericDropdownMenu_Option--label { + display: flex; + flex-direction: column; + flex-grow: 1; + + margin: 0; + + span:first-child { + color: $primary-content; + font-weight: $font-semi-bold; + } + } + + &.mx_GenericDropdownMenu_Option--header > .mx_GenericDropdownMenu_Option--label span:first-child { + font-size: $font-15px; + } + + &.mx_GenericDropdownMenu_Option--item { + &:hover { + background-color: $menu-selected-color; + } + + &[aria-checked="true"]::before { + content: ""; + width: 12px; + height: 12px; + margin-left: -20px; + margin-right: 8px; + mask-image: url("$(res)/img/feather-customised/check.svg"); + mask-size: 100%; + mask-repeat: no-repeat; + background-color: $primary-content; + display: inline-block; + vertical-align: middle; + } + } + } +} diff --git a/res/css/structures/_RoomDirectory.scss b/res/css/structures/_RoomDirectory.scss index 62d12965e41..f2d16b6d847 100644 --- a/res/css/structures/_RoomDirectory.scss +++ b/res/css/structures/_RoomDirectory.scss @@ -54,8 +54,9 @@ limitations under the License. flex: 1 !important; } -.mx_RoomDirectory_listheader .mx_NetworkDropdown { - flex: 0 0 200px; +.mx_RoomDirectory_listheader .mx_GenericDropdownMenu_button { + margin: 0 9px 0 auto; + width: fit-content; } .mx_RoomDirectory_tableWrapper { diff --git a/res/css/structures/_RoomSearch.scss b/res/css/structures/_RoomSearch.scss index 301e14942e4..218b76a8fd6 100644 --- a/res/css/structures/_RoomSearch.scss +++ b/res/css/structures/_RoomSearch.scss @@ -38,63 +38,11 @@ limitations under the License. margin-bottom: 2px; } - .mx_RoomSearch_input { - border: none !important; // !important to override default app-wide styles - flex: 1 !important; // !important to override default app-wide styles - color: $primary-content !important; // !important to override default app-wide styles - padding: 0; - height: 100%; - width: 100%; - - &:not(.mx_RoomSearch_inputExpanded)::placeholder { - color: $tertiary-content !important; // !important to override default app-wide styles - } - } - - .mx_RoomSearch_input, .mx_RoomSearch_spotlightTriggerText { font-size: $font-12px; line-height: $font-16px; } - &.mx_RoomSearch_hasQuery { - border-color: $secondary-content; - } - - &.mx_RoomSearch_focused { - border-color: $accent; - } - - &.mx_RoomSearch_focused, &.mx_RoomSearch_hasQuery { - background-color: $background; - - .mx_RoomSearch_clearButton { - width: 16px; - height: 16px; - margin-right: 8px; - background-color: $quinary-content; - border-radius: 50%; - position: relative; - - &::before { - content: ""; - position: absolute; - width: inherit; - height: inherit; - mask-image: url('$(res)/img/feather-customised/x.svg'); - mask-position: center; - mask-size: 12px; - mask-repeat: no-repeat; - background-color: $secondary-content; - } - } - } - - .mx_RoomSearch_clearButton { - width: 0; - height: 0; - } - &.mx_RoomSearch_minimized { height: 32px; min-height: 32px; diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index eba8ae8f6e8..b19173a3139 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -272,6 +272,10 @@ hr.mx_RoomView_myReadMarker { .mx_MatrixChat_useCompactLayout { .mx_RoomView_MessageList { margin-bottom: 4px; + + h2 { + margin-top: 6px; // TODO: Use a spacing variable + } } .mx_RoomView_statusAreaBox { diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index ae8fc0475b6..1215c78717b 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -22,10 +22,14 @@ limitations under the License. .mx_AccessibleButton { display: flex; align-items: center; - } - .mx_UserMenu_userAvatar { - position: relative; + .mx_UserMenu_userAvatar { + position: relative; + + .mx_BaseAvatar { + pointer-events: none; // makes the avatar non-draggable + } + } } .mx_UserMenu_name { @@ -35,13 +39,6 @@ limitations under the License. margin-left: 10px; } - .mx_UserMenu_cutout .mx_BaseAvatar { - mask-image: url('$(res)/img/element-icons/roomlist/dnd-avatar-mask.svg'); - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - } - // SC: Scale avatar well with the font size .mx_BaseAvatar_image, .mx_BaseAvatar_initial { width: 3.2rem !important; diff --git a/res/css/structures/_ViewSource.scss b/res/css/structures/_ViewSource.scss index 3dc3e21489e..c063eeb49cd 100644 --- a/res/css/structures/_ViewSource.scss +++ b/res/css/structures/_ViewSource.scss @@ -14,37 +14,38 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ViewSource_separator { - clear: both; - border-bottom: 1px solid #e5e5e5; - padding-top: 0.7em; - padding-bottom: 0.7em; -} - -.mx_ViewSource_heading { - font-size: $font-17px; - font-weight: 400; - color: $primary-content; - margin-top: 0.7em; -} - -.mx_ViewSource pre { - text-align: left; - font-size: $font-12px; - padding: 0.5em 1em 0.5em 1em; - word-wrap: break-word; - white-space: pre-wrap; - overflow-wrap: anywhere; -} - -.mx_ViewSource_details { - margin-top: 0.8em; -} - -.mx_ViewSource_container { - max-width: calc(100% - 24px); -} - -.mx_ViewSource_container .mx_CopyableText_border { - width: 100%; +.mx_ViewSource { + pre { + font-size: $font-12px; + padding: 0.5em 1em; + word-wrap: break-word; + white-space: pre-wrap; + overflow-wrap: anywhere; + } + + .mx_ViewSource_header { + border-bottom: 1px solid $quinary-content; + padding-bottom: $spacing-12; + margin-bottom: $spacing-12; + + .mx_CopyableText { + word-break: break-all; + } + } + + .mx_ViewSource_heading { + font-size: $font-17px; + font-weight: 400; + color: $primary-content; + margin-top: $spacing-12; + } + + .mx_ViewSource_details { + margin-top: $spacing-12; + } + + .mx_CopyableText_border { + box-sizing: border-box; + width: 100%; + } } diff --git a/res/css/structures/auth/_SetupEncryptionBody.scss b/res/css/structures/auth/_SetupEncryptionBody.scss index 1999fb581db..52651ec2a98 100644 --- a/res/css/structures/auth/_SetupEncryptionBody.scss +++ b/res/css/structures/auth/_SetupEncryptionBody.scss @@ -17,9 +17,11 @@ limitations under the License. .mx_SetupEncryptionBody_reset { color: $light-fg-color; margin-top: $font-14px; -} -.mx_SetupEncryptionBody_reset_link { - @mixin ButtonResetDefault; - color: $alert; + .mx_SetupEncryptionBody_reset_link { + &.mx_AccessibleButton_kind_link_inline { + padding: 0; + color: $alert; + } + } } diff --git a/res/css/views/audio_messages/_PlaybackContainer.scss b/res/css/views/audio_messages/_PlaybackContainer.scss index 4999980beaf..ebfcf229b08 100644 --- a/res/css/views/audio_messages/_PlaybackContainer.scss +++ b/res/css/views/audio_messages/_PlaybackContainer.scss @@ -52,16 +52,56 @@ limitations under the License. padding-left: 8px; // isolate from recording circle / play control } - // For timeline-rendered playback, mirror the values for where the clock is in - // the waveform version. - .mx_SeekBar { + .mx_RecordingPlayback_timelineLayoutMiddle { margin-left: 8px; margin-right: 6px; + position: relative; + display: inline-block; + flex: 1; + height: 30px; // same height as mx_Waveform, needed for automatic vertical centering + .mx_Waveform { + left: 0; + top: 0; + } + + .mx_SeekBar { + position: absolute; + left: 0; + height: 30px; + top: -2px; // visually vertically centered + + // Hide the hairline progress bar since we're at 100% height. Need to have distinct rules + // because CSS is weird. + background: none; + &::before { + background: none; + } + &::-moz-range-progress { + background: none; + } + + // Make the thumb easier to see. Like the SeekBar original styles, these need to be + // distinct. We make it transparent so it doesn't show up on the UI, but also larger + // so it's easier to grab by mouse users in some browsers. Most browsers let the user + // move and drag the thumb regardless of hitting the thumb, however. + &::-webkit-slider-thumb { + width: 10px; + height: 10px; + background-color: transparent; + } + &::-moz-range-thumb { + width: 10px; + height: 10px; + background-color: transparent; + } + } + + // For timeline-rendered playback, the clock is on the other side of the waveform. & + .mx_Clock { text-align: right; - // Take the padding off the clock because it's accounted for in the seek bar + // Take the padding off the clock because it's accounted for by the `timelineLayoutMiddle` padding: 0; } } diff --git a/res/css/views/avatars/_BaseAvatar.scss b/res/css/views/avatars/_BaseAvatar.scss index 16261f000e3..802a4235c13 100644 --- a/res/css/views/avatars/_BaseAvatar.scss +++ b/res/css/views/avatars/_BaseAvatar.scss @@ -52,3 +52,12 @@ limitations under the License. vertical-align: top; background-color: $background; } + +// Percy screenshot test specific CSS +@media only percy { + .mx_BaseAvatar_initial, + .mx_BaseAvatar_initial + .mx_BaseAvatar_image { + // Stick the default room avatar colour, so it doesn't cause a false diff on the screenshot + background-color: $username-variant2-color !important; + } +} diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss index d54dd4a4c82..642e08f3f41 100644 --- a/res/css/views/beta/_BetaCard.scss +++ b/res/css/views/beta/_BetaCard.scss @@ -20,6 +20,7 @@ limitations under the License. background-color: $system; border-radius: 8px; box-sizing: border-box; + color: $secondary-content; .mx_BetaCard_columns { display: flex; @@ -45,14 +46,13 @@ limitations under the License. .mx_BetaCard_caption { font-size: $font-15px; line-height: $font-20px; - color: $secondary-content; } .mx_BetaCard_buttons { display: flex; flex-wrap: wrap-reverse; - gap: 12px; - margin: 20px auto; + gap: $spacing-12; + margin: $spacing-20 auto 0; .mx_AccessibleButton { padding: 7px 40px; @@ -66,10 +66,16 @@ limitations under the License. } } - .mx_BetaCard_disclaimer { + .mx_BetaCard_refreshWarning { + margin-top: $spacing-8; + font-size: $font-10px; + text-align: center; + } + + .mx_BetaCard_faq { + margin-top: $spacing-20; font-size: $font-12px; line-height: $font-15px; - color: $secondary-content; > h4 { margin: 12px 0 0; @@ -105,7 +111,6 @@ limitations under the License. margin-top: 4px; font-size: $font-12px; line-height: $font-15px; - color: $secondary-content; } } } diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index 7073f0431b7..449b0ca47e7 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -16,18 +16,24 @@ limitations under the License. .mx_DevtoolsDialog_wrapper { .mx_Dialog { - height: 100%; + display: flex; + flex-direction: column; } .mx_Dialog_fixedWidth { - overflow-y: hidden; - height: 100%; + display: flex; + flex-direction: column; + min-height: 0; + max-height: 100%; + + .mx_Dialog_buttons button { + margin-bottom: 0; + } } } .mx_DevTools_content { overflow-y: auto; - height: calc(100% - 124px); // 58px for buttons + 50px for header + 8px margin around } .mx_DevTools_RoomStateExplorer_query { diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss index f648d315f59..c73852abb94 100644 --- a/res/css/views/dialogs/_InviteDialog.scss +++ b/res/css/views/dialogs/_InviteDialog.scss @@ -14,8 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_InviteDialog_flexWrapper .mx_Dialog { + display: flex; + flex-direction: column; +} + .mx_InviteDialog_transferWrapper .mx_Dialog { - padding-bottom: 16px; + padding-bottom: $spacing-16; } .mx_InviteDialog_addressBar { @@ -23,7 +28,7 @@ limitations under the License. flex-direction: row; // Right margin for the design. We could apply this to the whole dialog, but then the scrollbar // for the user section gets weird. - margin: 8px 45px 0 0; + margin: $spacing-8 45px 0 0; // TODO: Use a spacing variable .mx_InviteDialog_editor { flex: 1; @@ -31,25 +36,25 @@ limitations under the License. background-color: $header-panel-bg-color; border-radius: $border-radius-4px; min-height: 25px; - padding-left: 8px; + padding-inline-start: $spacing-8; overflow-x: hidden; overflow-y: auto; display: flex; flex-wrap: wrap; .mx_InviteDialog_userTile { - margin: 6px 6px 0 0; + margin: 6px 6px 0 0; // TODO: Use a spacing variable display: inline-block; min-width: max-content; // prevent manipulation by flexbox } // overrides bunch of our default text input styles > input[type="text"] { - margin: 6px 0 !important; + margin: 6px 0 !important; // TODO: Use a spacing variable height: 24px; line-height: $font-24px; font-size: $font-14px; - padding-left: 12px; + padding-inline-start: $spacing-12; border: 0 !important; outline: 0 !important; resize: none; @@ -62,7 +67,7 @@ limitations under the License. .mx_InviteDialog_goButton { min-width: 48px; - margin-left: 10px; + margin-inline-start: 10px; // TODO: Use a spacing variable height: 25px; line-height: $font-25px; } @@ -72,7 +77,7 @@ limitations under the License. // Width and height are required to trick the layout engine. width: 20px; height: 20px; - margin-left: 5px; + margin-inline-start: 5px; // TODO: Use a spacing variable display: inline-block; vertical-align: middle; } @@ -80,7 +85,7 @@ limitations under the License. } .mx_InviteDialog_section { - padding-bottom: 4px; + padding-bottom: $spacing-4; h3 { font-size: $font-12px; @@ -98,13 +103,13 @@ limitations under the License. } .mx_InviteDialog_section_showMore { - margin: 7px 18px; + margin: 7px 18px; // TODO: Use a spacing variable display: block; } } .mx_InviteDialog_section_hidden_suggestions_disclaimer { - padding: 8px 0 16px 0; + padding: $spacing-8 0 $spacing-16 0; font-size: $font-14px; > span { @@ -121,15 +126,16 @@ limitations under the License. border-top: 1px solid $input-border-color; > h3 { - margin: 12px 0; + margin: $spacing-12 0; font-size: $font-12px; color: $muted-fg-color; font-weight: bold; text-transform: uppercase; } - .mx_CopyableText { + .mx_CopyableText.mx_CopyableText_border { width: unset; // full width + margin-bottom: 0; > a { text-decoration: none; @@ -140,97 +146,9 @@ limitations under the License. } } -.mx_InviteDialog_roomTile { - cursor: pointer; - padding: 5px 10px; - - &:hover { - background-color: $header-panel-bg-color; - border-radius: $border-radius-4px; - } - - * { - vertical-align: middle; - } - - .mx_InviteDialog_roomTile_avatarStack { - display: inline-block; - position: relative; - width: 36px; - height: 36px; - - & > * { - position: absolute; - top: 0; - left: 0; - } - } - - .mx_InviteDialog_roomTile_selected { - width: 36px; - height: 36px; - border-radius: 36px; - background-color: $username-variant1-color; - display: inline-block; - position: relative; - - &::before { - content: ""; - width: 24px; - height: 24px; - grid-column: 1; - grid-row: 1; - mask-image: url("$(res)/img/feather-customised/check.svg"); - mask-size: 100%; - mask-repeat: no-repeat; - position: absolute; - top: 6px; // 50% - left: 6px; // 50% - background-color: #ffffff; // this is fine without a var because it's for both themes - } - } - - .mx_InviteDialog_roomTile_nameStack { - display: inline-block; - overflow: hidden; - } - - .mx_InviteDialog_roomTile_name { - font-weight: 600; - font-size: $font-14px; - color: $primary-content; - margin-left: 7px; - } - - .mx_InviteDialog_roomTile_userId { - font-size: $font-12px; - color: $muted-fg-color; - margin-left: 7px; - } - - .mx_InviteDialog_roomTile_name, - .mx_InviteDialog_roomTile_userId { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .mx_InviteDialog_roomTile_time { - text-align: right; - font-size: $font-12px; - color: $muted-fg-color; - float: right; - line-height: $font-36px; // Height of the avatar to keep the time vertically aligned - } - - .mx_InviteDialog_roomTile_highlight { - font-weight: 900; - } -} - // Many of these styles are stolen from mx_UserPill, but adjusted for the invite dialog. .mx_InviteDialog_userTile { - margin-right: 8px; + margin-inline-end: $spacing-8; .mx_InviteDialog_userTile_pill { background-color: $username-variant1-color; @@ -238,18 +156,17 @@ limitations under the License. display: inline-block; height: 24px; line-height: $font-24px; - padding-left: 8px; - padding-right: 8px; + padding-inline: $spacing-8; color: #ffffff; // this is fine without a var because it's for both themes - .mx_InviteDialog_userTile_avatar { + .mx_SearchResultAvatar { border-radius: 20px; position: relative; left: -5px; top: 2px; } - img.mx_InviteDialog_userTile_avatar { + img.mx_SearchResultAvatar { vertical-align: top; } @@ -257,137 +174,137 @@ limitations under the License. vertical-align: top; } - .mx_InviteDialog_userTile_threepidAvatar { + .mx_SearchResultAvatar_threepidAvatar { background-color: #ffffff; // this is fine without a var because it's for both themes } } .mx_InviteDialog_userTile_remove { display: inline-block; - margin-left: 4px; + margin-inline-start: $spacing-4; } } .mx_InviteDialog_other { // Prevent the dialog from jumping around randomly when elements change. + display: flex; + flex-direction: column; height: 600px; + overflow: hidden; .mx_InviteDialog_addressBar { - margin-right: 0; + margin-inline-end: 0; } .mx_InviteDialog_userSections { - height: calc(100% - 115px); // mx_InviteDialog's height minus some for the upper and lower elements - padding-right: 0; + flex-grow: 1; + padding-inline-end: 0; .mx_InviteDialog_section { padding-bottom: 0; - margin-top: 12px; - } - } - - .mx_InviteDialog_hasFooter { - .mx_InviteDialog_userSections { - height: calc(100% - 175px); // For displaying an invite link on the footer of the dialog + margin-top: $spacing-12; } } } .mx_InviteDialog_content { - height: calc(100% - 36px); // full height minus the size of the header + display: flex; + flex-direction: column; + flex-grow: 1; overflow: hidden; } .mx_InviteDialog_transfer { - width: 496px; - height: 466px; - flex-direction: column; + width: auto; .mx_InviteDialog_content { - flex-direction: column; + width: 496px; + height: 430px; + overflow: visible; .mx_TabbedView { - height: calc(100% - 60px); + display: flex; + flex-direction: column; + flex-shrink: 1; + flex-grow: 1; + min-height: 0; + + .mx_TabbedView_tabPanel { + flex-direction: column; + + .mx_TabbedView_tabPanelContent { + display: flex; + flex-direction: column; + } + } } - overflow: visible; } .mx_InviteDialog_addressBar { - margin-top: 8px; + margin-top: $spacing-8; } input[type="checkbox"] { - margin-right: 8px; + margin-inline-end: $spacing-8; } } .mx_InviteDialog_userSections { - margin-top: 4px; + margin-top: $spacing-4; overflow-y: auto; - padding: 0 45px 4px 0; -} - -.mx_InviteDialog_hasFooter .mx_InviteDialog_userSections { - height: calc(100% - 175px); + padding: 0 45px $spacing-4 0; // TODO: Use a spacing variable } .mx_InviteDialog_helpText { margin: 0; -} - -.mx_InviteDialog_helpText .mx_AccessibleButton_kind_link { - padding: 0; -} -.mx_InviteDialog_dialPad .mx_InviteDialog_dialPadField { - border-top: 0; - border-left: 0; - border-right: 0; - border-radius: 0; - margin-top: 0; - border-color: $quaternary-content; - - input { - font-size: 18px; - font-weight: 600; - padding-top: 0; + .mx_AccessibleButton_kind_link { + padding: 0; } } -.mx_InviteDialog_dialPad .mx_InviteDialog_dialPadField:focus-within { - border-color: $accent; -} - -.mx_InviteDialog_dialPadField .mx_Field_postfix { - /* Remove border separator between postfix and field content */ - border-left: none; -} - .mx_InviteDialog_dialPad { width: 224px; - margin-top: 16px; - margin-left: auto; - margin-right: auto; -} + margin-top: $spacing-16; + margin-inline: auto; + + .mx_InviteDialog_dialPadField { + border-top: 0; + border-inline: 0; + border-radius: 0; + margin-top: 0; + border-color: $quaternary-content; + + &:focus-within { + border-color: $accent; + } + + input { + font-size: 18px; + font-weight: 600; + padding-top: 0; + } -.mx_InviteDialog_dialPad .mx_DialPad { - row-gap: 16px; - column-gap: 48px; + .mx_Field_postfix { + /* Remove border separator between postfix and field content */ + border-left: none; + } + } - margin-left: auto; - margin-right: auto; + .mx_DialPad { + row-gap: $spacing-16; + column-gap: 48px; // TODO: Use a spacing variable + margin-inline: auto; + } } .mx_InviteDialog_transferConsultConnect { - padding-top: 16px; - /* This wants a drop shadow the full width of the dialog, so relative-position it - * and make it wider, then compensate with padding + padding-top: $spacing-16; + /* This wants a drop shadow the full width of the dialog, so use negative margin to make it full width, + * then compensate with padding */ - position: relative; - width: 496px; - left: -24px; - padding-left: 24px; - padding-right: 24px; + padding-inline: $spacing-24; + margin-inline: calc(-1 * $spacing-24); border-top: 1px solid $quinary-content; display: flex; @@ -396,7 +313,7 @@ limitations under the License. } .mx_InviteDialog_transferConsultConnect_pushRight { - margin-left: auto; + margin-inline-start: auto; } .mx_InviteDialog_userDirectoryIcon::before { @@ -407,6 +324,125 @@ limitations under the License. mask-image: url('$(res)/img/voip/tab-dialpad.svg'); } +.mx_InviteDialog_tile { + cursor: pointer; + display: grid; + gap: $spacing-8 $spacing-12; + align-items: center; + + &.mx_InviteDialog_tile--room { + grid-template-columns: min-content auto auto; // mx_InviteDialog_tile_avatarStack, mx_InviteDialog_tile_nameStack, time + padding: $spacing-4 $spacing-8; + + &:hover { + background-color: $header-panel-bg-color; + border-radius: 4px; + } + + .mx_InviteDialog_tile--room_selected { + border-radius: 36px; + background-color: $username-variant1-color; + + &::before { + content: ""; + width: 24px; + height: 24px; + grid-column: 1; + grid-row: 1; + mask-image: url("$(res)/img/feather-customised/check.svg"); + mask-size: 100%; + mask-repeat: no-repeat; + position: absolute; + top: 6px; // 50% + left: 6px; // 50% + background-color: #ffffff; // this is fine without a var because it's for both themes + } + } + + .mx_InviteDialog_tile--room_time { + margin-inline-start: auto; + width: max-content; + font-size: $font-12px; + color: $muted-fg-color; + } + + .mx_InviteDialog_tile--room_highlight { + font-weight: 900; + } + } + + &.mx_InviteDialog_tile--inviterError { + grid-template-columns: max-content auto; // max-content = avatar width + margin-bottom: $spacing-24; + + &:last-child { + margin-bottom: 0; + } + + .mx_InviteDialog_tile--inviterError_errorText { + grid-row-start: 2; + grid-column-start: 2; + + font-size: $font-15px; + color: $alert; + } + } + + * { + vertical-align: middle; + } + + .mx_InviteDialog_tile_avatarStack, + .mx_InviteDialog_tile--room_selected { + width: 36px; + height: 36px; + display: inline-block; + position: relative; + } + + .mx_InviteDialog_tile_avatarStack { + grid-row-start: 1; + grid-column-start: 1; + + & > * { + position: absolute; + top: 0; + left: 0; + } + } + + .mx_InviteDialog_tile_nameStack { + grid-row-start: 1; + grid-column-start: 2; + + display: flex; + flex-flow: column; + align-self: center; + align-items: baseline; + gap: 2px 0; + overflow: hidden; + + .mx_InviteDialog_tile_nameStack_name, + .mx_InviteDialog_tile_nameStack_userId { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; + } + + .mx_InviteDialog_tile_nameStack_name { + font-size: $font-15px; + font-weight: $font-semi-bold; + color: $primary-content; + } + + .mx_InviteDialog_tile_nameStack_userId { + font-size: $font-12px; + color: $muted-fg-color; + } + } +} + .mx_InviteDialog_multiInviterError { > h4 { font-size: $font-15px; @@ -414,38 +450,8 @@ limitations under the License. color: $secondary-content; font-weight: normal; } - - > div { - .mx_InviteDialog_multiInviterError_entry { - margin-bottom: 24px; - - .mx_InviteDialog_multiInviterError_entry_userProfile { - .mx_InviteDialog_multiInviterError_entry_name { - margin-left: 6px; - font-size: $font-15px; - line-height: $font-24px; - font-weight: $font-semi-bold; - color: $primary-content; - } - - .mx_InviteDialog_multiInviterError_entry_userId { - margin-left: 6px; - font-size: $font-12px; - line-height: $font-15px; - color: $tertiary-content; - } - } - - .mx_InviteDialog_multiInviterError_entry_error { - margin-left: 32px; - font-size: $font-15px; - line-height: $font-24px; - color: $alert; - } - } - } } .mx_InviteDialog_identityServer { - margin-top: 1em; + margin-top: 1em; // TODO: Use a spacing variable } diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss index 1d7759fe2bb..242605144a6 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.scss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -14,10 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_MessageEditHistoryDialog .mx_Dialog_header > .mx_Dialog_title { - text-align: center; -} - .mx_MessageEditHistoryDialog { display: flex; flex-direction: column; @@ -56,17 +52,31 @@ limitations under the License. } .mx_EventTile { + padding-top: 0 !important; // Override mx_EventTile:not([data-layout=bubble]) + .mx_MessageTimestamp { position: absolute; } - } - .mx_EventTile_line, .mx_EventTile_content { - margin-right: 0px; + .mx_EventTile_line { + padding-top: 1px; + padding-bottom: 3px; + + line-height: $font-22px; + + .mx_EventTile_content { + margin-right: 0px; + } + } } .mx_MessageActionBar .mx_AccessibleButton { - font-size: $font-10px; - padding: 0 8px; + display: flex; + align-items: center; + + padding-inline-start: $spacing-8; + padding-inline-end: $spacing-8; + + font-size: $font-15px; } } diff --git a/res/css/views/dialogs/_SpotlightDialog.scss b/res/css/views/dialogs/_SpotlightDialog.scss index 9f78d905b52..ebd5324be13 100644 --- a/res/css/views/dialogs/_SpotlightDialog.scss +++ b/res/css/views/dialogs/_SpotlightDialog.scss @@ -24,7 +24,7 @@ limitations under the License. #mx_SpotlightDialog_keyboardPrompt { position: absolute; - padding: 8px; + padding: $spacing-8; border-radius: 8px; background-color: $background; top: -60px; // relative to the top of the modal @@ -36,8 +36,8 @@ limitations under the License. > span > div { display: inline-block; - padding: 2px 4px; - margin: 0 4px; + padding: 2px $spacing-4; // TODO: Use a spacing variable + margin: 0 $spacing-4; border-radius: 6px; background-color: $quinary-content; vertical-align: middle; @@ -58,9 +58,72 @@ limitations under the License. .mx_SpotlightDialog_searchBox { margin: 0; border: none; - padding: 12px 16px; + padding: $spacing-12 $spacing-16; border-bottom: 1px solid $system; + > .mx_SpotlightDialog_filter { + display: flex; + align-content: center; + align-items: center; + border-radius: 8px; + margin-right: $spacing-8; + background-color: $quinary-content; + vertical-align: middle; + color: $primary-content; + position: relative; + padding: $spacing-4 $spacing-8 $spacing-4 37px; // TODO: Use a spacing variable + + &::before { + background-color: $secondary-content; + content: ""; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + width: 18px; + height: 18px; + position: absolute; + left: $spacing-8; + top: 50%; + transform: translateY(-50%); + } + + &.mx_SpotlightDialog_filterPeople::before { + mask-image: url('$(res)/img/element-icons/room/members.svg'); + } + + &.mx_SpotlightDialog_filterPublicRooms::before { + mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + } + + .mx_SpotlightDialog_filter--close { + position: relative; + display: inline-block; + width: 16px; + height: 16px; + background: $system; + border-radius: 8px; + margin-left: $spacing-8; + text-align: center; + line-height: 16px; + color: $secondary-content; + + &::before { + background-color: $secondary-content; + content: ""; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + width: 8px; + height: 8px; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + mask-image: url("$(res)/img/cancel-small.svg"); + } + } + } + > input { display: block; box-sizing: border-box; @@ -73,25 +136,47 @@ limitations under the License. font-size: $font-15px; line-height: $font-24px; } + + > .mx_Spinner { + flex-grow: 0; + width: unset; + height: unset; + margin-left: $spacing-16; + } } #mx_SpotlightDialog_content { - margin: 16px; height: 100%; overflow-y: auto; + padding: $spacing-16; .mx_SpotlightDialog_section { - > h4 { + > h4, > .mx_SpotlightDialog_sectionHeader > h4 { font-weight: $font-semi-bold; font-size: $font-12px; line-height: $font-15px; color: $secondary-content; - margin-top: 0; - margin-bottom: 8px; + margin: 0; + } + + > h4 { + margin-bottom: $spacing-8; + } + + .mx_SpotlightDialog_sectionHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: $spacing-8; + + .mx_SpotlightDialog_options { + display: flex; + gap: $spacing-4; + } } & + .mx_SpotlightDialog_section { - margin-top: 24px; + margin-top: $spacing-24; } } @@ -103,9 +188,9 @@ limitations under the License. margin-right: 1px; // occlude the 1px visible of the very next tile to prevent it looking broken } - .mx_AccessibleButton { + .mx_SpotlightDialog_option { border-radius: 8px; - padding: 4px; + padding: $spacing-4; color: $primary-content; font-size: $font-12px; line-height: $font-15px; @@ -119,11 +204,11 @@ limitations under the License. text-overflow: ellipsis; .mx_DecoratedRoomAvatar { - margin: 0 9px 4px; // maintain centering + margin: 0 9px $spacing-4; // maintain centering } - & + .mx_AccessibleButton { - margin-left: 16px; + & + .mx_SpotlightDialog_option { + margin-left: $spacing-16; } &:hover, &[aria-selected=true] { @@ -134,9 +219,10 @@ limitations under the License. .mx_SpotlightDialog_results, .mx_SpotlightDialog_recentSearches, - .mx_SpotlightDialog_otherSearches { - .mx_AccessibleButton { - padding: 6px 4px; + .mx_SpotlightDialog_otherSearches, + .mx_SpotlightDialog_hiddenResults { + .mx_SpotlightDialog_option { + padding: 6px $spacing-4; // TODO: Use a spacing variable border-radius: 8px; font-size: $font-15px; line-height: $font-24px; @@ -148,10 +234,24 @@ limitations under the License. text-overflow: ellipsis; overflow: hidden; + &.mx_SpotlightDialog_result_multiline { + align-items: start; + + .mx_AccessibleButton { + padding: $spacing-4 $spacing-20; + margin: 2px $spacing-4; // TODO: Use a spacing variable + } + + .mx_SpotlightDialog_enterPrompt { + margin-top: 9px; // TODO: Use a spacing variable + margin-right: $spacing-8; + } + } + > .mx_SpotlightDialog_metaspaceResult, > .mx_DecoratedRoomAvatar, > .mx_BaseAvatar { - margin-right: 8px; + margin-right: $spacing-8; width: 24px; height: 24px; @@ -161,8 +261,47 @@ limitations under the License. } } + .mx_SpotlightDialog_result_publicRoomDetails { + display: flex; + flex-direction: column; + flex-grow: 1; + min-width: 0; + + .mx_SpotlightDialog_result_publicRoomHeader { + display: flex; + flex-direction: row; + line-height: $font-24px; + margin-right: $spacing-8; + + .mx_SpotlightDialog_result_publicRoomName { + color: $primary-content; + font-size: $font-15px; + overflow: hidden; + text-overflow: ellipsis; + } + .mx_SpotlightDialog_result_publicRoomAlias { + color: $tertiary-content; + font-size: $font-12px; + margin-left: $spacing-8; + overflow: hidden; + text-overflow: ellipsis; + } + } + .mx_SpotlightDialog_result_publicRoomDescription { + color: $secondary-content; + font-size: $font-12px; + white-space: normal; + word-wrap: break-word; + line-height: $font-20px; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + overflow: hidden; + } + } + .mx_NotificationBadge { - margin-left: 8px; + margin-left: $spacing-8; } &:hover, &[aria-selected=true] { @@ -175,11 +314,44 @@ limitations under the License. } } + .mx_SpotlightDialog_inviteLink, + .mx_SpotlightDialog_createRoom { + margin-top: $spacing-8; + + .mx_AccessibleButton { + position: relative; + margin: 0; + padding: 3px $spacing-8 3px $spacing-28; // TODO: Use a spacing variable + + &::before { + content: ""; + display: block; + position: absolute; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + left: $spacing-8; + width: 16px; + height: 16px; + background: $accent; + } + } + } + + .mx_SpotlightDialog_inviteLink .mx_AccessibleButton::before { + mask-image: url("$(res)/img/element-icons/link.svg"); + } + + .mx_SpotlightDialog_createRoom .mx_AccessibleButton::before { + mask-image: url("$(res)/img/element-icons/roomlist/hash.svg"); + } + .mx_SpotlightDialog_otherSearches { .mx_SpotlightDialog_startChat, .mx_SpotlightDialog_joinRoomAlias, - .mx_SpotlightDialog_explorePublicRooms { - padding-left: 32px; + .mx_SpotlightDialog_explorePublicRooms, + .mx_SpotlightDialog_startGroupChat { + padding-left: $spacing-32; position: relative; &::before { @@ -191,7 +363,7 @@ limitations under the License. width: 24px; height: 24px; position: absolute; - left: 4px; + left: $spacing-4; top: 50%; transform: translateY(-50%); } @@ -209,6 +381,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); } + .mx_SpotlightDialog_startGroupChat::before { + mask-image: url('$(res)/img/element-icons/group-members.svg'); + } + .mx_SpotlightDialog_otherSearches_messageSearchText { font-size: $font-15px; line-height: $font-24px; @@ -228,8 +404,8 @@ limitations under the License. } .mx_SpotlightDialog_result_details { - margin-left: 8px; - margin-right: 8px; + margin-left: $spacing-8; + margin-right: $spacing-8; color: $tertiary-content; font-size: $font-12px; line-height: $font-15px; @@ -247,13 +423,13 @@ limitations under the License. } .mx_SpotlightDialog_enterPrompt { - padding: 2px 4px; + padding: 2px $spacing-4; // TODO: Use a spacing variable font-size: $font-12px; line-height: $font-15px; color: $tertiary-content; border-radius: 6px; background-color: $quinary-content; - margin: 0 4px 0 auto; + margin: 0 $spacing-4 0 auto; display: none; } @@ -285,16 +461,10 @@ limitations under the License. font-size: $font-12px; line-height: $font-15px; color: $secondary-content; - padding: 12px 16px 16px; + padding: $spacing-12 $spacing-16 $spacing-16; display: flex; border-top: 1px solid $quinary-content; - .mx_BetaCard_betaPill { - margin-right: 12px; - height: min-content; - align-self: center; - } - > span { align-self: center; @@ -304,7 +474,7 @@ limitations under the License. } .mx_AccessibleButton_kind_primary_outline { - padding: 4px 8px; + padding: $spacing-4 $spacing-8; border-color: $secondary-content; color: $secondary-content; margin-left: auto; diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss index f3558212cca..fc24f27858e 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss @@ -14,126 +14,132 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_AccessSecretStorageDialog_titleWithIcon::before { - content: ''; - display: inline-block; - width: 24px; - height: 24px; - margin-inline-end: $spacing-8; - position: relative; - top: 5px; // TODO: spacing variable - background-color: $primary-content; -} - -.mx_AccessSecretStorageDialog_resetBadge::before { - // The image isn't capable of masking, so we use a background instead. - background-image: url("$(res)/img/element-icons/warning-badge.svg"); - background-size: 24px; - background-color: transparent; -} - -.mx_AccessSecretStorageDialog_secureBackupTitle::before { - mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); -} - -.mx_AccessSecretStorageDialog_securePhraseTitle::before { - mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); -} +.mx_AccessSecretStorageDialog { + .mx_AccessSecretStorageDialog_titleWithIcon { + &::before { + content: ''; + display: inline-block; + width: 24px; + height: 24px; + margin-inline-end: $spacing-8; + position: relative; + top: 5px; // TODO: spacing variable + background-color: $primary-content; + } -.mx_AccessSecretStorageDialog_keyStatus { - height: 30px; -} + &.mx_AccessSecretStorageDialog_resetBadge::before { + // The image isn't capable of masking, so we use a background instead. + background-image: url("$(res)/img/element-icons/warning-badge.svg"); + background-size: 24px; + background-color: transparent; + } -.mx_AccessSecretStorageDialog_passPhraseInput { - width: 300px; - border: 1px solid $accent; - border-radius: $border-radius-5px; -} + &.mx_AccessSecretStorageDialog_secureBackupTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); + } -.mx_AccessSecretStorageDialog_recoveryKeyEntry { - display: flex; - align-items: center; -} + &.mx_AccessSecretStorageDialog_securePhraseTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); + } + } -.mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput { - flex-grow: 1; -} + .mx_AccessSecretStorageDialog_primaryContainer { + .mx_AccessSecretStorageDialog_passPhraseInput { + width: 300px; + border: 1px solid $accent; + border-radius: 5px; + } -.mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText { - margin: $spacing-16; -} + .mx_AccessSecretStorageDialog_keyStatus { + height: 30px; + } -.mx_AccessSecretStorageDialog_recoveryKeyFeedback { - &::before { - content: ""; - display: inline-block; - vertical-align: bottom; - width: 20px; - height: 20px; - mask-repeat: no-repeat; - mask-position: center; - mask-size: 20px; - margin-inline-end: 5px; // TODO: spacing variable - } -} + .mx_AccessSecretStorageDialog_recoveryKeyEntry { + display: flex; + align-items: center; -.mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid { - color: $accent; - &::before { - mask-image: url('$(res)/img/feather-customised/check.svg'); - background-color: $accent; - } -} + .mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput { + flex-grow: 1; + } -.mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid { - color: $alert; - &::before { - mask-image: url('$(res)/img/feather-customised/x.svg'); - background-color: $alert; - } -} + .mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText { + margin: $spacing-16; + } -.mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput { - display: none; -} + .mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput { + display: none; + } + } -.mx_AccessSecretStorageDialog_primaryContainer { - .mx_Dialog_buttons { - $spacingStart: $spacing-24; // 16px icon + 8px padding + .mx_AccessSecretStorageDialog_recoveryKeyFeedback { + &::before { + content: ""; + display: inline-block; + vertical-align: bottom; + width: 20px; + height: 20px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 20px; + margin-inline-end: 5px; // TODO: spacing variable + } - text-align: initial; - display: flex; - flex-flow: column; - gap: 14px; // TODO: spacing variable + &.mx_AccessSecretStorageDialog_recoveryKeyFeedback--valid { + color: $accent; - .mx_Dialog_buttons_additive { - float: none; + &::before { + mask-image: url('$(res)/img/feather-customised/check.svg'); + background-color: $accent; + } + } - .mx_AccessSecretStorageDialog_reset { - position: relative; - padding-inline-start: $spacingStart; + &.mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid { + color: $alert; &::before { - content: ""; - display: inline-block; - position: absolute; - height: 16px; - width: 16px; - left: 0; - top: 2px; // alignment - background-image: url("$(res)/img/element-icons/warning-badge.svg"); - background-size: contain; + mask-image: url('$(res)/img/feather-customised/x.svg'); + background-color: $alert; } + } + } - .mx_AccessSecretStorageDialog_reset_link { - color: $alert; + .mx_Dialog_buttons { + $spacingStart: $spacing-24; // 16px icon + 8px padding + + text-align: initial; + display: flex; + flex-flow: column; + gap: 14px; // TODO: spacing variable + + .mx_Dialog_buttons_additive { + float: none; + + .mx_AccessSecretStorageDialog_reset { + position: relative; + padding-inline-start: $spacingStart; + + &::before { + content: ""; + display: inline-block; + position: absolute; + height: 16px; + width: 16px; + left: 0; + top: 2px; // alignment + background-image: url("$(res)/img/element-icons/warning-badge.svg"); + background-size: contain; + } + + .mx_AccessSecretStorageDialog_reset_link { + color: $alert; + } } } - } - .mx_Dialog_buttons_row { - gap: $spacing-16; // TODO: needs normalization - padding-inline-start: $spacingStart; + .mx_Dialog_buttons_row { + gap: $spacing-16; // TODO: needs normalization + padding-inline-start: $spacingStart; + } } } } diff --git a/res/css/views/directory/_NetworkDropdown.scss b/res/css/views/directory/_NetworkDropdown.scss index 7f512bc4a08..c27e17c2f96 100644 --- a/res/css/views/directory/_NetworkDropdown.scss +++ b/res/css/views/directory/_NetworkDropdown.scss @@ -1,5 +1,5 @@ /* -Copyright 2020 The Matrix.org Foundation C.I.C. +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. @@ -14,151 +14,72 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_NetworkDropdown { - height: 32px; - position: relative; - width: max-content; - padding-right: 32px; - margin-left: auto; - margin-right: 9px; - margin-top: 12px; - - .mx_AccessibleButton { - width: max-content; - } -} - -.mx_NetworkDropdown_menu { - min-width: 204px; - margin: 0; - box-sizing: border-box; - border-radius: $border-radius-4px; - border: 1px solid $dialog-close-fg-color; - background-color: $background; - max-height: calc(100vh - 20px); // allow 10px padding on both top and bottom - overflow-y: auto; -} - -.mx_NetworkDropdown_menu_network { - font-weight: bold; -} +.mx_NetworkDropdown_wrapper .mx_ContextualMenu { + .mx_GenericDropdownMenu_Option { + &.mx_GenericDropdownMenu_Option--header { + padding-top: $spacing-12; + padding-bottom: $spacing-4; + min-width: 160px; + } -.mx_NetworkDropdown_server { - padding: 12px 0; - border-bottom: 1px solid $input-darker-fg-color; + &.mx_GenericDropdownMenu_Option--item { + padding-top: $spacing-4; + padding-bottom: $spacing-4; - .mx_NetworkDropdown_server_title { - padding: 0 10px; - font-size: $font-15px; - font-weight: 600; - line-height: $font-20px; - margin-bottom: 4px; - position: relative; + > .mx_GenericDropdownMenu_Option--label span:first-child { + font-weight: normal; + } + } - // remove server button - .mx_AccessibleButton { - position: absolute; - display: inline; - right: 10px; - height: 16px; - width: 16px; - margin-top: 2px; + > .mx_GenericDropdownMenu_Option--label { + flex-direction: row; + align-items: baseline; + align-content: baseline; + color: $primary-content; - &::after { - content: ""; - position: absolute; - width: 16px; - height: 16px; - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - mask-image: url('$(res)/img/feather-customised/x.svg'); - background-color: $alert; + span:not(:first-child) { + margin-left: $spacing-4; + color: $secondary-content; } } } - .mx_NetworkDropdown_server_subtitle { - padding: 0 10px; - font-size: $font-10px; - line-height: $font-14px; - margin-top: -4px; - margin-bottom: 4px; - color: $muted-fg-color; - } - - .mx_NetworkDropdown_server_network { - font-size: $font-12px; - line-height: $font-16px; - padding: 4px 10px; - cursor: pointer; - position: relative; - - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - - &[aria-checked=true]::after { - content: ""; - position: absolute; - width: 16px; - height: 16px; - right: 10px; - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - mask-image: url('$(res)/img/feather-customised/check.svg'); - background-color: $accent; - } + .mx_GenericDropdownMenu_divider { + margin-top: $spacing-4; + margin-bottom: $spacing-4; } } -.mx_NetworkDropdown_server_add, -.mx_NetworkDropdown_server_network { - &:hover { - background-color: $header-panel-bg-color; - } +.mx_NetworkDropdown_addServer { + font-weight: normal; + font-size: $font-15px; } -.mx_NetworkDropdown_server_add { - padding: 16px 10px 16px 32px; +.mx_NetworkDropdown_removeServer { position: relative; - border-radius: 0 0 $border-radius-4px $border-radius-4px; + display: inline-block; + width: 16px; + height: 16px; + background: $quinary-content; + border-radius: $border-radius-8px; + text-align: center; + line-height: 16px; + color: $secondary-content; + margin-left: auto; &::before { + background-color: $secondary-content; content: ""; - position: absolute; - width: 16px; - height: 16px; - left: 7px; mask-repeat: no-repeat; mask-position: center; mask-size: contain; - mask-image: url('$(res)/img/feather-customised/plus.svg'); - background-color: $muted-fg-color; - } -} - -.mx_NetworkDropdown_handle { - position: relative; - - &::after { - content: ""; + width: 8px; + height: 8px; position: absolute; - width: 26px; - height: 26px; - right: -27.5px; // - (width: 26 + spacing to align with X above: 1.5) - top: -3px; - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - mask-image: url('$(res)/img/feather-customised/chevron-down-thin.svg'); - background-color: $primary-content; - } - - .mx_NetworkDropdown_handle_server { - color: $muted-fg-color; - font-size: $font-12px; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + mask-image: url("$(res)/img/cancel-small.svg"); } } diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss index bd665b5148a..58653a7fbc9 100644 --- a/res/css/views/elements/_AccessibleButton.scss +++ b/res/css/views/elements/_AccessibleButton.scss @@ -18,7 +18,7 @@ limitations under the License. cursor: pointer; &.mx_AccessibleButton_disabled { - cursor: default; + cursor: not-allowed; &.mx_AccessibleButton_kind_primary, &.mx_AccessibleButton_kind_primary_outline, diff --git a/res/css/views/elements/_EventTilePreview.scss b/res/css/views/elements/_EventTilePreview.scss index 6bb726168f1..1f2df50f28e 100644 --- a/res/css/views/elements/_EventTilePreview.scss +++ b/res/css/views/elements/_EventTilePreview.scss @@ -14,9 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_EventTilePreview_loader { - &.mx_IRCLayout, - &.mx_GroupLayout { - padding: 9px 0; - } +.mx_FontScalingPanel_preview.mx_EventTilePreview_loader { + padding: 9px 0; } diff --git a/res/css/views/elements/_GenericEventListSummary.scss b/res/css/views/elements/_GenericEventListSummary.scss index 1e4d87487d8..395c6eb4498 100644 --- a/res/css/views/elements/_GenericEventListSummary.scss +++ b/res/css/views/elements/_GenericEventListSummary.scss @@ -17,8 +17,27 @@ limitations under the License. .mx_GenericEventListSummary { position: relative; - .mx_DateSeparator { - display: none; + &[data-layout=irc], + &[data-layout=group] { + .mx_GenericEventListSummary_toggle { + float: right; + margin: 8px 10px 0 0; + } + + .mx_GenericEventListSummary_avatars { + padding-top: $spacing-8; + } + } + + &[data-layout=irc] { + .mx_GenericEventListSummary_avatars { + padding: 0; + margin: 0 9px 0 0; + } + } + + &[data-layout=bubble] { + // SC: not here } } @@ -35,7 +54,6 @@ limitations under the License. .mx_GenericEventListSummary_avatars { display: inline-block; margin-right: 8px; - padding-top: 8px; line-height: $font-12px; } @@ -47,9 +65,6 @@ limitations under the License. .mx_GenericEventListSummary_toggle { color: $accent; cursor: pointer; - float: right; - margin-right: 10px; - margin-top: 8px; } .mx_GenericEventListSummary_line { diff --git a/res/css/views/elements/_Pill.scss b/res/css/views/elements/_Pill.scss index 3494c45a9ca..340737ff64c 100644 --- a/res/css/views/elements/_Pill.scss +++ b/res/css/views/elements/_Pill.scss @@ -21,7 +21,9 @@ limitations under the License. vertical-align: text-top; display: inline-flex; align-items: center; - + box-sizing: border-box; + max-width: 100%; + overflow: hidden; cursor: pointer; color: $primary-content !important; // To override .markdown-body @@ -56,8 +58,17 @@ limitations under the License. &::before, .mx_BaseAvatar { - margin-left: -0.3em; // Otherwise the gap is too large - margin-right: 0.2em; + margin-inline-start: -0.3em; // Otherwise the gap is too large + margin-inline-end: 0.2em; + min-width: $font-16px; // ensure the avatar is not compressed + } + + .mx_Pill_linkText { + white-space: nowrap; // enforce the pill text to be a single line + overflow: hidden; + text-overflow: ellipsis; + + pointer-events: none; // ensure clicks on the pills go through the anchor } a& { diff --git a/res/css/views/elements/_ReplyChain.scss b/res/css/views/elements/_ReplyChain.scss index fc5833c6be1..840ab4cc57d 100644 --- a/res/css/views/elements/_ReplyChain.scss +++ b/res/css/views/elements/_ReplyChain.scss @@ -15,19 +15,19 @@ limitations under the License. */ .mx_ReplyChain { - margin-top: 0; - margin-left: 0; - margin-right: 0; - margin-bottom: 8px; - padding: 0 10px; + margin: 0 0 $spacing-8 0; + padding: 0 10px; // TODO: Use a spacing variable border-left: 2px solid $accent; .mx_ReplyChain_show { - @mixin ButtonResetDefault; - color: inherit; - - &:hover { - color: $links; + &.mx_AccessibleButton_kind_link_inline { + padding: 0; + color: unset; + white-space: nowrap; // Enforce 'In reply to' to be a single line + + &:hover { + color: $links; + } } } diff --git a/res/css/views/elements/_ToggleSwitch.scss b/res/css/views/elements/_ToggleSwitch.scss index e5e90e2d57d..09c04f2c08e 100644 --- a/res/css/views/elements/_ToggleSwitch.scss +++ b/res/css/views/elements/_ToggleSwitch.scss @@ -15,6 +15,8 @@ limitations under the License. */ .mx_ToggleSwitch { + --ToggleSwitch-min-width: $font-44px; + transition: background-color 0.20s ease-out 0.1s; width: $font-44px; diff --git a/res/css/views/messages/_EventTileBubble.scss b/res/css/views/messages/_EventTileBubble.scss index 16104862229..4cd584ae203 100644 --- a/res/css/views/messages/_EventTileBubble.scss +++ b/res/css/views/messages/_EventTileBubble.scss @@ -19,11 +19,17 @@ limitations under the License. padding: 10px; border-radius: $border-radius-8px; margin: 10px auto; - max-width: 75%; + // Reserve space for external timestamps, but also cap the width + max-width: min(calc(100% - 2 * $MessageTimestamp_width), 600px); box-sizing: border-box; display: grid; grid-template-columns: 24px minmax(0, 1fr) min-content min-content; + .mx_EventTile[data-layout=bubble] & { + // Timestamps are inside the tile, so the width can be less constrained + max-width: 600px; + } + &::before, &::after { position: relative; grid-column: 1; diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index 7cd34cba612..a0cae7c4cac 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -19,12 +19,12 @@ $timeline-image-border-radius: $border-radius-8px; .mx_MImageBody_banner { position: absolute; - bottom: 4px; - left: 4px; - padding: 4px; + bottom: $spacing-4; + left: $spacing-4; + padding: $spacing-4; border-radius: $timeline-image-border-radius; font-size: $font-15px; - + user-select: none; // prevent banner text from being selected pointer-events: none; // let the cursor go through to the media underneath // Trying to match the width of the image is surprisingly difficult, so arbitrarily break it off early. diff --git a/res/css/views/messages/_MImageReplyBody.scss b/res/css/views/messages/_MImageReplyBody.scss index 3207443d65b..364a756dc83 100644 --- a/res/css/views/messages/_MImageReplyBody.scss +++ b/res/css/views/messages/_MImageReplyBody.scss @@ -16,17 +16,21 @@ limitations under the License. .mx_MImageReplyBody { display: flex; + column-gap: $spacing-4; - .mx_MImageBody_thumbnail_container { + .mx_MImageBody_thumbnail_container, + .mx_MImageReplyBody_info { flex: 1; - margin-right: 4px; + min-width: 0; // Prevent a blowout } .mx_MImageReplyBody_info { - flex: 1; - .mx_MImageReplyBody_sender { grid-area: sender; + + .mx_DisambiguatedProfile { + max-width: 100%; + } } .mx_MImageReplyBody_filename { diff --git a/res/css/views/messages/_MLocationBody.scss b/res/css/views/messages/_MLocationBody.scss index cbbd34526db..2a8866c03b0 100644 --- a/res/css/views/messages/_MLocationBody.scss +++ b/res/css/views/messages/_MLocationBody.scss @@ -42,3 +42,8 @@ limitations under the License. .mx_DisambiguatedProfile ~ .mx_MLocationBody { margin-top: 6px; // See: https://github.com/matrix-org/matrix-react-sdk/pull/8442 } + +.mx_ReplyTile .mx_MLocationBody { + // Prevent clicking a location within a reply + pointer-events: none; +} diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index c00e47a93f6..beccb2e89f6 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -18,6 +18,9 @@ limitations under the License. .mx_MessageActionBar { --MessageActionBar-size-button: 28px; --MessageActionBar-size-box: 32px; // 28px + 2px (margin) * 2 + --MessageActionBar-item-hover-background: $panel-actions; + --MessageActionBar-item-hover-borderRadius: $border-radius-6px; + --MessageActionBar-item-hover-zIndex: 1; position: absolute; visibility: hidden; @@ -59,97 +62,101 @@ limitations under the License. margin: 2px; &:hover { - background: $panel-actions; - border-radius: $border-radius-6px; - z-index: 1; + background: var(--MessageActionBar-item-hover-background); + border-radius: var(--MessageActionBar-item-hover-borderRadius); + z-index: var(--MessageActionBar-item-hover-zIndex); } } -} -.mx_MessageActionBar_maskButton { - width: var(--MessageActionBar-size-button); - height: var(--MessageActionBar-size-button); + .mx_MessageActionBar_maskButton { + width: var(--MessageActionBar-size-button); + height: var(--MessageActionBar-size-button); - &:disabled, - &[disabled] { - cursor: not-allowed; - opacity: .75; - } -} + &:disabled, + &[disabled] { + cursor: not-allowed; + opacity: .75; + } -.mx_MessageActionBar_maskButton::after { - content: ''; - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; - mask-size: 18px; - mask-repeat: no-repeat; - mask-position: center; - background-color: $secondary-content; -} + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + mask-size: 18px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $secondary-content; + } -.mx_MessageActionBar_maskButton:hover::after { - background-color: $primary-content; -} + &:hover::after { + background-color: $primary-content; + } -.mx_MessageActionBar_reactButton::after { - mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); -} + &.mx_MessageActionBar_reactButton::after { + mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); + } -.mx_MessageActionBar_replyButton::after { - mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); -} + &.mx_MessageActionBar_replyButton::after { + mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); + } -.mx_MessageActionBar_threadButton::after { - mask-image: url('$(res)/img/element-icons/message/thread.svg'); -} + &.mx_MessageActionBar_threadButton { + &::after { + mask-image: url('$(res)/img/element-icons/message/thread.svg'); + } -.mx_MessageActionBar_threadButton .mx_Indicator { - background: $links; - animation-iteration-count: infinite; -} + .mx_Indicator { + background: $links; + animation-iteration-count: infinite; + } + } -.mx_MessageActionBar_editButton::after { - mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg'); -} + &.mx_MessageActionBar_editButton::after { + mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg'); + } -.mx_MessageActionBar_optionsButton::after { - mask-image: url('$(res)/img/element-icons/context-menu.svg'); -} + &.mx_MessageActionBar_optionsButton::after { + mask-image: url('$(res)/img/element-icons/context-menu.svg'); + } -.mx_MessageActionBar_resendButton::after { - mask-image: url('$(res)/img/element-icons/retry.svg'); -} + &.mx_MessageActionBar_resendButton::after { + mask-image: url('$(res)/img/element-icons/retry.svg'); + } -.mx_MessageActionBar_cancelButton::after { - mask-image: url('$(res)/img/element-icons/trashcan.svg'); -} + &.mx_MessageActionBar_cancelButton::after { + mask-image: url('$(res)/img/element-icons/trashcan.svg'); + } -.mx_MessageActionBar_downloadButton::after { - mask-size: 14px; - mask-image: url('$(res)/img/download.svg'); -} + &.mx_MessageActionBar_downloadButton { + &::after { + mask-size: 14px; + mask-image: url('$(res)/img/download.svg'); + } -.mx_MessageActionBar_expandMessageButton::after { - mask-size: 12px; - mask-image: url('$(res)/img/element-icons/expand-message.svg'); -} + &.mx_MessageActionBar_downloadSpinnerButton::after { + background-color: transparent; // hide the download icon mask + } + } -.mx_MessageActionBar_collapseMessageButton::after { - mask-size: 12px; - mask-image: url('$(res)/img/element-icons/collapse-message.svg'); -} + &.mx_MessageActionBar_expandMessageButton::after { + mask-size: 12px; + mask-image: url('$(res)/img/element-icons/expand-message.svg'); + } -.mx_MessageActionBar_viewInRoom::after { - mask-image: url('$(res)/img/element-icons/view-in-room.svg'); -} + &.mx_MessageActionBar_collapseMessageButton::after { + mask-size: 12px; + mask-image: url('$(res)/img/element-icons/collapse-message.svg'); + } -.mx_MessageActionBar_copyLinkToThread::after { - mask-image: url('$(res)/img/element-icons/link.svg'); -} + &.mx_MessageActionBar_viewInRoomButton::after { + mask-image: url('$(res)/img/element-icons/view-in-room.svg'); + } -.mx_MessageActionBar_downloadButton.mx_MessageActionBar_downloadSpinnerButton::after { - background-color: transparent; // hide the download icon mask + &.mx_MessageActionBar_copyLinkButton::after { + mask-image: url('$(res)/img/element-icons/link.svg'); + } + } } diff --git a/res/css/views/messages/_ReactionsRowButton.scss b/res/css/views/messages/_ReactionsRowButton.scss index 0702fe21a85..244585fd2d1 100644 --- a/res/css/views/messages/_ReactionsRowButton.scss +++ b/res/css/views/messages/_ReactionsRowButton.scss @@ -17,14 +17,11 @@ limitations under the License. .mx_ReactionsRowButton { display: inline-flex; line-height: $font-20px; - margin-right: 6px; padding: 1px 6px; border: 1px solid $message-action-bar-border-color; border-radius: 500px; background-color: $header-panel-bg-color; - cursor: pointer; user-select: none; - vertical-align: middle; &:hover { border-color: $reaction-row-button-hover-border-color; diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index 894138add20..672b6594771 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -15,10 +15,12 @@ limitations under the License. */ .mx_BaseCard { + --BaseCard_padding-inline: $spacing-8; --BaseCard_EventTile_line-padding-block: 2px; - --BaseCard_EventTile-spacing-horizontal: 36px; + --BaseCard_EventTile-spacing-inline: 36px; // TODO: Use a spacing variable + --BaseCard_header-button-size: 24px; - padding: 0 8px; + padding: 0 var(--BaseCard_padding-inline); overflow: hidden; display: flex; flex-direction: column; @@ -27,10 +29,10 @@ limitations under the License. .mx_BaseCard_header { --BaseCard_header_button-margin: $spacing-12; - margin: $spacing-4 0; + margin: $spacing-4 0 $spacing-12; > h2 { - margin: 0 44px; + margin: 0 44px; // TODO: Use a spacing variable font-size: $font-18px; font-weight: $font-semi-bold; overflow: hidden; @@ -42,8 +44,8 @@ limitations under the License. .mx_BaseCard_close { position: absolute; background-color: rgba(141, 151, 165, 0.2); - height: 20px; - width: 20px; + width: var(--BaseCard_header-button-size); + height: var(--BaseCard_header-button-size); margin: var(--BaseCard_header_button-margin); top: 0; border-radius: $border-radius-10px; @@ -63,22 +65,73 @@ limitations under the License. .mx_BaseCard_back { left: 0; + margin-inline-start: calc(var(--BaseCard_header_button-margin) - $spacing-4); &::before { transform: rotate(90deg); mask-size: 22px; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } + + // Header title with the back button + ~ .mx_BaseCard_header_title { + width: calc(100% - 60px); + margin-inline-start: var(--BaseCard_header-button-size); + + .mx_BaseCard_header_title_heading { + margin-inline-start: 6px; // TODO: Use a spacing variable + } + } } .mx_BaseCard_close { right: 0; + margin-inline-end: calc(var(--BaseCard_header_button-margin) - $spacing-4); &::before { mask-image: url('$(res)/img/icons-close.svg'); mask-size: 8px; } } + + .mx_BaseCard_header_title { + display: flex; + align-items: center; + justify-content: space-between; + width: calc(100% - 38px); + height: 24px; + flex: 1; + + .mx_BaseCard_header_title_heading { + color: $secondary-content; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .mx_BaseCard_header_title_button--option { + position: relative; + width: var(--BaseCard_header-button-size); + height: var(--BaseCard_header-button-size); + + &::after { + content: ''; + position: absolute; + inset-block-start: 0; + inset-inline-start: 0; + height: 100%; + width: 100%; + mask-repeat: no-repeat; + mask-position: center; + mask-image: url("$(res)/img/element-icons/message/overflow-large.svg"); + background-color: $secondary-content; + } + + &:hover::after { + background-color: $primary-content; + } + } + } } .mx_AutoHideScrollbar { @@ -91,11 +144,11 @@ limitations under the License. } .mx_BaseCard_Group { - margin: 20px 0 16px; + margin: $spacing-20 0 $spacing-16; & > * { - margin-left: 12px; - margin-right: 12px; + margin-left: $spacing-12; + margin-right: $spacing-12; } > h1 { @@ -105,7 +158,8 @@ limitations under the License. } .mx_BaseCard_Button { - padding: 10px 10px 10px 12px; + padding: 10px; // TODO: Use a spacing variable + padding-inline-start: $spacing-12; margin: 0; position: relative; font-size: $font-13px; @@ -127,7 +181,7 @@ limitations under the License. } &.mx_AccessibleButton_disabled { - padding-right: 12px; + padding-right: $spacing-12; &::after { content: unset; } @@ -136,7 +190,7 @@ limitations under the License. } .mx_BaseCard_footer { - padding-top: 4px; + padding-top: $spacing-4; text-align: center; display: flex; justify-content: space-around; @@ -159,7 +213,7 @@ limitations under the License. .mx_NotificationPanel, .mx_MemberList { &.mx_BaseCard { - padding: 32px 0 0; + padding: $spacing-32 0 0; .mx_AutoHideScrollbar { margin-right: unset; @@ -167,3 +221,38 @@ limitations under the License. } } } + +.mx_ContextualMenu_wrapper.mx_BaseCard_header_title { + .mx_ContextualMenu { + position: initial; + + span:first-of-type { + font-weight: $font-semi-bold; + font-size: inherit; + color: $primary-content; + } + + font-size: $font-12px; + color: $secondary-content; + padding-top: 10px; // TODO: Use a spacing variable + padding-bottom: 10px; // TODO: Use a spacing variable + + border: 1px solid $quinary-content; + box-shadow: 0px 1px 3px rgba(23, 25, 28, 0.05); + } + + .mx_ContextualMenu_chevron_top { + left: auto; + right: 22px; // TODO: Use a spacing variable + border-bottom-color: $quinary-content; + + &::after { + content: ""; + border: inherit; + border-bottom-color: $menu-bg-color; + position: absolute; + top: 1px; + left: -8px; + } + } +} diff --git a/res/css/views/right_panel/_PinnedMessagesCard.scss b/res/css/views/right_panel/_PinnedMessagesCard.scss index b2c0a132d42..ac76cf6754e 100644 --- a/res/css/views/right_panel/_PinnedMessagesCard.scss +++ b/res/css/views/right_panel/_PinnedMessagesCard.scss @@ -15,55 +15,33 @@ limitations under the License. */ .mx_PinnedMessagesCard { - padding-top: 0; - - .mx_BaseCard_header { - text-align: center; - margin-top: 0; - border-bottom: 1px solid $menu-border-color; - - > h2 { - font-weight: $font-semi-bold; - font-size: $font-18px; - margin: 8px 0; - } - - .mx_BaseCard_close { - margin-right: 6px; - } - } - - .mx_PinnedMessagesCard_empty { + .mx_PinnedMessagesCard_empty_wrapper { display: flex; height: 100%; - > div { + .mx_PinnedMessagesCard_empty { height: max-content; text-align: center; margin: auto 40px; .mx_PinnedMessagesCard_MessageActionBar { pointer-events: none; - display: flex; - height: 32px; - line-height: $font-24px; - border-radius: 8px; - background: $background; - border: 1px solid $input-border-color; - padding: 1px; width: max-content; margin: 0 auto; - box-sizing: border-box; - .mx_MessageActionBar_maskButton { - display: inline-block; - position: relative; + // Cancel the default values for non-interactivity + position: unset; + visibility: visible; + cursor: unset; + + &::before { + content: unset; } .mx_MessageActionBar_optionsButton { - background: $panel-actions; - border-radius: 6px; - z-index: 1; + background: var(--MessageActionBar-item-hover-background); + border-radius: var(--MessageActionBar-item-hover-borderRadius); + z-index: var(--MessageActionBar-item-hover-zIndex); &::after { background-color: $primary-content; @@ -71,13 +49,9 @@ limitations under the License. } } - > h2 { - font-weight: $font-semi-bold; - font-size: $font-15px; - line-height: $font-24px; + .mx_PinnedMessagesCard_empty_header { color: $primary-content; - margin-top: 24px; - margin-bottom: 20px; + margin-block: $spacing-24 $spacing-20; } > span { diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 7e1061ac54e..11fb848ac1a 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -17,25 +17,23 @@ limitations under the License. .mx_RoomSummaryCard { .mx_BaseCard_header { text-align: center; - margin-top: 20px; + margin-top: $spacing-20; h2 { - font-weight: $font-semi-bold; - font-size: $font-18px; - margin: 12px 0 4px; + margin: $spacing-12 0 $spacing-4; } .mx_RoomSummaryCard_alias { font-size: $font-13px; color: $secondary-content; + overflow: hidden; + text-overflow: ellipsis; } h2, .mx_RoomSummaryCard_alias { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; white-space: pre-wrap; } diff --git a/res/css/views/right_panel/_ThreadPanel.scss b/res/css/views/right_panel/_ThreadPanel.scss index 3b2f1219ce9..3441c43fbb1 100644 --- a/res/css/views/right_panel/_ThreadPanel.scss +++ b/res/css/views/right_panel/_ThreadPanel.scss @@ -15,71 +15,37 @@ limitations under the License. */ .mx_ThreadPanel { - --ThreadPanel_header-button-size: 24px; - - display: flex; - flex-direction: column; height: 100px; overflow: visible; .mx_BaseCard_header { - margin-bottom: $spacing-12; - - .mx_BaseCard_close, - .mx_BaseCard_back { - width: var(--ThreadPanel_header-button-size); - height: var(--ThreadPanel_header-button-size); - } - - .mx_BaseCard_back { - margin-inline-start: calc(var(--BaseCard_header_button-margin) - 4px); - } - - .mx_BaseCard_close { - margin-inline-end: calc(var(--BaseCard_header_button-margin) - 4px); - } - } - - .mx_BaseCard_back ~ .mx_ThreadPanel__header { - width: calc(100% - 60px); - margin-inline-start: var(--ThreadPanel_header-button-size); - - span { - margin-inline-start: 6px; - } - } - - .mx_ThreadPanel__header { - width: calc(100% - 38px); - height: 24px; - display: flex; - flex: 1; - justify-content: space-between; - align-items: center; - - span:first-of-type { - font-weight: 600; - font-size: 15px; - line-height: 18px; - color: $secondary-content; - } - - .mx_AccessibleButton { - font-size: 12px; - color: $secondary-content; - } + .mx_BaseCard_header_title { + .mx_AccessibleButton { + font-size: 12px; + color: $secondary-content; + } - .mx_MessageActionBar_optionsButton { - position: relative; - } + .mx_ThreadPanel_dropdown { + padding: 3px $spacing-4 3px $spacing-8; // TODO: Use a spacing variable + border-radius: 4px; + line-height: 1.5; + user-select: none; - .mx_MessageActionBar_maskButton { - width: var(--ThreadPanel_header-button-size); - height: var(--ThreadPanel_header-button-size); + &:hover, + &[aria-expanded=true] { + background: $quinary-content; + } - &::after { - mask-size: var(--ThreadPanel_header-button-size); - mask-image: url("$(res)/img/element-icons/message/overflow-large.svg"); + &::before { + content: ""; + width: 18px; + height: 18px; + background: currentColor; + mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); + mask-size: 100%; + mask-repeat: no-repeat; + float: right; + } } } } @@ -93,43 +59,18 @@ limitations under the License. height: 100%; } - // Override _GroupLayout.scss for the thread panel - .mx_GroupLayout { - .mx_EventTile { - .mx_MessageActionBar { - right: 0; - top: -36px; // 2px above EventTile - z-index: 10; // See _EventTile.scss - } - - &[data-shape=ThreadsList] { - > .mx_DisambiguatedProfile { - margin-inline-start: 0; - } - - .mx_MessageTimestamp { - position: initial; - width: auto; - } - - .mx_EventTile_line { - padding-bottom: 0; // Override mx_EventTile_line on _GroupLayout.scss - } - } + .mx_EventTile[data-layout=group] { + .mx_MessageActionBar { + right: 0; + top: -36px; // 2px above EventTile + z-index: 10; // See _EventTile.scss } } + // For style rules of EventTile in a thread, see _EventTile.scss &.mx_ThreadView { max-height: 100%; - // Inside a thread timeline only - .mx_GenericEventListSummary { - &:not([data-layout=bubble]) > .mx_EventTile_line { - padding-inline-start: var(--ThreadView_group_spacing-start); // align summary text with message text - padding-inline-end: var(--ThreadView_group_spacing-end); // align summary text with message text - } - } - .mx_ThreadView_timelinePanelWrapper { position: relative; min-height: 0; // don't displace the composer @@ -179,95 +120,11 @@ limitations under the License. box-sizing: border-box; } - .mx_ThreadPanel_dropdown { - padding: 3px 4px 3px 8px; - border-radius: $border-radius-4px; - line-height: 1.5; - user-select: none; - } - - .mx_ThreadPanel_dropdown:hover, - .mx_ThreadPanel_dropdown[aria-expanded=true] { - background: $quinary-content; - } - - .mx_ThreadPanel_dropdown::before { - content: ""; - width: 18px; - height: 18px; - background: currentColor; - mask-image: url("$(res)/img/feather-customised/chevron-down.svg"); - mask-size: 100%; - mask-repeat: no-repeat; - float: right; - } - .mx_MessageTimestamp { font-size: $font-12px; color: $secondary-content; } - &.mx_ThreadView .mx_EventTile { - // handling for hidden events (e.g reactions) in the thread view - - &:not([data-layout=bubble]) { - &:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, - &:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, - &:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { - padding-inline-start: 0; // Override - } - } - - &.mx_EventTile_info { - padding-top: 0; - - &.mx_EventTile_selected .mx_EventTile_line, - .mx_EventTile_line { - $line-height: $font-12px; - - padding-inline-start: 0; - line-height: $line-height; - - .mx_EventTile_content, - .mx_RedactedBody { - width: auto; - margin-inline-start: calc(var(--ThreadView_group_spacing-start) + 14px + 6px); // 14px: avatar width, 6px: 20px - 14px - font-size: $line-height; - } - } - - &:not([data-layout=bubble]) { - .mx_MessageTimestamp { - top: 2px; // Align with avatar - } - - .mx_EventTile_avatar { - left: calc($MessageTimestamp_width + 14px - 4px); // 14px: avatar width, 4px: align with text - z-index: 9; // position above the hover styling - } - } - - &[data-layout=bubble] { - .mx_EventTile_avatar { - inset-inline-start: 0; - } - } - - .mx_EventTile_avatar { - position: absolute; - top: 1.5px; // Align with hidden event content - margin-top: 0; - margin-bottom: 0; - width: 14px; // avatar img size - height: 14px; // avatar img size - } - - .mx_ViewSourceEvent_toggle { - display: none; // hide the hidden event expand button, not enough space, view source can still be used - } - } - } - .mx_BaseCard_footer { text-align: left; font-size: $font-12px; @@ -284,18 +141,6 @@ limitations under the License. } } -.mx_ThreadPanel_replies { - margin-top: $spacing-8; - display: flex; - align-items: center; - position: relative; - - .mx_ThreadPanel_ThreadsAmount { - @mixin ThreadsAmount; - font-size: $font-12px; // Same font size as the counter on the main panel - } -} - .mx_ThreadPanel_viewInRoom::before { mask-image: url('$(res)/img/element-icons/view-in-room.svg'); } @@ -376,40 +221,7 @@ limitations under the License. } } -.mx_ContextualMenu_wrapper.mx_ThreadPanel__header { - .mx_ContextualMenu { - position: initial; - - span:first-of-type { - font-weight: $font-semi-bold; - font-size: inherit; - color: $primary-content; - } - - font-size: $font-12px; - color: $secondary-content; - padding-top: 10px; - padding-bottom: 10px; - - border: 1px solid $quinary-content; - box-shadow: 0px 1px 3px rgba(23, 25, 28, 0.05); - } - - .mx_ContextualMenu_chevron_top { - left: auto; - right: 22px; - border-bottom-color: $quinary-content; - - &::after { - content: ""; - border: inherit; - border-bottom-color: $menu-bg-color; - position: absolute; - top: 1px; - left: -8px; - } - } - +.mx_ContextualMenu_wrapper { .mx_ThreadPanel_Header_FilterOptionItem { display: flex; flex-grow: 1; diff --git a/res/css/views/right_panel/_TimelineCard.scss b/res/css/views/right_panel/_TimelineCard.scss index 3bc71040811..93eb6c1b4e9 100644 --- a/res/css/views/right_panel/_TimelineCard.scss +++ b/res/css/views/right_panel/_TimelineCard.scss @@ -15,26 +15,6 @@ limitations under the License. */ .mx_TimelineCard { - .mx_TimelineCard__header { - margin-left: 6px; - - span:first-of-type { - font-weight: 600; - font-size: 15px; - line-height: 18px; - color: $secondary-content; - } - } - - .mx_BaseCard_header { - margin: 5px 0 9px 0; - - .mx_BaseCard_close { - margin: 8px; - right: 0; - } - } - .mx_TimelineCard_timeline { overflow: hidden; position: relative; // offset parent for jump to bottom button @@ -43,61 +23,86 @@ limitations under the License. } .mx_NewRoomIntro { - margin-inline-start: var(--BaseCard_EventTile-spacing-horizontal); - margin-inline-end: var(--BaseCard_EventTile-spacing-horizontal); + margin-inline-start: var(--BaseCard_EventTile-spacing-inline); + margin-inline-end: var(--BaseCard_EventTile-spacing-inline); } .mx_EventTile_content { margin-right: 0; } - .mx_EventTile:not([data-layout="bubble"]) { - &.mx_EventTile_info .mx_EventTile_line, - .mx_EventTile_line { - padding: var(--BaseCard_EventTile_line-padding-block) var(--BaseCard_EventTile-spacing-horizontal); + .mx_EventTile { + &[data-layout=irc], + &[data-layout=group] { + .mx_EventTile_avatar { + position: absolute; // for IRC layout + left: -3px; + } - .mx_EventTile_e2eIcon { - inset-inline-start: 8px; + .mx_EventTile_msgOption { + margin-inline-end: 0; } - } - .mx_DisambiguatedProfile, - .mx_ReactionsRow, - .mx_ThreadSummary { - margin-inline-start: var(--BaseCard_EventTile-spacing-horizontal); - } + .mx_DisambiguatedProfile, + .mx_ReactionsRow, + .mx_ThreadSummary { + margin-inline-start: var(--BaseCard_EventTile-spacing-inline); + } - .mx_ReactionsRow { - padding: 0; + .mx_DisambiguatedProfile { + max-width: calc(100% - var(--BaseCard_EventTile-spacing-inline)); // instead of $left-gutter + } - // See margin setting of ReactionsRow on _EventTile.scss - margin-right: 8px; - } + .mx_ReplyTile .mx_DisambiguatedProfile { + margin-inline-start: 0; + max-width: unset; + } - .mx_ThreadSummary { - margin-right: 0; - max-width: min(calc(100% - 36px), 600px); - } + .mx_ReactionsRow { + margin-inline-end: $spacing-8; // See: var(--ThreadView_group_spacing-end) for ReactionsRow on _EventTile.scss + } - .mx_EventTile_avatar { - position: absolute; // for IRC layout - top: 12px; - left: -3px; + .mx_ThreadSummary { + margin-inline-end: 0; + max-width: min(calc(100% - 36px), 600px); + } } - .mx_MessageTimestamp { - position: absolute; // for modern layout and IRC layout - right: -4px; - left: auto; - } + &[data-layout=bubble] { + &::before { + z-index: auto; // enable background color on hover + } - .mx_EventTile_msgOption { - margin-right: 2px; + &.mx_EventTile_info .mx_MessageActionBar { + inset-inline-end: calc($container-gap-width + var(--BaseCard_padding-inline) + 1px); // 1px: border width + } + + .mx_ReactionsRow { + position: relative; // display on hover + } } - &.mx_EventTile_info { - .mx_EventTile_avatar { - left: 18px; + &:not([data-layout="bubble"]) { + &.mx_EventTile_info .mx_EventTile_line, + .mx_EventTile_line { + padding: var(--BaseCard_EventTile_line-padding-block) var(--BaseCard_EventTile-spacing-inline); + padding-inline-end: $MessageTimestamp_width; // ensure timestamp is not hidden + + .mx_EventTile_e2eIcon { + inset-inline-start: 8px; + } + } + + .mx_MessageTimestamp { + position: absolute; // for modern layout and IRC layout + inset-inline-start: auto; + inset-inline-end: 0; + } + + &.mx_EventTile_info { + .mx_EventTile_avatar { + left: 18px; + } } } } @@ -132,8 +137,8 @@ limitations under the License. .mx_GenericEventListSummary:not([data-layout=bubble]) { .mx_EventTile_line, > .mx_GenericEventListSummary_unstyledList > .mx_EventTile_info .mx_EventTile_avatar ~ .mx_EventTile_line { - padding-inline-start: var(--BaseCard_EventTile-spacing-horizontal); - padding-inline-end: var(--BaseCard_EventTile-spacing-horizontal); + padding-inline-start: var(--BaseCard_EventTile-spacing-inline); + padding-inline-end: var(--BaseCard_EventTile-spacing-inline); } } @@ -149,11 +154,19 @@ limitations under the License. flex-basis: 48px; // 12 (padding on message list) + 36 (padding on event lines) } - &.mx_BaseCard { - // For a chat timeline on the right panel when the widget is maximised - // TODO: rename ThreadPanel - &.mx_ThreadPanel { - padding-right: 8px; // .mx_RightPanel padding + .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_EventTile[data-layout=bubble] { + .mx_ReadReceiptGroup { + // 6px: scroll bar width (magic number) + inset-inline-end: calc(-1 * var(--ReadReceiptGroup_EventBubbleTile-spacing-end) + $container-gap-width + 6px); + } + + &.mx_EventTile_info { + .mx_ReadReceiptGroup { + inset-inline-end: -4px; // align with RR outside of info tile + } + } } } } diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index 12e93395f5b..506dca687ee 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -18,9 +18,6 @@ limitations under the License. .mx_UserInfo.mx_BaseCard { // UserInfo has a circular image at the top so it fits between the back & close buttons padding-top: 0; - display: flex; - flex-direction: column; - flex: 1; overflow-y: auto; font-size: $font-12px; diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss index 812de3cfc83..6759cbc0d8a 100644 --- a/res/css/views/right_panel/_WidgetCard.scss +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -15,48 +15,12 @@ limitations under the License. */ .mx_WidgetCard { - height: 100%; - display: contents; - .mx_AppTileFullWidth { max-width: unset; width: auto !important; - margin: 0px $container-border-width 0px $container-border-width; height: 100%; border: 0; } - - .mx_BaseCard_header { - display: inline-flex; - - & > h2 { - margin-right: 0; - flex-grow: 1; - } - - .mx_WidgetCard_optionsButton { - position: relative; - margin-right: 44px; - height: 20px; - width: 20px; - min-width: 20px; // prevent crushing by the flexbox - padding: 0; - - &::before { - content: ""; - position: absolute; - width: 20px; - height: 20px; - top: 0; - left: 4px; - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); - background-color: $secondary-content; - } - } - } } .mx_WidgetCard_maxPinnedTooltip { diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index f1cbeb2d8ba..547b0ad6ab5 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -16,6 +16,8 @@ limitations under the License. */ $MiniAppTileHeight: 200px; +// TODO this should be 300px but that's too large +$MinWidth: 240px; .mx_AppsDrawer { margin: $container-gap-width; @@ -88,6 +90,30 @@ $MiniAppTileHeight: 200px; opacity: 0.8; } } + + .mx_AppTile { + width: 50%; + min-width: $MinWidth; + } + + &.mx_AppsDrawer_2apps .mx_AppTile { + width: 50%; + + &:nth-child(3) { + flex-grow: 1; + width: 0 !important; + min-width: $MinWidth !important; + } + } + &.mx_AppsDrawer_3apps .mx_AppTile { + width: 33%; + + &:nth-child(3) { + flex-grow: 1; + width: 0 !important; + min-width: $MinWidth !important; + } + } } .mx_AppsContainer_resizer { @@ -122,31 +148,7 @@ $MiniAppTileHeight: 200px; } } -// TODO this should be 300px but that's too large -$MinWidth: 240px; - -.mx_AppsDrawer_2apps .mx_AppTile { - width: 50%; - - &:nth-child(3) { - flex-grow: 1; - width: 0 !important; - min-width: $MinWidth !important; - } -} -.mx_AppsDrawer_3apps .mx_AppTile { - width: 33%; - - &:nth-child(3) { - flex-grow: 1; - width: 0 !important; - min-width: $MinWidth !important; - } -} - .mx_AppTile { - width: 50%; - min-width: $MinWidth; border: $container-border-width solid $widget-menu-bar-bg-color; display: flex; flex-direction: column; @@ -194,8 +196,8 @@ $MinWidth: 240px; align-items: center; justify-content: space-between; width: 100%; - padding-top: 2px; - padding-bottom: 8px; + padding-top: 3px; + padding-bottom: 6px; } .mx_AppTileMenuBarTitle { @@ -221,39 +223,50 @@ $MinWidth: 240px; } .mx_AppTileMenuBar_iconButton { - width: 12px; - height: 12px; - mask-repeat: no-repeat; - mask-position: 0 center; - mask-size: auto 12px; - background-color: $topleftmenu-color; - margin: 0 5px; + height: 24px; + margin: 0 4px; + position: relative; + width: 24px; + + &::before { + background-color: $muted-fg-color; + content: ''; + height: 24px; + mask-position: center; + mask-repeat: no-repeat; + mask-size: 12px; + position: absolute; + width: 24px; + } - &.mx_AppTileMenuBar_iconButton_close { - mask-size: auto 10px; - mask-image: url("$(res)/img/element-icons/maximise-expand.svg"); - background-color: $accent; + &:hover::after { + background-color: $panel-actions; + border-radius: 50%; + content: ''; + height: 24px; + left: 0; + position: absolute; + top: 0; + width: 24px; } - &.mx_AppTileMenuBar_iconButton_maximise { - mask-size: auto 10px; - mask-image: url("$(res)/img/element-icons/maximise-expand.svg"); + &.mx_AppTileMenuBar_iconButton_collapse::before { + mask-image: url("$(res)/img/element-icons/minimise-collapse.svg"); } - &.mx_AppTileMenuBar_iconButton_unpin { - mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); - background-color: $accent; + &.mx_AppTileMenuBar_iconButton_maximise::before { + mask-image: url("$(res)/img/element-icons/maximise-expand.svg"); } - &.mx_AppTileMenuBar_iconButton_pin { - mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); + &.mx_AppTileMenuBar_iconButton_minimise::before { + mask-image: url("$(res)/img/element-icons/minus-button.svg"); } - &.mx_AppTileMenuBar_iconButton_popout { + &.mx_AppTileMenuBar_iconButton_popout::before { mask-image: url('$(res)/img/feather-customised/widget/external-link.svg'); } - &.mx_AppTileMenuBar_iconButton_menu { + &.mx_AppTileMenuBar_iconButton_menu::before { mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); } } diff --git a/res/css/views/rooms/_BasicMessageComposer.scss b/res/css/views/rooms/_BasicMessageComposer.scss index 8bb8fc0aacb..536192b8a8b 100644 --- a/res/css/views/rooms/_BasicMessageComposer.scss +++ b/res/css/views/rooms/_BasicMessageComposer.scss @@ -51,10 +51,15 @@ limitations under the License. } &.mx_BasicMessageComposer_input_shouldShowPillAvatar { - span.mx_UserPill, span.mx_RoomPill, span.mx_SpacePill { + span.mx_UserPill, + span.mx_RoomPill, + span.mx_SpacePill { user-select: all; position: relative; cursor: unset; // We don't want indicate clickability + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; &:hover { // We don't want indicate clickability | To override the overriding of .markdown-body @@ -63,10 +68,12 @@ limitations under the License. // avatar psuedo element &::before { + display: inline-block; content: var(--avatar-letter); width: $font-16px; + min-width: $font-16px; // ensure the avatar is not compressed height: $font-16px; - margin-right: 0.24rem; + margin-inline-end: 0.24rem; background: var(--avatar-background), $background; color: $avatar-initial-color; background-repeat: no-repeat; diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 75071366fac..285f16788bb 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -53,9 +53,15 @@ limitations under the License. } .mx_EventTile[data-layout=bubble] { + --EventTile_bubble-margin-inline-start: 49px; + --EventTile_bubble-margin-inline-end: 60px; + --EventTile_bubble_line-margin-inline-start: -9px; + --EventTile_bubble_line-margin-inline-end: -12px; + --EventTile_bubble_gap-inline: 5px; + position: relative; margin-top: var(--gutterSize); - margin-left: 49px; + margin-left: var(--EventTile_bubble-margin-inline-start); font-size: $font-14px; .mx_ThreadSummary { @@ -89,8 +95,8 @@ limitations under the License. position: absolute; top: -1px; bottom: -1px; - left: -60px; - right: -60px; + left: calc(-1 * var(--EventTile_bubble-margin-inline-start)); + right: calc(-1 * var(--EventTile_bubble-margin-inline-end)); z-index: -1; border-radius: 4px; } @@ -173,6 +179,10 @@ limitations under the License. border-color: $quinary-content; } + .mx_ReactionsRow { + margin-inline: var(--EventTile_bubble_line-margin-inline-start) var(--EventTile_bubble_line-margin-inline-end); + } + &[data-self=false] { .mx_EventTile_line { border-bottom-right-radius: var(--cornerRadius); @@ -201,6 +211,10 @@ limitations under the License. margin-inline-end: auto; } + .mx_ReactionsRow { + justify-content: flex-start; + } + --backgroundColor: $eventbubble-others-bg; } @@ -235,16 +249,14 @@ limitations under the License. .mx_ReplyTile .mx_DisambiguatedProfile { display: block; + max-width: 100%; } .mx_ReactionsRow { - float: right; - clear: right; - display: flex; + justify-content: flex-end; - /* Moving the "add reaction button" before the reactions */ > :last-child { - order: -1; + order: -1; // Moving the "add reaction button" before the reactions } } @@ -265,12 +277,11 @@ limitations under the License. } .mx_EventTile_line { - --EventBubbleTile_line-margin-inline-end: -12px; - position: relative; display: flex; - gap: 5px; - margin: 0 var(--EventBubbleTile_line-margin-inline-end) 0 -9px; + gap: 5px var(--EventTile_bubble_gap-inline); + margin-block: 0; + margin-inline: var(--EventTile_bubble_line-margin-inline-start) var(--EventTile_bubble_line-margin-inline-end); border-top-left-radius: var(--cornerRadius); border-top-right-radius: var(--cornerRadius); @@ -444,10 +455,6 @@ limitations under the License. flex-direction: column; } - .mx_ReplyChain_show { - order: 99999; - } - .mx_ReplyChain { .mx_EventTile_reply { max-width: 90%; @@ -481,12 +488,13 @@ limitations under the License. } } - .mx_ReactionsRow { - margin-right: -18px; - margin-left: -9px; - } - &.mx_EventTile_bad { + &:hover { + &::before { + background: transparent; + } + } + /* Special layout scenario for "Unable To Decrypt (UTD)" events */ .mx_EventTile_line { display: grid; @@ -539,8 +547,10 @@ limitations under the License. .mx_ReadReceiptGroup { position: absolute; + // as close to right gutter without clipping as possible - right: -78px; + inset-inline-end: calc(-1 * var(--ReadReceiptGroup_EventBubbleTile-spacing-end)); + // (EventTileLine.line-height - ReadReceiptGroup.height) / 2 // this centers the ReadReceiptGroup if we’ve got a single line bottom: calc(($font-18px - 24px) / 2); @@ -584,6 +594,15 @@ limitations under the License. } } +.mx_EventTile.mx_EventTile_bubbleContainer[data-layout=bubble], +.mx_EventTile.mx_EventTile_leftAlignedBubble[data-layout=bubble], +.mx_EventTile.mx_EventTile_info[data-layout=bubble] { + padding: 5px 0; + display: flex; + align-items: center; + justify-content: flex-start; +} + .mx_EventTile.mx_EventTile_bubbleContainer[data-layout=bubble], .mx_EventTile.mx_EventTile_leftAlignedBubble[data-layout=bubble], .mx_EventTile.mx_EventTile_info[data-layout=bubble], @@ -591,15 +610,10 @@ limitations under the License. --backgroundColor: transparent; --gutterSize: 0; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 5px 0; - .mx_EventTile_avatar { position: static; order: -1; - margin-right: 5px; + margin-inline-end: var(--EventTile_bubble_gap-inline); // Same spacing between E2E icon and a hidden event } .mx_EventTile_line, @@ -610,7 +624,8 @@ limitations under the License. } .mx_EventTile_e2eIcon { - margin-left: 9px; + margin-inline-start: 0; // mx_EventTile_avatar has margin-inline-end, so margin is not needed here + align-self: center; } .mx_EventTile_line { @@ -629,92 +644,34 @@ limitations under the License. } .mx_GenericEventListSummary[data-layout=bubble] { - --maxWidth: 70%; - margin-left: calc(var(--avatarSize) + var(--gutterSize)); - - .mx_GenericEventListSummary_toggle { - margin: 0 55px 0 5px; - float: none; - - &[aria-expanded=false] { - order: 9; - } - &[aria-expanded=true] { - text-align: right; - } - } - - .mx_GenericEventListSummary_line { - display: none; - } - - .mx_GenericEventListSummary_avatars { - padding-top: 0; - } - - .mx_EventTile { - &.mx_EventTile_info { - .mx_EventTile_line { - // Avoid overflow of event info by cancelling width settings - width: 100%; - min-width: 0; - max-width: 100%; - } - } + .mx_EventTile.mx_EventTile_info .mx_EventTile_line { + // Avoid overflow of event info by cancelling width settings + width: 100%; + min-width: 0; + max-width: 100%; } - &::after { - content: ""; - clear: both; + // increase margin between ELS and the next Event to not have our user avatar overlap the expand/collapse button + &[data-expanded=false] + .mx_EventTile[data-layout=bubble][data-self=true] { + margin-top: 20px; } - &[data-expanded=false] { - // Align with left edge of bubble tiles - padding: 0 49px; + &[data-expanded=true] .mx_EventTile_info { + padding: 2px 0; + margin-right: 0; - // increase margin between ELS and the next Event to not have our user avatar overlap the expand/collapse button - + .mx_EventTile[data-layout=bubble][data-self=true] { - margin-top: 20px; + .mx_MessageActionBar { + inset-inline-start: initial; // Reset .mx_EventTile[data-layout="bubble"][data-self="false"] .mx_MessageActionBar + inset-inline-end: 48px; // align with that of right-column bubbles } - } - - // ideally we'd use display=contents here for the layout to all work regardless of the *ELS but - // that breaks ScrollPanel's reliance upon offsetTop so we have to have a bit more finesse. - &[data-expanded=true] { - display: flex; - flex-direction: column; - margin: 0; - - .mx_EventTile_info { - padding: 2px 0; - margin-right: 0; - - .mx_MessageActionBar { - inset-inline-start: initial; // Reset .mx_EventTile[data-layout="bubble"][data-self="false"] .mx_MessageActionBar - right: 48px; // align with that of right-column bubbles - } - .mx_ReadReceiptGroup { - right: -18px; // match alignment to RRs of chat bubbles - } - - &::before { - right: 0; // match alignment of the hover background to that of chat bubbles - } + .mx_ReadReceiptGroup { + // match alignment to RRs of chat bubbles + inset-inline-end: calc(-1 * var(--ReadReceiptGroup_EventBubbleTile-spacing-end) + 60px); } - } -} - -/* events that do not require bubble layout */ -.mx_GenericEventListSummary[data-layout=bubble], -.mx_EventTile.mx_EventTile_bad[data-layout=bubble] { - .mx_EventTile_line { - background: transparent; - } - &:hover { &::before { - background: transparent; + inset-inline-end: 0; // match alignment of the hover background to that of chat bubbles } } } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 30776b32b7f..ad183a6692f 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -21,6 +21,11 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss .mx_EventTile { flex-shrink: 0; + .mx_EventTile_avatar { + cursor: pointer; + user-select: none; + } + .mx_EventTile_receiptSent, .mx_EventTile_receiptSending { position: relative; @@ -65,9 +70,67 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss } } + .mx_ReactionsRow { + display: flex; + flex-flow: wrap; + align-items: center; + gap: $spacing-4; + } + + &[data-layout=irc], &[data-layout=group] { - .mx_EventTile_line { - line-height: var(--GroupLayout-EventTile-line-height); + .mx_EventTile_e2eIcon { + position: absolute; + } + + .mx_MImageBody { + margin-right: 34px; + } + } + + &[data-layout=group] { + > .mx_DisambiguatedProfile { + line-height: $font-20px; + margin-left: $left-gutter; + max-width: calc(100% - $left-gutter); + } + + > .mx_EventTile_avatar { + position: absolute; + z-index: 9; + } + + .mx_EventTile_avatar { + top: 14px; + left: $spacing-8; + } + + .mx_MessageTimestamp { + position: absolute; // for modern layout + } + + .mx_EventTile_line, + .mx_EventTile_reply { + padding-top: 1px; + padding-bottom: 3px; + line-height: $font-22px; + } + + .mx_EventTile_e2eIcon { + inset: 6px 0 0 44px; + } + + .mx_EventTile_msgOption { + margin-right: 10px; + } + + .mx_ThreadSummary, + .mx_ThreadSummaryIcon { + margin-left: $left-gutter; + } + + .mx_ReactionsRow { + margin: $spacing-4 64px; } } } @@ -79,18 +142,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss font-size: $font-14px; position: relative; - .mx_ThreadSummary, - .mx_ThreadSummaryIcon { - margin-left: 64px; - } - - .mx_EventTile_avatar { - top: 14px; - left: 8px; - cursor: pointer; - user-select: none; - } - &.mx_EventTile_info { padding-top: 0; @@ -128,8 +179,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss display: inline-block; padding-bottom: 0px; padding-top: 0px; - margin: 0px; - max-width: calc(100% - $left-gutter); } &.mx_EventTile_isEditing .mx_MessageTimestamp { @@ -194,11 +243,11 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss padding-left: 2px; padding-right: 2px; cursor: pointer; - } - .mx_EventTile_searchHighlight a { - background-color: $accent; - color: $accent-fg-color; + a { + background-color: $accent; + color: $accent-fg-color; + } } &.mx_EventTile_contextual { @@ -216,11 +265,9 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss marker being in between. Content overflows. */ height: 1px; - margin-right: 10px; - } - - .mx_EventTile_msgOption a { - text-decoration: none; + a { + text-decoration: none; + } } /* De-zalgoing */ @@ -252,36 +299,19 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss } .mx_MImageBody { - margin-right: 34px; - .mx_MImageBody_thumbnail_container { justify-content: flex-start; min-height: $font-44px; min-width: $font-44px; } } - - .mx_EventTile_e2eIcon { - position: absolute; - top: 6px; - left: 44px; - bottom: 0; - right: 0; - } - - .mx_ReactionsRow { - margin: 0; - padding: 4px 64px; - } } -.mx_GenericEventListSummary:not([data-layout=bubble]) { - .mx_EventTile_line { - padding-left: $left-gutter; +.mx_GenericEventListSummary:not([data-layout=bubble]) .mx_EventTile_line { + padding-left: $left-gutter; - .mx_RedactedBody { - line-height: 1; // remove spacing between lines - } + .mx_RedactedBody { + line-height: 1; // remove spacing between lines } } @@ -421,12 +451,12 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss .mx_ThreadSummaryIcon, .mx_EventTile_line { /* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */ - margin-right: 110px; + margin-right: 80px; min-height: $font-14px; } .mx_ThreadSummary { - max-width: min(calc(100% - $left-gutter - 110px), 600px); // leave space on both left & right gutters + max-width: min(calc(100% - $left-gutter - 80px), 600px); // leave space on both left & right gutters } } @@ -518,8 +548,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss } } -/* Various markdown overrides */ - .mx_EventTile_body { a:hover { text-decoration: underline; @@ -527,6 +555,11 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss pre { border: 1px solid transparent; + + .mx_EventTile:hover &, + .mx_EventTile.focus-visible:focus-within & { + border: 1px solid $tertiary-content; + } } // selector wrongly applies to pill avatars but those have explicit width/height passed at a higher specificity @@ -534,16 +567,50 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss object-fit: contain; object-position: left top; } -} -.mx_EventTile_clamp { - .mx_EventTile_body { + .mx_EventTile_clamp & { -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; } + + .mx_EventTile_pre_container { + // For correct positioning of _copyButton (See TextualBody) + position: relative; + + &:focus-within, + &:hover { + .mx_EventTile_button { + visibility: visible; + } + } + + .mx_EventTile_collapsedCodeBlock { + max-height: 30vh; + } + + // Inserted adjacent to
 blocks, (See TextualBody)
+        .mx_EventTile_button {
+            position: absolute;
+            top: $spacing-8;
+            right: $spacing-8;
+            width: 19px;
+            height: 19px;
+            visibility: hidden;
+            background-color: $message-action-bar-fg-color;
+
+            &.mx_EventTile_buttonBottom {
+                top: 33px;
+            }
+
+            &.mx_EventTile_collapseButton,
+            &.mx_EventTile_expandButton {
+                mask-size: 75%;
+            }
+        }
+    }
 }
 
 .mx_EventTile_lineNumbers {
@@ -558,11 +625,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
     }
 }
 
-.mx_EventTile:hover .mx_EventTile_body pre,
-.mx_EventTile.focus-visible:focus-within .mx_EventTile_body pre {
-    border: 1px solid $tertiary-content;
-}
-
 .mx_EventTile_button {
     display: inline-block;
     cursor: pointer;
@@ -586,44 +648,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
     mask-image: url("$(res)/img/element-icons/maximise-expand.svg");
 }
 
-.mx_EventTile_body .mx_EventTile_pre_container {
-    // For correct positioning of _copyButton (See TextualBody)
-    position: relative;
-
-    &:focus-within,
-    &:hover {
-        .mx_EventTile_button {
-            visibility: visible;
-        }
-    }
-
-    .mx_EventTile_collapsedCodeBlock {
-        max-height: 30vh;
-    }
-
-    // Inserted adjacent to 
 blocks, (See TextualBody)
-    .mx_EventTile_button {
-        position: absolute;
-        top: 8px;
-        right: 8px;
-        width: 19px;
-        height: 19px;
-        visibility: hidden;
-        background-color: $message-action-bar-fg-color;
-
-        &.mx_EventTile_buttonBottom {
-            top: 33px;
-        }
-
-        &.mx_EventTile_collapseButton,
-        &.mx_EventTile_expandButton {
-            mask-size: 75%;
-        }
-    }
-}
-
-/* end of overrides */
-
 .mx_EventTile_keyRequestInfo {
     font-size: $font-12px;
 
@@ -665,10 +689,10 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
     .mx_EventTile_line {
         padding-left: 0;
         margin-right: 0;
-    }
 
-    .mx_EventTile_line span {
-        padding: 4px 8px;
+        span {
+            padding: $spacing-4 $spacing-8;
+        }
     }
 
     a {
@@ -693,20 +717,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
     }
 }
 
-@media only screen and (max-width: 480px) {
-
-    .mx_EventTile_line,
-    .mx_EventTile_reply {
-        padding-left: 0;
-        margin-right: 0;
-    }
-
-    .mx_EventTile_content {
-        margin-top: 10px;
-        margin-right: 0;
-    }
-}
-
 .mx_ThreadPanel_replies::before,
 .mx_ThreadSummaryIcon::before,
 .mx_ThreadSummary::before {
@@ -740,6 +750,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
     $borderRadius: $border-radius-8px;
     $padding: $spacing-8;
     $hrHeight: 1px;
+    $notification-dot-size: 8px; // notification dot next to the timestamp
 
     margin: calc(var(--topOffset) + $hrHeight) 0 var(--topOffset); // include the height of horizontal line
     padding: $padding $spacing-24 $padding $padding;
@@ -759,29 +770,34 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
         box-shadow: none; // don't show the verification left stroke in the thread list
     }
 
-    &::after {
+    &::after,
+    &::before {
         content: "";
         position: absolute;
-        left: calc(var(--leftOffset) + $padding);
-        right: $spacing-24; // 24px: 32px - 8px (right padding)
+    }
+
+    &::after {
+        $inset-block-start: auto;
+        $inset-inline-end: calc(32px - $padding);
+        $inset-block-end: calc(-1 * var(--topOffset) - $hrHeight); // exclude the height of horizontal line
+        $inset-inline-start: calc(var(--leftOffset) + $padding);
+        inset: $inset-block-start $inset-inline-end $inset-block-end $inset-inline-start;
+
         height: $hrHeight;
-        bottom: calc(-1 * var(--topOffset) - $hrHeight); // exclude the height of horizontal line
         background-color: $quinary-content;
         pointer-events: none; // disable the message action bar on hover
     }
 
     &::before {
-        content: "";
-        position: absolute;
         inset: 0;
     }
 
     // Display notification dot
     &[data-notification]::before {
-        width: 8px;
-        height: 8px;
+        width: $notification-dot-size;
+        height: $notification-dot-size;
         border-radius: 50%;
-        inset: 14px 8px auto auto; // 14px: align the dot with the timestamp row
+        inset: 14px $spacing-8 auto auto; // 14px: align the dot with the timestamp row
     }
 
     &[data-notification=total]::before {
@@ -805,36 +821,47 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
     }
 
     .mx_EventTile_avatar {
-        top: $padding;
-        left: $padding;
+        inset: $padding auto auto $padding;
     }
 
     .mx_DisambiguatedProfile {
-        margin-right: 12px;
+        margin-inline: 0 $spacing-12;
         display: inline-flex;
         flex: 1;
-    }
 
-    .mx_DisambiguatedProfile_displayName,
-    .mx_DisambiguatedProfile_mxid {
-        display: block;
-        overflow: hidden;
-        text-overflow: ellipsis;
-    }
+        .mx_DisambiguatedProfile_displayName,
+        .mx_DisambiguatedProfile_mxid {
+            display: block;
+            overflow: hidden;
+            text-overflow: ellipsis;
+        }
 
-    .mx_DisambiguatedProfile_displayName {
-        flex: none;
-        max-width: 100%;
-    }
+        .mx_DisambiguatedProfile_displayName {
+            flex: none;
+            max-width: 100%;
+        }
 
-    .mx_DisambiguatedProfile_mxid {
-        flex: 1;
+        .mx_DisambiguatedProfile_mxid {
+            flex: 1;
+        }
     }
 
     .mx_EventTile_line {
         width: 100%;
         box-sizing: border-box;
-        border-radius: $borderRadius !important; // override 4px
+        padding-bottom: 0;
+
+        .mx_ThreadPanel_replies {
+            margin-top: $spacing-8;
+            display: flex;
+            align-items: center;
+            position: relative;
+
+            .mx_ThreadPanel_replies_amount {
+                @mixin ThreadsAmount;
+                font-size: $font-12px; // Same font size as the counter on the main panel
+            }
+        }
     }
 
     .mx_DisambiguatedProfile,
@@ -845,21 +872,23 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
     .mx_MessageTimestamp {
         max-width: 80px;
         width: auto;
+        position: initial;
     }
 }
 
+// For style rules of ThreadView, see _ThreadPanel.scss
 .mx_ThreadView {
     --ThreadView_group_spacing-start: 56px; // 56px: 64px - 8px (padding)
     --ThreadView_group_spacing-end: 8px; // same as padding
 
-    .mx_EventTile_roomName {
-        display: none;
-    }
-
     .mx_EventTile {
         display: flex;
         flex-direction: column;
 
+        .mx_EventTile_roomName {
+            display: none;
+        }
+
         .mx_EventTile_line {
             padding-top: var(--BaseCard_EventTile_line-padding-block);
             padding-bottom: var(--BaseCard_EventTile_line-padding-block);
@@ -878,9 +907,65 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
             font-size: $font-10px;
         }
 
+        // handling for hidden events (e.g reactions) in the thread view
+        &.mx_EventTile_info {
+            padding-top: 0;
+
+            .mx_EventTile_avatar {
+                position: absolute;
+                top: 1.5px; // Align with hidden event content
+                margin-top: 0;
+                margin-bottom: 0;
+                width: 14px; // avatar img size
+                height: 14px; // avatar img size
+            }
+
+            .mx_ViewSourceEvent_toggle {
+                display: none; // hide the hidden event expand button, not enough space, view source can still be used
+            }
+
+            &.mx_EventTile_selected .mx_EventTile_line,
+            .mx_EventTile_line {
+                $line-height: $font-12px;
+
+                padding-inline-start: 0;
+                line-height: $line-height;
+
+                .mx_EventTile_content,
+                .mx_RedactedBody {
+                    width: auto;
+                    margin-inline-start: calc(var(--ThreadView_group_spacing-start) + 14px + 6px); // 14px: avatar width, 6px: 20px - 14px
+                    font-size: $line-height;
+                }
+            }
+
+            &:not([data-layout=bubble]) {
+                .mx_MessageTimestamp {
+                    top: 2px; // Align with avatar
+                }
+
+                .mx_EventTile_avatar {
+                    left: calc($MessageTimestamp_width + 14px - 4px); // 14px: avatar width, 4px: align with text
+                    z-index: 9; // position above the hover styling
+                }
+            }
+
+            &[data-layout=bubble] {
+                .mx_EventTile_avatar {
+                    inset-inline-start: 0;
+                }
+            }
+        }
+
         &:not([data-layout=bubble]) {
             padding-top: $spacing-16;
 
+            &:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
+            &:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line,
+            &:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line {
+                padding-inline-start: 0; // Override
+            }
+
             .mx_EventTile_line {
                 padding-top: var(--BaseCard_EventTile_line-padding-block);
                 padding-bottom: var(--BaseCard_EventTile_line-padding-block);
@@ -894,8 +979,17 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
         }
 
         &[data-layout=bubble] {
-            margin-inline-start: var(--BaseCard_EventTile-spacing-horizontal);
-            margin-inline-end: var(--BaseCard_EventTile-spacing-horizontal);
+            margin-inline-start: var(--BaseCard_EventTile-spacing-inline);
+            margin-inline-end: var(--BaseCard_EventTile-spacing-inline);
+
+            &::before {
+                inset-inline: calc(-1 * var(--BaseCard_EventTile-spacing-inline));
+                z-index: auto; // enable background color on hover
+            }
+
+            .mx_ReactionsRow {
+                position: relative; // display on hover
+            }
 
             .mx_EventTile_line.mx_EventTile_mediaLine {
                 padding-block: 0;
@@ -907,77 +1001,175 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
                 align-items: flex-end;
 
                 .mx_EventTile_line.mx_EventTile_mediaLine {
-                    margin: 0 var(--EventBubbleTile_line-margin-inline-end) 0 0; // align with normal messages
+                    margin: 0 var(--EventTile_bubble_line-margin-inline-end) 0 0; // align with normal messages
                 }
             }
         }
-    }
 
-    .mx_EventTile[data-layout=group] {
-        width: 100%;
-
-        .mx_EventTile_content,
-        .mx_HiddenBody,
-        .mx_RedactedBody,
-        .mx_UnknownBody,
-        .mx_MPollBody,
-        .mx_MLocationBody,
-        .mx_ReplyChain_wrapper,
-        .mx_ReactionsRow {
-            margin-inline-start: var(--ThreadView_group_spacing-start);
-            margin-inline-end: var(--ThreadView_group_spacing-end);
+        &[data-layout=group] {
+            width: 100%;
 
             .mx_EventTile_content,
             .mx_HiddenBody,
             .mx_RedactedBody,
-            .mx_MImageBody {
-                margin: 0;
-            }
-        }
-
-        .mx_ReplyChain_wrapper {
+            .mx_UnknownBody,
+            .mx_MPollBody,
             .mx_MLocationBody,
-            .mx_UnknownBody { // Error message inside ReplyTile
-                margin-inline: unset;
-            }
-        }
-
-        .mx_EventTile_mediaLine {
-            // such as MImageBody
-            > div,
-            > span {
+            .mx_ReplyChain_wrapper,
+            .mx_ReactionsRow {
                 margin-inline-start: var(--ThreadView_group_spacing-start);
                 margin-inline-end: var(--ThreadView_group_spacing-end);
+
+                .mx_EventTile_content,
+                .mx_HiddenBody,
+                .mx_RedactedBody,
+                .mx_MImageBody {
+                    margin: 0;
+                }
             }
 
-            // such as MAudioBody and MFileBody
-            > span {
-                display: block; // Apply the margin declarations to span element
+            .mx_ReplyChain_wrapper {
+                .mx_MLocationBody,
+                .mx_UnknownBody { // Error message inside ReplyTile
+                    margin-inline: unset;
+                }
             }
-        }
 
-        .mx_MessageTimestamp {
-            top: 2px; // Align with mx_EventTile_content
-        }
+            .mx_EventTile_mediaLine {
+                // such as MImageBody
+                > div,
+                > span {
+                    margin-inline-start: var(--ThreadView_group_spacing-start);
+                    margin-inline-end: var(--ThreadView_group_spacing-end);
+                }
 
-        .mx_EventTile_senderDetails {
-            display: flex;
-            align-items: center;
-            gap: $spacing-16; // gap between the avatar and the sender ID
-            padding-inline-start: $spacing-8;
+                // such as MAudioBody and MFileBody
+                > span {
+                    display: block; // Apply the margin declarations to span element
+                }
+            }
 
-            a {
-                flex: 1;
-                min-width: unset;
-                max-width: 100%;
+            .mx_MessageTimestamp {
+                top: 2px; // Align with mx_EventTile_content
+            }
+
+            .mx_EventTile_senderDetails {
                 display: flex;
                 align-items: center;
+                gap: $spacing-16; // gap between the avatar and the sender ID
+                padding-inline-start: $spacing-8;
 
-                .mx_DisambiguatedProfile {
-                    margin-left: 8px;
+                a {
                     flex: 1;
+                    min-width: unset;
+                    max-width: 100%;
+                    display: flex;
+                    align-items: center;
+
+                    .mx_DisambiguatedProfile {
+                        margin-left: 8px;
+                        flex: 1;
+                    }
+                }
+            }
+        }
+    }
+
+    .mx_GenericEventListSummary {
+        &:not([data-layout=bubble]) > .mx_EventTile_line {
+            padding-inline-start: var(--ThreadView_group_spacing-start); // align summary text with message text
+            padding-inline-end: var(--ThreadView_group_spacing-end); // align summary text with message text
+        }
+    }
+}
+
+// Cascading - compact modern layout on the main timeline and the right panel
+.mx_MatrixChat_useCompactLayout {
+    .mx_EventTile {
+        // Override :not([data-layout="bubble"])
+        &[data-layout=group] {
+            padding-top: $spacing-4;
+
+            &.mx_EventTile_info {
+                padding-top: 0; // same as the padding for non-compact .mx_EventTile.mx_EventTile_info
+                font-size: $font-13px;
+
+                .mx_EventTile_avatar {
+                    top: $spacing-4;
+                }
+
+                .mx_EventTile_line,
+                .mx_EventTile_reply {
+                    line-height: $font-20px;
+                }
+            }
+
+            &.mx_EventTile_emote {
+                padding-top: $spacing-8; // add a bit more space for emotes so that avatars don't collide
+
+                &.mx_EventTile_continuation {
+                    padding-top: 0;
+
+                    .mx_EventTile_line,
+                    .mx_EventTile_reply {
+                        padding-top: 0;
+                        padding-bottom: 0;
+                    }
+                }
+
+                .mx_EventTile_avatar {
+                    top: 2px;
+                }
+
+                .mx_EventTile_line,
+                .mx_EventTile_reply {
+                    padding-top: 0;
+                    padding-bottom: 1px;
+                }
+            }
+
+            .mx_EventTile_avatar {
+                top: 2px;
+            }
+
+            .mx_EventTile_line,
+            .mx_EventTile_reply {
+                padding-top: 0;
+                padding-bottom: 0;
+            }
+
+            .mx_EventTile_e2eIcon {
+                top: 3px;
+            }
+
+            .mx_DisambiguatedProfile {
+                font-size: $font-13px;
+            }
+
+            .mx_ReadReceiptGroup {
+                // This aligns the avatar with the last line of the
+                // message. We want to move it one line up - 2rem
+                top: -2rem;
+            }
+
+            .mx_EventTile_content .markdown-body {
+                p,
+                ul,
+                ol,
+                dl,
+                blockquote,
+                pre,
+                table {
+                    margin-bottom: $spacing-4; // 1/4 of the non-compact margin-bottom
                 }
             }
         }
     }
 }
+
+// Media query for mobile UI
+@media only screen and (max-width: 480px) {
+    .mx_EventTile_content {
+        margin-right: 0;
+    }
+}
diff --git a/res/css/views/rooms/_GroupLayout.scss b/res/css/views/rooms/_GroupLayout.scss
deleted file mode 100644
index 28ba80b3895..00000000000
--- a/res/css/views/rooms/_GroupLayout.scss
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-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.
-*/
-
-$left-gutter: 64px;
-
-.mx_GroupLayout {
-    --GroupLayout-EventTile-line-height: $font-22px;
-
-    .mx_EventTile {
-        > .mx_DisambiguatedProfile {
-            line-height: $font-20px;
-            margin-left: $left-gutter;
-        }
-
-        > .mx_EventTile_avatar {
-            position: absolute;
-            z-index: 9;
-        }
-
-        .mx_MessageTimestamp {
-            position: absolute; // for modern layout
-        }
-
-        .mx_EventTile_line,
-        .mx_EventTile_reply {
-            padding-top: 1px;
-            padding-bottom: 3px;
-        }
-
-        .mx_EventTile_reply {
-            line-height: var(--GroupLayout-EventTile-line-height);
-        }
-    }
-
-    li > .mx_DateSeparator {
-        margin-top: 9px;
-    }
-}
-
-/* Compact layout overrides */
-
-.mx_MatrixChat_useCompactLayout .mx_GroupLayout {
-    .mx_EventTile {
-        padding-top: 4px;
-
-        .mx_EventTile_line,
-        .mx_EventTile_reply {
-            padding-top: 0;
-            padding-bottom: 0;
-        }
-
-        &.mx_EventTile_info {
-            // same as the padding for non-compact .mx_EventTile.mx_EventTile_info
-            padding-top: 0px;
-            font-size: $font-13px;
-
-            .mx_EventTile_line,
-            .mx_EventTile_reply {
-                line-height: $font-20px;
-            }
-
-            .mx_EventTile_avatar {
-                top: 4px;
-            }
-        }
-
-        .mx_DisambiguatedProfile {
-            font-size: $font-13px;
-        }
-
-        &.mx_EventTile_emote {
-            // add a bit more space for emotes so that avatars don't collide
-            padding-top: 8px;
-
-            .mx_EventTile_avatar {
-                top: 2px;
-            }
-
-            .mx_EventTile_line,
-            .mx_EventTile_reply {
-                padding-top: 0px;
-                padding-bottom: 1px;
-            }
-        }
-
-        &.mx_EventTile_emote.mx_EventTile_continuation {
-            padding-top: 0;
-
-            .mx_EventTile_line,
-            .mx_EventTile_reply {
-                padding-top: 0px;
-                padding-bottom: 0px;
-            }
-        }
-
-        .mx_EventTile_avatar {
-            top: 2px;
-        }
-
-        .mx_EventTile_e2eIcon {
-            top: 3px;
-        }
-
-        .mx_ReadReceiptGroup {
-            // This aligns the avatar with the last line of the
-            // message. We want to move it one line up - 2rem
-            top: -2rem;
-        }
-
-        .mx_EventTile_content .markdown-body {
-            p, ul, ol, dl, blockquote, pre, table {
-                margin-bottom: 4px; // 1/4 of the non-compact margin-bottom
-            }
-        }
-    }
-
-    .mx_RoomView_MessageList h2 {
-        margin-top: 6px;
-    }
-}
diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss
index d689d152348..36b06d8cc2c 100644
--- a/res/css/views/rooms/_IRCLayout.scss
+++ b/res/css/views/rooms/_IRCLayout.scss
@@ -39,11 +39,6 @@ $irc-line-height: $font-18px;
             margin-right: $right-padding;
         }
 
-        .mx_ThreadSummary {
-            margin-right: 0;
-            margin-left: 0;
-        }
-
         > .mx_EventTile_msgOption {
             order: 5;
             flex-shrink: 0;
@@ -63,11 +58,9 @@ $irc-line-height: $font-18px;
             min-width: 0;
         }
 
-        > .mx_EventTile_avatar {
+        .mx_EventTile_avatar {
             order: 1;
             position: relative;
-            top: 0;
-            left: 0;
             flex-shrink: 0;
             height: $irc-line-height;
             display: flex;
@@ -87,12 +80,7 @@ $irc-line-height: $font-18px;
             text-align: right;
         }
 
-        > .mx_EventTile_e2eIcon {
-            position: absolute;
-            right: unset;
-            left: unset;
-            top: 0;
-
+        .mx_EventTile_e2eIcon {
             padding: 0;
 
             flex-shrink: 0;
@@ -120,15 +108,10 @@ $irc-line-height: $font-18px;
         .mx_EditMessageComposer_buttons {
             position: relative;
         }
-
-        .mx_ReactionsRow {
-            padding-left: 0;
-            padding-right: 0;
-        }
     }
 
     .mx_EventTile_emote {
-        > .mx_EventTile_avatar {
+        .mx_EventTile_avatar {
             margin-left: calc(var(--name-width) + $icon-width + $right-padding);
         }
     }
@@ -137,15 +120,8 @@ $irc-line-height: $font-18px;
         margin: 0;
     }
 
-    .mx_GenericEventListSummary {
-        > .mx_EventTile_line {
-            padding-left: calc(var(--name-width) + $icon-width + $MessageTimestamp_width + 3 * $right-padding); // 15 px of padding
-        }
-
-        .mx_GenericEventListSummary_avatars {
-            padding: 0;
-            margin: 0 9px 0 0;
-        }
+    .mx_GenericEventListSummary > .mx_EventTile_line {
+        padding-left: calc(var(--name-width) + $icon-width + $MessageTimestamp_width + 3 * $right-padding); // 15 px of padding
     }
 
     .mx_EventTile.mx_EventTile_info {
@@ -173,6 +149,7 @@ $irc-line-height: $font-18px;
 
     .mx_DisambiguatedProfile {
         width: var(--name-width);
+        margin-inline-end: 0; // override mx_EventTile > *
         display: flex;
         order: 2;
         flex-shrink: 0;
@@ -217,7 +194,6 @@ $irc-line-height: $font-18px;
         margin: 0;
         .mx_DisambiguatedProfile {
             order: unset;
-            max-width: unset;
             width: unset;
             background: transparent;
         }
diff --git a/res/css/views/rooms/_PinnedEventTile.scss b/res/css/views/rooms/_PinnedEventTile.scss
index d31b1cbcda4..82de811b285 100644
--- a/res/css/views/rooms/_PinnedEventTile.scss
+++ b/res/css/views/rooms/_PinnedEventTile.scss
@@ -34,6 +34,14 @@ limitations under the License.
         border-top: 1px solid $menu-border-color;
     }
 
+    .mx_PinnedEventTile_senderAvatar,
+    .mx_PinnedEventTile_sender,
+    .mx_PinnedEventTile_unpinButton,
+    .mx_PinnedEventTile_message,
+    .mx_PinnedEventTile_footer {
+        min-width: 0; // Prevent a grid blowout
+    }
+
     .mx_PinnedEventTile_senderAvatar {
         grid-area: avatar;
     }
diff --git a/res/css/views/rooms/_ReadReceiptGroup.scss b/res/css/views/rooms/_ReadReceiptGroup.scss
index a67ea4d4861..290d3da6829 100644
--- a/res/css/views/rooms/_ReadReceiptGroup.scss
+++ b/res/css/views/rooms/_ReadReceiptGroup.scss
@@ -15,11 +15,13 @@ limitations under the License.
 */
 
 .mx_ReadReceiptGroup {
+    --ReadReceiptGroup_EventBubbleTile-spacing-end: 78px;
+
     position: relative;
     display: inline-block;
     // This aligns the avatar with the last line of the
     // message. We want to move it one line up
-    // See .mx_GroupLayout .mx_EventTile .mx_EventTile_line in _GroupLayout.scss
+    // See .mx_EventTile[data-layout=group] .mx_EventTile_line in _EventTile.scss
     top: calc(-$font-22px - 3px);
     user-select: none;
     z-index: 1;
diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss
index fbb65bbecb7..2a12121ef3c 100644
--- a/res/css/views/rooms/_RoomHeader.scss
+++ b/res/css/views/rooms/_RoomHeader.scss
@@ -44,6 +44,10 @@ limitations under the License.
     .mx_InviteOnlyIcon_large {
         margin: 0;
     }
+
+    .mx_BetaCard_betaPill {
+        margin-right: $spacing-8;
+    }
 }
 
 .mx_RoomHeader_spinner {
@@ -165,6 +169,12 @@ limitations under the License.
     display: -webkit-box;
 }
 
+.mx_RoomHeader_topic .mx_Emoji {
+    // Undo font size increase to prevent vertical cropping and ensure the same size
+    // as in plain text emojis
+    font-size: inherit;
+}
+
 .mx_RoomHeader_avatar {
     flex: 0;
     margin: 0 6px 0 7px;
diff --git a/res/css/views/rooms/_RoomPreviewCard.scss b/res/css/views/rooms/_RoomPreviewCard.scss
index b561bf666df..3ee37e585d2 100644
--- a/res/css/views/rooms/_RoomPreviewCard.scss
+++ b/res/css/views/rooms/_RoomPreviewCard.scss
@@ -71,6 +71,12 @@ limitations under the License.
                 color: $secondary-content;
             }
         }
+
+        // XXX Remove this when video rooms leave beta
+        .mx_BetaCard_betaPill {
+            margin-inline-start: auto;
+            align-self: start;
+        }
     }
 
     .mx_RoomPreviewCard_avatar {
diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss
index 7d43155625e..9b57f54b8a4 100644
--- a/res/css/views/rooms/_RoomTile.scss
+++ b/res/css/views/rooms/_RoomTile.scss
@@ -89,40 +89,6 @@ limitations under the License.
         .mx_RoomTile_subtitle {
             line-height: $font-18px;
             color: $secondary-content;
-
-            .mx_RoomTile_videoIndicator {
-                &::before {
-                    display: inline-block;
-                    vertical-align: text-bottom;
-                    content: '';
-                    background-color: $secondary-content;
-                    mask-image: url('$(res)/img/element-icons/call/video-call.svg');
-                    mask-size: 16px;
-                    width: 16px;
-                    height: 16px;
-                    margin-right: 4px;
-                }
-
-                &.mx_RoomTile_videoIndicator_active {
-                    color: $accent;
-
-                    &::before {
-                        background-color: $accent;
-                    }
-                }
-            }
-
-            .mx_RoomTile_videoParticipants::before {
-                display: inline-block;
-                vertical-align: text-bottom;
-                content: '';
-                background-color: $secondary-content;
-                mask-image: url('$(res)/img/element-icons/group-members.svg');
-                mask-size: 16px;
-                width: 16px;
-                height: 16px;
-                margin-right: 2px;
-            }
         }
     }
 
diff --git a/res/css/views/rooms/_VideoRoomSummary.scss b/res/css/views/rooms/_VideoRoomSummary.scss
new file mode 100644
index 00000000000..b3e9af3f651
--- /dev/null
+++ b/res/css/views/rooms/_VideoRoomSummary.scss
@@ -0,0 +1,51 @@
+/*
+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_VideoRoomSummary {
+    .mx_VideoRoomSummary_indicator {
+        &::before {
+            display: inline-block;
+            vertical-align: text-bottom;
+            content: '';
+            background-color: $secondary-content;
+            mask-image: url('$(res)/img/element-icons/call/video-call.svg');
+            mask-size: 16px;
+            width: 16px;
+            height: 16px;
+            margin-right: 4px;
+        }
+
+        &.mx_VideoRoomSummary_indicator_active {
+            color: $accent;
+
+            &::before {
+                background-color: $accent;
+            }
+        }
+    }
+
+    .mx_VideoRoomSummary_participants::before {
+        display: inline-block;
+        vertical-align: text-bottom;
+        content: '';
+        background-color: $secondary-content;
+        mask-image: url('$(res)/img/element-icons/group-members.svg');
+        mask-size: 16px;
+        width: 16px;
+        height: 16px;
+        margin-right: 2px;
+    }
+}
diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss
index ab5afbcd3f1..8c63a00aa8a 100644
--- a/res/css/views/settings/_AvatarSetting.scss
+++ b/res/css/views/settings/_AvatarSetting.scss
@@ -37,7 +37,7 @@ limitations under the License.
         text-align: center;
 
         > span {
-            color: #fff; // hardcoded to contrast with background
+            color: $primary-content;
             position: relative; // tricks the layout engine into putting this on top of the bg
             font-weight: 500;
         }
@@ -51,7 +51,7 @@ limitations under the License.
             right: 0;
 
             opacity: 0.5;
-            background-color: $settings-profile-overlay-placeholder-fg-color;
+            background-color: $quinary-content;
             border-radius: 90px;
         }
     }
@@ -91,7 +91,7 @@ limitations under the License.
     }
 
     .mx_AvatarSetting_avatarPlaceholder::before {
-        background-color: $settings-profile-overlay-placeholder-fg-color;
+        background-color: $quinary-content;
         mask: url("$(res)/img/feather-customised/user.svg");
         mask-repeat: no-repeat;
         mask-size: 36px;
@@ -108,7 +108,7 @@ limitations under the License.
         width: 32px;
         height: 32px;
         border-radius: 32px;
-        background-color: $settings-profile-button-bg-color;
+        background-color: $secondary-content;
 
         position: absolute;
         bottom: 0;
@@ -123,7 +123,7 @@ limitations under the License.
         mask-repeat: no-repeat;
         mask-position: center;
         mask-size: 55%;
-        background-color: $settings-profile-overlay-placeholder-fg-color;
+        background-color: $quinary-content;
         mask-image: url('$(res)/img/feather-customised/edit.svg');
     }
 }
diff --git a/res/css/views/settings/_DevicesPanel.scss b/res/css/views/settings/_DevicesPanel.scss
index 1732688cc8b..9cbdb6a2a1b 100644
--- a/res/css/views/settings/_DevicesPanel.scss
+++ b/res/css/views/settings/_DevicesPanel.scss
@@ -19,9 +19,8 @@ limitations under the License.
     max-width: 880px;
 
     hr {
-        opacity: 0.2;
         border: none;
-        border-bottom: 1px solid $primary-content;
+        border-bottom: 1px solid $quinary-content;
     }
 }
 
diff --git a/res/css/views/settings/_E2eAdvancedPanel.scss b/res/css/views/settings/_E2eAdvancedPanel.scss
deleted file mode 100644
index 3f180e6fcd2..00000000000
--- a/res/css/views/settings/_E2eAdvancedPanel.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-.mx_E2eAdvancedPanel_settingLongDescription {
-    margin-right: 150px;
-}
diff --git a/res/css/views/settings/_FontScalingPanel.scss b/res/css/views/settings/_FontScalingPanel.scss
index bd2b0af61c1..09c5600e33b 100644
--- a/res/css/views/settings/_FontScalingPanel.scss
+++ b/res/css/views/settings/_FontScalingPanel.scss
@@ -16,9 +16,6 @@ limitations under the License.
 
 .mx_FontScalingPanel {
     color: $primary-content;
-    > .mx_SettingsTab_SubHeading {
-        margin-bottom: 32px;
-    }
 }
 
 .mx_FontScalingPanel .mx_Field {
@@ -52,12 +49,54 @@ limitations under the License.
         margin-top: 3px;
     }
 
-    .mx_EventTile_msgOption {
-        display: none;
+    .mx_FontScalingPanel_preview {
+        border: 1px solid $quinary-content;
+        border-radius: 10px;
+        padding: 0 $spacing-16 9px $spacing-16;
+        pointer-events: none;
+        display: flow-root;
+
+        &.mx_IRCLayout {
+            padding-top: 9px; // TODO: Use a spacing variable
+        }
+
+        .mx_EventTile[data-layout=bubble] {
+            margin-top: 30px; // TODO: Use a spacing variable
+        }
+
+        .mx_EventTile_msgOption {
+            display: none;
+        }
+    }
+
+    .mx_FontScalingPanel_fontSlider {
+        display: flex;
+        align-items: center;
+        padding: 15px $spacing-20 35px; // TODO: Use spacing variables
+        background: rgba($quinary-content, 0.2);
+        border-radius: 10px;
+        font-size: $font-10px;
+        margin-top: $spacing-24;
+        margin-bottom: $spacing-24;
+
+        .mx_FontScalingPanel_fontSlider_smallText,
+        .mx_FontScalingPanel_fontSlider_largeText {
+            font-weight: 500;
+        }
+
+        .mx_FontScalingPanel_fontSlider_smallText {
+            font-size: $font-15px;
+            padding-inline-end: $spacing-20;
+        }
+
+        .mx_FontScalingPanel_fontSlider_largeText {
+            font-size: $font-18px;
+            padding-inline-start: $spacing-20;
+        }
     }
 
-    &.mx_IRCLayout {
-        padding-top: 9px;
+    .mx_FontScalingPanel_customFontSizeField {
+        margin-inline-start: var(--AppearanceUserSettingsTab_Field-margin-inline-start);
     }
 }
 
diff --git a/res/css/views/settings/_IntegrationManager.scss b/res/css/views/settings/_IntegrationManager.scss
index 043e7201caf..f91d3fdd6c9 100644
--- a/res/css/views/settings/_IntegrationManager.scss
+++ b/res/css/views/settings/_IntegrationManager.scss
@@ -14,31 +14,33 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-.mx_IntegrationManager .mx_Dialog {
-    width: 60%;
-    height: 70%;
-    overflow: hidden;
-    padding: 0px;
-    max-width: initial;
-    max-height: initial;
-}
-
-.mx_IntegrationManager iframe {
-    background-color: #fff;
-    border: 0px;
-    width: 100%;
-    height: 100%;
-}
-
-.mx_IntegrationManager_loading h3 {
-    text-align: center;
-}
-
-.mx_IntegrationManager_error {
-    text-align: center;
-    padding-top: 20px;
-}
-
-.mx_IntegrationManager_error h3 {
-    color: $alert;
+.mx_IntegrationManager {
+    .mx_Dialog {
+        box-sizing: border-box;
+        width: 60%;
+        height: 70%;
+        overflow: hidden;
+        max-width: initial;
+        max-height: initial;
+    }
+
+    iframe {
+        background-color: #fff;
+        border: 0;
+        width: 100%;
+        height: 100%;
+    }
+
+    h3 {
+        margin-block: $spacing-20;
+    }
+
+    .mx_IntegrationManager_loading,
+    .mx_IntegrationManager_error {
+        text-align: center;
+    }
+
+    .mx_IntegrationManager_error h3 {
+        color: $alert;
+    }
 }
diff --git a/res/css/views/settings/_KeyboardShortcut.scss b/res/css/views/settings/_KeyboardShortcut.scss
index d3ca6cc9435..721d5fd8fef 100644
--- a/res/css/views/settings/_KeyboardShortcut.scss
+++ b/res/css/views/settings/_KeyboardShortcut.scss
@@ -20,17 +20,11 @@ limitations under the License.
         padding: 5px;
         border-radius: 4px;
         background-color: $header-panel-bg-color;
-        margin-right: 5px;
         min-width: 20px;
         text-align: center;
         display: inline-block;
         border: 1px solid $kbd-border-color;
         box-shadow: 0 2px $kbd-border-color;
-        margin-bottom: 4px;
         text-transform: capitalize;
-
-        & + kbd {
-            margin-left: 5px;
-        }
     }
 }
diff --git a/res/css/views/settings/_LayoutSwitcher.scss b/res/css/views/settings/_LayoutSwitcher.scss
index a6b623311e8..b4e7b9b9e45 100644
--- a/res/css/views/settings/_LayoutSwitcher.scss
+++ b/res/css/views/settings/_LayoutSwitcher.scss
@@ -33,7 +33,7 @@ limitations under the License.
             width: 300px;
             min-width: 0;
 
-            border: 1px solid $appearance-tab-border-color;
+            border: 1px solid $quinary-content;
             border-radius: $border-radius-10px;
 
             .mx_EventTile_msgOption,
@@ -68,11 +68,7 @@ limitations under the License.
         }
 
         .mx_StyledRadioButton {
-            border-top: 1px solid $appearance-tab-border-color;
-
-            > input + div {
-                border-color: rgba($muted-fg-color, 0.2);
-            }
+            border-top: 1px solid $quinary-content;
         }
 
         .mx_StyledRadioButton_checked {
diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss
index 6ab13058d40..a9e80880aac 100644
--- a/res/css/views/settings/_ProfileSettings.scss
+++ b/res/css/views/settings/_ProfileSettings.scss
@@ -14,48 +14,49 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-.mx_ProfileSettings_controls_topic {
-    & > textarea {
-        font-family: inherit;
-        resize: vertical;
+.mx_ProfileSettings {
+    margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end);
+    border-bottom: 1px solid $quinary-content;
+
+    .mx_ProfileSettings_avatarUpload {
+        display: none;
     }
-}
 
-.mx_ProfileSettings_profile {
-    display: flex;
-}
+    .mx_ProfileSettings_profile {
+        display: flex;
 
-.mx_ProfileSettings_controls {
-    flex-grow: 1;
-    margin-right: 54px;
-}
+        .mx_ProfileSettings_profile_controls {
+            flex-grow: 1;
+            margin-inline-end: 54px;
 
-.mx_ProfileSettings_controls .mx_Field #profileTopic {
-    height: 4em;
-}
+            .mx_Field:first-child {
+                margin-top: 0;
+            }
 
-.mx_ProfileSettings_controls .mx_Field:first-child {
-    margin-top: 0;
-}
+            .mx_ProfileSettings_profile_controls_topic {
+                & > textarea {
+                    font-family: inherit;
+                    resize: vertical;
+                }
 
-.mx_ProfileSettings_userId {
-    margin-right: $spacing-20;
-}
-
-.mx_ProfileSettings_avatarUpload {
-    display: none;
-}
+                &.mx_ProfileSettings_profile_controls_topic--room {
+                    height: 4em;
+                }
+            }
 
-.mx_ProfileSettings_profileForm {
-    @mixin mx_Settings_fullWidthField;
-}
+            .mx_ProfileSettings_profile_controls_userId {
+                margin-inline-end: $spacing-20;
+            }
+        }
+    }
 
-.mx_ProfileSettings_buttons {
-    margin-top: 10px; // 18px is already accounted for by the 

above the buttons - margin-bottom: 28px; + .mx_ProfileSettings_buttons { + margin-top: 10px; // 18px is already accounted for by the

above the buttons + margin-bottom: $spacing-28; - > .mx_AccessibleButton_kind_link { - font-size: $font-14px; - margin-right: 10px; + > .mx_AccessibleButton_kind_link { + font-size: $font-14px; + margin-inline-end: 10px; // TODO: Use a spacing variable + } } } diff --git a/res/css/views/settings/_SetIdServer.scss b/res/css/views/settings/_SetIdServer.scss index 98c64b72187..8dc634400c3 100644 --- a/res/css/views/settings/_SetIdServer.scss +++ b/res/css/views/settings/_SetIdServer.scss @@ -15,9 +15,9 @@ limitations under the License. */ .mx_SetIdServer .mx_Field_input { - @mixin mx_Settings_fullWidthField; + margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end); } .mx_SetIdServer_tooltip { - @mixin mx_Settings_tooltip; + max-width: var(--SettingsTab_tooltip-max-width); } diff --git a/res/css/views/settings/_SetIntegrationManager.scss b/res/css/views/settings/_SetIntegrationManager.scss index 521b1ee8ab7..3d98f473467 100644 --- a/res/css/views/settings/_SetIntegrationManager.scss +++ b/res/css/views/settings/_SetIntegrationManager.scss @@ -15,22 +15,26 @@ limitations under the License. */ .mx_SetIntegrationManager { - margin-top: 10px; - margin-bottom: 10px; -} + .mx_SettingsFlag { + align-items: center; + margin-top: var(--SettingsTab_heading_nth_child-margin-top); -.mx_SetIntegrationManager > .mx_SettingsTab_heading { - margin-bottom: 10px; -} + .mx_SetIntegrationManager_heading_manager { + display: flex; + align-items: center; + flex-wrap: wrap; + column-gap: $spacing-4; -.mx_SetIntegrationManager > .mx_SettingsTab_heading > .mx_SettingsTab_subheading { - display: inline-block; - padding-left: 5px; - margin-top: 0px; -} + .mx_SettingsTab_heading, + .mx_SettingsTab_subheading { + margin-top: 0; + margin-bottom: 0; + } + } -.mx_SetIntegrationManager .mx_ToggleSwitch { - display: inline-block; - float: right; - top: 9px; + .mx_ToggleSwitch { + align-self: flex-start; + min-width: var(--ToggleSwitch-min-width); // avoid compression + } + } } diff --git a/res/css/views/settings/_SpellCheckLanguages.scss b/res/css/views/settings/_SpellCheckLanguages.scss index bb322c983f4..0b5e140bd22 100644 --- a/res/css/views/settings/_SpellCheckLanguages.scss +++ b/res/css/views/settings/_SpellCheckLanguages.scss @@ -31,5 +31,5 @@ limitations under the License. } .mx_SpellCheckLanguages { - @mixin mx_Settings_fullWidthField; + margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end); } diff --git a/res/css/views/settings/_ThemeChoicePanel.scss b/res/css/views/settings/_ThemeChoicePanel.scss index 69d5735668f..9d20111a9b9 100644 --- a/res/css/views/settings/_ThemeChoicePanel.scss +++ b/res/css/views/settings/_ThemeChoicePanel.scss @@ -20,7 +20,6 @@ limitations under the License. } .mx_ThemeChoicePanel { - $radio-bg-color: $input-darker-bg-color; color: $primary-content; > .mx_ThemeSelectors { @@ -39,7 +38,7 @@ limitations under the License. display: flex; align-items: center; - background: $radio-bg-color; + background: $quinary-content; opacity: 0.4; flex-shrink: 1; @@ -49,7 +48,6 @@ limitations under the License. margin-top: 10px; font-weight: 600; - color: $muted-fg-color; .mx_StyledRadioButton_content { margin-right: 0 @@ -123,11 +121,4 @@ limitations under the License. .mx_ThemeChoicePanel_Advanced { color: $primary-content; - - .mx_ThemeChoicePanel_AdvancedToggle { - color: $accent; - cursor: pointer; - margin-bottom: 20px; - font-size: $font-15px; - } } diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 1eb4868e557..033afd1fc43 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -15,7 +15,16 @@ limitations under the License. */ .mx_SettingsTab { + --SettingsTab_section-margin-bottom-preferences-labs: 30px; + --SettingsTab_heading_nth_child-margin-top: 30px; // TODO: Use a spacing variable + --SettingsTab_fullWidthField-margin-inline-end: 100px; + --SettingsTab_tooltip-max-width: 120px; // So it fits in the space provided by the page + color: $primary-content; + + a { + color: $links; + } } .mx_SettingsTab_warningText { @@ -26,12 +35,12 @@ limitations under the License. font-size: $font-20px; font-weight: 600; color: $primary-content; - margin-bottom: 10px; - margin-top: 10px; -} + margin-top: 10px; // TODO: Use a spacing variable + margin-bottom: 10px; // TODO: Use a spacing variable -.mx_SettingsTab_heading:nth-child(n + 2) { - margin-top: 30px; + &:nth-child(n + 2) { + margin-top: var(--SettingsTab_heading_nth_child-margin-top); + } } .mx_SettingsTab_subheading { @@ -39,57 +48,50 @@ limitations under the License. display: block; font-weight: 600; color: $primary-content; - margin-bottom: 10px; - margin-top: 12px; + margin-top: $spacing-12; + margin-bottom: 10px; // TODO: Use a spacing variable } .mx_SettingsTab_subsectionText { - color: $settings-subsection-fg-color; + color: $secondary-content; font-size: $font-14px; display: block; - margin: 10px 80px 10px 0; // Align with the rest of the view + margin-top: 10px; // TODO: Use a spacing variable + margin-inline-end: 80px; // Align with the rest of the view + margin-bottom: 10px; // TODO: Use a spacing variable + margin-inline-start: 0; } .mx_SettingsTab_section { - $right-gutter: 80px; + $end-gutter: 80px; - margin-bottom: 24px; + margin-bottom: $spacing-24; .mx_SettingsFlag { - margin-right: $right-gutter; - margin-bottom: 10px; + margin-inline-end: $end-gutter; + margin-bottom: 10px; // TODO: Use a spacing variable + + .mx_SettingsFlag_label { + vertical-align: middle; + display: inline-block; + max-width: calc(100% - $font-48px); // Force word wrap instead of colliding with the switch + box-sizing: border-box; + } + + .mx_ToggleSwitch { + float: inline-end; + } } > p { - margin-right: $right-gutter; + margin-inline-end: $end-gutter; } &.mx_SettingsTab_subsectionText .mx_SettingsFlag { - margin-right: 0 !important; + margin-inline-end: 0 !important; } } -.mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_label { - vertical-align: middle; - display: inline-block; - font-size: $font-14px; - color: $primary-content; - max-width: calc(100% - $font-48px); // Force word wrap instead of colliding with the switch - box-sizing: border-box; - padding-right: 10px; -} - -.mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_microcopy { - margin-top: 4px; - font-size: $font-12px; - line-height: $font-15px; - color: $secondary-content; -} - -.mx_SettingsTab_section .mx_SettingsFlag .mx_ToggleSwitch { - float: right; -} - .mx_SettingsTab_settingsTable { table-layout: fixed; border-collapse: separate; @@ -157,10 +159,6 @@ limitations under the License. word-break: break-all; } -.mx_SettingsTab a { - color: $accent-alt; -} - .mx_SettingsTab_toggleWithDescription { - margin-top: 24px; + margin-top: $spacing-24; } diff --git a/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss index a3b3b17899f..c1426534902 100644 --- a/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss +++ b/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss @@ -34,6 +34,6 @@ limitations under the License. .mx_SecurityRoomSettingsTab_encryptionSection { padding-bottom: 24px; - border-bottom: 1px solid $menu-border-color; + border-bottom: 1px solid $quinary-content; margin-bottom: 32px; } diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index 58443216e67..11b50690748 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -14,22 +14,25 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_AppearanceUserSettingsTab { + --AppearanceUserSettingsTab_Field-margin-inline-start: calc($font-16px + 10px); -.mx_AppearanceUserSettingsTab{ - font-size: $font-14px; -} - -.mx_AppearanceUserSettingsTab .mx_Field { - width: 256px; -} + .mx_SettingsTab_subsectionText { + margin-block: $spacing-12 $spacing-32; + color: $primary-content; // Same as mx_SettingsTab + } -.mx_AppearanceUserSettingsTab { - > .mx_SettingsTab_SubHeading { - margin-bottom: 32px; - margin-top: 12px; + .mx_Field { + width: 256px; } -} -.mx_AppearanceUserSettingsTab_RoomListStyleSection { - color: $primary-content; + .mx_AppearanceUserSettingsTab_Advanced { + .mx_Checkbox { + margin-block: $spacing-16; + } + + .mx_AppearanceUserSettingsTab_systemFont { + margin-inline-start: var(--AppearanceUserSettingsTab_Field-margin-inline-start); + } + } } diff --git a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss index 8b73e690315..0c0e13d691c 100644 --- a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_GeneralUserSettingsTab_changePassword .mx_Field { - @mixin mx_Settings_fullWidthField; + margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end); } .mx_GeneralUserSettingsTab_changePassword .mx_Field:first-child { @@ -40,7 +40,7 @@ limitations under the License. .mx_GeneralUserSettingsTab_discovery .mx_ExistingEmailAddress, .mx_GeneralUserSettingsTab_discovery .mx_ExistingPhoneNumber, .mx_GeneralUserSettingsTab_languageInput { - @mixin mx_Settings_fullWidthField; + margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end); } .mx_GeneralUserSettingsTab_warningIcon { diff --git a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss index 3779162223b..c03de9f36ce 100644 --- a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss @@ -15,16 +15,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_HelpUserSettingsTab_debugButton { - margin-bottom: 5px; - margin-top: 5px; -} +.mx_HelpUserSettingsTab { + code { + word-break: break-all; + user-select: all; + } -.mx_HelpUserSettingsTab span.mx_AccessibleButton { - word-break: break-word; -} + details { + margin: $spacing-16 auto; -.mx_HelpUserSettingsTab code { - word-break: break-all; - user-select: all; + summary { + margin-bottom: $spacing-16; + } + } } diff --git a/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss index 1ba3a8599b8..4fe4d175df4 100644 --- a/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss @@ -16,9 +16,25 @@ limitations under the License. */ .mx_KeyboardUserSettingsTab .mx_SettingsTab_section { - .mx_KeyboardShortcut_shortcutRow { + .mx_KeyboardShortcut_shortcutRow, + .mx_KeyboardShortcut { display: flex; justify-content: space-between; align-items: center; } + + .mx_KeyboardShortcut_shortcutRow { + column-gap: $spacing-8; + margin-bottom: $spacing-4; + + // TODO: Use flexbox + &:last-of-type { + margin-bottom: 0; + } + + .mx_KeyboardShortcut { + flex-wrap: nowrap; + column-gap: 5px; // TODO: Use a spacing variable + } + } } diff --git a/res/css/views/settings/tabs/user/_LabsUserSettingsTab.scss b/res/css/views/settings/tabs/user/_LabsUserSettingsTab.scss index 6fa751a96f1..051ec4c9a00 100644 --- a/res/css/views/settings/tabs/user/_LabsUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_LabsUserSettingsTab.scss @@ -15,18 +15,13 @@ limitations under the License. */ .mx_LabsUserSettingsTab { - .mx_SettingsTab_subsectionText, .mx_SettingsTab_section { - margin-bottom: 30px; + .mx_SettingsTab_subsectionText, + .mx_SettingsTab_section { + margin-bottom: var(--SettingsTab_section-margin-bottom-preferences-labs); } - .mx_SettingsTab_section .mx_SettingsFlag { + .mx_SettingsFlag { margin-right: 0; // remove right margin to align with beta cards - display: flex; align-items: center; - justify-content: space-between; - - .mx_ToggleSwitch { - float: unset; - } } } diff --git a/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss b/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss index 2a3fd12f317..26d0b00080e 100644 --- a/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_MjolnirUserSettingsTab .mx_Field { - @mixin mx_Settings_fullWidthField; + margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end); } .mx_MjolnirUserSettingsTab_listItem { diff --git a/res/css/views/settings/tabs/user/_NotificationUserSettingsTab.scss b/res/css/views/settings/tabs/user/_NotificationUserSettingsTab.scss deleted file mode 100644 index b57c46a1d9d..00000000000 --- a/res/css/views/settings/tabs/user/_NotificationUserSettingsTab.scss +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -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_NotificationUserSettingsTab .mx_SettingsTab_heading { - margin-bottom: 10px; // Give some spacing between the title and the first elements -} diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index ea979ac074c..c7eb699d4c9 100644 --- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -16,21 +16,10 @@ limitations under the License. .mx_PreferencesUserSettingsTab { .mx_Field { - @mixin mx_Settings_fullWidthField; + margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end); } .mx_SettingsTab_section { - margin-bottom: 30px; - - > details { - > summary { - cursor: pointer; - color: $primary-content; - } - - & + .mx_SettingsFlag { - margin-top: 20px; - } - } + margin-bottom: var(--SettingsTab_section-margin-bottom-preferences-labs); } } diff --git a/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss index 5000f3e9a69..f4c248a584a 100644 --- a/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss @@ -15,17 +15,6 @@ limitations under the License. */ .mx_SidebarUserSettingsTab { - .mx_SettingsTab_section { - margin-top: 12px; - } - - .mx_SidebarUserSettingsTab_subheading { - font-size: $font-15px; - line-height: $font-24px; - color: $primary-content; - margin-bottom: 4px; - } - .mx_Checkbox { margin-top: 12px; font-size: $font-15px; diff --git a/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss index 69d57bdba10..138ca6f9de3 100644 --- a/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_VoiceUserSettingsTab .mx_Field { - @mixin mx_Settings_fullWidthField; + margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end); } .mx_VoiceUserSettingsTab_missingMediaPermissions { diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss index 5ed772fa85f..fe768a2978f 100644 --- a/res/css/views/spaces/_SpaceBasicSettings.scss +++ b/res/css/views/spaces/_SpaceBasicSettings.scss @@ -64,7 +64,7 @@ limitations under the License. > .mx_AccessibleButton_kind_link { display: inline-block; margin: auto 18px; - color: #368bd6; + color: $links; font-size: $font-14px; // See _SpaceSettingsDialog.scss } diff --git a/res/img/betas/new_search_experience.gif b/res/img/betas/new_search_experience.gif deleted file mode 100644 index 6dc1e001f3e..00000000000 Binary files a/res/img/betas/new_search_experience.gif and /dev/null differ diff --git a/res/img/betas/video_rooms.png b/res/img/betas/video_rooms.png new file mode 100644 index 00000000000..02a9e8c86b7 Binary files /dev/null and b/res/img/betas/video_rooms.png differ diff --git a/res/img/element-icons/roomlist/decorated-avatar-mask.svg b/res/img/element-icons/roomlist/decorated-avatar-mask.svg index fb09c16bba5..1cca51666e9 100644 --- a/res/img/element-icons/roomlist/decorated-avatar-mask.svg +++ b/res/img/element-icons/roomlist/decorated-avatar-mask.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/res/img/element-icons/roomlist/dnd-avatar-mask.svg b/res/img/element-icons/roomlist/dnd-avatar-mask.svg deleted file mode 100644 index f97f0836a3e..00000000000 --- a/res/img/element-icons/roomlist/dnd-avatar-mask.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 3caf3486c92..fe93433fdd5 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -76,8 +76,7 @@ $menu-selected-color: $room-highlight-color; // Settings // ******************** -$settings-profile-overlay-placeholder-fg-color: #424242; -$settings-profile-button-bg-color: #e0e0e0; +$settings-profile-button-bg-color: #e7e7e7; $settings-subsection-fg-color: $text-secondary-color; // ******************** diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 45dee94e578..a7d33acdba5 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -76,9 +76,7 @@ $dialog-close-external-color: $text-primary-color; $lightbox-background-bg-color: #000; $lightbox-background-bg-opacity: 0.85; -$settings-grey-fg-color: #9e9e9e; -$settings-profile-overlay-placeholder-fg-color: #424242; -$settings-profile-button-bg-color: #e0e0e0; +$settings-grey-fg-color: #a2a2a2; $settings-subsection-fg-color: $text-secondary-color; $topleftmenu-color: $text-primary-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index c01597ffe72..fed1fd6796f 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -125,10 +125,8 @@ $preview-widget-bar-color: #bdbdbd; $blockquote-bar-color: #bdbdbd; -$settings-grey-fg-color: #9e9e9e; -$settings-profile-overlay-placeholder-fg-color: #212121; -$settings-profile-button-bg-color: #e0e0e0; -$settings-subsection-fg-color: #616161; +$settings-grey-fg-color: #a2a2a2; +$settings-subsection-fg-color: #61708b; $rte-bg-color: #e0e0e0; $rte-code-bg-color: rgba(0, 0, 0, 0.04); diff --git a/res/themes/light-custom/css/_custom.scss b/res/themes/light-custom/css/_custom.scss index 38995139d02..bf07a490591 100644 --- a/res/themes/light-custom/css/_custom.scss +++ b/res/themes/light-custom/css/_custom.scss @@ -75,7 +75,6 @@ $roomlist-bg-color: var(--roomlist-background-color); // --timeline-text-color $message-action-bar-fg-color: var(--timeline-text-color); $primary-content: var(--timeline-text-color); -$settings-profile-overlay-placeholder-fg-color: var(--timeline-text-color); $roomtopic-color: var(--timeline-text-color-50pct); $tab-label-fg-color: var(--timeline-text-color); // was #212121 diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 72d917b82c6..9d226110576 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -140,9 +140,7 @@ $menu-selected-color: #f5f5f5; // Settings // ******************** -$settings-grey-fg-color: #9e9e9e; -$settings-profile-overlay-placeholder-fg-color: #212121; -$settings-profile-button-bg-color: $menu-border-color; +$settings-grey-fg-color: #a2a2a2; $settings-subsection-fg-color: $muted-fg-color; // ******************** diff --git a/scripts/ci/install-deps.sh b/scripts/ci/install-deps.sh index c10884361e9..7121e7a36ce 100755 --- a/scripts/ci/install-deps.sh +++ b/scripts/ci/install-deps.sh @@ -19,8 +19,9 @@ scripts/fetchdep.sh matrix-org matrix-analytics-events main pushd matrix-analytics-events yarn link yarn install --pure-lockfile $@ +yarn build:ts popd yarn link matrix-js-sdk -yarn link matrix-analytics-events +yarn link @matrix-org/analytics-events yarn install --pure-lockfile $@ diff --git a/scripts/ci/layered.sh b/scripts/ci/layered.sh index e66eddf9aa4..fdde9dd8286 100755 --- a/scripts/ci/layered.sh +++ b/scripts/ci/layered.sh @@ -24,11 +24,12 @@ scripts/fetchdep.sh matrix-org matrix-analytics-events main pushd matrix-analytics-events yarn link yarn install --pure-lockfile +yarn build:ts popd # Now set up the react-sdk yarn link matrix-js-sdk -yarn link matrix-analytics-events +yarn link @matrix-org/analytics-events yarn link yarn install --pure-lockfile diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 4d87e0a2f02..bbce7ea8759 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -34,7 +34,6 @@ import type { Renderer } from "react-dom"; import RightPanelStore from "../stores/right-panel/RightPanelStore"; import WidgetStore from "../stores/WidgetStore"; import CallHandler from "../CallHandler"; -import { Analytics } from "../Analytics"; import UserActivity from "../UserActivity"; import { ModalWidgetStore } from "../stores/ModalWidgetStore"; import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; @@ -91,7 +90,6 @@ declare global { mxWidgetStore: WidgetStore; mxWidgetLayoutStore: WidgetLayoutStore; mxCallHandler: CallHandler; - mxAnalytics: Analytics; mxUserActivity: UserActivity; mxModalWidgetStore: ModalWidgetStore; mxVoipUserMapper: VoipUserMapper; diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts index 4c84294f7a6..415d6d7ad0e 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -206,7 +206,7 @@ export default class AddThreepid { continueKind: "primary", }, }; - const { finished } = Modal.createTrackedDialog('Add Email', '', InteractiveAuthDialog, { + const { finished } = Modal.createDialog(InteractiveAuthDialog, { title: _t("Add Email Address"), matrixClient: MatrixClientPeg.get(), authData: e.data, @@ -319,7 +319,7 @@ export default class AddThreepid { continueKind: "primary", }, }; - const { finished } = Modal.createTrackedDialog('Add MSISDN', '', InteractiveAuthDialog, { + const { finished } = Modal.createDialog(InteractiveAuthDialog, { title: _t("Add Phone Number"), matrixClient: MatrixClientPeg.get(), authData: e.data, diff --git a/src/Analytics.tsx b/src/Analytics.tsx deleted file mode 100644 index 0249c6ad1a2..00000000000 --- a/src/Analytics.tsx +++ /dev/null @@ -1,460 +0,0 @@ -/* -Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2020 - 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. -*/ - -import React from 'react'; -import { logger } from "matrix-js-sdk/src/logger"; -import { Optional } from "matrix-events-sdk"; -import { randomString } from 'matrix-js-sdk/src/randomstring'; - -import { getCurrentLanguage, _t, _td, IVariables } from './languageHandler'; -import PlatformPeg from './PlatformPeg'; -import SdkConfig from './SdkConfig'; -import Modal from './Modal'; -import ErrorDialog from "./components/views/dialogs/ErrorDialog"; -import { SnakedObject } from "./utils/SnakedObject"; -import { IConfigOptions } from "./IConfigOptions"; - -// Note: we keep the analytics redaction on groups in case people have old links. -const hashRegex = /#\/(groups?|room|user|settings|register|login|forgot_password|home|directory)/; -const hashVarRegex = /#\/(group|room|user)\/.*$/; - -// Remove all but the first item in the hash path. Redact unexpected hashes. -function getRedactedHash(hash: string): string { - // Don't leak URLs we aren't expecting - they could contain tokens/PII - const match = hashRegex.exec(hash); - if (!match) { - logger.warn(`Unexpected hash location "${hash}"`); - return '#/'; - } - - if (hashVarRegex.test(hash)) { - return hash.replace(hashVarRegex, "#/$1/"); - } - - return hash.replace(hashRegex, "#/$1"); -} - -// Return the current origin, path and hash separated with a `/`. This does -// not include query parameters. -function getRedactedUrl(): string { - const { origin, hash } = window.location; - let { pathname } = window.location; - - // Redact paths which could contain unexpected PII - if (origin.startsWith('file://')) { - pathname = "//"; - } - - return origin + pathname + getRedactedHash(hash); -} - -interface IData { - /* eslint-disable camelcase */ - gt_ms?: string; - e_c?: string; - e_a?: string; - e_n?: string; - e_v?: string; - ping?: string; - /* eslint-enable camelcase */ -} - -interface IVariable { - id: number; - expl: string; // explanation - example: string; // example value - getTextVariables?(): IVariables; // object to pass as 2nd argument to `_t` -} - -const customVariables: Record = { - // The Matomo installation at https://matomo.riot.im is currently configured - // with a limit of 10 custom variables. - 'App Platform': { - id: 1, - expl: _td('The platform you\'re on'), - example: 'Electron Platform', - }, - 'App Version': { - id: 2, - expl: _td('The version of %(brand)s'), - getTextVariables: () => ({ - brand: SdkConfig.get().brand, - }), - example: '15.0.0', - }, - 'User Type': { - id: 3, - expl: _td('Whether or not you\'re logged in (we don\'t record your username)'), - example: 'Logged In', - }, - 'Chosen Language': { - id: 4, - expl: _td('Your language of choice'), - example: 'en', - }, - 'Instance': { - id: 5, - expl: _td('Which officially provided instance you are using, if any'), - example: 'app', - }, - 'RTE: Uses Richtext Mode': { - id: 6, - expl: _td('Whether or not you\'re using the Richtext mode of the Rich Text Editor'), - example: 'off', - }, - 'Homeserver URL': { - id: 7, - expl: _td('Your homeserver\'s URL'), - example: 'https://matrix.org', - }, - 'Touch Input': { - id: 8, - expl: _td("Whether you're using %(brand)s on a device where touch is the primary input mechanism"), - getTextVariables: () => ({ - brand: SdkConfig.get().brand, - }), - example: 'false', - }, - 'Breadcrumbs': { - id: 9, - expl: _td("Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)"), - example: 'disabled', - }, - 'Installed PWA': { - id: 10, - expl: _td("Whether you're using %(brand)s as an installed Progressive Web App"), - getTextVariables: () => ({ - brand: SdkConfig.get().brand, - }), - example: 'false', - }, -}; - -function whitelistRedact(whitelist: string[], str: string): string { - if (whitelist.includes(str)) return str; - return ''; -} - -const UID_KEY = "mx_Riot_Analytics_uid"; -const CREATION_TS_KEY = "mx_Riot_Analytics_cts"; -const VISIT_COUNT_KEY = "mx_Riot_Analytics_vc"; -const LAST_VISIT_TS_KEY = "mx_Riot_Analytics_lvts"; - -function getUid(): string { - try { - let data = localStorage?.getItem(UID_KEY); - if (!data && localStorage) { - localStorage.setItem(UID_KEY, data = randomString(16)); - } - return data; - } catch (e) { - logger.error("Analytics error: ", e); - return ""; - } -} - -const HEARTBEAT_INTERVAL = 30 * 1000; // seconds - -export class Analytics { - private baseUrl: URL = null; - private visitVariables: Record = {}; // {[id: number]: [name: string, value: string]} - private firstPage = true; - private heartbeatIntervalID: number = null; - - private readonly creationTs: string; - private readonly lastVisitTs: string; - private readonly visitCount: string; - - constructor() { - this.creationTs = localStorage && localStorage.getItem(CREATION_TS_KEY); - if (!this.creationTs && localStorage) { - localStorage.setItem(CREATION_TS_KEY, this.creationTs = String(new Date().getTime())); - } - - this.lastVisitTs = localStorage && localStorage.getItem(LAST_VISIT_TS_KEY); - this.visitCount = localStorage && localStorage.getItem(VISIT_COUNT_KEY) || "0"; - this.visitCount = String(parseInt(this.visitCount, 10) + 1); // increment - if (localStorage) { - localStorage.setItem(VISIT_COUNT_KEY, this.visitCount); - } - } - - public get disabled() { - return !this.baseUrl; - } - - public canEnable() { - const piwikConfig = SdkConfig.get("piwik"); - let piwik: Optional>>; - if (typeof piwikConfig === 'object') { - piwik = new SnakedObject(piwikConfig); - } - return navigator.doNotTrack !== "1" && piwik?.get("site_id"); - } - - /** - * Enable Analytics if initialized but disabled - * otherwise try and initalize, no-op if piwik config missing - */ - public async enable() { - if (!this.disabled) return; - if (!this.canEnable()) return; - const piwikConfig = SdkConfig.get("piwik"); - let piwik: Optional>>; - if (typeof piwikConfig === 'object') { - piwik = new SnakedObject(piwikConfig); - } - - this.baseUrl = new URL("piwik.php", piwik.get("url")); - // set constants - this.baseUrl.searchParams.set("rec", "1"); // rec is required for tracking - this.baseUrl.searchParams.set("idsite", piwik.get("site_id")); // idsite is required for tracking - this.baseUrl.searchParams.set("apiv", "1"); // API version to use - this.baseUrl.searchParams.set("send_image", "0"); // we want a 204, not a tiny GIF - // set user parameters - this.baseUrl.searchParams.set("_id", getUid()); // uuid - this.baseUrl.searchParams.set("_idts", this.creationTs); // first ts - this.baseUrl.searchParams.set("_idvc", this.visitCount); // visit count - if (this.lastVisitTs) { - this.baseUrl.searchParams.set("_viewts", this.lastVisitTs); // last visit ts - } - - const platform = PlatformPeg.get(); - this.setVisitVariable('App Platform', platform.getHumanReadableName()); - try { - this.setVisitVariable('App Version', await platform.getAppVersion()); - } catch (e) { - this.setVisitVariable('App Version', 'unknown'); - } - - this.setVisitVariable('Chosen Language', getCurrentLanguage()); - - const hostname = window.location.hostname; - if (hostname === 'riot.im') { - this.setVisitVariable('Instance', window.location.pathname); - } else if (hostname.endsWith('.element.io')) { - this.setVisitVariable('Instance', hostname.replace('.element.io', '')); - } - - let installedPWA = "unknown"; - try { - // Known to work at least for desktop Chrome - installedPWA = String(window.matchMedia('(display-mode: standalone)').matches); - } catch (e) { } - this.setVisitVariable('Installed PWA', installedPWA); - - let touchInput = "unknown"; - try { - // MDN claims broad support across browsers - touchInput = String(window.matchMedia('(pointer: coarse)').matches); - } catch (e) { } - this.setVisitVariable('Touch Input', touchInput); - - // start heartbeat - this.heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL); - } - - /** - * Disable Analytics, stop the heartbeat and clear identifiers from localStorage - */ - public disable() { - if (this.disabled) return; - this.trackEvent('Analytics', 'opt-out'); - window.clearInterval(this.heartbeatIntervalID); - this.baseUrl = null; - this.visitVariables = {}; - localStorage.removeItem(UID_KEY); - localStorage.removeItem(CREATION_TS_KEY); - localStorage.removeItem(VISIT_COUNT_KEY); - localStorage.removeItem(LAST_VISIT_TS_KEY); - } - - private async track(data: IData) { - if (this.disabled) return; - - const now = new Date(); - const params = { - ...data, - url: getRedactedUrl(), - - _cvar: JSON.stringify(this.visitVariables), // user custom vars - res: `${window.screen.width}x${window.screen.height}`, // resolution as WWWWxHHHH - rand: String(Math.random()).slice(2, 8), // random nonce to cache-bust - h: now.getHours(), - m: now.getMinutes(), - s: now.getSeconds(), - }; - - const url = new URL(this.baseUrl.toString()); // copy - for (const key in params) { - url.searchParams.set(key, params[key]); - } - - try { - await window.fetch(url.toString(), { - method: "GET", - mode: "no-cors", - cache: "no-cache", - redirect: "follow", - }); - } catch (e) { - logger.error("Analytics error: ", e); - } - } - - public ping() { - this.track({ - ping: "1", - }); - localStorage.setItem(LAST_VISIT_TS_KEY, String(new Date().getTime())); // update last visit ts - } - - public trackPageChange(generationTimeMs?: number) { - if (this.disabled) return; - if (this.firstPage) { - // De-duplicate first page - // router seems to hit the fn twice - this.firstPage = false; - return; - } - - if (typeof generationTimeMs !== 'number') { - logger.warn('Analytics.trackPageChange: expected generationTimeMs to be a number'); - // But continue anyway because we still want to track the change - } - - this.track({ - gt_ms: String(generationTimeMs), - }); - } - - public trackEvent(category: string, action: string, name?: string, value?: string) { - if (this.disabled) return; - this.track({ - e_c: category, - e_a: action, - e_n: name, - e_v: value, - }); - } - - private setVisitVariable(key: keyof typeof customVariables, value: string) { - if (this.disabled) return; - this.visitVariables[customVariables[key].id] = [key, value]; - } - - public setLoggedIn(isGuest: boolean, homeserverUrl: string) { - if (this.disabled) return; - - const piwikConfig = SdkConfig.get("piwik"); - let piwik: Optional>>; - if (typeof piwikConfig === 'object') { - piwik = new SnakedObject(piwikConfig); - } - if (!piwik) return; - - const whitelistedHSUrls = piwik.get("whitelisted_hs_urls", "whitelistedHSUrls") || []; - - this.setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In'); - this.setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl)); - } - - public setBreadcrumbs(state: boolean) { - if (this.disabled) return; - this.setVisitVariable('Breadcrumbs', state ? 'enabled' : 'disabled'); - } - - public showDetailsModal = () => { - let rows = []; - if (!this.disabled) { - rows = Object.values(this.visitVariables); - } else { - rows = Object.keys(customVariables).map( - (k) => [ - k, - _t('e.g. %(exampleValue)s', { exampleValue: customVariables[k].example }), - ], - ); - } - - const resolution = `${window.screen.width}x${window.screen.height}`; - const otherVariables = [ - { - expl: _td('Every page you use in the app'), - value: _t( - 'e.g. ', - {}, - { - CurrentPageURL: getRedactedUrl, - }, - ), - }, - { expl: _td('Your user agent'), value: navigator.userAgent }, - { expl: _td('Your device resolution'), value: resolution }, - ]; - - const piwikConfig = SdkConfig.get("piwik"); - let piwik: Optional>>; - if (typeof piwikConfig === 'object') { - piwik = new SnakedObject(piwikConfig); - } - const cookiePolicyUrl = piwik?.get("policy_url"); - const cookiePolicyLink = _t( - "Our complete cookie policy can be found here.", - {}, - { - "CookiePolicyLink": (sub) => { - return { sub }; - }, - }); - Modal.createTrackedDialog('Analytics Details', '', ErrorDialog, { - title: _t('Analytics'), - description:

- { cookiePolicyUrl &&

{ cookiePolicyLink }

} -
{ _t('Some examples of the information being sent to us to help make %(brand)s better includes:', { - brand: SdkConfig.get().brand, - }) }
- - { rows.map((row) => - - { row[1] !== undefined && } - ) } - { otherVariables.map((item, index) => - - - - , - ) } -
{ _t( - customVariables[row[0]].expl, - customVariables[row[0]].getTextVariables ? - customVariables[row[0]].getTextVariables() : - null, - ) }{ row[1] }
{ _t(item.expl) }{ item.value }
-
- { _t('Where this page includes identifiable information, such as a room, ' - + 'user ID, that data is removed before being sent to the server.') } -
-
, - }); - }; -} - -if (!window.mxAnalytics) { - window.mxAnalytics = new Analytics(); -} -export default window.mxAnalytics; diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 787f602fb77..55c29c7a6f4 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -47,7 +47,6 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import WidgetStore from "./stores/WidgetStore"; import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; -import Analytics from './Analytics'; import { UIFeature } from "./settings/UIFeature"; import { Action } from './dispatcher/actions'; import VoipUserMapper from './VoipUserMapper'; @@ -305,7 +304,6 @@ export default class CallHandler extends EventEmitter { return; } - Analytics.trackEvent('voip', 'receiveCall', 'type', call.type); this.addCallForRoom(mappedRoomId, call); this.setCallListeners(call); // Explicitly handle first state change @@ -375,6 +373,8 @@ export default class CallHandler extends EventEmitter { } public play(audioId: AudioID): void { + const logPrefix = `CallHandler.play(${audioId}):`; + logger.debug(`${logPrefix} beginning of function`); // TODO: Attach an invisible element for this instead // which listens? const audio = document.getElementById(audioId) as HTMLMediaElement; @@ -383,13 +383,15 @@ export default class CallHandler extends EventEmitter { try { // This still causes the chrome debugger to break on promise rejection if // the promise is rejected, even though we're catching the exception. + logger.debug(`${logPrefix} attempting to play audio`); await audio.play(); + logger.debug(`${logPrefix} playing audio successfully`); } catch (e) { // This is usually because the user hasn't interacted with the document, // or chrome doesn't think so and is denying the request. Not sure what // we can really do here... // https://github.com/vector-im/element-web/issues/7657 - logger.log("Unable to play audio clip", e); + logger.warn(`${logPrefix} unable to play audio clip`, e); } }; if (this.audioPromises.has(audioId)) { @@ -400,20 +402,30 @@ export default class CallHandler extends EventEmitter { } else { this.audioPromises.set(audioId, playAudio()); } + } else { + logger.warn(`${logPrefix} unable to find