diff --git a/.editorconfig b/.editorconfig
index 880331a09e5..56631484cd5 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -21,3 +21,6 @@ insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
+
+[*.{yml,yaml}]
+indent_size = 2
diff --git a/.eslintignore b/.eslintignore
index b715bcd4f69..5d117f54b54 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,5 +1,6 @@
-src/component-index.js
test/end-to-end-tests/node_modules/
test/end-to-end-tests/element/
test/end-to-end-tests/synapse/
test/end-to-end-tests/lib/
+# Legacy skinning file that some people might still have
+src/component-index.js
diff --git a/.eslintrc.js b/.eslintrc.js
index 45cd05506f6..67e6ab1e642 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -92,6 +92,7 @@ module.exports = {
files: [
"src/**/*.{ts,tsx}",
"test/**/*.{ts,tsx}",
+ "cypress/**/*.ts",
],
extends: [
"plugin:matrix-org/typescript",
@@ -123,7 +124,6 @@ module.exports = {
"src/components/structures/UserMenu.tsx",
"src/components/views/avatars/WidgetAvatar.tsx",
"src/components/views/dialogs/AddExistingToSpaceDialog.tsx",
- "src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx",
"src/components/views/dialogs/ForwardDialog.tsx",
"src/components/views/dialogs/InviteDialog.tsx",
"src/components/views/dialogs/ModalWidgetDialog.tsx",
diff --git a/.github/codecov.yml b/.github/codecov.yml
new file mode 100644
index 00000000000..449fa0a733a
--- /dev/null
+++ b/.github/codecov.yml
@@ -0,0 +1,12 @@
+codecov:
+ allow_coverage_offsets: True
+coverage:
+ status:
+ project: off
+ patch: off
+comment:
+ layout: "diff, files"
+ behavior: default
+ require_changes: false
+ require_base: no
+ require_head: no
diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
deleted file mode 100644
index 456c97d5807..00000000000
--- a/.github/workflows/develop.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-name: Develop
-on:
- # These tests won't work for non-develop branches at the moment as they
- # won't pull in the right versions of other repos, so they're only enabled
- # on develop.
- push:
- branches: [develop]
- pull_request:
- branches: [develop]
-jobs:
- end-to-end:
- runs-on: ubuntu-latest
- env:
- PR_NUMBER: ${{github.event.number}}
- container: vectorim/element-web-ci-e2etests-env:latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v2
- - name: Prepare End-to-End tests
- run: ./scripts/ci/prepare-end-to-end-tests.sh
- - name: Run End-to-End tests
- run: ./scripts/ci/run-end-to-end-tests.sh
- - name: Archive logs
- uses: actions/upload-artifact@v2
- if: ${{ always() }}
- with:
- path: |
- test/end-to-end-tests/logs/**/*
- test/end-to-end-tests/synapse/installations/consent/homeserver.log
- retention-days: 14
- - name: Download previous benchmark data
- uses: actions/cache@v1
- with:
- path: ./cache
- key: ${{ runner.os }}-benchmark
- - name: Store benchmark result
- uses: matrix-org/github-action-benchmark@jsperfentry-1
- with:
- tool: 'jsperformanceentry'
- output-file-path: test/end-to-end-tests/performance-entries.json
- fail-on-alert: false
- comment-on-alert: false
- # Only temporary to monitor where failures occur
- alert-comment-cc-users: '@gsouquet'
- github-token: ${{ secrets.DEPLOY_GH_PAGES }}
- auto-push: ${{ github.ref == 'refs/heads/develop' }}
diff --git a/.github/workflows/element-build-and-test.yaml b/.github/workflows/element-build-and-test.yaml
new file mode 100644
index 00000000000..9b3d0f373a0
--- /dev/null
+++ b/.github/workflows/element-build-and-test.yaml
@@ -0,0 +1,102 @@
+# 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
+ record: true
+ env:
+ # pass the Dashboard record key as an environment variable
+ CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
+ # pass GitHub token to allow accurately detecting a build vs a re-run build
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Upload Artifact
+ if: failure()
+ uses: actions/upload-artifact@v2
+ with:
+ name: cypress-results
+ path: |
+ cypress/screenshots
+ cypress/videos
+ cypress/synapselogs
+
+ 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/end-to-end-tests.yaml b/.github/workflows/end-to-end-tests.yaml
new file mode 100644
index 00000000000..6c663a0e018
--- /dev/null
+++ b/.github/workflows/end-to-end-tests.yaml
@@ -0,0 +1,59 @@
+name: End-to-end Tests
+on:
+ # These tests won't work for non-develop branches at the moment as they
+ # won't pull in the right versions of other repos, so they're only enabled
+ # on develop.
+ push:
+ branches: [ develop ]
+ pull_request:
+ branches: [ develop ]
+ 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:
+ end-to-end:
+ runs-on: ubuntu-latest
+ container: vectorim/element-web-ci-e2etests-env:latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - uses: actions/setup-node@v3
+ with:
+ cache: 'yarn'
+
+ - name: Prepare End-to-End tests
+ run: ./scripts/ci/prepare-end-to-end-tests.sh
+
+ - name: Run End-to-End tests
+ run: ./scripts/ci/run-end-to-end-tests.sh
+
+ - name: Archive logs
+ uses: actions/upload-artifact@v2
+ if: ${{ always() }}
+ with:
+ path: |
+ test/end-to-end-tests/logs/**/*
+ test/end-to-end-tests/synapse/installations/consent/homeserver.log
+ retention-days: 14
+
+ - name: Download previous benchmark data
+ uses: actions/cache@v1
+ with:
+ path: ./cache
+ key: ${{ runner.os }}-benchmark
+
+ - name: Store benchmark result
+ uses: matrix-org/github-action-benchmark@jsperfentry-1
+ with:
+ tool: 'jsperformanceentry'
+ output-file-path: test/end-to-end-tests/performance-entries.json
+ fail-on-alert: false
+ comment-on-alert: false
+ # Only temporary to monitor where failures occur
+ alert-comment-cc-users: '@gsouquet'
+ github-token: ${{ secrets.DEPLOY_GH_PAGES }}
+ auto-push: ${{ github.ref == 'refs/heads/develop' }}
diff --git a/.github/workflows/layered-build.yaml b/.github/workflows/layered-build.yaml
deleted file mode 100644
index fd531fc9689..00000000000
--- a/.github/workflows/layered-build.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: Layered Preview Build
-on:
- pull_request:
-jobs:
- build:
- runs-on: ubuntu-latest
- env:
- PR_NUMBER: ${{github.event.number}}
- steps:
- - uses: actions/checkout@v2
- - name: Build
- run: scripts/ci/layered.sh && cd element-web && cp element.io/develop/config.json config.json && CI_PACKAGE=true yarn build
- - 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/netlify.yaml b/.github/workflows/netlify.yaml
index 7c472ab24d1..260f8f130c5 100644
--- a/.github/workflows/netlify.yaml
+++ b/.github/workflows/netlify.yaml
@@ -1,75 +1,94 @@
+# Triggers after the layered build has finished, taking the artifact
+# and uploading it to netlify
name: Upload Preview Build to Netlify
on:
- workflow_run:
- workflows: ["Layered Preview Build"]
- types:
- - completed
+ workflow_run:
+ workflows: [ "Element Web - Build and Test" ]
+ types:
+ - completed
jobs:
- build:
- runs-on: ubuntu-latest
- if: >
- ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
- steps:
- - name: "🔍 Read PR number"
- id: readctx
- # we need to find the PR number that corresponds to the branch, which we do by
- # searching the GH API
- # The workflow_run event includes a list of pull requests, but it doesn't get populated for
- # forked PRs: https://docs.github.com/en/rest/reference/checks#create-a-check-run
- run: |
- head_branch='${{github.event.workflow_run.head_repository.owner.login}}:${{github.event.workflow_run.head_branch}}'
- echo "head branch: $head_branch"
- pulls_uri="https://api.github.com/repos/${{ github.repository }}/pulls?head=$(jq -Rr '@uri' <<<$head_branch)"
- pr_number=$(curl -s -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' "$pulls_uri" |
- jq -r '.[] | .number')
- echo "PR number: $pr_number"
- echo "::set-output name=prnumber::$pr_number"
- # 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: actions/github-script@v3.1.0
- with:
- script: |
- var artifacts = await github.actions.listWorkflowRunArtifacts({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: ${{github.event.workflow_run.id }},
- });
- var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
- return artifact.name == "previewbuild"
- })[0];
- var download = await github.actions.downloadArtifact({
- owner: context.repo.owner,
- repo: context.repo.repo,
- artifact_id: matchArtifact.id,
- archive_format: 'zip',
- });
- var fs = require('fs');
- fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data));
- - name: Extract Artifacts
- run: unzip -d webapp previewbuild.zip && rm previewbuild.zip
- - name: Deploy to Netlify
- id: netlify
- uses: nwtgck/actions-netlify@v1.2
- with:
- publish-dir: webapp
- deploy-message: "Deploy from GitHub Actions"
- # These don't work because we're in workflow_run
- enable-pull-request-comment: false
- enable-commit-comment: false
- alias: pr${{ steps.readctx.outputs.prnumber }}
- env:
- NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
- NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
- timeout-minutes: 1
- - name: Edit PR Description
- uses: Beakyn/gha-comment-pull-request@2167a7aee24f9e61ce76a23039f322e49a990409
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- pull-request-number: ${{ steps.readctx.outputs.prnumber }}
- description-message: |
- Preview: ${{ steps.netlify.outputs.deploy-url }}
- ⚠️ Do you trust the author of this PR? Maybe this build will steal your keys or give you malware. Exercise caution. Use test accounts.
+ deploy:
+ runs-on: ubuntu-latest
+ if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request'
+ steps:
+ - name: "🔍 Read PR number"
+ id: readctx
+ # We need to find the PR number that corresponds to the branch, which we do by searching the GH API
+ # The workflow_run event includes a list of pull requests, but it doesn't get populated for
+ # forked PRs: https://docs.github.com/en/rest/reference/checks#create-a-check-run
+ run: |
+ head_branch='${{github.event.workflow_run.head_repository.owner.login}}:${{github.event.workflow_run.head_branch}}'
+ echo "Head branch: $head_branch"
+ pulls_uri="https://api.github.com/repos/${{ github.repository }}/pulls?head=$(jq -Rr '@uri' <<<$head_branch)"
+ pr_number=$(curl -s -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' "$pulls_uri" |
+ jq -r '.[] | .number')
+ echo "PR number: $pr_number"
+ echo "::set-output name=prnumber::$pr_number"
+ - name: Create Deployment
+ uses: bobheadxi/deployments@v1
+ id: deployment
+ with:
+ step: start
+ token: ${{ secrets.GITHUB_TOKEN }}
+ env: Netlify
+ ref: ${{ github.event.workflow_run.head_sha }}
+ desc: |
+ Do you trust the author of this PR? Maybe this build will steal your keys or give you malware.
+ Exercise caution. Use test accounts.
+
+ # 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: actions/github-script@v3.1.0
+ with:
+ script: |
+ const artifacts = await github.actions.listWorkflowRunArtifacts({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: ${{ github.event.workflow_run.id }},
+ });
+ const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
+ return artifact.name == "previewbuild"
+ })[0];
+ const download = await github.actions.downloadArtifact({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ artifact_id: matchArtifact.id,
+ archive_format: 'zip',
+ });
+ const fs = require('fs');
+ fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data));
+
+ - name: Extract Artifacts
+ run: unzip -d webapp previewbuild.zip && rm previewbuild.zip
+
+ - name: Deploy to Netlify
+ id: netlify
+ uses: nwtgck/actions-netlify@v1.2
+ with:
+ publish-dir: webapp
+ deploy-message: "Deploy from GitHub Actions"
+ # These don't work because we're in workflow_run
+ enable-pull-request-comment: false
+ enable-commit-comment: false
+ alias: pr${{ steps.readctx.outputs.prnumber }}
+ env:
+ NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
+ NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
+ timeout-minutes: 1
+
+ - name: Update deployment status
+ uses: bobheadxi/deployments@v1
+ if: always()
+ with:
+ step: finish
+ token: ${{ secrets.GITHUB_TOKEN }}
+ status: ${{ job.status }}
+ env: ${{ steps.deployment.outputs.env }}
+ deployment_id: ${{ steps.deployment.outputs.deployment_id }}
+ env_url: ${{ steps.netlify.outputs.deploy-url }}
+ desc: |
+ Do you trust the author of this PR? Maybe this build will steal your keys or give you malware.
+ Exercise caution. Use test accounts.
diff --git a/.github/workflows/notify-element-web.yml b/.github/workflows/notify-element-web.yml
index ef463784f38..1d60a1523cc 100644
--- a/.github/workflows/notify-element-web.yml
+++ b/.github/workflows/notify-element-web.yml
@@ -1,15 +1,17 @@
name: Notify element-web
on:
- push:
- branches: [develop]
+ push:
+ branches: [ develop ]
+ repository_dispatch:
+ types: [ upstream-sdk-notify ]
jobs:
- notify-element-web:
- runs-on: ubuntu-latest
- environment: develop
- steps:
- - name: Notify element-web repo that a new SDK build is on develop
- uses: peter-evans/repository-dispatch@v1
- with:
- token: ${{ secrets.ELEMENT_WEB_NOTIFY_TOKEN }}
- repository: vector-im/element-web
- event-type: element-web-notify
+ notify-element-web:
+ name: "Notify Element Web"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Notify element-web repo that a new SDK build is on develop
+ uses: peter-evans/repository-dispatch@v1
+ with:
+ token: ${{ secrets.ELEMENT_BOT_TOKEN }}
+ repository: vector-im/element-web
+ event-type: element-web-notify
diff --git a/.github/workflows/preview_changelog.yaml b/.github/workflows/preview_changelog.yaml
index d68d19361da..786d828d419 100644
--- a/.github/workflows/preview_changelog.yaml
+++ b/.github/workflows/preview_changelog.yaml
@@ -3,10 +3,10 @@ on:
pull_request_target:
types: [ opened, edited, labeled ]
jobs:
- changelog:
- runs-on: ubuntu-latest
- steps:
- - name: Preview Changelog
- uses: matrix-org/allchange@main
- with:
- ghToken: ${{ secrets.GITHUB_TOKEN }}
+ changelog:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Preview Changelog
+ uses: matrix-org/allchange@main
+ with:
+ ghToken: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml
new file mode 100644
index 00000000000..63e939f7f9a
--- /dev/null
+++ b/.github/workflows/static_analysis.yaml
@@ -0,0 +1,102 @@
+name: Static Analysis
+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:
+ ts_lint:
+ name: "Typescript Syntax Check"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions/setup-node@v3
+ with:
+ cache: 'yarn'
+
+ - name: Install Deps
+ run: "./scripts/ci/install-deps.sh --ignore-scripts"
+
+ - name: Typecheck
+ run: "yarn run lint:types"
+
+ - name: Switch js-sdk to release mode
+ run: |
+ scripts/ci/js-sdk-to-release.js
+ cd node_modules/matrix-js-sdk
+ yarn install
+ yarn run build:compile
+ yarn run build:types
+
+ - name: Typecheck (release mode)
+ run: "yarn run lint:types"
+
+ i18n_lint:
+ name: "i18n Diff Check"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions/setup-node@v3
+ with:
+ cache: 'yarn'
+
+ # Does not need branch matching as only analyses this layer
+ - name: Install Deps
+ run: "yarn install"
+
+ - name: i18n Check
+ run: "yarn run diff-i18n"
+
+ js_lint:
+ name: "ESLint"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions/setup-node@v3
+ with:
+ cache: 'yarn'
+
+ # Does not need branch matching as only analyses this layer
+ - name: Install Deps
+ run: "yarn install"
+
+ - name: Run Linter
+ run: "yarn run lint:js"
+
+ style_lint:
+ name: "Style Lint"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions/setup-node@v3
+ with:
+ cache: 'yarn'
+
+ # Does not need branch matching as only analyses this layer
+ - name: Install Deps
+ run: "yarn install"
+
+ - name: Run Linter
+ run: "yarn run lint:style"
+
+ sonarqube:
+ name: "SonarQube"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
+ - name: SonarCloud Scan
+ uses: SonarSource/sonarcloud-github-action@master
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000000..f160e42844d
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,41 @@
+name: Tests
+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:
+ jest:
+ name: Jest with Codecov
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ with:
+ # If this is a pull request, make sure we check out its head rather than the
+ # automatically generated merge commit, so that the coverage diff excludes
+ # unrelated changes in the base branch
+ ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
+
+ - name: Yarn cache
+ uses: actions/setup-node@v3
+ with:
+ cache: 'yarn'
+
+ - name: Install Deps
+ run: "./scripts/ci/install-deps.sh --ignore-scripts"
+
+ - name: Run tests with coverage
+ run: "yarn coverage"
+
+ - name: Upload coverage
+ uses: codecov/codecov-action@v2
+ with:
+ fail_ci_if_error: false
+ verbose: true
+ override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
diff --git a/.github/workflows/triage-move-review-requests.yml b/.github/workflows/triage-move-review-requests.yml
deleted file mode 100644
index 49db29ff5c1..00000000000
--- a/.github/workflows/triage-move-review-requests.yml
+++ /dev/null
@@ -1,139 +0,0 @@
-name: Move pull requests asking for review to the relevant project
-on:
- pull_request_target:
- types: [review_requested]
-
-jobs:
- add_design_pr_to_project:
- name: Move PRs asking for design review to the design board
- runs-on: ubuntu-latest
- steps:
- - uses: octokit/graphql-action@v2.x
- id: find_team_members
- with:
- headers: '{"GraphQL-Features": "projects_next_graphql"}'
- query: |
- query find_team_members($team: String!) {
- organization(login: "matrix-org") {
- team(slug: $team) {
- members {
- nodes {
- login
- }
- }
- }
- }
- }
- team: ${{ env.TEAM }}
- env:
- TEAM: "design"
- GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
- - id: any_matching_reviewers
- run: |
- # Fetch requested reviewers, and people who are on the team
- echo '${{ tojson(fromjson(steps.find_team_members.outputs.data).organization.team.members.nodes[*].login) }}' | tee /tmp/team_members.json
- echo '${{ tojson(github.event.pull_request.requested_reviewers[*].login) }}' | tee /tmp/reviewers.json
- jq --raw-output .[] < /tmp/team_members.json | sort | tee /tmp/team_members.txt
- jq --raw-output .[] < /tmp/reviewers.json | sort | tee /tmp/reviewers.txt
-
- # Fetch requested team reviewers, and the name of the team
- echo '${{ tojson(github.event.pull_request.requested_teams[*].slug) }}' | tee /tmp/team_reviewers.json
- jq --raw-output .[] < /tmp/team_reviewers.json | sort | tee /tmp/team_reviewers.txt
- echo '${{ env.TEAM }}' | tee /tmp/team.txt
-
- # If either a reviewer matches a team member, or a team matches our team, say "true"
- if [ $(join /tmp/team_members.txt /tmp/reviewers.txt | wc -l) != 0 ]; then
- echo "::set-output name=match::true"
- elif [ $(join /tmp/team.txt /tmp/team_reviewers.txt | wc -l) != 0 ]; then
- echo "::set-output name=match::true"
- else
- echo "::set-output name=match::false"
- fi
- env:
- TEAM: "design"
- - uses: octokit/graphql-action@v2.x
- id: add_to_project
- if: steps.any_matching_reviewers.outputs.match == 'true'
- with:
- headers: '{"GraphQL-Features": "projects_next_graphql"}'
- query: |
- mutation add_to_project($projectid:ID!, $contentid:ID!) {
- addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
- projectNextItem {
- id
- }
- }
- }
- projectid: ${{ env.PROJECT_ID }}
- contentid: ${{ github.event.pull_request.node_id }}
- env:
- PROJECT_ID: "PN_kwDOAM0swc0sUA"
- TEAM: "design"
- GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
-
- add_product_pr_to_project:
- name: Move PRs asking for design review to the design board
- runs-on: ubuntu-latest
- steps:
- - uses: octokit/graphql-action@v2.x
- id: find_team_members
- with:
- headers: '{"GraphQL-Features": "projects_next_graphql"}'
- query: |
- query find_team_members($team: String!) {
- organization(login: "matrix-org") {
- team(slug: $team) {
- members {
- nodes {
- login
- }
- }
- }
- }
- }
- team: ${{ env.TEAM }}
- env:
- TEAM: "product"
- GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
- - id: any_matching_reviewers
- run: |
- # Fetch requested reviewers, and people who are on the team
- echo '${{ tojson(fromjson(steps.find_team_members.outputs.data).organization.team.members.nodes[*].login) }}' | tee /tmp/team_members.json
- echo '${{ tojson(github.event.pull_request.requested_reviewers[*].login) }}' | tee /tmp/reviewers.json
- jq --raw-output .[] < /tmp/team_members.json | sort | tee /tmp/team_members.txt
- jq --raw-output .[] < /tmp/reviewers.json | sort | tee /tmp/reviewers.txt
-
- # Fetch requested team reviewers, and the name of the team
- echo '${{ tojson(github.event.pull_request.requested_teams[*].slug) }}' | tee /tmp/team_reviewers.json
- jq --raw-output .[] < /tmp/team_reviewers.json | sort | tee /tmp/team_reviewers.txt
- echo '${{ env.TEAM }}' | tee /tmp/team.txt
-
- # If either a reviewer matches a team member, or a team matches our team, say "true"
- if [ $(join /tmp/team_members.txt /tmp/reviewers.txt | wc -l) != 0 ]; then
- echo "::set-output name=match::true"
- elif [ $(join /tmp/team.txt /tmp/team_reviewers.txt | wc -l) != 0 ]; then
- echo "::set-output name=match::true"
- else
- echo "::set-output name=match::false"
- fi
- env:
- TEAM: "product"
- - uses: octokit/graphql-action@v2.x
- id: add_to_project
- if: steps.any_matching_reviewers.outputs.match == 'true'
- with:
- headers: '{"GraphQL-Features": "projects_next_graphql"}'
- query: |
- mutation add_to_project($projectid:ID!, $contentid:ID!) {
- addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
- projectNextItem {
- id
- }
- }
- }
- projectid: ${{ env.PROJECT_ID }}
- contentid: ${{ github.event.pull_request.node_id }}
- env:
- PROJECT_ID: "PN_kwDOAM0swc4AAg6N"
- TEAM: "product"
- GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
diff --git a/.github/workflows/typecheck.yaml b/.github/workflows/typecheck.yaml
deleted file mode 100644
index f6ab6439582..00000000000
--- a/.github/workflows/typecheck.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Type Check
-on:
- pull_request:
- branches: [develop]
-jobs:
- build:
- runs-on: ubuntu-latest
- env:
- PR_NUMBER: ${{github.event.number}}
- steps:
- - uses: actions/checkout@v2
- - uses: c-hive/gha-yarn-cache@v2
- - name: Install Deps
- run: "./scripts/ci/install-deps.sh --ignore-scripts"
- - name: Typecheck
- run: "yarn run lint:types"
- - name: Switch js-sdk to release mode
- run: |
- scripts/ci/js-sdk-to-release.js
- cd node_modules/matrix-js-sdk
- yarn install
- yarn run build:compile
- yarn run build:types
- - name: Typecheck (release mode)
- run: "yarn run lint:types"
-
diff --git a/.gitignore b/.gitignore
index 102f4b5ec1f..8e14ba9057b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ package-lock.json
/matrix-react-sdk-*.tgz
/.idea
+# Legacy skinning file that some people might still have
/src/component-index.js
.DS_Store
@@ -18,3 +19,11 @@ package-lock.json
.vscode
.vscode/
+
+/cypress/videos
+/cypress/downloads
+/cypress/screenshots
+/cypress/synapselogs
+# These could have files in them but don't currently
+# Cypress will still auto-create them though...
+/cypress/fixtures
diff --git a/.stylelintrc.js b/.stylelintrc.js
index 0bdea3cccd7..e72e8ca55c3 100644
--- a/.stylelintrc.js
+++ b/.stylelintrc.js
@@ -23,5 +23,10 @@ module.exports = {
// https://github.com/vector-im/element-web/issues/10544
"ignoreAtRules": ["define-mixin"],
}],
+ // Disable `&_kind`-style selectors while our unused CSS approach is "Find & Replace All"
+ // rather than a CI thing. Shorthand selectors are harder to detect when searching for a
+ // class name. This regex is trying to *allow* anything except `&words`, such as `&::before`,
+ // `&.mx_Class`, etc.
+ "selector-nested-pattern": "^((&[ :.\\\[,])|([^&]))"
}
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0aa6b18fc2a..841a7360a10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,253 @@
+Changes in [3.43.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.43.0) (2022-04-26)
+=====================================================================================================
+
+## ✨ Features
+ * Improve performance of switching to rooms with lots of servers and ACLs ([\#8347](https://github.com/matrix-org/matrix-react-sdk/pull/8347)).
+ * Avoid a reflow when setting caret position on an empty composer ([\#8348](https://github.com/matrix-org/matrix-react-sdk/pull/8348)).
+ * Add message right-click context menu as a labs feature ([\#5672](https://github.com/matrix-org/matrix-react-sdk/pull/5672)).
+ * Live location sharing - basic maximised beacon map ([\#8310](https://github.com/matrix-org/matrix-react-sdk/pull/8310)).
+ * Live location sharing - render users own beacons in timeline ([\#8296](https://github.com/matrix-org/matrix-react-sdk/pull/8296)).
+ * Improve Threads beta around degraded mode ([\#8318](https://github.com/matrix-org/matrix-react-sdk/pull/8318)).
+ * Live location sharing - beacon in timeline happy path ([\#8285](https://github.com/matrix-org/matrix-react-sdk/pull/8285)).
+ * Add copy button to View Source screen ([\#8278](https://github.com/matrix-org/matrix-react-sdk/pull/8278)). Fixes vector-im/element-web#21482. Contributed by @olivialivia.
+ * Add heart effect ([\#6188](https://github.com/matrix-org/matrix-react-sdk/pull/6188)). Contributed by @CicadaCinema.
+ * Update new room icon ([\#8239](https://github.com/matrix-org/matrix-react-sdk/pull/8239)).
+
+## 🐛 Bug Fixes
+ * Fix: "Code formatting button does not escape backticks" ([\#8181](https://github.com/matrix-org/matrix-react-sdk/pull/8181)). Contributed by @yaya-usman.
+ * Fix beta indicator dot causing excessive CPU usage ([\#8340](https://github.com/matrix-org/matrix-react-sdk/pull/8340)). Fixes vector-im/element-web#21793.
+ * Fix overlapping timestamps on empty messages ([\#8205](https://github.com/matrix-org/matrix-react-sdk/pull/8205)). Fixes vector-im/element-web#21381. Contributed by @goelesha.
+ * Fix power selector not showing up in user info when state_default undefined ([\#8297](https://github.com/matrix-org/matrix-react-sdk/pull/8297)). Fixes vector-im/element-web#21669.
+ * Avoid looking up settings during timeline rendering ([\#8313](https://github.com/matrix-org/matrix-react-sdk/pull/8313)). Fixes vector-im/element-web#21740.
+ * Fix a soft crash with video rooms ([\#8333](https://github.com/matrix-org/matrix-react-sdk/pull/8333)).
+ * Fixes call tiles overflow ([\#8096](https://github.com/matrix-org/matrix-react-sdk/pull/8096)). Fixes vector-im/element-web#20254. Contributed by @luixxiul.
+ * Fix a bug with emoji autocomplete sorting where adding the final ":" would cause the emoji with the typed shortcode to no longer be at the top of the autocomplete list. ([\#8086](https://github.com/matrix-org/matrix-react-sdk/pull/8086)). Fixes vector-im/element-web#19302. Contributed by @commonlawfeature.
+ * Fix image preview sizing for edge cases ([\#8322](https://github.com/matrix-org/matrix-react-sdk/pull/8322)). Fixes vector-im/element-web#20088.
+ * Refactor SecurityRoomSettingsTab and remove unused state ([\#8306](https://github.com/matrix-org/matrix-react-sdk/pull/8306)). Fixes matrix-org/element-web-rageshakes#12002.
+ * Don't show the prompt to enable desktop notifications immediately after registration ([\#8274](https://github.com/matrix-org/matrix-react-sdk/pull/8274)).
+ * Stop tracking threads if threads support is disabled ([\#8308](https://github.com/matrix-org/matrix-react-sdk/pull/8308)). Fixes vector-im/element-web#21766.
+ * Fix some issues with threads rendering ([\#8305](https://github.com/matrix-org/matrix-react-sdk/pull/8305)). Fixes vector-im/element-web#21670.
+ * Fix threads rendering issue in Safari ([\#8298](https://github.com/matrix-org/matrix-react-sdk/pull/8298)). Fixes vector-im/element-web#21757.
+ * Fix space panel width change on hovering over space item ([\#8299](https://github.com/matrix-org/matrix-react-sdk/pull/8299)). Fixes vector-im/element-web#19891.
+ * Hide the reply in thread button in deployments where beta is forcibly disabled ([\#8294](https://github.com/matrix-org/matrix-react-sdk/pull/8294)). Fixes vector-im/element-web#21753.
+ * Prevent soft crash around room list header context menu when space changes ([\#8289](https://github.com/matrix-org/matrix-react-sdk/pull/8289)). Fixes matrix-org/element-web-rageshakes#11416, matrix-org/element-web-rageshakes#11692, matrix-org/element-web-rageshakes#11739, matrix-org/element-web-rageshakes#11772, matrix-org/element-web-rageshakes#11891 matrix-org/element-web-rageshakes#11858 and matrix-org/element-web-rageshakes#11456.
+ * When selecting reply in thread on a thread response open existing thread ([\#8291](https://github.com/matrix-org/matrix-react-sdk/pull/8291)). Fixes vector-im/element-web#21743.
+ * Handle thread bundled relationships coming from the server via MSC3666 ([\#8292](https://github.com/matrix-org/matrix-react-sdk/pull/8292)). Fixes vector-im/element-web#21450.
+ * Fix: Avatar preview does not update when same file is selected repeatedly ([\#8288](https://github.com/matrix-org/matrix-react-sdk/pull/8288)). Fixes vector-im/element-web#20098.
+ * Fix a bug where user gets a warning when changing powerlevel from **Admin** to **custom level (100)** ([\#8248](https://github.com/matrix-org/matrix-react-sdk/pull/8248)). Fixes vector-im/element-web#21682. Contributed by @Jumeb.
+ * Use a consistent alignment for all text items in a list ([\#8276](https://github.com/matrix-org/matrix-react-sdk/pull/8276)). Fixes vector-im/element-web#21731. Contributed by @luixxiul.
+ * Fixes button labels being collapsed per a character in CJK languages ([\#8212](https://github.com/matrix-org/matrix-react-sdk/pull/8212)). Fixes vector-im/element-web#21287. Contributed by @luixxiul.
+ * Fix: Remove jittery timeline scrolling after jumping to an event ([\#8263](https://github.com/matrix-org/matrix-react-sdk/pull/8263)).
+ * Fix regression of edits showing up in the timeline with hidden events shown ([\#8260](https://github.com/matrix-org/matrix-react-sdk/pull/8260)). Fixes vector-im/element-web#21694.
+ * Fix reporting events not working ([\#8257](https://github.com/matrix-org/matrix-react-sdk/pull/8257)). Fixes vector-im/element-web#21713.
+ * Make Jitsi widgets in video rooms immutable ([\#8244](https://github.com/matrix-org/matrix-react-sdk/pull/8244)). Fixes vector-im/element-web#21647.
+ * Fix: Ensure links to events scroll the correct events into view ([\#8250](https://github.com/matrix-org/matrix-react-sdk/pull/8250)). Fixes vector-im/element-web#19934.
+
+Changes in [3.42.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.42.4) (2022-04-14)
+=====================================================================================================
+
+## 🐛 Bug Fixes
+ * Fixes around threads beta in degraded mode ([\#8319](https://github.com/matrix-org/matrix-react-sdk/pull/8319)). Fixes vector-im/element-web#21762.
+
+Changes in [3.42.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.42.3) (2022-04-12)
+=====================================================================================================
+
+## ✨ Features
+ * Release threads as a beta feature ([\#8081](https://github.com/matrix-org/matrix-react-sdk/pull/8081)). Fixes vector-im/element-web#21351.
+ * More video rooms design updates ([\#8222](https://github.com/matrix-org/matrix-react-sdk/pull/8222)).
+ * Update video rooms to new design specs ([\#8207](https://github.com/matrix-org/matrix-react-sdk/pull/8207)). Fixes vector-im/element-web#21515, vector-im/element-web#21516 vector-im/element-web#21519 and vector-im/element-web#21526.
+ * Live Location Sharing - left panel warning with error ([\#8201](https://github.com/matrix-org/matrix-react-sdk/pull/8201)).
+ * Live location sharing - Stop publishing location to beacons with consecutive errors ([\#8194](https://github.com/matrix-org/matrix-react-sdk/pull/8194)).
+ * Live location sharing: allow retry when stop sharing fails ([\#8193](https://github.com/matrix-org/matrix-react-sdk/pull/8193)).
+ * Allow voice messages to be scrubbed in the timeline ([\#8079](https://github.com/matrix-org/matrix-react-sdk/pull/8079)). Fixes vector-im/element-web#18713.
+ * Live location sharing - stop sharing to beacons in rooms you left ([\#8187](https://github.com/matrix-org/matrix-react-sdk/pull/8187)).
+ * Allow sending and thumbnailing AVIF images ([\#8172](https://github.com/matrix-org/matrix-react-sdk/pull/8172)).
+ * Live location sharing - handle geolocation errors ([\#8179](https://github.com/matrix-org/matrix-react-sdk/pull/8179)).
+ * Show voice room participants when not connected ([\#8136](https://github.com/matrix-org/matrix-react-sdk/pull/8136)). Fixes vector-im/element-web#21513.
+ * Add margins between labs sections ([\#8169](https://github.com/matrix-org/matrix-react-sdk/pull/8169)).
+ * Live location sharing - send geolocation beacon events - happy path ([\#8127](https://github.com/matrix-org/matrix-react-sdk/pull/8127)).
+ * Add support for Animated (A)PNG ([\#8158](https://github.com/matrix-org/matrix-react-sdk/pull/8158)). Fixes vector-im/element-web#12967.
+ * Don't form continuations from thread roots ([\#8166](https://github.com/matrix-org/matrix-react-sdk/pull/8166)). Fixes vector-im/element-web#20908.
+ * Improve handling of animated GIF and WEBP images ([\#8153](https://github.com/matrix-org/matrix-react-sdk/pull/8153)). Fixes vector-im/element-web#16193 and vector-im/element-web#6684.
+ * Wire up file preview for video files ([\#8140](https://github.com/matrix-org/matrix-react-sdk/pull/8140)). Fixes vector-im/element-web#21539.
+ * When showing thread, always auto-focus its composer ([\#8115](https://github.com/matrix-org/matrix-react-sdk/pull/8115)). Fixes vector-im/element-web#21438.
+ * Live location sharing - refresh beacon expiry in room ([\#8116](https://github.com/matrix-org/matrix-react-sdk/pull/8116)).
+ * Use styled mxids in member list v2 ([\#8110](https://github.com/matrix-org/matrix-react-sdk/pull/8110)). Fixes vector-im/element-web#14825. Contributed by @SimonBrandner.
+ * Delete groups (legacy communities system) ([\#8027](https://github.com/matrix-org/matrix-react-sdk/pull/8027)). Fixes vector-im/element-web#17532.
+ * Add a prototype of voice rooms in labs ([\#8084](https://github.com/matrix-org/matrix-react-sdk/pull/8084)). Fixes vector-im/element-web#3546.
+
+## 🐛 Bug Fixes
+ * Fix editing `
` tags with a non-1 start attribute ([\#8211](https://github.com/matrix-org/matrix-react-sdk/pull/8211)). Fixes vector-im/element-web#21625.
+ * Fix URL previews being enabled when room first created ([\#8227](https://github.com/matrix-org/matrix-react-sdk/pull/8227)). Fixes vector-im/element-web#21659.
+ * Don't use m.call for Jitsi video rooms ([\#8223](https://github.com/matrix-org/matrix-react-sdk/pull/8223)).
+ * Scale emoji with size of surrounding text ([\#8224](https://github.com/matrix-org/matrix-react-sdk/pull/8224)).
+ * Make "Jump to date" translatable ([\#8218](https://github.com/matrix-org/matrix-react-sdk/pull/8218)).
+ * Normalize call buttons ([\#8129](https://github.com/matrix-org/matrix-react-sdk/pull/8129)). Fixes vector-im/element-web#21493. Contributed by @luixxiul.
+ * Show room preview bar with maximised widgets ([\#8180](https://github.com/matrix-org/matrix-react-sdk/pull/8180)). Fixes vector-im/element-web#21542.
+ * Update more strings to not wrongly mention room when it is/could be a space ([\#7722](https://github.com/matrix-org/matrix-react-sdk/pull/7722)). Fixes vector-im/element-web#20243 and vector-im/element-web#20910.
+ * Fix issue with redacting via edit composer flow causing stuck editStates ([\#8184](https://github.com/matrix-org/matrix-react-sdk/pull/8184)).
+ * Fix some image/video scroll jumps ([\#8182](https://github.com/matrix-org/matrix-react-sdk/pull/8182)).
+ * Fix "react error on share dialog" ([\#8170](https://github.com/matrix-org/matrix-react-sdk/pull/8170)). Contributed by @yaya-usman.
+ * Fix disambiguated profile in threads in bubble layout ([\#8168](https://github.com/matrix-org/matrix-react-sdk/pull/8168)). Fixes vector-im/element-web#21570. Contributed by @SimonBrandner.
+ * Responsive BetaCard on Labs ([\#8154](https://github.com/matrix-org/matrix-react-sdk/pull/8154)). Fixes vector-im/element-web#21554. Contributed by @luixxiul.
+ * Display button as inline in room directory dialog ([\#8164](https://github.com/matrix-org/matrix-react-sdk/pull/8164)). Fixes vector-im/element-web#21567. Contributed by @luixxiul.
+ * Null guard TimelinePanel unmount edge ([\#8171](https://github.com/matrix-org/matrix-react-sdk/pull/8171)).
+ * Fix beta pill label breaking ([\#8162](https://github.com/matrix-org/matrix-react-sdk/pull/8162)). Fixes vector-im/element-web#21566. Contributed by @luixxiul.
+ * Strip relations when forwarding ([\#7929](https://github.com/matrix-org/matrix-react-sdk/pull/7929)). Fixes vector-im/element-web#19769, vector-im/element-web#18067 vector-im/element-web#21015 and vector-im/element-web#10924.
+ * Don't try (and fail) to show replies for redacted events ([\#8141](https://github.com/matrix-org/matrix-react-sdk/pull/8141)). Fixes vector-im/element-web#21435.
+ * Fix 3pid member info for space member list ([\#8128](https://github.com/matrix-org/matrix-react-sdk/pull/8128)). Fixes vector-im/element-web#21534.
+ * Set max-width to user context menu ([\#8089](https://github.com/matrix-org/matrix-react-sdk/pull/8089)). Fixes vector-im/element-web#21486. Contributed by @luixxiul.
+ * Fix issue with falsey hrefs being sent in events ([\#8113](https://github.com/matrix-org/matrix-react-sdk/pull/8113)). Fixes vector-im/element-web#21417.
+ * Make video sizing consistent with images ([\#8102](https://github.com/matrix-org/matrix-react-sdk/pull/8102)). Fixes vector-im/element-web#20072.
+
+Changes in [3.42.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.42.0) (2022-03-15)
+=====================================================================================================
+
+## 🔒 SECURITY FIXES
+
+ * Fix a bug where URL previews could be enabled in the left-panel when they
+ should not have been.
+
+## ✨ Features
+ * Add unexposed account setting for hiding poll creation ([\#7972](https://github.com/matrix-org/matrix-react-sdk/pull/7972)).
+ * Allow pinning polls ([\#7922](https://github.com/matrix-org/matrix-react-sdk/pull/7922)). Fixes vector-im/element-web#20152.
+ * Make trailing `:` into a setting ([\#6711](https://github.com/matrix-org/matrix-react-sdk/pull/6711)). Fixes vector-im/element-web#16682. Contributed by @SimonBrandner.
+ * Location sharing > back button ([\#7958](https://github.com/matrix-org/matrix-react-sdk/pull/7958)).
+ * use LocationAssetType ([\#7965](https://github.com/matrix-org/matrix-react-sdk/pull/7965)).
+ * Location share type UI ([\#7924](https://github.com/matrix-org/matrix-react-sdk/pull/7924)).
+ * Add a few more UIComponent flags, and ensure they are used in existing code ([\#7937](https://github.com/matrix-org/matrix-react-sdk/pull/7937)).
+ * Add support for overriding strings in the app ([\#7886](https://github.com/matrix-org/matrix-react-sdk/pull/7886)).
+ * Add support for redirecting to external pages after logout ([\#7905](https://github.com/matrix-org/matrix-react-sdk/pull/7905)).
+ * Expose redaction power level in room settings ([\#7599](https://github.com/matrix-org/matrix-react-sdk/pull/7599)). Fixes vector-im/element-web#20590. Contributed by @SimonBrandner.
+ * Update and expand ways to access pinned messages ([\#7906](https://github.com/matrix-org/matrix-react-sdk/pull/7906)). Fixes vector-im/element-web#21209 and vector-im/element-web#21211.
+ * Add slash command to switch to a room's virtual room ([\#7839](https://github.com/matrix-org/matrix-react-sdk/pull/7839)).
+
+## 🐛 Bug Fixes
+ * Merge pull request from GHSA-qmf4-7w7j-vf23 ([\#8059](https://github.com/matrix-org/matrix-react-sdk/pull/8059)).
+ * Add another null guard for member ([\#7984](https://github.com/matrix-org/matrix-react-sdk/pull/7984)). Fixes vector-im/element-web#21319.
+ * Fix room account settings ([\#7999](https://github.com/matrix-org/matrix-react-sdk/pull/7999)).
+ * Fix missing summary text for pinned message changes ([\#7989](https://github.com/matrix-org/matrix-react-sdk/pull/7989)). Fixes vector-im/element-web#19823.
+ * Pass room to getRoomTombstone to avoid racing with setState ([\#7986](https://github.com/matrix-org/matrix-react-sdk/pull/7986)).
+ * Hide composer and call buttons when the room is tombstoned ([\#7975](https://github.com/matrix-org/matrix-react-sdk/pull/7975)). Fixes vector-im/element-web#21286.
+ * Fix bad ternary statement in autocomplete user pill insertions ([\#7977](https://github.com/matrix-org/matrix-react-sdk/pull/7977)). Fixes vector-im/element-web#21307.
+ * Fix sending locations into threads and fix i18n ([\#7943](https://github.com/matrix-org/matrix-react-sdk/pull/7943)). Fixes vector-im/element-web#21267.
+ * Fix location map attribution rendering over message action bar ([\#7974](https://github.com/matrix-org/matrix-react-sdk/pull/7974)). Fixes vector-im/element-web#21297.
+ * Fix wrongly asserting that PushRule::conditions is non-null ([\#7973](https://github.com/matrix-org/matrix-react-sdk/pull/7973)). Fixes vector-im/element-web#21305.
+ * Fix account & room settings race condition ([\#7953](https://github.com/matrix-org/matrix-react-sdk/pull/7953)). Fixes vector-im/element-web#21163.
+ * Fix bug with some space selections not being applied ([\#7971](https://github.com/matrix-org/matrix-react-sdk/pull/7971)). Fixes vector-im/element-web#21290.
+ * Revert "replace all require(.svg) with esm import" ([\#7969](https://github.com/matrix-org/matrix-react-sdk/pull/7969)). Fixes vector-im/element-web#21293.
+ * Hide unpinnable pinned messages in more cases ([\#7921](https://github.com/matrix-org/matrix-react-sdk/pull/7921)).
+ * Fix room list being laggy while scrolling 🐌 ([\#7939](https://github.com/matrix-org/matrix-react-sdk/pull/7939)). Fixes vector-im/element-web#21262.
+ * Make pinned messages more reliably reflect edits ([\#7920](https://github.com/matrix-org/matrix-react-sdk/pull/7920)). Fixes vector-im/element-web#17098.
+ * Improve accessibility of the BetaPill ([\#7949](https://github.com/matrix-org/matrix-react-sdk/pull/7949)). Fixes vector-im/element-web#21255.
+ * Autofocus correct composer after sending reaction ([\#7950](https://github.com/matrix-org/matrix-react-sdk/pull/7950)). Fixes vector-im/element-web#21273.
+ * Consider polls as message events for rendering redactions ([\#7944](https://github.com/matrix-org/matrix-react-sdk/pull/7944)). Fixes vector-im/element-web#21125.
+ * Prevent event tiles being shrunk/collapsed by flexbox ([\#7942](https://github.com/matrix-org/matrix-react-sdk/pull/7942)). Fixes vector-im/element-web#21269.
+ * Fix ExportDialog title on export cancellation ([\#7936](https://github.com/matrix-org/matrix-react-sdk/pull/7936)). Fixes vector-im/element-web#21260. Contributed by @luixxiul.
+ * Mandate use of js-sdk/src/matrix import over js-sdk/src ([\#7933](https://github.com/matrix-org/matrix-react-sdk/pull/7933)). Fixes vector-im/element-web#21253.
+ * Fix backspace not working in the invite dialog ([\#7931](https://github.com/matrix-org/matrix-react-sdk/pull/7931)). Fixes vector-im/element-web#21249. Contributed by @SimonBrandner.
+ * Fix right panel soft crashes due to missing room prop ([\#7923](https://github.com/matrix-org/matrix-react-sdk/pull/7923)). Fixes vector-im/element-web#21243.
+ * fix color of location share caret ([\#7917](https://github.com/matrix-org/matrix-react-sdk/pull/7917)).
+ * Wrap all EventTiles with a TileErrorBoundary and guard parsePermalink ([\#7916](https://github.com/matrix-org/matrix-react-sdk/pull/7916)). Fixes vector-im/element-web#21216.
+ * Fix changing space sometimes bouncing to the wrong space ([\#7910](https://github.com/matrix-org/matrix-react-sdk/pull/7910)). Fixes vector-im/element-web#20425.
+ * Ensure EventListSummary key does not change during backpagination ([\#7915](https://github.com/matrix-org/matrix-react-sdk/pull/7915)). Fixes vector-im/element-web#9192.
+ * Fix positioning of the thread context menu ([\#7918](https://github.com/matrix-org/matrix-react-sdk/pull/7918)). Fixes vector-im/element-web#21236.
+ * Inject sender into pinned messages ([\#7904](https://github.com/matrix-org/matrix-react-sdk/pull/7904)). Fixes vector-im/element-web#20314.
+ * Tweak info message padding in right panel timeline ([\#7901](https://github.com/matrix-org/matrix-react-sdk/pull/7901)). Fixes vector-im/element-web#21212.
+ * Fix another freeze on room switch ([\#7900](https://github.com/matrix-org/matrix-react-sdk/pull/7900)). Fixes vector-im/element-web#21127.
+ * Clean up error listener when location picker closes ([\#7902](https://github.com/matrix-org/matrix-react-sdk/pull/7902)). Fixes vector-im/element-web#21213.
+ * Fix edge case in context menu chevron positioning ([\#7899](https://github.com/matrix-org/matrix-react-sdk/pull/7899)).
+ * Fix composer format buttons on WebKit ([\#7898](https://github.com/matrix-org/matrix-react-sdk/pull/7898)). Fixes vector-im/element-web#20868.
+ * manage voicerecording state when deleting or sending a voice message ([\#7896](https://github.com/matrix-org/matrix-react-sdk/pull/7896)). Fixes vector-im/element-web#21151.
+ * Fix bug with useRoomHierarchy tight-looping loadMore on error ([\#7893](https://github.com/matrix-org/matrix-react-sdk/pull/7893)).
+ * Fix upload button & shortcut not working for narrow composer mode ([\#7894](https://github.com/matrix-org/matrix-react-sdk/pull/7894)). Fixes vector-im/element-web#21175 and vector-im/element-web#21142.
+ * Fix emoji insertion in thread composer going to the main composer ([\#7895](https://github.com/matrix-org/matrix-react-sdk/pull/7895)). Fixes vector-im/element-web#21202.
+ * Try harder to keep context menus inside the window ([\#7863](https://github.com/matrix-org/matrix-react-sdk/pull/7863)). Fixes vector-im/element-web#17527 and vector-im/element-web#18377.
+ * Fix edge case around event list summary layout ([\#7891](https://github.com/matrix-org/matrix-react-sdk/pull/7891)). Fixes vector-im/element-web#21180.
+ * Fix event list summary 1 hidden message pluralisation ([\#7890](https://github.com/matrix-org/matrix-react-sdk/pull/7890)). Fixes vector-im/element-web#21196.
+ * Fix vanishing recently viewed menu ([\#7887](https://github.com/matrix-org/matrix-react-sdk/pull/7887)). Fixes vector-im/element-web#20827.
+ * Fix freeze on room switch ([\#7884](https://github.com/matrix-org/matrix-react-sdk/pull/7884)). Fixes vector-im/element-web#21127.
+ * Check 'useSystemTheme' in quick settings theme switcher ([\#7809](https://github.com/matrix-org/matrix-react-sdk/pull/7809)). Fixes vector-im/element-web#21061.
+ * Fix 'my threads' filtering to include participated threads ([\#7882](https://github.com/matrix-org/matrix-react-sdk/pull/7882)). Fixes vector-im/element-web#20877.
+ * Remove log line to try to fix freeze on answering VoIP call ([\#7883](https://github.com/matrix-org/matrix-react-sdk/pull/7883)).
+ * Support social login & password on soft logout page ([\#7879](https://github.com/matrix-org/matrix-react-sdk/pull/7879)). Fixes vector-im/element-web#21099.
+ * Fix missing padding on server picker ([\#7864](https://github.com/matrix-org/matrix-react-sdk/pull/7864)).
+ * Throttle RoomState.members handlers ([\#7876](https://github.com/matrix-org/matrix-react-sdk/pull/7876)). Fixes vector-im/element-web#21127.
+ * Only show joined/invited in search dialog ([\#7875](https://github.com/matrix-org/matrix-react-sdk/pull/7875)). Fixes vector-im/element-web#21161.
+ * Don't pillify code blocks ([\#7861](https://github.com/matrix-org/matrix-react-sdk/pull/7861)). Fixes vector-im/element-web#20851 and vector-im/element-web#18687.
+ * Fix keyboard shortcut icons on macOS ([\#7869](https://github.com/matrix-org/matrix-react-sdk/pull/7869)).
+
+Changes in [3.42.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.42.0-rc.1) (2022-03-08)
+===============================================================================================================
+
+## ✨ Features
+ * Add unexposed account setting for hiding poll creation ([\#7972](https://github.com/matrix-org/matrix-react-sdk/pull/7972)).
+ * Allow pinning polls ([\#7922](https://github.com/matrix-org/matrix-react-sdk/pull/7922)). Fixes vector-im/element-web#20152.
+ * Make trailing `:` into a setting ([\#6711](https://github.com/matrix-org/matrix-react-sdk/pull/6711)). Fixes vector-im/element-web#16682. Contributed by @SimonBrandner.
+ * Location sharing > back button ([\#7958](https://github.com/matrix-org/matrix-react-sdk/pull/7958)).
+ * use LocationAssetType ([\#7965](https://github.com/matrix-org/matrix-react-sdk/pull/7965)).
+ * Location share type UI ([\#7924](https://github.com/matrix-org/matrix-react-sdk/pull/7924)).
+ * Add a few more UIComponent flags, and ensure they are used in existing code ([\#7937](https://github.com/matrix-org/matrix-react-sdk/pull/7937)).
+ * Add support for overriding strings in the app ([\#7886](https://github.com/matrix-org/matrix-react-sdk/pull/7886)).
+ * Add support for redirecting to external pages after logout ([\#7905](https://github.com/matrix-org/matrix-react-sdk/pull/7905)).
+ * Expose redaction power level in room settings ([\#7599](https://github.com/matrix-org/matrix-react-sdk/pull/7599)). Fixes vector-im/element-web#20590. Contributed by @SimonBrandner.
+ * Update and expand ways to access pinned messages ([\#7906](https://github.com/matrix-org/matrix-react-sdk/pull/7906)). Fixes vector-im/element-web#21209 and vector-im/element-web#21211.
+ * Add slash command to switch to a room's virtual room ([\#7839](https://github.com/matrix-org/matrix-react-sdk/pull/7839)).
+
+## 🐛 Bug Fixes
+ * Add another null guard for member ([\#7984](https://github.com/matrix-org/matrix-react-sdk/pull/7984)). Fixes vector-im/element-web#21319.
+ * Fix room account settings ([\#7999](https://github.com/matrix-org/matrix-react-sdk/pull/7999)).
+ * Fix missing summary text for pinned message changes ([\#7989](https://github.com/matrix-org/matrix-react-sdk/pull/7989)). Fixes vector-im/element-web#19823.
+ * Pass room to getRoomTombstone to avoid racing with setState ([\#7986](https://github.com/matrix-org/matrix-react-sdk/pull/7986)).
+ * Hide composer and call buttons when the room is tombstoned ([\#7975](https://github.com/matrix-org/matrix-react-sdk/pull/7975)). Fixes vector-im/element-web#21286.
+ * Fix bad ternary statement in autocomplete user pill insertions ([\#7977](https://github.com/matrix-org/matrix-react-sdk/pull/7977)). Fixes vector-im/element-web#21307.
+ * Fix sending locations into threads and fix i18n ([\#7943](https://github.com/matrix-org/matrix-react-sdk/pull/7943)). Fixes vector-im/element-web#21267.
+ * Fix location map attribution rendering over message action bar ([\#7974](https://github.com/matrix-org/matrix-react-sdk/pull/7974)). Fixes vector-im/element-web#21297.
+ * Fix wrongly asserting that PushRule::conditions is non-null ([\#7973](https://github.com/matrix-org/matrix-react-sdk/pull/7973)). Fixes vector-im/element-web#21305.
+ * Fix account & room settings race condition ([\#7953](https://github.com/matrix-org/matrix-react-sdk/pull/7953)). Fixes vector-im/element-web#21163.
+ * Fix bug with some space selections not being applied ([\#7971](https://github.com/matrix-org/matrix-react-sdk/pull/7971)). Fixes vector-im/element-web#21290.
+ * Revert "replace all require(.svg) with esm import" ([\#7969](https://github.com/matrix-org/matrix-react-sdk/pull/7969)). Fixes vector-im/element-web#21293.
+ * Hide unpinnable pinned messages in more cases ([\#7921](https://github.com/matrix-org/matrix-react-sdk/pull/7921)).
+ * Fix room list being laggy while scrolling 🐌 ([\#7939](https://github.com/matrix-org/matrix-react-sdk/pull/7939)). Fixes vector-im/element-web#21262.
+ * Make pinned messages more reliably reflect edits ([\#7920](https://github.com/matrix-org/matrix-react-sdk/pull/7920)). Fixes vector-im/element-web#17098.
+ * Improve accessibility of the BetaPill ([\#7949](https://github.com/matrix-org/matrix-react-sdk/pull/7949)). Fixes vector-im/element-web#21255.
+ * Autofocus correct composer after sending reaction ([\#7950](https://github.com/matrix-org/matrix-react-sdk/pull/7950)). Fixes vector-im/element-web#21273.
+ * Consider polls as message events for rendering redactions ([\#7944](https://github.com/matrix-org/matrix-react-sdk/pull/7944)). Fixes vector-im/element-web#21125.
+ * Prevent event tiles being shrunk/collapsed by flexbox ([\#7942](https://github.com/matrix-org/matrix-react-sdk/pull/7942)). Fixes vector-im/element-web#21269.
+ * Fix ExportDialog title on export cancellation ([\#7936](https://github.com/matrix-org/matrix-react-sdk/pull/7936)). Fixes vector-im/element-web#21260. Contributed by @luixxiul.
+ * Mandate use of js-sdk/src/matrix import over js-sdk/src ([\#7933](https://github.com/matrix-org/matrix-react-sdk/pull/7933)). Fixes vector-im/element-web#21253.
+ * Fix backspace not working in the invite dialog ([\#7931](https://github.com/matrix-org/matrix-react-sdk/pull/7931)). Fixes vector-im/element-web#21249. Contributed by @SimonBrandner.
+ * Fix right panel soft crashes due to missing room prop ([\#7923](https://github.com/matrix-org/matrix-react-sdk/pull/7923)). Fixes vector-im/element-web#21243.
+ * fix color of location share caret ([\#7917](https://github.com/matrix-org/matrix-react-sdk/pull/7917)).
+ * Wrap all EventTiles with a TileErrorBoundary and guard parsePermalink ([\#7916](https://github.com/matrix-org/matrix-react-sdk/pull/7916)). Fixes vector-im/element-web#21216.
+ * Fix changing space sometimes bouncing to the wrong space ([\#7910](https://github.com/matrix-org/matrix-react-sdk/pull/7910)). Fixes vector-im/element-web#20425.
+ * Ensure EventListSummary key does not change during backpagination ([\#7915](https://github.com/matrix-org/matrix-react-sdk/pull/7915)). Fixes vector-im/element-web#9192.
+ * Fix positioning of the thread context menu ([\#7918](https://github.com/matrix-org/matrix-react-sdk/pull/7918)). Fixes vector-im/element-web#21236.
+ * Inject sender into pinned messages ([\#7904](https://github.com/matrix-org/matrix-react-sdk/pull/7904)). Fixes vector-im/element-web#20314.
+ * Tweak info message padding in right panel timeline ([\#7901](https://github.com/matrix-org/matrix-react-sdk/pull/7901)). Fixes vector-im/element-web#21212.
+ * Fix another freeze on room switch ([\#7900](https://github.com/matrix-org/matrix-react-sdk/pull/7900)). Fixes vector-im/element-web#21127.
+ * Clean up error listener when location picker closes ([\#7902](https://github.com/matrix-org/matrix-react-sdk/pull/7902)). Fixes vector-im/element-web#21213.
+ * Fix edge case in context menu chevron positioning ([\#7899](https://github.com/matrix-org/matrix-react-sdk/pull/7899)).
+ * Fix composer format buttons on WebKit ([\#7898](https://github.com/matrix-org/matrix-react-sdk/pull/7898)). Fixes vector-im/element-web#20868.
+ * manage voicerecording state when deleting or sending a voice message ([\#7896](https://github.com/matrix-org/matrix-react-sdk/pull/7896)). Fixes vector-im/element-web#21151.
+ * Fix bug with useRoomHierarchy tight-looping loadMore on error ([\#7893](https://github.com/matrix-org/matrix-react-sdk/pull/7893)).
+ * Fix upload button & shortcut not working for narrow composer mode ([\#7894](https://github.com/matrix-org/matrix-react-sdk/pull/7894)). Fixes vector-im/element-web#21175 and vector-im/element-web#21142.
+ * Fix emoji insertion in thread composer going to the main composer ([\#7895](https://github.com/matrix-org/matrix-react-sdk/pull/7895)). Fixes vector-im/element-web#21202.
+ * Try harder to keep context menus inside the window ([\#7863](https://github.com/matrix-org/matrix-react-sdk/pull/7863)). Fixes vector-im/element-web#17527 and vector-im/element-web#18377.
+ * Fix edge case around event list summary layout ([\#7891](https://github.com/matrix-org/matrix-react-sdk/pull/7891)). Fixes vector-im/element-web#21180.
+ * Fix event list summary 1 hidden message pluralisation ([\#7890](https://github.com/matrix-org/matrix-react-sdk/pull/7890)). Fixes vector-im/element-web#21196.
+ * Fix vanishing recently viewed menu ([\#7887](https://github.com/matrix-org/matrix-react-sdk/pull/7887)). Fixes vector-im/element-web#20827.
+ * Fix freeze on room switch ([\#7884](https://github.com/matrix-org/matrix-react-sdk/pull/7884)). Fixes vector-im/element-web#21127.
+ * Check 'useSystemTheme' in quick settings theme switcher ([\#7809](https://github.com/matrix-org/matrix-react-sdk/pull/7809)). Fixes vector-im/element-web#21061.
+ * Fix 'my threads' filtering to include participated threads ([\#7882](https://github.com/matrix-org/matrix-react-sdk/pull/7882)). Fixes vector-im/element-web#20877.
+ * Remove log line to try to fix freeze on answering VoIP call ([\#7883](https://github.com/matrix-org/matrix-react-sdk/pull/7883)).
+ * Support social login & password on soft logout page ([\#7879](https://github.com/matrix-org/matrix-react-sdk/pull/7879)). Fixes vector-im/element-web#21099.
+ * Fix missing padding on server picker ([\#7864](https://github.com/matrix-org/matrix-react-sdk/pull/7864)).
+ * Throttle RoomState.members handlers ([\#7876](https://github.com/matrix-org/matrix-react-sdk/pull/7876)). Fixes vector-im/element-web#21127.
+ * Only show joined/invited in search dialog ([\#7875](https://github.com/matrix-org/matrix-react-sdk/pull/7875)). Fixes vector-im/element-web#21161.
+ * Don't pillify code blocks ([\#7861](https://github.com/matrix-org/matrix-react-sdk/pull/7861)). Fixes vector-im/element-web#20851 and vector-im/element-web#18687.
+ * Fix keyboard shortcut icons on macOS ([\#7869](https://github.com/matrix-org/matrix-react-sdk/pull/7869)).
+
Changes in [3.41.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.41.1) (2022-03-01)
=====================================================================================================
@@ -278,7 +528,7 @@ Changes in [3.39.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/
=====================================================================================================
## 🐛 Bug Fixes
- * Fix the sticker picker ([\#7692](https://github.com/matrix-org/matrix-react-sdk/pull/7692)). Fixes vector-im/element-web#20797.
+ * Fix the sticker picker ([\#7692](https://github.com/matrix-org/matrix-react-sdk/pull/7692)). Fixes vector-im/element-web#20797.
* Ensure UserInfo can be rendered without a room ([\#7687](https://github.com/matrix-org/matrix-react-sdk/pull/7687)). Fixes vector-im/element-web#20830.
* Fix publishing address wrongly demanding the alias be available ([\#7690](https://github.com/matrix-org/matrix-react-sdk/pull/7690)). Fixes vector-im/element-web#12013 and vector-im/element-web#20833.
@@ -1137,7 +1387,7 @@ Changes in [3.32.1](https://github.com/vector-im/element-desktop/releases/tag/v3
## 🐛 Bug Fixes
* Upgrade to matrix-js-sdk#14.0.1
-
+
Changes in [3.32.0](https://github.com/vector-im/element-desktop/releases/tag/v3.32.0) (2021-10-11)
===================================================================================================
@@ -2283,7 +2533,7 @@ related to file upload. When uploading a file, the local file preview can lead
to execution of scripts embedded in the uploaded file, but only after several
user interactions to open the preview in a separate tab. This only impacts the
local user while in the process of uploading. It cannot be exploited remotely
-or by other users. Thanks to [Muhammad Zaid Ghifari](https://github.com/MR-ZHEEV)
+or by other users. Thanks to [Muhammad Zaid Ghifari](https://github.com/MR-ZHEEV)
for responsibly disclosing this via Matrix's Security Disclosure Policy.
## All changes
@@ -6278,7 +6528,7 @@ Changes in [2.1.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/
=============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.1.0-rc.1...v2.1.0-rc.2)
- * Fix error in previous attempt to upgrade JS SDK
+ * Fix error in previous attempt to upgrade JS SDK
Changes in [2.1.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.1.0-rc.1) (2020-02-13)
=============================================================================================================
diff --git a/README.md b/README.md
index 9059c5b5b8f..4664887360a 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ tracks lots of state for its child components which it passes into them for visu
rendering via props.
Good separation between the components is maintained by adopting various best
-practices that anyone working with the SDK needs to be be aware of and uphold:
+practices that anyone working with the SDK needs to be aware of and uphold:
* Components are named with upper camel case (e.g. views/rooms/EventTile.js)
@@ -65,13 +65,9 @@ practices that anyone working with the SDK needs to be be aware of and uphold:
component is a view or a structure, and then a broad functional grouping
(e.g. 'rooms' here)
- * After creating a new component you must run `yarn reskindex` to regenerate
- the `component-index.js` for the SDK (used in future for skinning)
-
-
* The view's CSS file MUST have the same name (e.g. view/rooms/MessageTile.css).
CSS for matrix-react-sdk currently resides in
- https://github.com/vector-im/element-web/tree/master/src/skins/vector/css/matrix-react-sdk.
+ https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css.
* Per-view CSS is optional - it could choose to inherit all its styling from
the context of the rest of the app, although this is unusual for any but
@@ -158,9 +154,6 @@ cd matrix-react-sdk
git checkout develop
yarn link matrix-js-sdk
yarn install
-
-# Generate the `component-index.js` file.
-yarn reskindex
```
See the [help for `yarn link`](https://classic.yarnpkg.com/docs/cli/link) for
diff --git a/__mocks__/maplibre-gl.js b/__mocks__/maplibre-gl.js
index b1f114e8eff..599cacde13d 100644
--- a/__mocks__/maplibre-gl.js
+++ b/__mocks__/maplibre-gl.js
@@ -1,20 +1,30 @@
const EventEmitter = require("events");
-const { LngLat } = require('maplibre-gl');
+const { LngLat, NavigationControl, LngLatBounds } = require('maplibre-gl');
class MockMap extends EventEmitter {
addControl = jest.fn();
removeControl = jest.fn();
+ zoomIn = jest.fn();
+ zoomOut = jest.fn();
+ setCenter = jest.fn();
+ setStyle = jest.fn();
+ fitBounds = jest.fn();
}
-class MockGeolocateControl extends EventEmitter {
+const MockMapInstance = new MockMap();
+class MockGeolocateControl extends EventEmitter {
+ trigger = jest.fn();
}
-class MockMarker extends EventEmitter {
- setLngLat = jest.fn().mockReturnValue(this);
- addTo = jest.fn();
-}
+const MockGeolocateInstance = new MockGeolocateControl();
+const MockMarker = {}
+MockMarker.setLngLat = jest.fn().mockReturnValue(MockMarker);
+MockMarker.addTo = jest.fn().mockReturnValue(MockMarker);
+MockMarker.remove = jest.fn().mockReturnValue(MockMarker);
module.exports = {
- Map: MockMap,
- GeolocateControl: MockGeolocateControl,
- Marker: MockMarker,
+ Map: jest.fn().mockReturnValue(MockMapInstance),
+ GeolocateControl: jest.fn().mockReturnValue(MockGeolocateInstance),
+ Marker: jest.fn().mockReturnValue(MockMarker),
LngLat,
+ LngLatBounds,
+ NavigationControl,
};
diff --git a/babel.config.js b/babel.config.js
index f00e83652c1..ac94a29559e 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -13,7 +13,6 @@ module.exports = {
"@babel/preset-react",
],
"plugins": [
- ["@babel/plugin-proposal-decorators", {legacy: true}],
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-class-properties",
diff --git a/cypress.json b/cypress.json
new file mode 100644
index 00000000000..2c39bb411fe
--- /dev/null
+++ b/cypress.json
@@ -0,0 +1,5 @@
+{
+ "baseUrl": "http://localhost:8080",
+ "videoUploadOnPasses": false,
+ "projectId": "ppvnzg"
+}
diff --git a/cypress/integration/1-register/register.spec.ts b/cypress/integration/1-register/register.spec.ts
new file mode 100644
index 00000000000..f719da55477
--- /dev/null
+++ b/cypress/integration/1-register/register.spec.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 { SynapseInstance } from "../../plugins/synapsedocker/index";
+
+describe("Registration", () => {
+ let synapseId;
+ let synapsePort;
+
+ beforeEach(() => {
+ cy.task("synapseStart", "consent").then(result => {
+ synapseId = result.synapseId;
+ synapsePort = result.port;
+ });
+ cy.visit("/#/register");
+ });
+
+ afterEach(() => {
+ cy.task("synapseStop", synapseId);
+ });
+
+ it("registers an account and lands on the home screen", () => {
+ cy.get(".mx_ServerPicker_change", { timeout: 15000 }).click();
+ cy.get(".mx_ServerPickerDialog_otherHomeserver").type(`http://localhost:${synapsePort}`);
+ cy.get(".mx_ServerPickerDialog_continue").click();
+ // wait for the dialog to go away
+ cy.get('.mx_ServerPickerDialog').should('not.exist');
+ cy.get("#mx_RegistrationForm_username").type("alice");
+ cy.get("#mx_RegistrationForm_password").type("totally a great password");
+ cy.get("#mx_RegistrationForm_passwordConfirm").type("totally a great password");
+ cy.get(".mx_Login_submit").click();
+ cy.get(".mx_RegistrationEmailPromptDialog button.mx_Dialog_primary").click();
+ cy.get(".mx_InteractiveAuthEntryComponents_termsPolicy input").click();
+ cy.get(".mx_InteractiveAuthEntryComponents_termsSubmit").click();
+ cy.url().should('contain', '/#/home');
+ });
+});
diff --git a/test/minimal-sdk.js b/cypress/plugins/index.ts
similarity index 74%
rename from test/minimal-sdk.js
rename to cypress/plugins/index.ts
index f39893f78c2..db01ceceb4f 100644
--- a/test/minimal-sdk.js
+++ b/cypress/plugins/index.ts
@@ -14,16 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-/*
- * minimal-sdk.js
- *
- * Starts up the skin system with even less than `skinned-sdk`.
- */
-
-import * as sdk from "../src/index";
-
-const components = {};
+///
-sdk.loadSkin({ components });
+import { synapseDocker } from "./synapsedocker/index";
-export default sdk;
+export default function(on, config) {
+ synapseDocker(on, config);
+}
diff --git a/cypress/plugins/synapsedocker/index.ts b/cypress/plugins/synapsedocker/index.ts
new file mode 100644
index 00000000000..0f029e7b2ed
--- /dev/null
+++ b/cypress/plugins/synapsedocker/index.ts
@@ -0,0 +1,212 @@
+/*
+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 path from "path";
+import * as os from "os";
+import * as crypto from "crypto";
+import * as childProcess from "child_process";
+import * as fse from "fs-extra";
+
+// A cypress plugins to add command to start & stop synapses in
+// docker with preset templates.
+
+interface SynapseConfig {
+ configDir: string;
+ registrationSecret: string;
+}
+
+export interface SynapseInstance extends SynapseConfig {
+ synapseId: string;
+ port: number;
+}
+
+const synapses = new Map();
+
+function randB64Bytes(numBytes: number): string {
+ return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
+}
+
+async function cfgDirFromTemplate(template: string): Promise {
+ const templateDir = path.join(__dirname, "templates", template);
+
+ const stats = await fse.stat(templateDir);
+ if (!stats?.isDirectory) {
+ throw new Error(`No such template: ${template}`);
+ }
+ const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), 'react-sdk-synapsedocker-'));
+
+ // change permissions on the temp directory so the docker container can see its contents
+ await fse.chmod(tempDir, 0o777);
+
+ // copy the contents of the template dir, omitting homeserver.yaml as we'll template that
+ console.log(`Copy ${templateDir} -> ${tempDir}`);
+ await fse.copy(templateDir, tempDir, { filter: f => path.basename(f) !== 'homeserver.yaml' });
+
+ const registrationSecret = randB64Bytes(16);
+ const macaroonSecret = randB64Bytes(16);
+ const formSecret = randB64Bytes(16);
+
+ // now copy homeserver.yaml, applying sustitutions
+ console.log(`Gen ${path.join(templateDir, "homeserver.yaml")}`);
+ let hsYaml = await fse.readFile(path.join(templateDir, "homeserver.yaml"), "utf8");
+ hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
+ hsYaml = hsYaml.replace(/{{MACAROON_SECRET_KEY}}/g, macaroonSecret);
+ hsYaml = hsYaml.replace(/{{FORM_SECRET}}/g, formSecret);
+ await fse.writeFile(path.join(tempDir, "homeserver.yaml"), hsYaml);
+
+ // now generate a signing key (we could use synapse's config generation for
+ // this, or we could just do this...)
+ // NB. This assumes the homeserver.yaml specifies the key in this location
+ const signingKey = randB64Bytes(32);
+ console.log(`Gen ${path.join(templateDir, "localhost.signing.key")}`);
+ await fse.writeFile(path.join(tempDir, "localhost.signing.key"), `ed25519 x ${signingKey}`);
+
+ return {
+ configDir: tempDir,
+ registrationSecret,
+ };
+}
+
+// Start a synapse instance: the template must be the name of
+// one of the templates in the cypress/plugins/synapsedocker/templates
+// directory
+async function synapseStart(template: string): Promise {
+ const synCfg = await cfgDirFromTemplate(template);
+
+ console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
+
+ const containerName = `react-sdk-cypress-synapse-${crypto.randomBytes(4).toString("hex")}`;
+
+ const synapseId = await new Promise((resolve, reject) => {
+ childProcess.execFile('docker', [
+ "run",
+ "--name", containerName,
+ "-d",
+ "-v", `${synCfg.configDir}:/data`,
+ "-p", "8008/tcp",
+ "matrixdotorg/synapse:develop",
+ "run",
+ ], (err, stdout) => {
+ if (err) reject(err);
+ resolve(stdout.trim());
+ });
+ });
+
+ // Get the port that docker allocated: specifying only one
+ // port above leaves docker to just grab a free one, although
+ // in hindsight we need to put the port in public_baseurl in the
+ // config really, so this will probably need changing to use a fixed
+ // / configured port.
+ const port = await new Promise((resolve, reject) => {
+ childProcess.execFile('docker', [
+ "port", synapseId, "8008",
+ ], { encoding: 'utf8' }, (err, stdout) => {
+ if (err) reject(err);
+ resolve(Number(stdout.trim().split(":")[1]));
+ });
+ });
+
+ synapses.set(synapseId, Object.assign({
+ port,
+ synapseId,
+ }, synCfg));
+
+ console.log(`Started synapse with id ${synapseId} on port ${port}.`);
+ return synapses.get(synapseId);
+}
+
+async function synapseStop(id) {
+ const synCfg = synapses.get(id);
+
+ if (!synCfg) throw new Error("Unknown synapse ID");
+
+ try {
+ const synapseLogsPath = path.join("cypress", "synapselogs", id);
+ await fse.ensureDir(synapseLogsPath);
+
+ const stdoutFile = await fse.open(path.join(synapseLogsPath, "stdout.log"), "w");
+ const stderrFile = await fse.open(path.join(synapseLogsPath, "stderr.log"), "w");
+ await new Promise((resolve, reject) => {
+ childProcess.spawn('docker', [
+ "logs",
+ id,
+ ], {
+ stdio: ["ignore", stdoutFile, stderrFile],
+ }).once('close', resolve);
+ });
+ await fse.close(stdoutFile);
+ await fse.close(stderrFile);
+
+ await new Promise((resolve, reject) => {
+ childProcess.execFile('docker', [
+ "stop",
+ id,
+ ], err => {
+ if (err) reject(err);
+ resolve();
+ });
+ });
+ } finally {
+ await new Promise((resolve, reject) => {
+ childProcess.execFile('docker', [
+ "rm",
+ id,
+ ], err => {
+ if (err) reject(err);
+ resolve();
+ });
+ });
+ }
+
+ await fse.remove(synCfg.configDir);
+
+ synapses.delete(id);
+
+ console.log(`Stopped synapse id ${id}.`);
+ // cypres deliberately fails if you return 'undefined', so
+ // return null to signal all is well and we've handled the task.
+ return null;
+}
+
+/**
+ * @type {Cypress.PluginConfig}
+ */
+// eslint-disable-next-line no-unused-vars
+export function synapseDocker(on, config) {
+ on("task", {
+ synapseStart, synapseStop,
+ });
+
+ on("after:spec", async (spec) => {
+ // Cleans up any remaining synapse instances after a spec run
+ // This is on the theory that we should avoid re-using synapse
+ // instances between spec runs: they should be cheap enough to
+ // start that we can have a separate one for each spec run or even
+ // test. If we accidentally re-use synapses, we could inadvertantly
+ // make our tests depend on each other.
+ for (const synId of synapses.keys()) {
+ console.warn(`Cleaning up synapse ID ${synId} after ${spec.name}`);
+ await synapseStop(synId);
+ }
+ });
+
+ on("before:run", async () => {
+ // tidy up old synapse log files before each run
+ await fse.emptyDir(path.join("cypress", "synapselogs"));
+ });
+}
diff --git a/cypress/plugins/synapsedocker/templates/COPYME/README.md b/cypress/plugins/synapsedocker/templates/COPYME/README.md
new file mode 100644
index 00000000000..df1ed89e6e4
--- /dev/null
+++ b/cypress/plugins/synapsedocker/templates/COPYME/README.md
@@ -0,0 +1,3 @@
+# Meta-template for synapse templates
+
+To make another template, you can copy this directory
diff --git a/cypress/plugins/synapsedocker/templates/COPYME/homeserver.yaml b/cypress/plugins/synapsedocker/templates/COPYME/homeserver.yaml
new file mode 100644
index 00000000000..fab1bc1c451
--- /dev/null
+++ b/cypress/plugins/synapsedocker/templates/COPYME/homeserver.yaml
@@ -0,0 +1,72 @@
+server_name: "localhost"
+pid_file: /data/homeserver.pid
+# XXX: This won't actually be right: it lets docker allocate an ephemeral port,
+# so we have a chicken-and-egg problem
+public_baseurl: http://localhost:8008/
+# Listener is always port 8008 (configured in the container)
+listeners:
+ - port: 8008
+ tls: false
+ bind_addresses: ['::']
+ type: http
+ x_forwarded: true
+
+ resources:
+ - names: [client, federation, consent]
+ compress: false
+
+# An sqlite in-memory database is fast & automatically wipes each time
+database:
+ name: "sqlite3"
+ args:
+ database: ":memory:"
+
+# Needs to be configured to log to the console like a good docker process
+log_config: "/data/log.config"
+
+rc_messages_per_second: 10000
+rc_message_burst_count: 10000
+rc_registration:
+ per_second: 10000
+ burst_count: 10000
+
+rc_login:
+ address:
+ per_second: 10000
+ burst_count: 10000
+ account:
+ per_second: 10000
+ burst_count: 10000
+ failed_attempts:
+ per_second: 10000
+ burst_count: 10000
+
+media_store_path: "/data/media_store"
+uploads_path: "/data/uploads"
+enable_registration: true
+enable_registration_without_verification: true
+disable_msisdn_registration: false
+# These placeholders will be be replaced with values generated at start
+registration_shared_secret: "{{REGISTRATION_SECRET}}"
+report_stats: false
+macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
+form_secret: "{{FORM_SECRET}}"
+# Signing key must be here: it will be generated to this file
+signing_key_path: "/data/localhost.signing.key"
+email:
+ enable_notifs: false
+ smtp_host: "localhost"
+ smtp_port: 25
+ smtp_user: "exampleusername"
+ smtp_pass: "examplepassword"
+ require_transport_security: False
+ notif_from: "Your Friendly %(app)s homeserver "
+ app_name: Matrix
+ notif_template_html: notif_mail.html
+ notif_template_text: notif_mail.txt
+ notif_for_new_users: True
+ client_base_url: "http://localhost/element"
+
+trusted_key_servers:
+ - server_name: "matrix.org"
+suppress_key_server_warning: true
diff --git a/cypress/plugins/synapsedocker/templates/COPYME/log.config b/cypress/plugins/synapsedocker/templates/COPYME/log.config
new file mode 100644
index 00000000000..ac232762da3
--- /dev/null
+++ b/cypress/plugins/synapsedocker/templates/COPYME/log.config
@@ -0,0 +1,50 @@
+# Log configuration for Synapse.
+#
+# This is a YAML file containing a standard Python logging configuration
+# dictionary. See [1] for details on the valid settings.
+#
+# Synapse also supports structured logging for machine readable logs which can
+# be ingested by ELK stacks. See [2] for details.
+#
+# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
+# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
+
+version: 1
+
+formatters:
+ precise:
+ format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
+
+handlers:
+ # A handler that writes logs to stderr. Unused by default, but can be used
+ # instead of "buffer" and "file" in the logger handlers.
+ console:
+ class: logging.StreamHandler
+ formatter: precise
+
+loggers:
+ synapse.storage.SQL:
+ # beware: increasing this to DEBUG will make synapse log sensitive
+ # information such as access tokens.
+ level: INFO
+
+ twisted:
+ # We send the twisted logging directly to the file handler,
+ # to work around https://github.com/matrix-org/synapse/issues/3471
+ # when using "buffer" logger. Use "console" to log to stderr instead.
+ handlers: [console]
+ propagate: false
+
+root:
+ level: INFO
+
+ # Write logs to the `buffer` handler, which will buffer them together in memory,
+ # then write them to a file.
+ #
+ # Replace "buffer" with "console" to log to stderr instead. (Note that you'll
+ # also need to update the configuration for the `twisted` logger above, in
+ # this case.)
+ #
+ handlers: [console]
+
+disable_existing_loggers: false
diff --git a/cypress/plugins/synapsedocker/templates/consent/README.md b/cypress/plugins/synapsedocker/templates/consent/README.md
new file mode 100644
index 00000000000..713e55f9d51
--- /dev/null
+++ b/cypress/plugins/synapsedocker/templates/consent/README.md
@@ -0,0 +1 @@
+A synapse configured with user privacy consent enabled
diff --git a/cypress/plugins/synapsedocker/templates/consent/homeserver.yaml b/cypress/plugins/synapsedocker/templates/consent/homeserver.yaml
new file mode 100644
index 00000000000..e26133f6d11
--- /dev/null
+++ b/cypress/plugins/synapsedocker/templates/consent/homeserver.yaml
@@ -0,0 +1,84 @@
+server_name: "localhost"
+pid_file: /data/homeserver.pid
+public_baseurl: http://localhost:5005/
+listeners:
+ - port: 8008
+ tls: false
+ bind_addresses: ['::']
+ type: http
+ x_forwarded: true
+
+ resources:
+ - names: [client, federation, consent]
+ compress: false
+
+database:
+ name: "sqlite3"
+ args:
+ database: ":memory:"
+
+log_config: "/data/log.config"
+
+rc_messages_per_second: 10000
+rc_message_burst_count: 10000
+rc_registration:
+ per_second: 10000
+ burst_count: 10000
+
+rc_login:
+ address:
+ per_second: 10000
+ burst_count: 10000
+ account:
+ per_second: 10000
+ burst_count: 10000
+ failed_attempts:
+ per_second: 10000
+ burst_count: 10000
+
+media_store_path: "/data/media_store"
+uploads_path: "/data/uploads"
+enable_registration: true
+enable_registration_without_verification: true
+disable_msisdn_registration: false
+registration_shared_secret: "{{REGISTRATION_SECRET}}"
+report_stats: false
+macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
+form_secret: "{{FORM_SECRET}}"
+signing_key_path: "/data/localhost.signing.key"
+email:
+ enable_notifs: false
+ smtp_host: "localhost"
+ smtp_port: 25
+ smtp_user: "exampleusername"
+ smtp_pass: "examplepassword"
+ require_transport_security: False
+ notif_from: "Your Friendly %(app)s homeserver "
+ app_name: Matrix
+ notif_template_html: notif_mail.html
+ notif_template_text: notif_mail.txt
+ notif_for_new_users: True
+ client_base_url: "http://localhost/element"
+
+user_consent:
+ template_dir: /data/res/templates/privacy
+ version: 1.0
+ server_notice_content:
+ msgtype: m.text
+ body: >-
+ To continue using this homeserver you must review and agree to the
+ terms and conditions at %(consent_uri)s
+ send_server_notice_to_guests: True
+ block_events_error: >-
+ To continue using this homeserver you must review and agree to the
+ terms and conditions at %(consent_uri)s
+ require_at_registration: true
+
+server_notices:
+ system_mxid_localpart: notices
+ system_mxid_display_name: "Server Notices"
+ system_mxid_avatar_url: "mxc://localhost:5005/oumMVlgDnLYFaPVkExemNVVZ"
+ room_name: "Server Notices"
+trusted_key_servers:
+ - server_name: "matrix.org"
+suppress_key_server_warning: true
diff --git a/cypress/plugins/synapsedocker/templates/consent/log.config b/cypress/plugins/synapsedocker/templates/consent/log.config
new file mode 100644
index 00000000000..ac232762da3
--- /dev/null
+++ b/cypress/plugins/synapsedocker/templates/consent/log.config
@@ -0,0 +1,50 @@
+# Log configuration for Synapse.
+#
+# This is a YAML file containing a standard Python logging configuration
+# dictionary. See [1] for details on the valid settings.
+#
+# Synapse also supports structured logging for machine readable logs which can
+# be ingested by ELK stacks. See [2] for details.
+#
+# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
+# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
+
+version: 1
+
+formatters:
+ precise:
+ format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
+
+handlers:
+ # A handler that writes logs to stderr. Unused by default, but can be used
+ # instead of "buffer" and "file" in the logger handlers.
+ console:
+ class: logging.StreamHandler
+ formatter: precise
+
+loggers:
+ synapse.storage.SQL:
+ # beware: increasing this to DEBUG will make synapse log sensitive
+ # information such as access tokens.
+ level: INFO
+
+ twisted:
+ # We send the twisted logging directly to the file handler,
+ # to work around https://github.com/matrix-org/synapse/issues/3471
+ # when using "buffer" logger. Use "console" to log to stderr instead.
+ handlers: [console]
+ propagate: false
+
+root:
+ level: INFO
+
+ # Write logs to the `buffer` handler, which will buffer them together in memory,
+ # then write them to a file.
+ #
+ # Replace "buffer" with "console" to log to stderr instead. (Note that you'll
+ # also need to update the configuration for the `twisted` logger above, in
+ # this case.)
+ #
+ handlers: [console]
+
+disable_existing_loggers: false
diff --git a/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/1.0.html b/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/1.0.html
new file mode 100644
index 00000000000..d4959b4bcb3
--- /dev/null
+++ b/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/1.0.html
@@ -0,0 +1,23 @@
+
+
+
+ Test Privacy policy
+
+
+ {% if has_consented %}
+
+ Thank you, you've already accepted the license.
+
+ {% else %}
+
+ Please accept the license!
+
+
+ {% endif %}
+
+
\ No newline at end of file
diff --git a/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/success.html b/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/success.html
new file mode 100644
index 00000000000..abe27d87ca1
--- /dev/null
+++ b/cypress/plugins/synapsedocker/templates/consent/res/templates/privacy/en/success.html
@@ -0,0 +1,9 @@
+
+
+
+ Test Privacy policy
+
+
+
Danke schon
+
+
\ No newline at end of file
diff --git a/cypress/support/index.ts b/cypress/support/index.ts
new file mode 100644
index 00000000000..9901ef4cb80
--- /dev/null
+++ b/cypress/support/index.ts
@@ -0,0 +1,3 @@
+// Empty file to prevent cypress from recreating a helpful example
+// file on every run (their example file doesn't use semicolons and
+// so fails our lint rules).
diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json
new file mode 100644
index 00000000000..85239e1a2a7
--- /dev/null
+++ b/cypress/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "target": "es2016",
+ "lib": ["es2020", "dom"],
+ "types": ["cypress"],
+ "moduleResolution": "node"
+ },
+ "include": ["**/*.ts"]
+}
diff --git a/docs/features/keyboardShortcuts.md b/docs/features/keyboardShortcuts.md
index 71814023543..349535918b3 100644
--- a/docs/features/keyboardShortcuts.md
+++ b/docs/features/keyboardShortcuts.md
@@ -1,6 +1,6 @@
# Keyboard shortcuts
-## Using the `KeyBindingManger`
+## Using the `KeyBindingManager`
The `KeyBindingManager` (accessible using `getKeyBindingManager()`) is a class
with several methods that allow you to get a `KeyBindingAction` based on a
diff --git a/docs/settings.md b/docs/settings.md
index 379f3c5dcd4..dae6eb22b81 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -1,38 +1,38 @@
# Settings Reference
-This document serves as developer documentation for using "Granular Settings". Granular Settings allow users to specify
-different values for a setting at particular levels of interest. For example, a user may say that in a particular room
-they want URL previews off, but in all other rooms they want them enabled. The `SettingsStore` helps mask the complexity
+This document serves as developer documentation for using "Granular Settings". Granular Settings allow users to specify
+different values for a setting at particular levels of interest. For example, a user may say that in a particular room
+they want URL previews off, but in all other rooms they want them enabled. The `SettingsStore` helps mask the complexity
of dealing with the different levels and exposes easy to use getters and setters.
## Levels
-Granular Settings rely on a series of known levels in order to use the correct value for the scenario. These levels, in
+Granular Settings rely on a series of known levels in order to use the correct value for the scenario. These levels, in
order of priority, are:
* `device` - The current user's device
* `room-device` - The current user's device, but only when in a specific room
* `room-account` - The current user's account, but only when in a specific room
* `account` - The current user's account
* `room` - A specific room (setting for all members of the room)
-* `config` - Values are defined by the `settingDefaults` key (usually) in `config.json`
+* `config` - Values are defined by the `setting_defaults` key (usually) in `config.json`
* `default` - The hardcoded default for the settings
-Individual settings may control which levels are appropriate for them as part of the defaults. This is often to ensure
+Individual settings may control which levels are appropriate for them as part of the defaults. This is often to ensure
that room administrators cannot force account-only settings upon participants.
## Settings
-Settings are the different options a user may set or experience in the application. These are pre-defined in
+Settings are the different options a user may set or experience in the application. These are pre-defined in
`src/settings/Settings.tsx` under the `SETTINGS` constant, and match the `ISetting` interface as defined there.
-Settings that support the config level can be set in the config file under the `settingDefaults` key (note that some
+Settings that support the config level can be set in the config file under the `setting_defaults` key (note that some
settings, like the "theme" setting, are special cased in the config file):
-```json
+```json5
{
...
- "settingDefaults": {
+ "setting_defaults": {
"settingName": true
},
...
@@ -41,20 +41,20 @@ settings, like the "theme" setting, are special cased in the config file):
### Getting values for a setting
-After importing `SettingsStore`, simply make a call to `SettingsStore.getValue`. The `roomId` parameter should always
-be supplied where possible, even if the setting does not have a per-room level value. This is to ensure that the value
+After importing `SettingsStore`, simply make a call to `SettingsStore.getValue`. The `roomId` parameter should always
+be supplied where possible, even if the setting does not have a per-room level value. This is to ensure that the value
returned is best represented in the room, particularly if the setting ever gets a per-room level in the future.
-In settings pages it is often desired to have the value at a particular level instead of getting the calculated value.
-Call `SettingsStore.getValueAt` to get the value of a setting at a particular level, and optionally make it explicitly
-at that level. By default `getValueAt` will traverse the tree starting at the provided level; making it explicit means
-it will not go beyond the provided level. When using `getValueAt`, please be sure to use `SettingLevel` to represent the
+In settings pages it is often desired to have the value at a particular level instead of getting the calculated value.
+Call `SettingsStore.getValueAt` to get the value of a setting at a particular level, and optionally make it explicitly
+at that level. By default `getValueAt` will traverse the tree starting at the provided level; making it explicit means
+it will not go beyond the provided level. When using `getValueAt`, please be sure to use `SettingLevel` to represent the
target level.
### Setting values for a setting
-Values are defined at particular levels and should be done in a safe manner. There are two checks to perform to ensure a
-clean save: is the level supported and can the user actually set the value. In most cases, neither should be an issue
+Values are defined at particular levels and should be done in a safe manner. There are two checks to perform to ensure a
+clean save: is the level supported and can the user actually set the value. In most cases, neither should be an issue
although there are circumstances where this changes. An example of a safe call is:
```javascript
const isSupported = SettingsStore.isLevelSupported(SettingLevel.ROOM);
@@ -66,12 +66,12 @@ if (isSupported) {
}
```
-These checks may also be performed in different areas of the application to avoid the verbose example above. For
+These checks may also be performed in different areas of the application to avoid the verbose example above. For
instance, the component which allows changing the setting may be hidden conditionally on the above conditions.
##### `SettingsFlag` component
-Where possible, the `SettingsFlag` component should be used to set simple "flip-a-bit" (true/false) settings. The
+Where possible, the `SettingsFlag` component should be used to set simple "flip-a-bit" (true/false) settings. The
`SettingsFlag` also supports simple radio button options, such as the theme the user would like to use.
```html
{
@@ -163,27 +163,27 @@ SettingsStore.getValue(...); // this will return the value set in `setValue` abo
## Watching for changes
-Most use cases do not need to set up a watcher because they are able to react to changes as they are made, or the
-changes which are made are not significant enough for it to matter. Watchers are intended to be used in scenarios where
-it is important to react to changes made by other logged in devices. Typically, this would be done within the component
-itself, however the component should not be aware of the intricacies of setting inversion or remapping to particular
-data structures. Instead, a generic watcher interface is provided on `SettingsStore` to watch (and subsequently unwatch)
+Most use cases do not need to set up a watcher because they are able to react to changes as they are made, or the
+changes which are made are not significant enough for it to matter. Watchers are intended to be used in scenarios where
+it is important to react to changes made by other logged in devices. Typically, this would be done within the component
+itself, however the component should not be aware of the intricacies of setting inversion or remapping to particular
+data structures. Instead, a generic watcher interface is provided on `SettingsStore` to watch (and subsequently unwatch)
for changes in a setting.
An example of a watcher in action would be:
```javascript
class MyComponent extends React.Component {
-
+
settingWatcherRef = null;
-
+
componentWillMount() {
const callback = (settingName, roomId, level, newValAtLevel, newVal) => {
this.setState({color: newVal});
};
this.settingWatcherRef = SettingsStore.watchSetting("roomColor", "!example:matrix.org", callback);
}
-
+
componentWillUnmount() {
SettingsStore.unwatchSetting(this.settingWatcherRef);
}
@@ -193,29 +193,29 @@ class MyComponent extends React.Component {
# Maintainers Reference
-The granular settings system has a few complex parts to power it. This section is to document how the `SettingsStore` is
+The granular settings system has a few complex parts to power it. This section is to document how the `SettingsStore` is
supposed to work.
### General information
-The `SettingsStore` uses the hardcoded `LEVEL_ORDER` constant to ensure that it is using the correct override procedure.
-The array is checked from left to right, simulating the behaviour of overriding values from the higher levels. Each
+The `SettingsStore` uses the hardcoded `LEVEL_ORDER` constant to ensure that it is using the correct override procedure.
+The array is checked from left to right, simulating the behaviour of overriding values from the higher levels. Each
level should be defined in this array, including `default`.
-Handlers (`src/settings/handlers/SettingsHandler.ts`) represent a single level and are responsible for getting and
-setting values at that level. Handlers also provide additional information to the `SettingsStore` such as if the level
-is supported or if the current user may set values at the level. The `SettingsStore` will use the handler to enforce
-checks and manipulate settings. Handlers are also responsible for dealing with migration patterns or legacy settings for
-their level (for example, a setting being renamed or using a different key from other settings in the underlying store).
-Handlers are provided to the `SettingsStore` via the `LEVEL_HANDLERS` constant. `SettingsStore` will optimize lookups by
+Handlers (`src/settings/handlers/SettingsHandler.ts`) represent a single level and are responsible for getting and
+setting values at that level. Handlers also provide additional information to the `SettingsStore` such as if the level
+is supported or if the current user may set values at the level. The `SettingsStore` will use the handler to enforce
+checks and manipulate settings. Handlers are also responsible for dealing with migration patterns or legacy settings for
+their level (for example, a setting being renamed or using a different key from other settings in the underlying store).
+Handlers are provided to the `SettingsStore` via the `LEVEL_HANDLERS` constant. `SettingsStore` will optimize lookups by
only considering handlers that are supported on the platform.
-Local echo is achieved through `src/settings/handlers/LocalEchoWrapper.ts` which acts as a wrapper around a given
-handler. This is automatically applied to all defined `LEVEL_HANDLERS` and proxies the calls to the wrapped handler
-where possible. The echo is achieved by a simple object cache stored within the class itself. The cache is invalidated
+Local echo is achieved through `src/settings/handlers/LocalEchoWrapper.ts` which acts as a wrapper around a given
+handler. This is automatically applied to all defined `LEVEL_HANDLERS` and proxies the calls to the wrapped handler
+where possible. The echo is achieved by a simple object cache stored within the class itself. The cache is invalidated
immediately upon the proxied save call succeeding or failing.
-Controllers are notified of changes by the `SettingsStore`, and are given the opportunity to override values after the
+Controllers are notified of changes by the `SettingsStore`, and are given the opportunity to override values after the
`SettingsStore` has deemed the value calculated. Controllers are invoked as the last possible step in the code.
### Features
@@ -224,17 +224,17 @@ See above for feature reference.
### Watchers
-Watchers can appear complicated under the hood: there is a central `WatchManager` which handles the actual invocation
-of callbacks, and callbacks are managed by the SettingsStore by redirecting the caller's callback to a dedicated
-callback. This is done so that the caller can reuse the same function as their callback without worrying about whether
-or not it'll unsubscribe all watchers.
+Watchers can appear complicated under the hood: there is a central `WatchManager` which handles the actual invocation
+of callbacks, and callbacks are managed by the SettingsStore by redirecting the caller's callback to a dedicated
+callback. This is done so that the caller can reuse the same function as their callback without worrying about whether
+or not it'll unsubscribe all watchers.
-Setting changes are emitted into the default `WatchManager`, which calculates the new value for the setting. Ideally,
-we'd also try and suppress updates which don't have a consequence on this value, however there's not an easy way to do
-this. Instead, we just dispatch an update for all changes and leave it up to the consumer to deduplicate.
+Setting changes are emitted into the default `WatchManager`, which calculates the new value for the setting. Ideally,
+we'd also try and suppress updates which don't have a consequence on this value, however there's not an easy way to do
+this. Instead, we just dispatch an update for all changes and leave it up to the consumer to deduplicate.
-In practice, handlers which rely on remote changes (account data, room events, etc) will always attach a listener to the
-`MatrixClient`. They then watch for changes to events they care about and send off appropriate updates to the
-generalized `WatchManager` - a class specifically designed to deduplicate the logic of managing watchers. The handlers
-which are localized to the local client (device) generally just trigger the `WatchManager` when they manipulate the
+In practice, handlers which rely on remote changes (account data, room events, etc) will always attach a listener to the
+`MatrixClient`. They then watch for changes to events they care about and send off appropriate updates to the
+generalized `WatchManager` - a class specifically designed to deduplicate the logic of managing watchers. The handlers
+which are localized to the local client (device) generally just trigger the `WatchManager` when they manipulate the
setting themselves as there's nothing to really 'watch'.
diff --git a/docs/skinning.md b/docs/skinning.md
index 229bc783724..0186186c24a 100644
--- a/docs/skinning.md
+++ b/docs/skinning.md
@@ -1,71 +1,18 @@
# Skinning
-The react-sdk can be skinned to replace presentation components, CSS, or
-other relevant parts of the SDK. Typically consumers will replace entire
-components and get the ability for custom CSS as a result.
-
-This doc isn't exhaustive on how skinning works, though it should cover
-some of the more complicated parts such as component replacement.
-
-## Loading a skin
-
-1. Generate a `component-index.js` (preferably using the tools that the react-sdk
-exposes). This can typically be done with a npm script like `"reskindex -h src/header"`.
-2. In your app's entry point, add something like this code:
- ```javascript
- import {loadSkin} from "matrix-react-sdk";
- loadSkin(import("component-index").components);
- // The rest of your imports go under this.
- ```
-3. Import the remainder of the SDK and bootstrap your app.
-
-It is extremely important that you **do not** import anything else from the
-SDK prior to loading your skin as otherwise the skin might not work. Loading
-the skin should be one of the first things your app does, if not the very
-first thing.
-
-Additionally, **do not** provide `loadSkin` with the react-sdk components
-themselves otherwise the app might explode. The SDK is already aware of its
-components and doesn't need to be told.
-
-## Replacing components
-
-Components that replace the react-sdk ones MUST have a `replaces` static
-key on the component's class to describe which component it overrides. For
-example, if your `VectorAuthPage` component is meant to replace the react-sdk
-`AuthPage` component then you'd add `static replaces = 'views.auth.AuthPage';`
-to the `VectorAuthPage` class.
-
-Other than that, the skin just needs to be loaded normally as mentioned above.
-Consumers of the SDK likely will not be interested in the rest of this section.
-
-### SDK developer notes
-
-Components in the react-sdk MUST be decorated with the `@replaceableComponent`
-function. For components that can't use the decorator, they must use a
-variation that provides similar functionality. The decorator gives consumers
-an opportunity to load skinned components by abusing import ordering and
-behaviour.
-
-Decorators are executed at import time which is why we can abuse the import
-ordering behaviour: importing `loadSkin` doesn't trigger any components to
-be imported, allowing the consumer to specify a skin. When the consumer does
-import a component (for example, `MatrixChat`), it starts to pull in all the
-components via `import` statements. When the components get pulled in the
-decorator checks with the skinned components to see if it should be replacing
-the component being imported. The decorator then effectively replaces the
-components when needed by specifying the skinned component as an override for
-the SDK's component, which should in theory override critical functions like
-`render()` and lifecycle event handlers.
-
-The decorator also means that older usage of `getComponent()` is no longer
-required because components should be replaced by the decorator. Eventually
-the react-sdk should only have one usage of `getComponent()`: the decorator.
-
-The decorator assumes that if `getComponent()` returns null that there is
-no skinned version of the component and continues on using the SDK's component.
-In previous versions of the SDK, the function would throw an error instead
-because it also expected the skin to list the SDK's components as well, however
-that is no longer possible due to the above.
-
-In short, components should always be `import`ed.
+Skinning in the context of the react-sdk is component replacement rather than CSS. This means you can override (replace)
+any accessible component in the project to implement custom behaviour, look & feel, etc. Depending on your approach,
+overriding CSS classes to apply custom styling is also possible, though harder to do.
+
+At present, the react-sdk offers no stable interface for components - this means properties and state can and do change
+at any time without notice. Once we determine the react-sdk to be stable enough to use as a proper SDK, we will adjust
+this policy. In the meantime, skinning is done completely at your own risk.
+
+The approach you take is up to you - we suggest using a module replacement plugin, as found in
+[webpack](https://webpack.js.org/plugins/normal-module-replacement-plugin/), though you're free to use whichever build
+system works for you. The react-sdk does not have any particular functions to call to load skins, so simply replace or
+extend the components/stores/etc you're after and build. As a reminder though, this is done completely at your own risk
+as we cannot guarantee a stable interface at this time.
+
+Taking a look at [element-web](https://github.com/vector-im/element-web)'s approach to skinning may be worthwhile, as it
+overrides some relatively simple components.
diff --git a/docs/slate-formats.md b/docs/slate-formats.md
deleted file mode 100644
index 7bb2fc9c5ff..00000000000
--- a/docs/slate-formats.md
+++ /dev/null
@@ -1,88 +0,0 @@
-Guide to data types used by the Slate-based Rich Text Editor
-------------------------------------------------------------
-
-We always store the Slate editor state in its Value form.
-
-The schema for the Value is the same whether the editor is in MD or rich text mode, and is currently (rather arbitrarily)
-dictated by the schema expected by slate-md-serializer, simply because it was the only bit of the pipeline which
-has opinions on the schema. (slate-html-serializer lets you define how to serialize whatever schema you like).
-
-The BLOCK_TAGS and MARK_TAGS give the mapping from HTML tags to the schema's node types (for blocks, which describe
-block content like divs, and marks, which describe inline formatted sections like spans).
-
-We use as the parent tag for the message (XXX: although some tags are technically not allowed to be nested within p's)
-
-Various conversions are performed as content is moved between HTML, MD, and plaintext representations of HTML and MD.
-
-The primitives used are:
-
- * Markdown.js - models commonmark-formatted MD strings (as entered by the composer in MD mode)
- * toHtml() - renders them to HTML suitable for sending on the wire
- * isPlainText() - checks whether the parsed MD contains anything other than simple text.
- * toPlainText() - renders MD to plain text in order to remove backslashes. Only works if the MD is already plaintext (otherwise it just emits HTML)
-
- * slate-html-serializer
- * converts Values to HTML (serialising) using our schema rules
- * converts HTML to Values (deserialising) using our schema rules
-
- * slate-md-serializer
- * converts rich Values to MD strings (serialising) but using a non-commonmark generic MD dialect.
- * This should use commonmark, but we use the serializer here for expedience rather than writing a commonmark one.
-
- * slate-plain-serializer
- * converts Values to plain text strings (serialising them) by concatenating the strings together
- * converts Values from plain text strings (deserialiasing them).
- * Used to initialise the editor by deserializing "" into a Value. Apparently this is the idiomatic way to initialise a blank editor.
- * Used (as a bodge) to turn a rich text editor into a MD editor, when deserialising the converted MD string of the editor into a value
-
- * PlainWithPillsSerializer
- * A fork of slate-plain-serializer which is aware of Pills (hence the name) and Emoji.
- * It can be configured to output Pills as:
- * "plain": Pills are rendered via their 'completion' text - e.g. 'Matthew'; used for sending messages)
- * "md": Pills are rendered as MD, e.g. [Matthew](https://matrix.to/#/@matthew:matrix.org) )
- * "id": Pills are rendered as IDs, e.g. '@matthew:matrix.org' (used for authoring / commands)
- * Emoji nodes are converted to inline utf8 emoji.
-
-The actual conversion transitions are:
-
- * Quoting:
- * The message being quoted is taken as HTML
- * ...and deserialised into a Value
- * ...and then serialised into MD via slate-md-serializer if the editor is in MD mode
-
- * Roundtripping between MD and rich text editor mode
- * From MD to richtext (mdToRichEditorState):
- * Serialise the MD-format Value to a MD string (converting pills to MD) with PlainWithPillsSerializer in 'md' mode
- * Convert that MD string to HTML via Markdown.js
- * Deserialise that Value to HTML via slate-html-serializer
- * From richtext to MD (richToMdEditorState):
- * Serialise the richtext-format Value to a MD string with slate-md-serializer (XXX: this should use commonmark)
- * Deserialise that to a plain text value via slate-plain-serializer
-
- * Loading history in one format into an editor which is in the other format
- * Uses the same functions as for roundtripping
-
- * Scanning the editor for a slash command
- * If the editor is a single line node starting with /, then serialize it to a string with PlainWithPillsSerializer in 'id' mode
- So that pills get converted to IDs suitable for commands being passed around
-
- * Sending messages
- * In RT mode:
- * If there is rich content, serialize the RT-format Value to HTML body via slate-html-serializer
- * Serialize the RT-format Value to the plain text fallback via PlainWithPillsSerializer in 'plain' mode
- * In MD mode:
- * Serialize the MD-format Value into an MD string with PlainWithPillsSerializer in 'md' mode
- * Parse the string with Markdown.js
- * If it contains no formatting:
- * Send as plaintext (as taken from Markdown.toPlainText())
- * Otherwise
- * Send as HTML (as taken from Markdown.toHtml())
- * Serialize the RT-format Value to the plain text fallback via PlainWithPillsSerializer in 'plain' mode
-
- * Pasting HTML
- * Deserialize HTML to a RT Value via slate-html-serializer
- * In RT mode, insert it straight into the editor as a fragment
- * In MD mode, serialise it to an MD string via slate-md-serializer and then insert the string into the editor as a fragment.
-
-The various scenarios and transitions could be drawn into a pretty diagram if one felt the urge, but hopefully the above
-gives sufficient detail on how it's all meant to work.
\ No newline at end of file
diff --git a/package.json b/package.json
index 957982c474d..c62184810c5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "3.41.1",
+ "version": "3.43.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@@ -22,10 +22,7 @@
"README.md",
"package.json"
],
- "bin": {
- "reskindex": "scripts/reskindex.js"
- },
- "main": "./src/index.ts",
+ "main": "./lib/index.ts",
"matrix_src_main": "./src/index.ts",
"matrix_lib_main": "./lib/index.ts",
"matrix_lib_typings": "./lib/index.d.ts",
@@ -37,23 +34,23 @@
"i18n": "matrix-gen-i18n",
"prunei18n": "matrix-prune-i18n",
"diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && matrix-gen-i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
- "reskindex": "node scripts/reskindex.js -h header",
"make-component": "node scripts/make-react-component.js",
- "reskindex:watch": "node scripts/reskindex.js -h header -w",
"rethemendex": "res/css/rethemendex.sh",
"clean": "rimraf lib",
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
- "build:compile": "yarn reskindex && babel -d lib --verbose --extensions \".ts,.js,.tsx\" src",
+ "build:compile": "babel -d lib --verbose --extensions \".ts,.js,.tsx\" src",
"build:types": "tsc --emitDeclarationOnly --jsx react",
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all",
- "start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn start:build\" \"yarn reskindex:watch\"",
+ "start:all": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:build",
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
- "lint:js": "eslint --max-warnings 0 src test",
- "lint:js-fix": "eslint --fix src test",
- "lint:types": "tsc --noEmit --jsx react",
- "lint:style": "stylelint 'res/css/**/*.scss'",
+ "lint:js": "eslint --max-warnings 0 src test cypress",
+ "lint:js-fix": "eslint --fix src test cypress",
+ "lint:types": "tsc --noEmit --jsx react && tsc --noEmit -p cypress",
+ "lint:style": "stylelint \"res/css/**/*.scss\"",
"test": "jest",
+ "test:cypress": "cypress run",
+ "test:cypress:open": "cypress open",
"test:e2e": "./test/end-to-end-tests/run.sh --app-url http://localhost:8080",
"coverage": "yarn test --coverage"
},
@@ -64,7 +61,6 @@
"@types/geojson": "^7946.0.8",
"await-lock": "^2.1.0",
"blurhash": "^1.1.3",
- "browser-encrypt-attachment": "^0.3.0",
"browser-request": "^0.3.3",
"cheerio": "^1.0.0-rc.9",
"classnames": "^2.2.6",
@@ -72,8 +68,9 @@
"counterpart": "^0.18.6",
"diff-dom": "^4.2.2",
"diff-match-patch": "^1.0.5",
- "emojibase-data": "^6.2.0",
- "emojibase-regex": "^5.1.3",
+ "emojibase": "6.0.2",
+ "emojibase-data": "7.0.0",
+ "emojibase-regex": "6.0.0",
"escape-html": "^1.0.3",
"file-saver": "^2.0.5",
"filesize": "6.1.0",
@@ -91,7 +88,8 @@
"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#daad3faed54f0b1f1e026a7498b4653e4d01cd90",
+ "matrix-analytics-events": "github:matrix-org/matrix-analytics-events.git#4aef17b56798639906f26a8739043a3c5c5fde7e",
+ "matrix-encrypt-attachment": "^1.0.3",
"matrix-events-sdk": "^0.0.1-beta.7",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-widget-api": "^0.1.0-beta.18",
@@ -124,7 +122,6 @@
"@babel/eslint-plugin": "^7.12.10",
"@babel/parser": "^7.12.11",
"@babel/plugin-proposal-class-properties": "^7.12.1",
- "@babel/plugin-proposal-decorators": "^7.12.12",
"@babel/plugin-proposal-export-default-from": "^7.12.1",
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
@@ -137,15 +134,17 @@
"@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",
"@sentry/types": "^6.10.0",
- "@sinonjs/fake-timers": "^7.0.2",
+ "@sinonjs/fake-timers": "^9.1.2",
"@types/classnames": "^2.2.11",
"@types/commonmark": "^0.27.4",
"@types/counterpart": "^0.18.1",
"@types/css-font-loading-module": "^0.0.6",
"@types/diff-match-patch": "^1.0.32",
"@types/enzyme": "^3.10.9",
+ "@types/escape-html": "^1.0.1",
"@types/file-saver": "^2.0.3",
"@types/flux": "^3.1.9",
+ "@types/fs-extra": "^9.0.13",
"@types/jest": "^26.0.20",
"@types/lodash": "^4.14.168",
"@types/modernizr": "^3.5.3",
@@ -156,6 +155,7 @@
"@types/react": "17.0.14",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "17.0.9",
+ "@types/react-test-renderer": "^17.0.1",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1",
"@types/zxcvbn": "^4.4.0",
@@ -164,8 +164,9 @@
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
"allchange": "^1.0.6",
"babel-jest": "^26.6.3",
+ "blob-polyfill": "^6.0.20211015",
"chokidar": "^3.5.1",
- "concurrently": "^5.3.0",
+ "cypress": "^9.5.4",
"enzyme": "^3.11.0",
"enzyme-to-json": "^3.6.2",
"eslint": "8.9.0",
@@ -175,6 +176,7 @@
"eslint-plugin-matrix-org": "^0.4.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
+ "fs-extra": "^10.0.1",
"glob": "^7.1.6",
"jest": "^27.4.0",
"jest-canvas-mock": "^2.3.0",
@@ -231,7 +233,9 @@
"/src/**/*.{js,ts,tsx}"
],
"coverageReporters": [
- "text"
+ "text",
+ "json"
]
- }
+ },
+ "typings": "./lib/index.d.ts"
}
diff --git a/res/css/_common.scss b/res/css/_common.scss
index a4b470d0527..94ec5ea0115 100644
--- a/res/css/_common.scss
+++ b/res/css/_common.scss
@@ -254,7 +254,7 @@ legend {
}
// These are magic constants which are excluded from tinting, to let themes
-// (which only have CSS, unlike skins) tell the app what their non-tinted
+// (which only have CSS) tell the app what their non-tinted
// colourscheme is by inspecting the stylesheet DOM.
//
// They are not used for layout!!
@@ -294,7 +294,6 @@ legend {
background-color: $background;
color: $light-fg-color;
z-index: 4012;
- font-weight: 300;
font-size: $font-15px;
position: relative;
padding: 24px;
@@ -670,3 +669,54 @@ legend {
line-height: inherit;
cursor: pointer;
}
+
+@define-mixin CallButton {
+ box-sizing: border-box;
+ font-weight: 600;
+ height: $font-24px;
+ line-height: $font-24px;
+ margin-right: 0;
+
+ span {
+ display: flex;
+ align-items: center;
+
+ &::before {
+ content: '';
+ display: inline-block;
+ background-color: $button-fg-color;
+ mask-position: center;
+ mask-repeat: no-repeat;
+ margin-right: 8px;
+ }
+ }
+}
+
+@define-mixin ThreadsAmount {
+ $threadInfoLineHeight: calc(2 * $font-12px);
+
+ color: $secondary-content;
+ font-weight: $font-semi-bold;
+ line-height: $threadInfoLineHeight;
+ white-space: nowrap;
+ position: relative;
+ padding: 0 $spacing-12 0 $spacing-8;
+}
+
+@define-mixin ThreadInfoIcon {
+ content: "";
+ display: inline-block;
+ mask-image: url('$(res)/img/element-icons/thread-summary.svg');
+ mask-position: center;
+ height: 18px;
+ min-width: 18px;
+ background-color: $secondary-content !important;
+ 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 65ce8742b87..3f371478d12 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -4,28 +4,38 @@
@import "./_font-sizes.scss";
@import "./_font-weights.scss";
@import "./_spacing.scss";
+@import "./components/views/beacon/_BeaconListItem.scss";
+@import "./components/views/beacon/_BeaconStatus.scss";
+@import "./components/views/beacon/_BeaconViewDialog.scss";
+@import "./components/views/beacon/_DialogOwnBeaconStatus.scss";
+@import "./components/views/beacon/_DialogSidebar.scss";
+@import "./components/views/beacon/_LeftPanelLiveShareWarning.scss";
+@import "./components/views/beacon/_LiveTimeRemaining.scss";
+@import "./components/views/beacon/_OwnBeaconStatus.scss";
+@import "./components/views/beacon/_RoomLiveShareWarning.scss";
+@import "./components/views/beacon/_StyledLiveBeaconIcon.scss";
+@import "./components/views/location/_EnableLiveShare.scss";
+@import "./components/views/location/_LiveDurationDropdown.scss";
@import "./components/views/location/_LocationShareMenu.scss";
+@import "./components/views/location/_MapError.scss";
+@import "./components/views/location/_Marker.scss";
@import "./components/views/location/_ShareDialogButtons.scss";
@import "./components/views/location/_ShareType.scss";
+@import "./components/views/location/_ZoomButtons.scss";
+@import "./components/views/messages/_MBeaconBody.scss";
@import "./components/views/spaces/_QuickThemeSwitcher.scss";
@import "./structures/_AutoHideScrollbar.scss";
@import "./structures/_BackdropPanel.scss";
@import "./structures/_CompatibilityPage.scss";
@import "./structures/_ContextualMenu.scss";
-@import "./structures/_CreateRoom.scss";
-@import "./structures/_CustomRoomTagPanel.scss";
@import "./structures/_FileDropTarget.scss";
@import "./structures/_FilePanel.scss";
@import "./structures/_GenericErrorPage.scss";
-@import "./structures/_GroupFilterPanel.scss";
-@import "./structures/_GroupView.scss";
@import "./structures/_HeaderButtons.scss";
@import "./structures/_HomePage.scss";
@import "./structures/_LeftPanel.scss";
-@import "./structures/_LeftPanelWidget.scss";
@import "./structures/_MainSplit.scss";
@import "./structures/_MatrixChat.scss";
-@import "./structures/_MyGroups.scss";
@import "./structures/_NonUrgentToastContainer.scss";
@import "./structures/_NotificationPanel.scss";
@import "./structures/_QuickSettingsButton.scss";
@@ -43,6 +53,7 @@
@import "./structures/_ToastContainer.scss";
@import "./structures/_UploadBar.scss";
@import "./structures/_UserMenu.scss";
+@import "./structures/_VideoRoomView.scss";
@import "./structures/_ViewSource.scss";
@import "./structures/auth/_CompleteSecurity.scss";
@import "./structures/auth/_Login.scss";
@@ -71,31 +82,24 @@
@import "./views/context_menus/_CallContextMenu.scss";
@import "./views/context_menus/_IconizedContextMenu.scss";
@import "./views/context_menus/_MessageContextMenu.scss";
-@import "./views/context_menus/_TagTileContextMenu.scss";
@import "./views/dialogs/_AddExistingToSpaceDialog.scss";
-@import "./views/dialogs/_AddressPickerDialog.scss";
@import "./views/dialogs/_Analytics.scss";
@import "./views/dialogs/_AnalyticsLearnMoreDialog.scss";
@import "./views/dialogs/_BugReportDialog.scss";
+@import "./views/dialogs/_BulkRedactDialog.scss";
@import "./views/dialogs/_ChangelogDialog.scss";
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
-@import "./views/dialogs/_CommunityPrototypeInviteDialog.scss";
@import "./views/dialogs/_CompoundDialog.scss";
@import "./views/dialogs/_ConfirmSpaceUserActionDialog.scss";
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
-@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
-@import "./views/dialogs/_CreateGroupDialog.scss";
@import "./views/dialogs/_CreateRoomDialog.scss";
-@import "./views/dialogs/_CreateSpaceFromCommunityDialog.scss";
@import "./views/dialogs/_CreateSubspaceDialog.scss";
@import "./views/dialogs/_DeactivateAccountDialog.scss";
@import "./views/dialogs/_DevtoolsDialog.scss";
-@import "./views/dialogs/_EditCommunityPrototypeDialog.scss";
@import "./views/dialogs/_ExportDialog.scss";
@import "./views/dialogs/_FeedbackDialog.scss";
@import "./views/dialogs/_ForwardDialog.scss";
@import "./views/dialogs/_GenericFeatureFeedbackDialog.scss";
-@import "./views/dialogs/_GroupAddressPicker.scss";
@import "./views/dialogs/_HostSignupDialog.scss";
@import "./views/dialogs/_IncomingSasDialog.scss";
@import "./views/dialogs/_InviteDialog.scss";
@@ -181,13 +185,11 @@
@import "./views/elements/_TooltipButton.scss";
@import "./views/elements/_Validation.scss";
@import "./views/emojipicker/_EmojiPicker.scss";
-@import "./views/groups/_GroupPublicityToggle.scss";
-@import "./views/groups/_GroupRoomList.scss";
-@import "./views/groups/_GroupUserSettings.scss";
@import "./views/location/_LocationPicker.scss";
@import "./views/messages/_CallEvent.scss";
@import "./views/messages/_CreateEvent.scss";
@import "./views/messages/_DateSeparator.scss";
+@import "./views/messages/_DisambiguatedProfile.scss";
@import "./views/messages/_EventTileBubble.scss";
@import "./views/messages/_HiddenBody.scss";
@import "./views/messages/_JumpToDatePicker.scss";
@@ -210,7 +212,6 @@
@import "./views/messages/_ReactionsRowButton.scss";
@import "./views/messages/_RedactedBody.scss";
@import "./views/messages/_RoomAvatarEvent.scss";
-@import "./views/messages/_SenderProfile.scss";
@import "./views/messages/_TextualEvent.scss";
@import "./views/messages/_UnknownBody.scss";
@import "./views/messages/_ViewSourceEvent.scss";
@@ -248,6 +249,7 @@
@import "./views/rooms/_NotificationBadge.scss";
@import "./views/rooms/_PinnedEventTile.scss";
@import "./views/rooms/_PresenceLabel.scss";
+@import "./views/rooms/_ReadReceiptGroup.scss";
@import "./views/rooms/_RecentlyViewedButton.scss";
@import "./views/rooms/_ReplyPreview.scss";
@import "./views/rooms/_ReplyTile.scss";
@@ -262,6 +264,7 @@
@import "./views/rooms/_SearchBar.scss";
@import "./views/rooms/_SendMessageComposer.scss";
@import "./views/rooms/_Stickers.scss";
+@import "./views/rooms/_ThreadSummary.scss";
@import "./views/rooms/_TopUnreadMessagesBar.scss";
@import "./views/rooms/_VoiceRecordComposerTile.scss";
@import "./views/rooms/_WhoIsTypingTile.scss";
@@ -275,6 +278,7 @@
@import "./views/settings/_ImageSizePanel.scss";
@import "./views/settings/_IntegrationManager.scss";
@import "./views/settings/_JoinRuleSettings.scss";
+@import "./views/settings/_KeyboardShortcut.scss";
@import "./views/settings/_LayoutSwitcher.scss";
@import "./views/settings/_Notifications.scss";
@import "./views/settings/_PhoneNumbers.scss";
@@ -323,3 +327,4 @@
@import "./views/voip/_DialPadModal.scss";
@import "./views/voip/_PiPContainer.scss";
@import "./views/voip/_VideoFeed.scss";
+@import "./views/voip/_VideoLobby.scss";
diff --git a/res/css/components/views/beacon/_BeaconListItem.scss b/res/css/components/views/beacon/_BeaconListItem.scss
new file mode 100644
index 00000000000..60311a4466f
--- /dev/null
+++ b/res/css/components/views/beacon/_BeaconListItem.scss
@@ -0,0 +1,61 @@
+/*
+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_BeaconListItem {
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ padding: $spacing-12 0;
+
+ border-bottom: 1px solid $system;
+}
+
+.mx_BeaconListItem_avatarIcon {
+ flex: 0 0;
+ height: 32px;
+ width: 32px;
+}
+
+.mx_BeaconListItem_avatar {
+ flex: 0 0;
+ box-sizing: border-box;
+
+ margin-right: $spacing-8;
+ border: 2px solid $location-live-color;
+}
+
+.mx_BeaconListItem_info {
+ flex: 1 1 0;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+}
+
+.mx_BeaconListItem_status {
+ // override beacon status padding
+ padding: 0 !important;
+ margin-bottom: $spacing-8;
+
+ .mx_BeaconStatus_label {
+ font-weight: $font-semi-bold;
+ }
+}
+
+.mx_BeaconListItem_lastUpdated {
+ color: $tertiary-content;
+ font-size: $font-10px;
+}
diff --git a/res/css/components/views/beacon/_BeaconStatus.scss b/res/css/components/views/beacon/_BeaconStatus.scss
new file mode 100644
index 00000000000..4dd3d325475
--- /dev/null
+++ b/res/css/components/views/beacon/_BeaconStatus.scss
@@ -0,0 +1,65 @@
+/*
+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_BeaconStatus {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+
+ box-sizing: border-box;
+ padding: $spacing-8;
+
+ color: var(--color);
+ font-size: $font-12px;
+}
+
+.mx_BeaconStatus_Loading,
+.mx_BeaconStatus_Stopped {
+ --color: $tertiary-content;
+}
+
+.mx_BeaconStatus_Active,
+.mx_BeaconStatus_Error {
+ --color: $primary-content;
+}
+
+.mx_BeaconStatus_icon {
+ height: 32px;
+ width: 32px;
+
+ flex: 0 0 32px;
+ margin-right: $spacing-8;
+}
+
+.mx_BeaconStatus_description {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ line-height: $font-14px;
+
+ padding-right: $spacing-8;
+
+ // TODO handle text-overflow
+}
+
+.mx_BeaconStatus_expiryTime {
+ color: $secondary-content;
+}
+
+.mx_BeaconStatus_label {
+ margin-bottom: 2px;
+}
diff --git a/res/css/components/views/beacon/_BeaconViewDialog.scss b/res/css/components/views/beacon/_BeaconViewDialog.scss
new file mode 100644
index 00000000000..6ad1a2a6139
--- /dev/null
+++ b/res/css/components/views/beacon/_BeaconViewDialog.scss
@@ -0,0 +1,88 @@
+/*
+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_BeaconViewDialog_wrapper .mx_Dialog {
+ padding: 0px;
+
+ /* Unset contain and position to allow the close button
+ to appear outside the dialog */
+ contain: unset;
+ position: unset;
+}
+
+.mx_BeaconViewDialog {
+ /* subtract 0.5px to prevent single-pixel margin due to rounding */
+ width: calc(80vw - 0.5px);
+ height: calc(80vh - 0.5px);
+ overflow: hidden;
+
+ // sidebar is absolutely positioned inside
+ position: relative;
+
+ .mx_Dialog_header {
+ margin: 0px;
+ padding: 0px;
+ position: unset;
+
+ .mx_Dialog_title {
+ display: none;
+ }
+
+ .mx_Dialog_cancelButton {
+ z-index: 4010;
+ position: fixed;
+ right: 5vw;
+ top: 5vh;
+ width: 20px;
+ height: 20px;
+ background-color: $dialog-close-external-color;
+ }
+ }
+}
+
+.mx_BeaconViewDialog_map {
+ width: 80vw;
+ height: 80vh;
+ border-radius: 8px;
+}
+
+.mx_BeaconViewDialog_mapFallback {
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ background: url('$(res)/img/location/map.svg');
+ background-size: cover;
+}
+
+.mx_BeaconViewDialog_mapFallbackIcon {
+ width: 65px;
+ margin-bottom: $spacing-16;
+ color: $quaternary-content;
+}
+
+.mx_BeaconViewDialog_mapFallbackMessage {
+ color: $secondary-content;
+ margin-bottom: $spacing-16;
+}
+
+.mx_BeaconViewDialog_viewListButton {
+ position: absolute;
+ top: $spacing-24;
+ left: $spacing-24;
+}
diff --git a/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss b/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss
new file mode 100644
index 00000000000..791e276f050
--- /dev/null
+++ b/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss
@@ -0,0 +1,55 @@
+/*
+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_DialogOwnBeaconStatus {
+ position: absolute;
+ bottom: $spacing-32;
+ width: 300px;
+ margin-left: -150px;
+ left: 50%;
+
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: stretch;
+
+ background: $background;
+ border-radius: 8px;
+ box-shadow: 4px 4px 12px 0 $menu-box-shadow-color;
+
+ padding: 0 $spacing-12;
+}
+
+.mx_DialogOwnBeaconStatus_avatarIcon {
+ flex: 0 0;
+ height: 32px;
+ width: 32px;
+ margin: $spacing-8 0 $spacing-8 0;
+}
+
+.mx_DialogOwnBeaconStatus_avatar {
+ flex: 0 0;
+ box-sizing: border-box;
+
+ border: 2px solid $location-live-color;
+ margin: $spacing-8 0 $spacing-8 0;
+}
+
+.mx_DialogOwnBeaconStatus_status {
+ flex: 1 1;
+ padding-right: 0;
+}
diff --git a/res/css/components/views/beacon/_DialogSidebar.scss b/res/css/components/views/beacon/_DialogSidebar.scss
new file mode 100644
index 00000000000..1989b57c301
--- /dev/null
+++ b/res/css/components/views/beacon/_DialogSidebar.scss
@@ -0,0 +1,60 @@
+/*
+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_DialogSidebar {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 265px;
+
+ display: flex;
+ flex-direction: column;
+
+ box-sizing: border-box;
+ padding: $spacing-16;
+
+ 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;
+}
diff --git a/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss b/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss
new file mode 100644
index 00000000000..04645c965ed
--- /dev/null
+++ b/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss
@@ -0,0 +1,36 @@
+/*
+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_LeftPanelLiveShareWarning {
+ @mixin ButtonResetDefault;
+ width: 100%;
+ box-sizing: border-box;
+
+ padding: $spacing-4;
+ text-align: center;
+
+ background-color: $accent;
+ color: #fff;
+ font-size: $font-10px;
+
+ // panel backdrops overlay the whole sidepanel
+ // go above to get hover for title
+ z-index: 1;
+}
+
+.mx_LeftPanelLiveShareWarning__error {
+ background-color: $alert;
+}
diff --git a/res/css/views/groups/_GroupUserSettings.scss b/res/css/components/views/beacon/_LiveTimeRemaining.scss
similarity index 73%
rename from res/css/views/groups/_GroupUserSettings.scss
rename to res/css/components/views/beacon/_LiveTimeRemaining.scss
index b207aa29585..de13f7aab2e 100644
--- a/res/css/views/groups/_GroupUserSettings.scss
+++ b/res/css/components/views/beacon/_LiveTimeRemaining.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2017 New Vector Ltd
+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,9 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_GroupUserSettings_groupPublicity_scrollbox {
- height: 200px;
- border: 1px solid $primary-hairline-color;
- border-radius: 3px;
- overflow: hidden;
+.mx_LiveTimeRemaining {
+ color: $secondary-content;
+ font-size: $font-12px;
}
diff --git a/res/css/components/views/beacon/_OwnBeaconStatus.scss b/res/css/components/views/beacon/_OwnBeaconStatus.scss
new file mode 100644
index 00000000000..aa01b6269a4
--- /dev/null
+++ b/res/css/components/views/beacon/_OwnBeaconStatus.scss
@@ -0,0 +1,27 @@
+/*
+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_EventTile[data-layout="bubble"] .mx_OwnBeaconStatus_button {
+ // align to top to make room for timestamp
+ // in bubble view
+ align-self: start;
+}
+
+.mx_OwnBeaconStatus_destructiveButton {
+ // override button link_inline styles
+ color: $alert !important;
+ font-weight: $font-semi-bold !important;
+}
diff --git a/res/css/components/views/beacon/_RoomLiveShareWarning.scss b/res/css/components/views/beacon/_RoomLiveShareWarning.scss
new file mode 100644
index 00000000000..f82c7f4de40
--- /dev/null
+++ b/res/css/components/views/beacon/_RoomLiveShareWarning.scss
@@ -0,0 +1,58 @@
+/*
+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_RoomLiveShareWarning {
+ width: 100%;
+
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ box-sizing: border-box;
+ padding: $spacing-12 $spacing-16;
+
+ color: $primary-content;
+ background-color: $system;
+}
+
+.mx_RoomLiveShareWarning_icon {
+ height: 32px;
+ width: 32px;
+ margin-right: $spacing-8;
+}
+
+.mx_RoomLiveShareWarning_label {
+ flex: 1;
+ font-size: $font-15px;
+}
+
+.mx_RoomLiveShareWarning_spinner {
+ margin-right: $spacing-16;
+}
+
+.mx_RoomLiveShareWarning_closeButton {
+ @mixin ButtonResetDefault;
+ margin-left: $spacing-16;
+}
+
+.mx_RoomLiveShareWarning_stopButton {
+ margin-left: $spacing-16;
+}
+
+.mx_RoomLiveShareWarning_closeButtonIcon {
+ height: $font-18px;
+ padding: $spacing-4;
+}
diff --git a/res/css/components/views/beacon/_StyledLiveBeaconIcon.scss b/res/css/components/views/beacon/_StyledLiveBeaconIcon.scss
new file mode 100644
index 00000000000..9096c3c71f4
--- /dev/null
+++ b/res/css/components/views/beacon/_StyledLiveBeaconIcon.scss
@@ -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.
+*/
+
+.mx_StyledLiveBeaconIcon {
+ flex-grow: 0;
+ flex-shrink: 0;
+ box-sizing: border-box;
+ border-width: 2px;
+ border-style: solid;
+ border-radius: 50%;
+
+ background-color: $location-live-color;
+ border-color: $location-live-secondary-color;
+ padding: 2px;
+ // colors icon
+ color: white;
+}
+
+.mx_StyledLiveBeaconIcon.mx_StyledLiveBeaconIcon_error {
+ background-color: $alert;
+ border-color: $alert;
+}
+
+.mx_StyledLiveBeaconIcon.mx_StyledLiveBeaconIcon_idle {
+ background-color: $quaternary-content;
+ border-color: $quaternary-content;
+}
diff --git a/res/css/components/views/location/_EnableLiveShare.scss b/res/css/components/views/location/_EnableLiveShare.scss
new file mode 100644
index 00000000000..7c10cc1923f
--- /dev/null
+++ b/res/css/components/views/location/_EnableLiveShare.scss
@@ -0,0 +1,48 @@
+/*
+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_EnableLiveShare {
+ flex: 1 1 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: center;
+
+ padding: $spacing-32 $spacing-16;
+ text-align: center;
+ box-sizing: border-box;
+}
+
+.mx_EnableLiveShare_heading {
+ padding-top: $spacing-24;
+}
+
+.mx_EnableLiveShare_icon {
+ height: 58px;
+ width: 58px;
+}
+
+.mx_EnableLiveShare_description {
+ padding: 0 $spacing-24;
+ margin-bottom: $spacing-32;
+ line-height: $font-20px;
+}
+
+.mx_EnableLiveShare_button {
+ margin-top: $spacing-32;
+ height: 48px;
+ width: 100%;
+}
diff --git a/res/css/views/dialogs/_GroupAddressPicker.scss b/res/css/components/views/location/_LiveDurationDropdown.scss
similarity index 82%
rename from res/css/views/dialogs/_GroupAddressPicker.scss
rename to res/css/components/views/location/_LiveDurationDropdown.scss
index 5fa18931f0a..9b0e39a07c6 100644
--- a/res/css/views/dialogs/_GroupAddressPicker.scss
+++ b/res/css/components/views/location/_LiveDurationDropdown.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2017 New Vector Ltd
+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,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_GroupAddressPicker_checkboxContainer {
- margin-top: 10px;
- display: flex;
+.mx_LiveDurationDropdown {
+ margin-bottom: $spacing-16;
}
diff --git a/res/css/components/views/location/_MapError.scss b/res/css/components/views/location/_MapError.scss
new file mode 100644
index 00000000000..83d6316ec44
--- /dev/null
+++ b/res/css/components/views/location/_MapError.scss
@@ -0,0 +1,36 @@
+/*
+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_MapError {
+ padding: 100px $spacing-32 0;
+ text-align: center;
+
+ p {
+ margin: $spacing-16 0 $spacing-32;
+ }
+}
+
+.mx_MapError_heading {
+ padding-top: $spacing-24;
+}
+
+.mx_MapError_icon {
+ height: 58px;
+
+ path {
+ fill: $secondary-content;
+ }
+}
diff --git a/res/css/components/views/location/_Marker.scss b/res/css/components/views/location/_Marker.scss
new file mode 100644
index 00000000000..7a1baccf9f1
--- /dev/null
+++ b/res/css/components/views/location/_Marker.scss
@@ -0,0 +1,46 @@
+/*
+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_Marker_defaultColor {
+ color: $accent;
+}
+
+.mx_Marker_border {
+ width: 42px;
+ height: 42px;
+ border-radius: 50%;
+ filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
+ background-color: currentColor;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ // caret down
+ &::before {
+ content: '';
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 5px solid currentColor;
+ position: absolute;
+ bottom: -4px;
+ }
+}
+
+.mx_Marker_icon {
+ color: white;
+ height: 20px;
+}
diff --git a/res/css/components/views/location/_ShareType.scss b/res/css/components/views/location/_ShareType.scss
index ba21a7caa0d..458be106eb0 100644
--- a/res/css/components/views/location/_ShareType.scss
+++ b/res/css/components/views/location/_ShareType.scss
@@ -83,19 +83,10 @@ limitations under the License.
border-style: solid;
border-radius: 50%;
- &.Own {
- // color is set by user color class
- // generated from id
- border-color: currentColor;
- }
+ // Live is styled by StyledLiveBeaconIcon
- &.Live {
- background-color: $location-live-color;
- // 20% brightness $location-live-color
- border-color: #deddfd;
- padding: 2px;
- // colors icon
- color: white;
+ &.Own {
+ border-color: $accent;
}
&.Pin {
diff --git a/res/css/components/views/location/_ZoomButtons.scss b/res/css/components/views/location/_ZoomButtons.scss
new file mode 100644
index 00000000000..59d52477f97
--- /dev/null
+++ b/res/css/components/views/location/_ZoomButtons.scss
@@ -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.
+*/
+
+.mx_ZoomButtons {
+ 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;
+}
diff --git a/res/css/components/views/messages/_MBeaconBody.scss b/res/css/components/views/messages/_MBeaconBody.scss
new file mode 100644
index 00000000000..dc63d6676db
--- /dev/null
+++ b/res/css/components/views/messages/_MBeaconBody.scss
@@ -0,0 +1,65 @@
+/*
+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_MBeaconBody {
+ position: relative;
+ height: 220px;
+ width: 325px;
+
+ border-radius: $timeline-image-border-radius;
+ overflow: hidden;
+}
+
+.mx_MBeaconBody_map {
+ height: 100%;
+ width: 100%;
+ z-index: 0; // keeps the entire map under the message action bar
+
+ &:not(.mx_MBeaconBody_mapFallback) {
+ cursor: pointer;
+ }
+}
+
+.mx_MBeaconBody_mapFallback {
+ box-sizing: border-box;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ // pushes spinner/icon up
+ // to appear more centered with the footer
+ padding-bottom: 50px;
+
+ background: url('$(res)/img/location/map.svg');
+ background-size: cover;
+}
+
+.mx_MBeaconBody_mapFallbackIcon {
+ width: 65px;
+ color: $quaternary-content;
+}
+
+.mx_MBeaconBody_chin {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ background-color: $overlay-background;
+}
+
+.mx_EventTile[data-layout="bubble"] .mx_EventTile_line .mx_MBeaconBody {
+ max-width: 100%;
+ width: 450px;
+}
diff --git a/res/css/structures/_CustomRoomTagPanel.scss b/res/css/structures/_CustomRoomTagPanel.scss
deleted file mode 100644
index 627644f1020..00000000000
--- a/res/css/structures/_CustomRoomTagPanel.scss
+++ /dev/null
@@ -1,53 +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.
-*/
-
-// TODO: Update design for custom tags to match new designs
-
-.mx_CustomRoomTagPanel {
- background-color: $groupFilterPanel-bg-color;
- max-height: 40vh;
-}
-
-.mx_CustomRoomTagPanel_scroller {
- max-height: inherit;
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-.mx_CustomRoomTagPanel .mx_AccessibleButton {
- margin: 0 auto;
- width: 40px;
- padding: 10px 0 9px 0;
- position: relative;
-}
-
-.mx_CustomRoomTagPanel .mx_BaseAvatar_image {
- box-sizing: border-box;
- width: 40px;
- height: 40px;
-}
-
-.mx_CustomRoomTagPanel .mx_AccessibleButton.CustomRoomTagPanel_tileSelected::before {
- content: '';
- height: 56px;
- background-color: $accent-alt;
- width: 5px;
- position: absolute;
- left: -9px;
- border-radius: 0 3px 3px 0;
- top: 5px; // just feels right (see comment above about designs needing to be updated)
-}
diff --git a/res/css/structures/_FilePanel.scss b/res/css/structures/_FilePanel.scss
index c180a8a02dd..634ce9604cc 100644
--- a/res/css/structures/_FilePanel.scss
+++ b/res/css/structures/_FilePanel.scss
@@ -93,7 +93,7 @@ limitations under the License.
text-decoration: none;
}
-.mx_FilePanel .mx_EventTile .mx_SenderProfile {
+.mx_FilePanel .mx_EventTile .mx_DisambiguatedProfile {
flex: 1 1 auto;
line-height: initial;
padding: 0px;
diff --git a/res/css/structures/_GroupFilterPanel.scss b/res/css/structures/_GroupFilterPanel.scss
deleted file mode 100644
index bdbd9f76435..00000000000
--- a/res/css/structures/_GroupFilterPanel.scss
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
-Copyright 2017 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.
-*/
-
-$groupFilterPanelWidth: 56px; // only applies in this file, used for calculations
-
-.mx_GroupFilterPanelContainer {
- flex-grow: 0;
- flex-shrink: 0;
- width: $groupFilterPanelWidth;
- height: 100%;
-
- // Create another flexbox so the GroupFilterPanel fills the container
- display: flex;
- flex-direction: column;
-
- // GroupFilterPanel handles its own CSS
-}
-
-.mx_GroupFilterPanel {
- z-index: 1;
- background-color: $groupFilterPanel-bg-color;
- flex: 1;
- cursor: pointer;
- position: relative;
-
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: space-between;
- min-height: 0;
-}
-
-.mx_GroupFilterPanel_items_selected {
- cursor: pointer;
-}
-
-.mx_GroupFilterPanel .mx_GroupFilterPanel_divider {
- height: 0px;
- width: 90%;
- border: none;
- border-bottom: 1px solid $tertiary-content;
-}
-
-.mx_GroupFilterPanel .mx_GroupFilterPanel_scroller {
- flex-grow: 1;
- width: 100%;
-}
-
-.mx_GroupFilterPanel .mx_GroupFilterPanel_tagTileContainer {
- display: flex;
- flex-direction: column;
- align-items: center;
-
- padding-top: 6px;
-}
-.mx_GroupFilterPanel .mx_GroupFilterPanel_tagTileContainer > div {
- margin: 6px 0;
-}
-
-.mx_GroupFilterPanel .mx_TagTile {
- // opacity: 0.5;
- position: relative;
-}
-
-.mx_GroupFilterPanel .mx_TagTile.mx_TagTile_prototype {
- padding: 3px;
-}
-
-.mx_GroupFilterPanel .mx_TagTile:focus,
-.mx_GroupFilterPanel .mx_TagTile:hover,
-.mx_GroupFilterPanel .mx_TagTile.mx_TagTile_selected {
- // opacity: 1;
-}
-
-.mx_GroupFilterPanel .mx_TagTile.mx_TagTile_selected_prototype {
- background-color: $background;
- border-radius: 6px;
-}
-
-.mx_TagTile_selected_prototype {
- .mx_TagTile_homeIcon::before {
- background-color: $primary-content; // dark-on-light
- }
-}
-
-.mx_TagTile:not(.mx_TagTile_selected_prototype) .mx_TagTile_homeIcon {
- background-color: $roomheader-addroom-bg-color;
- border-radius: 48px;
-
- &::before {
- background-color: $roomheader-addroom-fg-color;
- }
-}
-
-.mx_TagTile_homeIcon {
- width: 32px;
- height: 32px;
- position: relative;
-
- &::before {
- mask-image: url('$(res)/img/element-icons/home.svg');
- mask-position: center;
- mask-repeat: no-repeat;
- mask-size: 21px;
- content: '';
- display: inline-block;
- width: 32px;
- height: 32px;
- position: absolute;
- top: calc(50% - 16px);
- left: calc(50% - 16px);
- }
-}
-
-.mx_GroupFilterPanel .mx_TagTile_plus {
- margin-bottom: 12px;
- height: 32px;
- width: 32px;
- border-radius: 20px;
- background-color: $roomheader-addroom-bg-color;
- position: relative;
- /* overwrite mx_RoleButton inline-block */
- display: block !important;
-
- &::before {
- background-color: $roomheader-addroom-fg-color;
- mask-image: url('$(res)/img/feather-customised/plus.svg');
- mask-position: center;
- mask-repeat: no-repeat;
- content: '';
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- }
-}
-
-.mx_GroupFilterPanel .mx_TagTile.mx_TagTile_selected::before {
- content: '';
- height: 100%;
- background-color: $accent;
- width: 4px;
- position: absolute;
- left: -12px;
- border-radius: 0 3px 3px 0;
-}
-
-.mx_GroupFilterPanel .mx_TagTile.mx_AccessibleButton:focus {
- filter: none;
-}
-
-.mx_TagTile_tooltip {
- position: relative;
- top: -30px;
- left: 5px;
-}
-
-.mx_TagTile_context_button {
- min-width: 15px;
- height: 15px;
- position: absolute;
- right: -5px;
- top: -8px;
- border-radius: 8px;
- background-color: $neutral-badge-color;
- color: #000;
- font-weight: 600;
- font-size: $font-10px;
- text-align: center;
- padding-top: 1px;
- padding-left: 4px;
- padding-right: 4px;
-}
-
-.mx_TagTile_avatar {
- position: relative;
-}
-
-.mx_TagTile_badge {
- position: absolute;
- right: -4px;
- top: -2px;
- border-radius: 8px;
- color: $accent-fg-color;
- font-weight: 600;
- font-size: $font-14px;
- padding: 0 5px;
- background-color: $muted-fg-color;
-}
-
-.mx_TagTile_badgeHighlight {
- background-color: $alert;
-}
diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss
deleted file mode 100644
index 9cbf632cdd0..00000000000
--- a/res/css/structures/_GroupView.scss
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
-Copyright 2017 Vector Creations 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_GroupView {
- display: flex;
- flex-direction: column;
- overflow: hidden;
- flex-grow: 1;
-}
-
-.mx_GroupView_error {
- margin: auto;
-}
-
-.mx_GroupView_header {
- min-height: 52px;
- align-items: center;
- display: flex;
- padding-bottom: 10px;
- padding-left: 19px;
-}
-
-.mx_GroupView_header_view {
- border-bottom: 1px solid $primary-hairline-color;
- padding-bottom: 0px;
- padding-right: 8px;
-}
-
-.mx_GroupView_header_avatar, .mx_GroupView_header_info {
- display: table-cell;
- vertical-align: middle;
-}
-
-.mx_GroupHeader_button {
- position: relative;
- margin-left: 5px;
- margin-right: 5px;
- cursor: pointer;
- height: 20px;
- width: 20px;
-
- &::before {
- content: '';
- position: absolute;
- height: 20px;
- width: 20px;
- background-color: $header-panel-text-primary-color;
- mask-repeat: no-repeat;
- mask-size: contain;
- }
-}
-
-.mx_GroupHeader_editButton::before {
- mask-image: url('$(res)/img/element-icons/settings.svg');
-}
-
-.mx_GroupHeader_shareButton::before {
- mask-image: url('$(res)/img/element-icons/room/share.svg');
-}
-
-.mx_GroupView_hostingSignup img {
- margin-left: 5px;
-}
-
-.mx_GroupView_editable {
- border-bottom: 1px solid $strong-input-border-color !important;
- min-width: 150px;
- cursor: text;
-}
-
-.mx_GroupView_editable:focus {
- border-bottom: 1px solid $accent !important;
- outline: none;
- box-shadow: none;
-}
-
-.mx_GroupView_header_isUserMember .mx_GroupView_header_name:hover div:not(.mx_GroupView_editable) {
- color: $accent;
- cursor: pointer;
-}
-
-.mx_GroupView_avatarPicker {
- position: relative;
-}
-
-.mx_GroupView_avatarPicker_edit {
- position: absolute;
- top: 50px;
- left: 15px;
-}
-
-.mx_GroupView_avatarPicker .mx_Spinner {
- width: 48px;
- height: 48px !important;
-}
-
-.mx_GroupView_header_leftCol {
- flex: 1;
-
- overflow: hidden;
-}
-
-.mx_GroupView_header_rightCol {
- display: flex;
- align-items: center;
-}
-
-.mx_GroupView_textButton {
- display: inline-block;
-}
-
-.mx_GroupView_header_groupid {
- font-weight: normal;
- font-size: initial;
- padding-left: 10px;
-}
-
-.mx_GroupView_header_name {
- vertical-align: middle;
- width: 100%;
- height: 31px;
- overflow: hidden;
- color: $primary-content;
- font-weight: bold;
- font-size: $font-22px;
- padding-left: 19px;
- padding-right: 16px;
- /* why isn't text-overflow working? */
- text-overflow: ellipsis;
- border-bottom: 1px solid transparent;
-}
-
-.mx_GroupView_header_shortDesc {
- vertical-align: bottom;
- float: left;
- max-height: 42px;
- color: $settings-grey-fg-color;
- font-weight: 300;
- font-size: $font-13px;
- padding-left: 19px;
- margin-right: 16px;
- overflow: hidden;
- text-overflow: ellipsis;
- border-bottom: 1px solid transparent;
-}
-
-.mx_GroupView_avatarPicker_label {
- cursor: pointer;
-}
-
-.mx_GroupView_cancelButton {
- padding-left: 8px;
-}
-
-.mx_GroupView_cancelButton img {
- position: relative;
- top: 5px;
-}
-
-.mx_GroupView input[type='radio'] {
- margin: 10px 10px 0px 10px;
-}
-
-.mx_GroupView_label_text {
- display: inline-block;
- max-width: 80%;
- vertical-align: 0.1em;
- line-height: 2em;
-}
-
-.mx_GroupView_body {
- flex-grow: 1;
- margin: 0 24px;
-}
-
-.mx_GroupView_rooms {
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- min-height: 200px;
- user-select: none;
-}
-
-.mx_GroupView h3 {
- text-transform: uppercase;
- color: $h3-color;
- font-weight: 600;
- font-size: $font-13px;
- margin-bottom: 10px;
-}
-
-.mx_GroupView_rooms_header .mx_AccessibleButton {
- padding-left: 14px;
- margin-bottom: 14px;
- height: 24px;
-}
-
-.mx_GroupView_group {
- border-top: 1px solid $primary-hairline-color;
-}
-
-.mx_GroupView_group_disabled {
- opacity: 0.3;
- pointer-events: none;
-}
-
-.mx_GroupView_rooms_header_addRow_button {
- display: inline-block;
-}
-
-.mx_GroupView_rooms_header_addRow_button object {
- pointer-events: none;
-}
-
-.mx_GroupView_rooms_header_addRow_label {
- display: inline-block;
- vertical-align: top;
- line-height: $font-24px;
- padding-left: 28px;
- color: $accent;
-}
-
-.mx_GroupView_rooms .mx_RoomDetailList {
- flex-grow: 1;
- border-top: 1px solid $primary-hairline-color;
- padding-top: 10px;
- word-break: break-word;
-}
-
-.mx_GroupView .mx_RoomView_messageListWrapper {
- justify-content: flex-start;
-}
-
-.mx_GroupView_membershipSection {
- color: $info-plinth-fg-color;
- margin-top: 10px;
-}
-
-.mx_GroupView_membershipSubSection {
- justify-content: space-between;
- display: flex;
- padding-bottom: 8px;
-}
-
-.mx_GroupView_membershipSubSection .mx_Spinner {
- justify-content: flex-end;
-}
-
-.mx_GroupView_membershipSection_description {
- /* To match textButton */
- line-height: $font-34px;
-}
-
-.mx_GroupView_membershipSection_description .mx_BaseAvatar {
- margin-right: 10px;
-}
-
-.mx_GroupView_membershipSection .mx_GroupView_textButton {
- margin-right: 0px;
- margin-top: 0px;
- margin-left: 8px;
-}
-
-.mx_GroupView_memberSettings_toggle label {
- cursor: pointer;
- user-select: none;
-}
-
-.mx_GroupView_memberSettings input {
- margin-right: 6px;
-}
-
-.mx_GroupView_featuredThings {
- margin-top: 20px;
-}
-
-.mx_GroupView_featuredThings_header {
- font-weight: bold;
- font-size: 120%;
- margin-bottom: 20px;
-}
-
-.mx_GroupView_featuredThings_category {
- font-weight: bold;
- font-size: 110%;
- margin-top: 10px;
-}
-
-.mx_GroupView_featuredThings_container {
- display: flex;
-}
-
-.mx_GroupView_featuredThings_addButton,
-.mx_GroupView_featuredThing {
- display: table-cell;
- text-align: center;
-
- width: 100px;
- margin: 0px 20px;
-}
-
-.mx_GroupView_featuredThing {
- position: relative;
-}
-
-.mx_GroupView_featuredThing .mx_GroupView_featuredThing_deleteButton {
- position: absolute;
- top: -7px;
- right: 11px;
- opacity: 0.4;
-}
-
-.mx_GroupView_featuredThing .mx_BaseAvatar {
- /* To prevent misalignment with img (in addButton) */
- vertical-align: initial;
-}
-
-.mx_GroupView_featuredThings_addButton object {
- pointer-events: none;
-}
-
-.mx_GroupView_featuredThing_name {
- word-wrap: break-word;
-}
-
-.mx_GroupView_uploadInput {
- display: none;
-}
-
-.mx_GroupView_body .mx_AutoHideScrollbar > * {
- margin: 11px 50px 50px 68px;
-}
-
-.mx_GroupView_groupDesc textarea {
- width: 100%;
- max-width: 100%;
- height: 150px;
-}
-
-.mx_GroupView_groupDesc_placeholder,
-.mx_GroupView_changeDelayWarning {
- background-color: $info-plinth-bg-color;
- color: $info-plinth-fg-color;
- border-radius: 10px;
- text-align: center;
-
- margin: 20px 0px;
-}
-
-.mx_GroupView_groupDesc_placeholder {
- padding: 100px 20px;
- cursor: pointer;
-}
-
-.mx_GroupView_changeDelayWarning {
- padding: 40px 20px;
-}
-
-.mx_GroupView_spaceUpgradePrompt {
- padding: 16px 50px;
- background-color: $header-panel-bg-color;
- border-radius: 8px;
- max-width: 632px;
- font-size: $font-15px;
- line-height: $font-24px;
- margin-top: 24px;
- position: relative;
-
- > h2 {
- font-size: inherit;
- font-weight: $font-semi-bold;
- }
-
- > p, h2 {
- margin: 0;
- }
-
- &::before {
- content: "";
- position: absolute;
- height: $font-24px;
- width: 20px;
- left: 18px;
- mask-repeat: no-repeat;
- mask-position: center;
- mask-size: contain;
- mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
- background-color: $secondary-content;
- }
-
- .mx_AccessibleButton_kind_link {
- padding: 0;
- }
-
- .mx_GroupView_spaceUpgradePrompt_close {
- width: 16px;
- height: 16px;
- border-radius: 8px;
- background-color: $input-darker-bg-color;
- position: absolute;
- top: 16px;
- right: 16px;
-
- &::before {
- content: "";
- position: absolute;
- width: inherit;
- height: inherit;
- mask-repeat: no-repeat;
- mask-position: center;
- mask-size: 8px;
- mask-image: url('$(res)/img/image-view/close.svg');
- background-color: $secondary-content;
- }
- }
-}
-
-.mx_GroupView .mx_MemberInfo .mx_AutoHideScrollbar > :not(.mx_MemberInfo_avatar) {
- padding-left: 16px;
- padding-right: 16px;
-}
diff --git a/res/css/structures/_HomePage.scss b/res/css/structures/_HomePage.scss
index 77da167099c..a85bb0c42b6 100644
--- a/res/css/structures/_HomePage.scss
+++ b/res/css/structures/_HomePage.scss
@@ -95,11 +95,13 @@ limitations under the License.
&.mx_HomePage_button_sendDm::before {
mask-image: url('$(res)/img/element-icons/feedback.svg');
}
+
&.mx_HomePage_button_explore::before {
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
}
+
&.mx_HomePage_button_createGroup::before {
- mask-image: url('$(res)/img/element-icons/community-members.svg');
+ mask-image: url('$(res)/img/element-icons/group-members.svg');
}
}
}
diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss
index 1113a5d9efd..18ef247b5f0 100644
--- a/res/css/structures/_LeftPanel.scss
+++ b/res/css/structures/_LeftPanel.scss
@@ -23,13 +23,21 @@ $roomListCollapsedWidth: 68px;
}
}
-.mx_LeftPanel_wrapper {
+.mx_LeftPanel_outerWrapper {
display: flex;
+ flex-direction: column;
max-width: 50%;
position: relative;
// Contain the amount of layers rendered by constraining what actually needs re-layering via css
contain: layout paint;
+}
+
+.mx_LeftPanel_wrapper {
+ display: flex;
+ flex-direction: row;
+ flex: 1;
+ height: 100%; // ensure space panel is still scrollable with an outer wrapper
.mx_LeftPanel_wrapper--user {
background-color: $roomlist-bg-color;
@@ -45,9 +53,8 @@ $roomListCollapsedWidth: 68px;
.mx_LeftPanel {
background-color: $roomlist-bg-color;
- // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
- // Create a row-based flexbox for the GroupFilterPanel and the room list
+ // Create a row-based flexbox for the space panel and the room list
display: flex;
contain: content;
position: relative;
@@ -111,11 +118,6 @@ $roomListCollapsedWidth: 68px;
display: flex;
align-items: center;
- .mx_UserMenu {
- // mini-mode for when Space Panel is disabled
- margin-right: 12px;
- }
-
& + .mx_RoomListHeader {
margin-top: 12px;
}
diff --git a/res/css/structures/_LeftPanelWidget.scss b/res/css/structures/_LeftPanelWidget.scss
deleted file mode 100644
index 0a01a19b90a..00000000000
--- a/res/css/structures/_LeftPanelWidget.scss
+++ /dev/null
@@ -1,145 +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_LeftPanelWidget {
- // largely based on RoomSublist
- margin-left: 8px;
- margin-bottom: 4px;
-
- .mx_LeftPanelWidget_headerContainer {
- display: flex;
- align-items: center;
-
- height: 24px;
- color: $tertiary-content;
- margin-top: 4px;
-
- .mx_LeftPanelWidget_stickable {
- flex: 1;
- max-width: 100%;
-
- display: flex;
- align-items: center;
- }
-
- .mx_LeftPanelWidget_headerText {
- flex: 1;
- max-width: calc(100% - 16px);
- line-height: $font-16px;
- font-size: $font-13px;
- font-weight: 600;
-
- // Ellipsize any text overflow
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
-
- .mx_LeftPanelWidget_collapseBtn {
- display: inline-block;
- position: relative;
- width: 14px;
- height: 14px;
- margin-right: 6px;
-
- &::before {
- content: '';
- width: 18px;
- height: 18px;
- position: absolute;
- mask-position: center;
- mask-size: contain;
- mask-repeat: no-repeat;
- background-color: $tertiary-content;
- mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
- }
-
- &.mx_LeftPanelWidget_collapseBtn_collapsed::before {
- transform: rotate(-90deg);
- }
- }
- }
- }
-
- .mx_LeftPanelWidget_resizeBox {
- position: relative;
-
- display: flex;
- flex-direction: column;
- overflow: visible; // let the resize handle out
- }
-
- .mx_AppTileFullWidth {
- flex: 1 0 0;
- overflow: hidden;
- // need this to be flex otherwise the overflow hidden from above
- // sometimes vertically centers the clipped list ... no idea why it would do this
- // as the box model should be top aligned. Happens in both FF and Chromium
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
-
- mask-image: linear-gradient(0deg, transparent, black 4px);
- }
-
- .mx_LeftPanelWidget_resizerHandle {
- cursor: ns-resize;
- border-radius: 3px;
-
- // Override styles from library
- width: unset !important;
- height: 4px !important;
-
- position: absolute;
- top: -24px !important; // override from library - puts it in the margin-top of the headerContainer
-
- // Together, these make the bar 64px wide
- // These are also overridden from the library
- left: calc(50% - 32px) !important;
- right: calc(50% - 32px) !important;
- }
-
- &:hover .mx_LeftPanelWidget_resizerHandle {
- opacity: 0.8;
- background-color: $primary-content;
- }
-
- .mx_LeftPanelWidget_maximizeButton {
- margin-left: 8px;
- margin-right: 7px;
- position: relative;
- width: 24px;
- height: 24px;
- border-radius: 32px;
-
- &::before {
- content: '';
- width: 16px;
- height: 16px;
- position: absolute;
- top: 4px;
- left: 4px;
- mask-position: center;
- mask-size: contain;
- mask-repeat: no-repeat;
- mask-image: url("$(res)/img/element-icons/maximise-expand.svg");
- background: $muted-fg-color;
- }
- }
-}
-
-.mx_LeftPanelWidget_maximizeButtonTooltip {
- margin-top: -3px;
-}
diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss
index a95bfa9eb9f..0523e677616 100644
--- a/res/css/structures/_MatrixChat.scss
+++ b/res/css/structures/_MatrixChat.scss
@@ -62,8 +62,8 @@ limitations under the License.
transform: translateX(-50%);
}
-/* not the left panel, and not the resize handle, so the roomview/groupview/... */
-.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_SpacePanel):not(.mx_ResizeHandle):not(.mx_LeftPanel_wrapper) {
+/* not the left panel, and not the resize handle, so the roomview and friends */
+.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_SpacePanel):not(.mx_ResizeHandle):not(.mx_LeftPanel_outerWrapper) {
background-color: $background;
flex: 1 1 0;
diff --git a/res/css/structures/_MyGroups.scss b/res/css/structures/_MyGroups.scss
deleted file mode 100644
index 9c0062b72d3..00000000000
--- a/res/css/structures/_MyGroups.scss
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
-Copyright 2017 Vector Creations 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_MyGroups {
- display: flex;
- flex-direction: column;
-
- .mx_BetaCard {
- margin: 0 72px;
- max-width: 760px;
- }
-}
-
-.mx_MyGroups .mx_RoomHeader_simpleHeader {
- margin-left: 0px;
-}
-
-.mx_MyGroups_header {
- /* Keep mid-point of create button aligned with icon in page header */
- margin-left: 2px;
- display: flex;
- flex-wrap: wrap;
-}
-
-.mx_MyGroups > :not(.mx_RoomHeader):not(.mx_BetaCard) {
- max-width: 960px;
- margin: 40px;
-}
-
-.mx_MyGroups_headerCard {
- flex: 1 0 50%;
- margin-bottom: 30px;
- min-width: 400px;
- display: flex;
- align-items: center;
-}
-
-.mx_MyGroups_headerCard .mx_MyGroups_headerCard_button {
- flex: 0 0 auto;
- margin-right: 13px;
- height: 40px;
- width: 40px;
- border-radius: 20px;
- background-color: $roomheader-addroom-bg-color;
- position: relative;
-
- &::before {
- background-color: $roomheader-addroom-fg-color;
- mask: url('$(res)/img/icons-create-room.svg');
- mask-repeat: no-repeat;
- mask-position: center;
- mask-size: 80%;
- content: '';
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- }
-}
-
-.mx_MyGroups_headerCard_header {
- font-weight: bold;
- margin-bottom: 10px;
-}
-
-.mx_MyGroups_headerCard_content {
- padding-right: 15px;
-}
-
-/* Until the button is wired up */
-.mx_MyGroups_joinBox {
- visibility: hidden;
-
- /* When joinBox wraps onto its own row, it should take up zero height so
- that there isn't an awkward gap between MyGroups_createBox and
- MyGroups_content.
- */
- height: 0px;
- margin: 0px;
-}
-
-.mx_MyGroups_content {
- margin-left: 2px;
-
- flex: 1 0 0;
-
- display: flex;
- flex-direction: column;
- overflow-y: auto;
-}
-
-.mx_MyGroups_scrollable {
- overflow-y: inherit;
-}
-
-.mx_MyGroups_placeholder {
- background-color: $info-plinth-bg-color;
- color: $info-plinth-fg-color;
- line-height: $font-400px;
- border-radius: 10px;
- text-align: center;
-}
-
-.mx_MyGroups_joinedGroups {
- border-top: 1px solid $primary-hairline-color;
- overflow-x: hidden;
-
- display: flex;
- flex-flow: row wrap;
- align-content: flex-start;
-}
-
-.mx_MyGroups_joinedGroups .mx_GroupTile {
- min-width: 300px;
- max-width: 33%;
- flex: 1 0 300px;
- height: 75px;
- margin: 10px 0px;
- display: flex;
- align-items: flex-start;
- cursor: pointer;
-}
-
-.mx_GroupTile_avatar {
- cursor: grab, -webkit-grab;
-}
-
-.mx_GroupTile_profile {
- margin-left: 10px;
- display: flex;
- flex-direction: column;
- justify-content: center;
-}
-
-.mx_GroupTile_profile .mx_GroupTile_name,
-.mx_GroupTile_profile .mx_GroupTile_groupId,
-.mx_GroupTile_profile .mx_GroupTile_desc {
- padding-right: 10px;
-}
-
-.mx_GroupTile_profile .mx_GroupTile_name {
- margin: 0px;
- font-size: $font-15px;
-}
-
-.mx_GroupTile_profile .mx_GroupTile_groupId {
- font-size: $font-13px;
- opacity: 0.7;
-}
-
-.mx_GroupTile_profile .mx_GroupTile_desc {
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- font-size: $font-13px;
- max-height: 36px;
- overflow: hidden;
-}
diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss
index 68e1dd6a9a0..be953a750fc 100644
--- a/res/css/structures/_NotificationPanel.scss
+++ b/res/css/structures/_NotificationPanel.scss
@@ -77,7 +77,7 @@ limitations under the License.
display: none; // we don't need this in this view
}
-.mx_NotificationPanel .mx_EventTile .mx_SenderProfile,
+.mx_NotificationPanel .mx_EventTile .mx_DisambiguatedProfile,
.mx_NotificationPanel .mx_EventTile .mx_MessageTimestamp {
color: $primary-content;
font-size: $font-12px;
diff --git a/res/css/structures/_QuickSettingsButton.scss b/res/css/structures/_QuickSettingsButton.scss
index 17417fa36f4..153dedf6b73 100644
--- a/res/css/structures/_QuickSettingsButton.scss
+++ b/res/css/structures/_QuickSettingsButton.scss
@@ -66,8 +66,9 @@ limitations under the License.
margin: 0 0 16px;
}
- .mx_AccessibleButton_kind_primary_outline {
+ .mx_AccessibleButton_hasKind {
display: block;
+ margin-top: 4px;
}
> div > h4 {
diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss
index 5fb57d19992..a25e172c731 100644
--- a/res/css/structures/_RightPanel.scss
+++ b/res/css/structures/_RightPanel.scss
@@ -93,17 +93,8 @@ limitations under the License.
mask-position: center;
}
-.mx_RightPanel_groupMembersButton::before {
- mask-image: url('$(res)/img/element-icons/community-members.svg');
- mask-position: center;
-}
-
-.mx_RightPanel_roomsButton::before {
- mask-image: url('$(res)/img/element-icons/community-rooms.svg');
- mask-position: center;
-}
-
-$dot-size: 7px;
+$dot-size: 8px;
+$dot-offset: -3px;
$pulse-color: $alert;
.mx_RightPanel_pinnedMessagesButton {
@@ -112,10 +103,11 @@ $pulse-color: $alert;
mask-position: center;
}
}
+
.mx_RightPanel_headerButton_unreadIndicator_bg {
position: absolute;
- right: 0;
- top: 0;
+ right: $dot-offset;
+ top: $dot-offset;
margin: 4px;
width: $dot-size;
height: $dot-size;
@@ -127,17 +119,9 @@ $pulse-color: $alert;
.mx_RightPanel_headerButton_unreadIndicator {
position: absolute;
- right: 0;
- top: 0;
+ right: $dot-offset;
+ top: $dot-offset;
margin: 4px;
- width: $dot-size;
- height: $dot-size;
- border-radius: 50%;
- transform: scale(1);
- background: rgba($pulse-color, 1);
- box-shadow: 0 0 0 0 rgba($pulse-color, 1);
- animation: mx_RightPanel_indicator_pulse 2s infinite;
- animation-iteration-count: 1;
&.mx_Indicator_red {
background: rgba($alert, 1);
@@ -145,29 +129,13 @@ $pulse-color: $alert;
}
&.mx_Indicator_gray {
- background: rgba($roomtile-default-badge-bg-color, 1);
- box-shadow: rgba($roomtile-default-badge-bg-color, 1);
+ background: rgba($room-icon-unread-color, 1);
+ box-shadow: rgba($room-icon-unread-color, 1);
}
&.mx_Indicator_bold {
- background: rgba($input-darker-fg-color, 1);
- box-shadow: rgba($input-darker-fg-color, 1);
- }
-
- &::after {
- content: "";
- position: absolute;
- width: inherit;
- height: inherit;
- top: 0;
- left: 0;
- transform: scale(1);
- transform-origin: center center;
- animation-name: mx_RightPanel_indicator_pulse_shadow;
- animation-duration: inherit;
- animation-iteration-count: inherit;
- border-radius: 50%;
- background: inherit;
+ background: rgba($primary-content, 1);
+ box-shadow: rgba($primary-content, 1);
}
}
@@ -207,7 +175,14 @@ $pulse-color: $alert;
}
}
-.mx_RightPanel_headerButton_highlight {
+.mx_RightPanel_headerButton_unread {
+ &::before {
+ background-color: $room-icon-unread-color !important;
+ }
+}
+
+.mx_RightPanel_headerButton_highlight,
+.mx_RightPanel_headerButton:hover {
&::before {
background-color: $accent !important;
}
@@ -234,7 +209,6 @@ $pulse-color: $alert;
.mx_RightPanel .mx_MemberList,
.mx_RightPanel .mx_MemberInfo,
-.mx_RightPanel .mx_GroupRoomList,
.mx_RightPanel_blank {
order: 2;
flex: 1 1 0;
@@ -253,7 +227,8 @@ $pulse-color: $alert;
margin: 16px 0;
}
- h2, p {
+ h2,
+ p {
font-size: $font-14px;
}
diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss
index a6b2970ddf6..eba8ae8f6e8 100644
--- a/res/css/structures/_RoomView.scss
+++ b/res/css/structures/_RoomView.scss
@@ -70,10 +70,6 @@ limitations under the License.
overflow-y: auto;
flex: 1 1 0;
overflow-anchor: none;
-
- &[data-scrollbar=false] {
- overflow-y: hidden;
- }
}
.mx_RoomView_messagePanelSearchSpinner {
@@ -105,7 +101,9 @@ limitations under the License.
flex: 1;
min-width: 0;
- .mx_RoomView_messagePanel, .mx_RoomView_messagePanelSpinner, .mx_RoomView_messagePanelSearchSpinner {
+ .mx_RoomView_messagePanel,
+ .mx_RoomView_messagePanelSpinner,
+ .mx_RoomView_messagePanelSearchSpinner {
order: 2;
}
}
@@ -147,20 +145,17 @@ limitations under the License.
}
.mx_RoomView_messageListWrapper {
- min-height: 100%;
-
display: flex;
-
flex-direction: column;
-
justify-content: flex-end;
+ position: relative;
}
.mx_RoomView_searchResultsPanel {
.mx_RoomView_messageListWrapper {
justify-content: flex-start;
- > .mx_RoomView_MessageList > li > ol {
+ >.mx_RoomView_MessageList > li > ol {
list-style-type: none;
}
}
@@ -212,6 +207,11 @@ hr.mx_RoomView_myReadMarker {
opacity: 1;
}
+// Rooms with immersive content
+.mx_RoomView_immersive .mx_RoomHeader_wrapper {
+ border: unset;
+}
+
.mx_RoomView_callStatusBar .mx_UploadBar_uploadProgressInner {
background-color: $background;
}
@@ -278,3 +278,62 @@ hr.mx_RoomView_myReadMarker {
min-height: 42px;
}
}
+
+@keyframes mx_Indicator_pulse {
+ 0% {
+ transform: scale(0.95);
+ }
+
+ 70% {
+ transform: scale(1);
+ }
+
+ 100% {
+ transform: scale(0.95);
+ }
+}
+
+@keyframes mx_Indicator_pulse_shadow {
+ 0% {
+ opacity: 0.7;
+ }
+
+ 70% {
+ transform: scale(2.2);
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 0;
+ }
+}
+
+.mx_Indicator {
+ position: absolute;
+ right: -3px;
+ top: -3px;
+ width: $dot-size;
+ height: $dot-size;
+ border-radius: 50%;
+ transform: scale(1);
+ background: rgba($pulse-color, 1);
+ box-shadow: 0 0 0 0 rgba($pulse-color, 1);
+ animation: mx_Indicator_pulse 2s infinite;
+ animation-iteration-count: 1;
+
+ &::after {
+ content: "";
+ position: absolute;
+ width: inherit;
+ height: inherit;
+ top: 0;
+ left: 0;
+ transform: scale(1);
+ transform-origin: center center;
+ animation-name: mx_Indicator_pulse_shadow;
+ animation-duration: inherit;
+ animation-iteration-count: inherit;
+ border-radius: 50%;
+ background: inherit;
+ }
+}
diff --git a/res/css/structures/_SpaceHierarchy.scss b/res/css/structures/_SpaceHierarchy.scss
index a43e538b721..32dda6b90d0 100644
--- a/res/css/structures/_SpaceHierarchy.scss
+++ b/res/css/structures/_SpaceHierarchy.scss
@@ -16,7 +16,6 @@ limitations under the License.
.mx_SpaceRoomView_landing {
.mx_AccessibleButton_kind_link {
- padding: 0;
font-size: inherit;
}
@@ -36,24 +35,31 @@ limitations under the License.
.mx_SpaceHierarchy_listHeader {
display: flex;
- min-height: 32px;
+ flex-flow: wrap;
+ justify-content: space-between;
align-items: center;
+ gap: 12px;
font-size: $font-15px;
line-height: $font-24px;
color: $primary-content;
margin-bottom: 12px;
- > h4 {
+ .mx_SpaceHierarchy_listHeader_header {
+ grid-column-start: 1;
font-weight: $font-semi-bold;
margin: 0;
}
- .mx_AccessibleButton {
- padding: 4px 12px;
- font-weight: normal;
+ .mx_SpaceHierarchy_listHeader_buttons {
+ grid-column-start: 2;
+ display: flex;
+ flex-flow: wrap;
+ gap: 12px;
+ min-height: 32px;
- & + .mx_AccessibleButton {
- margin-left: 16px;
+ .mx_AccessibleButton {
+ padding: 4px 12px;
+ font-weight: normal;
}
}
@@ -61,10 +67,6 @@ limitations under the License.
.mx_AccessibleButton_kind_primary_outline {
padding: 3px 12px; // to account for the 1px border
}
-
- > span {
- margin-left: auto;
- }
}
.mx_SpaceHierarchy_error {
@@ -89,12 +91,6 @@ limitations under the License.
}
}
- .mx_SpaceHierarchy_list {
- list-style: none;
- padding: 0;
- margin: 0;
- }
-
.mx_SpaceHierarchy_roomCount {
> h3 {
display: inline;
@@ -152,124 +148,146 @@ limitations under the License.
padding-left: 12px;
}
- .mx_SpaceHierarchy_roomTile {
- position: relative;
- padding: 8px 16px;
- border-radius: 8px;
- box-sizing: border-box;
-
- display: grid;
- grid-template-columns: 20px auto max-content;
- grid-column-gap: 8px;
- grid-row-gap: 6px;
- align-items: center;
+ .mx_SpaceHierarchy_list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
- .mx_BaseAvatar {
- grid-row: 1;
- grid-column: 1;
- }
+ li.mx_SpaceHierarchy_roomTileWrapper {
+ list-style: none;
- .mx_SpaceHierarchy_roomTile_name {
- font-weight: $font-semi-bold;
- font-size: $font-15px;
- line-height: $font-18px;
- grid-row: 1;
- grid-column: 2;
-
- .mx_InfoTooltip,
- .mx_SpaceHierarchy_roomTile_joined {
- display: inline;
- margin-left: 12px;
- color: $tertiary-content;
- font-size: $font-12px;
- line-height: $font-15px;
-
- .mx_InfoTooltip_icon {
- margin-right: 4px;
- position: relative;
- vertical-align: text-top;
-
- &::before {
- position: absolute;
- top: 0;
- left: 0;
+ .mx_SpaceHierarchy_roomTile {
+ position: relative;
+ padding: 8px 16px;
+ border-radius: 8px;
+ box-sizing: border-box;
+
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px 12px;
+
+ .mx_SpaceHierarchy_roomTile_item {
+ font-weight: $font-semi-bold;
+ font-size: $font-15px;
+ line-height: $font-18px;
+ display: grid;
+ grid-template-columns: 20px auto;
+ gap: 6px 8px;
+ align-items: center;
+ flex: 1; // wrap action buttons
+
+ .mx_SpaceHierarchy_roomTile_avatar {
+ grid-row: 1;
+ grid-column: 1;
}
- }
- }
- .mx_SpaceHierarchy_roomTile_joined {
- position: relative;
- padding-left: 16px;
-
- &::before {
- content: '';
- width: 20px;
- height: 20px;
- top: -2px;
- left: -4px;
- position: absolute;
- mask-position: center;
- mask-size: contain;
- mask-repeat: no-repeat;
- background-color: $accent;
- mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
+ .mx_SpaceHierarchy_roomTile_name {
+ grid-row: 1;
+ grid-column: 2;
+
+ .mx_InfoTooltip,
+ .mx_SpaceHierarchy_roomTile_joined {
+ margin-left: 12px;
+ color: $tertiary-content;
+ font-size: $font-12px;
+ line-height: $font-15px;
+
+ .mx_InfoTooltip_icon {
+ margin-right: 4px;
+ position: relative;
+ vertical-align: text-top;
+
+ &::before {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+ }
+ }
+
+ .mx_InfoTooltip {
+ display: inline-block;
+ }
+
+ .mx_SpaceHierarchy_roomTile_joined {
+ display: inline;
+ position: relative;
+ padding-left: 16px;
+
+ &::before {
+ content: '';
+ width: 20px;
+ height: 20px;
+ top: -2px;
+ left: -4px;
+ position: absolute;
+ mask-position: center;
+ mask-size: contain;
+ mask-repeat: no-repeat;
+ background-color: $accent;
+ mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
+ }
+ }
+ }
+
+ .mx_SpaceHierarchy_roomTile_info {
+ grid-row: 2;
+ grid-column: 2;
+ font-size: $font-14px;
+ font-weight: initial;
+ line-height: $font-18px;
+ color: $secondary-content;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ }
}
- }
- }
- .mx_SpaceHierarchy_roomTile_info {
- font-size: $font-14px;
- line-height: $font-18px;
- color: $secondary-content;
- grid-row: 2;
- grid-column: 2/3;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
- overflow: hidden;
- }
+ .mx_SpaceHierarchy_actions {
+ display: flex;
+ align-items: center;
+ column-gap: 12px;
+ margin-left: auto;
+
+ .mx_AccessibleButton {
+ line-height: $font-24px;
+ padding: 4px 16px;
+ display: inline-block;
+ visibility: hidden;
+ }
- .mx_SpaceHierarchy_actions {
- text-align: right;
- margin-left: 20px;
- grid-column: 3;
- grid-row: 1/3;
+ .mx_AccessibleButton_kind_danger_outline,
+ .mx_AccessibleButton_kind_primary_outline {
+ padding: 3px 16px; // to account for the 1px border
+ }
- .mx_AccessibleButton {
- line-height: $font-24px;
- padding: 4px 16px;
- display: inline-block;
- visibility: hidden;
- }
+ .mx_Checkbox {
+ display: inline-flex;
- .mx_AccessibleButton_kind_danger_outline,
- .mx_AccessibleButton_kind_primary_outline {
- padding: 3px 16px; // to account for the 1px border
- }
+ label {
+ width: 16px;
+ height: 16px;
+ }
+ }
+ }
- .mx_Checkbox {
- display: inline-flex;
- vertical-align: middle;
- margin-left: 12px;
- }
- }
+ &:hover, &:focus-within {
+ background-color: $spacePanel-bg-color;
- &:hover, &:focus-within {
- background-color: $groupFilterPanel-bg-color;
+ .mx_AccessibleButton {
+ visibility: visible;
+ }
+ }
- .mx_AccessibleButton {
- visibility: visible;
+ &.mx_SpaceHierarchy_joining {
+ .mx_AccessibleButton {
+ visibility: visible;
+ padding: 4px 18px;
+ }
+ }
}
}
-
- &.mx_SpaceHierarchy_joining .mx_AccessibleButton {
- visibility: visible;
- padding: 4px 18px;
- }
- }
-
- li.mx_SpaceHierarchy_roomTileWrapper {
- list-style: none;
}
.mx_SpaceHierarchy_roomTile,
@@ -277,7 +295,7 @@ limitations under the License.
&::before {
content: "";
position: absolute;
- background-color: $groupFilterPanel-bg-color;
+ background-color: $spacePanel-bg-color;
width: 1px;
height: 100%;
left: 6px;
diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss
index d999ff89b8f..9d23dd5fc93 100644
--- a/res/css/structures/_SpacePanel.scss
+++ b/res/css/structures/_SpacePanel.scss
@@ -23,7 +23,7 @@ $activeBackgroundColor: $panel-actions;
$activeBorderColor: $primary-content;
.mx_SpacePanel {
- background-color: $groupFilterPanel-bg-color;
+ background-color: $spacePanel-bg-color;
flex: 0 0 auto;
padding: 0;
margin: 0;
@@ -155,6 +155,7 @@ $activeBorderColor: $primary-content;
border-radius: 12px;
padding: 4px;
width: calc(100% - 32px);
+ min-width: 0;
}
.mx_SpaceButton_name {
@@ -251,7 +252,8 @@ $activeBorderColor: $primary-content;
margin-top: auto;
margin-bottom: auto;
display: none;
- position: relative;
+ position: absolute;
+ right: 4px;
&::before {
top: 3px;
@@ -273,6 +275,7 @@ $activeBorderColor: $primary-content;
display: flex;
flex-direction: column;
max-width: 250px;
+ min-width: 0;
flex-grow: 1;
.mx_BaseAvatar:not(.mx_UserMenu_userAvatar_BaseAvatar) .mx_BaseAvatar_initial {
@@ -309,13 +312,13 @@ $activeBorderColor: $primary-content;
.mx_NotificationBadge_dot {
// make the smaller dot occupy the same width for centering
margin: 0 -1px 0 0;
- border: 3px solid $groupFilterPanel-bg-color;
+ border: 3px solid $spacePanel-bg-color;
}
.mx_NotificationBadge_2char,
.mx_NotificationBadge_3char {
margin: -5px -5px 0 0;
- border: 2px solid $groupFilterPanel-bg-color;
+ border: 2px solid $spacePanel-bg-color;
}
}
@@ -327,6 +330,16 @@ $activeBorderColor: $primary-content;
}
}
+ .mx_SpaceItem:not(.mx_SpaceItem_new) {
+ .mx_SpaceButton:hover,
+ .mx_SpaceButton:focus-within,
+ .mx_SpaceButton_hasMenuOpen {
+ &:not(.mx_SpaceButton_narrow):not(.mx_SpaceButton_invite) .mx_SpaceButton_name {
+ max-width: calc(100% - 56px);
+ }
+ }
+ }
+
/* root space buttons are bigger and not indented */
& > .mx_AutoHideScrollbar {
flex: 1;
diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss
index 8fbd6fbb703..eed3d8830f6 100644
--- a/res/css/structures/_SpaceRoomView.scss
+++ b/res/css/structures/_SpaceRoomView.scss
@@ -137,6 +137,23 @@ $SpaceRoomViewInnerWidth: 428px;
}
}
+ .mx_SpaceRoomView_preview,
+ .mx_SpaceRoomView_landing {
+ .mx_SpaceRoomView_info_memberCount {
+ color: inherit;
+ position: relative;
+ padding: 0 0 0 16px;
+ font-size: $font-15px;
+ display: inline; // cancel inline-flex
+
+ &::before {
+ content: "·"; // visual separator
+ position: absolute;
+ left: 6px;
+ }
+ }
+ }
+
.mx_SpaceRoomView_preview {
padding: 32px 24px !important; // override default padding from above
margin: auto;
@@ -152,6 +169,7 @@ $SpaceRoomViewInnerWidth: 428px;
right: 24px;
top: 32px;
}
+
// XXX remove this when spaces leaves Beta
.mx_SpaceRoomView_preview_spaceBetaPrompt {
font-weight: $font-semi-bold;
@@ -183,18 +201,6 @@ $SpaceRoomViewInnerWidth: 428px;
}
}
- .mx_SpaceRoomView_preview_migratedCommunity {
- margin-bottom: 16px;
- padding: 8px 12px;
- border-radius: 8px;
- border: 1px solid $input-border-color;
- width: max-content;
-
- .mx_BaseAvatar {
- margin-right: 4px;
- }
- }
-
.mx_SpaceRoomView_preview_inviter {
display: flex;
align-items: center;
@@ -254,13 +260,27 @@ $SpaceRoomViewInnerWidth: 428px;
flex-direction: column;
min-width: 0;
- > .mx_BaseAvatar {
- width: 80px;
- }
+ .mx_SpaceRoomView_landing_header {
+ display: flex;
+ justify-content: space-between;
+
+ .mx_BaseAvatar {
+ width: 80px;
- > .mx_BaseAvatar_image,
- > .mx_BaseAvatar > .mx_BaseAvatar_image {
- border-radius: 12px;
+ .mx_BaseAvatar_image {
+ border-radius: 12px;
+ }
+ }
+
+ // XXX: Temporary for the Spaces release only
+ .mx_SpaceFeedbackPrompt {
+ padding: 7px; // 8px - 1px border
+ border: 1px solid rgba($primary-content, .1);
+ border-radius: 8px;
+ width: max-content;
+ height: fit-content;
+ margin-left: 24px;
+ }
}
.mx_SpaceRoomView_landing_name {
@@ -269,13 +289,11 @@ $SpaceRoomViewInnerWidth: 428px;
color: $secondary-content;
> span {
- display: inline-block;
- }
+ display: flex;
+ flex-flow: column;
+ gap: 12px 0;
- .mx_SpaceRoomView_landing_nameRow {
- margin-top: 12px;
-
- > h1 {
+ h1 {
display: inline-block;
}
}
@@ -288,62 +306,101 @@ $SpaceRoomViewInnerWidth: 428px;
}
}
- .mx_SpaceRoomView_landing_info {
+ .mx_SpaceRoomView_landing_infoBar {
display: flex;
align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
+ line-height: $font-24px;
.mx_SpaceRoomView_info {
+ color: $secondary-content;
+ font-size: $font-15px;
display: inline-block;
- margin: 0 auto 0 0;
- }
- .mx_FacePile {
- display: inline-block;
- margin-right: 12px;
+ .mx_SpaceRoomView_info_public,
+ .mx_SpaceRoomView_info_private {
+ padding-left: 20px;
+ position: relative;
+
+ &::before {
+ position: absolute;
+ content: "";
+ width: 20px;
+ height: 20px;
+ top: 0;
+ left: -2px;
+ mask-position: center;
+ mask-repeat: no-repeat;
+ background-color: $tertiary-content;
+ }
+ }
- .mx_FacePile_faces {
- cursor: pointer;
+ .mx_SpaceRoomView_info_public::before {
+ mask-size: 12px;
+ mask-image: url("$(res)/img/globe.svg");
+ }
+
+ .mx_SpaceRoomView_info_private::before {
+ mask-size: 14px;
+ mask-image: url("$(res)/img/element-icons/lock.svg");
}
}
- .mx_SpaceRoomView_landing_inviteButton {
- position: relative;
- padding: 4px 18px 4px 40px;
- line-height: $font-24px;
- height: min-content;
+ .mx_SpaceRoomView_landing_infoBar_interactive {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 12px 12px;
- &::before {
- position: absolute;
- content: "";
- left: 8px;
- height: 16px;
- width: 16px;
- background: #ffffff; // white icon fill
- mask-position: center;
- mask-size: 16px;
- mask-repeat: no-repeat;
- mask-image: url('$(res)/img/element-icons/room/invite.svg');
+ .mx_FacePile {
+ display: inline-block;
+
+ .mx_FacePile_faces {
+ cursor: pointer;
+ }
}
- }
- .mx_SpaceRoomView_landing_settingsButton {
- position: relative;
- margin-left: 16px;
- width: 24px;
- height: 24px;
+ .mx_SpaceRoomView_landing_inviteButton,
+ .mx_SpaceRoomView_landing_settingsButton {
+ position: relative;
- &::before {
- position: absolute;
- content: "";
- left: 0;
- top: 0;
- height: 24px;
+ &::before {
+ position: absolute;
+ content: "";
+ mask-position: center;
+ mask-repeat: no-repeat;
+ }
+ }
+
+ .mx_SpaceRoomView_landing_inviteButton {
+ padding: 4px 18px 4px 40px;
+ height: min-content;
+
+ &::before {
+ left: 8px;
+ height: 16px;
+ width: 16px;
+ background: #fff; // white icon fill
+ mask-size: 16px;
+ mask-image: url('$(res)/img/element-icons/room/invite.svg');
+ }
+ }
+
+ .mx_SpaceRoomView_landing_settingsButton {
width: 24px;
- background: $tertiary-content;
- mask-position: center;
- mask-size: contain;
- mask-repeat: no-repeat;
- mask-image: url('$(res)/img/element-icons/settings.svg');
+ height: 24px;
+
+ &::before {
+ left: 0;
+ top: 0;
+ height: 24px;
+ width: 24px;
+ background: $tertiary-content;
+ mask-size: contain;
+ mask-image: url('$(res)/img/element-icons/settings.svg');
+ }
}
}
}
@@ -360,14 +417,6 @@ $SpaceRoomViewInnerWidth: 428px;
margin: 0 0 20px;
flex: 0;
}
-
- .mx_SpaceFeedbackPrompt {
- padding: 7px; // 8px - 1px border
- border: 1px solid rgba($primary-content, .1);
- border-radius: 8px;
- width: max-content;
- margin: 0 0 -40px auto; // collapse its own height to not push other components down
- }
}
.mx_SpaceRoomView_privateScope {
@@ -380,7 +429,7 @@ $SpaceRoomViewInnerWidth: 428px;
}
.mx_SpaceRoomView_privateScope_meAndMyTeammatesButton::before {
- mask-image: url('$(res)/img/element-icons/community-members.svg');
+ mask-image: url('$(res)/img/element-icons/group-members.svg');
}
}
@@ -441,51 +490,3 @@ $SpaceRoomViewInnerWidth: 428px;
}
}
}
-
-.mx_SpaceRoomView_info {
- color: $secondary-content;
- font-size: $font-15px;
- line-height: $font-24px;
- margin: 20px 0;
-
- .mx_SpaceRoomView_info_public,
- .mx_SpaceRoomView_info_private {
- padding-left: 20px;
- position: relative;
-
- &::before {
- position: absolute;
- content: "";
- width: 20px;
- height: 20px;
- top: 0;
- left: -2px;
- mask-position: center;
- mask-repeat: no-repeat;
- background-color: $tertiary-content;
- }
- }
-
- .mx_SpaceRoomView_info_public::before {
- mask-size: 12px;
- mask-image: url("$(res)/img/globe.svg");
- }
-
- .mx_SpaceRoomView_info_private::before {
- mask-size: 14px;
- mask-image: url("$(res)/img/element-icons/lock.svg");
- }
-
- .mx_SpaceRoomView_info_memberCount {
- color: inherit;
- position: relative;
- padding: 0 0 0 16px;
- font-size: $font-15px;
-
- &::before {
- content: "·"; // visual separator
- position: absolute;
- left: 6px;
- }
- }
-}
diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss
index e4fe3c1e664..00c7cd05b45 100644
--- a/res/css/structures/_ToastContainer.scss
+++ b/res/css/structures/_ToastContainer.scss
@@ -108,26 +108,25 @@ limitations under the License.
}
.mx_Toast_title {
+ display: flex;
+ align-items: center;
+ column-gap: 8px;
width: 100%;
box-sizing: border-box;
h2 {
- grid-column: 1 / 3;
- grid-row: 1;
margin: 0;
font-size: $font-15px;
font-weight: 600;
display: inline;
width: auto;
- vertical-align: middle;
}
- span {
- padding-left: 8px;
- float: right;
+ .mx_Toast_title_countIndicator {
font-size: $font-12px;
line-height: $font-22px;
color: $secondary-content;
+ margin-inline-start: auto; // on the end side of the div
}
}
@@ -137,17 +136,14 @@ limitations under the License.
}
.mx_Toast_buttons {
- float: right;
display: flex;
+ justify-content: flex-end;
+ column-gap: 5px;
.mx_AccessibleButton {
min-width: 96px;
box-sizing: border-box;
}
-
- .mx_AccessibleButton + .mx_AccessibleButton {
- margin-left: 5px;
- }
}
.mx_Toast_description {
@@ -157,11 +153,6 @@ limitations under the License.
margin: 4px 0 11px 0;
font-size: $font-12px;
- .mx_AccessibleButton_kind_link {
- font-size: inherit;
- padding: 0;
- }
-
a {
text-decoration: none;
}
diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss
index aa4e7d6403e..a88e9eddb90 100644
--- a/res/css/structures/_UserMenu.scss
+++ b/res/css/structures/_UserMenu.scss
@@ -64,9 +64,13 @@ limitations under the License.
}
}
-.mx_UserMenu_contextMenu {
- width: 258px;
+.mx_IconizedContextMenu {
+ &.mx_UserMenu_contextMenu {
+ width: 258px;
+ }
+}
+.mx_UserMenu_contextMenu {
&.mx_IconizedContextMenu .mx_IconizedContextMenu_optionList_red {
.mx_AccessibleButton {
padding-top: 16px;
@@ -87,12 +91,15 @@ limitations under the License.
flex-direction: column;
width: calc(100% - 40px); // 40px = 32px theme button + 8px margin to theme button
- * {
- // Automatically grow all subelements to fit the container
+ .mx_UserMenu_contextMenu_displayName,
+ .mx_UserMenu_contextMenu_userId {
+ font-size: $font-15px;
+
+ // Automatically grow subelements to fit the container
flex: 1;
width: 100%;
- // Ellipsize any text overflow
+ // Ellipsize text overflow
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
@@ -100,12 +107,10 @@ limitations under the License.
.mx_UserMenu_contextMenu_displayName {
font-weight: bold;
- font-size: $font-15px;
line-height: $font-20px;
}
.mx_UserMenu_contextMenu_userId {
- font-size: $font-15px;
line-height: $font-24px;
}
}
@@ -142,12 +147,6 @@ limitations under the License.
margin-top: 8px;
}
}
-
- .mx_AccessibleButton_kind_link {
- font-weight: normal;
- font-size: inherit;
- padding: 0;
- }
}
}
diff --git a/res/css/structures/_VideoRoomView.scss b/res/css/structures/_VideoRoomView.scss
new file mode 100644
index 00000000000..d99b3f5894b
--- /dev/null
+++ b/res/css/structures/_VideoRoomView.scss
@@ -0,0 +1,41 @@
+/*
+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_VideoRoomView {
+ flex-grow: 1;
+ min-height: 0;
+
+ display: flex;
+ flex-direction: column;
+ margin: $container-gap-width;
+ margin-right: calc($container-gap-width / 2);
+
+ background-color: $header-panel-bg-color;
+ padding-top: 33px; // to match the right panel chat heading
+ border: 8px solid $header-panel-bg-color;
+ border-radius: 8px;
+
+ .mx_AppTile {
+ width: auto;
+ height: 100%;
+ border: none;
+ }
+
+ // While the lobby is shown, the widget needs to stay loaded but hidden in the background
+ .mx_VideoLobby ~ .mx_AppTile {
+ display: none;
+ }
+}
diff --git a/res/css/structures/_ViewSource.scss b/res/css/structures/_ViewSource.scss
index e3d6135ef30..3dc3e21489e 100644
--- a/res/css/structures/_ViewSource.scss
+++ b/res/css/structures/_ViewSource.scss
@@ -34,8 +34,17 @@ limitations under the License.
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%;
+}
diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss
index 2290b6a3455..638917f1c28 100644
--- a/res/css/structures/auth/_Login.scss
+++ b/res/css/structures/auth/_Login.scss
@@ -88,14 +88,12 @@ limitations under the License.
div.mx_AccessibleButton_kind_link.mx_Login_forgot {
display: block;
margin: 0 auto;
- // style it as a link
- font-size: inherit;
- padding: 0;
&.mx_AccessibleButton_disabled {
cursor: not-allowed;
}
}
+
.mx_Login_spinner {
display: flex;
justify-content: center;
diff --git a/res/css/views/audio_messages/_PlaybackContainer.scss b/res/css/views/audio_messages/_PlaybackContainer.scss
index 408a2953893..4999980beaf 100644
--- a/res/css/views/audio_messages/_PlaybackContainer.scss
+++ b/res/css/views/audio_messages/_PlaybackContainer.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2021 The Matrix.org Foundation C.I.C.
+Copyright 2021 - 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.
@@ -29,6 +29,7 @@ limitations under the License.
contain: content;
+ // Waveforms are present in live recording only
.mx_Waveform {
.mx_Waveform_bar {
background-color: $quaternary-content;
@@ -46,11 +47,22 @@ limitations under the License.
.mx_Clock {
width: $font-42px; // we're not using a monospace font, so fake it
+ min-width: $font-42px; // force sensible layouts in awkward flexboxes (file panel, for example)
padding-right: 6px; // with the fixed width this ends up as a visual 8px most of the time, as intended.
padding-left: 8px; // isolate from recording circle / play control
}
- &.mx_VoiceMessagePrimaryContainer_noWaveform {
- max-width: 162px; // with all the padding this results in 185px wide
+ // For timeline-rendered playback, mirror the values for where the clock is in
+ // the waveform version.
+ .mx_SeekBar {
+ margin-left: 8px;
+ margin-right: 6px;
+
+ & + .mx_Clock {
+ text-align: right;
+
+ // Take the padding off the clock because it's accounted for in the seek bar
+ padding: 0;
+ }
}
}
diff --git a/res/css/views/avatars/_DecoratedRoomAvatar.scss b/res/css/views/avatars/_DecoratedRoomAvatar.scss
index b912302f382..7927f82c2f5 100644
--- a/res/css/views/avatars/_DecoratedRoomAvatar.scss
+++ b/res/css/views/avatars/_DecoratedRoomAvatar.scss
@@ -65,6 +65,10 @@ limitations under the License.
background-color: $presence-away;
}
+ .mx_DecoratedRoomAvatar_icon_busy::before {
+ background-color: $presence-busy;
+ }
+
.mx_NotificationBadge, .mx_RoomTile_badgeContainer {
position: absolute;
top: 0;
diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss
index 80a70ad0d62..658e43f051b 100644
--- a/res/css/views/beta/_BetaCard.scss
+++ b/res/css/views/beta/_BetaCard.scss
@@ -23,8 +23,13 @@ limitations under the License.
.mx_BetaCard_columns {
display: flex;
+ flex-flow: wrap;
+ gap: 20px;
+ justify-content: center;
+
+ .mx_BetaCard_columns_description {
+ flex: 1;
- > div {
.mx_BetaCard_title {
font-weight: $font-semi-bold;
font-size: $font-18px;
@@ -32,47 +37,60 @@ limitations under the License.
color: $primary-content;
margin: 4px 0 14px;
- .mx_BetaCard_betaPill {
- margin-left: 12px;
- }
+ display: flex;
+ align-items: center;
+ column-gap: 12px;
}
.mx_BetaCard_caption {
font-size: $font-15px;
line-height: $font-20px;
color: $secondary-content;
- margin-bottom: 20px;
}
- .mx_BetaCard_buttons .mx_AccessibleButton {
- display: block;
- margin: 12px 0;
- padding: 7px 40px;
- width: auto;
+ .mx_BetaCard_buttons {
+ display: flex;
+ flex-wrap: wrap-reverse;
+ gap: 12px;
+ margin: 20px auto;
+
+ .mx_AccessibleButton {
+ padding: 7px 40px;
+ width: auto;
+ flex: 1;
+ white-space: nowrap; // text might overflow
+
+ &:nth-child(1) {
+ order: 2; // Place feedback button top and right
+ }
+ }
}
.mx_BetaCard_disclaimer {
font-size: $font-12px;
line-height: $font-15px;
color: $secondary-content;
- margin-top: 20px;
> h4 {
- margin: 0;
+ margin: 12px 0 0;
}
> p {
- margin-top: 0;
+ margin: 0;
}
}
}
- > img {
- margin: auto 0 auto 20px;
- width: 300px;
- object-fit: contain;
- height: 100%;
- border-radius: 4px;
+ .mx_BetaCard_columns_image_wrapper {
+ margin: auto 0;
+
+ .mx_BetaCard_columns_image {
+ width: 100%;
+ max-width: 300px;
+ object-fit: contain;
+ height: 100%;
+ border-radius: 4px;
+ }
}
}
@@ -104,6 +122,7 @@ limitations under the License.
color: #FFFFFF;
display: inline-block;
vertical-align: text-bottom;
+ word-break: keep-all; // avoid multiple lines on CJK language
&.mx_AccessibleButton {
cursor: pointer;
diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss
index e743619f8fd..b92ce10d355 100644
--- a/res/css/views/context_menus/_MessageContextMenu.scss
+++ b/res/css/views/context_menus/_MessageContextMenu.scss
@@ -90,6 +90,22 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/room/pin.svg');
}
+ .mx_MessageContextMenu_iconCopy::before {
+ mask-image: url($copy-button-url);
+ }
+
+ .mx_MessageContextMenu_iconEdit::before {
+ mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg');
+ }
+
+ .mx_MessageContextMenu_iconReply::before {
+ mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg');
+ }
+
+ .mx_MessageContextMenu_iconReact::before {
+ mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg');
+ }
+
.mx_MessageContextMenu_iconViewInRoom::before {
mask-image: url('$(res)/img/element-icons/view-in-room.svg');
}
diff --git a/res/css/views/context_menus/_TagTileContextMenu.scss b/res/css/views/context_menus/_TagTileContextMenu.scss
deleted file mode 100644
index 14f5ec817ef..00000000000
--- a/res/css/views/context_menus/_TagTileContextMenu.scss
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-Copyright 2018 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_TagTileContextMenu_item {
- padding: 8px;
- padding-right: 20px;
- cursor: pointer;
- white-space: nowrap;
- display: flex;
- align-items: center;
- line-height: $font-16px;
-}
-
-.mx_TagTileContextMenu_item::before {
- content: '';
- height: 15px;
- width: 15px;
- background-color: currentColor;
- mask-repeat: no-repeat;
- mask-size: contain;
- margin-right: 8px;
-}
-
-.mx_TagTileContextMenu_viewCommunity::before {
- mask-image: url('$(res)/img/element-icons/view-community.svg');
-}
-
-.mx_TagTileContextMenu_moveUp::before {
- transform: rotate(180deg);
- mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
-}
-
-.mx_TagTileContextMenu_moveDown::before {
- mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
-}
-
-.mx_TagTileContextMenu_hideCommunity::before {
- mask-image: url('$(res)/img/element-icons/hide.svg');
-}
-
-.mx_TagTileContextMenu_createSpace::before {
- mask-image: url('$(res)/img/element-icons/message/fwd.svg');
-}
-
-.mx_TagTileContextMenu_separator {
- margin-top: 0;
- margin-bottom: 0;
- border-bottom-style: none;
- border-left-style: none;
- border-right-style: none;
- border-top-style: solid;
- border-top-width: 1px;
- border-color: $menu-border-color;
-}
diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
index 5d53a996610..f82a29c546b 100644
--- a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
+++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
@@ -54,7 +54,6 @@ limitations under the License.
font-size: $font-12px;
line-height: $font-15px;
margin-top: 8px;
- padding: 0;
}
}
@@ -136,10 +135,6 @@ limitations under the License.
left: 0;
}
}
-
- .mx_AccessibleButton_kind_link {
- padding: 0;
- }
}
}
diff --git a/res/css/views/dialogs/_AddressPickerDialog.scss b/res/css/views/dialogs/_AddressPickerDialog.scss
deleted file mode 100644
index 44e9f94c5f7..00000000000
--- a/res/css/views/dialogs/_AddressPickerDialog.scss
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2019 New Vector Ltd
-Copyright 2019 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_AddressPickerDialog {
- a:link,
- a:hover,
- a:visited {
- @mixin mx_Dialog_link;
- }
-}
-
-/* Using a textarea for this element, to circumvent autofill */
-.mx_AddressPickerDialog_input,
-.mx_AddressPickerDialog_input:focus {
- height: 26px;
- font-size: $font-14px;
- padding-left: 12px;
- padding-right: 12px;
- margin: 0 !important;
- border: 0 !important;
- outline: 0 !important;
- width: 1000%; /* Pretend that this is an "input type=text" */
- resize: none;
- overflow: hidden;
- vertical-align: middle;
- box-sizing: border-box;
- word-wrap: nowrap;
-}
-
-.mx_AddressPickerDialog .mx_Dialog_content {
- min-height: 50px;
-}
-
-.mx_AddressPickerDialog_inputContainer {
- border-radius: 3px;
- border: solid 1px $input-border-color;
- line-height: $font-36px;
- padding-left: 4px;
- padding-right: 4px;
- padding-top: 1px;
- padding-bottom: 1px;
- max-height: 150px;
- overflow-x: hidden;
- overflow-y: auto;
-}
-
-.mx_AddressPickerDialog_error {
- margin-top: 10px;
- color: $alert;
-}
-
-.mx_AddressPickerDialog_cancel {
- position: absolute;
- right: 11px;
- top: 13px;
- cursor: pointer;
-}
-
-.mx_AddressPickerDialog_cancel object {
- pointer-events: none;
-}
-
-.mx_AddressPickerDialog_identityServer {
- margin-top: 1em;
-}
diff --git a/res/css/views/dialogs/_BugReportDialog.scss b/res/css/views/dialogs/_BugReportDialog.scss
index 1920ac33eaa..348d9ebb99e 100644
--- a/res/css/views/dialogs/_BugReportDialog.scss
+++ b/res/css/views/dialogs/_BugReportDialog.scss
@@ -17,7 +17,7 @@ limitations under the License.
.mx_BugReportDialog {
.mx_BugReportDialog_download {
.mx_AccessibleButton_kind_link {
- padding-left: 0;
+ margin-right: 18px; // Space between "Downloading logs..."
}
}
}
diff --git a/res/css/views/messages/_SenderProfile.scss b/res/css/views/dialogs/_BulkRedactDialog.scss
similarity index 65%
rename from res/css/views/messages/_SenderProfile.scss
rename to res/css/views/dialogs/_BulkRedactDialog.scss
index 08644b14e3f..c6b2adff874 100644
--- a/res/css/views/messages/_SenderProfile.scss
+++ b/res/css/views/dialogs/_BulkRedactDialog.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2021 Robin Townsend
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_SenderProfile_displayName {
- font-weight: 600;
-}
+.mx_BulkRedactDialog {
+ .mx_Checkbox, .mx_BulkRedactDialog_checkboxMicrocopy {
+ line-height: $font-20px;
+ }
-.mx_SenderProfile_mxid {
- font-weight: 600;
- font-size: 1.1rem;
- margin-left: 5px;
- opacity: 0.5; // Match mx_TextualEvent
+ .mx_BulkRedactDialog_checkboxMicrocopy {
+ margin-left: 26px;
+ color: $secondary-content;
+ }
}
diff --git a/res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss b/res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss
deleted file mode 100644
index 5d6c817b14d..00000000000
--- a/res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss
+++ /dev/null
@@ -1,88 +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_CommunityPrototypeInviteDialog {
- &.mx_Dialog_fixedWidth {
- width: 360px;
- }
-
- .mx_Dialog_content {
- margin-bottom: 0;
-
- .mx_CommunityPrototypeInviteDialog_people {
- position: relative;
- margin-bottom: 4px;
-
- .mx_AccessibleButton {
- display: inline-block;
- background-color: $focus-bg-color; // XXX: Abuse of variables
- border-radius: 4px;
- padding: 3px 5px;
- font-size: $font-12px;
- float: right;
- }
- }
-
- .mx_CommunityPrototypeInviteDialog_morePeople {
- margin-top: 8px;
- }
-
- .mx_CommunityPrototypeInviteDialog_person {
- position: relative;
- margin-top: 4px;
-
- & > * {
- vertical-align: middle;
- }
-
- .mx_Checkbox {
- position: absolute;
- right: 0;
- top: calc(50% - 8px); // checkbox is 16px high
- width: 16px; // to force a square
- }
-
- .mx_CommunityPrototypeInviteDialog_personIdentifiers {
- display: inline-block;
-
- & > * {
- display: block;
- }
-
- .mx_CommunityPrototypeInviteDialog_personName {
- font-weight: 600;
- font-size: $font-14px;
- color: $primary-content;
- margin-left: 7px;
- }
-
- .mx_CommunityPrototypeInviteDialog_personId {
- font-size: $font-12px;
- color: $muted-fg-color;
- margin-left: 7px;
- }
- }
- }
-
- .mx_CommunityPrototypeInviteDialog_primaryButton {
- display: block;
- font-size: $font-13px;
- line-height: 20px;
- height: 20px;
- margin-top: 24px;
- }
- }
-}
diff --git a/res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss b/res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss
deleted file mode 100644
index a2378115a85..00000000000
--- a/res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss
+++ /dev/null
@@ -1,102 +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_CreateCommunityPrototypeDialog {
- .mx_Dialog_content {
- display: flex;
- flex-direction: row;
- margin-bottom: 12px;
-
- .mx_CreateCommunityPrototypeDialog_colName {
- flex-basis: 66.66%;
- padding-right: 100px;
-
- .mx_Field input {
- font-size: $font-16px;
- line-height: $font-20px;
- }
-
- .mx_CreateCommunityPrototypeDialog_subtext {
- display: block;
- color: $muted-fg-color;
- margin-bottom: 16px;
-
- &:last-child {
- margin-top: 16px;
- }
-
- &.mx_CreateCommunityPrototypeDialog_subtext_error {
- color: $alert;
- }
- }
-
- .mx_CreateCommunityPrototypeDialog_communityId {
- position: relative;
-
- .mx_InfoTooltip {
- float: right;
- }
- }
-
- .mx_AccessibleButton {
- display: block;
- height: 32px;
- font-size: $font-16px;
- line-height: 32px;
- }
- }
-
- .mx_CreateCommunityPrototypeDialog_colAvatar {
- flex-basis: 33.33%;
-
- .mx_CreateCommunityPrototypeDialog_avatarContainer {
- margin-top: 12px;
- margin-bottom: 20px;
-
- .mx_CreateCommunityPrototypeDialog_avatar,
- .mx_CreateCommunityPrototypeDialog_placeholderAvatar {
- width: 96px;
- height: 96px;
- border-radius: 96px;
- }
-
- .mx_CreateCommunityPrototypeDialog_placeholderAvatar {
- background-color: #368bd6; // hardcoded for both themes
-
- &::before {
- display: inline-block;
- background-color: #fff; // hardcoded because the background is
- mask-repeat: no-repeat;
- mask-size: 96px;
- width: 96px;
- height: 96px;
- mask-position: center;
- content: '';
- vertical-align: middle;
- mask-image: url('$(res)/img/element-icons/add-photo.svg');
- }
- }
- }
-
- .mx_CreateCommunityPrototypeDialog_tip {
- & > b, & > span {
- display: block;
- color: $muted-fg-color;
- }
- }
- }
- }
-}
diff --git a/res/css/views/dialogs/_CreateGroupDialog.scss b/res/css/views/dialogs/_CreateGroupDialog.scss
deleted file mode 100644
index ef9c2b73d43..00000000000
--- a/res/css/views/dialogs/_CreateGroupDialog.scss
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-Copyright 2017 Vector Creations 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_CreateGroupDialog_inputRow {
- margin-top: 10px;
- margin-bottom: 10px;
-}
-
-.mx_CreateGroupDialog_label {
- text-align: left;
- padding-bottom: 12px;
-}
-
-.mx_CreateGroupDialog_input {
- font-size: $font-15px;
- border-radius: 3px;
- border: 1px solid $input-border-color;
- padding: 9px;
- color: $primary-content;
- background-color: $background;
-}
-
-.mx_CreateGroupDialog_input_hasPrefixAndSuffix {
- border-radius: 0px;
-}
-
-.mx_CreateGroupDialog_input_group {
- display: flex;
-}
-
-.mx_CreateGroupDialog_prefix,
-.mx_CreateGroupDialog_suffix {
- padding: 0px 5px;
- line-height: $font-37px;
- background-color: $input-darker-bg-color;
- border: 1px solid $input-border-color;
- text-align: center;
-}
-
-.mx_CreateGroupDialog_prefix {
- border-right: 0px;
- border-radius: 3px 0px 0px 3px;
-}
-
-.mx_CreateGroupDialog_suffix {
- border-left: 0px;
- border-radius: 0px 3px 3px 0px;
-}
diff --git a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss
deleted file mode 100644
index 72c2d13ae38..00000000000
--- a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
-Copyright 2021 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_CreateSpaceFromCommunityDialog_wrapper {
- .mx_Dialog {
- display: flex;
- flex-direction: column;
- }
-}
-
-.mx_CreateSpaceFromCommunityDialog {
- width: 480px;
- color: $primary-content;
- display: flex;
- flex-direction: column;
- flex-wrap: nowrap;
- min-height: 0;
-
- .mx_CreateSpaceFromCommunityDialog_content {
- > p {
- font-size: $font-15px;
- line-height: $font-24px;
-
- &:first-of-type {
- margin-top: 0;
- }
-
- &.mx_CreateSpaceFromCommunityDialog_flairNotice {
- font-size: $font-12px;
- line-height: $font-15px;
- }
- }
-
- .mx_SpaceBasicSettings {
- > p {
- font-size: $font-12px;
- line-height: $font-15px;
- margin: 16px 0;
- }
-
- .mx_Field_textarea {
- margin-bottom: 0;
- }
- }
-
- .mx_JoinRuleDropdown .mx_Dropdown_menu {
- width: auto !important; // override fixed width
- }
-
- .mx_CreateSpaceFromCommunityDialog_nonPublicSpacer {
- height: 63px; // balance the height of the missing room alias field to prevent modal bouncing
- }
- }
-
- .mx_CreateSpaceFromCommunityDialog_footer {
- display: flex;
- margin-top: 20px;
-
- > span {
- flex-grow: 1;
- font-size: $font-12px;
- line-height: $font-15px;
- color: $secondary-content;
- margin-top: -13px; // match height of buttons to prevent height changing
-
- .mx_ProgressBar {
- height: 8px;
- width: 100%;
-
- @mixin ProgressBarBorderRadius 8px;
- }
-
- .mx_CreateSpaceFromCommunityDialog_progressText {
- margin-top: 8px;
- font-size: $font-15px;
- line-height: $font-24px;
- color: $primary-content;
- }
-
- > * {
- vertical-align: middle;
- }
- }
-
- .mx_CreateSpaceFromCommunityDialog_error {
- padding-left: 12px;
-
- > img {
- align-self: center;
- }
-
- .mx_CreateSpaceFromCommunityDialog_errorHeading {
- font-weight: $font-semi-bold;
- font-size: $font-15px;
- line-height: $font-18px;
- color: $alert;
- }
-
- .mx_CreateSpaceFromCommunityDialog_errorCaption {
- margin-top: 4px;
- font-size: $font-12px;
- line-height: $font-15px;
- color: $primary-content;
- }
- }
-
- .mx_AccessibleButton {
- display: inline-block;
- align-self: center;
- }
-
- .mx_AccessibleButton_kind_primary {
- padding: 8px 36px;
- margin-left: 24px;
- }
-
- .mx_AccessibleButton_kind_primary_outline {
- margin-left: auto;
- }
-
- .mx_CreateSpaceFromCommunityDialog_retryButton {
- margin-left: 12px;
- padding-left: 24px;
- position: relative;
-
- &::before {
- content: '';
- position: absolute;
- background-color: $primary-content;
- mask-repeat: no-repeat;
- mask-position: center;
- mask-size: contain;
- mask-image: url('$(res)/img/element-icons/retry.svg');
- width: 18px;
- height: 18px;
- left: 0;
- }
- }
-
- .mx_AccessibleButton_kind_link {
- padding: 0;
- }
- }
-}
-
-.mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog {
- .mx_InfoDialog {
- max-width: 500px;
- }
-
- .mx_AccessibleButton_kind_link {
- padding: 0;
- }
-
- .mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog_checkmark {
- position: relative;
- border-radius: 50%;
- border: 3px solid $accent;
- width: 68px;
- height: 68px;
- margin: 12px auto 32px;
-
- &::before {
- width: inherit;
- height: inherit;
- content: '';
- position: absolute;
- background-color: $accent;
- mask-repeat: no-repeat;
- mask-position: center;
- mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
- mask-size: 48px;
- }
- }
-}
diff --git a/res/css/views/dialogs/_CreateSubspaceDialog.scss b/res/css/views/dialogs/_CreateSubspaceDialog.scss
index 1ed10df35c4..bcce6def89f 100644
--- a/res/css/views/dialogs/_CreateSubspaceDialog.scss
+++ b/res/css/views/dialogs/_CreateSubspaceDialog.scss
@@ -73,9 +73,5 @@ limitations under the License.
margin-left: 16px;
padding: 8px 36px;
}
-
- .mx_AccessibleButton_kind_link {
- padding: 0;
- }
}
}
diff --git a/res/css/views/dialogs/_DeactivateAccountDialog.scss b/res/css/views/dialogs/_DeactivateAccountDialog.scss
index 192917b2d00..8941afaf39c 100644
--- a/res/css/views/dialogs/_DeactivateAccountDialog.scss
+++ b/res/css/views/dialogs/_DeactivateAccountDialog.scss
@@ -18,10 +18,6 @@ limitations under the License.
margin-bottom: 30px;
}
-.mx_DeactivateAccountDialog .mx_DeactivateAccountDialog_input_section {
- margin-top: 60px;
-}
-
.mx_DeactivateAccountDialog .mx_DeactivateAccountDialog_input_section .mx_Field {
width: 300px;
}
diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss
index 37974d1358d..b1fed0d29bd 100644
--- a/res/css/views/dialogs/_DevtoolsDialog.scss
+++ b/res/css/views/dialogs/_DevtoolsDialog.scss
@@ -14,36 +14,39 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_DevTools_content {
- margin: 10px 0;
+.mx_DevtoolsDialog_wrapper {
+ .mx_Dialog {
+ height: 100%;
+ }
+
+ .mx_Dialog_fixedWidth {
+ overflow-y: hidden;
+ height: 100%;
+ }
}
-.mx_DevTools_ServersInRoomList_button {
- /* Set the cursor back to default as `.mx_Dialog button` sets it to pointer */
- cursor: default !important;
+.mx_DevTools_content {
+ overflow-y: auto;
+ height: calc(100% - 124px); // 58px for buttons + 50px for header + 8px margin around
}
.mx_DevTools_RoomStateExplorer_query {
margin-bottom: 10px;
}
-.mx_DevTools_RoomStateExplorer_button {
- font-family: monospace;
+.mx_DevTools_button {
+ font-family: monospace !important;
+ margin-bottom: 8px !important;
}
.mx_DevTools_RoomStateExplorer_button_hasSpaces {
text-decoration: underline;
}
-.mx_DevTools_RoomStateExplorer_button.mx_DevTools_RoomStateExplorer_button_emptyString {
+.mx_DevTools_button.mx_DevTools_RoomStateExplorer_button_emptyString {
font-style: italic;
}
-.mx_DevTools_RoomStateExplorer_button, .mx_DevTools_ServersInRoomList_button {
- margin-bottom: 10px;
- width: 100%;
-}
-
.mx_DevTools_label_left {
float: left;
}
@@ -83,108 +86,6 @@ limitations under the License.
margin-right: 42px;
}
-.mx_DevTools_tgl {
- display: none;
-
- // add default box-sizing for this scope
- &,
- &::after,
- &::before,
- & *,
- & *::after,
- & *::before,
- & + .mx_DevTools_tgl-btn {
- box-sizing: border-box;
- &::selection {
- background: none;
- }
- }
-
- + .mx_DevTools_tgl-btn {
- outline: 0;
- display: block;
- width: 7em;
- height: 2em;
- position: relative;
- cursor: pointer;
- user-select: none;
- &::after,
- &::before {
- position: relative;
- display: block;
- content: "";
- width: 50%;
- height: 100%;
- }
-
- &::after {
- left: 0;
- }
-
- &::before {
- display: none;
- }
- }
-
- &:checked + .mx_DevTools_tgl-btn::after {
- left: 50%;
- }
-}
-
-.mx_DevTools_tgl-flip {
- + .mx_DevTools_tgl-btn {
- padding: 2px;
- transition: all .2s ease;
- perspective: 100px;
- &::after,
- &::before {
- display: inline-block;
- transition: all .4s ease;
- width: 100%;
- text-align: center;
- position: absolute;
- line-height: 2em;
- font-weight: bold;
- color: #fff;
- top: 0;
- left: 0;
- backface-visibility: hidden;
- border-radius: 4px;
- }
-
- &::after {
- content: attr(data-tg-on);
- background: #02c66f;
- transform: rotateY(-180deg);
- }
-
- &::before {
- background: #ff3a19;
- content: attr(data-tg-off);
- }
-
- &:active::before {
- transform: rotateY(-20deg);
- }
- }
-
- &:checked + .mx_DevTools_tgl-btn {
- &::before {
- transform: rotateY(180deg);
- }
-
- &::after {
- transform: rotateY(0);
- left: 0;
- background: #7fc6a6;
- }
-
- &:active::after {
- transform: rotateY(20deg);
- }
- }
-}
-
.mx_DevTools_VerificationRequest {
border: 1px solid #cccccc;
border-radius: 3px;
diff --git a/res/css/views/dialogs/_EditCommunityPrototypeDialog.scss b/res/css/views/dialogs/_EditCommunityPrototypeDialog.scss
deleted file mode 100644
index 75a56bf6b37..00000000000
--- a/res/css/views/dialogs/_EditCommunityPrototypeDialog.scss
+++ /dev/null
@@ -1,77 +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.
-*/
-
-// XXX: many of these styles are shared with the create dialog
-.mx_EditCommunityPrototypeDialog {
- &.mx_Dialog_fixedWidth {
- width: 360px;
- }
-
- .mx_Dialog_content {
- margin-bottom: 12px;
-
- .mx_AccessibleButton.mx_AccessibleButton_kind_primary {
- display: block;
- height: 32px;
- font-size: $font-16px;
- line-height: 32px;
- }
-
- .mx_EditCommunityPrototypeDialog_rowAvatar {
- display: flex;
- flex-direction: row;
- align-items: center;
- }
-
- .mx_EditCommunityPrototypeDialog_avatarContainer {
- margin-top: 20px;
- margin-bottom: 20px;
-
- .mx_EditCommunityPrototypeDialog_avatar,
- .mx_EditCommunityPrototypeDialog_placeholderAvatar {
- width: 96px;
- height: 96px;
- border-radius: 96px;
- }
-
- .mx_EditCommunityPrototypeDialog_placeholderAvatar {
- background-color: #368bd6; // hardcoded for both themes
-
- &::before {
- display: inline-block;
- background-color: #fff; // hardcoded because the background is
- mask-repeat: no-repeat;
- mask-size: 96px;
- width: 96px;
- height: 96px;
- mask-position: center;
- content: '';
- vertical-align: middle;
- mask-image: url('$(res)/img/element-icons/add-photo.svg');
- }
- }
- }
-
- .mx_EditCommunityPrototypeDialog_tip {
- margin-left: 20px;
-
- & > b, & > span {
- display: block;
- color: $muted-fg-color;
- }
- }
- }
-}
diff --git a/res/css/views/dialogs/_FeedbackDialog.scss b/res/css/views/dialogs/_FeedbackDialog.scss
index 9e4cf5e8aae..8d11b83fe11 100644
--- a/res/css/views/dialogs/_FeedbackDialog.scss
+++ b/res/css/views/dialogs/_FeedbackDialog.scss
@@ -58,11 +58,6 @@ limitations under the License.
line-height: $font-15px;
}
- .mx_AccessibleButton_kind_link {
- padding: 0;
- font-size: inherit;
- }
-
a, .mx_AccessibleButton_kind_link {
color: $accent;
text-decoration: underline;
diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss
index 205aaaa2026..ad7bf9a8167 100644
--- a/res/css/views/dialogs/_ForwardDialog.scss
+++ b/res/css/views/dialogs/_ForwardDialog.scss
@@ -93,7 +93,7 @@ limitations under the License.
border-radius: 8px;
&:hover {
- background-color: $groupFilterPanel-bg-color;
+ background-color: $spacePanel-bg-color;
}
.mx_ForwardList_roomButton {
diff --git a/res/css/views/dialogs/_GenericFeatureFeedbackDialog.scss b/res/css/views/dialogs/_GenericFeatureFeedbackDialog.scss
index ab7496249dc..83f93495143 100644
--- a/res/css/views/dialogs/_GenericFeatureFeedbackDialog.scss
+++ b/res/css/views/dialogs/_GenericFeatureFeedbackDialog.scss
@@ -21,10 +21,4 @@ limitations under the License.
line-height: $font-20px;
margin-bottom: 24px;
}
-
- .mx_AccessibleButton_kind_link {
- padding: 0;
- font-size: inherit;
- line-height: inherit;
- }
}
diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss
index 546327ff40a..35fedd7cf28 100644
--- a/res/css/views/dialogs/_InviteDialog.scss
+++ b/res/css/views/dialogs/_InviteDialog.scss
@@ -43,7 +43,7 @@ limitations under the License.
min-width: max-content; // prevent manipulation by flexbox
}
- // Mostly copied from AddressPickerDialog; overrides bunch of our default text input styles
+ // overrides bunch of our default text input styles
> input[type="text"] {
margin: 6px 0 !important;
height: 24px;
@@ -97,11 +97,9 @@ limitations under the License.
color: $primary-content;
}
- .mx_InviteDialog_subname {
- margin-bottom: 10px;
- margin-top: -10px; // HACK: Positioning with margins is bad
- font-size: $font-12px;
- color: $muted-fg-color;
+ .mx_InviteDialog_section_showMore {
+ margin: 7px 18px;
+ display: block;
}
}
@@ -273,10 +271,25 @@ limitations under the License.
.mx_InviteDialog_other {
// Prevent the dialog from jumping around randomly when elements change.
height: 600px;
- padding-left: 20px; // the design wants some padding on the left
+
+ .mx_InviteDialog_addressBar {
+ margin-right: 0;
+ }
.mx_InviteDialog_userSections {
height: calc(100% - 115px); // mx_InviteDialog's height minus some for the upper and lower elements
+ padding-right: 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
+ }
}
}
@@ -432,3 +445,7 @@ limitations under the License.
}
}
}
+
+.mx_InviteDialog_identityServer {
+ margin-top: 1em;
+}
diff --git a/res/css/views/dialogs/_JoinRuleDropdown.scss b/res/css/views/dialogs/_JoinRuleDropdown.scss
index 19209e95361..b4d13909a77 100644
--- a/res/css/views/dialogs/_JoinRuleDropdown.scss
+++ b/res/css/views/dialogs/_JoinRuleDropdown.scss
@@ -60,7 +60,7 @@ limitations under the License.
}
.mx_JoinRuleDropdown_restricted::before {
- mask-image: url('$(res)/img/element-icons/community-members.svg');
+ mask-image: url('$(res)/img/element-icons/group-members.svg');
mask-size: contain;
}
}
diff --git a/res/css/views/dialogs/_LeaveSpaceDialog.scss b/res/css/views/dialogs/_LeaveSpaceDialog.scss
index baae73a90b9..aa20efe89a8 100644
--- a/res/css/views/dialogs/_LeaveSpaceDialog.scss
+++ b/res/css/views/dialogs/_LeaveSpaceDialog.scss
@@ -19,58 +19,49 @@ limitations under the License.
display: flex;
flex-direction: column;
padding: 24px 32px;
- }
-}
-.mx_LeaveSpaceDialog {
- width: 440px;
- display: flex;
- flex-direction: column;
- flex-wrap: nowrap;
- height: 520px;
+ .mx_LeaveSpaceDialog {
+ width: 440px;
+ display: flex;
+ flex-direction: column;
+ flex-wrap: nowrap;
+ height: 520px;
- .mx_Dialog_content {
- flex-grow: 1;
- margin: 0;
- overflow-y: auto;
+ .mx_Dialog_content {
+ flex-grow: 1;
+ margin: 0;
+ overflow-y: auto;
- .mx_LeaveSpaceDialog_section_warning {
- position: relative;
- border-radius: 8px;
- margin: 12px 0 0;
- padding: 12px 8px 12px 42px;
- background-color: $header-panel-bg-color;
+ .mx_LeaveSpaceDialog_section_warning {
+ position: relative;
+ border-radius: 8px;
+ margin: 12px 0 0;
+ padding: 12px 8px 12px 42px;
+ background-color: $header-panel-bg-color;
- font-size: $font-12px;
- line-height: $font-15px;
- color: $secondary-content;
+ font-size: $font-12px;
+ line-height: $font-15px;
+ color: $secondary-content;
- &::before {
- content: '';
- position: absolute;
- left: 10px;
- top: calc(50% - 8px); // vertical centering
- height: 16px;
- width: 16px;
- background-color: $secondary-content;
- mask-repeat: no-repeat;
- mask-size: contain;
- mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
- mask-position: center;
- }
- }
+ &::before {
+ content: '';
+ position: absolute;
+ left: 10px;
+ top: calc(50% - 8px); // vertical centering
+ height: 16px;
+ width: 16px;
+ background-color: $secondary-content;
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
+ mask-position: center;
+ }
+ }
- > p {
- color: $primary-content;
- }
- }
-
- .mx_Dialog_buttons {
- margin-top: 20px;
-
- .mx_Dialog_primary {
- background-color: $alert !important; // override default colour
- border-color: $alert;
+ > p {
+ color: $primary-content;
+ }
+ }
}
}
}
diff --git a/res/css/views/dialogs/_LocationViewDialog.scss b/res/css/views/dialogs/_LocationViewDialog.scss
index e7cdaf88007..600c3082657 100644
--- a/res/css/views/dialogs/_LocationViewDialog.scss
+++ b/res/css/views/dialogs/_LocationViewDialog.scss
@@ -48,49 +48,10 @@ limitations under the License.
background-color: $dialog-close-external-color;
}
}
+}
- .mx_MLocationBody {
- position: absolute;
-
- .mx_MLocationBody_map {
- width: 80vw;
- height: 80vh;
- }
-
- .mx_MLocationBody_zoomButtons {
- position: absolute;
- display: grid;
- grid-template-columns: auto;
- grid-row-gap: 8px;
-
- right: 24px;
- bottom: 48px;
-
- .mx_AccessibleButton {
- background-color: $background;
- box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.25);
- border-radius: 4px;
- width: 24px;
- height: 24px;
-
- .mx_MLocationBody_zoomButton {
- background-color: $primary-content;
- margin: 4px;
- width: 16px;
- height: 16px;
- mask-repeat: no-repeat;
- mask-size: contain;
- mask-position: center;
- }
-
- .mx_MLocationBody_plusButton {
- mask-image: url('$(res)/img/element-icons/plus-button.svg');
- }
-
- .mx_MLocationBody_minusButton {
- mask-image: url('$(res)/img/element-icons/minus-button.svg');
- }
- }
- }
- }
+.mx_LocationViewDialog_map {
+ width: 80vw;
+ height: 80vh;
+ border-radius: 8px;
}
diff --git a/res/css/views/dialogs/_RoomSettingsDialogBridges.scss b/res/css/views/dialogs/_RoomSettingsDialogBridges.scss
index f18b4917cf1..07735ad0278 100644
--- a/res/css/views/dialogs/_RoomSettingsDialogBridges.scss
+++ b/res/css/views/dialogs/_RoomSettingsDialogBridges.scss
@@ -22,84 +22,99 @@ limitations under the License.
margin: 0;
padding: 0;
}
-}
-.mx_RoomSettingsDialog_BridgeList li {
- list-style-type: none;
- padding: 5px;
- margin-bottom: 8px;
- border-width: 1px 1px;
- border-color: $primary-hairline-color;
- border-style: solid;
- border-radius: 5px;
+ li {
+ list-style-type: none;
- .column-icon {
- float: left;
- padding-right: 10px;
+ &.mx_RoomSettingsDialog_BridgeList_listItem {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $spacing-8;
+ padding: 5px;
+ margin-bottom: $spacing-8;
- * {
+ // border-style around each bridge list item
+ border-width: 1px 1px;
+ border-color: $primary-hairline-color;
+ border-style: solid;
border-radius: 5px;
- border: 1px solid $input-darker-bg-color;
- }
-
- .noProtocolIcon {
- width: 48px;
- height: 48px;
- background: $input-darker-bg-color;
- border-radius: 5px;
- }
- .protocol-icon {
- float: left;
- margin-right: 5px;
- img {
- border-radius: 5px;
- border-width: 1px 1px;
- border-color: $primary-hairline-color;
+ .mx_RoomSettingsDialog_column_icon {
+ .mx_RoomSettingsDialog_protocolIcon,
+ .mx_RoomSettingsDialog_protocolIcon span,
+ .mx_RoomSettingsDialog_noProtocolIcon {
+ box-sizing: border-box;
+ border-radius: 5px;
+ border: 1px solid $input-darker-bg-color;
+ }
+
+ .mx_RoomSettingsDialog_noProtocolIcon,
+ .mx_RoomSettingsDialog_protocolIcon img {
+ border-radius: 5px;
+ }
+
+ .mx_RoomSettingsDialog_noProtocolIcon {
+ width: 48px;
+ height: 48px;
+ background: $input-darker-bg-color;
+ }
+
+ .mx_RoomSettingsDialog_protocolIcon {
+ img {
+ border-width: 1px 1px;
+ border-color: $primary-hairline-color;
+ }
+
+ span {
+ /* Correct letter placement */
+ left: auto;
+ }
+ }
}
- span {
- /* Correct letter placement */
- left: auto;
- }
- }
- }
-
- .column-data {
- display: inline-block;
- width: 85%;
-
- > h3 {
- margin-top: 0px;
- margin-bottom: 0px;
- font-size: 16pt;
- color: $primary-content;
- }
-
- > * {
- margin-top: 4px;
- margin-bottom: 0;
- }
-
- .workspace-channel-details {
- color: $primary-content;
- font-weight: 600;
-
- .channel {
- margin-left: 5px;
- }
- }
- .metadata {
- color: $muted-fg-color;
- margin-bottom: 0;
- overflow-y: visible;
- text-overflow: ellipsis;
- white-space: normal;
- padding: 0;
-
- > li {
- padding: 0;
- border: 0;
+ .mx_RoomSettingsDialog_column_data {
+ display: inline-block;
+ width: 85%;
+
+ .mx_RoomSettingsDialog_column_data_details,
+ .mx_RoomSettingsDialog_column_data_metadata,
+ .mx_RoomSettingsDialog_column_data_metadata li,
+ .mx_RoomSettingsDialog_column_data_protocolName {
+ margin-bottom: 0;
+ }
+
+ .mx_RoomSettingsDialog_column_data_details,
+ .mx_RoomSettingsDialog_column_data_metadata {
+ margin-top: $spacing-4;
+ }
+
+ .mx_RoomSettingsDialog_column_data_metadata li {
+ margin-top: $spacing-8;
+ }
+
+ .mx_RoomSettingsDialog_column_data_protocolName {
+ margin-top: 0;
+ font-size: 16pt;
+ color: $primary-content;
+ }
+
+ .mx_RoomSettingsDialog_workspace_channel_details {
+ color: $primary-content;
+ font-weight: $font-semi-bold;
+
+ .mx_RoomSettingsDialog_channel {
+ margin-inline-start: 5px;
+ }
+ }
+
+ .mx_RoomSettingsDialog_metadata {
+ color: $muted-fg-color;
+ margin-bottom: 0;
+ overflow-y: visible;
+ text-overflow: ellipsis;
+ white-space: normal;
+ padding: 0;
+ }
}
}
}
diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.scss b/res/css/views/dialogs/_SpaceSettingsDialog.scss
index 8f064f6b380..ead082dba68 100644
--- a/res/css/views/dialogs/_SpaceSettingsDialog.scss
+++ b/res/css/views/dialogs/_SpaceSettingsDialog.scss
@@ -76,11 +76,13 @@ limitations under the License.
}
.mx_AccessibleButton_hasKind {
- padding: 8px 22px;
+ &.mx_AccessibleButton_kind_link {
+ font-size: $font-14px;
+ margin: 7px 18px;
- &.mx_SettingsTab_showAdvanced {
- margin: 16px 0;
- padding: 0;
+ &.mx_SettingsTab_showAdvanced {
+ margin: 18px 0;
+ }
}
}
diff --git a/res/css/views/dialogs/_SpotlightDialog.scss b/res/css/views/dialogs/_SpotlightDialog.scss
index 31358ad8a3b..9f78d905b52 100644
--- a/res/css/views/dialogs/_SpotlightDialog.scss
+++ b/res/css/views/dialogs/_SpotlightDialog.scss
@@ -100,6 +100,7 @@ limitations under the License.
display: flex;
white-space: nowrap;
overflow-x: hidden;
+ margin-right: 1px; // occlude the 1px visible of the very next tile to prevent it looking broken
}
.mx_AccessibleButton {
@@ -109,15 +110,16 @@ limitations under the License.
font-size: $font-12px;
line-height: $font-15px;
display: inline-block;
- width: 50px;
- min-width: 50px;
+ width: 58px;
+ height: 58px;
+ min-width: 58px;
box-sizing: border-box;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
.mx_DecoratedRoomAvatar {
- margin: 0 5px 4px; // maintain centering
+ margin: 0 9px 4px; // maintain centering
}
& + .mx_AccessibleButton {
@@ -150,8 +152,8 @@ limitations under the License.
> .mx_DecoratedRoomAvatar,
> .mx_BaseAvatar {
margin-right: 8px;
- width: 20px;
- height: 20px;
+ width: 24px;
+ height: 24px;
.mx_BaseAvatar {
width: inherit;
@@ -186,8 +188,8 @@ limitations under the License.
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
- width: 20px;
- height: 20px;
+ width: 24px;
+ height: 24px;
position: absolute;
left: 4px;
top: 50%;
@@ -214,9 +216,8 @@ limitations under the License.
.mx_SpotlightDialog_otherSearches_messageSearchIcon {
display: inline-block;
- margin-left: 8px;
- width: 20px;
- height: 20px;
+ width: 24px;
+ height: 24px;
background-color: $secondary-content;
vertical-align: text-bottom;
mask-repeat: no-repeat;
@@ -240,7 +241,6 @@ limitations under the License.
.mx_SpotlightDialog_recentSearches > h4 > .mx_AccessibleButton_kind_link {
padding: 0;
float: right;
- font-weight: normal;
font-size: $font-12px;
line-height: $font-15px;
color: $secondary-content;
diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss
index b013b4998af..79cacb5c507 100644
--- a/res/css/views/dialogs/_UserSettingsDialog.scss
+++ b/res/css/views/dialogs/_UserSettingsDialog.scss
@@ -60,7 +60,3 @@ limitations under the License.
.mx_UserSettingsDialog_mjolnirIcon::before {
mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg');
}
-
-.mx_UserSettingsDialog_flairIcon::before {
- mask-image: url('$(res)/img/element-icons/settings/flair.svg');
-}
diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss
index e8bde18232a..609c022f855 100644
--- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss
+++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss
@@ -18,6 +18,8 @@ limitations under the License.
position: relative;
padding-left: 24px; // 16px icon + 8px padding
margin-top: 7px; // vertical alignment to buttons
+ margin-bottom: 7px; // space between the buttons and the text when float is activated
+ text-align: left;
&::before {
content: "";
diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss
index bf5440d6829..021e8ffc8c1 100644
--- a/res/css/views/elements/_AccessibleButton.scss
+++ b/res/css/views/elements/_AccessibleButton.scss
@@ -16,152 +16,159 @@ limitations under the License.
.mx_AccessibleButton {
cursor: pointer;
-}
-
-.mx_AccessibleButton_disabled {
- cursor: default;
-}
-
-.mx_AccessibleButton_hasKind {
- padding: 7px 18px;
- text-align: center;
- border-radius: 8px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- font-size: $font-14px;
- border: none; // override default styles
-}
-
-.mx_AccessibleButton_kind_primary {
- color: $button-primary-fg-color;
- background-color: $accent;
- border: 1px solid $accent; // account for size loss of no border
- font-weight: 600;
-}
-
-.mx_AccessibleButton_kind_primary_outline {
- color: $accent;
- background-color: $button-secondary-bg-color;
- border: 1px solid $accent;
- font-weight: 600;
-}
-
-.mx_AccessibleButton_kind_secondary {
- color: $accent;
- font-weight: 600;
-}
-.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled,
-.mx_AccessibleButton_kind_primary_outline.mx_AccessibleButton_disabled {
- opacity: 0.4;
-}
-
-.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_primary_sm {
- padding: 5px 12px;
- color: $button-primary-fg-color;
- background-color: $accent;
-}
-
-.mx_AccessibleButton_kind_primary_sm.mx_AccessibleButton_disabled {
- opacity: 0.4;
-}
-
-.mx_AccessibleButton_kind_danger {
- color: $button-danger-fg-color;
- background-color: $alert;
-}
-
-.mx_AccessibleButton_kind_danger_outline {
- color: $alert;
- background-color: transparent;
- border: 1px solid $alert;
-}
+ &.mx_AccessibleButton_disabled {
+ cursor: default;
+
+ &.mx_AccessibleButton_kind_primary,
+ &.mx_AccessibleButton_kind_primary_outline,
+ &.mx_AccessibleButton_kind_primary_sm,
+ &.mx_AccessibleButton_kind_link,
+ &.mx_AccessibleButton_kind_link_inline,
+ &.mx_AccessibleButton_kind_link_sm {
+ opacity: 0.4;
+ }
+ }
-.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled {
- color: $button-danger-disabled-fg-color;
- background-color: $button-danger-disabled-bg-color;
-}
+ &.mx_AccessibleButton_hasKind {
+ padding: 7px 18px;
+ text-align: center;
+ border-radius: 8px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: $font-14px;
+ border: none; // override default styles
+ word-break: keep-all; // prevent button text in Chinese/Japanese/Korean (CJK) from being collapsed
+
+ &.mx_AccessibleButton_kind_primary_sm,
+ &.mx_AccessibleButton_kind_danger_sm,
+ &.mx_AccessibleButton_kind_link_sm {
+ padding: 5px 12px;
+ }
+
+ &.mx_AccessibleButton_kind_primary_sm {
+ color: $button-primary-fg-color;
+ background-color: $accent;
+ }
+
+ &.mx_AccessibleButton_kind_danger_sm {
+ color: $button-danger-fg-color;
+ background-color: $alert;
+ }
+
+ &.mx_AccessibleButton_kind_link_sm {
+ color: $accent;
+ }
+
+ &.mx_AccessibleButton_kind_confirm_sm {
+ background-color: $accent;
+
+ &::before {
+ mask-image: url('$(res)/img/feather-customised/check.svg');
+ }
+ }
+
+ &.mx_AccessibleButton_kind_cancel_sm {
+ background-color: $alert;
+
+ &::before {
+ mask-image: url('$(res)/img/feather-customised/x.svg');
+ }
+ }
+ }
-.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled {
- color: $button-danger-disabled-bg-color;
- border-color: $button-danger-disabled-bg-color;
-}
+ &.mx_AccessibleButton_kind_primary,
+ &.mx_AccessibleButton_kind_primary_outline,
+ &.mx_AccessibleButton_kind_secondary {
+ font-weight: 600;
+ }
-.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_danger_sm {
- padding: 5px 12px;
- color: $button-danger-fg-color;
- background-color: $alert;
-}
+ &.mx_AccessibleButton_kind_primary,
+ &.mx_AccessibleButton_kind_primary_outline {
+ border: 1px solid $accent;
+ }
-.mx_AccessibleButton_kind_danger_sm.mx_AccessibleButton_disabled {
- color: $button-danger-disabled-fg-color;
- background-color: $button-danger-disabled-bg-color;
-}
+ &.mx_AccessibleButton_kind_primary {
+ color: $button-primary-fg-color;
+ background-color: $accent;
+ }
-.mx_AccessibleButton_kind_link {
- color: $accent;
-}
+ &.mx_AccessibleButton_kind_primary_outline {
+ color: $accent;
+ background-color: $button-secondary-bg-color;
+ }
-.mx_AccessibleButton_kind_link.mx_AccessibleButton_disabled {
- opacity: 0.4;
-}
+ &.mx_AccessibleButton_kind_secondary {
+ color: $accent;
+ }
-.mx_AccessibleButton_kind_link_inline {
- color: $accent;
- font-size: inherit;
- padding: 0 2px;
-}
+ &.mx_AccessibleButton_kind_danger {
+ color: $button-danger-fg-color;
+ background-color: $alert;
-.mx_AccessibleButton_kind_link_inline.mx_AccessibleButton_disabled {
- opacity: 0.4;
-}
+ &.mx_AccessibleButton_disabled {
+ color: $button-danger-disabled-fg-color;
+ background-color: $button-danger-disabled-bg-color;
+ }
+ }
-.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_link_sm {
- padding: 5px 12px;
- color: $accent;
-}
+ &.mx_AccessibleButton_kind_danger_outline {
+ color: $alert;
+ background-color: transparent;
+ border: 1px solid $alert;
-.mx_AccessibleButton_kind_link_sm.mx_AccessibleButton_disabled {
- opacity: 0.4;
-}
+ &.mx_AccessibleButton_disabled {
+ color: $button-danger-disabled-bg-color;
+ border-color: $button-danger-disabled-bg-color;
+ }
+ }
-.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_confirm_sm {
- background-color: $accent;
+ &.mx_AccessibleButton_kind_danger_sm {
+ &.mx_AccessibleButton_disabled {
+ color: $button-danger-disabled-fg-color;
+ background-color: $button-danger-disabled-bg-color;
+ }
+ }
- &::before {
- mask-image: url('$(res)/img/feather-customised/check.svg');
+ &.mx_AccessibleButton_kind_link,
+ &.mx_AccessibleButton_kind_link_inline {
+ color: $accent;
+ font-size: inherit;
+ font-weight: normal;
+ line-height: inherit;
}
-}
-.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_cancel_sm {
- background-color: $alert;
+ &.mx_AccessibleButton_kind_link {
+ padding: 0;
+ }
- &::before {
- mask-image: url('$(res)/img/feather-customised/x.svg');
+ &.mx_AccessibleButton_kind_link_inline {
+ display: inline;
+ padding: 0 2px;
}
-}
-.mx_AccessibleButton_kind_confirm_sm,
-.mx_AccessibleButton_kind_cancel_sm {
- padding: 0px;
- width: 16px;
- height: 16px;
- border-radius: 100%;
- position: relative;
- display: block;
-
- &::before {
- content: "";
+ &.mx_AccessibleButton_kind_confirm_sm,
+ &.mx_AccessibleButton_kind_cancel_sm {
+ padding: 0px;
+ width: 16px;
+ height: 16px;
+ border-radius: 100%;
+ position: relative;
display: block;
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- background-color: #ffffff;
- mask-repeat: no-repeat;
- mask-position: center;
- mask-size: 80%;
+
+ &::before {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: #ffffff;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: 80%;
+ }
}
}
diff --git a/res/css/views/elements/_CopyableText.scss b/res/css/views/elements/_CopyableText.scss
index a08306b66a4..ceafd422730 100644
--- a/res/css/views/elements/_CopyableText.scss
+++ b/res/css/views/elements/_CopyableText.scss
@@ -18,14 +18,17 @@ limitations under the License.
.mx_CopyableText {
display: flex;
justify-content: space-between;
- border-radius: 5px;
- border: solid 1px $light-fg-color;
- margin-bottom: 10px;
- margin-top: 10px;
- padding: 10px;
width: max-content;
max-width: 100%;
+ &.mx_CopyableText_border {
+ border-radius: 5px;
+ border: solid 1px $light-fg-color;
+ margin-bottom: 10px;
+ margin-top: 10px;
+ padding: 10px;
+ }
+
.mx_CopyableText_copyButton {
flex-shrink: 0;
width: 20px;
diff --git a/res/css/views/elements/_FacePile.scss b/res/css/views/elements/_FacePile.scss
index 875e0e34d57..90f1c590a14 100644
--- a/res/css/views/elements/_FacePile.scss
+++ b/res/css/views/elements/_FacePile.scss
@@ -20,7 +20,8 @@ limitations under the License.
flex-direction: row-reverse;
vertical-align: middle;
- > .mx_FacePile_face + .mx_FacePile_face {
+ // Overlap the children
+ > * + * {
margin-right: -8px;
}
@@ -37,7 +38,7 @@ limitations under the License.
border-radius: 100%;
width: 30px;
height: 30px;
- background-color: $groupFilterPanel-bg-color;
+ background-color: $spacePanel-bg-color;
&::before {
content: "";
diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss
index 852097762ed..4615f5da23f 100644
--- a/res/css/views/elements/_RichText.scss
+++ b/res/css/views/elements/_RichText.scss
@@ -4,7 +4,6 @@
.mx_UserPill,
.mx_RoomPill,
-.mx_GroupPill,
.mx_AtRoomPill {
display: inline-flex;
align-items: center;
@@ -28,13 +27,6 @@ a.mx_Pill {
line-height: $font-17px;
}
-/* More specific to override `.markdown-body a` color */
-.mx_EventTile_content .markdown-body a.mx_GroupPill,
-.mx_GroupPill {
- color: $accent-fg-color;
- background-color: $rte-room-pill-color;
-}
-
/* More specific to override `.markdown-body a` text-decoration */
.mx_EventTile_content .markdown-body a.mx_Pill {
text-decoration: none;
@@ -62,22 +54,18 @@ a.mx_Pill {
/* More specific to override `.markdown-body a` color */
.mx_EventTile_content .markdown-body a.mx_RoomPill,
-.mx_EventTile_content .markdown-body a.mx_GroupPill,
-.mx_RoomPill,
-.mx_GroupPill {
+.mx_RoomPill {
color: $accent-fg-color;
background-color: $rte-room-pill-color;
}
.mx_EventTile_body .mx_UserPill,
-.mx_EventTile_body .mx_RoomPill,
-.mx_EventTile_body .mx_GroupPill {
+.mx_EventTile_body .mx_RoomPill {
cursor: pointer;
}
.mx_UserPill .mx_BaseAvatar,
.mx_RoomPill .mx_BaseAvatar,
-.mx_GroupPill .mx_BaseAvatar,
.mx_AtRoomPill .mx_BaseAvatar {
position: relative;
display: inline-flex;
@@ -88,7 +76,9 @@ a.mx_Pill {
}
.mx_Emoji {
- font-size: 1.8rem;
+ // Should be 1.8rem for our default 1.4rem message bodies,
+ // and scale with the size of the surrounding text
+ font-size: calc(18 / 14 * 1em);
vertical-align: bottom;
}
diff --git a/res/css/views/elements/_SettingsFlag.scss b/res/css/views/elements/_SettingsFlag.scss
index 533487d98cf..c6f4cf6ec5c 100644
--- a/res/css/views/elements/_SettingsFlag.scss
+++ b/res/css/views/elements/_SettingsFlag.scss
@@ -41,4 +41,10 @@ limitations under the License.
font-size: $font-12px;
line-height: $font-15px;
color: $secondary-content;
+
+ // Support code/pre elements in settings flag descriptions
+ pre, code {
+ font-family: $monospace-font-family !important;
+ background-color: $rte-code-bg-color;
+ }
}
diff --git a/res/css/views/elements/_TagComposer.scss b/res/css/views/elements/_TagComposer.scss
index f5bdb8d2d58..7efe83281f5 100644
--- a/res/css/views/elements/_TagComposer.scss
+++ b/res/css/views/elements/_TagComposer.scss
@@ -29,7 +29,9 @@ limitations under the License.
margin-left: 16px; // distance from
}
- .mx_Field, .mx_Field input, .mx_AccessibleButton {
+ .mx_Field,
+ .mx_Field input,
+ .mx_AccessibleButton {
// So they look related to each other by feeling the same
border-radius: 8px;
}
@@ -39,39 +41,48 @@ limitations under the License.
display: flex;
flex-wrap: wrap;
margin-top: 12px; // this plus 12px from the tags makes 24px from the input
+ }
- .mx_TagComposer_tag {
- padding: 6px 8px 8px 12px;
- position: relative;
- margin-right: 12px;
- margin-top: 12px;
-
- // Cheaty way to get an opacified variable colour background
- &::before {
- content: '';
- border-radius: 20px;
- background-color: $tertiary-content;
- opacity: 0.15;
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
-
- // Pass through the pointer otherwise we have effectively put a whole div
- // on top of the component, which makes it hard to interact with buttons.
- pointer-events: none;
- }
- }
+ .mx_Tag {
+ margin-right: 12px;
+ margin-top: 12px;
+ }
+}
- .mx_AccessibleButton {
- background-image: url('$(res)/img/subtract.svg');
- width: 16px;
- height: 16px;
- margin-left: 8px;
- display: inline-block;
+.mx_Tag {
+
+ font-size: $font-15px;
+
+ display: inline-flex;
+ align-items: center;
+
+ gap: 8px;
+ padding: 8px;
+ border-radius: 8px;
+
+ color: $primary-content;
+ background: $quinary-content;
+
+ >svg:first-child {
+ width: 1em;
+ color: $secondary-content;
+ transform: scale(1.25);
+ transform-origin: center;
+ }
+
+ .mx_Tag_delete {
+ border-radius: 50%;
+ text-align: center;
+ width: 1.066666em; // 16px;
+ height: 1.066666em;
+ line-height: 1em;
+ color: $secondary-content;
+ background: $system;
+ position: relative;
+
+ svg {
+ width: .5em;
vertical-align: middle;
- cursor: pointer;
}
}
}
diff --git a/res/css/views/elements/_Tooltip.scss b/res/css/views/elements/_Tooltip.scss
index 93ee6912b4f..c89c654178f 100644
--- a/res/css/views/elements/_Tooltip.scss
+++ b/res/css/views/elements/_Tooltip.scss
@@ -16,13 +16,23 @@ limitations under the License.
*/
@keyframes mx_fadein {
- from { opacity: 0; }
- to { opacity: 1; }
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
}
@keyframes mx_fadeout {
- from { opacity: 1; }
- to { opacity: 0; }
+ from {
+ opacity: 1;
+ }
+
+ to {
+ opacity: 0;
+ }
}
.mx_Tooltip_chevron {
@@ -58,7 +68,7 @@ limitations under the License.
line-height: $font-14px;
font-size: $font-12px;
font-weight: 500;
- max-width: 200px;
+ max-width: 300px;
word-break: break-word;
margin-left: 6px;
margin-right: 6px;
diff --git a/res/css/views/location/_LocationPicker.scss b/res/css/views/location/_LocationPicker.scss
index 76e56eedd9f..9b513a1fb59 100644
--- a/res/css/views/location/_LocationPicker.scss
+++ b/res/css/views/location/_LocationPicker.scss
@@ -19,21 +19,33 @@ limitations under the License.
height: 100%;
position: relative;
+ display: flex;
+ flex-direction: column;
+
+ // when there are errors loading the map
+ // the canvas is still inserted
+ // and can overlap error message/close buttons
+ // hide it
+ &.mx_LocationPicker_hasError {
+ .maplibregl-canvas-container, .maplibregl-control-container {
+ display: none;
+ }
+ }
#mx_LocationPicker_map {
- height: 100%;
- border-radius: 8px;
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+ flex: 1;
+
+ .maplibregl-ctrl.maplibregl-ctrl-group,
+ .maplibregl-ctrl.maplibregl-ctrl-attrib {
+ margin-right: $spacing-16;
+ }
.maplibregl-ctrl.maplibregl-ctrl-group {
// place below the close button
// padding-16 + 24px close button + padding-10
margin-top: 50px;
- margin-right: $spacing-16;
- }
-
- .maplibregl-ctrl-bottom-right {
- bottom: 68px;
- margin-right: $spacing-16;
}
.maplibregl-user-location-accuracy-circle {
@@ -43,63 +55,45 @@ limitations under the License.
.maplibregl-user-location-dot {
display: none;
}
-
- .mx_MLocationBody_markerBorder {
- width: 31px;
- height: 31px;
- border-radius: 50%;
- background-color: $accent;
- filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
-
- .mx_BaseAvatar {
- margin-top: 2px;
- margin-left: 2px;
- }
- }
-
- .mx_MLocationBody_pointer {
- position: absolute;
- bottom: -3px;
- left: 11px;
- width: 9px;
- height: 5px;
-
- &::before {
- mask-image: url('$(res)/img/location/pointer.svg');
- mask-position: center;
- mask-repeat: no-repeat;
- mask-size: 9px;
- content: '';
- display: inline-block;
- width: 9px;
- height: 5px;
- position: absolute;
- background-color: $accent;
- }
- }
}
.mx_LocationPicker_footer {
- position: absolute;
- bottom: 0px;
+ flex: 0;
width: 100%;
+ box-sizing: border-box;
+ padding: $spacing-16;
+ display: flex;
+ flex-direction: column;
+ justify-content: stretch;
- .mx_Dialog_buttons {
- text-align: center;
-
- /* Note the `button` prefix and `not()` clauses are needed to make
- these selectors more specific than those in _common.scss. */
+ border-bottom-left-radius: inherit;
+ border-bottom-right-radius: inherit;
- button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton) {
- margin: 0px 0px 16px 0px;
- min-width: 328px;
- min-height: 48px;
- }
- }
+ background-color: $header-panel-bg-color;
}
+}
+
+.mx_LocationPicker_pinText {
+ position: absolute;
+ top: $spacing-16;
+ width: 100%;
+ box-sizing: border-box;
+ text-align: center;
+ height: 0;
+ pointer-events: none;
+
+ span {
+ box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.15);
+ border-radius: 8px;
+ padding: $spacing-8;
+ background-color: $background;
+ color: $primary-content;
- .mx_LocationPicker_error {
- color: red;
- margin: auto;
+ font-size: $font-12px;
}
}
+
+.mx_LocationPicker_submitButton {
+ width: 100%;
+ height: 48px;
+}
diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss
index 2587e73e50d..b0b117efded 100644
--- a/res/css/views/messages/_CallEvent.scss
+++ b/res/css/views/messages/_CallEvent.scss
@@ -19,22 +19,24 @@ limitations under the License.
width: 100%;
.mx_CallEvent {
- position: relative;
display: flex;
flex-direction: row;
+ flex-wrap: wrap;
align-items: center;
justify-content: space-between;
+ gap: $spacing-4 0;
+ position: relative;
+ margin: $spacing-4 0;
+ padding: $spacing-12 $spacing-24;
+ box-sizing: border-box;
background-color: $dark-panel-bg-color;
border-radius: 8px;
width: 65%;
- box-sizing: border-box;
- height: 60px;
- margin: 4px 0;
+ height: fit-content;
.mx_CallEvent_iconButton {
display: inline-flex;
- margin-right: 8px;
&::before {
content: '';
@@ -62,6 +64,13 @@ limitations under the License.
.mx_CallEvent_content_button_answer span::before {
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
}
+
+ &.mx_CallEvent_rejected,
+ &.mx_CallEvent_noAnswer {
+ .mx_CallEvent_type_icon::before {
+ mask-image: url('$(res)/img/voip/declined-voice.svg');
+ }
+ }
}
&.mx_CallEvent_video {
@@ -70,44 +79,49 @@ limitations under the License.
.mx_CallEvent_content_button_answer span::before {
mask-image: url('$(res)/img/element-icons/call/video-call.svg');
}
- }
-
- &.mx_CallEvent_voice.mx_CallEvent_missed .mx_CallEvent_type_icon::before {
- mask-image: url('$(res)/img/voip/missed-voice.svg');
- }
- &.mx_CallEvent_video.mx_CallEvent_missed .mx_CallEvent_type_icon::before {
- mask-image: url('$(res)/img/voip/missed-video.svg');
+ &.mx_CallEvent_rejected,
+ &.mx_CallEvent_noAnswer {
+ .mx_CallEvent_type_icon::before {
+ mask-image: url('$(res)/img/voip/declined-video.svg');
+ }
+ }
}
- &.mx_CallEvent_voice.mx_CallEvent_rejected .mx_CallEvent_type_icon::before,
- &.mx_CallEvent_voice.mx_CallEvent_noAnswer .mx_CallEvent_type_icon::before {
- mask-image: url('$(res)/img/voip/declined-voice.svg');
- }
+ &.mx_CallEvent_missed {
+ &.mx_CallEvent_voice {
+ .mx_CallEvent_type_icon::before {
+ mask-image: url('$(res)/img/voip/missed-voice.svg');
+ }
+ }
- &.mx_CallEvent_video.mx_CallEvent_rejected .mx_CallEvent_type_icon::before,
- &.mx_CallEvent_video.mx_CallEvent_noAnswer .mx_CallEvent_type_icon::before {
- mask-image: url('$(res)/img/voip/declined-video.svg');
+ &.mx_CallEvent_video {
+ .mx_CallEvent_type_icon::before {
+ mask-image: url('$(res)/img/voip/missed-video.svg');
+ }
+ }
}
.mx_CallEvent_info {
display: flex;
flex-direction: row;
align-items: center;
- margin-left: 12px;
- min-width: 0;
+ width: fit-content;
+ max-width: 100%;
.mx_CallEvent_info_basic {
display: flex;
flex-direction: column;
+ gap: $spacing-4;
margin-left: 10px; // To match mx_CallEvent
+ margin-right: 10px;
min-width: 0;
.mx_CallEvent_sender {
font-weight: 600;
font-size: 1.5rem;
line-height: 1.8rem;
- margin-bottom: 3px;
+ margin-bottom: $spacing-4;
overflow: hidden;
white-space: nowrap;
@@ -115,12 +129,12 @@ limitations under the License.
}
.mx_CallEvent_type {
+ display: flex;
+ align-items: center;
font-weight: 400;
color: $secondary-content;
font-size: 1.2rem;
line-height: $font-13px;
- display: flex;
- align-items: center;
.mx_CallEvent_type_icon {
height: 13px;
@@ -143,39 +157,30 @@ limitations under the License.
.mx_CallEvent_content {
display: flex;
- flex-direction: row;
+ flex-wrap: wrap;
align-items: center;
color: $secondary-content;
- margin-right: 16px;
- gap: 8px;
- min-width: max-content;
+ gap: $spacing-12; // See mx_IncomingCallToast_buttons
+ margin-inline-start: 42px; // avatar (32px) + mx_CallEvent_info_basic margin (10px)
+ word-break: break-word;
+ max-width: fit-content;
.mx_CallEvent_content_button {
- padding: 0px 12px;
-
- span {
- padding: 1px 0;
- display: flex;
- align-items: center;
-
- &::before {
- content: '';
- display: inline-block;
- background-color: $button-fg-color;
- mask-position: center;
- mask-repeat: no-repeat;
- mask-size: 16px;
- width: 16px;
- height: 16px;
- margin-right: 8px;
-
- flex-shrink: 0;
- }
+ @mixin CallButton;
+ padding: 0 $spacing-12;
+
+ span::before {
+ mask-size: 16px;
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
}
}
- .mx_CallEvent_content_button_reject span::before {
- mask-image: url('$(res)/img/element-icons/call/hangup.svg');
+ .mx_CallEvent_content_button_reject {
+ span::before {
+ mask-image: url('$(res)/img/element-icons/call/hangup.svg');
+ }
}
.mx_CallEvent_content_tooltip {
@@ -183,16 +188,12 @@ limitations under the License.
}
}
- .mx_MessageTimestamp {
- margin-left: 16px;
- }
-
&.mx_CallEvent_narrow {
- height: unset;
- width: 290px;
flex-direction: column;
align-items: unset;
- gap: 16px;
+ gap: $spacing-4 $spacing-16;
+ height: unset;
+ min-width: 290px;
.mx_CallEvent_iconButton {
position: absolute;
@@ -206,18 +207,36 @@ limitations under the License.
.mx_CallEvent_info {
align-items: unset;
- margin-top: 12px;
- margin-right: 12px;
-
- .mx_CallEvent_sender {
- margin-bottom: 8px;
- }
}
+ }
+ }
+}
+
+.mx_EventTile[data-layout="bubble"] {
+ .mx_EventTile_e2eIcon + .mx_CallEvent_wrapper {
+ .mx_CallEvent {
+ position: relative;
- .mx_CallEvent_content {
- margin-left: 54px; // mx_CallEvent margin (12px) + avatar (32px) + mx_CallEvent_info_basic margin (10px)
- margin-bottom: 16px;
+ // 5px (gap) + 14px (e2e icon size * mask-size) + 9px (margin-left of e2e icon)
+ right: calc(5px + 14px + 9px);
+ }
+ }
+}
+
+.mx_EventTile_leftAlignedBubble {
+ .mx_CallEvent_wrapper {
+ .mx_CallEvent {
+ &.mx_CallEvent_narrow {
+ gap: $spacing-8 $spacing-4;
}
}
}
}
+
+.mx_IRCLayout {
+ .mx_CallEvent_wrapper {
+ .mx_CallEvent {
+ margin-inline-start: $spacing-4; // display green line
+ }
+ }
+}
diff --git a/res/css/structures/_CreateRoom.scss b/res/css/views/messages/_DisambiguatedProfile.scss
similarity index 58%
rename from res/css/structures/_CreateRoom.scss
rename to res/css/views/messages/_DisambiguatedProfile.scss
index 78e6881b108..caef2fa4ad3 100644
--- a/res/css/structures/_CreateRoom.scss
+++ b/res/css/views/messages/_DisambiguatedProfile.scss
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2021 Šimon Brandner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,23 +15,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_CreateRoom {
- width: 960px;
- margin-left: auto;
- margin-right: auto;
- color: $primary-content;
-}
+.mx_DisambiguatedProfile {
+ overflow: hidden;
+ text-overflow: ellipsis;
-.mx_CreateRoom input,
-.mx_CreateRoom textarea {
- border-radius: 3px;
- border: 1px solid $strong-input-border-color;
- font-weight: 300;
- font-size: $font-13px;
- padding: 9px;
- margin-top: 6px;
-}
+ .mx_DisambiguatedProfile_displayName {
+ font-weight: 600;
+ }
-.mx_CreateRoom_description {
- width: 330px;
+ .mx_DisambiguatedProfile_mxid {
+ font-weight: 600;
+ font-size: 1.1rem;
+ margin-left: 5px;
+ opacity: 0.5; // Match mx_TextualEvent
+ color: $primary-content;
+ }
}
diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss
index e155afcbf39..eb3d287eaab 100644
--- a/res/css/views/messages/_MImageBody.scss
+++ b/res/css/views/messages/_MImageBody.scss
@@ -17,40 +17,48 @@ limitations under the License.
$timeline-image-border-radius: 8px;
-.mx_MImageBody_thumbnail--blurhash {
+.mx_MImageBody_placeholder {
+ // Position the placeholder on top of the thumbnail, so that the reveal animation can work
position: absolute;
left: 0;
top: 0;
-}
-
-.mx_MImageBody_thumbnail {
- object-fit: contain;
- border-radius: $timeline-image-border-radius;
-
- display: flex;
- justify-content: center;
- align-items: center;
height: 100%;
width: 100%;
- // this is needed so that the Blurhash can get have rounded corners without beeing the correct size during loading.
- overflow: hidden;
+ background-color: $background;
+
.mx_Blurhash > canvas {
animation: mx--anim-pulse 1.75s infinite cubic-bezier(.4, 0, .6, 1);
}
-
- .mx_no-image-placeholder {
- background-color: $primary-content;
- }
}
.mx_MImageBody_thumbnail_container {
- // Prevent the padding-bottom (added inline in MImageBody.js) from
- // affecting elements below the container.
+ border-radius: $timeline-image-border-radius;
+
+ // Necessary for the border radius to apply correctly to the placeholder
overflow: hidden;
+ contain: paint;
+
+ min-height: $font-44px;
+ min-width: $font-44px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.mx_MImageBody_thumbnail {
+ display: block;
- // Make sure the _thumbnail is positioned relative to the _container
- position: relative;
+ // Force the image to be the full size of the container, even if the
+ // pixel size is smaller. The problem here is that we don't know what
+ // thumbnail size the HS is going to give us, but we have to commit to
+ // a container size immediately and not change it when the image loads
+ // or we'll get a scroll jump (or have to leave blank space).
+ // This will obviously result in an upscaled image which will be a bit
+ // blurry. The best fix would be for the HS to advertise what size thumbnails
+ // it guarantees to produce.
+ height: 100%;
+ width: 100%;
}
.mx_MImageBody_gifLabel {
diff --git a/res/css/views/messages/_MLocationBody.scss b/res/css/views/messages/_MLocationBody.scss
index e4135d46d44..72202ca6e1b 100644
--- a/res/css/views/messages/_MLocationBody.scss
+++ b/res/css/views/messages/_MLocationBody.scss
@@ -21,51 +21,7 @@ limitations under the License.
z-index: 0; // keeps the entire map under the message action bar
border-radius: $timeline-image-border-radius;
- }
-
- .mx_MLocationBody_markerBorder {
- width: 31px;
- height: 31px;
- border-radius: 50%;
- background-color: $accent;
- filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
-
- .mx_BaseAvatar {
- margin-top: 2px;
- margin-left: 2px;
- }
- }
-
- .mx_MLocationBody_pointer {
- position: absolute;
- bottom: -3px;
- left: 11px;
- width: 9px;
- height: 5px;
-
- &::before {
- mask-image: url('$(res)/img/location/pointer.svg');
- mask-position: center;
- mask-repeat: no-repeat;
- mask-size: 9px;
- content: '';
- display: inline-block;
- width: 9px;
- height: 5px;
- position: absolute;
- background-color: $accent;
- }
- }
-
- .mx_MLocationBody_markerContents {
- background-color: $location-marker-color;
- margin: 0;
- width: 31px;
- height: 31px;
- mask-repeat: no-repeat;
- mask-size: 16px;
- mask-position: center;
- mask-image: url('$(res)/img/element-icons/location.svg');
+ cursor: pointer;
}
}
diff --git a/res/css/views/messages/_MStickerBody.scss b/res/css/views/messages/_MStickerBody.scss
index 165dcd8208c..12cd7c6f39b 100644
--- a/res/css/views/messages/_MStickerBody.scss
+++ b/res/css/views/messages/_MStickerBody.scss
@@ -33,3 +33,10 @@ limitations under the License.
align-items: center;
justify-content: center;
}
+
+.mx_MStickerBody_placeholder {
+ // centering
+ position: absolute;
+ left: calc(50% - 40px);
+ top: calc(50% - 40px);
+}
diff --git a/res/css/views/messages/_MVideoBody.scss b/res/css/views/messages/_MVideoBody.scss
index ac4db004d78..b4cd545bf07 100644
--- a/res/css/views/messages/_MVideoBody.scss
+++ b/res/css/views/messages/_MVideoBody.scss
@@ -15,9 +15,15 @@ limitations under the License.
*/
span.mx_MVideoBody {
- video.mx_MVideoBody {
- max-width: 100%;
- height: auto;
+ overflow: hidden;
+
+ .mx_MVideoBody_container {
border-radius: $timeline-image-border-radius;
+ overflow: hidden;
+
+ video {
+ height: 100%;
+ width: 100%;
+ }
}
}
diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss
index a9c6e3b39c7..75318757a73 100644
--- a/res/css/views/messages/_MessageActionBar.scss
+++ b/res/css/views/messages/_MessageActionBar.scss
@@ -48,7 +48,7 @@ limitations under the License.
cursor: initial;
}
- > * {
+ >* {
white-space: nowrap;
display: inline-block;
position: relative;
@@ -65,6 +65,12 @@ limitations under the License.
.mx_MessageActionBar_maskButton {
width: 28px;
height: 28px;
+
+ &:disabled,
+ &[disabled] {
+ cursor: not-allowed;
+ opacity: .75;
+ }
}
.mx_MessageActionBar_maskButton::after {
@@ -96,6 +102,11 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/message/thread.svg');
}
+.mx_MessageActionBar_threadButton .mx_Indicator {
+ background: $links;
+ animation-iteration-count: infinite;
+}
+
.mx_MessageActionBar_editButton::after {
mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg');
}
diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss
index 14e5dc654fe..f340883d484 100644
--- a/res/css/views/right_panel/_RoomSummaryCard.scss
+++ b/res/css/views/right_panel/_RoomSummaryCard.scss
@@ -235,7 +235,6 @@ limitations under the License.
}
.mx_AccessibleButton_kind_link {
- padding: 0;
margin-top: 12px;
margin-bottom: 12px;
font-size: $font-13px;
diff --git a/res/css/views/right_panel/_ThreadPanel.scss b/res/css/views/right_panel/_ThreadPanel.scss
index cb438c47068..9e9c59d2cbb 100644
--- a/res/css/views/right_panel/_ThreadPanel.scss
+++ b/res/css/views/right_panel/_ThreadPanel.scss
@@ -17,17 +17,22 @@ limitations under the License.
.mx_ThreadPanel {
display: flex;
flex-direction: column;
+ height: 100px;
+ overflow: visible;
.mx_BaseCard_header {
margin-bottom: 12px;
+
.mx_BaseCard_close,
.mx_BaseCard_back {
width: 24px;
height: 24px;
}
+
.mx_BaseCard_back {
left: -4px;
}
+
.mx_BaseCard_close {
right: -4px;
}
@@ -39,7 +44,7 @@ limitations under the License.
}
.mx_ThreadPanel__header {
- width: calc(100% - 30px);
+ width: calc(100% - 38px);
height: 24px;
display: flex;
flex: 1;
@@ -66,6 +71,7 @@ limitations under the License.
--size: 24px;
width: var(--size);
height: var(--size);
+
&::after {
mask-size: var(--size);
mask-image: url("$(res)/img/element-icons/message/overflow-large.svg");
@@ -99,18 +105,39 @@ limitations under the License.
}
.mx_AutoHideScrollbar {
- background: #fff;
background-color: $background;
border-radius: 8px;
- width: calc(100% - 16px);
- padding-right: 16px;
+ padding-inline-end: 0;
+ overflow-y: scroll; // set gap between the thread tile and the right border
+ }
+
+ // 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_ThreadView .mx_ThreadView_timelinePanelWrapper {
- /* the scrollbar is 8px wide, and we want a 12px gap with the side of the
- panel. Hence the magic number, 8+4=12 */
- width: calc(100% - 4px);
- padding-right: 4px;
position: relative;
min-height: 0; // don't displace the composer
flex-grow: 1;
@@ -120,18 +147,27 @@ limitations under the License.
}
}
+ .mx_RoomView_messagePanel { // To avoid the rule from being applied to .mx_ThreadPanel_empty
+ .mx_RoomView_messageListWrapper {
+ width: calc(100% + 6px); // 8px - 2px
+ }
+ }
+
.mx_RoomView_MessageList {
- padding-left: 12px;
- padding-right: 0;
+ padding-inline-start: $spacing-8;
+ padding-inline-end: $spacing-8;
+ content-visibility: visible;
}
- .mx_EventTile, .mx_GenericEventListSummary {
+ .mx_EventTile,
+ .mx_GenericEventListSummary {
// Account for scrollbar when hovering
padding-top: 0;
- .mx_ThreadInfo {
+ .mx_ThreadSummary {
position: relative;
padding-right: 11px;
+
&::after {
content: '';
display: block;
@@ -153,10 +189,18 @@ limitations under the License.
}
}
+ .mx_GenericEventListSummary > .mx_EventTile_line {
+ padding-left: 30px !important; // Override main timeline styling - align summary text with message text
+ }
+
.mx_EventTile:not([data-layout=bubble]) {
.mx_EventTile_e2eIcon {
left: 8px;
}
+
+ &:hover .mx_EventTile_line {
+ box-shadow: unset !important; // don't show the verification left stroke in the thread list
+ }
}
.mx_MessageComposer {
@@ -168,7 +212,7 @@ limitations under the License.
}
.mx_ThreadPanel_dropdown {
- padding: 3px 8px;
+ padding: 3px 4px 3px 8px;
border-radius: 4px;
line-height: 1.5;
user-select: none;
@@ -190,29 +234,27 @@ limitations under the License.
float: right;
}
- .mx_ThreadPanel_dropdown[aria-expanded=true]::before {
- transform: rotate(180deg);
- }
-
.mx_MessageTimestamp {
font-size: $font-12px;
color: $secondary-content;
}
// handling for hidden events (e.g reactions) in the thread view
- &.mx_ThreadView .mx_GenericEventListSummary_unstyledList .mx_EventTile_info {
+ &.mx_ThreadView .mx_EventTile_info {
+ padding-top: 0 !important; // override main timeline padding
+
.mx_EventTile_line {
padding-left: 0 !important; // override main timeline padding
.mx_EventTile_content {
- margin-left: 54px; // align with text
- width: calc(100% - 54px - 8px); // match width of parent
+ margin-left: 48px; // align with text
+ width: calc(100% - 48px - 8px); // match width of parent
}
}
.mx_EventTile_avatar {
position: absolute;
- left: 36px !important; // override main timeline positioning
+ left: 30px !important; // override main timeline positioning
z-index: 9; // position above the hover styling
}
@@ -220,32 +262,33 @@ limitations under the License.
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;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 4px;
+ position: relative;
+ top: 2px;
+ padding-right: 8px;
+
+ .mx_AccessibleButton_kind_link_inline {
+ color: $secondary-content;
+ }
+ }
}
.mx_ThreadPanel_replies {
- margin-top: 8px;
-}
+ margin-top: $spacing-8;
+ display: flex;
+ align-items: center;
+ position: relative;
-.mx_ThreadPanel_repliesSummary {
- &::before {
- content: "";
- mask-image: url('$(res)/img/element-icons/thread-summary.svg');
- mask-position: center;
- display: inline-block;
- height: 18px;
- min-width: 18px;
- background-color: currentColor;
- mask-repeat: no-repeat;
- mask-size: contain;
- margin-right: 8px;
- vertical-align: middle;
+ .mx_ThreadPanel_ThreadsAmount {
+ @mixin ThreadsAmount;
+ font-size: $font-12px; // Same font size as the counter on the main panel
}
-
- color: $secondary-content;
- font-weight: 600;
- float: left;
- margin-right: 12px;
- font-size: $font-12px;
}
.mx_ThreadPanel_viewInRoom::before {
@@ -264,27 +307,30 @@ limitations under the License.
align-items: center;
justify-content: center;
position: absolute;
- top: 48px;
- bottom: 8px;
- left: 8px;
- right: 8px;
+ top: 0;
+ bottom: 0;
+ left: 0;
padding: 20px;
h2 {
color: $primary-content;
- font-weight: 600;
+ font-weight: $font-semi-bold;
font-size: $font-18px;
+ margin-top: 24px;
+ margin-bottom: 10px;
}
p {
font-size: $font-15px;
color: $secondary-content;
+ margin: 10px 0;
}
button {
border: none;
background: none;
color: $accent;
+ font-size: $font-15px;
&:hover,
&:active {
@@ -292,6 +338,15 @@ limitations under the License.
cursor: pointer;
}
}
+
+ .mx_ThreadPanel_empty_tip {
+ font-size: $font-12px;
+ line-height: $font-15px;
+
+ >b {
+ font-weight: $font-semi-bold;
+ }
+ }
}
.mx_ThreadPanel_largeIcon {
@@ -317,6 +372,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;
@@ -336,6 +392,7 @@ limitations under the License.
left: auto;
right: 22px;
border-bottom-color: $quinary-content;
+
&::after {
content: "";
border: inherit;
@@ -357,10 +414,12 @@ limitations under the License.
&:hover {
background-color: $event-selected-color;
}
+
&[aria-checked="true"] {
:first-child {
margin-left: -20px;
}
+
:first-child::before {
content: "";
width: 12px;
diff --git a/res/css/views/right_panel/_TimelineCard.scss b/res/css/views/right_panel/_TimelineCard.scss
index 07227c1b9af..5a121a8f61c 100644
--- a/res/css/views/right_panel/_TimelineCard.scss
+++ b/res/css/views/right_panel/_TimelineCard.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2021 The Matrix.org Foundation C.I.C.
+Copyright 2021 - 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.
@@ -35,9 +35,11 @@ limitations under the License.
}
}
- .mx_AutoHideScrollbar {
- padding-right: 10px;
- width: calc(100% - 10px);
+ .mx_TimelineCard_timeline {
+ overflow: hidden;
+ position: relative; // offset parent for jump to bottom button
+ flex: 1;
+ border-radius: 8px;
}
.mx_NewRoomIntro {
@@ -48,29 +50,61 @@ limitations under the License.
margin-right: 0;
}
- .mx_EventTile:not([data-layout="bubble"]) .mx_EventTile_line {
- padding-left: 36px;
- padding-right: 36px;
- }
+ .mx_EventTile:not([data-layout="bubble"]) {
+ .mx_EventTile_line {
+ padding-left: 36px;
+ padding-right: 36px;
+ }
- .mx_EventTile:not([data-layout="bubble"]) .mx_ReactionsRow {
- padding-left: 36px;
- padding-right: 36px;
- }
+ .mx_ReactionsRow {
+ padding: 0;
- .mx_EventTile:not([data-layout="bubble"]) .mx_ThreadInfo {
- margin-left: 36px;
- margin-right: 0;
- max-width: min(calc(100% - 36px), 600px);
- }
+ // See margin setting of ReactionsRow on _EventTile.scss
+ margin-left: 36px;
+ margin-right: 8px;
+ }
- .mx_GroupLayout .mx_EventTile > .mx_SenderProfile {
- margin-left: 36px;
+ .mx_ThreadSummary {
+ margin-left: 36px;
+ margin-right: 0;
+ max-width: min(calc(100% - 36px), 600px);
+ }
+
+ .mx_EventTile_avatar {
+ top: 12px;
+ left: -3px;
+ }
+
+ .mx_MessageTimestamp {
+ right: -4px;
+ left: auto;
+ }
+
+ .mx_EventTile_msgOption {
+ margin-right: 2px;
+ }
+
+ &.mx_EventTile_info {
+ .mx_EventTile_line {
+ padding-left: 36px;
+ }
+
+ .mx_EventTile_avatar {
+ left: 18px;
+ }
+ }
}
- .mx_EventTile:not([data-layout="bubble"]) .mx_EventTile_avatar {
- top: 12px;
- left: -3px;
+ .mx_GroupLayout {
+ .mx_EventTile {
+ > .mx_DisambiguatedProfile {
+ margin-left: 36px;
+ }
+
+ .mx_EventTile_line {
+ padding-bottom: 8px;
+ }
+ }
}
.mx_CallEvent_wrapper {
@@ -81,34 +115,13 @@ limitations under the License.
}
}
- .mx_EventTile:not([data-layout="bubble"]) .mx_MessageTimestamp {
- right: -4px;
- left: auto;
- }
-
- .mx_EventTile:not([data-layout="bubble"]) .mx_EventTile_msgOption {
- margin-right: 2px;
- }
-
.mx_GenericEventListSummary:not([data-layout=bubble]) .mx_EventTile_line,
.mx_GenericEventListSummary:not([data-layout=bubble]) > .mx_GenericEventListSummary_unstyledList > .mx_EventTile_info .mx_EventTile_avatar ~ .mx_EventTile_line {
padding-left: 36px;
}
- .mx_GroupLayout .mx_EventTile .mx_EventTile_line {
- padding-bottom: 8px;
- }
-
- .mx_EventTile_readAvatars {
- top: -10px;
- }
-
- .mx_EventTile:not([data-layout="bubble"]).mx_EventTile_info .mx_EventTile_line {
- padding-left: 36px;
- }
-
- .mx_EventTile:not([data-layout="bubble"]).mx_EventTile_info .mx_EventTile_avatar {
- left: 18px;
+ .mx_ReadReceiptGroup {
+ top: -6px;
}
.mx_WhoIsTypingTile {
@@ -118,9 +131,12 @@ limitations under the License.
.mx_WhoIsTypingTile_avatars {
flex-basis: 48px; // 12 (padding on message list) + 36 (padding on event lines)
}
-}
-.mx_TimelineCard_timeline {
- overflow: hidden;
- position: relative; // offset parent for jump to bottom button
+ &.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
+ }
+ }
}
diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss
index a172270de6e..3b7a51797f9 100644
--- a/res/css/views/right_panel/_UserInfo.scss
+++ b/res/css/views/right_panel/_UserInfo.scss
@@ -64,7 +64,7 @@ limitations under the License.
margin-bottom: 8px;
}
- .mx_RoomTile_nameContainer {
+ .mx_RoomTile_titleContainer {
width: 154px;
}
@@ -72,7 +72,7 @@ limitations under the License.
display: none;
}
- .mx_RoomTile_name {
+ .mx_RoomTile_title {
width: 160px;
}
diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss
index 1b008d76aa0..47aa9a9d2ab 100644
--- a/res/css/views/rooms/_AppsDrawer.scss
+++ b/res/css/views/rooms/_AppsDrawer.scss
@@ -148,8 +148,6 @@ $MinWidth: 240px;
width: 50%;
min-width: $MinWidth;
border: $container-border-width solid $widget-menu-bar-bg-color;
- border-left-width: 5px;
- border-right-width: 5px;
display: flex;
flex-direction: column;
box-sizing: border-box;
diff --git a/res/css/views/rooms/_EntityTile.scss b/res/css/views/rooms/_EntityTile.scss
index 10c8799413d..b8d71a4cc09 100644
--- a/res/css/views/rooms/_EntityTile.scss
+++ b/res/css/views/rooms/_EntityTile.scss
@@ -61,8 +61,7 @@ limitations under the License.
width: 26px;
}
-.mx_EntityTile_avatar,
-.mx_GroupRoomTile_avatar {
+.mx_EntityTile_avatar {
padding-left: 3px;
padding-right: 12px;
padding-top: 4px;
@@ -70,8 +69,7 @@ limitations under the License.
position: relative;
}
-.mx_EntityTile_name,
-.mx_GroupRoomTile_name {
+.mx_EntityTile_name {
flex: 1 1 0;
overflow: hidden;
font-size: $font-14px;
diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss
index 3472b656bf3..4104ae42733 100644
--- a/res/css/views/rooms/_EventBubbleTile.scss
+++ b/res/css/views/rooms/_EventBubbleTile.scss
@@ -37,7 +37,7 @@ limitations under the License.
margin-left: 49px;
font-size: $font-14px;
- .mx_ThreadInfo {
+ .mx_ThreadSummary {
clear: both;
width: fit-content;
}
@@ -88,7 +88,7 @@ limitations under the License.
}
}
- .mx_SenderProfile,
+ .mx_DisambiguatedProfile,
.mx_EventTile_line {
width: fit-content;
max-width: 70%;
@@ -96,7 +96,7 @@ limitations under the License.
line-height: $font-18px;
}
- > .mx_SenderProfile {
+ > .mx_DisambiguatedProfile {
position: relative;
top: -2px;
left: 2px;
@@ -114,6 +114,11 @@ limitations under the License.
padding-right: 48px;
}
+ .mx_MImageBody_thumbnail_container {
+ min-height: calc(1.8rem + var(--gutterSize) + var(--gutterSize));
+ min-width: calc(1.8rem + var(--gutterSize) + var(--gutterSize));
+ }
+
.mx_CallEvent {
background-color: unset;
@@ -126,10 +131,12 @@ limitations under the License.
.mx_EventTile_line {
border-bottom-right-radius: var(--cornerRadius);
- .mx_MImageBody .mx_MImageBody_thumbnail,
+ .mx_MImageBody .mx_MImageBody_thumbnail_container,
.mx_MImageBody::before,
+ .mx_MVideoBody .mx_MVideoBody_container,
.mx_MediaBody,
- .mx_MLocationBody_map {
+ .mx_MLocationBody_map,
+ .mx_MBeaconBody {
border-bottom-right-radius: var(--cornerRadius) !important;
}
}
@@ -147,13 +154,15 @@ limitations under the License.
&[data-self=true] {
.mx_EventTile_line {
- float: right;
+ margin-inline-start: auto;
border-bottom-left-radius: var(--cornerRadius);
- .mx_MImageBody .mx_MImageBody_thumbnail,
+ .mx_MImageBody .mx_MImageBody_thumbnail_container,
.mx_MImageBody::before,
+ .mx_MVideoBody .mx_MVideoBody_container,
.mx_MediaBody,
- .mx_MLocationBody_map {
+ .mx_MLocationBody_map,
+ .mx_MBeaconBody {
border-bottom-left-radius: var(--cornerRadius) !important;
}
}
@@ -163,16 +172,16 @@ limitations under the License.
margin-right: 32px;
}
- .mx_ThreadInfo {
+ .mx_ThreadSummary {
float: right;
margin-right: calc(-1 * var(--gutterSize));
}
- .mx_SenderProfile {
+ .mx_DisambiguatedProfile {
display: none;
}
- .mx_ReplyTile .mx_SenderProfile {
+ .mx_ReplyTile .mx_DisambiguatedProfile {
display: block;
}
@@ -266,7 +275,8 @@ limitations under the License.
}
//noinspection CssReplaceWithShorthandSafely
- .mx_MImageBody .mx_MImageBody_thumbnail,
+ .mx_MImageBody .mx_MImageBody_thumbnail_container,
+ .mx_MVideoBody .mx_MVideoBody_container,
.mx_MediaBody {
border-radius: unset;
border-top-left-radius: var(--cornerRadius);
@@ -293,20 +303,24 @@ limitations under the License.
&.mx_EventTile_continuation[data-self=false] .mx_EventTile_line {
border-top-left-radius: 0;
- .mx_MImageBody .mx_MImageBody_thumbnail,
+ .mx_MImageBody .mx_MImageBody_thumbnail_container,
+ .mx_MVideoBody .mx_MVideoBody_container,
.mx_MImageBody::before,
.mx_MediaBody,
- .mx_MLocationBody_map {
+ .mx_MLocationBody_map,
+ .mx_MBeaconBody {
border-top-left-radius: 0;
}
}
&.mx_EventTile_lastInSection[data-self=false] .mx_EventTile_line {
border-bottom-left-radius: var(--cornerRadius);
- .mx_MImageBody .mx_MImageBody_thumbnail,
+ .mx_MImageBody .mx_MImageBody_thumbnail_container,
+ .mx_MVideoBody .mx_MVideoBody_container,
.mx_MImageBody::before,
.mx_MediaBody,
- .mx_MLocationBody_map {
+ .mx_MLocationBody_map,
+ .mx_MBeaconBody {
border-bottom-left-radius: var(--cornerRadius);
}
}
@@ -314,20 +328,24 @@ limitations under the License.
&.mx_EventTile_continuation[data-self=true] .mx_EventTile_line {
border-top-right-radius: 0;
- .mx_MImageBody .mx_MImageBody_thumbnail,
+ .mx_MImageBody .mx_MImageBody_thumbnail_container,
+ .mx_MVideoBody .mx_MVideoBody_container,
.mx_MImageBody::before,
.mx_MediaBody,
- .mx_MLocationBody_map {
+ .mx_MLocationBody_map,
+ .mx_MBeaconBody {
border-top-right-radius: 0;
}
}
&.mx_EventTile_lastInSection[data-self=true] .mx_EventTile_line {
border-bottom-right-radius: var(--cornerRadius);
- .mx_MImageBody .mx_MImageBody_thumbnail,
+ .mx_MImageBody .mx_MImageBody_thumbnail_container,
+ .mx_MVideoBody .mx_MVideoBody_container,
.mx_MImageBody::before,
.mx_MediaBody,
- .mx_MLocationBody_map {
+ .mx_MLocationBody_map,
+ .mx_MBeaconBody {
border-bottom-right-radius: var(--cornerRadius);
}
}
@@ -379,7 +397,7 @@ limitations under the License.
.mx_EventTile_avatar {
position: static;
}
- .mx_SenderProfile {
+ .mx_DisambiguatedProfile {
display: none;
}
}
@@ -429,10 +447,13 @@ limitations under the License.
}
}
- .mx_EventTile_readAvatars {
+ .mx_ReadReceiptGroup {
position: absolute;
- right: -78px; // as close to right gutter without clipping as possible
- bottom: 0;
+ // as close to right gutter without clipping as possible
+ right: -78px;
+ // (EventTileLine.line-height - ReadReceiptGroup.height) / 2
+ // this centers the ReadReceiptGroup if we’ve got a single line
+ bottom: calc(($font-18px - 24px) / 2);
top: auto;
}
@@ -541,6 +562,17 @@ limitations under the License.
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%;
+ }
+ }
+ }
+
&::after {
content: "";
clear: both;
@@ -564,10 +596,10 @@ limitations under the License.
margin-right: 0;
.mx_MessageActionBar {
- right: 127px; // align with that of right-column bubbles
+ right: 48px; // align with that of right-column bubbles
}
- .mx_EventTile_readAvatars {
+ .mx_ReadReceiptGroup {
right: -18px; // match alignment to RRs of chat bubbles
}
diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index 586865cb3d0..a849c5fedb1 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -16,26 +16,25 @@ limitations under the License.
*/
$left-gutter: 64px;
+$threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss
.mx_EventTile {
flex-shrink: 0;
.mx_EventTile_receiptSent,
.mx_EventTile_receiptSending {
- // Give it some dimensions so the tooltip can position properly
+ position: relative;
display: inline-block;
- width: 14px;
- height: 14px;
- // We don't use `position: relative` on the element because then it won't line
- // up with the other read receipts
+ width: 16px;
+ height: 16px;
&::before {
background-color: $tertiary-content;
mask-repeat: no-repeat;
mask-position: center;
- mask-size: 14px;
- width: 14px;
- height: 14px;
+ mask-size: 16px;
+ width: 16px;
+ height: 16px;
content: '';
position: absolute;
top: 0;
@@ -43,9 +42,11 @@ $left-gutter: 64px;
right: 0;
}
}
+
.mx_EventTile_receiptSent::before {
mask-image: url('$(res)/img/element-icons/circle-sent.svg');
}
+
.mx_EventTile_receiptSending::before {
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
}
@@ -61,25 +62,24 @@ $left-gutter: 64px;
&[data-shape=ThreadsList][data-notification]::before {
content: "";
position: absolute;
- width: 8px;
- height: 8px;
+ width: 10px;
+ height: 10px;
border-radius: 50%;
- right: -16px;
- top: 6px;
+ right: -25px; // center it in the gutter (16px margin + 4px padding + half 10px width)
+ top: 4px;
left: auto;
}
&[data-shape=ThreadsList][data-notification=total]::before {
- background-color: $roomtile-default-badge-bg-color;
+ background-color: $room-icon-unread-color;
}
&[data-shape=ThreadsList][data-notification=highlight]::before {
background-color: $alert;
}
- .mx_ThreadInfo,
+ .mx_ThreadSummary,
.mx_ThreadSummaryIcon {
- margin-right: 110px;
margin-left: 64px;
}
@@ -112,10 +112,11 @@ $left-gutter: 64px;
background-color: $header-panel-bg-color;
}
- .mx_SenderProfile {
+ .mx_DisambiguatedProfile {
color: $primary-content;
font-size: $font-14px;
- display: inline-block; /* anti-zalgo, with overflow hidden */
+ display: inline-block;
+ /* anti-zalgo, with overflow hidden */
overflow: hidden;
padding-bottom: 0px;
padding-top: 0px;
@@ -126,21 +127,6 @@ $left-gutter: 64px;
max-width: calc(100% - $left-gutter);
}
- .mx_SenderProfile .mx_Flair {
- opacity: 0.7;
- margin-left: 5px;
- display: inline-block;
- vertical-align: top;
- overflow: hidden;
- user-select: none;
-
- img {
- vertical-align: -2px;
- margin-right: 2px;
- border-radius: 8px;
- }
- }
-
&.mx_EventTile_isEditing .mx_MessageTimestamp {
visibility: hidden;
}
@@ -157,7 +143,8 @@ $left-gutter: 64px;
clear: both;
}
- .mx_EventTile_line, .mx_EventTile_reply {
+ .mx_EventTile_line,
+ .mx_EventTile_reply {
position: relative;
padding-left: $left-gutter;
border-radius: 8px;
@@ -235,12 +222,6 @@ $left-gutter: 64px;
overflow-y: hidden;
}
- &.mx_EventTile_selected .mx_EventTile_line,
- &:hover .mx_EventTile_line {
- border-top-left-radius: 4px;
- border-bottom-left-radius: 4px;
- }
-
&:hover.mx_EventTile_verified .mx_EventTile_line {
box-shadow: inset calc(50px + $selected-message-border-width) 0 0 -50px $e2e-verified-color;
}
@@ -323,15 +304,24 @@ $left-gutter: 64px;
.mx_RoomView_timeline_rr_enabled {
.mx_EventTile[data-layout=group] {
+
+ .mx_ThreadSummary,
+ .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;
+ min-height: $font-14px;
+ }
+
+ .mx_ThreadSummary {
+ max-width: min(calc(100% - $left-gutter - 110px), 600px); // leave space on both left & right gutters
}
}
+
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
}
-.mx_SenderProfile {
+.mx_DisambiguatedProfile {
cursor: pointer;
}
@@ -358,36 +348,6 @@ $left-gutter: 64px;
}
}
-.mx_EventTile_readAvatars {
- position: relative;
- display: inline-block;
- width: 14px;
- height: 14px;
- // This aligns the avatar with the last line of the
- // message. We want to move it one line up - 2.2rem
- top: -2.2rem;
- user-select: none;
- z-index: 1;
-}
-
-.mx_EventTile_readAvatars .mx_BaseAvatar {
- position: absolute;
- display: inline-block;
- height: $font-14px;
- width: $font-14px;
-
- will-change: left, top;
- transition:
- left var(--transition-short) ease-out,
- top var(--transition-standard) ease-out;
-}
-
-.mx_EventTile_readAvatarRemainder {
- color: $event-timestamp-color;
- font-size: $font-11px;
- position: absolute;
-}
-
.mx_EventTile_bigEmoji {
font-size: 48px;
line-height: 57px;
@@ -423,7 +383,8 @@ $left-gutter: 64px;
background-repeat: no-repeat;
background-size: contain;
- &::before, &::after {
+ &::before,
+ &::after {
content: "";
display: block;
position: absolute;
@@ -448,6 +409,7 @@ $left-gutter: 64px;
mask-image: url('$(res)/img/e2e/warning.svg');
background-color: $alert;
}
+
opacity: 1;
}
@@ -456,6 +418,7 @@ $left-gutter: 64px;
mask-image: url('$(res)/img/e2e/normal.svg');
background-color: $header-panel-text-primary-color;
}
+
opacity: 1;
}
@@ -494,7 +457,8 @@ $left-gutter: 64px;
color: inherit; // inherit the colour from the dark or light theme by default (but not for code blocks)
font-size: $font-14px;
- pre, code {
+ pre,
+ code {
font-family: $monospace-font-family !important;
background-color: $codeblock-background-color;
}
@@ -507,7 +471,7 @@ $left-gutter: 64px;
pre code {
white-space: pre; // we want code blocks to be scrollable and not wrap
- > * {
+ >* {
display: inline;
}
}
@@ -529,6 +493,7 @@ $left-gutter: 64px;
float: left;
margin: 0 0.5em 0 -1.5em;
color: gray;
+
& span {
text-align: right;
display: block;
@@ -562,18 +527,22 @@ $left-gutter: 64px;
height: 19px;
background-color: $message-action-bar-fg-color;
}
+
.mx_EventTile_buttonBottom {
top: 33px;
}
+
.mx_EventTile_copyButton {
mask-image: url($copy-button-url);
}
+
.mx_EventTile_collapseButton {
mask-size: 75%;
mask-position: center;
mask-repeat: no-repeat;
mask-image: url("$(res)/img/element-icons/minimise-collapse.svg");
}
+
.mx_EventTile_expandButton {
mask-size: 75%;
mask-position: center;
@@ -688,146 +657,87 @@ $left-gutter: 64px;
visibility: visible;
}
+// Inverse of the above to *disable* the animation on any indicators. This approach
+// is less pretty, but is easier to target because otherwise we need to define the
+// animation for when it's shown which means duplicating the style definition in
+// multiple places.
+.mx_EventTile:not(:hover):not(.mx_EventTile_actionBarFocused):not([data-whatinput='keyboard'] :focus-within):not(.focus-visible:focus-within) {
+ .mx_MessageActionBar .mx_Indicator {
+ animation: none;
+ }
+}
+
@media only screen and (max-width: 480px) {
- .mx_EventTile_line, .mx_EventTile_reply {
+
+ .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_ThreadInfo::before {
- content: "";
- display: inline-block;
- mask-image: url('$(res)/img/element-icons/thread-summary.svg');
- mask-position: center;
- height: 18px;
- min-width: 18px;
- background-color: $secondary-content;
- mask-repeat: no-repeat;
- mask-size: contain;
+.mx_ThreadSummary::before {
+ @mixin ThreadInfoIcon;
+ background-color: $secondary-content !important;
}
.mx_ThreadSummaryIcon {
+ display: inline-block;
font-size: $font-12px;
- color: $secondary-content;
- &::before {
- vertical-align: middle;
- margin-left: 8px;
- }
-}
-
-.mx_ThreadInfo {
- min-width: 267px;
- max-width: min(calc(100% - 64px), 600px);
- width: fit-content;
- height: 40px;
- position: relative;
- background-color: $system;
- padding-left: 12px;
- display: flex;
- align-items: center;
- border-radius: 8px;
- padding-right: 16px;
+ color: $secondary-content !important;
margin-top: 8px;
- font-size: $font-12px;
- color: $secondary-content;
- box-sizing: border-box;
- justify-content: flex-start;
- clear: both;
- overflow: hidden;
-
- &:hover {
- cursor: pointer;
- border: 1px solid $quinary-content;
- padding-top: 7px;
- padding-bottom: 7px;
- padding-left: 11px;
- padding-right: 15px;
- }
-
- &::after {
- content: "›";
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- width: 60px;
- padding: 0 10px;
- font-size: 15px;
- line-height: 39px;
- box-sizing: border-box;
-
- text-align: right;
- font-weight: 600;
-
- background: linear-gradient(270deg, $system 52.6%, transparent 100%);
+ margin-bottom: 8px;
- opacity: 0;
- transform: translateX(20px);
- transition: all .1s ease-in-out;
- }
-
- &:hover::after {
- opacity: 1;
- transform: translateX(0);
+ &::before {
+ vertical-align: middle;
+ margin-right: 8px;
+ margin-top: -2px;
}
}
-.mx_MessagePanel_narrow .mx_ThreadInfo {
+.mx_MessagePanel_narrow .mx_ThreadSummary {
min-width: initial;
max-width: initial;
width: initial;
}
-.mx_ThreadInfo_content {
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- padding-left: 8px;
- font-size: $font-12px;
- line-height: calc(2 * $font-12px);
- color: $secondary-content;
-}
-
-.mx_ThreadInfo_avatar {
- float: left;
-}
-
-.mx_ThreadInfo_threads-amount {
- font-weight: 600;
- position: relative;
- padding: 0 12px 0 8px;
- white-space: nowrap;
-}
-
.mx_EventTile[data-shape=ThreadsList] {
- --topOffset: 20px;
- --leftOffset: 46px;
+ --topOffset: $spacing-12;
+ --leftOffset: 48px;
+ $borderRadius: 8px;
+ $padding: $spacing-8;
+ $hrHeight: 1px;
- margin: var(--topOffset) 16px var(--topOffset) 0;
- border-radius: 8px;
+ margin: calc(var(--topOffset) + $hrHeight) 0 var(--topOffset); // include the height of horizontal line
+ padding: $padding $spacing-24 $padding $padding;
+ border-radius: $borderRadius;
display: flex;
flex-flow: wrap;
align-items: center;
- &:hover {
+ &:hover,
+ // To cancel "&.mx_EventTile:hover .mx_EventTile_line"
+ &:not([data-layout=bubble]):hover .mx_EventTile_line {
background-color: $system;
}
&::after {
content: "";
position: absolute;
- left: var(--leftOffset);
- right: 0;
- height: 1px;
- bottom: calc(-1 * var(--topOffset));
+ left: calc(var(--leftOffset) + $padding);
+ right: $spacing-24; // 24px: 32px - 8px (right padding)
+ 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 {
@@ -845,6 +755,7 @@ $left-gutter: 64px;
&::after {
content: unset;
}
+
margin-bottom: 0;
}
@@ -852,57 +763,54 @@ $left-gutter: 64px;
margin-top: 0;
}
- padding-top: 0;
-
.mx_EventTile_avatar {
- top: -4px;
- left: 0;
+ top: $padding;
+ left: $padding;
}
- .mx_SenderProfile {
- margin-left: var(--leftOffset) !important;
- flex: 1;
+ .mx_DisambiguatedProfile {
margin-right: 12px;
-
display: inline-flex;
- // not a fan of the magic number here, but I just tweaked
- // the hardcoded value of the current implementation
- max-width: calc(100% - 96px);
+ flex: 1;
}
- .mx_SenderProfile_displayName,
- .mx_SenderProfile_mxid {
+ .mx_DisambiguatedProfile_displayName,
+ .mx_DisambiguatedProfile_mxid {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
- .mx_SenderProfile_displayName {
+ .mx_DisambiguatedProfile_displayName {
flex: none;
max-width: 100%;
}
- .mx_SenderProfile_mxid {
+ .mx_DisambiguatedProfile_mxid {
flex: 1;
}
.mx_EventTile_line {
width: 100%;
box-sizing: border-box;
- padding-left: var(--leftOffset) !important;
- padding-bottom: 0;
+ border-radius: $borderRadius !important; // override 4px
+ }
+
+ .mx_DisambiguatedProfile,
+ .mx_EventTile_line {
+ padding-inline-start: var(--leftOffset);
}
.mx_MessageTimestamp {
- position: initial !important;
max-width: 80px;
- width: auto !important;
+ width: auto;
}
}
.mx_ThreadView {
display: flex;
flex-direction: column;
+ max-height: 100%;
.mx_ThreadView_List {
flex: 1;
@@ -916,9 +824,10 @@ $left-gutter: 64px;
.mx_EventTile {
display: flex;
flex-direction: column;
- padding-top: 0;
.mx_EventTile_line {
+ padding-top: 2px;
+ padding-bottom: 2px;
padding-left: 0;
order: 10 !important;
}
@@ -932,6 +841,16 @@ $left-gutter: 64px;
padding-left: 0;
padding-right: 0;
}
+
+ .mx_ReplyChain {
+ .mx_MLocationBody {
+ margin-top: 6px; // See: https://github.com/matrix-org/matrix-react-sdk/pull/8442
+ }
+ }
+
+ &:not([data-layout=bubble]) {
+ padding-top: $spacing-16;
+ }
}
.mx_EventTile[data-layout=bubble] {
@@ -963,13 +882,19 @@ $left-gutter: 64px;
}
.mx_EventTile[data-layout=group] {
+ $spacing-start: 56px; // 56px: 64px - 8px (padding)
width: 100%;
.mx_EventTile_content,
+ .mx_EventTile_body,
.mx_HiddenBody,
.mx_RedactedBody,
- .mx_ReplyChain_wrapper {
- margin-left: 36px;
+ .mx_UnknownBody,
+ .mx_MPollBody,
+ .mx_MLocationBody,
+ .mx_ReplyChain_wrapper,
+ .mx_ReactionsRow {
+ margin-left: $spacing-start;
margin-right: 8px;
.mx_EventTile_content,
@@ -980,37 +905,44 @@ $left-gutter: 64px;
}
}
- .mx_ReactionsRow {
- margin-left: 36px;
- margin-right: 8px;
+ .mx_ReplyChain_wrapper {
+ .mx_MLocationBody {
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+ }
}
.mx_MessageTimestamp {
- top: 2px !important;
- width: auto;
+ top: 2px; // Align with mx_EventTile_content
}
.mx_EventTile_senderDetails {
display: flex;
align-items: center;
- gap: calc(6px + $selected-message-border-width);
+ gap: $spacing-16; // gap between the avatar and the sender ID
+ padding-inline-start: $spacing-8;
a {
flex: 1;
- min-width: none;
+ min-width: unset;
max-width: 100%;
display: flex;
align-items: center;
- .mx_SenderProfile {
+ .mx_DisambiguatedProfile {
+ margin-left: 8px;
flex: 1;
}
}
}
+
+ .mx_EventTile_mediaLine {
+ padding-inline-start: $spacing-start;
+ }
}
.mx_EventTile_mediaLine {
- padding-left: 36px !important;
+ padding-left: 36px;
padding-right: 50px;
.mx_MImageBody {
@@ -1022,4 +954,22 @@ $left-gutter: 64px;
.mx_MessageComposer_sendMessage {
margin-right: 0;
}
+
+ .mx_EditMessageComposer {
+ margin-left: 30px !important; // align start of first letter with that of the event body
+ }
+
+ .mx_EditMessageComposer_buttons {
+ padding-right: 11px; // align with right edge of input
+ margin-right: 0; // align with right edge of background
+ }
+
+ .mx_GroupLayout {
+ .mx_EventTile {
+ .mx_EventTile_line {
+ padding-top: 2px;
+ padding-bottom: 2px;
+ }
+ }
+ }
}
diff --git a/res/css/views/rooms/_GroupLayout.scss b/res/css/views/rooms/_GroupLayout.scss
index ebb7f99e45e..b1d6e8b535b 100644
--- a/res/css/views/rooms/_GroupLayout.scss
+++ b/res/css/views/rooms/_GroupLayout.scss
@@ -19,7 +19,7 @@ $left-gutter: 64px;
.mx_GroupLayout {
.mx_EventTile {
- > .mx_SenderProfile {
+ > .mx_DisambiguatedProfile {
line-height: $font-20px;
margin-left: $left-gutter;
}
@@ -65,7 +65,7 @@ $left-gutter: 64px;
}
}
- .mx_SenderProfile {
+ .mx_DisambiguatedProfile {
font-size: $font-13px;
}
@@ -97,7 +97,7 @@ $left-gutter: 64px;
top: 3px;
}
- .mx_EventTile_readAvatars {
+ .mx_ReadReceiptGroup {
// This aligns the avatar with the last line of the
// message. We want to move it one line up - 2rem
top: -2rem;
diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss
index 14b1c4ad60a..c09ee476767 100644
--- a/res/css/views/rooms/_IRCLayout.scss
+++ b/res/css/views/rooms/_IRCLayout.scss
@@ -40,7 +40,7 @@ $irc-line-height: $font-18px;
margin-right: $right-padding;
}
- .mx_ThreadInfo {
+ .mx_ThreadSummary {
margin-right: 0;
margin-left: 0;
}
@@ -49,8 +49,8 @@ $irc-line-height: $font-18px;
order: 5;
flex-shrink: 0;
- .mx_EventTile_readAvatars {
- top: 0.2rem; // ($irc-line-height - avatar height) / 2
+ .mx_ReadReceiptGroup {
+ top: -0.3rem; // ($irc-line-height - avatar height) / 2
}
}
@@ -173,7 +173,7 @@ $irc-line-height: $font-18px;
border-left: 0;
}
- .mx_SenderProfile {
+ .mx_DisambiguatedProfile {
width: var(--name-width);
display: flex;
order: 2;
@@ -181,14 +181,14 @@ $irc-line-height: $font-18px;
justify-content: flex-start;
align-items: center;
- > .mx_SenderProfile_displayName {
+ > .mx_DisambiguatedProfile_displayName {
width: 100%;
text-align: end;
overflow: hidden;
text-overflow: ellipsis;
}
- > .mx_SenderProfile_mxid {
+ > .mx_DisambiguatedProfile_mxid {
visibility: collapse;
// Override the inherited margin.
margin-left: 0;
@@ -196,11 +196,11 @@ $irc-line-height: $font-18px;
}
}
- .mx_SenderProfile:hover {
+ .mx_DisambiguatedProfile:hover {
overflow: visible;
z-index: 10;
- > .mx_SenderProfile_displayName {
+ > .mx_DisambiguatedProfile_displayName {
overflow: visible;
display: inline;
background-color: $event-selected-color;
@@ -208,7 +208,7 @@ $irc-line-height: $font-18px;
padding-right: 8px;
}
- > .mx_SenderProfile_mxid {
+ > .mx_DisambiguatedProfile_mxid {
visibility: visible;
opacity: 1;
background-color: $event-selected-color;
@@ -217,7 +217,7 @@ $irc-line-height: $font-18px;
.mx_ReplyChain {
margin: 0;
- .mx_SenderProfile {
+ .mx_DisambiguatedProfile {
order: unset;
max-width: unset;
width: unset;
@@ -252,10 +252,4 @@ $irc-line-height: $font-18px;
cursor: col-resize;
z-index: 100;
}
-
- // Need to use important to override the js provided height and width values.
- .mx_Flair > img {
- height: $font-14px !important;
- width: $font-14px !important;
- }
}
diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss
index b0583ee94ec..c29bb28689d 100644
--- a/res/css/views/rooms/_MemberInfo.scss
+++ b/res/css/views/rooms/_MemberInfo.scss
@@ -58,7 +58,7 @@ limitations under the License.
margin: 0 16px 16px 16px;
}
-.mx_MemberInfo .mx_RoomTile_nameContainer {
+.mx_MemberInfo .mx_RoomTile_titleContainer {
width: 154px;
}
@@ -66,12 +66,12 @@ limitations under the License.
display: none;
}
-.mx_MemberInfo .mx_RoomTile_name {
+.mx_MemberInfo .mx_RoomTile_title {
width: 160px;
}
.mx_MemberInfo_avatar {
- background: $groupFilterPanel-bg-color;
+ background: $spacePanel-bg-color;
margin-bottom: 16px;
}
diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss
index b84beea89be..41f394ff845 100644
--- a/res/css/views/rooms/_MemberList.scss
+++ b/res/css/views/rooms/_MemberList.scss
@@ -14,9 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_MemberList,
-.mx_GroupMemberList,
-.mx_GroupRoomList {
+.mx_MemberList {
flex: 1;
display: flex;
flex-direction: column;
@@ -52,11 +50,6 @@ limitations under the License.
}
}
-.mx_GroupMemberList_query,
-.mx_GroupRoomList_query {
- flex: 0 0 auto;
-}
-
.mx_MemberList_chevron {
position: absolute;
right: 35px;
@@ -117,11 +110,3 @@ limitations under the License.
margin-right: 5px;
}
}
-
-.mx_MemberList_inviteCommunity span::before {
- mask-image: url('$(res)/img/icon-invite-people.svg');
-}
-
-.mx_MemberList_addRoomToCommunity span::before {
- mask-image: url('$(res)/img/icons-room-add.svg');
-}
diff --git a/res/css/views/rooms/_NewRoomIntro.scss b/res/css/views/rooms/_NewRoomIntro.scss
index f0e471d3844..1b7e298ef6b 100644
--- a/res/css/views/rooms/_NewRoomIntro.scss
+++ b/res/css/views/rooms/_NewRoomIntro.scss
@@ -23,22 +23,16 @@ limitations under the License.
}
}
- .mx_AccessibleButton_kind_link {
- padding: 0;
- font-size: inherit;
- }
-
.mx_NewRoomIntro_buttons {
margin-top: 28px;
+ display: flex;
+ flex-flow: wrap;
+ gap: 14px 12px;
.mx_AccessibleButton {
line-height: $font-24px;
display: inline-block;
- & + .mx_AccessibleButton {
- margin-left: 12px;
- }
-
&:not(.mx_AccessibleButton_kind_primary_outline)::before {
content: '';
display: inline-block;
diff --git a/res/css/views/rooms/_PinnedEventTile.scss b/res/css/views/rooms/_PinnedEventTile.scss
index 07978a8f655..d31b1cbcda4 100644
--- a/res/css/views/rooms/_PinnedEventTile.scss
+++ b/res/css/views/rooms/_PinnedEventTile.scss
@@ -91,10 +91,7 @@ limitations under the License.
}
.mx_AccessibleButton_kind_link {
- padding: 0;
margin-left: 12px;
- font-size: inherit;
- line-height: inherit;
}
}
diff --git a/res/css/views/rooms/_ReadReceiptGroup.scss b/res/css/views/rooms/_ReadReceiptGroup.scss
new file mode 100644
index 00000000000..fe40b1263f3
--- /dev/null
+++ b/res/css/views/rooms/_ReadReceiptGroup.scss
@@ -0,0 +1,146 @@
+/*
+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_ReadReceiptGroup {
+ 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
+ top: calc(-$font-22px - 3px);
+ user-select: none;
+ z-index: 1;
+
+ .mx_ReadReceiptGroup_button {
+ display: inline-flex;
+ flex-direction: row;
+ height: 16px;
+ padding: 4px;
+ border-radius: 6px;
+
+ &.mx_AccessibleButton {
+ &:hover {
+ background: $event-selected-color;
+ }
+ }
+ }
+
+ .mx_ReadReceiptGroup_remainder {
+ color: $secondary-content;
+ font-size: $font-11px;
+ line-height: $font-16px;
+ margin-right: 4px;
+ }
+
+ .mx_ReadReceiptGroup_container {
+ position: relative;
+ display: block;
+ height: 100%;
+
+ .mx_BaseAvatar {
+ position: absolute;
+ display: inline-block;
+ height: 14px;
+ width: 14px;
+ border: 1px solid $background;
+ border-radius: 100%;
+
+ will-change: left, top;
+ transition:
+ left var(--transition-short) ease-out,
+ top var(--transition-standard) ease-out;
+ }
+ }
+}
+
+.mx_ReadReceiptGroup_popup {
+ max-height: 300px;
+ width: 220px;
+ border-radius: 8px;
+ display: flex;
+ flex-direction: column;
+ text-align: left;
+ font-size: 12px;
+ line-height: 15px;
+
+ right: 0;
+
+ &.mx_ContextualMenu_top {
+ top: 8px;
+ }
+
+ &.mx_ContextualMenu_bottom {
+ bottom: 8px;
+ }
+
+ .mx_ReadReceiptGroup_title {
+ font-size: 12px;
+ line-height: 15px;
+ margin: 16px 16px 8px;
+ font-weight: 600;
+ // shouldn’t be actually focusable
+ outline: none;
+ }
+
+ .mx_AutoHideScrollbar {
+ .mx_ReadReceiptGroup_person {
+ display: flex;
+ flex-direction: row;
+ padding: 4px;
+ margin: 0 12px;
+ border-radius: 8px;
+
+ &:hover {
+ background: $menu-selected-color;
+ }
+
+ &:last-child {
+ margin-bottom: 8px;
+ }
+
+ .mx_BaseAvatar {
+ margin: 6px 8px;
+ align-self: center;
+ justify-self: center;
+ }
+
+ .mx_ReadReceiptGroup_name {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ flex-shrink: 1;
+ overflow: hidden;
+
+ p {
+ margin: 2px 0;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+
+ .mx_ReadReceiptGroup_secondary {
+ color: $secondary-content;
+ }
+ }
+ }
+ }
+}
+
+.mx_ReadReceiptGroup_person--tooltip {
+ overflow-y: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
diff --git a/res/css/views/rooms/_ReplyTile.scss b/res/css/views/rooms/_ReplyTile.scss
index c2f19eff2d1..ca8f886737e 100644
--- a/res/css/views/rooms/_ReplyTile.scss
+++ b/res/css/views/rooms/_ReplyTile.scss
@@ -104,7 +104,7 @@ limitations under the License.
padding-top: 0;
}
- .mx_SenderProfile {
+ .mx_DisambiguatedProfile {
font-size: $font-14px;
line-height: $font-17px;
diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss
index 20369830c64..85c139402be 100644
--- a/res/css/views/rooms/_RoomHeader.scss
+++ b/res/css/views/rooms/_RoomHeader.scss
@@ -84,27 +84,6 @@ limitations under the License.
align-items: center;
}
-.mx_RoomHeader_simpleHeader {
- line-height: $font-52px;
- color: $primary-content;
- font-size: $font-18px;
- font-weight: $font-semi-bold;
- overflow: hidden;
- margin-left: 63px;
- text-overflow: ellipsis;
- width: 100%;
-
- .mx_RoomHeader_cancelButton {
- float: right;
- }
-
- .mx_RoomHeader_icon {
- margin-left: 14px;
- margin-right: 24px;
- vertical-align: -4px;
- }
-}
-
.mx_RoomHeader_name {
flex: 0 1 auto;
overflow: hidden;
@@ -162,17 +141,24 @@ limitations under the License.
}
.mx_RoomHeader_topic {
+ $lineHeight: $font-16px;
+ $lines: 2;
+
flex: 1;
color: $roomtopic-color;
font-weight: 400;
font-size: $font-13px;
+ line-height: $lineHeight;
+ max-height: calc($lineHeight * $lines);
+ border-bottom: 1px solid transparent;
+
// to align baseline of topic with room name
margin: 4px 7px 0;
+
overflow: hidden;
- text-overflow: ellipsis;
- border-bottom: 1px solid transparent;
- line-height: 1.2em;
- max-height: 2.4em;
+ -webkit-line-clamp: $lines; // See: https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
}
.mx_RoomHeader_avatar {
@@ -231,6 +217,10 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/room/search-inset.svg');
}
+.mx_RoomHeader_inviteButton::before {
+ mask-image: url('$(res)/img/element-icons/room/invite.svg');
+}
+
.mx_RoomHeader_voiceCallButton::before {
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss
index 029c5b6cdbb..bbac00e0e60 100644
--- a/res/css/views/rooms/_RoomList.scss
+++ b/res/css/views/rooms/_RoomList.scss
@@ -21,9 +21,12 @@ limitations under the License.
.mx_RoomList_iconPlus::before {
mask-image: url('$(res)/img/element-icons/roomlist/plus-circle.svg');
}
-.mx_RoomList_iconCreateNewRoom::before {
+.mx_RoomList_iconNewRoom::before {
mask-image: url('$(res)/img/element-icons/roomlist/hash-plus.svg');
}
+.mx_RoomList_iconNewVideoRoom::before {
+ mask-image: url('$(res)/img/element-icons/roomlist/hash-video.svg');
+}
.mx_RoomList_iconAddExistingRoom::before {
mask-image: url('$(res)/img/element-icons/roomlist/hash.svg');
}
diff --git a/res/css/views/rooms/_RoomListHeader.scss b/res/css/views/rooms/_RoomListHeader.scss
index 442456ff0e2..7f5d06d5499 100644
--- a/res/css/views/rooms/_RoomListHeader.scss
+++ b/res/css/views/rooms/_RoomListHeader.scss
@@ -103,9 +103,12 @@ limitations under the License.
.mx_RoomListHeader_iconStartChat::before {
mask-image: url('$(res)/img/element-icons/roomlist/member-plus.svg');
}
-.mx_RoomListHeader_iconCreateRoom::before {
+.mx_RoomListHeader_iconNewRoom::before {
mask-image: url('$(res)/img/element-icons/roomlist/hash-plus.svg');
}
+.mx_RoomListHeader_iconNewVideoRoom::before {
+ mask-image: url('$(res)/img/element-icons/roomlist/hash-video.svg');
+}
.mx_RoomListHeader_iconExplore::before {
mask-image: url('$(res)/img/element-icons/roomlist/hash-search.svg');
}
diff --git a/res/css/views/rooms/_RoomPreviewBar.scss b/res/css/views/rooms/_RoomPreviewBar.scss
index 8e4a9ee5756..7cce08c7896 100644
--- a/res/css/views/rooms/_RoomPreviewBar.scss
+++ b/res/css/views/rooms/_RoomPreviewBar.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2015 - 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.
@@ -98,6 +98,14 @@ limitations under the License.
}
}
+// With maximised widgets, the panel fits in better when rounded
+.mx_MainSplit_maximisedWidget .mx_RoomPreviewBar_panel {
+ margin: $container-gap-width;
+ margin-right: calc($container-gap-width / 2); // Shared with right panel
+ margin-top: 0; // Already covered by apps drawer
+ border-radius: 8px;
+}
+
.mx_RoomPreviewBar_dialog {
margin: auto;
box-sizing: content;
diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss
index d5b28d07e2e..c00ac227cf5 100644
--- a/res/css/views/rooms/_RoomSublist.scss
+++ b/res/css/views/rooms/_RoomSublist.scss
@@ -50,6 +50,10 @@ limitations under the License.
height: 24px;
color: $tertiary-content;
+ .mx_RoomSublist_stickableContainer {
+ width: 100%;
+ }
+
.mx_RoomSublist_stickable {
flex: 1;
max-width: 100%;
@@ -176,6 +180,14 @@ limitations under the License.
}
}
+ // In the general case, we reserve space for each sublist header to prevent
+ // scroll jumps when they become sticky. However, that leaves a gap when
+ // scrolled to the top above the first sublist (whose header can only ever
+ // stick to top), so we make sure to exclude the first visible sublist.
+ &:not(.mx_RoomSublist_hidden) ~ .mx_RoomSublist .mx_RoomSublist_stickableContainer {
+ height: 24px;
+ }
+
.mx_RoomSublist_resizeBox {
position: relative;
diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss
index 9253a25e327..23fb4f1e9af 100644
--- a/res/css/views/rooms/_RoomTile.scss
+++ b/res/css/views/rooms/_RoomTile.scss
@@ -19,12 +19,12 @@ limitations under the License.
margin-bottom: 4px;
padding: 4px;
+ // The tile is also a flexbox row itself
+ display: flex;
contain: content; // Not strict as it will break when resizing a sublist vertically
- height: 40px;
box-sizing: border-box;
- // The tile is also a flexbox row itself
- display: flex;
+ font-size: $font-13px;
&.mx_RoomTile_selected,
&:hover,
@@ -35,22 +35,22 @@ limitations under the License.
}
.mx_DecoratedRoomAvatar, .mx_RoomTile_avatarContainer {
- margin-right: 8px;
+ margin-right: 10px;
}
- .mx_RoomTile_nameContainer {
+ .mx_RoomTile_titleContainer {
+ height: 32px;
+ min-width: 0;
+ flex-basis: 0;
flex-grow: 1;
- min-width: 0; // allow flex to shrink it
margin-right: 8px; // spacing to buttons/badges
- // Create a new column layout flexbox for the name parts
+ // Create a new column layout flexbox for the title parts
display: flex;
flex-direction: column;
justify-content: center;
- .mx_RoomTile_name,
- .mx_RoomTile_messagePreview {
- margin: 0 2px;
+ .mx_RoomTile_title, .mx_RoomTile_subtitle {
width: 100%;
// Ellipsize any text overflow
@@ -59,23 +59,56 @@ limitations under the License.
white-space: nowrap;
}
- .mx_RoomTile_name {
+ .mx_RoomTile_title {
font-size: $font-14px;
line-height: $font-18px;
- }
- .mx_RoomTile_name.mx_RoomTile_nameHasUnreadEvents {
- font-weight: 600;
+ &.mx_RoomTile_titleHasUnreadEvents {
+ font-weight: 600;
+ }
}
- .mx_RoomTile_messagePreview {
- font-size: $font-13px;
+ .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;
+ }
}
- .mx_RoomTile_nameWithPreview {
- margin-top: -4px; // shift the name up a bit more
+ .mx_RoomTile_titleWithSubtitle {
+ margin-top: -3px; // shift the title up a bit more
}
}
@@ -229,6 +262,10 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/export.svg');
}
+ .mx_RoomTile_iconDeveloperTools::before {
+ mask-image: url('$(res)/img/element-icons/settings/flask.svg');
+ }
+
.mx_RoomTile_iconCopyLink::before {
mask-image: url('$(res)/img/element-icons/link.svg');
}
diff --git a/res/css/views/rooms/_ThreadSummary.scss b/res/css/views/rooms/_ThreadSummary.scss
new file mode 100644
index 00000000000..11ff6cdbbe3
--- /dev/null
+++ b/res/css/views/rooms/_ThreadSummary.scss
@@ -0,0 +1,113 @@
+/*
+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.
+*/
+
+$left-gutter: 64px; // From _EventTile.scss
+$threadSummaryLineHeight: calc(2 * $font-12px);
+
+.mx_ThreadSummary {
+ min-width: 267px;
+ max-width: min(calc(100% - $left-gutter), 600px); // leave space on both left & right gutters
+ width: fit-content;
+ height: 40px;
+ position: relative;
+ background-color: $system;
+ padding-left: $spacing-12;
+ display: flex;
+ align-items: center;
+ border-radius: 8px;
+ padding-right: $spacing-16;
+ margin-top: $spacing-8;
+ font-size: $font-12px;
+ color: $secondary-content;
+ box-sizing: border-box;
+ justify-content: flex-start;
+ clear: both;
+ overflow: hidden;
+ border: 1px solid $system; // always render a border so the hover effect doesn't require a re-layout
+
+ .mx_ThreadSummary_chevron {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 60px;
+ box-sizing: border-box;
+ // XXX: We use `$system-transparent` instead of `transparent` to work around a Safari <15.4 bug
+ background: linear-gradient(270deg, $system 50%, $system-transparent 100%);
+
+ opacity: 0;
+ transform: translateX(60px);
+ transition: all .1s ease-in-out;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ right: $spacing-12;
+ transform: translateY(-50%);
+ width: 12px;
+ height: 12px;
+ mask-image: url('$(res)/img/compound/chevron-right-12px.svg');
+ mask-position: center;
+ mask-size: contain;
+ mask-repeat: no-repeat;
+ background-color: $secondary-content;
+ }
+ }
+
+ &:hover,
+ &:focus {
+ cursor: pointer;
+ border-color: $quinary-content;
+
+ .mx_ThreadSummary_chevron {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ }
+
+ &::before {
+ align-self: center; // v-align the threads icon
+ }
+}
+
+// XXX: these classes are re-used outside of the component
+.mx_ThreadSummary_ThreadsAmount {
+ @mixin ThreadsAmount;
+}
+
+.mx_ThreadSummary_sender {
+ font-weight: $font-semi-bold;
+ line-height: $threadSummaryLineHeight;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.mx_ThreadSummary_content {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ margin-left: $spacing-4;
+ font-size: $font-12px;
+ line-height: $threadSummaryLineHeight;
+ color: $secondary-content;
+ flex: 1;
+}
+
+.mx_ThreadSummary_avatar {
+ margin-inline-end: $spacing-8;
+}
diff --git a/res/css/views/settings/_KeyboardShortcut.scss b/res/css/views/settings/_KeyboardShortcut.scss
new file mode 100644
index 00000000000..d3ca6cc9435
--- /dev/null
+++ b/res/css/views/settings/_KeyboardShortcut.scss
@@ -0,0 +1,36 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+Copyright 2021 Šimon Brandner
+
+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_KeyboardShortcut {
+ kbd {
+ 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/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss
index 09509f7d9eb..ae77a432e0a 100644
--- a/res/css/views/settings/_ProfileSettings.scss
+++ b/res/css/views/settings/_ProfileSettings.scss
@@ -61,8 +61,7 @@ limitations under the License.
margin-bottom: 28px;
> .mx_AccessibleButton_kind_link {
- padding-left: 0; // to align with left side
- padding-right: 0;
+ font-size: $font-14px;
margin-right: 10px;
}
}
diff --git a/res/css/views/settings/tabs/room/_NotificationSettingsTab.scss b/res/css/views/settings/tabs/room/_NotificationSettingsTab.scss
index 97b807863f9..036a3a5389f 100644
--- a/res/css/views/settings/tabs/room/_NotificationSettingsTab.scss
+++ b/res/css/views/settings/tabs/room/_NotificationSettingsTab.scss
@@ -50,11 +50,6 @@ limitations under the License.
font-size: $font-12px;
line-height: $font-15px;
margin-right: 32px;
-
- .mx_AccessibleButton_kind_link {
- padding: 0;
- font-size: inherit;
- }
}
}
diff --git a/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss
index 1ace6ec1514..1ba3a8599b8 100644
--- a/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss
@@ -21,22 +21,4 @@ limitations under the License.
justify-content: space-between;
align-items: center;
}
-
- kbd {
- 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/tabs/user/_LabsUserSettingsTab.scss b/res/css/views/settings/tabs/user/_LabsUserSettingsTab.scss
index 023c75c1866..6fa751a96f1 100644
--- a/res/css/views/settings/tabs/user/_LabsUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_LabsUserSettingsTab.scss
@@ -15,18 +15,18 @@ limitations under the License.
*/
.mx_LabsUserSettingsTab {
- .mx_SettingsTab_section {
- margin-top: 32px;
+ .mx_SettingsTab_subsectionText, .mx_SettingsTab_section {
+ margin-bottom: 30px;
+ }
- .mx_SettingsFlag {
- margin-right: 0; // remove right margin to align with beta cards
- display: flex;
- align-items: center;
- justify-content: space-between;
+ .mx_SettingsTab_section .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;
- }
+ .mx_ToggleSwitch {
+ float: unset;
}
}
}
diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss
index b5791560c7d..ea979ac074c 100644
--- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss
@@ -32,31 +32,5 @@ limitations under the License.
margin-top: 20px;
}
}
-
- .mx_AccessibleButton_kind_link {
- padding: 0;
- font-size: inherit;
- }
- }
-
- .mx_PreferencesUserSettingsTab_CommunityMigrator {
- margin-right: 200px;
-
- > div {
- font-weight: $font-semi-bold;
- font-size: $font-15px;
- line-height: $font-18px;
- color: $primary-content;
- margin: 16px 0;
-
- .mx_BaseAvatar {
- margin-right: 12px;
- vertical-align: middle;
- }
-
- .mx_AccessibleButton {
- float: right;
- }
- }
}
}
diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss
index a8e0e0e2f25..5ed772fa85f 100644
--- a/res/css/views/spaces/_SpaceBasicSettings.scss
+++ b/res/css/views/spaces/_SpaceBasicSettings.scss
@@ -63,9 +63,9 @@ limitations under the License.
> .mx_AccessibleButton_kind_link {
display: inline-block;
- padding: 0;
- margin: auto 16px;
+ margin: auto 18px;
color: #368bd6;
+ font-size: $font-14px; // See _SpaceSettingsDialog.scss
}
> .mx_SpaceBasicSettings_avatar_remove {
@@ -74,7 +74,6 @@ limitations under the License.
}
.mx_AccessibleButton_hasKind {
- padding: 8px 22px;
margin-left: auto;
display: block;
width: min-content;
diff --git a/res/css/views/spaces/_SpaceCreateMenu.scss b/res/css/views/spaces/_SpaceCreateMenu.scss
index 80c1c39da22..2b24b9a2dcf 100644
--- a/res/css/views/spaces/_SpaceCreateMenu.scss
+++ b/res/css/views/spaces/_SpaceCreateMenu.scss
@@ -43,6 +43,7 @@ $spacePanelWidth: 68px;
color: $secondary-content;
}
+ // XXX: Temporary for the Spaces release only
.mx_SpaceFeedbackPrompt {
border-top: 1px solid $input-border-color;
padding-top: 12px;
@@ -92,11 +93,6 @@ $spacePanelWidth: 68px;
width: min-content;
}
- .mx_AccessibleButton_kind_link {
- padding: 0;
- font-size: inherit;
- }
-
.mx_AccessibleButton_disabled {
cursor: not-allowed;
}
@@ -112,15 +108,11 @@ $spacePanelWidth: 68px;
position: relative;
font-size: inherit;
line-height: inherit;
- margin-right: auto;
+ margin-right: 8px;
}
.mx_AccessibleButton_kind_link {
color: $accent;
position: relative;
- padding: 0;
- margin-left: 8px;
- font-size: inherit;
- line-height: inherit;
}
}
diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss
index cb05b1a977b..5fd244c2778 100644
--- a/res/css/views/toasts/_IncomingCallToast.scss
+++ b/res/css/views/toasts/_IncomingCallToast.scss
@@ -90,27 +90,14 @@ limitations under the License.
gap: 12px;
.mx_IncomingCallToast_button {
- height: 24px;
+ @mixin CallButton;
padding: 0px 8px;
flex-shrink: 0;
flex-grow: 1;
- margin-right: 0;
font-size: $font-15px;
- line-height: $font-24px;
span {
padding: 8px 0;
- display: flex;
- align-items: center;
-
- &::before {
- content: '';
- display: inline-block;
- background-color: $button-fg-color;
- mask-position: center;
- mask-repeat: no-repeat;
- margin-right: 8px;
- }
}
&.mx_IncomingCallToast_button_accept span::before {
diff --git a/res/css/views/typography/_Heading.scss b/res/css/views/typography/_Heading.scss
index 9b7ddeaef3f..84a008c18f8 100644
--- a/res/css/views/typography/_Heading.scss
+++ b/res/css/views/typography/_Heading.scss
@@ -37,3 +37,11 @@ limitations under the License.
margin-inline: unset;
margin-block: unset;
}
+
+.mx_Heading_h4 {
+ font-size: $font-15px;
+ font-weight: $font-semi-bold;
+ line-height: $font-20px;
+ margin-inline: unset;
+ margin-block: unset;
+}
diff --git a/res/css/views/voip/_VideoLobby.scss b/res/css/views/voip/_VideoLobby.scss
new file mode 100644
index 00000000000..a708e79c90e
--- /dev/null
+++ b/res/css/views/voip/_VideoLobby.scss
@@ -0,0 +1,174 @@
+/*
+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_VideoLobby {
+ min-height: 0;
+ flex-grow: 1;
+ padding: $spacing-12;
+ color: $video-lobby-primary-content;
+ background-color: $video-lobby-background;
+ border-radius: 8px;
+
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: $spacing-32;
+
+ .mx_FacePile {
+ width: fit-content;
+ margin: $spacing-8 auto 0;
+
+ .mx_FacePile_faces .mx_BaseAvatar_image {
+ border-color: $video-lobby-background;
+ }
+ }
+
+ .mx_VideoLobby_preview {
+ position: relative;
+ width: 100%;
+ max-width: 800px;
+ aspect-ratio: 1.5;
+ background-color: $video-lobby-system;
+
+ border-radius: 20px;
+ overflow: hidden;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ .mx_BaseAvatar {
+ margin: $spacing-20;
+
+ // Override the explicit dimensions on the element so that this gets sized responsively
+ width: unset !important;
+ height: unset !important;
+ min-width: 0;
+ min-height: 0;
+ flex: 0 1 200px;
+ }
+
+ video {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ transform: scaleX(-1); // flip the image
+ background-color: black;
+ }
+
+ .mx_VideoLobby_controls {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+
+ background-color: rgba($video-lobby-background, 0.9);
+
+ display: flex;
+ justify-content: center;
+ gap: $spacing-24;
+
+ .mx_VideoLobby_deviceButtonWrapper {
+ position: relative;
+ margin: 6px 0 10px;
+
+ .mx_VideoLobby_deviceButton {
+ $size: 50px;
+
+ width: $size;
+ height: $size;
+
+ background-color: $video-lobby-primary-content;
+ border-radius: calc($size / 2);
+
+ &::before {
+ content: '';
+ display: inline-block;
+ mask-repeat: no-repeat;
+ mask-size: 20px;
+ mask-position: center;
+ background-color: $video-lobby-system;
+ height: 100%;
+ width: 100%;
+ }
+
+ &.mx_VideoLobby_deviceButton_audio::before {
+ mask-image: url('$(res)/img/voip/call-view/mic-off.svg');
+ }
+
+ &.mx_VideoLobby_deviceButton_video::before {
+ mask-image: url('$(res)/img/voip/call-view/cam-off.svg');
+ }
+ }
+
+ .mx_VideoLobby_deviceListButton {
+ $size: 15px;
+
+ position: absolute;
+ bottom: 0;
+ right: -2.5px;
+ width: $size;
+ height: $size;
+
+ background-color: $video-lobby-primary-content;
+ border-radius: calc($size / 2);
+
+ &::before {
+ content: '';
+ display: inline-block;
+ mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
+ mask-size: $size;
+ mask-position: center;
+ background-color: $video-lobby-system;
+ height: 100%;
+ width: 100%;
+ }
+ }
+
+ &.mx_VideoLobby_deviceButtonWrapper_active {
+ .mx_VideoLobby_deviceButton,
+ .mx_VideoLobby_deviceListButton {
+ background-color: $video-lobby-system;
+
+ &::before {
+ background-color: $video-lobby-primary-content;
+ }
+ }
+
+ .mx_VideoLobby_deviceButton {
+ &.mx_VideoLobby_deviceButton_audio::before {
+ mask-image: url('$(res)/img/voip/call-view/mic-on.svg');
+ }
+
+ &.mx_VideoLobby_deviceButton_video::before {
+ mask-image: url('$(res)/img/voip/call-view/cam-on.svg');
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .mx_VideoLobby_joinButton {
+ padding-left: 50px;
+ padding-right: 50px;
+ }
+}
diff --git a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2
index 128aac8139b..b3b20e9aa38 100644
Binary files a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 and b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 differ
diff --git a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2 b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2
index a95e89c0942..48f66733aa3 100644
Binary files a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2 and b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2 differ
diff --git a/res/img/betas/threads.png b/res/img/betas/threads.png
new file mode 100644
index 00000000000..f34fb5f8958
Binary files /dev/null and b/res/img/betas/threads.png differ
diff --git a/res/img/compound/chevron-right-12px.svg b/res/img/compound/chevron-right-12px.svg
new file mode 100644
index 00000000000..02f61f36ff3
--- /dev/null
+++ b/res/img/compound/chevron-right-12px.svg
@@ -0,0 +1,10 @@
+
diff --git a/res/img/element-icons/community-members.svg b/res/img/element-icons/group-members.svg
similarity index 100%
rename from res/img/element-icons/community-members.svg
rename to res/img/element-icons/group-members.svg
diff --git a/res/img/element-icons/minus-button.svg b/res/img/element-icons/minus-button.svg
index ca61c23b76b..6e7ea87c0b1 100644
--- a/res/img/element-icons/minus-button.svg
+++ b/res/img/element-icons/minus-button.svg
@@ -1,3 +1,3 @@
diff --git a/res/img/element-icons/plus-button.svg b/res/img/element-icons/plus-button.svg
index cbc25c4553e..9a14c85ee5e 100644
--- a/res/img/element-icons/plus-button.svg
+++ b/res/img/element-icons/plus-button.svg
@@ -1,3 +1,3 @@
diff --git a/res/img/element-icons/roomlist/hash-plus.svg b/res/img/element-icons/roomlist/hash-plus.svg
index 807c42cd809..326716ebe64 100644
--- a/res/img/element-icons/roomlist/hash-plus.svg
+++ b/res/img/element-icons/roomlist/hash-plus.svg
@@ -1,5 +1,4 @@
diff --git a/res/img/element-icons/roomlist/hash-video.svg b/res/img/element-icons/roomlist/hash-video.svg
new file mode 100644
index 00000000000..b0e1decf686
--- /dev/null
+++ b/res/img/element-icons/roomlist/hash-video.svg
@@ -0,0 +1,5 @@
+
diff --git a/res/img/element-icons/settings/flair.svg b/res/img/element-icons/settings/flair.svg
deleted file mode 100644
index e1ae44f386f..00000000000
--- a/res/img/element-icons/settings/flair.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/res/img/icons-groups.svg b/res/img/icons-groups.svg
deleted file mode 100644
index 8f89ba83c4c..00000000000
--- a/res/img/icons-groups.svg
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
diff --git a/res/img/location/map.svg b/res/img/location/map.svg
new file mode 100644
index 00000000000..67be3a35ad4
--- /dev/null
+++ b/res/img/location/map.svg
@@ -0,0 +1,9 @@
+
diff --git a/res/img/matrix.svg b/res/img/matrix.svg
new file mode 100644
index 00000000000..13adcab25a2
--- /dev/null
+++ b/res/img/matrix.svg
@@ -0,0 +1,153 @@
+
+
+
+
diff --git a/res/img/voip/signal-bars.svg b/res/img/voip/signal-bars.svg
new file mode 100644
index 00000000000..6802ba2d34b
--- /dev/null
+++ b/res/img/voip/signal-bars.svg
@@ -0,0 +1,5 @@
+
diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index bc64bca3889..ce187345942 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -7,7 +7,9 @@ $quaternary-content: #6F7882;
$quinary-content: #394049;
$system: #21262C;
+$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
$background: #15191E;
+$overlay-background: rgba($background, 0.85);
$panel-base: #8D97A5; // This color is not intended for use in the app
$panels: rgba($system, 0.9);
@@ -45,7 +47,7 @@ $info-plinth-bg-color: $header-panel-bg-color;
$event-selected-color: $system;
$topleftmenu-color: $primary-content;
$roomtopic-color: $text-secondary-color;
-$groupFilterPanel-bg-color: rgba(38, 39, 43, 0.82);
+$spacePanel-bg-color: rgba(38, 39, 43, 0.82);
$panel-gradient: rgba(34, 38, 46, 0), rgba(34, 38, 46, 1);
$h3-color: $primary-content;
$event-highlight-bg-color: #25271F;
@@ -54,7 +56,7 @@ $header-panel-text-primary-color: $text-secondary-color;
// Tooltip
// ********************
-$tooltip-timeline-bg-color: $groupFilterPanel-bg-color;
+$tooltip-timeline-bg-color: $spacePanel-bg-color;
$tooltip-timeline-fg-color: $primary-content;
// ********************
@@ -79,6 +81,11 @@ $settings-profile-button-bg-color: #e7e7e7;
$settings-subsection-fg-color: $text-secondary-color;
// ********************
+// Room
+// ********************
+$room-icon-unread-color: #fff;
+// ********************
+
// RoomHeader
// ********************
$roomheader-addroom-bg-color: rgba(92, 100, 112, 0.3);
@@ -181,11 +188,16 @@ $call-view-button-off-background: $primary-content;
$call-view-content-background: $quinary-content;
$video-feed-secondary-background: $system;
+
+$video-lobby-system: $system;
+$video-lobby-background: $background;
+$video-lobby-primary-content: $primary-content;
// ********************
// Location sharing
// ********************
$location-live-color: #5c56f5;
+$location-live-secondary-color: #deddfd;
// ********************
// Location sharing
diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss
index 04ecb32ecb7..9ee07c09685 100644
--- a/res/themes/legacy-dark/css/_legacy-dark.scss
+++ b/res/themes/legacy-dark/css/_legacy-dark.scss
@@ -1,5 +1,6 @@
// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A741
$system: #21262C;
+$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
// unified palette
// try to use these colors when possible
@@ -33,8 +34,8 @@ $rte-room-pill-color: $room-highlight-color;
$info-plinth-bg-color: $header-panel-bg-color;
$info-plinth-fg-color: #888;
-$groupFilterPanel-bg-color: $base-color;
-$inverted-bg-color: $groupFilterPanel-bg-color;
+$spacePanel-bg-color: $base-color;
+$inverted-bg-color: $spacePanel-bg-color;
// used by AddressSelector
$selected-color: $room-highlight-color;
@@ -85,6 +86,7 @@ $roomheader-addroom-bg-color: #3c4556;
$roomheader-addroom-fg-color: $text-primary-color;
$icon-button-color: $header-panel-text-primary-color;
$roomtopic-color: $text-secondary-color;
+$room-icon-unread-color: #fff;
// Legacy theme backports
$accent: #0DBD8B;
@@ -96,7 +98,10 @@ $tertiary-content: $tertiary-fg-color;
$quaternary-content: #6F7882;
$quinary-content: $quaternary-content;
$system: #21262C;
+$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
$background: $primary-bg-color;
+$overlay-background: rgba($background, 0.85);
+
$panels: rgba($system, 0.9);
$panel-base: #8D97A5; // This color is not intended for use in the app
$panel-selected: rgba($panel-base, 0.3);
@@ -116,12 +121,16 @@ $call-view-content-background: $quinary-content;
$video-feed-secondary-background: $system;
+$video-lobby-system: $system;
+$video-lobby-background: $background;
+$video-lobby-primary-content: $primary-content;
+
$roomlist-filter-active-bg-color: $panel-actions;
$roomlist-bg-color: $header-panel-bg-color;
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
-$groupFilterPanel-divider-color: $tertiary-content;
+$spacePanel-divider-color: $tertiary-content;
$roomtile-default-badge-bg-color: #61708b;
$roomtile-selected-bg-color: #1A1D23;
@@ -167,7 +176,7 @@ $reaction-row-button-selected-bg-color: #1f6954;
$kbd-border-color: #000000;
-$tooltip-timeline-bg-color: $groupFilterPanel-bg-color;
+$tooltip-timeline-bg-color: $spacePanel-bg-color;
$tooltip-timeline-fg-color: #ffffff;
$breadcrumb-placeholder-bg-color: #272c35;
@@ -193,6 +202,7 @@ $eventbubble-reply-color: #C1C6CD;
// ********************
$location-marker-color: #ffffff;
$location-live-color: #5c56f5;
+$location-live-secondary-color: #deddfd;
// ********************
// ***** Mixins! *****
diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss
index a12b13d0b8f..e1c475fc636 100644
--- a/res/themes/legacy-light/css/_legacy-light.scss
+++ b/res/themes/legacy-light/css/_legacy-light.scss
@@ -46,8 +46,8 @@ $info-plinth-fg-color: #888;
// left-panel style muted accent color
$secondary-accent-color: #f2f5f8;
-$groupFilterPanel-bg-color: #27303a;
-$inverted-bg-color: $groupFilterPanel-bg-color;
+$spacePanel-bg-color: #27303a;
+$inverted-bg-color: $spacePanel-bg-color;
// used by RoomDropTarget
$droptarget-bg-color: rgba(255, 255, 255, 0.5);
@@ -127,6 +127,7 @@ $roomheader-addroom-bg-color: #91a1c0;
$roomheader-addroom-fg-color: $accent-fg-color;
$icon-button-color: #91a1c0;
$roomtopic-color: #9e9e9e;
+$room-icon-unread-color: #737D8C;
// ********************
@@ -137,6 +138,7 @@ $roomtile-selected-bg-color: #fff;
$presence-away: #d9b072;
$presence-offline: #e3e8f0;
+$presence-busy: #FF5B55;
// Legacy theme backports
$accent: #0DBD8B;
@@ -148,7 +150,10 @@ $tertiary-content: $tertiary-fg-color;
$quaternary-content: #6F7882;
$quinary-content: $quaternary-content;
$system: #F4F6FA;
+$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
$background: $primary-bg-color;
+$overlay-background: rgba($background, 0.85);
+
$panels: rgba($system, 0.9);
$panel-base: #8D97A5; // This color is not intended for use in the app
$panel-selected: rgba($tertiary-content, 0.3);
@@ -174,6 +179,11 @@ $call-view-content-background: #21262C;
$video-feed-secondary-background: #394049; // XXX: Color from dark theme
+// All of these are from dark theme
+$video-lobby-system: #21262C;
+$video-lobby-background: #15191E;
+$video-lobby-primary-content: #FFFFFF;
+
$username-variant1-color: #368bd6;
$username-variant2-color: #ac3ba8;
$username-variant3-color: #03b381;
@@ -255,7 +265,7 @@ $reaction-row-button-selected-bg-color: #e9fff9;
$kbd-border-color: $message-action-bar-border-color;
-$tooltip-timeline-bg-color: $groupFilterPanel-bg-color;
+$tooltip-timeline-bg-color: $spacePanel-bg-color;
$tooltip-timeline-fg-color: #ffffff;
$breadcrumb-placeholder-bg-color: #e8eef5;
@@ -282,12 +292,13 @@ $eventbubble-reply-color: #C1C6CD;
// pinned events indicator
$pinned-color: $tertiary-content;
-$groupFilterPanel-divider-color: $tertiary-content;
+$spacePanel-divider-color: $tertiary-content;
// Location sharing
// ********************
$location-marker-color: #ffffff;
$location-live-color: #5c56f5;
+$location-live-secondary-color: #deddfd;
// ********************
// ***** Mixins! *****
diff --git a/res/themes/light-custom/css/_custom.scss b/res/themes/light-custom/css/_custom.scss
index 81b7fa62b87..e2d4fef8fe1 100644
--- a/res/themes/light-custom/css/_custom.scss
+++ b/res/themes/light-custom/css/_custom.scss
@@ -26,6 +26,8 @@ $secondary-content: var(--secondary-content, $secondary-content);
$tertiary-content: var(--tertiary-content, $tertiary-content);
$quaternary-content: var(--quaternary-content, $quaternary-content);
$quinary-content: var(--quinary-content, $quinary-content);
+// XXX: workaround for Safari 15.3 linear-gradient bug https://github.com/vector-im/element-web/issues/21670
+$system-transparent: var(--system-transparent, rgba($system, 0.0));
$system: var(--system, $system);
$background: var(--background, $background);
$panels: rgba($system, 0.9);
@@ -55,7 +57,7 @@ $roomheader-bg-color: var(--timeline-background-color);
$panel-actions: var(--roomlist-highlights-color);
//
// --sidebar-color
-$groupFilterPanel-bg-color: var(--sidebar-color);
+$spacePanel-bg-color: var(--sidebar-color);
$tooltip-timeline-bg-color: var(--sidebar-color);
$dialog-backdrop-color: var(--sidebar-color-50pct);
//
diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss
index 09c98ac8d41..3d09e400819 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -8,9 +8,22 @@
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
digits in flowed text to stand out.
TODO: Consider putting all emoji fonts to the end rather than the front. */
-$font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji';
-
-$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji';
+$font-family: 'Inter',
+'Twemoji',
+'Apple Color Emoji',
+'Segoe UI Emoji',
+'Arial',
+'Helvetica',
+sans-serif,
+'Noto Color Emoji';
+
+$monospace-font-family: 'Inconsolata',
+'Twemoji',
+'Apple Color Emoji',
+'Segoe UI Emoji',
+'Courier',
+monospace,
+'Noto Color Emoji';
// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120
// ********************
@@ -21,7 +34,9 @@ $quaternary-content: #c1c6cd;
$quinary-content: #E3E8F0;
$system: #F4F6FA;
+$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
$background: #ffffff;
+$overlay-background: rgba($background, 0.85);
$panels: rgba($system, 0.9);
$panel-selected: rgba($tertiary-content, 0.3);
@@ -57,7 +72,7 @@ $icon-button-color: $quaternary-content;
// Colors that aren't in Figma and are theme specific - we need to get rid of these
// ********************
$selection-fg-color: $background;
-$yellow-background: #fff8e3;
+$yellow-background: #fff8e3;
$secondary-accent-color: #f2f5f8;
$button-fg-color: $background;
$neutral-badge-color: #dbdbdb;
@@ -78,8 +93,9 @@ $info-plinth-bg-color: #f7f7f7;
$event-selected-color: #f6f7f8;
$topleftmenu-color: #212121;
$roomtopic-color: #9e9e9e;
-$groupFilterPanel-bg-color: rgba(232, 232, 232, 0.77);
-$panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1);
+$spacePanel-bg-color: rgba(232, 232, 232, 0.77);
+$panel-gradient: rgba(242, 245, 248, 0),
+rgba(242, 245, 248, 1);
$h3-color: #3d3b39;
$event-highlight-bg-color: $yellow-background;
$header-panel-text-primary-color: #91A1C0;
@@ -118,6 +134,11 @@ $settings-profile-button-bg-color: $menu-border-color;
$settings-subsection-fg-color: $muted-fg-color;
// ********************
+// Room
+// ********************
+$room-icon-unread-color: $secondary-content;
+// ********************
+
// RoomHeader
// ********************
$roomheader-addroom-bg-color: rgba(92, 100, 112, 0.2);
@@ -136,6 +157,7 @@ $rte-code-bg-color: rgba(0, 0, 0, 0.04);
// ********************
$presence-away: #d9b072;
$presence-offline: $quinary-content;
+$presence-busy: $alert;
// ********************
// Inputs
@@ -259,6 +281,11 @@ $call-view-content-background: #21262C;
$video-feed-secondary-background: #394049; // XXX: Color from dark theme
$voipcall-plinth-color: $system;
+
+// All of these are from dark theme
+$video-lobby-system: #21262C;
+$video-lobby-background: #15191E;
+$video-lobby-primary-content: #FFFFFF;
// ********************
// One-off colors
@@ -291,6 +318,7 @@ $focus-brightness: 105%;
:root {
--lp-background-blur: 40px;
}
+
// ********************
// Icon URLs
@@ -302,6 +330,7 @@ $copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
// ********************
$location-marker-color: #ffffff;
$location-live-color: #5c56f5;
+$location-live-secondary-color: #deddfd;
// ********************
// Mixins
@@ -324,8 +353,7 @@ $location-live-color: #5c56f5;
outline: none;
}
-@define-mixin mx_DialogButton_hover {
-}
+@define-mixin mx_DialogButton_hover {}
@define-mixin mx_DialogButton_danger {
background-color: $accent;
@@ -349,6 +377,7 @@ $location-live-color: #5c56f5;
color: $accent;
text-decoration: none;
}
+
// ********************
// diff highlight colors
@@ -360,4 +389,5 @@ $location-live-color: #5c56f5;
.hljs-deletion {
background: #fdd;
}
+
// ********************
diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile
index 447ed09b7c9..08c9153578b 100644
--- a/scripts/ci/Dockerfile
+++ b/scripts/ci/Dockerfile
@@ -1,3 +1,5 @@
+# Docker file for end-to-end tests
+
# Update on docker hub with the following commands in the directory of this file:
# If you're on linux amd64
# docker build -t vectorim/element-web-ci-e2etests-env:latest .
diff --git a/scripts/ci/install-deps.sh b/scripts/ci/install-deps.sh
index 0741ad2ab34..c10884361e9 100755
--- a/scripts/ci/install-deps.sh
+++ b/scripts/ci/install-deps.sh
@@ -1,5 +1,12 @@
#!/bin/bash
+# This installs other Matrix dependencies that are often
+# developed in parallel with react-sdk, using fetchdep.sh
+# for branch matching.
+# This will set up a working react-sdk environment, so is
+# used for running react-sdk standalone tests. To set up a
+# build of element-web, use layered.sh
+
set -ex
scripts/fetchdep.sh matrix-org matrix-js-sdk
diff --git a/scripts/ci/layered.sh b/scripts/ci/layered.sh
index 3e30cc808f7..e66eddf9aa4 100755
--- a/scripts/ci/layered.sh
+++ b/scripts/ci/layered.sh
@@ -1,7 +1,9 @@
#!/bin/bash
# Creates a layered environment with the full repo for the app and SDKs cloned
-# and linked.
+# and linked. This gives an element-web dev environment ready to build with
+# the current react-sdk branch and any matching branches of react-sdk's dependencies
+# so that changes can be tested in element-web.
# Note that this style is different from the recommended developer setup: this
# file nests js-sdk and element-web inside react-sdk, while the local
@@ -16,7 +18,8 @@ yarn link
yarn install --pure-lockfile
popd
-# Set up the js-sdk first
+# Also set up matrix-analytics-events so we get the latest from
+# the main branch or a branch with matching name
scripts/fetchdep.sh matrix-org matrix-analytics-events main
pushd matrix-analytics-events
yarn link
@@ -28,7 +31,6 @@ yarn link matrix-js-sdk
yarn link matrix-analytics-events
yarn link
yarn install --pure-lockfile
-yarn reskindex
# Finally, set up element-web
scripts/fetchdep.sh vector-im element-web
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index 15e998ddbed..737e87844f5 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -29,7 +29,7 @@ getPRInfo() {
if [ -n "$number" ]; then
echo "Getting info about a PR with number $number"
- apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
+ apiEndpoint="https://api.github.com/repos/${REPOSITORY:-"matrix-org/matrix-react-sdk"}/pulls/"
apiEndpoint+=$number
head=$(curl $apiEndpoint | jq -r '.head.label')
@@ -57,15 +57,18 @@ BRANCH_ARRAY=(${head//:/ })
TRY_ORG=$deforg
TRY_BRANCH=${BRANCH_ARRAY[0]}
if [[ "$head" == *":"* ]]; then
- TRY_ORG=${BRANCH_ARRAY[0]}
+ # ... but only match that fork if it's a real fork
+ if [ "${BRANCH_ARRAY[0]}" != "matrix-org" ]; then
+ TRY_ORG=${BRANCH_ARRAY[0]}
+ fi
TRY_BRANCH=${BRANCH_ARRAY[1]}
fi
clone ${TRY_ORG} $defrepo ${TRY_BRANCH}
# Try the target branch of the push or PR.
-if [ -n $GITHUB_BASE_REF ]; then
+if [ -n "$GITHUB_BASE_REF" ]; then
clone $deforg $defrepo $GITHUB_BASE_REF
-elif [ -n $BUILDKITE_PULL_REQUEST_BASE_BRANCH ]; then
+elif [ -n "$BUILDKITE_PULL_REQUEST_BASE_BRANCH" ]; then
clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
fi
diff --git a/scripts/make-react-component.js b/scripts/make-react-component.js
index 063914fafec..56616c33508 100755
--- a/scripts/make-react-component.js
+++ b/scripts/make-react-component.js
@@ -6,7 +6,7 @@ const path = require('path');
* Unsophisticated script to create a styled, unit-tested react component.
* -filePath / -f : path to the component to be created, including new component name, excluding extension, relative to src
* -withStyle / -s : optional, flag to create a style file for the component. Defaults to false.
- *
+ *
* eg:
* ```
* node srcipts/make-react-component.js -f components/toasts/NewToast -s
@@ -15,7 +15,7 @@ const path = require('path');
* - src/components/toasts/NewToast.tsx
* - test/components/toasts/NewToast-test.tsx
* - res/css/components/toasts/_NewToast.scss
- *
+ *
*/
const TEMPLATES = {
@@ -34,7 +34,6 @@ export default %%ComponentName%%;
import React from 'react';
import { mount } from 'enzyme';
-import '%%SkinnedSdkPath%%';
import %%ComponentName%% from '%%RelativeComponentPath%%';
describe('<%%ComponentName%% />', () => {
@@ -85,10 +84,8 @@ const makeFile = async ({
const relativePathToComponent = path.parse(path.relative(path.dirname(newFilePath), componentFilePath || ''));
const importComponentPath = path.join(relativePathToComponent.dir, relativePathToComponent.name);
- const skinnedSdkPath = path.relative(path.dirname(newFilePath), 'test/skinned-sdk')
-
try {
- await fs.writeFile(newFilePath, fillTemplate(template, componentName, importComponentPath, skinnedSdkPath), { flag: 'wx' });
+ await fs.writeFile(newFilePath, fillTemplate(template, componentName, importComponentPath), { flag: 'wx' });
console.log(`Created ${path.relative(process.cwd(), newFilePath)}`);
return newFilePath;
} catch (error) {
@@ -104,7 +101,6 @@ const makeFile = async ({
const fillTemplate = (template, componentName, relativeComponentFilePath, skinnedSdkPath) =>
template.replace(/%%ComponentName%%/g, componentName)
.replace(/%%RelativeComponentPath%%/g, relativeComponentFilePath)
- .replace(/%%SkinnedSdkPath%%/g, skinnedSdkPath)
const makeReactComponent = async () => {
diff --git a/scripts/reskindex.js b/scripts/reskindex.js
deleted file mode 100755
index 5eaec4d1d5e..00000000000
--- a/scripts/reskindex.js
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env node
-const fs = require('fs');
-const { promises: fsp } = fs;
-const path = require('path');
-const glob = require('glob');
-const util = require('util');
-const args = require('minimist')(process.argv);
-const chokidar = require('chokidar');
-
-const componentIndex = path.join('src', 'component-index.js');
-const componentIndexTmp = componentIndex+".tmp";
-const componentsDir = path.join('src', 'components');
-const componentJsGlob = '**/*.js';
-const componentTsGlob = '**/*.tsx';
-let prevFiles = [];
-
-async function reskindex() {
- const jsFiles = glob.sync(componentJsGlob, {cwd: componentsDir}).sort();
- const tsFiles = glob.sync(componentTsGlob, {cwd: componentsDir}).sort();
- const files = [...tsFiles, ...jsFiles];
- if (!filesHaveChanged(files, prevFiles)) {
- return;
- }
- prevFiles = files;
-
- const header = args.h || args.header;
-
- const strm = fs.createWriteStream(componentIndexTmp);
- // Wait for the open event to ensure the file descriptor is set
- await new Promise(resolve => strm.once("open", resolve));
-
- if (header) {
- strm.write(fs.readFileSync(header));
- strm.write('\n');
- }
-
- strm.write("/*\n");
- strm.write(" * THIS FILE IS AUTO-GENERATED\n");
- strm.write(" * You can edit it you like, but your changes will be overwritten,\n");
- strm.write(" * so you'd just be trying to swim upstream like a salmon.\n");
- strm.write(" * You are not a salmon.\n");
- strm.write(" */\n\n");
- strm.write("let components = {};\n");
-
- for (let i = 0; i < files.length; ++i) {
- const file = files[i].replace('.js', '').replace('.tsx', '');
-
- const moduleName = (file.replace(/\//g, '.'));
- const importName = moduleName.replace(/\./g, "$");
-
- strm.write("import " + importName + " from './components/" + file + "';\n");
- strm.write(importName + " && (components['"+moduleName+"'] = " + importName + ");");
- strm.write('\n');
- strm.uncork();
- }
-
- strm.write("export {components};\n");
- // Ensure the file has been fully written to disk before proceeding
- await util.promisify(fs.fsync)(strm.fd);
- await util.promisify(strm.end);
- await fsp.rename(componentIndexTmp, componentIndex);
-}
-
-// Expects both arrays of file names to be sorted
-function filesHaveChanged(files, prevFiles) {
- if (files.length !== prevFiles.length) {
- return true;
- }
- // Check for name changes
- for (let i = 0; i < files.length; i++) {
- if (prevFiles[i] !== files[i]) {
- return true;
- }
- }
- return false;
-}
-
-// Wrapper since await at the top level is not well supported yet
-function run() {
- (async function() {
- await reskindex();
- console.log("Reskindex completed");
- })();
-}
-
-// -w indicates watch mode where any FS events will trigger reskindex
-if (!args.w) {
- run();
- return;
-}
-
-let watchDebouncer = null;
-chokidar.watch(path.join(componentsDir, componentJsGlob)).on('all', (event, path) => {
- if (path === componentIndex) return;
- if (watchDebouncer) clearTimeout(watchDebouncer);
- watchDebouncer = setTimeout(run, 1000);
-});
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 00000000000..afeecf737be
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,16 @@
+sonar.projectKey=matrix-react-sdk
+sonar.organization=matrix-org
+
+# This is the name and version displayed in the SonarCloud UI.
+#sonar.projectName=matrix-react-sdk
+#sonar.projectVersion=1.0
+
+# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
+#sonar.sources=.
+
+# Encoding of the source code. Default is default system encoding
+#sonar.sourceEncoding=UTF-8
+
+sonar.sources=src,res
+sonar.tests=test,cypress
+sonar.exclusions=__mocks__,docs
diff --git a/src/@types/browser-encrypt-attachment.ts b/src/@types/browser-encrypt-attachment.ts
deleted file mode 100644
index a8249ab3505..00000000000
--- a/src/@types/browser-encrypt-attachment.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-Copyright 2021 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.
-*/
-
-declare module "browser-encrypt-attachment" {
- interface IInfo {
- v: string;
- key: {
- alg: string;
- key_ops: string[]; // eslint-disable-line camelcase
- kty: string;
- k: string;
- ext: boolean;
- };
- iv: string;
- hashes: {[alg: string]: string};
- }
-
- interface IEncryptedAttachment {
- data: ArrayBuffer;
- info: IInfo;
- }
-
- export function encryptAttachment(plaintextBuffer: ArrayBuffer): Promise;
- export function decryptAttachment(ciphertextBuffer: ArrayBuffer, info: IInfo): Promise;
-}
diff --git a/src/@types/common.ts b/src/@types/common.ts
index 991cfb7e085..b4d01a75a54 100644
--- a/src/@types/common.ts
+++ b/src/@types/common.ts
@@ -34,3 +34,18 @@ type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...0[]];
export type Leaves = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: Join> }[keyof T] : "";
+
+export type RecursivePartial = {
+ [P in keyof T]?:
+ T[P] extends (infer U)[] ? RecursivePartial[] :
+ T[P] extends object ? RecursivePartial :
+ T[P];
+};
+
+// Inspired by https://stackoverflow.com/a/60206860
+export type KeysWithObjectShape = {
+ [P in keyof Input]: Input[P] extends object
+ // Arrays are counted as objects - exclude them
+ ? (Input[P] extends Array ? never : P)
+ : never;
+}[keyof Input];
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 6ac72a2dd61..d0f266470c9 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -29,7 +29,6 @@ import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
import { IntegrationManagers } from "../integrations/IntegrationManagers";
import { ModalManager } from "../Modal";
import SettingsStore from "../settings/SettingsStore";
-import { ActiveRoomObserver } from "../ActiveRoomObserver";
import { Notifier } from "../Notifier";
import type { Renderer } from "react-dom";
import RightPanelStore from "../stores/right-panel/RightPanelStore";
@@ -50,9 +49,8 @@ import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake";
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
-import { Skinner } from "../Skinner";
import AutoRageshakeStore from "../stores/AutoRageshakeStore";
-import { ConfigOptions } from "../SdkConfig";
+import { IConfigOptions } from "../IConfigOptions";
/* eslint-disable @typescript-eslint/naming-convention */
@@ -63,7 +61,7 @@ declare global {
Olm: {
init: () => Promise;
};
- mxReactSdkConfig: ConfigOptions;
+ mxReactSdkConfig: IConfigOptions;
// Needed for Safari, unknown to TypeScript
webkitAudioContext: typeof AudioContext;
@@ -83,7 +81,6 @@ declare global {
mxDeviceListener: DeviceListener;
mxRoomListStore: RoomListStoreClass;
mxRoomListLayoutStore: RoomListLayoutStore;
- mxActiveRoomObserver: ActiveRoomObserver;
mxPlatformPeg: PlatformPeg;
mxIntegrationManagers: typeof IntegrationManagers;
singletonModalManager: ModalManager;
@@ -107,7 +104,6 @@ declare global {
mxSetupEncryptionStore?: SetupEncryptionStore;
mxRoomScrollStateStore?: RoomScrollStateStore;
mxActiveWidgetStore?: ActiveWidgetStore;
- mxSkinner?: Skinner;
mxOnRecaptchaLoaded?: () => void;
electron?: Electron;
mxSendSentryReport: (userText: string, issueUrl: string, error: Error) => Promise;
diff --git a/src/@types/groups.ts b/src/@types/groups.ts
deleted file mode 100644
index d3a7455e2fc..00000000000
--- a/src/@types/groups.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
-Copyright 2021 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 const CreateEventField = "io.element.migrated_from_community";
-
-export interface IGroupRoom {
- displayname: string;
- name?: string;
- roomId: string;
- canonicalAlias?: string;
- avatarUrl?: string;
- topic?: string;
- numJoinedMembers?: number;
- worldReadable?: boolean;
- guestCanJoin?: boolean;
- isPublic?: boolean;
-}
-
-/* eslint-disable camelcase */
-export interface IGroupSummary {
- profile: {
- avatar_url?: string;
- is_openly_joinable?: boolean;
- is_public?: boolean;
- long_description: string;
- name: string;
- short_description: string;
- };
- rooms_section: {
- rooms: unknown[];
- categories: Record;
- total_room_count_estimate: number;
- };
- user: {
- is_privileged: boolean;
- is_public: boolean;
- is_publicised: boolean;
- membership: string;
- };
- users_section: {
- users: unknown[];
- roles: Record;
- total_user_count_estimate: number;
- };
-}
-/* eslint-enable camelcase */
diff --git a/src/ActiveRoomObserver.ts b/src/ActiveRoomObserver.ts
deleted file mode 100644
index 4da0a6eac3a..00000000000
--- a/src/ActiveRoomObserver.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-Copyright 2017 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.
-*/
-
-import { EventSubscription } from 'fbemitter';
-import { logger } from "matrix-js-sdk/src/logger";
-
-import RoomViewStore from './stores/RoomViewStore';
-
-type Listener = (isActive: boolean) => void;
-
-/**
- * Consumes changes from the RoomViewStore and notifies specific things
- * about when the active room changes. Unlike listening for RoomViewStore
- * changes, you can subscribe to only changes relevant to a particular
- * room.
- *
- * TODO: If we introduce an observer for something else, factor out
- * the adding / removing of listeners & emitting into a common class.
- */
-export class ActiveRoomObserver {
- private listeners: {[key: string]: Listener[]} = {};
- private _activeRoomId = RoomViewStore.getRoomId();
- private readonly roomStoreToken: EventSubscription;
-
- constructor() {
- // TODO: We could self-destruct when the last listener goes away, or at least stop listening.
- this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
- }
-
- public get activeRoomId(): string {
- return this._activeRoomId;
- }
-
- public addListener(roomId, listener) {
- if (!this.listeners[roomId]) this.listeners[roomId] = [];
- this.listeners[roomId].push(listener);
- }
-
- public removeListener(roomId, listener) {
- if (this.listeners[roomId]) {
- const i = this.listeners[roomId].indexOf(listener);
- if (i > -1) {
- this.listeners[roomId].splice(i, 1);
- }
- } else {
- logger.warn("Unregistering unrecognised listener (roomId=" + roomId + ")");
- }
- }
-
- private emit(roomId, isActive: boolean) {
- if (!this.listeners[roomId]) return;
-
- for (const l of this.listeners[roomId]) {
- l.call(null, isActive);
- }
- }
-
- private onRoomViewStoreUpdate = () => {
- // emit for the old room ID
- if (this._activeRoomId) this.emit(this._activeRoomId, false);
-
- // update our cache
- this._activeRoomId = RoomViewStore.getRoomId();
-
- // and emit for the new one
- if (this._activeRoomId) this.emit(this._activeRoomId, true);
- };
-}
-
-if (window.mxActiveRoomObserver === undefined) {
- window.mxActiveRoomObserver = new ActiveRoomObserver();
-}
-export default window.mxActiveRoomObserver;
diff --git a/src/Analytics.tsx b/src/Analytics.tsx
index 09cb78d980b..639950574df 100644
--- a/src/Analytics.tsx
+++ b/src/Analytics.tsx
@@ -1,6 +1,6 @@
/*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
-Copyright 2020 The Matrix.org Foundation C.I.C.
+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.
@@ -17,13 +17,17 @@ limitations under the License.
import React from 'react';
import { logger } from "matrix-js-sdk/src/logger";
+import { Optional } from "matrix-events-sdk";
import { getCurrentLanguage, _t, _td, IVariables } from './languageHandler';
import PlatformPeg from './PlatformPeg';
import SdkConfig from './SdkConfig';
import Modal from './Modal';
-import * as sdk from './index';
+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)\/.*$/;
@@ -166,7 +170,6 @@ const HEARTBEAT_INTERVAL = 30 * 1000; // seconds
export class Analytics {
private baseUrl: URL = null;
- private siteId: string = null;
private visitVariables: Record = {}; // {[id: number]: [name: string, value: string]}
private firstPage = true;
private heartbeatIntervalID: number = null;
@@ -194,8 +197,12 @@ export class Analytics {
}
public canEnable() {
- const config = SdkConfig.get();
- return navigator.doNotTrack !== "1" && config && config.piwik && config.piwik.url && config.piwik.siteId;
+ const piwikConfig = SdkConfig.get("piwik");
+ let piwik: Optional>>;
+ if (typeof piwikConfig === 'object') {
+ piwik = new SnakedObject(piwikConfig);
+ }
+ return navigator.doNotTrack !== "1" && piwik?.get("site_id");
}
/**
@@ -205,12 +212,16 @@ export class Analytics {
public async enable() {
if (!this.disabled) return;
if (!this.canEnable()) return;
- const config = SdkConfig.get();
+ const piwikConfig = SdkConfig.get("piwik");
+ let piwik: Optional>>;
+ if (typeof piwikConfig === 'object') {
+ piwik = new SnakedObject(piwikConfig);
+ }
- this.baseUrl = new URL("piwik.php", config.piwik.url);
+ 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", config.piwik.siteId); // 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
@@ -348,10 +359,14 @@ export class Analytics {
public setLoggedIn(isGuest: boolean, homeserverUrl: string) {
if (this.disabled) return;
- const config = SdkConfig.get();
- if (!config.piwik) return;
+ const piwikConfig = SdkConfig.get("piwik");
+ let piwik: Optional>>;
+ if (typeof piwikConfig === 'object') {
+ piwik = new SnakedObject(piwikConfig);
+ }
+ if (!piwik) return;
- const whitelistedHSUrls = config.piwik.whitelistedHSUrls || [];
+ const whitelistedHSUrls = piwik.get("whitelisted_hs_urls", "whitelistedHSUrls") || [];
this.setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
this.setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
@@ -391,9 +406,12 @@ export class Analytics {
{ expl: _td('Your device resolution'), value: resolution },
];
- // FIXME: Using an import will result in test failures
- const cookiePolicyUrl = SdkConfig.get().piwik?.policyUrl;
- const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
+ 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.",
{},
@@ -428,7 +446,7 @@ export class Analytics {
{ _t('Where this page includes identifiable information, such as a room, '
- + 'user or group ID, that data is removed before being sent to the server.') }
+ + 'user ID, that data is removed before being sent to the server.') }
,
});
diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx
index 166e4f21e39..07a06f20d44 100644
--- a/src/AsyncWrapper.tsx
+++ b/src/AsyncWrapper.tsx
@@ -17,9 +17,11 @@ limitations under the License.
import React, { ComponentType } from "react";
import { logger } from "matrix-js-sdk/src/logger";
-import * as sdk from './index';
import { _t } from './languageHandler';
import { IDialogProps } from "./components/views/dialogs/IDialogProps";
+import BaseDialog from "./components/views/dialogs/BaseDialog";
+import DialogButtons from "./components/views/elements/DialogButtons";
+import Spinner from "./components/views/elements/Spinner";
type AsyncImport = { default: T };
@@ -78,9 +80,6 @@ export default class AsyncWrapper extends React.Component {
const Component = this.state.component;
return ;
} else if (this.state.error) {
- // FIXME: Using an import will result in test failures
- const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
- const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return
{ _t("Unable to load! Check your network connectivity and try again.") }
{
;
} else {
// show a spinner until the component is loaded.
- const Spinner = sdk.getComponent("elements.Spinner");
return ;
}
}
diff --git a/src/Avatar.ts b/src/Avatar.ts
index 310fec5f4c9..86560713aed 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -22,7 +22,6 @@ import { split } from "lodash";
import DMRoomMap from './utils/DMRoomMap';
import { mediaFromMxc } from "./customisations/Media";
-import SpaceStore from "./stores/spaces/SpaceStore";
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
export function avatarUrlForMember(
@@ -58,7 +57,7 @@ function isValidHexColor(color: string): boolean {
return typeof color === "string" &&
(color.length === 7 || color.length === 9) &&
color.charAt(0) === "#" &&
- !color.substr(1).split("").some(c => isNaN(parseInt(c, 16)));
+ !color.slice(1).split("").some(c => isNaN(parseInt(c, 16)));
}
function urlForColor(color: string): string {
@@ -140,7 +139,7 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
}
// space rooms cannot be DMs so skip the rest
- if (SpaceStore.spacesEnabled && room.isSpaceRoom()) return null;
+ if (room.isSpaceRoom()) return null;
// If the room is not a DM don't fallback to a member avatar
if (!DMRoomMap.shared().getUserIdForRoomId(room.roomId)) return null;
diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts
index 40ac9d11996..b7f52d38952 100644
--- a/src/BasePlatform.ts
+++ b/src/BasePlatform.ts
@@ -32,6 +32,7 @@ import { hideToast as hideUpdateToast } from "./toasts/UpdateToast";
import { MatrixClientPeg } from "./MatrixClientPeg";
import { idbLoad, idbSave, idbDelete } from "./utils/StorageManager";
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
+import { IConfigOptions } from "./IConfigOptions";
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
@@ -62,7 +63,7 @@ export default abstract class BasePlatform {
this.startUpdateCheck = this.startUpdateCheck.bind(this);
}
- abstract getConfig(): Promise<{}>;
+ abstract getConfig(): Promise;
abstract getDefaultDeviceDisplayName(): string;
@@ -144,6 +145,13 @@ export default abstract class BasePlatform {
return false;
}
+ /**
+ * Returns true if platform allows overriding native context menus
+ */
+ public allowOverridingNativeContextMenus(): boolean {
+ return false;
+ }
+
/**
* Returns true if the platform supports displaying
* notifications, otherwise false.
diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx
index 60094262edb..2b934251b72 100644
--- a/src/CallHandler.tsx
+++ b/src/CallHandler.tsx
@@ -1,7 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017, 2018 New Vector Ltd
-Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
Copyright 2021 Šimon Brandner
Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,6 @@ limitations under the License.
*/
import React from 'react';
-import { base32 } from "rfc4648";
import {
CallError,
CallErrorCode,
@@ -29,7 +28,6 @@ import {
MatrixCall,
} from "matrix-js-sdk/src/webrtc/call";
import { logger } from 'matrix-js-sdk/src/logger';
-import { randomLowercaseString, randomUppercaseString } from "matrix-js-sdk/src/randomstring";
import EventEmitter from 'events';
import { RuleId, TweakName, Tweaks } from "matrix-js-sdk/src/@types/PushRules";
import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor';
@@ -42,12 +40,10 @@ import { _t } from './languageHandler';
import dis from './dispatcher/dispatcher';
import WidgetUtils from './utils/WidgetUtils';
import SettingsStore from './settings/SettingsStore';
-import { Jitsi } from "./widgets/Jitsi";
import { WidgetType } from "./widgets/WidgetType";
import { SettingLevel } from "./settings/SettingLevel";
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
-import InviteDialog, { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialog";
import WidgetStore from "./stores/WidgetStore";
import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
@@ -57,12 +53,15 @@ import { Action } from './dispatcher/actions';
import VoipUserMapper from './VoipUserMapper';
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
import SdkConfig from './SdkConfig';
-import { ensureDMExists, findDMForUser } from './createRoom';
+import { ensureDMExists } from './createRoom';
import { Container, WidgetLayoutStore } from './stores/widgets/WidgetLayoutStore';
import IncomingCallToast, { getIncomingCallToastKey } from './toasts/IncomingCallToast';
import ToastStore from './stores/ToastStore';
import Resend from './Resend';
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
+import { findDMForUser } from "./utils/direct-messages";
+import { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialogTypes";
+import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
export const PROTOCOL_PSTN = 'm.protocol.pstn';
export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';
@@ -70,9 +69,6 @@ export const PROTOCOL_SIP_NATIVE = 'im.vector.protocol.sip_native';
export const PROTOCOL_SIP_VIRTUAL = 'im.vector.protocol.sip_virtual';
const CHECK_PROTOCOLS_ATTEMPTS = 3;
-// Event type for room account data and room creation content used to mark rooms as virtual rooms
-// (and store the ID of their native room)
-export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room';
enum AudioID {
Ring = 'ringAudio',
@@ -125,10 +121,6 @@ export default class CallHandler extends EventEmitter {
private supportsPstnProtocol = null;
private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol
private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
- private pstnSupportCheckTimer: number;
- // For rooms we've been invited to, true if they're from virtual user, false if we've checked and they aren't.
- private invitedRoomsAreVirtual = new Map();
- private invitedRoomCheckInProgress = false;
// Map of the asserted identity users after we've looked them up using the API.
// We need to be be able to determine the mapped room synchronously, so we
@@ -255,7 +247,7 @@ export default class CallHandler extends EventEmitter {
logger.log("Failed to check for protocol support and no retries remain: assuming no support", e);
} else {
logger.log("Failed to check for protocol support: will retry", e);
- this.pstnSupportCheckTimer = setTimeout(() => {
+ setTimeout(() => {
this.checkProtocols(maxTries - 1);
}, 10000);
}
@@ -263,7 +255,7 @@ export default class CallHandler extends EventEmitter {
}
private shouldObeyAssertedfIdentity(): boolean {
- return SdkConfig.get()['voip']?.obeyAssertedIdentity;
+ return SdkConfig.getObject("voip")?.get("obey_asserted_identity");
}
public getSupportsPstnProtocol(): boolean {
@@ -1027,65 +1019,26 @@ export default class CallHandler extends EventEmitter {
return false;
}
- private async placeJitsiCall(roomId: string, type: string): Promise {
- logger.info("Place conference call in " + roomId);
+ private async placeJitsiCall(roomId: string, type: CallType): Promise {
+ const client = MatrixClientPeg.get();
+ logger.info(`Place conference call in ${roomId}`);
Analytics.trackEvent('voip', 'placeConferenceCall');
- dis.dispatch({
- action: 'appsDrawer',
- show: true,
- });
+ dis.dispatch({ action: 'appsDrawer', show: true });
- // prevent double clicking the call button
- const room = MatrixClientPeg.get().getRoom(roomId);
- const jitsiWidget = WidgetStore.instance.getApps(roomId).find((app) => WidgetType.JITSI.matches(app.type));
- if (jitsiWidget) {
- // If there already is a Jitsi widget pin it
- WidgetLayoutStore.instance.moveToContainer(room, jitsiWidget, Container.Top);
+ // Prevent double clicking the call button
+ const widget = WidgetStore.instance.getApps(roomId).find(app => WidgetType.JITSI.matches(app.type));
+ if (widget) {
+ // If there already is a Jitsi widget, pin it
+ WidgetLayoutStore.instance.moveToContainer(client.getRoom(roomId), widget, Container.Top);
return;
}
- const jitsiDomain = Jitsi.getInstance().preferredDomain;
- const jitsiAuth = await Jitsi.getInstance().getJitsiAuth();
- let confId;
- if (jitsiAuth === 'openidtoken-jwt') {
- // Create conference ID from room ID
- // For compatibility with Jitsi, use base32 without padding.
- // More details here:
- // https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
- confId = base32.stringify(Buffer.from(roomId), { pad: false });
- } else {
- // Create a random conference ID
- const random = randomUppercaseString(1) + randomLowercaseString(23);
- confId = 'Jitsi' + random;
- }
-
- let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({ auth: jitsiAuth });
-
- // TODO: Remove URL hacks when the mobile clients eventually support v2 widgets
- const parsedUrl = new URL(widgetUrl);
- parsedUrl.search = ''; // set to empty string to make the URL class use searchParams instead
- parsedUrl.searchParams.set('confId', confId);
- widgetUrl = parsedUrl.toString();
-
- const widgetData = {
- conferenceId: confId,
- isAudioOnly: type === 'voice',
- domain: jitsiDomain,
- auth: jitsiAuth,
- roomName: room.name,
- };
-
- const widgetId = (
- 'jitsi_' +
- MatrixClientPeg.get().credentials.userId +
- '_' +
- Date.now()
- );
-
- WidgetUtils.setRoomWidget(roomId, widgetId, WidgetType.JITSI, widgetUrl, 'Jitsi', widgetData).then(() => {
+ try {
+ const userId = client.credentials.userId;
+ await WidgetUtils.addJitsiWidget(roomId, type, 'Jitsi', `jitsi_${userId}_${Date.now()}`);
logger.log('Jitsi widget added');
- }).catch((e) => {
+ } catch (e) {
if (e.errcode === 'M_FORBIDDEN') {
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
title: _t('Permission Required'),
@@ -1093,7 +1046,7 @@ export default class CallHandler extends EventEmitter {
});
}
logger.error(e);
- });
+ }
}
public terminateCallApp(roomId: string): void {
@@ -1127,7 +1080,7 @@ export default class CallHandler extends EventEmitter {
const jitsiWidgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type));
jitsiWidgets.forEach(w => {
- const messaging = WidgetMessagingStore.instance.getMessagingForId(w.id);
+ const messaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(w));
if (!messaging) return; // more "should never happen" words
messaging.transport.send(ElementWidgetActions.HangupCall, {});
@@ -1140,14 +1093,17 @@ export default class CallHandler extends EventEmitter {
*/
public showTransferDialog(call: MatrixCall): void {
call.setRemoteOnHold(true);
- const { finished } = Modal.createTrackedDialog(
- 'Transfer Call', '', InviteDialog, { kind: KIND_CALL_TRANSFER, call },
- /*className=*/"mx_InviteDialog_transferWrapper", /*isPriority=*/false, /*isStatic=*/true,
- );
- finished.then((results: boolean[]) => {
- if (results.length === 0 || results[0] === false) {
- call.setRemoteOnHold(false);
- }
+ dis.dispatch({
+ action: Action.OpenInviteDialog,
+ kind: KIND_CALL_TRANSFER,
+ call,
+ analyticsName: "Transfer Call",
+ className: "mx_InviteDialog_transferWrapper",
+ onFinishedCallback: (results) => {
+ if (results.length === 0 || results[0] === false) {
+ call.setRemoteOnHold(false);
+ }
+ },
});
}
diff --git a/src/ContentMessages.tsx b/src/ContentMessages.ts
similarity index 78%
rename from src/ContentMessages.tsx
rename to src/ContentMessages.ts
index 8b0ddc83688..1d54b1adc3a 100644
--- a/src/ContentMessages.tsx
+++ b/src/ContentMessages.ts
@@ -16,22 +16,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { IUploadOpts } from "matrix-js-sdk/src/@types/requests";
-import { MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
-import encrypt from "browser-encrypt-attachment";
+import { MsgType } from "matrix-js-sdk/src/@types/event";
+import encrypt from "matrix-encrypt-attachment";
import extractPngChunks from "png-chunks-extract";
import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
import { logger } from "matrix-js-sdk/src/logger";
-import { IEventRelation, ISendEventResponse } from "matrix-js-sdk/src/matrix";
+import { IEventRelation, ISendEventResponse, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
import { IEncryptedFile, IMediaEventInfo } from "./customisations/models/IMediaEventContent";
import dis from './dispatcher/dispatcher';
-import * as sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
-import RoomViewStore from './stores/RoomViewStore';
import Spinner from "./components/views/elements/Spinner";
import { Action } from "./dispatcher/actions";
import {
@@ -42,24 +40,23 @@ import {
UploadStartedPayload,
} from "./dispatcher/payloads/UploadPayload";
import { IUpload } from "./models/IUpload";
-import { BlurhashEncoder } from "./BlurhashEncoder";
import SettingsStore from "./settings/SettingsStore";
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
import { TimelineRenderingType } from "./contexts/RoomContext";
-
-const MAX_WIDTH = 800;
-const MAX_HEIGHT = 600;
+import { RoomViewStore } from "./stores/RoomViewStore";
+import { addReplyToMessageContent } from "./utils/Reply";
+import ErrorDialog from "./components/views/dialogs/ErrorDialog";
+import UploadFailureDialog from "./components/views/dialogs/UploadFailureDialog";
+import UploadConfirmDialog from "./components/views/dialogs/UploadConfirmDialog";
+import { createThumbnail } from "./utils/image-media";
+import { attachRelation } from "./components/views/rooms/SendMessageComposer";
// scraped out of a macOS hidpi (5660ppm) screenshot png
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01];
-export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
-
export class UploadCanceledError extends Error {}
-type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
-
interface IMediaConfig {
"m.upload.size"?: number;
}
@@ -75,101 +72,6 @@ interface IContent {
url?: string;
}
-interface IThumbnail {
- info: {
- // eslint-disable-next-line camelcase
- thumbnail_info: {
- w: number;
- h: number;
- mimetype: string;
- size: number;
- };
- w: number;
- h: number;
- [BLURHASH_FIELD]: string;
- };
- thumbnail: Blob;
-}
-
-/**
- * Create a thumbnail for a image DOM element.
- * The image will be smaller than MAX_WIDTH and MAX_HEIGHT.
- * The thumbnail will have the same aspect ratio as the original.
- * Draws the element into a canvas using CanvasRenderingContext2D.drawImage
- * Then calls Canvas.toBlob to get a blob object for the image data.
- *
- * Since it needs to calculate the dimensions of the source image and the
- * thumbnailed image it returns an info object filled out with information
- * about the original image and the thumbnail.
- *
- * @param {HTMLElement} element The element to thumbnail.
- * @param {number} inputWidth The width of the image in the input element.
- * @param {number} inputHeight the width of the image in the input element.
- * @param {String} mimeType The mimeType to save the blob as.
- * @return {Promise} A promise that resolves with an object with an info key
- * and a thumbnail key.
- */
-async function createThumbnail(
- element: ThumbnailableElement,
- inputWidth: number,
- inputHeight: number,
- mimeType: string,
-): Promise {
- let targetWidth = inputWidth;
- let targetHeight = inputHeight;
- if (targetHeight > MAX_HEIGHT) {
- targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
- targetHeight = MAX_HEIGHT;
- }
- if (targetWidth > MAX_WIDTH) {
- targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
- targetWidth = MAX_WIDTH;
- }
-
- let canvas: HTMLCanvasElement | OffscreenCanvas;
- let context: CanvasRenderingContext2D;
- try {
- canvas = new window.OffscreenCanvas(targetWidth, targetHeight);
- context = canvas.getContext("2d");
- } catch (e) {
- // Fallback support for other browsers (Safari and Firefox for now)
- canvas = document.createElement("canvas");
- (canvas as HTMLCanvasElement).width = targetWidth;
- (canvas as HTMLCanvasElement).height = targetHeight;
- context = canvas.getContext("2d");
- }
-
- context.drawImage(element, 0, 0, targetWidth, targetHeight);
-
- let thumbnailPromise: Promise;
-
- if (window.OffscreenCanvas) {
- thumbnailPromise = (canvas as OffscreenCanvas).convertToBlob({ type: mimeType });
- } else {
- thumbnailPromise = new Promise(resolve => (canvas as HTMLCanvasElement).toBlob(resolve, mimeType));
- }
-
- const imageData = context.getImageData(0, 0, targetWidth, targetHeight);
- // thumbnailPromise and blurhash promise are being awaited concurrently
- const blurhash = await BlurhashEncoder.instance.getBlurhash(imageData);
- const thumbnail = await thumbnailPromise;
-
- return {
- info: {
- thumbnail_info: {
- w: targetWidth,
- h: targetHeight,
- mimetype: thumbnail.type,
- size: thumbnail.size,
- },
- w: inputWidth,
- h: inputHeight,
- [BLURHASH_FIELD]: blurhash,
- },
- thumbnail,
- };
-}
-
/**
* Load a file into a newly created image element.
*
@@ -246,15 +148,20 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag
const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType);
const imageInfo = result.info;
- // we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from.
- const sizeDifference = imageFile.size - imageInfo.thumbnail_info.size;
- if (
- imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL || // image is small enough already
- (sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE && // thumbnail is not sufficiently smaller than original
- sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT))
- ) {
- delete imageInfo["thumbnail_info"];
- return imageInfo;
+ // For lesser supported image types, always include the thumbnail even if it is larger
+ if (!["image/avif", "image/webp"].includes(imageFile.type)) {
+ // we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from.
+ const sizeDifference = imageFile.size - imageInfo.thumbnail_info.size;
+ if (
+ // image is small enough already
+ imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL ||
+ // thumbnail is not sufficiently smaller than original
+ (sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE &&
+ sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT))
+ ) {
+ delete imageInfo["thumbnail_info"];
+ return imageInfo;
+ }
}
const uploadResult = await uploadFile(matrixClient, roomId, result.thumbnail);
@@ -291,7 +198,14 @@ function loadVideoElement(videoFile): Promise {
reject(e);
};
- video.src = ev.target.result as string;
+ let dataUrl = ev.target.result as string;
+ // Chrome chokes on quicktime but likes mp4, and `file.type` is
+ // read only, so do this horrible hack to unbreak quicktime
+ if (dataUrl.startsWith("data:video/quicktime;")) {
+ dataUrl = dataUrl.replace("data:video/quicktime;", "data:video/mp4;");
+ }
+
+ video.src = dataUrl;
video.load();
video.play();
};
@@ -456,25 +370,7 @@ export default class ContentMessages {
return;
}
- const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
- if (isQuoting) {
- // FIXME: Using an import will result in Element crashing
- const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
- const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Reply Warning', '', QuestionDialog, {
- title: _t('Replying With Files'),
- description: (
-
{ _t(
- 'At this time it is not possible to reply with a file. ' +
- 'Would you like to upload this file without replying?',
- ) }
- ),
- hasCancelButton: true,
- button: _t("Continue"),
- });
- const [shouldUpload] = await finished;
- if (!shouldUpload) return;
- }
-
+ const replyToEvent = RoomViewStore.instance.getQuotingEvent();
if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
await this.ensureMediaConfigFetched(matrixClient);
@@ -493,8 +389,6 @@ export default class ContentMessages {
}
if (tooBigFiles.length > 0) {
- // FIXME: Using an import will result in Element crashing
- const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, {
badFiles: tooBigFiles,
totalFiles: files.length,
@@ -511,8 +405,6 @@ export default class ContentMessages {
for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i];
if (!uploadAll) {
- // FIXME: Using an import will result in Element crashing
- const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
const { finished } = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation',
'', UploadConfirmDialog, {
file,
@@ -527,7 +419,16 @@ export default class ContentMessages {
}
}
- promBefore = this.sendContentToRoom(file, roomId, relation, matrixClient, promBefore);
+ promBefore = this.sendContentToRoom(file, roomId, relation, matrixClient, replyToEvent, promBefore);
+ }
+
+ if (replyToEvent) {
+ // Clear event being replied to
+ dis.dispatch({
+ action: "reply_to_event",
+ event: null,
+ context,
+ });
}
// Focus the correct composer
@@ -568,6 +469,7 @@ export default class ContentMessages {
roomId: string,
relation: IEventRelation | undefined,
matrixClient: MatrixClient,
+ replyToEvent: MatrixEvent | undefined,
promBefore: Promise,
) {
const content: IContent = {
@@ -578,8 +480,11 @@ export default class ContentMessages {
msgtype: "", // set later
};
- if (relation) {
- content["m.relates_to"] = relation;
+ attachRelation(content, relation);
+ if (replyToEvent) {
+ addReplyToMessageContent(content, replyToEvent, {
+ includeLegacyFallback: false,
+ });
}
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
@@ -658,7 +563,7 @@ export default class ContentMessages {
return promBefore;
}).then(function() {
if (upload.canceled) throw new UploadCanceledError();
- const threadId = relation?.rel_type === RelationType.Thread
+ const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name
? relation.event_id
: null;
const prom = matrixClient.sendMessage(roomId, threadId, content);
@@ -678,8 +583,6 @@ export default class ContentMessages {
{ fileName: upload.fileName },
);
}
- // FIXME: Using an import will result in Element crashing
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
title: _t('Upload Failed'),
description: desc,
diff --git a/src/DateUtils.ts b/src/DateUtils.ts
index c9f33b2eee7..20227354f02 100644
--- a/src/DateUtils.ts
+++ b/src/DateUtils.ts
@@ -201,3 +201,25 @@ export function formatRelativeTime(date: Date, showTwelveHour = false): string {
return relativeDate;
}
}
+
+/**
+ * Formats duration in ms to human readable string
+ * Returns value in biggest possible unit (day, hour, min, second)
+ * Rounds values up until unit threshold
+ * ie. 23:13:57 -> 23h, 24:13:57 -> 1d, 44:56:56 -> 2d
+ */
+const MINUTE_MS = 60000;
+const HOUR_MS = MINUTE_MS * 60;
+const DAY_MS = HOUR_MS * 24;
+export function formatDuration(durationMs: number): string {
+ if (durationMs >= DAY_MS) {
+ return _t('%(value)sd', { value: Math.round(durationMs / DAY_MS) });
+ }
+ if (durationMs >= HOUR_MS) {
+ return _t('%(value)sh', { value: Math.round(durationMs / HOUR_MS) });
+ }
+ if (durationMs >= MINUTE_MS) {
+ return _t('%(value)sm', { value: Math.round(durationMs / MINUTE_MS) });
+ }
+ return _t('%(value)ss', { value: Math.round(durationMs / 1000) });
+}
diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts
index 05f86453f1f..adba51d1352 100644
--- a/src/DeviceListener.ts
+++ b/src/DeviceListener.ts
@@ -36,9 +36,9 @@ import {
} from "./toasts/UnverifiedSessionToast";
import { accessSecretStorage, isSecretStorageBeingAccessed } from "./SecurityManager";
import { isSecureBackupRequired } from './utils/WellKnownUtils';
-import { isLoggedIn } from './components/structures/MatrixChat';
import { ActionPayload } from "./dispatcher/payloads";
import { Action } from "./dispatcher/actions";
+import { isLoggedIn } from "./utils/login";
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
diff --git a/src/Editing.ts b/src/Editing.ts
new file mode 100644
index 00000000000..57e58cc2a75
--- /dev/null
+++ b/src/Editing.ts
@@ -0,0 +1,20 @@
+/*
+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 { TimelineRenderingType } from "./contexts/RoomContext";
+
+export const editorRoomKey = (roomId: string, context: TimelineRenderingType) => `mx_edit_room_${roomId}_${context}`;
+export const editorStateKey = (eventId: string) => `mx_edit_state_${eventId}`;
diff --git a/src/GroupAddressPicker.js b/src/GroupAddressPicker.js
deleted file mode 100644
index 6430090819d..00000000000
--- a/src/GroupAddressPicker.js
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
-Copyright 2017 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.
-*/
-
-import React from 'react';
-
-import Modal from './Modal';
-import * as sdk from './';
-import MultiInviter from './utils/MultiInviter';
-import { _t } from './languageHandler';
-import { MatrixClientPeg } from './MatrixClientPeg';
-import GroupStore from './stores/GroupStore';
-import StyledCheckbox from './components/views/elements/StyledCheckbox';
-
-export function showGroupInviteDialog(groupId) {
- return new Promise((resolve, reject) => {
- const description =
-
{ _t("Who would you like to add to this community?") }
-
- { _t(
- "Warning: any person you add to a community will be publicly "+
- "visible to anyone who knows the community ID",
- ) }
-
{ _t("Which rooms would you like to add to this community?") }
-
;
-
- const checkboxContainer =
- { _t("Show these rooms to non-members on the community page and room list?") }
- ;
-
- const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
- Modal.createTrackedDialog('Add Rooms to Group', '', AddressPickerDialog, {
- title: _t("Add rooms to the community"),
- description: description,
- extraNode: checkboxContainer,
- placeholder: _t("Room name or address"),
- button: _t("Add to community"),
- pickerType: 'room',
- validAddressTypes: ['mx-room-id'],
- onFinished: (success, addrs) => {
- if (!success) return;
-
- _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly).then(resolve, reject);
- },
- }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
- });
-}
-
-function _onGroupInviteFinished(groupId, addrs) {
- const multiInviter = new MultiInviter(groupId);
-
- const addrTexts = addrs.map((addr) => addr.address);
-
- return multiInviter.invite(addrTexts).then((completionStates) => {
- // Show user any errors
- const errorList = [];
- for (const addr of Object.keys(completionStates)) {
- if (addrs[addr] === "error") {
- errorList.push(addr);
- }
- }
-
- if (errorList.length > 0) {
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- Modal.createTrackedDialog('Failed to invite the following users to the group', '', ErrorDialog, {
- title: _t("Failed to invite the following users to %(groupId)s:", { groupId: groupId }),
- description: errorList.join(", "),
- });
- }
- }).catch((err) => {
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- Modal.createTrackedDialog('Failed to invite users to group', '', ErrorDialog, {
- title: _t("Failed to invite users to community"),
- description: _t("Failed to invite users to %(groupId)s", { groupId: groupId }),
- });
- });
-}
-
-function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) {
- const matrixClient = MatrixClientPeg.get();
- const errorList = [];
- return Promise.allSettled(addrs.map((addr) => {
- return GroupStore
- .addRoomToGroup(groupId, addr.address, addRoomsPublicly)
- .catch(() => { errorList.push(addr.address); })
- .then(() => {
- const roomId = addr.address;
- const room = matrixClient.getRoom(roomId);
- // Can the user change related groups?
- if (!room || !room.currentState.mayClientSendStateEvent("m.room.related_groups", matrixClient)) {
- return;
- }
- // Get the related groups
- const relatedGroupsEvent = room.currentState.getStateEvents('m.room.related_groups', '');
- const groups = relatedGroupsEvent ? relatedGroupsEvent.getContent().groups || [] : [];
-
- // Add this group as related
- if (!groups.includes(groupId)) {
- groups.push(groupId);
- return MatrixClientPeg.get().sendStateEvent(roomId, 'm.room.related_groups', { groups }, '');
- }
- });
- })).then(() => {
- if (errorList.length === 0) {
- return;
- }
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- Modal.createTrackedDialog(
- 'Failed to add the following room to the group',
- '',
- ErrorDialog,
- {
- title: _t(
- "Failed to add the following rooms to %(groupId)s:",
- { groupId },
- ),
- description: errorList.join(", "),
- },
- );
- });
-}
diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index 2f574351c94..ac26eccc718 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -182,7 +182,11 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
) {
delete attribs.target;
}
+ } else {
+ // Delete the href attrib if it is falsey
+ delete attribs.href;
}
+
attribs.rel = 'noreferrer noopener'; // https://mathiasbynens.github.io/rel-noopener/
return { tagName, attribs };
},
@@ -236,6 +240,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
}
return { tagName, attribs };
},
+ // eslint-disable-next-line @typescript-eslint/naming-convention
'*': function(tagName: string, attribs: sanitizeHtml.Attributes) {
// Delete any style previously assigned, style is an allowedTag for font, span & img,
// because attributes are stripped after transforming.
diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts
new file mode 100644
index 00000000000..cfaeecb75ea
--- /dev/null
+++ b/src/IConfigOptions.ts
@@ -0,0 +1,189 @@
+/*
+Copyright 2016 OpenMarket Ltd
+Copyright 2019 - 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 { IClientWellKnown } from "matrix-js-sdk/src/matrix";
+
+import { ValidatedServerConfig } from "./utils/AutoDiscoveryUtils";
+
+// Convention decision: All config options are lower_snake_case
+// We use an isolated file for the interface so we can mess around with the eslint options.
+
+/* eslint-disable camelcase */
+/* eslint @typescript-eslint/naming-convention: ["error", { "selector": "property", "format": ["snake_case"] } ] */
+
+// see element-web config.md for non-developer docs
+export interface IConfigOptions {
+ // dev note: while true that this is arbitrary JSON, it's valuable to enforce that all
+ // config options are documented for "find all usages" sort of searching.
+ // [key: string]: any;
+
+ // Properties of this interface are roughly grouped by their subject matter, such as
+ // "instance customisation", "login stuff", "branding", etc. Use blank lines to denote
+ // a logical separation of properties, but keep similar ones near each other.
+
+ // Exactly one of the following must be supplied
+ default_server_config?: IClientWellKnown; // copy/paste of client well-known
+ default_server_name?: string; // domain to do well-known lookup on
+ default_hs_url?: string; // http url
+
+ default_is_url?: string; // used in combination with default_hs_url, but for the identity server
+
+ // This is intended to be overridden by app startup and not specified by the user
+ // This is also why it's allowed to have an interface that isn't snake_case
+ validated_server_config?: ValidatedServerConfig;
+
+ fallback_hs_url?: string;
+
+ disable_custom_urls?: boolean;
+ disable_guests?: boolean;
+ disable_login_language_selector?: boolean;
+ disable_3pid_login?: boolean;
+
+ brand: string;
+ branding?: {
+ welcome_background_url?: string | string[]; // chosen at random if array
+ auth_header_logo_url?: string;
+ auth_footer_links?: {text: string, url: string}[];
+ };
+
+ map_style_url?: string; // for location-shared maps
+
+ embedded_pages?: {
+ welcome_url?: string;
+ home_url?: string;
+ login_for_welcome?: boolean;
+ };
+
+ permalink_prefix?: string;
+
+ update_base_url?: string;
+ desktop_builds?: {
+ available: boolean;
+ logo: string; // url
+ url: string; // download url
+ };
+ mobile_builds?: {
+ ios?: string; // download url
+ android?: string; // download url
+ fdroid?: string; // download url
+ };
+
+ mobile_guide_toast?: boolean;
+
+ default_theme?: "light" | "dark" | string; // custom themes are strings
+ default_country_code?: string; // ISO 3166 alpha2 country code
+ default_federate?: boolean;
+ default_device_display_name?: string; // for device naming on login+registration
+
+ setting_defaults?: Record; //
+
+ integrations_ui_url?: string;
+ integrations_rest_url?: string;
+ integrations_widgets_urls?: string[];
+
+ show_labs_settings?: boolean;
+ features?: Record; //
+
+ bug_report_endpoint_url?: string; // omission disables bug reporting
+ uisi_autorageshake_app?: string;
+ sentry?: {
+ dsn: string;
+ environment?: string; // "production", etc
+ };
+
+ widget_build_url?: string; // url called to replace jitsi/call widget creation
+ audio_stream_url?: string;
+ jitsi?: {
+ preferred_domain: string;
+ };
+ jitsi_widget?: {
+ skip_built_in_welcome_screen?: boolean;
+ };
+ voip?: {
+ obey_asserted_identity?: boolean; // MSC3086
+ };
+
+ logout_redirect_url?: string;
+
+ // sso_immediate_redirect is deprecated in favour of sso_redirect_options.immediate
+ sso_immediate_redirect?: boolean;
+ sso_redirect_options?: ISsoRedirectOptions;
+
+ custom_translations_url?: string;
+
+ report_event?: {
+ admin_message_md: string; // message for how to contact the server owner when reporting an event
+ };
+
+ welcome_user_id?: string;
+
+ room_directory?: {
+ servers: string[];
+ };
+
+ // piwik (matomo) is deprecated in favour of posthog
+ piwik?: false | {
+ url: string; // piwik instance
+ site_id: string;
+ policy_url: string; // cookie policy
+ whitelisted_hs_urls: string[];
+ };
+ posthog?: {
+ project_api_key: string;
+ api_host: string; // hostname
+ };
+ analytics_owner?: string; // defaults to `brand`
+
+ // Server hosting upsell options
+ hosting_signup_link?: string; // slightly different from `host_signup`
+ host_signup?: {
+ brand?: string; // acts as the enabled flag too (truthy == show)
+
+ // Required-ness denotes when `brand` is truthy
+ cookie_policy_url: string;
+ privacy_policy_url: string;
+ terms_of_service_url: string;
+ url: string;
+ domains?: string[];
+ };
+
+ enable_presence_by_hs_url?: Record; //
+
+ terms_and_conditions_links?: { url: string, text: string }[];
+
+ latex_maths_delims?: {
+ inline?: {
+ left?: string;
+ right?: string;
+ };
+ display?: {
+ left?: string;
+ right?: string;
+ };
+ };
+
+ sync_timeline_limit?: number;
+ dangerously_allow_unsafe_and_insecure_passwords?: boolean; // developer option
+
+ // XXX: Undocumented URL for the "Learn more about spaces" link in the "Communities don't exist" messaging.
+ spaces_learn_more_url?: string;
+}
+
+export interface ISsoRedirectOptions {
+ immediate?: boolean;
+ on_welcome_page?: boolean;
+}
diff --git a/src/KeyBindingsDefaults.ts b/src/KeyBindingsDefaults.ts
index 5870f6451ea..d4f4ffc6811 100644
--- a/src/KeyBindingsDefaults.ts
+++ b/src/KeyBindingsDefaults.ts
@@ -26,9 +26,9 @@ import {
import {
CATEGORIES,
CategoryName,
- getKeyboardShortcuts,
KeyBindingAction,
} from "./accessibility/KeyboardShortcuts";
+import { getKeyboardShortcuts } from "./accessibility/KeyboardShortcutUtils";
export const getBindingsByCategory = (category: CategoryName): KeyBinding[] => {
return CATEGORIES[category].settingNames.reduce((bindings, name) => {
@@ -161,7 +161,7 @@ const callBindings = (): KeyBinding[] => {
};
const labsBindings = (): KeyBinding[] => {
- if (!SdkConfig.get()['showLabsSettings']) return [];
+ if (!SdkConfig.get("show_labs_settings")) return [];
return getBindingsByCategory(CategoryName.LABS);
};
diff --git a/src/Keyboard.ts b/src/Keyboard.ts
index 7040898872e..8d7d39fc190 100644
--- a/src/Keyboard.ts
+++ b/src/Keyboard.ts
@@ -45,6 +45,7 @@ export const Key = {
SLASH: "/",
SQUARE_BRACKET_LEFT: "[",
SQUARE_BRACKET_RIGHT: "]",
+ SEMICOLON: ";",
A: "a",
B: "b",
C: "c",
diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts
index 85f05358516..f91158c38aa 100644
--- a/src/Lifecycle.ts
+++ b/src/Lifecycle.ts
@@ -59,10 +59,19 @@ import SessionRestoreErrorDialog from "./components/views/dialogs/SessionRestore
import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog";
import { setSentryUser } from "./sentry";
import SdkConfig from "./SdkConfig";
+import { DialogOpener } from "./utils/DialogOpener";
+import { Action } from "./dispatcher/actions";
const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";
+dis.register((payload) => {
+ if (payload.action === Action.TriggerLogout) {
+ // noinspection JSIgnoredPromiseFromCall - we don't care if it fails
+ onLoggedOut();
+ }
+});
+
interface ILoadSessionOpts {
enableGuest?: boolean;
guestHsUrl?: string;
@@ -790,6 +799,7 @@ async function startMatrixClient(startSyncing = true): Promise {
TypingStore.sharedInstance().reset();
ToastStore.sharedInstance().reset();
+ DialogOpener.instance.prepare();
Notifier.start();
UserActivity.sharedInstance().start();
DMRoomMap.makeShared().start();
@@ -863,8 +873,9 @@ async function clearStorage(opts?: { deleteEverything?: boolean }): Promise {
const roomId = i.roomId;
delete i.roomId; // delete to avoid confusing the store
ThreepidInviteStore.instance.storeInvite(roomId, i);
});
+
+ if (registrationTime) {
+ window.localStorage.setItem("mx_registration_time", registrationTime);
+ }
}
}
diff --git a/src/Livestream.ts b/src/Livestream.ts
index b136fbeaea8..ed7b61dd6c7 100644
--- a/src/Livestream.ts
+++ b/src/Livestream.ts
@@ -21,7 +21,7 @@ import SdkConfig from "./SdkConfig";
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
export function getConfigLivestreamUrl() {
- return SdkConfig.get()["audioStreamUrl"];
+ return SdkConfig.get("audio_stream_url");
}
// Dummy rtmp URL used to signal that we want a special audio-only stream
diff --git a/src/Markdown.ts b/src/Markdown.ts
index e24c9b72870..9f88fbe41f1 100644
--- a/src/Markdown.ts
+++ b/src/Markdown.ts
@@ -153,10 +153,27 @@ export default class Markdown {
(node.type === 'text' && node.literal === ' ')
) {
text = '';
+ continue;
}
+
+ // Break up text nodes on spaces, so that we don't shoot past them without resetting
if (node.type === 'text') {
- text += node.literal;
+ const [thisPart, ...nextParts] = node.literal.split(/( )/);
+ node.literal = thisPart;
+ text += thisPart;
+
+ // Add the remaining parts as siblings
+ nextParts.reverse().forEach(part => {
+ if (part) {
+ const nextNode = new commonmark.Node('text');
+ nextNode.literal = part;
+ node.insertAfter(nextNode);
+ // Make the iterator aware of the newly inserted node
+ walker.resumeAt(nextNode, true);
+ }
+ });
}
+
// We should not do this if previous node was not a textnode, as we can't combine it then.
if ((node.type === 'emph' || node.type === 'strong') && previousNode.type === 'text') {
if (event.entering) {
diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts
index c8502b0795e..46d599b156d 100644
--- a/src/MatrixClientPeg.ts
+++ b/src/MatrixClientPeg.ts
@@ -27,7 +27,6 @@ import { verificationMethods } from 'matrix-js-sdk/src/crypto';
import { SHOW_QR_CODE_METHOD } from "matrix-js-sdk/src/crypto/verification/QRCode";
import { logger } from "matrix-js-sdk/src/logger";
-import * as sdk from './index';
import createMatrixClient from './utils/createMatrixClient';
import SettingsStore from './settings/SettingsStore';
import MatrixActionCreators from './actions/MatrixActionCreators';
@@ -37,6 +36,7 @@ import * as StorageManager from './utils/StorageManager';
import IdentityAuthClient from './IdentityAuthClient';
import { crossSigningCallbacks, tryToUnlockSecretStorageWithDehydrationKey } from './SecurityManager';
import SecurityCustomisations from "./customisations/Security";
+import CryptoStoreTooNewDialog from "./components/views/dialogs/CryptoStoreTooNewDialog";
export interface IMatrixClientCreds {
homeserverUrl: string;
@@ -72,11 +72,11 @@ export interface IMatrixClientPeg {
* If we've registered a user ID we set this to the ID of the
* user we've just registered. If they then go & log in, we
* can send them to the welcome user (obviously this doesn't
- * guarentee they'll get a chat with the welcome user).
+ * guarantee they'll get a chat with the welcome user).
*
* @param {string} uid The user ID of the user we've just registered
*/
- setJustRegisteredUserId(uid: string): void;
+ setJustRegisteredUserId(uid: string | null): void;
/**
* Returns true if the current user has just been registered by this
@@ -117,7 +117,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
};
private matrixClient: MatrixClient = null;
- private justRegisteredUserId: string;
+ private justRegisteredUserId: string | null = null;
// the credentials used to init the current client object.
// used if we tear it down & recreate it with a different store
@@ -136,10 +136,11 @@ class MatrixClientPegClass implements IMatrixClientPeg {
MatrixActionCreators.stop();
}
- public setJustRegisteredUserId(uid: string): void {
+ public setJustRegisteredUserId(uid: string | null): void {
this.justRegisteredUserId = uid;
if (uid) {
- window.localStorage.setItem("mx_registration_time", String(new Date().getTime()));
+ const registrationTime = Date.now().toString();
+ window.localStorage.setItem("mx_registration_time", registrationTime);
}
}
@@ -151,9 +152,14 @@ class MatrixClientPegClass implements IMatrixClientPeg {
}
public userRegisteredWithinLastHours(hours: number): boolean {
+ if (hours <= 0) {
+ return false;
+ }
+
try {
- const date = new Date(window.localStorage.getItem("mx_registration_time"));
- return ((new Date().getTime() - date.getTime()) / 36e5) <= hours;
+ const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time"), 10);
+ const diff = Date.now() - registrationTime;
+ return (diff / 36e5) <= hours;
} catch (e) {
return false;
}
@@ -200,9 +206,6 @@ class MatrixClientPegClass implements IMatrixClientPeg {
} catch (e) {
if (e && e.name === 'InvalidCryptoStoreError') {
// The js-sdk found a crypto DB too new for it to use
- // FIXME: Using an import will result in test failures
- const CryptoStoreTooNewDialog =
- sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
Modal.createDialog(CryptoStoreTooNewDialog);
}
// this can happen for a number of reasons, the most likely being
diff --git a/src/Notifier.ts b/src/Notifier.ts
index c5383488f2f..62e2f093703 100644
--- a/src/Notifier.ts
+++ b/src/Notifier.ts
@@ -22,7 +22,7 @@ import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { ClientEvent } from "matrix-js-sdk/src/client";
import { logger } from "matrix-js-sdk/src/logger";
import { MsgType } from "matrix-js-sdk/src/@types/event";
-import { LOCATION_EVENT_TYPE } from "matrix-js-sdk/src/@types/location";
+import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
import { MatrixClientPeg } from './MatrixClientPeg';
import SdkConfig from './SdkConfig';
@@ -37,7 +37,7 @@ import SettingsStore from "./settings/SettingsStore";
import { hideToast as hideNotificationsToast } from "./toasts/DesktopNotificationsToast";
import { SettingLevel } from "./settings/SettingLevel";
import { isPushNotifyDisabled } from "./settings/controllers/NotificationControllers";
-import RoomViewStore from "./stores/RoomViewStore";
+import { RoomViewStore } from "./stores/RoomViewStore";
import UserActivity from "./UserActivity";
import { mediaFromMxc } from "./customisations/Media";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
@@ -64,10 +64,10 @@ const msgTypeHandlers = {
const name = (event.sender || {}).name;
return _t("%(name)s is requesting verification", { name });
},
- [LOCATION_EVENT_TYPE.name]: (event: MatrixEvent) => {
+ [M_LOCATION.name]: (event: MatrixEvent) => {
return TextForEvent.textForLocationEvent(event)();
},
- [LOCATION_EVENT_TYPE.altName]: (event: MatrixEvent) => {
+ [M_LOCATION.altName]: (event: MatrixEvent) => {
return TextForEvent.textForLocationEvent(event)();
},
};
@@ -401,7 +401,7 @@ export const Notifier = {
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions?.notify) {
- if (RoomViewStore.getRoomId() === room.roomId &&
+ if (RoomViewStore.instance.getRoomId() === room.roomId &&
UserActivity.sharedInstance().userActiveRecently() &&
!Modal.hasDialogs()
) {
diff --git a/src/PageTypes.ts b/src/PageTypes.ts
index 0e4944efc95..fb0424f6e05 100644
--- a/src/PageTypes.ts
+++ b/src/PageTypes.ts
@@ -20,8 +20,7 @@ enum PageType {
HomePage = "home_page",
RoomView = "room_view",
UserView = "user_view",
- GroupView = "group_view",
- MyGroups = "my_groups",
+ LegacyGroupView = "legacy_group_view",
}
export default PageType;
diff --git a/src/PasswordReset.ts b/src/PasswordReset.ts
index 7b0b6f9f846..df812bafb2c 100644
--- a/src/PasswordReset.ts
+++ b/src/PasswordReset.ts
@@ -1,6 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 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.
@@ -29,9 +29,9 @@ import { _t } from './languageHandler';
export default class PasswordReset {
private client: MatrixClient;
private clientSecret: string;
- private identityServerDomain: string;
private password: string;
private sessionId: string;
+ private logoutDevices: boolean;
/**
* Configure the endpoints for password resetting.
@@ -44,7 +44,6 @@ export default class PasswordReset {
idBaseUrl: identityUrl,
});
this.clientSecret = this.client.generateClientSecret();
- this.identityServerDomain = identityUrl ? identityUrl.split("://")[1] : null;
}
/**
@@ -52,10 +51,16 @@ export default class PasswordReset {
* sending an email to the provided email address.
* @param {string} emailAddress The email address
* @param {string} newPassword The new password for the account.
+ * @param {boolean} logoutDevices Should all devices be signed out after the reset? Defaults to `true`.
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
*/
- public resetPassword(emailAddress: string, newPassword: string): Promise {
+ public resetPassword(
+ emailAddress: string,
+ newPassword: string,
+ logoutDevices = true,
+ ): Promise {
this.password = newPassword;
+ this.logoutDevices = logoutDevices;
return this.client.requestPasswordEmailToken(emailAddress, this.clientSecret, 1).then((res) => {
this.sessionId = res.sid;
return res;
@@ -92,7 +97,7 @@ export default class PasswordReset {
// See https://github.com/matrix-org/matrix-doc/issues/2220
threepid_creds: creds,
threepidCreds: creds,
- }, this.password);
+ }, this.password, this.logoutDevices);
} catch (err) {
if (err.httpStatus === 401) {
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts
index 3b2ae009aec..3ce8707d1c2 100644
--- a/src/PosthogAnalytics.ts
+++ b/src/PosthogAnalytics.ts
@@ -18,6 +18,7 @@ import posthog, { PostHog } from 'posthog-js';
import { MatrixClient } from "matrix-js-sdk/src/client";
import { logger } from "matrix-js-sdk/src/logger";
import { UserProperties } from "matrix-analytics-events/types/typescript/UserProperties";
+import { Signup } from 'matrix-analytics-events/types/typescript/Signup';
import PlatformPeg from './PlatformPeg';
import SdkConfig from './SdkConfig';
@@ -41,7 +42,7 @@ import { ScreenName } from "./PosthogTrackers";
* - If both flags are false or not set, events are not sent.
*/
-interface IEvent {
+export interface IPosthogEvent {
// The event name that will be used by PostHog. Event names should use camelCase.
eventName: string;
@@ -50,6 +51,10 @@ interface IEvent {
"$set_once"?: void;
}
+export interface IPostHogEventOptions {
+ timestamp?: Date;
+}
+
export enum Anonymity {
Disabled,
Anonymous,
@@ -58,7 +63,7 @@ export enum Anonymity {
const whitelistedScreens = new Set([
"register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory",
- "start_sso", "start_cas", "groups", "complete_security", "post_registration", "room", "user", "group",
+ "start_sso", "start_cas", "complete_security", "post_registration", "room", "user",
]);
export function getRedactedCurrentLocation(
@@ -117,6 +122,7 @@ export class PosthogAnalytics {
private static ANALYTICS_EVENT_TYPE = "im.vector.analytics";
private propertiesForNextEvent: Partial> = {};
private userPropertyCache: UserProperties = {};
+ private authenticationType: Signup["authenticationType"] = "Other";
public static get instance(): PosthogAnalytics {
if (!this._instance) {
@@ -126,10 +132,10 @@ export class PosthogAnalytics {
}
constructor(private readonly posthog: PostHog) {
- const posthogConfig = SdkConfig.get()["posthog"];
+ const posthogConfig = SdkConfig.getObject("posthog");
if (posthogConfig) {
- this.posthog.init(posthogConfig.projectApiKey, {
- api_host: posthogConfig.apiHost,
+ this.posthog.init(posthogConfig.get("project_api_key"), {
+ api_host: posthogConfig.get("api_host"),
autocapture: false,
mask_all_text: true,
mask_all_element_attributes: true,
@@ -200,16 +206,20 @@ export class PosthogAnalytics {
};
}
- private capture(eventName: string, properties: posthog.Properties) {
+ // eslint-disable-nextline no-unused-varsx
+ private capture(eventName: string, properties: posthog.Properties, options?: IPostHogEventOptions) {
if (!this.enabled) {
return;
}
const { origin, hash, pathname } = window.location;
properties["redactedCurrentUrl"] = getRedactedCurrentLocation(origin, hash, pathname);
- this.posthog.capture(eventName, {
- ...this.propertiesForNextEvent,
- ...properties,
- });
+ this.posthog.capture(
+ eventName,
+ { ...this.propertiesForNextEvent, ...properties },
+ // TODO: Uncomment below once https://github.com/PostHog/posthog-js/pull/391
+ // gets merged
+ /* options as any, */ // No proper type definition in the posthog library
+ );
this.propertiesForNextEvent = {};
}
@@ -272,9 +282,12 @@ export class PosthogAnalytics {
this.setAnonymity(Anonymity.Disabled);
}
- public trackEvent({ eventName, ...properties }: E): void {
+ public trackEvent(
+ { eventName, ...properties }: E,
+ options?: IPostHogEventOptions,
+ ): void {
if (this.anonymity == Anonymity.Disabled || this.anonymity == Anonymity.Anonymous) return;
- this.capture(eventName, properties);
+ this.capture(eventName, properties, options);
}
public setProperty(key: K, value: UserProperties[K]): void {
@@ -313,6 +326,9 @@ export class PosthogAnalytics {
this.setAnonymity(anonymity);
if (anonymity === Anonymity.Pseudonymous) {
await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getRandomAnalyticsId);
+ if (MatrixClientPeg.currentUserIsJustRegistered()) {
+ this.trackNewUserEvent();
+ }
}
if (anonymity !== Anonymity.Disabled) {
@@ -334,4 +350,25 @@ export class PosthogAnalytics {
this.updateAnonymityFromSettings(!!newValue);
});
}
+
+ public setAuthenticationType(authenticationType: Signup["authenticationType"]): void {
+ this.authenticationType = authenticationType;
+ }
+
+ private trackNewUserEvent(): void {
+ // This is the only event that could have occured before analytics opt-in
+ // that we want to accumulate before the user has given consent
+ // All other scenarios should not track a user before they have given
+ // explicit consent that they are ok with their analytics data being collected
+ const options: IPostHogEventOptions = {};
+ const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time"), 10);
+ if (!isNaN(registrationTime)) {
+ options.timestamp = new Date(registrationTime);
+ }
+
+ return this.trackEvent({
+ eventName: "Signup",
+ authenticationType: this.authenticationType,
+ }, options);
+ }
}
diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts
index b07ba5569c8..434d142c8cd 100644
--- a/src/PosthogTrackers.ts
+++ b/src/PosthogTrackers.ts
@@ -40,8 +40,7 @@ const loggedInPageTypeMap: Record = {
[PageType.HomePage]: "Home",
[PageType.RoomView]: "Room",
[PageType.UserView]: "User",
- [PageType.GroupView]: "Group",
- [PageType.MyGroups]: "MyGroups",
+ [PageType.LegacyGroupView]: "Group",
};
export default class PosthogTrackers {
diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx
index a6eefaeb81c..200da2f7cf8 100644
--- a/src/RoomInvite.tsx
+++ b/src/RoomInvite.tsx
@@ -24,12 +24,12 @@ import { MatrixClientPeg } from './MatrixClientPeg';
import MultiInviter, { CompletionStates } from './utils/MultiInviter';
import Modal from './Modal';
import { _t } from './languageHandler';
-import InviteDialog, { KIND_DM, KIND_INVITE, Member } from "./components/views/dialogs/InviteDialog";
-import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
-import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
+import InviteDialog from "./components/views/dialogs/InviteDialog";
import BaseAvatar from "./components/views/avatars/BaseAvatar";
import { mediaFromMxc } from "./customisations/Media";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
+import { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialogTypes";
+import { Member } from "./utils/direct-messages";
export interface IInviteResult {
states: CompletionStates;
@@ -78,23 +78,6 @@ export function showRoomInviteDialog(roomId: string, initialText = ""): void {
);
}
-export function showCommunityRoomInviteDialog(roomId: string, communityName: string): void {
- Modal.createTrackedDialog(
- 'Invite Users to Community', '', CommunityPrototypeInviteDialog, { communityName, roomId },
- /*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
- );
-}
-
-export function showCommunityInviteDialog(communityId: string): void {
- const chat = CommunityPrototypeStore.instance.getGeneralChat(communityId);
- if (chat) {
- const name = CommunityPrototypeStore.instance.getCommunityName(communityId);
- showCommunityRoomInviteDialog(chat.roomId, name);
- } else {
- throw new Error("Failed to locate appropriate room to start an invite in");
- }
-}
-
/**
* Checks if the given MatrixEvent is a valid 3rd party user invite.
* @param {MatrixEvent} event The event to check
@@ -144,7 +127,7 @@ export function showAnyInviteErrors(
// user. This usually means that no other users were attempted, making it
// pointless for us to list who failed exactly.
Modal.createTrackedDialog('Failed to invite users to the room', '', ErrorDialog, {
- title: _t("Failed to invite users to the room:", { roomName: room.name }),
+ title: _t("Failed to invite users to %(roomName)s", { roomName: room.name }),
description: inviter.getErrorText(failedUsers[0]),
});
return false;
diff --git a/src/Rooms.ts b/src/Rooms.ts
index 9bb43c3d8c5..32419080acf 100644
--- a/src/Rooms.ts
+++ b/src/Rooms.ts
@@ -19,9 +19,6 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { MatrixClientPeg } from './MatrixClientPeg';
import AliasCustomisations from './customisations/Alias';
-import DMRoomMap from "./utils/DMRoomMap";
-import SpaceStore from "./stores/spaces/SpaceStore";
-import { _t } from "./languageHandler";
/**
* Given a room object, return the alias we should use for it,
@@ -157,48 +154,3 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
if (oldestUser === undefined) return myUserId;
return oldestUser.userId;
}
-
-export function spaceContextDetailsText(space: Room): string {
- if (!space.isSpaceRoom()) return undefined;
-
- const [parent, secondParent, ...otherParents] = SpaceStore.instance.getKnownParents(space.roomId);
- if (secondParent && !otherParents?.length) {
- // exactly 2 edge case for improved i18n
- return _t("%(space1Name)s and %(space2Name)s", {
- space1Name: space.client.getRoom(parent)?.name,
- space2Name: space.client.getRoom(secondParent)?.name,
- });
- } else if (parent) {
- return _t("%(spaceName)s and %(count)s others", {
- spaceName: space.client.getRoom(parent)?.name,
- count: otherParents.length,
- });
- }
-
- return space.getCanonicalAlias();
-}
-
-export function roomContextDetailsText(room: Room): string {
- if (room.isSpaceRoom()) return undefined;
-
- const dmPartner = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
- if (dmPartner) {
- return dmPartner;
- }
-
- const [parent, secondParent, ...otherParents] = SpaceStore.instance.getKnownParents(room.roomId);
- if (secondParent && !otherParents?.length) {
- // exactly 2 edge case for improved i18n
- return _t("%(space1Name)s and %(space2Name)s", {
- space1Name: room.client.getRoom(parent)?.name,
- space2Name: room.client.getRoom(secondParent)?.name,
- });
- } else if (parent) {
- return _t("%(spaceName)s and %(count)s others", {
- spaceName: room.client.getRoom(parent)?.name,
- count: otherParents.length,
- });
- }
-
- return room.getCanonicalAlias();
-}
diff --git a/src/ScalarAuthClient.ts b/src/ScalarAuthClient.ts
index 9d71bf5b053..b1fb6e44f4d 100644
--- a/src/ScalarAuthClient.ts
+++ b/src/ScalarAuthClient.ts
@@ -44,8 +44,8 @@ export default class ScalarAuthClient {
// We try and store the token on a per-manager basis, but need a fallback
// for the default manager.
- const configApiUrl = SdkConfig.get()['integrations_rest_url'];
- const configUiUrl = SdkConfig.get()['integrations_ui_url'];
+ const configApiUrl = SdkConfig.get("integrations_rest_url");
+ const configUiUrl = SdkConfig.get("integrations_ui_url");
this.isDefaultManager = apiUrl === configApiUrl && configUiUrl === uiUrl;
}
diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts
index b0b8b3a0d4e..a380c760b56 100644
--- a/src/ScalarMessaging.ts
+++ b/src/ScalarMessaging.ts
@@ -241,7 +241,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from './MatrixClientPeg';
import dis from './dispatcher/dispatcher';
import WidgetUtils from './utils/WidgetUtils';
-import RoomViewStore from './stores/RoomViewStore';
+import { RoomViewStore } from './stores/RoomViewStore';
import { _t } from './languageHandler';
import { IntegrationManagers } from "./integrations/IntegrationManagers";
import { WidgetType } from "./widgets/WidgetType";
@@ -638,7 +638,7 @@ const onMessage = function(event: MessageEvent): void {
}
}
- if (roomId !== RoomViewStore.getRoomId()) {
+ if (roomId !== RoomViewStore.instance.getRoomId()) {
sendError(event, _t('Room %(roomId)s not visible', { roomId: roomId }));
return;
}
diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts
index c4387d1f237..93b89a92eac 100644
--- a/src/SdkConfig.ts
+++ b/src/SdkConfig.ts
@@ -1,6 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
-Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 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.
@@ -15,61 +15,71 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-export interface ISsoRedirectOptions {
- immediate?: boolean;
- on_welcome_page?: boolean; // eslint-disable-line camelcase
-}
-
-/* eslint-disable camelcase */
-export interface ConfigOptions {
- [key: string]: any;
-
- logout_redirect_url?: string;
+import { Optional } from "matrix-events-sdk";
- // sso_immediate_redirect is deprecated in favour of sso_redirect_options.immediate
- sso_immediate_redirect?: boolean;
- sso_redirect_options?: ISsoRedirectOptions;
+import { SnakedObject } from "./utils/SnakedObject";
+import { IConfigOptions, ISsoRedirectOptions } from "./IConfigOptions";
+import { KeysWithObjectShape } from "./@types/common";
- custom_translations_url?: string;
-}
-/* eslint-enable camelcase*/
-
-export const DEFAULTS: ConfigOptions = {
- // Brand name of the app
+// see element-web config.md for docs, or the IConfigOptions interface for dev docs
+export const DEFAULTS: IConfigOptions = {
brand: "Element",
- // URL to a page we show in an iframe to configure integrations
integrations_ui_url: "https://scalar.vector.im/",
- // Base URL to the REST interface of the integrations server
integrations_rest_url: "https://scalar.vector.im/api",
- // Where to send bug reports. If not specified, bugs cannot be sent.
bug_report_endpoint_url: null,
- // Jitsi conference options
jitsi: {
- // Default conference domain
- preferredDomain: "meet.element.io",
+ preferred_domain: "meet.element.io",
},
+
+ // @ts-ignore - we deliberately use the camelCase version here so we trigger
+ // the fallback behaviour. If we used the snake_case version then we'd break
+ // everyone's config which has the camelCase property because our default would
+ // be preferred over their config.
desktopBuilds: {
available: true,
logo: require("../res/img/element-desktop-logo.svg").default,
url: "https://element.io/get-started",
},
+ spaces_learn_more_url: "https://element.io/blog/spaces-blast-out-of-beta/",
};
export default class SdkConfig {
- private static instance: ConfigOptions;
+ private static instance: IConfigOptions;
+ private static fallback: SnakedObject;
- private static setInstance(i: ConfigOptions) {
+ private static setInstance(i: IConfigOptions) {
SdkConfig.instance = i;
+ SdkConfig.fallback = new SnakedObject(i);
// For debugging purposes
window.mxReactSdkConfig = i;
}
- public static get() {
- return SdkConfig.instance || {};
+ public static get(): IConfigOptions;
+ public static get(key: K, altCaseName?: string): IConfigOptions[K];
+ public static get(
+ key?: K, altCaseName?: string,
+ ): IConfigOptions | IConfigOptions[K] {
+ if (key === undefined) {
+ // safe to cast as a fallback - we want to break the runtime contract in this case
+ return SdkConfig.instance || {};
+ }
+ return SdkConfig.fallback.get(key, altCaseName);
+ }
+
+ public static getObject>(
+ key: K, altCaseName?: string,
+ ): Optional> {
+ const val = SdkConfig.get(key, altCaseName);
+ if (val !== null && val !== undefined) {
+ return new SnakedObject(val);
+ }
+
+ // return the same type for sensitive callers (some want `undefined` specifically)
+ return val === undefined ? undefined : null;
}
- public static put(cfg: ConfigOptions) {
+ public static put(cfg: IConfigOptions) {
const defaultKeys = Object.keys(DEFAULTS);
for (let i = 0; i < defaultKeys.length; ++i) {
if (cfg[defaultKeys[i]] === undefined) {
@@ -79,18 +89,21 @@ export default class SdkConfig {
SdkConfig.setInstance(cfg);
}
+ /**
+ * Resets the config to be completely empty.
+ */
public static unset() {
- SdkConfig.setInstance({});
+ SdkConfig.setInstance({}); // safe to cast - defaults will be applied
}
- public static add(cfg: ConfigOptions) {
+ public static add(cfg: Partial) {
const liveConfig = SdkConfig.get();
const newConfig = Object.assign({}, liveConfig, cfg);
SdkConfig.put(newConfig);
}
}
-export function parseSsoRedirectOptions(config: ConfigOptions): ISsoRedirectOptions {
+export function parseSsoRedirectOptions(config: IConfigOptions): ISsoRedirectOptions {
// Ignore deprecated options if the config is using new ones
if (config.sso_redirect_options) return config.sso_redirect_options;
diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts
index 0383b50fc41..f3d254d0590 100644
--- a/src/SecurityManager.ts
+++ b/src/SecurityManager.ts
@@ -25,7 +25,6 @@ import { logger } from "matrix-js-sdk/src/logger";
import { ComponentType } from "react";
import Modal from './Modal';
-import * as sdk from './index';
import { MatrixClientPeg } from './MatrixClientPeg';
import { _t } from './languageHandler';
import { isSecureBackupRequired } from './utils/WellKnownUtils';
@@ -34,6 +33,7 @@ import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreK
import SettingsStore from "./settings/SettingsStore";
import SecurityCustomisations from "./customisations/Security";
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
+import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
// This stores the secret storage private keys in memory for the JS SDK. This is
// only meant to act as a cache to avoid prompting the user multiple times
@@ -360,8 +360,6 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
throw new Error("Secret storage creation canceled");
}
} else {
- // FIXME: Using an import will result in test failures
- const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => {
const { finished } = Modal.createTrackedDialog(
diff --git a/src/Skinner.ts b/src/Skinner.ts
deleted file mode 100644
index 6b20781b598..00000000000
--- a/src/Skinner.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket 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.
-*/
-
-import React from "react";
-
-export interface IComponents {
- [key: string]: React.Component;
-}
-
-export interface ISkinObject {
- components: IComponents;
-}
-
-export class Skinner {
- public components: IComponents = null;
-
- public getComponent(name: string): React.Component {
- if (!name) throw new Error(`Invalid component name: ${name}`);
- if (this.components === null) {
- throw new Error(
- `Attempted to get a component (${name}) before a skin has been loaded.`+
- " This is probably because either:"+
- " a) Your app has not called sdk.loadSkin(), or"+
- " b) A component has called getComponent at the root level",
- );
- }
-
- const doLookup = (components: IComponents): React.Component => {
- if (!components) return null;
- let comp = components[name];
- // XXX: Temporarily also try 'views.' as we're currently
- // leaving the 'views.' off views.
- if (!comp) {
- comp = components['views.' + name];
- }
- return comp;
- };
-
- // Check the skin first
- const comp = doLookup(this.components);
-
- // Just return nothing instead of erroring - the consumer should be smart enough to
- // handle this at this point.
- if (!comp) {
- return null;
- }
-
- // components have to be functions or forwardRef objects with a render function.
- const validType = typeof comp === 'function' || comp.render;
- if (!validType) {
- throw new Error(`Not a valid component: ${name} (type = ${typeof(comp)}).`);
- }
- return comp;
- }
-
- public load(skinObject: ISkinObject): void {
- if (this.components !== null) {
- throw new Error(
- "Attempted to load a skin while a skin is already loaded"+
- "If you want to change the active skin, call resetSkin first");
- }
- this.components = {};
- const compKeys = Object.keys(skinObject.components);
- for (let i = 0; i < compKeys.length; ++i) {
- const comp = skinObject.components[compKeys[i]];
- this.addComponent(compKeys[i], comp);
- }
-
- // Now that we have a skin, load our components too
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const idx = require("./component-index");
- if (!idx || !idx.components) throw new Error("Invalid react-sdk component index");
- for (const c in idx.components) {
- if (!this.components[c]) this.components[c] = idx.components[c];
- }
- }
-
- public addComponent(name: string, comp: any) {
- let slot = name;
- if (comp.replaces !== undefined) {
- if (comp.replaces.indexOf('.') > -1) {
- slot = comp.replaces;
- } else {
- slot = name.substr(0, name.lastIndexOf('.') + 1) + comp.replaces.split('.').pop();
- }
- }
- this.components[slot] = comp;
- }
-
- public reset(): void {
- this.components = null;
- }
-}
-
-// We define one Skinner globally, because the intention is
-// very much that it is a singleton. Relying on there only being one
-// copy of the module can be dicey and not work as browserify's
-// behaviour with multiple copies of files etc. is erratic at best.
-// XXX: We can still end up with the same file twice in the resulting
-// JS bundle which is nonideal.
-// See https://derickbailey.com/2016/03/09/creating-a-true-singleton-in-node-js-with-es6-symbols/
-// or https://nodejs.org/api/modules.html#modules_module_caching_caveats
-// ("Modules are cached based on their resolved filename")
-if (window.mxSkinner === undefined) {
- window.mxSkinner = new Skinner();
-}
-export default window.mxSkinner;
-
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index 9bb90e607a5..4227577d203 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -46,7 +46,7 @@ import BugReportDialog from "./components/views/dialogs/BugReportDialog";
import { ensureDMExists } from "./createRoom";
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
import { Action } from "./dispatcher/actions";
-import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from "./utils/membership";
+import { EffectiveMembership, getEffectiveMembership } from "./utils/membership";
import SdkConfig from "./SdkConfig";
import SettingsStore from "./settings/SettingsStore";
import { UIComponent, UIFeature } from "./settings/UIFeature";
@@ -61,11 +61,12 @@ import InfoDialog from "./components/views/dialogs/InfoDialog";
import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
import { shouldShowComponent } from "./customisations/helpers/UIComponents";
import { TimelineRenderingType } from './contexts/RoomContext';
-import RoomViewStore from "./stores/RoomViewStore";
+import { RoomViewStore } from "./stores/RoomViewStore";
import { XOR } from "./@types/common";
import { PosthogAnalytics } from "./PosthogAnalytics";
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
import VoipUserMapper from './VoipUserMapper';
+import { leaveRoomBehaviour } from "./utils/leave-behaviour";
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
interface HTMLInputEvent extends Event {
@@ -153,13 +154,11 @@ export class Command {
public run(roomId: string, threadId: string, args: string): RunResult {
// if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me`
if (!this.runFn) {
- reject(
+ return reject(
newTranslatableError(
"Command error: Unable to handle slash command.",
),
);
-
- return;
}
const renderingType = threadId
@@ -322,8 +321,8 @@ export const Commands = [
}),
new Command({
command: 'jumptodate',
- args: '',
- description: _td('Jump to the given date in the timeline (YYYY-MM-DD)'),
+ args: '',
+ description: _td('Jump to the given date in the timeline'),
isEnabled: () => SettingsStore.getValue("feature_jump_to_date"),
runFn: function(roomId, args) {
if (args) {
@@ -641,7 +640,7 @@ export const Commands = [
return reject(this.getUsage());
}
- // If for some reason someone wanted to join a group or user, we should
+ // If for some reason someone wanted to join a user, we should
// stop them now.
if (!permalinkParts.roomIdOrAlias) {
return reject(this.getUsage());
@@ -853,7 +852,7 @@ export const Commands = [
description: _td('Define the power level of a user'),
isEnabled(): boolean {
const cli = MatrixClientPeg.get();
- const room = cli.getRoom(RoomViewStore.getRoomId());
+ const room = cli.getRoom(RoomViewStore.instance.getRoomId());
return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId());
},
runFn: function(roomId, args) {
@@ -893,7 +892,7 @@ export const Commands = [
description: _td('Deops user with given id'),
isEnabled(): boolean {
const cli = MatrixClientPeg.get();
- const room = cli.getRoom(RoomViewStore.getRoomId());
+ const room = cli.getRoom(RoomViewStore.instance.getRoomId());
return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId());
},
runFn: function(roomId, args) {
@@ -924,7 +923,7 @@ export const Commands = [
command: 'devtools',
description: _td('Opens the Developer Tools dialog'),
runFn: function(roomId) {
- Modal.createDialog(DevtoolsDialog, { roomId });
+ Modal.createDialog(DevtoolsDialog, { roomId }, "mx_DevtoolsDialog_wrapper");
return success();
},
category: CommandCategories.advanced,
diff --git a/src/Terms.ts b/src/Terms.ts
index 4b1480f536a..aa94309ae35 100644
--- a/src/Terms.ts
+++ b/src/Terms.ts
@@ -19,8 +19,8 @@ import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from './MatrixClientPeg';
-import * as sdk from '.';
import Modal from './Modal';
+import TermsDialog from "./components/views/dialogs/TermsDialog";
export class TermsNotSignedError extends Error {}
@@ -189,8 +189,6 @@ export async function dialogTermsInteractionCallback(
extraClassNames?: string,
): Promise {
logger.log("Terms that need agreement", policiesAndServicePairs);
- // FIXME: Using an import will result in test failures
- const TermsDialog = sdk.getComponent("views.dialogs.TermsDialog");
const { finished } = Modal.createTrackedDialog<[boolean, string[]]>('Terms of Service', '', TermsDialog, {
policiesAndServicePairs,
diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx
index 49da76b8b85..bb6d9eab3c7 100644
--- a/src/TextForEvent.tsx
+++ b/src/TextForEvent.tsx
@@ -29,7 +29,7 @@ import {
M_POLL_END,
PollStartEvent,
} from "matrix-events-sdk";
-import { LOCATION_EVENT_TYPE } from "matrix-js-sdk/src/@types/location";
+import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
import { _t } from './languageHandler';
import * as Roles from './Roles';
@@ -274,36 +274,6 @@ function textForGuestAccessEvent(ev: MatrixEvent): () => string | null {
}
}
-function textForRelatedGroupsEvent(ev: MatrixEvent): () => string | null {
- const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
- const groups = ev.getContent().groups || [];
- const prevGroups = ev.getPrevContent().groups || [];
- const added = groups.filter((g) => !prevGroups.includes(g));
- const removed = prevGroups.filter((g) => !groups.includes(g));
-
- if (added.length && !removed.length) {
- return () => _t('%(senderDisplayName)s enabled flair for %(groups)s in this room.', {
- senderDisplayName,
- groups: added.join(', '),
- });
- } else if (!added.length && removed.length) {
- return () => _t('%(senderDisplayName)s disabled flair for %(groups)s in this room.', {
- senderDisplayName,
- groups: removed.join(', '),
- });
- } else if (added.length && removed.length) {
- return () => _t('%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for ' +
- '%(oldGroups)s in this room.', {
- senderDisplayName,
- newGroups: added.join(', '),
- oldGroups: removed.join(', '),
- });
- } else {
- // Don't bother rendering this change (because there were no changes)
- return null;
- }
-}
-
function textForServerACLEvent(ev: MatrixEvent): () => string | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
const prevContent = ev.getPrevContent();
@@ -339,7 +309,7 @@ function textForMessageEvent(ev: MatrixEvent): () => string | null {
const content = ev.getContent();
const msgtype = content.msgtype;
- if (LOCATION_EVENT_TYPE.matches(type) || LOCATION_EVENT_TYPE.matches(msgtype)) {
+ if (M_LOCATION.matches(type) || M_LOCATION.matches(msgtype)) {
return textForLocationEvent(ev);
}
@@ -347,16 +317,7 @@ function textForMessageEvent(ev: MatrixEvent): () => string | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
let message = ev.getContent().body;
if (ev.isRedacted()) {
- message = _t("Message deleted");
- const unsigned = ev.getUnsigned();
- const redactedBecauseUserId = unsigned?.redacted_because?.sender;
- if (redactedBecauseUserId && redactedBecauseUserId !== ev.getSender()) {
- const room = MatrixClientPeg.get().getRoom(ev.getRoomId());
- const sender = room?.getMember(redactedBecauseUserId);
- message = _t("Message deleted by %(name)s", {
- name: sender?.name || redactedBecauseUserId,
- });
- }
+ message = textForRedactedPollAndMessageEvent(ev);
}
if (SettingsStore.isEnabled("feature_extensible_events")) {
@@ -757,11 +718,38 @@ export function textForLocationEvent(event: MatrixEvent): () => string | null {
});
}
+function textForRedactedPollAndMessageEvent(ev: MatrixEvent): string {
+ let message = _t("Message deleted");
+ const unsigned = ev.getUnsigned();
+ const redactedBecauseUserId = unsigned?.redacted_because?.sender;
+ if (redactedBecauseUserId && redactedBecauseUserId !== ev.getSender()) {
+ const room = MatrixClientPeg.get().getRoom(ev.getRoomId());
+ const sender = room?.getMember(redactedBecauseUserId);
+ message = _t("Message deleted by %(name)s", {
+ name: sender?.name || redactedBecauseUserId,
+ });
+ }
+
+ return message;
+}
+
function textForPollStartEvent(event: MatrixEvent): () => string | null {
- return () => _t("%(senderName)s has started a poll - %(pollQuestion)s", {
- senderName: getSenderName(event),
- pollQuestion: (event.unstableExtensibleEvent as PollStartEvent)?.question?.text,
- });
+ return () => {
+ let message = '';
+
+ if (event.isRedacted()) {
+ message = textForRedactedPollAndMessageEvent(event);
+ const senderDisplayName = event.sender?.name ?? event.getSender();
+ message = senderDisplayName + ': ' + message;
+ } else {
+ message = _t("%(senderName)s has started a poll - %(pollQuestion)s", {
+ senderName: getSenderName(event),
+ pollQuestion: (event.unstableExtensibleEvent as PollStartEvent)?.question?.text,
+ });
+ }
+
+ return message;
+ };
}
function textForPollEndEvent(event: MatrixEvent): () => string | null {
@@ -800,7 +788,6 @@ const stateHandlers: IHandlers = {
[EventType.RoomTombstone]: textForTombstoneEvent,
[EventType.RoomJoinRules]: textForJoinRulesEvent,
[EventType.RoomGuestAccess]: textForGuestAccessEvent,
- 'm.room.related_groups': textForRelatedGroupsEvent,
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
'im.vector.modular.widgets': textForWidgetEvent,
diff --git a/src/Unread.ts b/src/Unread.ts
index 905798eb038..7deafef9678 100644
--- a/src/Unread.ts
+++ b/src/Unread.ts
@@ -20,10 +20,12 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { MatrixClientPeg } from "./MatrixClientPeg";
import shouldHideEvent from './shouldHideEvent';
-import { haveTileForEvent } from "./components/views/rooms/EventTile";
+import { haveRendererForEvent } from "./events/EventTileFactory";
+import SettingsStore from "./settings/SettingsStore";
+import { RoomNotificationStateStore } from "./stores/notifications/RoomNotificationStateStore";
/**
- * Returns true iff this event arriving in a room should affect the room's
+ * Returns true if this event arriving in a room should affect the room's
* count of unread messages
*
* @param {Object} ev The event
@@ -46,7 +48,7 @@ export function eventTriggersUnreadCount(ev: MatrixEvent): boolean {
}
if (ev.isRedacted()) return false;
- return haveTileForEvent(ev);
+ return haveRendererForEvent(ev, false /* hidden messages should never trigger unread counts anyways */);
}
export function doesRoomHaveUnreadMessages(room: Room): boolean {
@@ -57,14 +59,21 @@ export function doesRoomHaveUnreadMessages(room: Room): boolean {
// despite the name of the method :((
const readUpToId = room.getEventReadUpTo(myUserId);
- // as we don't send RRs for our own messages, make sure we special case that
- // if *we* sent the last message into the room, we consider it not unread!
- // Should fix: https://github.com/vector-im/element-web/issues/3263
- // https://github.com/vector-im/element-web/issues/2427
- // ...and possibly some of the others at
- // https://github.com/vector-im/element-web/issues/3363
- if (room.timeline.length && room.timeline[room.timeline.length - 1].getSender() === myUserId) {
- return false;
+ if (!SettingsStore.getValue("feature_thread")) {
+ // as we don't send RRs for our own messages, make sure we special case that
+ // if *we* sent the last message into the room, we consider it not unread!
+ // Should fix: https://github.com/vector-im/element-web/issues/3263
+ // https://github.com/vector-im/element-web/issues/2427
+ // ...and possibly some of the others at
+ // https://github.com/vector-im/element-web/issues/3363
+ if (room.timeline.length && room.timeline[room.timeline.length - 1].getSender() === myUserId) {
+ return false;
+ }
+ } else {
+ const threadState = RoomNotificationStateStore.instance.getThreadsRoomState(room);
+ if (threadState.color > 0) {
+ return true;
+ }
}
// if the read receipt relates to an event is that part of a thread
diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts
index cb39d9affb4..f7026da394e 100644
--- a/src/VoipUserMapper.ts
+++ b/src/VoipUserMapper.ts
@@ -18,10 +18,12 @@ import { Room } from 'matrix-js-sdk/src/models/room';
import { logger } from "matrix-js-sdk/src/logger";
import { EventType } from 'matrix-js-sdk/src/@types/event';
-import { ensureVirtualRoomExists, findDMForUser } from './createRoom';
+import { ensureVirtualRoomExists } from './createRoom';
import { MatrixClientPeg } from "./MatrixClientPeg";
import DMRoomMap from "./utils/DMRoomMap";
-import CallHandler, { VIRTUAL_ROOM_EVENT_TYPE } from './CallHandler';
+import CallHandler from './CallHandler';
+import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
+import { findDMForUser } from "./utils/direct-messages";
// Functions for mapping virtual users & rooms. Currently the only lookup
// is sip virtual: there could be others in the future.
diff --git a/src/accessibility/KeyboardShortcutUtils.ts b/src/accessibility/KeyboardShortcutUtils.ts
new file mode 100644
index 00000000000..434116d4303
--- /dev/null
+++ b/src/accessibility/KeyboardShortcutUtils.ts
@@ -0,0 +1,131 @@
+/*
+Copyright 2022 Šimon Brandner
+
+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 { KeyCombo } from "../KeyBindingsManager";
+import { isMac, Key } from "../Keyboard";
+import { _t, _td } from "../languageHandler";
+import PlatformPeg from "../PlatformPeg";
+import SettingsStore from "../settings/SettingsStore";
+import {
+ DESKTOP_SHORTCUTS,
+ DIGITS,
+ IKeyboardShortcuts,
+ KeyBindingAction,
+ KEYBOARD_SHORTCUTS,
+ MAC_ONLY_SHORTCUTS,
+} from "./KeyboardShortcuts";
+
+/**
+ * This function gets the keyboard shortcuts that should be presented in the UI
+ * but they shouldn't be consumed by KeyBindingDefaults. That means that these
+ * have to be manually mirrored in KeyBindingDefaults.
+ */
+const getUIOnlyShortcuts = (): IKeyboardShortcuts => {
+ const ctrlEnterToSend = SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend');
+
+ const keyboardShortcuts: IKeyboardShortcuts = {
+ [KeyBindingAction.SendMessage]: {
+ default: {
+ key: Key.ENTER,
+ ctrlOrCmdKey: ctrlEnterToSend,
+ },
+ displayName: _td("Send message"),
+ },
+ [KeyBindingAction.NewLine]: {
+ default: {
+ key: Key.ENTER,
+ shiftKey: !ctrlEnterToSend,
+ },
+ displayName: _td("New line"),
+ },
+ [KeyBindingAction.CompleteAutocomplete]: {
+ default: {
+ key: Key.ENTER,
+ },
+ displayName: _td("Complete"),
+ },
+ [KeyBindingAction.ForceCompleteAutocomplete]: {
+ default: {
+ key: Key.TAB,
+ },
+ displayName: _td("Force complete"),
+ },
+ [KeyBindingAction.SearchInRoom]: {
+ default: {
+ ctrlOrCmdKey: true,
+ key: Key.F,
+ },
+ displayName: _td("Search (must be enabled)"),
+ },
+ };
+
+ if (PlatformPeg.get().overrideBrowserShortcuts()) {
+ // XXX: This keyboard shortcut isn't manually added to
+ // KeyBindingDefaults as it can't be easily handled by the
+ // KeyBindingManager
+ keyboardShortcuts[KeyBindingAction.SwitchToSpaceByNumber] = {
+ default: {
+ ctrlOrCmdKey: true,
+ key: DIGITS,
+ },
+ displayName: _td("Switch to space by number"),
+ };
+ }
+
+ return keyboardShortcuts;
+};
+
+/**
+ * This function gets keyboard shortcuts that can be consumed by the KeyBindingDefaults.
+ */
+export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
+ const overrideBrowserShortcuts = PlatformPeg.get().overrideBrowserShortcuts();
+
+ return Object.keys(KEYBOARD_SHORTCUTS).filter((k: KeyBindingAction) => {
+ if (KEYBOARD_SHORTCUTS[k]?.controller?.settingDisabled) return false;
+ if (MAC_ONLY_SHORTCUTS.includes(k) && !isMac) return false;
+ if (DESKTOP_SHORTCUTS.includes(k) && !overrideBrowserShortcuts) return false;
+
+ return true;
+ }).reduce((o, key) => {
+ o[key] = KEYBOARD_SHORTCUTS[key];
+ return o;
+ }, {} as IKeyboardShortcuts);
+};
+
+/**
+ * Gets keyboard shortcuts that should be presented to the user in the UI.
+ */
+export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
+ const entries = [
+ ...Object.entries(getUIOnlyShortcuts()),
+ ...Object.entries(getKeyboardShortcuts()),
+ ];
+
+ return entries.reduce((acc, [key, value]) => {
+ acc[key] = value;
+ return acc;
+ }, {} as IKeyboardShortcuts);
+};
+
+export const getKeyboardShortcutValue = (name: string): KeyCombo => {
+ return getKeyboardShortcutsForUI()[name]?.default;
+};
+
+export const getKeyboardShortcutDisplayName = (name: string): string | null => {
+ const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
+ return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName);
+};
diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts
index c7f4ef68bbd..97e428d2a0f 100644
--- a/src/accessibility/KeyboardShortcuts.ts
+++ b/src/accessibility/KeyboardShortcuts.ts
@@ -18,9 +18,8 @@ limitations under the License.
import { _td } from "../languageHandler";
import { isMac, Key } from "../Keyboard";
import { IBaseSetting } from "../settings/Settings";
-import SettingsStore from "../settings/SettingsStore";
import IncompatibleController from "../settings/controllers/IncompatibleController";
-import PlatformPeg from "../PlatformPeg";
+import { KeyCombo } from "../KeyBindingsManager";
export enum KeyBindingAction {
/** Send a message */
@@ -35,11 +34,17 @@ export enum KeyBindingAction {
EditNextMessage = 'KeyBinding.editNextMessage',
/** Cancel editing a message or cancel replying to a message */
CancelReplyOrEdit = 'KeyBinding.cancelReplyInComposer',
+ /** Show the sticker picker */
+ ShowStickerPicker = 'KeyBinding.showStickerPicker',
/** Set bold format the current selection */
FormatBold = 'KeyBinding.toggleBoldInComposer',
/** Set italics format the current selection */
FormatItalics = 'KeyBinding.toggleItalicsInComposer',
+ /** Insert link for current selection */
+ FormatLink = 'KeyBinding.FormatLink',
+ /** Set code format for current selection */
+ FormatCode = 'KeyBinding.FormatCode',
/** Format the current selection as quote */
FormatQuote = 'KeyBinding.toggleQuoteInComposer',
/** Undo the last editing */
@@ -121,9 +126,9 @@ export enum KeyBindingAction {
/** Opens user settings */
OpenUserSettings = "KeyBinding.openUserSettings",
/** Navigates backward */
- PreviousVisitedRoomOrCommunity = "KeyBinding.previousVisitedRoomOrCommunity",
+ PreviousVisitedRoomOrSpace = "KeyBinding.PreviousVisitedRoomOrSpace",
/** Navigates forward */
- NextVisitedRoomOrCommunity = "KeyBinding.nextVisitedRoomOrCommunity",
+ NextVisitedRoomOrSpace = "KeyBinding.NextVisitedRoomOrSpace",
/** Toggles microphone while on a call */
ToggleMicInCall = "KeyBinding.toggleMicInCall",
@@ -149,18 +154,9 @@ export enum KeyBindingAction {
ToggleHiddenEventVisibility = 'KeyBinding.toggleHiddenEventVisibility',
}
-export type KeyBindingConfig = {
- key: string;
- ctrlOrCmdKey?: boolean;
- ctrlKey?: boolean;
- altKey?: boolean;
- shiftKey?: boolean;
- metaKey?: boolean;
-};
-
-type KeyboardShortcutSetting = IBaseSetting;
+type KeyboardShortcutSetting = IBaseSetting;
-type IKeyboardShortcuts = {
+export type IKeyboardShortcuts = {
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
[k in (KeyBindingAction)]?: KeyboardShortcutSetting;
};
@@ -218,6 +214,8 @@ export const CATEGORIES: Record = {
KeyBindingAction.FormatBold,
KeyBindingAction.FormatItalics,
KeyBindingAction.FormatQuote,
+ KeyBindingAction.FormatLink,
+ KeyBindingAction.FormatCode,
KeyBindingAction.EditUndo,
KeyBindingAction.EditRedo,
KeyBindingAction.MoveCursorToStart,
@@ -227,6 +225,7 @@ export const CATEGORIES: Record = {
KeyBindingAction.EditPrevMessage,
KeyBindingAction.SelectNextSendHistory,
KeyBindingAction.SelectPrevSendHistory,
+ KeyBindingAction.ShowStickerPicker,
],
}, [CategoryName.CALLS]: {
categoryLabel: _td("Calls"),
@@ -287,8 +286,8 @@ export const CATEGORIES: Record = {
KeyBindingAction.SelectPrevRoom,
KeyBindingAction.OpenUserSettings,
KeyBindingAction.SwitchToSpaceByNumber,
- KeyBindingAction.PreviousVisitedRoomOrCommunity,
- KeyBindingAction.NextVisitedRoomOrCommunity,
+ KeyBindingAction.PreviousVisitedRoomOrSpace,
+ KeyBindingAction.NextVisitedRoomOrSpace,
],
}, [CategoryName.AUTOCOMPLETE]: {
categoryLabel: _td("Autocomplete"),
@@ -307,14 +306,14 @@ export const CATEGORIES: Record = {
},
};
-const DESKTOP_SHORTCUTS = [
+export const DESKTOP_SHORTCUTS = [
KeyBindingAction.OpenUserSettings,
KeyBindingAction.SwitchToSpaceByNumber,
- KeyBindingAction.PreviousVisitedRoomOrCommunity,
- KeyBindingAction.NextVisitedRoomOrCommunity,
+ KeyBindingAction.PreviousVisitedRoomOrSpace,
+ KeyBindingAction.NextVisitedRoomOrSpace,
];
-const MAC_ONLY_SHORTCUTS = [
+export const MAC_ONLY_SHORTCUTS = [
KeyBindingAction.OpenUserSettings,
];
@@ -344,6 +343,21 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
},
displayName: _td("Toggle Quote"),
},
+ [KeyBindingAction.FormatCode]: {
+ default: {
+ ctrlOrCmdKey: true,
+ key: Key.E,
+ },
+ displayName: _td("Toggle Code Block"),
+ },
+ [KeyBindingAction.FormatLink]: {
+ default: {
+ ctrlOrCmdKey: true,
+ shiftKey: true,
+ key: Key.L,
+ },
+ displayName: _td("Toggle Link"),
+ },
[KeyBindingAction.CancelReplyOrEdit]: {
default: {
key: Key.ESCAPE,
@@ -392,6 +406,13 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
},
displayName: _td("Navigate to previous message in composer history"),
},
+ [KeyBindingAction.ShowStickerPicker]: {
+ default: {
+ ctrlOrCmdKey: true,
+ key: Key.SEMICOLON,
+ },
+ displayName: _td("Send a sticker"),
+ },
[KeyBindingAction.ToggleMicInCall]: {
default: {
ctrlOrCmdKey: true,
@@ -606,21 +627,21 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
},
displayName: _td("Redo edit"),
},
- [KeyBindingAction.PreviousVisitedRoomOrCommunity]: {
+ [KeyBindingAction.PreviousVisitedRoomOrSpace]: {
default: {
metaKey: isMac,
altKey: !isMac,
key: isMac ? Key.SQUARE_BRACKET_LEFT : Key.ARROW_LEFT,
},
- displayName: _td("Previous recently visited room or community"),
+ displayName: _td("Previous recently visited room or space"),
},
- [KeyBindingAction.NextVisitedRoomOrCommunity]: {
+ [KeyBindingAction.NextVisitedRoomOrSpace]: {
default: {
metaKey: isMac,
altKey: !isMac,
key: isMac ? Key.SQUARE_BRACKET_RIGHT : Key.ARROW_RIGHT,
},
- displayName: _td("Next recently visited room or community"),
+ displayName: _td("Next recently visited room or space"),
},
[KeyBindingAction.SwitchToSpaceByNumber]: {
default: {
@@ -700,99 +721,6 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
},
};
-/**
- * This function gets the keyboard shortcuts that should be presented in the UI
- * but they shouldn't be consumed by KeyBindingDefaults. That means that these
- * have to be manually mirrored in KeyBindingDefaults.
- */
-const getUIOnlyShortcuts = (): IKeyboardShortcuts => {
- const ctrlEnterToSend = SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend');
-
- const keyboardShortcuts: IKeyboardShortcuts = {
- [KeyBindingAction.SendMessage]: {
- default: {
- key: Key.ENTER,
- ctrlOrCmdKey: ctrlEnterToSend,
- },
- displayName: _td("Send message"),
- },
- [KeyBindingAction.NewLine]: {
- default: {
- key: Key.ENTER,
- shiftKey: !ctrlEnterToSend,
- },
- displayName: _td("New line"),
- },
- [KeyBindingAction.CompleteAutocomplete]: {
- default: {
- key: Key.ENTER,
- },
- displayName: _td("Complete"),
- },
- [KeyBindingAction.ForceCompleteAutocomplete]: {
- default: {
- key: Key.TAB,
- },
- displayName: _td("Force complete"),
- },
- [KeyBindingAction.SearchInRoom]: {
- default: {
- ctrlOrCmdKey: true,
- key: Key.F,
- },
- displayName: _td("Search (must be enabled)"),
- },
- };
-
- if (PlatformPeg.get().overrideBrowserShortcuts()) {
- // XXX: This keyboard shortcut isn't manually added to
- // KeyBindingDefaults as it can't be easily handled by the
- // KeyBindingManager
- keyboardShortcuts[KeyBindingAction.SwitchToSpaceByNumber] = {
- default: {
- ctrlOrCmdKey: true,
- key: DIGITS,
- },
- displayName: _td("Switch to space by number"),
- };
- }
-
- return keyboardShortcuts;
-};
-
-/**
- * This function gets keyboard shortcuts that can be consumed by the KeyBindingDefaults.
- */
-export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
- const overrideBrowserShortcuts = PlatformPeg.get().overrideBrowserShortcuts();
-
- return Object.keys(KEYBOARD_SHORTCUTS).filter((k: KeyBindingAction) => {
- if (KEYBOARD_SHORTCUTS[k]?.controller?.settingDisabled) return false;
- if (MAC_ONLY_SHORTCUTS.includes(k) && !isMac) return false;
- if (DESKTOP_SHORTCUTS.includes(k) && !overrideBrowserShortcuts) return false;
-
- return true;
- }).reduce((o, key) => {
- o[key] = KEYBOARD_SHORTCUTS[key];
- return o;
- }, {} as IKeyboardShortcuts);
-};
-
-/**
- * Gets keyboard shortcuts that should be presented to the user in the UI.
- */
-export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
- const entries = [
- ...Object.entries(getUIOnlyShortcuts()),
- ...Object.entries(getKeyboardShortcuts()),
- ];
-
- return entries.reduce((acc, [key, value]) => {
- acc[key] = value;
- return acc;
- }, {} as IKeyboardShortcuts);
-};
-
// For tests
export function mock({ keyboardShortcuts, macOnlyShortcuts, desktopShortcuts }): void {
Object.keys(KEYBOARD_SHORTCUTS).forEach((k) => delete KEYBOARD_SHORTCUTS[k]);
diff --git a/src/accessibility/roving/RovingAccessibleButton.tsx b/src/accessibility/roving/RovingAccessibleButton.tsx
index f9ce87db6a7..0d9025dd59f 100644
--- a/src/accessibility/roving/RovingAccessibleButton.tsx
+++ b/src/accessibility/roving/RovingAccessibleButton.tsx
@@ -20,13 +20,21 @@ import AccessibleButton from "../../components/views/elements/AccessibleButton";
import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types";
-interface IProps extends Omit, "onFocus" | "inputRef" | "tabIndex"> {
+interface IProps extends Omit, "inputRef" | "tabIndex"> {
inputRef?: Ref;
}
// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
-export const RovingAccessibleButton: React.FC = ({ inputRef, ...props }) => {
- const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
- return ;
+export const RovingAccessibleButton: React.FC = ({ inputRef, onFocus, ...props }) => {
+ const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
+ return {
+ onFocusInternal();
+ onFocus?.(event);
+ }}
+ inputRef={ref}
+ tabIndex={isActive ? 0 : -1}
+ />;
};
diff --git a/src/accessibility/roving/RovingAccessibleTooltipButton.tsx b/src/accessibility/roving/RovingAccessibleTooltipButton.tsx
index d9e393d7282..432e8880173 100644
--- a/src/accessibility/roving/RovingAccessibleTooltipButton.tsx
+++ b/src/accessibility/roving/RovingAccessibleTooltipButton.tsx
@@ -21,13 +21,21 @@ import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types";
type ATBProps = React.ComponentProps;
-interface IProps extends Omit {
+interface IProps extends Omit {
inputRef?: Ref;
}
// Wrapper to allow use of useRovingTabIndex for simple AccessibleTooltipButtons outside of React Functional Components.
-export const RovingAccessibleTooltipButton: React.FC = ({ inputRef, ...props }) => {
- const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
- return ;
+export const RovingAccessibleTooltipButton: React.FC = ({ inputRef, onFocus, ...props }) => {
+ const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
+ return {
+ onFocusInternal();
+ onFocus?.(event);
+ }}
+ inputRef={ref}
+ tabIndex={isActive ? 0 : -1}
+ />;
};
diff --git a/src/actions/GroupActions.ts b/src/actions/GroupActions.ts
deleted file mode 100644
index 44776f0a253..00000000000
--- a/src/actions/GroupActions.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-Copyright 2017 New Vector 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.
-*/
-
-import { MatrixClient } from "matrix-js-sdk/src/client";
-
-import { asyncAction } from './actionCreators';
-import { AsyncActionPayload } from "../dispatcher/payloads";
-
-export default class GroupActions {
- /**
- * Creates an action thunk that will do an asynchronous request to fetch
- * the groups to which a user is joined.
- *
- * @param {MatrixClient} matrixClient the matrix client to query.
- * @returns {AsyncActionPayload} An async action payload.
- * @see asyncAction
- */
- public static fetchJoinedGroups(matrixClient: MatrixClient): AsyncActionPayload {
- return asyncAction('GroupActions.fetchJoinedGroups', () => matrixClient.getJoinedGroups(), null);
- }
-}
diff --git a/src/actions/TagOrderActions.ts b/src/actions/TagOrderActions.ts
deleted file mode 100644
index 4ae600472ed..00000000000
--- a/src/actions/TagOrderActions.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
-Copyright 2017 New Vector 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.
-*/
-
-import { MatrixClient } from "matrix-js-sdk/src/client";
-
-import Analytics from '../Analytics';
-import { asyncAction } from './actionCreators';
-import GroupFilterOrderStore from '../stores/GroupFilterOrderStore';
-import { AsyncActionPayload } from "../dispatcher/payloads";
-
-export default class TagOrderActions {
- /**
- * Creates an action thunk that will do an asynchronous request to
- * move a tag in GroupFilterOrderStore to destinationIx.
- *
- * @param {MatrixClient} matrixClient the matrix client to set the
- * account data on.
- * @param {string} tag the tag to move.
- * @param {number} destinationIx the new position of the tag.
- * @returns {AsyncActionPayload} an async action payload that will
- * dispatch actions indicating the status of the request.
- * @see asyncAction
- */
- public static moveTag(matrixClient: MatrixClient, tag: string, destinationIx: number): AsyncActionPayload {
- // Only commit tags if the state is ready, i.e. not null
- let tags = GroupFilterOrderStore.getOrderedTags();
- let removedTags = GroupFilterOrderStore.getRemovedTagsAccountData() || [];
- if (!tags) {
- return;
- }
-
- tags = tags.filter((t) => t !== tag);
- tags = [...tags.slice(0, destinationIx), tag, ...tags.slice(destinationIx)];
-
- removedTags = removedTags.filter((t) => t !== tag);
-
- const storeId = GroupFilterOrderStore.getStoreId();
-
- return asyncAction('TagOrderActions.moveTag', () => {
- Analytics.trackEvent('TagOrderActions', 'commitTagOrdering');
- return matrixClient.setAccountData(
- 'im.vector.web.tag_ordering',
- { tags, removedTags, _storeId: storeId },
- );
- }, () => {
- // For an optimistic update
- return { tags, removedTags };
- });
- }
-
- /**
- * Creates an action thunk that will do an asynchronous request to
- * label a tag as removed in im.vector.web.tag_ordering account data.
- *
- * The reason this is implemented with new state `removedTags` is that
- * we incrementally and initially populate `tags` with groups that
- * have been joined. If we remove a group from `tags`, it will just
- * get added (as it looks like a group we've recently joined).
- *
- * NB: If we ever support adding of tags (which is planned), we should
- * take special care to remove the tag from `removedTags` when we add
- * it.
- *
- * @param {MatrixClient} matrixClient the matrix client to set the
- * account data on.
- * @param {string} tag the tag to remove.
- * @returns {function} an async action payload that will dispatch
- * actions indicating the status of the request.
- * @see asyncAction
- */
- public static removeTag(matrixClient: MatrixClient, tag: string): AsyncActionPayload {
- // Don't change tags, just removedTags
- const tags = GroupFilterOrderStore.getOrderedTags();
- const removedTags = GroupFilterOrderStore.getRemovedTagsAccountData() || [];
-
- if (removedTags.includes(tag)) {
- // Return a thunk that doesn't do anything, we don't even need
- // an asynchronous action here, the tag is already removed.
- return new AsyncActionPayload(() => {});
- }
-
- removedTags.push(tag);
-
- const storeId = GroupFilterOrderStore.getStoreId();
-
- return asyncAction('TagOrderActions.removeTag', () => {
- Analytics.trackEvent('TagOrderActions', 'removeTag');
- return matrixClient.setAccountData(
- 'im.vector.web.tag_ordering',
- { tags, removedTags, _storeId: storeId },
- );
- }, () => {
- // For an optimistic update
- return { removedTags };
- });
- }
-}
diff --git a/src/audio/PlaybackQueue.ts b/src/audio/PlaybackQueue.ts
index 3b1b001bb91..72ed8cf1691 100644
--- a/src/audio/PlaybackQueue.ts
+++ b/src/audio/PlaybackQueue.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2021 The Matrix.org Foundation C.I.C.
+Copyright 2021 - 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,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { EventType } from "matrix-js-sdk/src/@types/event";
@@ -26,7 +25,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
import { arrayFastClone } from "../utils/arrays";
import { PlaybackManager } from "./PlaybackManager";
import { isVoiceMessage } from "../utils/EventUtils";
-import RoomViewStore from "../stores/RoomViewStore";
+import { RoomViewStore } from "../stores/RoomViewStore";
/**
* Audio playback queue management for a given room. This keeps track of where the user
@@ -49,18 +48,18 @@ export class PlaybackQueue {
private currentPlaybackId: string; // event ID, broken out from above for ease of use
private recentFullPlays = new Set(); // event IDs
- constructor(private client: MatrixClient, private room: Room) {
+ constructor(private room: Room) {
this.loadClocks();
- RoomViewStore.addListener(() => {
- if (RoomViewStore.getRoomId() === this.room.roomId) {
- // Reset the state of the playbacks before they start mounting and enqueuing updates.
- // We reset the entirety of the queue, including order, to ensure the user isn't left
- // confused with what order the messages are playing in.
- this.currentPlaybackId = null; // this in particular stops autoplay when the room is switched to
- this.recentFullPlays = new Set();
- this.playbackIdOrder = [];
- }
+ RoomViewStore.instance.addRoomListener(this.room.roomId, (isActive) => {
+ if (!isActive) return;
+
+ // Reset the state of the playbacks before they start mounting and enqueuing updates.
+ // We reset the entirety of the queue, including order, to ensure the user isn't left
+ // confused with what order the messages are playing in.
+ this.currentPlaybackId = null; // this in particular stops autoplay when the room is switched to
+ this.recentFullPlays = new Set();
+ this.playbackIdOrder = [];
});
}
@@ -71,7 +70,7 @@ export class PlaybackQueue {
if (PlaybackQueue.queues.has(room.roomId)) {
return PlaybackQueue.queues.get(room.roomId);
}
- const queue = new PlaybackQueue(cli, room);
+ const queue = new PlaybackQueue(room);
PlaybackQueue.queues.set(room.roomId, queue);
return queue;
}
diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts
index aeee335e04d..880f4e68732 100644
--- a/src/autocomplete/Autocompleter.ts
+++ b/src/autocomplete/Autocompleter.ts
@@ -19,7 +19,6 @@ import { ReactElement } from 'react';
import { Room } from 'matrix-js-sdk/src/models/room';
import CommandProvider from './CommandProvider';
-import CommunityProvider from './CommunityProvider';
import RoomProvider from './RoomProvider';
import UserProvider from './UserProvider';
import EmojiProvider from './EmojiProvider';
@@ -27,7 +26,6 @@ import NotifProvider from './NotifProvider';
import { timeout } from "../utils/promise";
import AutocompleteProvider, { ICommand } from "./AutocompleteProvider";
import SpaceProvider from "./SpaceProvider";
-import SpaceStore from "../stores/spaces/SpaceStore";
import { TimelineRenderingType } from '../contexts/RoomContext';
export interface ISelectionRange {
@@ -55,14 +53,9 @@ const PROVIDERS = [
EmojiProvider,
NotifProvider,
CommandProvider,
+ SpaceProvider,
];
-if (SpaceStore.spacesEnabled) {
- PROVIDERS.push(SpaceProvider);
-} else {
- PROVIDERS.push(CommunityProvider);
-}
-
// Providers will get rejected if they take longer than this.
const PROVIDER_COMPLETION_TIMEOUT = 3000;
diff --git a/src/autocomplete/CommandProvider.tsx b/src/autocomplete/CommandProvider.tsx
index 23b31407a86..e9ef2e946d7 100644
--- a/src/autocomplete/CommandProvider.tsx
+++ b/src/autocomplete/CommandProvider.tsx
@@ -55,7 +55,7 @@ export default class CommandProvider extends AutocompleteProvider {
// check if the full match differs from the first word (i.e. returns false if the command has args)
if (command[0] !== command[1]) {
// The input looks like a command with arguments, perform exact match
- const name = command[1].substr(1); // strip leading `/`
+ const name = command[1].slice(1); // strip leading `/`
if (CommandMap.has(name) && CommandMap.get(name).isEnabled()) {
// some commands, namely `me` don't suit having the usage shown whilst typing their arguments
if (CommandMap.get(name).hideCompletionAfterSpace) return [];
diff --git a/src/autocomplete/CommunityProvider.tsx b/src/autocomplete/CommunityProvider.tsx
deleted file mode 100644
index b3a4079f94d..00000000000
--- a/src/autocomplete/CommunityProvider.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
-Copyright 2018 New Vector Ltd
-Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
-
-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 Group from "matrix-js-sdk/src/models/group";
-import { sortBy } from "lodash";
-import { Room } from 'matrix-js-sdk/src/models/room';
-
-import { _t } from '../languageHandler';
-import AutocompleteProvider from './AutocompleteProvider';
-import { MatrixClientPeg } from '../MatrixClientPeg';
-import QueryMatcher from './QueryMatcher';
-import { PillCompletion } from './Components';
-import { makeGroupPermalink } from "../utils/permalinks/Permalinks";
-import { ICompletion, ISelectionRange } from "./Autocompleter";
-import FlairStore from "../stores/FlairStore";
-import { mediaFromMxc } from "../customisations/Media";
-import BaseAvatar from '../components/views/avatars/BaseAvatar';
-import { TimelineRenderingType } from '../contexts/RoomContext';
-
-const COMMUNITY_REGEX = /\B\+\S*/g;
-
-function score(query, space) {
- const index = space.indexOf(query);
- if (index === -1) {
- return Infinity;
- } else {
- return index;
- }
-}
-
-export default class CommunityProvider extends AutocompleteProvider {
- matcher: QueryMatcher;
-
- constructor(room: Room, renderingType?: TimelineRenderingType) {
- super({ commandRegex: COMMUNITY_REGEX, renderingType });
- this.matcher = new QueryMatcher([], {
- keys: ['groupId', 'name', 'shortDescription'],
- });
- }
-
- async getCompletions(
- query: string,
- selection: ISelectionRange,
- force = false,
- limit = -1,
- ): Promise {
- // Disable autocompletions when composing commands because of various issues
- // (see https://github.com/vector-im/element-web/issues/4762)
- if (/^(\/join|\/leave)/.test(query)) {
- return [];
- }
-
- const cli = MatrixClientPeg.get();
- let completions = [];
- const { command, range } = this.getCurrentCommand(query, selection, force);
- if (command) {
- const joinedGroups = cli.getGroups().filter(({ myMembership }) => myMembership === 'join');
-
- const groups = (await Promise.all(joinedGroups.map(async ({ groupId }) => {
- try {
- return FlairStore.getGroupProfileCached(cli, groupId);
- } catch (e) { // if FlairStore failed, fall back to just groupId
- return Promise.resolve({
- name: '',
- groupId,
- avatarUrl: '',
- shortDescription: '',
- });
- }
- })));
-
- this.matcher.setObjects(groups);
-
- const matchedString = command[0];
- completions = this.matcher.match(matchedString, limit);
- completions = sortBy(completions, [
- (c) => score(matchedString, c.groupId),
- (c) => c.groupId.length,
- ]).map(({ avatarUrl, groupId, name }) => ({
- completion: groupId,
- suffix: ' ',
- type: "community",
- href: makeGroupPermalink(groupId),
- component: (
-
-
-
- ),
- range,
- })).slice(0, 4);
- }
- return completions;
- }
-
- getName() {
- return '💬 ' + _t('Communities');
- }
-
- renderCompletions(completions: React.ReactNode[]): React.ReactNode {
- return (
-
- { completions }
-
- );
- }
-}
diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx
index 960b0f9475c..4c051a71269 100644
--- a/src/autocomplete/EmojiProvider.tsx
+++ b/src/autocomplete/EmojiProvider.tsx
@@ -3,6 +3,7 @@ Copyright 2016 Aviral Dasgupta
Copyright 2017 Vector Creations Ltd
Copyright 2017, 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2022 Ryan Browne
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -62,6 +63,13 @@ function score(query, space) {
}
}
+function colonsTrimmed(str: string): string {
+ // Trim off leading and potentially trailing `:` to correctly match the emoji data as they exist in emojibase.
+ // Notes: The regex is pinned to the start and end of the string so that we can use the lazy-capturing `*?` matcher.
+ // It needs to be lazy so that the trailing `:` is not captured in the replacement group, if it exists.
+ return str.replace(/^:(.*?):?$/, "$1");
+}
+
export default class EmojiProvider extends AutocompleteProvider {
matcher: QueryMatcher;
nameMatcher: QueryMatcher;
@@ -75,7 +83,7 @@ export default class EmojiProvider extends AutocompleteProvider {
shouldMatchWordsOnly: false,
});
this.nameMatcher = new QueryMatcher(SORTED_EMOJI, {
- keys: ['emoji.annotation'],
+ keys: ['emoji.label'],
// For removing punctuation
shouldMatchWordsOnly: true,
});
@@ -108,8 +116,9 @@ export default class EmojiProvider extends AutocompleteProvider {
// then sort by score (Infinity if matchedString not in shortcode)
sorters.push(c => score(matchedString, c.emoji.shortcodes[0]));
// then sort by max score of all shortcodes, trim off the `:`
+ const trimmedMatch = colonsTrimmed(matchedString);
sorters.push(c => Math.min(
- ...c.emoji.shortcodes.map(s => score(matchedString.substring(1), s)),
+ ...c.emoji.shortcodes.map(s => score(trimmedMatch, s)),
));
// If the matchedString is not empty, sort by length of shortcode. Example:
// matchedString = ":bookmark"
diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx
index 1879c85fe3f..cac3531813b 100644
--- a/src/autocomplete/RoomProvider.tsx
+++ b/src/autocomplete/RoomProvider.tsx
@@ -17,7 +17,7 @@ limitations under the License.
*/
import React from "react";
-import { uniqBy, sortBy } from "lodash";
+import { sortBy, uniqBy } from "lodash";
import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from '../languageHandler';
@@ -28,7 +28,6 @@ import { PillCompletion } from './Components';
import { makeRoomPermalink } from "../utils/permalinks/Permalinks";
import { ICompletion, ISelectionRange } from "./Autocompleter";
import RoomAvatar from '../components/views/avatars/RoomAvatar';
-import SpaceStore from "../stores/spaces/SpaceStore";
import { TimelineRenderingType } from "../contexts/RoomContext";
const ROOM_REGEX = /\B#\S*/g;
@@ -58,14 +57,9 @@ export default class RoomProvider extends AutocompleteProvider {
protected getRooms() {
const cli = MatrixClientPeg.get();
- let rooms = cli.getVisibleRooms();
- // if spaces are enabled then filter them out here as they get their own autocomplete provider
- if (SpaceStore.spacesEnabled) {
- rooms = rooms.filter(r => !r.isSpaceRoom());
- }
-
- return rooms;
+ // filter out spaces here as they get their own autocomplete provider
+ return cli.getVisibleRooms().filter(r => !r.isSpaceRoom());
}
async getCompletions(
diff --git a/src/call-types.ts b/src/call-types.ts
new file mode 100644
index 00000000000..c12491e102e
--- /dev/null
+++ b/src/call-types.ts
@@ -0,0 +1,19 @@
+/*
+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.
+*/
+
+// Event type for room account data and room creation content used to mark rooms as virtual rooms
+// (and store the ID of their native room)
+export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room';
diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx
index 22316189940..187e55cc392 100644
--- a/src/components/structures/ContextMenu.tsx
+++ b/src/components/structures/ContextMenu.tsx
@@ -22,7 +22,6 @@ import classNames from "classnames";
import FocusLock from "react-focus-lock";
import { Writeable } from "../../@types/common";
-import { replaceableComponent } from "../../utils/replaceableComponent";
import UIStore from "../../stores/UIStore";
import { checkInputableElement, RovingTabIndexProvider } from "../../accessibility/RovingTabIndex";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
@@ -83,6 +82,7 @@ export interface IProps extends IPosition {
// whether this context menu should be focus managed. If false it must handle itself
managed?: boolean;
wrapperClassName?: string;
+ menuClassName?: string;
// If true, this context menu will be mounted as a child to the parent container. Otherwise
// it will be mounted to a container at the root of the DOM.
@@ -105,7 +105,6 @@ interface IState {
// Generic ContextMenu Portal wrapper
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
-@replaceableComponent("structures.ContextMenu")
export default class ContextMenu extends React.PureComponent {
private readonly initialFocus: HTMLElement;
@@ -321,7 +320,7 @@ export default class ContextMenu extends React.PureComponent {
'mx_ContextualMenu_withChevron_bottom': chevronFace === ChevronFace.Bottom,
'mx_ContextualMenu_rightAligned': this.props.rightAligned === true,
'mx_ContextualMenu_bottomAligned': this.props.bottomAligned === true,
- });
+ }, this.props.menuClassName);
const menuStyle: CSSProperties = {};
if (props.menuWidth) {
@@ -431,7 +430,7 @@ export type AboveLeftOf = IPosition & {
// Placement method for to position context menu right-aligned and flowing to the left of elementRect,
// and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?)
export const aboveLeftOf = (
- elementRect: DOMRect,
+ elementRect: Pick,
chevronFace = ChevronFace.None,
vPadding = 0,
): AboveLeftOf => {
@@ -452,9 +451,37 @@ export const aboveLeftOf = (
return menuOptions;
};
+// Placement method for to position context menu right-aligned and flowing to the right of elementRect,
+// and either above or below: wherever there is more space (maybe this should be aboveOrBelowRightOf?)
+export const aboveRightOf = (
+ elementRect: Pick,
+ chevronFace = ChevronFace.None,
+ vPadding = 0,
+): AboveLeftOf => {
+ const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
+
+ const buttonLeft = elementRect.left + window.pageXOffset;
+ const buttonBottom = elementRect.bottom + window.pageYOffset;
+ const buttonTop = elementRect.top + window.pageYOffset;
+ // Align the left edge of the menu to the left edge of the button
+ menuOptions.left = buttonLeft;
+ // Align the menu vertically on whichever side of the button has more space available.
+ if (buttonBottom < UIStore.instance.windowHeight / 2) {
+ menuOptions.top = buttonBottom + vPadding;
+ } else {
+ menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding;
+ }
+
+ return menuOptions;
+};
+
// Placement method for to position context menu right-aligned and flowing to the left of elementRect
// and always above elementRect
-export const alwaysAboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => {
+export const alwaysAboveLeftOf = (
+ elementRect: Pick,
+ chevronFace = ChevronFace.None,
+ vPadding = 0,
+) => {
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
const buttonRight = elementRect.right + window.pageXOffset;
@@ -474,7 +501,11 @@ export const alwaysAboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFac
// Placement method for to position context menu right-aligned and flowing to the right of elementRect
// and always above elementRect
-export const alwaysAboveRightOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => {
+export const alwaysAboveRightOf = (
+ elementRect: Pick,
+ chevronFace = ChevronFace.None,
+ vPadding = 0,
+) => {
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
const buttonLeft = elementRect.left + window.pageXOffset;
diff --git a/src/components/structures/CustomRoomTagPanel.js b/src/components/structures/CustomRoomTagPanel.js
deleted file mode 100644
index 365b30bb0ec..00000000000
--- a/src/components/structures/CustomRoomTagPanel.js
+++ /dev/null
@@ -1,107 +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.
-*/
-
-import React from 'react';
-import classNames from 'classnames';
-
-import CustomRoomTagStore from '../../stores/CustomRoomTagStore';
-import AutoHideScrollbar from './AutoHideScrollbar';
-import * as sdk from '../../index';
-import dis from '../../dispatcher/dispatcher';
-import * as FormattingUtils from '../../utils/FormattingUtils';
-import { replaceableComponent } from "../../utils/replaceableComponent";
-
-@replaceableComponent("structures.CustomRoomTagPanel")
-class CustomRoomTagPanel extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- tags: CustomRoomTagStore.getSortedTags(),
- };
- }
-
- componentDidMount() {
- this._tagStoreToken = CustomRoomTagStore.addListener(() => {
- this.setState({ tags: CustomRoomTagStore.getSortedTags() });
- });
- }
-
- componentWillUnmount() {
- if (this._tagStoreToken) {
- this._tagStoreToken.remove();
- }
- }
-
- render() {
- const tags = this.state.tags.map((tag) => {
- return ();
- });
-
- const classes = classNames('mx_CustomRoomTagPanel', {
- mx_CustomRoomTagPanel_empty: this.state.tags.length === 0,
- });
-
- return (
-
- );
- }
-}
-
-export default CustomRoomTagPanel;
diff --git a/src/components/structures/EmbeddedPage.tsx b/src/components/structures/EmbeddedPage.tsx
index 4b309a1838c..2053140ba43 100644
--- a/src/components/structures/EmbeddedPage.tsx
+++ b/src/components/structures/EmbeddedPage.tsx
@@ -37,7 +37,7 @@ interface IProps {
// Whether to wrap the page in a scrollbar
scrollbar?: boolean;
// Map of keys to replace with values, e.g {$placeholder: "value"}
- replaceMap?: Map;
+ replaceMap?: Record;
}
interface IState {
@@ -57,8 +57,7 @@ export default class EmbeddedPage extends React.PureComponent {
};
}
- protected translate(s: string): string {
- // default implementation - skins may wish to extend this
+ private translate(s: string): string {
return sanitizeHtml(_t(s));
}
diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx
index 8466c26bacc..d248c6556fd 100644
--- a/src/components/structures/FilePanel.tsx
+++ b/src/components/structures/FilePanel.tsx
@@ -29,7 +29,6 @@ import EventIndexPeg from "../../indexing/EventIndexPeg";
import { _t } from '../../languageHandler';
import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice";
import BaseCard from "../views/right_panel/BaseCard";
-import { replaceableComponent } from "../../utils/replaceableComponent";
import ResizeNotifier from '../../utils/ResizeNotifier';
import TimelinePanel from "./TimelinePanel";
import Spinner from "../views/elements/Spinner";
@@ -51,7 +50,6 @@ interface IState {
/*
* Component which shows the filtered file using a TimelinePanel
*/
-@replaceableComponent("structures.FilePanel")
class FilePanel extends React.Component {
static contextType = RoomContext;
diff --git a/src/components/structures/GenericErrorPage.tsx b/src/components/structures/GenericErrorPage.tsx
index 25892f46df4..f9a68753a07 100644
--- a/src/components/structures/GenericErrorPage.tsx
+++ b/src/components/structures/GenericErrorPage.tsx
@@ -16,14 +16,11 @@ limitations under the License.
import React from 'react';
-import { replaceableComponent } from "../../utils/replaceableComponent";
-
interface IProps {
title: React.ReactNode;
message: React.ReactNode;
}
-@replaceableComponent("structures.GenericErrorPage")
export default class GenericErrorPage extends React.PureComponent {
render() {
return
diff --git a/src/components/structures/GroupFilterPanel.tsx b/src/components/structures/GroupFilterPanel.tsx
deleted file mode 100644
index c86e243fa90..00000000000
--- a/src/components/structures/GroupFilterPanel.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
-Copyright 2017, 2018 New Vector 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.
-*/
-
-import React from 'react';
-import classNames from 'classnames';
-import { ClientEvent } from "matrix-js-sdk/src/client";
-
-import type { EventSubscription } from "fbemitter";
-import GroupFilterOrderStore from '../../stores/GroupFilterOrderStore';
-import GroupActions from '../../actions/GroupActions';
-import dis from '../../dispatcher/dispatcher';
-import { _t } from '../../languageHandler';
-import MatrixClientContext from "../../contexts/MatrixClientContext";
-import AutoHideScrollbar from "./AutoHideScrollbar";
-import SettingsStore from "../../settings/SettingsStore";
-import UserTagTile from "../views/elements/UserTagTile";
-import { replaceableComponent } from "../../utils/replaceableComponent";
-import UIStore from "../../stores/UIStore";
-import DNDTagTile from "../views/elements/DNDTagTile";
-import ActionButton from "../views/elements/ActionButton";
-
-interface IGroupFilterPanelProps {
-
-}
-
-// FIXME: Properly type this after migrating GroupFilterOrderStore.js to Typescript
-type OrderedTagsTemporaryType = Array<{}>;
-// FIXME: Properly type this after migrating GroupFilterOrderStore.js to Typescript
-type SelectedTagsTemporaryType = Array<{}>;
-
-interface IGroupFilterPanelState {
- // FIXME: Properly type this after migrating GroupFilterOrderStore.js to Typescript
- orderedTags: OrderedTagsTemporaryType;
- // FIXME: Properly type this after migrating GroupFilterOrderStore.js to Typescript
- selectedTags: SelectedTagsTemporaryType;
-}
-
-@replaceableComponent("structures.GroupFilterPanel")
-class GroupFilterPanel extends React.Component {
- public static contextType = MatrixClientContext;
- public context!: React.ContextType;
-
- public state = {
- orderedTags: [],
- selectedTags: [],
- };
-
- private ref = React.createRef();
- private unmounted = false;
- private groupFilterOrderStoreToken?: EventSubscription;
-
- public componentDidMount() {
- this.unmounted = false;
- this.context.on(ClientEvent.GroupMyMembership, this.onGroupMyMembership);
- this.context.on(ClientEvent.Sync, this.onClientSync);
-
- this.groupFilterOrderStoreToken = GroupFilterOrderStore.addListener(() => {
- if (this.unmounted) {
- return;
- }
- this.setState({
- orderedTags: GroupFilterOrderStore.getOrderedTags() || [],
- selectedTags: GroupFilterOrderStore.getSelectedTags(),
- });
- });
- // This could be done by anything with a matrix client
- dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
- UIStore.instance.trackElementDimensions("GroupPanel", this.ref.current);
- }
-
- public componentWillUnmount() {
- this.unmounted = true;
- this.context.removeListener(ClientEvent.GroupMyMembership, this.onGroupMyMembership);
- this.context.removeListener(ClientEvent.Sync, this.onClientSync);
- if (this.groupFilterOrderStoreToken) {
- this.groupFilterOrderStoreToken.remove();
- }
- UIStore.instance.stopTrackingElementDimensions("GroupPanel");
- }
-
- private onGroupMyMembership = () => {
- if (this.unmounted) return;
- dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
- };
-
- private onClientSync = (syncState, prevState) => {
- // Consider the client reconnected if there is no error with syncing.
- // This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
- const reconnected = syncState !== "ERROR" && prevState !== syncState;
- if (reconnected) {
- // Load joined groups
- dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
- }
- };
-
- private onClick = e => {
- // only dispatch if its not a no-op
- if (this.state.selectedTags.length > 0) {
- dis.dispatch({ action: 'deselect_tags' });
- }
- };
-
- private onClearFilterClick = ev => {
- dis.dispatch({ action: 'deselect_tags' });
- };
-
- private renderGlobalIcon() {
- if (!SettingsStore.getValue("feature_communities_v2_prototypes")) return null;
-
- return (
-
;
- }
-}
-export default GroupFilterPanel;
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
deleted file mode 100644
index d5da40b86a9..00000000000
--- a/src/components/structures/GroupView.js
+++ /dev/null
@@ -1,1424 +0,0 @@
-/*
-Copyright 2017 Vector Creations Ltd.
-Copyright 2017, 2018 New Vector Ltd.
-Copyright 2019 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 PropTypes from 'prop-types';
-import classnames from 'classnames';
-import { Group } from "matrix-js-sdk/src/models/group";
-import { sleep } from "matrix-js-sdk/src/utils";
-import { logger } from "matrix-js-sdk/src/logger";
-
-import { MatrixClientPeg } from '../../MatrixClientPeg';
-import * as sdk from '../../index';
-import dis from '../../dispatcher/dispatcher';
-import { getHostingLink } from '../../utils/HostingLink';
-import { sanitizedHtmlNode } from '../../HtmlUtils';
-import { _t, _td } from '../../languageHandler';
-import AccessibleButton from '../views/elements/AccessibleButton';
-import GroupHeaderButtons from '../views/right_panel/GroupHeaderButtons';
-import MainSplit from './MainSplit';
-import RightPanel from './RightPanel';
-import Modal from '../../Modal';
-import GroupStore from '../../stores/GroupStore';
-import FlairStore from '../../stores/FlairStore';
-import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
-import { makeGroupPermalink, makeUserPermalink } from "../../utils/permalinks/Permalinks";
-import RightPanelStore from "../../stores/right-panel/RightPanelStore";
-import AutoHideScrollbar from "./AutoHideScrollbar";
-import { mediaFromMxc } from "../../customisations/Media";
-import { replaceableComponent } from "../../utils/replaceableComponent";
-import { createSpaceFromCommunity } from "../../utils/space";
-import { Action } from "../../dispatcher/actions";
-import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
-import { UPDATE_EVENT } from "../../stores/AsyncStore";
-
-const LONG_DESC_PLACEHOLDER = _td(
- `
HTML for your community's page
-
- Use the long description to introduce new members to the community, or distribute
- some important links
-
-
- You can even add images with Matrix URLs
-
-`);
-
-const RoomSummaryType = PropTypes.shape({
- room_id: PropTypes.string.isRequired,
- profile: PropTypes.shape({
- name: PropTypes.string,
- avatar_url: PropTypes.string,
- canonical_alias: PropTypes.string,
- }).isRequired,
-});
-
-const UserSummaryType = PropTypes.shape({
- summaryInfo: PropTypes.shape({
- user_id: PropTypes.string.isRequired,
- role_id: PropTypes.string,
- avatar_url: PropTypes.string,
- displayname: PropTypes.string,
- }).isRequired,
-});
-
-class CategoryRoomList extends React.Component {
- static propTypes = {
- rooms: PropTypes.arrayOf(RoomSummaryType).isRequired,
- category: PropTypes.shape({
- profile: PropTypes.shape({
- name: PropTypes.string,
- }).isRequired,
- }),
- groupId: PropTypes.string.isRequired,
-
- // Whether the list should be editable
- editing: PropTypes.bool.isRequired,
- };
-
- onAddRoomsToSummaryClicked = (ev) => {
- ev.preventDefault();
- const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
- Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, {
- title: _t('Add rooms to the community summary'),
- description: _t("Which rooms would you like to add to this summary?"),
- placeholder: _t("Room name or address"),
- button: _t("Add to summary"),
- pickerType: 'room',
- validAddressTypes: ['mx-room-id'],
- groupId: this.props.groupId,
- onFinished: (success, addrs) => {
- if (!success) return;
- const errorList = [];
- Promise.allSettled(addrs.map((addr) => {
- return GroupStore
- .addRoomToGroupSummary(this.props.groupId, addr.address)
- .catch(() => { errorList.push(addr.address); });
- })).then(() => {
- if (errorList.length === 0) {
- return;
- }
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- Modal.createTrackedDialog(
- 'Failed to add the following room to the group summary',
- '',
- ErrorDialog,
- {
- title: _t(
- "Failed to add the following rooms to the summary of %(groupId)s:",
- { groupId: this.props.groupId },
- ),
- description: errorList.join(", "),
- },
- );
- });
- },
- }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
- };
-
- render() {
- const addButton = this.props.editing ?
- (
-
-
- { _t(
- 'Changes made to your community name and avatar ' +
- 'might not be seen by other users for up to 30 minutes.',
- {},
- {
- 'bold1': (sub) => { sub } ,
- 'bold2': (sub) => { sub } ,
- },
- ) }
-
: ;
-
- let communitiesUpgradeNotice;
- if (this.state.showUpgradeNotice) {
- let text;
- if (this.state.isUserPrivileged) {
- text = _t("You can create a Space from this community here.", {}, {
- a: sub =>
- { sub }
- ,
- });
- } else {
- text = _t("Ask the admins of this community to make it into a Space " +
- "and keep a look out for the invite.", {}, {
- a: sub =>
- { sub }
- ,
- });
- }
-
- communitiesUpgradeNotice =
-
{ _t("Communities can now be made into Spaces") }
-
- { _t("Spaces are a new way to make a community, with new features coming.") }
-
- { text }
-
- { _t("Communities won't receive further updates.") }
-
- { _t(
- 'Your community hasn\'t got a Long Description, a HTML page to show to community members. ' +
- 'Click here to open settings and give it one!',
- {},
- { 'br': },
- ) }
-
- {
- setExpanded(!expanded);
- }}
- >
-
- { WidgetUtils.getWidgetName(app) }
-
-
- { /* Code for the maximise button for once we have full screen widgets */ }
- { /* {
- }}
- className="mx_LeftPanelWidget_maximizeButton"
- tooltipClassName="mx_LeftPanelWidget_maximizeButtonTooltip"
- title={_t("Maximise")}
- />*/ }
-
-
-
- { content }
-
;
-};
-
-export default LeftPanelWidget;
diff --git a/src/components/structures/LegacyCommunityPreview.tsx b/src/components/structures/LegacyCommunityPreview.tsx
deleted file mode 100644
index 7d7f1b90fb0..00000000000
--- a/src/components/structures/LegacyCommunityPreview.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
-Copyright 2021 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, { useContext } from "react";
-
-import MatrixClientContext from "../../contexts/MatrixClientContext";
-import { _t } from "../../languageHandler";
-import AccessibleButton from "../views/elements/AccessibleButton";
-import ErrorBoundary from "../views/elements/ErrorBoundary";
-import { useAsyncMemo } from "../../hooks/useAsyncMemo";
-import Spinner from "../views/elements/Spinner";
-import GroupAvatar from "../views/avatars/GroupAvatar";
-import { linkifyElement } from "../../HtmlUtils";
-import defaultDispatcher from "../../dispatcher/dispatcher";
-import { Action } from "../../dispatcher/actions";
-import { UserTab } from "../views/dialogs/UserSettingsDialog";
-import { IGroupSummary } from "../../@types/groups";
-
-interface IProps {
- groupId: string;
-}
-
-const onSwapClick = () => {
- defaultDispatcher.dispatch({
- action: Action.ViewUserSettings,
- initialTabId: UserTab.Preferences,
- });
-};
-
-// XXX: temporary community migration component, reuses SpaceRoomView & SpacePreview classes for simplicity
-const LegacyCommunityPreview = ({ groupId }: IProps) => {
- const cli = useContext(MatrixClientContext);
-
- const groupSummary = useAsyncMemo(() => cli.getGroupSummary(groupId), [cli, groupId]);
-
- if (!groupSummary) {
- return
-
e && linkifyElement(e)}>
- { groupSummary.profile.short_description }
-
-
- { groupSummary.user?.membership === "join"
- ? _t("To view %(communityName)s, swap to communities in your preferences", {
- communityName: groupSummary.profile.name,
- }, {
- a: sub => (
- { sub }
- ),
- })
- : _t("To join %(communityName)s, swap to communities in your preferences", {
- communityName: groupSummary.profile.name,
- }, {
- a: sub => (
- { sub }
- ),
- })
- }
-
-
-
-
- ;
-};
-
-export default LegacyCommunityPreview;
diff --git a/src/components/structures/LegacyGroupView.tsx b/src/components/structures/LegacyGroupView.tsx
new file mode 100644
index 00000000000..236c4f6ecde
--- /dev/null
+++ b/src/components/structures/LegacyGroupView.tsx
@@ -0,0 +1,51 @@
+/*
+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.
+*/
+
+import * as React from "react";
+
+import AutoHideScrollbar from './AutoHideScrollbar';
+import { _t } from "../../languageHandler";
+import SdkConfig, { DEFAULTS } from "../../SdkConfig";
+
+interface IProps {
+ groupId: string;
+}
+
+const LegacyGroupView: React.FC = ({ groupId }) => {
+ // XXX: Stealing classes from the HomePage component for CSS simplicity.
+ // XXX: Inline CSS because this is all temporary
+ const learnMoreUrl = SdkConfig.get().spaces_learn_more_url ?? DEFAULTS.spaces_learn_more_url;
+ return
+
+
{ _t("That link is no longer supported") }
+
+ { _t(
+ "You're trying to access a community link (%(groupId)s). " +
+ "Communities are no longer supported and have been replaced by spaces." +
+ "Learn more about spaces here.",
+ { groupId },
+ {
+ br: () => ,
+ br2: () => ,
+ a: (sub) => { sub },
+ },
+ ) }
+
+
+ ;
+};
+
+export default LegacyGroupView;
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 4c868ab15fe..de60ca71fa1 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
+Copyright 2015 - 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.
@@ -52,28 +52,25 @@ import HostSignupContainer from '../views/host_signup/HostSignupContainer';
import { getKeyBindingsManager } from '../../KeyBindingsManager';
import { IOpts } from "../../createRoom";
import SpacePanel from "../views/spaces/SpacePanel";
-import { replaceableComponent } from "../../utils/replaceableComponent";
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
import { OwnProfileStore } from '../../stores/OwnProfileStore';
import { UPDATE_EVENT } from "../../stores/AsyncStore";
import RoomView from './RoomView';
+import type { RoomView as RoomViewType } from './RoomView';
import ToastContainer from './ToastContainer';
-import MyGroups from "./MyGroups";
import UserView from "./UserView";
-import GroupView from "./GroupView";
import BackdropPanel from "./BackdropPanel";
-import SpaceStore from "../../stores/spaces/SpaceStore";
-import GroupFilterPanel from './GroupFilterPanel';
-import CustomRoomTagPanel from './CustomRoomTagPanel';
import { mediaFromMxc } from "../../customisations/Media";
-import LegacyCommunityPreview from "./LegacyCommunityPreview";
-import { UserTab } from "../views/dialogs/UserSettingsDialog";
+import { UserTab } from "../views/dialogs/UserTab";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import RightPanelStore from '../../stores/right-panel/RightPanelStore';
import { TimelineRenderingType } from "../../contexts/RoomContext";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
+import LegacyGroupView from "./LegacyGroupView";
+import { IConfigOptions } from "../../IConfigOptions";
+import LeftPanelLiveShareWarning from '../views/beacon/LeftPanelLiveShareWarning';
// We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity.
@@ -101,18 +98,13 @@ interface IProps {
roomOobData?: IOOBData;
currentRoomId: string;
collapseLhs: boolean;
- config: {
- piwik: {
- policyUrl: string;
- };
- [key: string]: any;
- };
+ config: IConfigOptions;
currentUserId?: string;
- currentGroupId?: string;
- currentGroupIsNew?: boolean;
justRegistered?: boolean;
roomJustCreatedOpts?: IOpts;
forceTimeline?: boolean; // see props on MatrixChat
+
+ currentGroupId?: string;
}
interface IState {
@@ -134,12 +126,11 @@ interface IState {
*
* Components mounted below us can access the matrix client via the react context.
*/
-@replaceableComponent("structures.LoggedInView")
class LoggedInView extends React.Component {
static displayName = 'LoggedInView';
protected readonly _matrixClient: MatrixClient;
- protected readonly _roomView: React.RefObject;
+ protected readonly _roomView: React.RefObject;
protected readonly _resizeContainer: React.RefObject;
protected readonly resizeHandler: React.RefObject;
protected layoutWatcherRef: string;
@@ -501,7 +492,7 @@ class LoggedInView extends React.Component {
handled = true;
break;
case KeyBindingAction.ToggleRoomSidePanel:
- if (this.props.page_type === "room_view" || this.props.page_type === "group_view") {
+ if (this.props.page_type === "room_view") {
RightPanelStore.instance.togglePanel();
handled = true;
}
@@ -536,11 +527,11 @@ class LoggedInView extends React.Component {
unread: true,
});
break;
- case KeyBindingAction.PreviousVisitedRoomOrCommunity:
+ case KeyBindingAction.PreviousVisitedRoomOrSpace:
PlatformPeg.get().navigateForwardBack(true);
handled = true;
break;
- case KeyBindingAction.NextVisitedRoomOrCommunity:
+ case KeyBindingAction.NextVisitedRoomOrSpace:
PlatformPeg.get().navigateForwardBack(false);
handled = true;
break;
@@ -572,7 +563,6 @@ class LoggedInView extends React.Component {
if (
!handled &&
PlatformPeg.get().overrideBrowserShortcuts() &&
- SpaceStore.spacesEnabled &&
ev.code.startsWith("Digit") &&
ev.code !== "Digit0" && // this is the shortcut for reset zoom, don't override it
isOnlyCtrlOrCmdKeyEvent(ev)
@@ -644,10 +634,6 @@ class LoggedInView extends React.Component {
/>;
break;
- case PageTypes.MyGroups:
- pageElement = ;
- break;
-
case PageTypes.HomePage:
pageElement = ;
break;
@@ -655,16 +641,9 @@ class LoggedInView extends React.Component {
case PageTypes.UserView:
pageElement = ;
break;
- case PageTypes.GroupView:
- if (SpaceStore.spacesEnabled) {
- pageElement = ;
- } else {
- pageElement = ;
- }
+
+ case PageTypes.LegacyGroupView:
+ pageElement = ;
break;
}
@@ -693,36 +672,27 @@ class LoggedInView extends React.Component {
>
diff --git a/src/components/structures/MainSplit.tsx b/src/components/structures/MainSplit.tsx
index 7cf6964c1fd..a90758b4427 100644
--- a/src/components/structures/MainSplit.tsx
+++ b/src/components/structures/MainSplit.tsx
@@ -19,7 +19,6 @@ import React from 'react';
import { NumberSize, Resizable } from 're-resizable';
import { Direction } from "re-resizable/lib/resizer";
-import { replaceableComponent } from "../../utils/replaceableComponent";
import ResizeNotifier from "../../utils/ResizeNotifier";
interface IProps {
@@ -28,7 +27,6 @@ interface IProps {
panel?: JSX.Element;
}
-@replaceableComponent("structures.MainSplit")
export default class MainSplit extends React.Component {
private onResizeStart = (): void => {
this.props.resizeNotifier.startResizing();
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index ebc90f51ad2..328af853142 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -31,8 +31,9 @@ import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { throttle } from "lodash";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
+import { RoomType } from "matrix-js-sdk/src/@types/event";
-// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
+// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by various components
import 'focus-visible';
// what-input helps improve keyboard accessibility
import 'what-input';
@@ -83,23 +84,18 @@ import {
UPDATE_STATUS_INDICATOR,
} from "../../stores/notifications/RoomNotificationStateStore";
import { SettingLevel } from "../../settings/SettingLevel";
-import { leaveRoomBehaviour } from "../../utils/membership";
-import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore";
import { UIFeature } from "../../settings/UIFeature";
-import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore";
import DialPadModal from "../views/voip/DialPadModal";
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
import { shouldUseLoginForWelcome } from "../../utils/pages";
-import SpaceStore from "../../stores/spaces/SpaceStore";
-import { replaceableComponent } from "../../utils/replaceableComponent";
import RoomListStore from "../../stores/room-list/RoomListStore";
import { RoomUpdateCause } from "../../stores/room-list/models";
import SecurityCustomisations from "../../customisations/Security";
import Spinner from "../views/elements/Spinner";
import QuestionDialog from "../views/dialogs/QuestionDialog";
-import UserSettingsDialog, { UserTab } from '../views/dialogs/UserSettingsDialog';
-import CreateGroupDialog from '../views/dialogs/CreateGroupDialog';
+import UserSettingsDialog from '../views/dialogs/UserSettingsDialog';
+import { UserTab } from "../views/dialogs/UserTab";
import CreateRoomDialog from '../views/dialogs/CreateRoomDialog';
import RoomDirectory from './RoomDirectory';
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
@@ -131,6 +127,11 @@ import { ViewHomePagePayload } from '../../dispatcher/payloads/ViewHomePagePaylo
import { AfterLeaveRoomPayload } from '../../dispatcher/payloads/AfterLeaveRoomPayload';
import { DoAfterSyncPreparedPayload } from '../../dispatcher/payloads/DoAfterSyncPreparedPayload';
import { ViewStartChatOrReusePayload } from '../../dispatcher/payloads/ViewStartChatOrReusePayload';
+import { IConfigOptions } from "../../IConfigOptions";
+import { SnakedObject } from "../../utils/SnakedObject";
+import InfoDialog from '../views/dialogs/InfoDialog';
+import { leaveRoomBehaviour } from "../../utils/leave-behaviour";
+import VideoChannelStore from "../../stores/VideoChannelStore";
// legacy export
export { default as Views } from "../../Views";
@@ -144,7 +145,6 @@ const ONBOARDING_FLOW_STARTERS = [
Action.ViewUserSettings,
'view_create_chat',
'view_create_room',
- 'view_create_group',
];
interface IScreen {
@@ -153,12 +153,7 @@ interface IScreen {
}
interface IProps { // TODO type things better
- config: {
- piwik: {
- policyUrl: string;
- };
- [key: string]: any;
- };
+ config: IConfigOptions;
serverConfig?: ValidatedServerConfig;
onNewScreen: (screen: string, replaceLast: boolean) => void;
enableGuest?: boolean;
@@ -186,10 +181,10 @@ interface IState {
// in the case where we view a room by ID or by RoomView when it resolves
// what ID an alias points at.
currentRoomId?: string;
- currentGroupId?: string;
- currentGroupIsNew?: boolean;
// If we're trying to just view a user ID (i.e. /user URL), this is it
currentUserId?: string;
+ // Group ID for legacy "communities don't exist" page
+ currentGroupId?: string;
// this is persisted as mx_lhs_size, loaded in LoggedInView
collapseLhs: boolean;
// Parameters used in the registration dance with the IS
@@ -214,7 +209,6 @@ interface IState {
forceTimeline?: boolean; // see props
}
-@replaceableComponent("structures.MatrixChat")
export default class MatrixChat extends React.PureComponent {
static displayName = "MatrixChat";
@@ -229,7 +223,6 @@ export default class MatrixChat extends React.PureComponent {
private firstSyncPromise: IDeferred;
private screenAfterLogin?: IScreen;
- private pageChanging: boolean;
private tokenLogin?: boolean;
private accountPassword?: string;
private accountPasswordTimer?: number;
@@ -284,8 +277,6 @@ export default class MatrixChat extends React.PureComponent {
this.prevWindowWidth = UIStore.instance.windowWidth || 1000;
UIStore.instance.on(UI_EVENTS.Resize, this.handleResize);
- this.pageChanging = false;
-
// For PersistentElement
this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize);
@@ -357,7 +348,7 @@ export default class MatrixChat extends React.PureComponent {
Analytics.enable();
}
- initSentry(SdkConfig.get()["sentry"]);
+ initSentry(SdkConfig.get("sentry"));
}
private async postLoginSetup() {
@@ -476,7 +467,7 @@ export default class MatrixChat extends React.PureComponent {
private getServerProperties() {
let props = this.state.serverConfig;
if (!props) props = this.props.serverConfig; // for unit tests
- if (!props) props = SdkConfig.get()["validated_server_config"];
+ if (!props) props = SdkConfig.get("validated_server_config");
return { serverConfig: props };
}
@@ -586,6 +577,7 @@ export default class MatrixChat extends React.PureComponent {
break;
case 'logout':
CallHandler.instance.hangupAllCalls();
+ if (VideoChannelStore.instance.connected) VideoChannelStore.instance.setDisconnected();
Lifecycle.logout();
break;
case 'require_registration':
@@ -673,6 +665,9 @@ export default class MatrixChat extends React.PureComponent {
}
break;
}
+ case 'view_legacy_group':
+ this.viewLegacyGroup(payload.groupId);
+ break;
case Action.ViewUserSettings: {
const tabPayload = payload as OpenToTabPayload;
Modal.createTrackedDialog('User settings', '', UserSettingsDialog,
@@ -684,20 +679,11 @@ export default class MatrixChat extends React.PureComponent {
break;
}
case 'view_create_room':
- this.createRoom(payload.public, payload.defaultName);
+ this.createRoom(payload.public, payload.defaultName, payload.type);
// View the welcome or home page if we need something to look at
this.viewSomethingBehindModal();
break;
- case 'view_create_group': {
- const prototype = SettingsStore.getValue("feature_communities_v2_prototypes");
- Modal.createTrackedDialog(
- 'Create Community',
- '',
- prototype ? CreateCommunityPrototypeDialog : CreateGroupDialog,
- );
- break;
- }
case Action.ViewRoomDirectory: {
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
initialText: payload.initialText,
@@ -707,13 +693,6 @@ export default class MatrixChat extends React.PureComponent {
this.viewSomethingBehindModal();
break;
}
- case 'view_my_groups':
- this.setPage(PageType.MyGroups);
- this.notifyNewScreen('groups');
- break;
- case 'view_group':
- this.viewGroup(payload);
- break;
case 'view_welcome_page':
this.viewWelcome();
break;
@@ -745,17 +724,6 @@ export default class MatrixChat extends React.PureComponent {
// function will have cleared that state and not execute that path.
this.showScreenAfterLogin();
break;
- case 'toggle_my_groups':
- // persist that the user has interacted with this, use it to dismiss the beta dot
- localStorage.setItem("mx_seenSpacesBeta", "1");
- // We just dispatch the page change rather than have to worry about
- // what the logic is for each of these branches.
- if (this.state.page_type === PageType.MyGroups) {
- dis.dispatch({ action: 'view_last_screen' });
- } else {
- dis.dispatch({ action: 'view_my_groups' });
- }
- break;
case 'hide_left_panel':
this.setState({
collapseLhs: true,
@@ -867,7 +835,7 @@ export default class MatrixChat extends React.PureComponent {
);
// If the hs url matches then take the hs name we know locally as it is likely prettier
- const defaultConfig = SdkConfig.get()["validated_server_config"] as ValidatedServerConfig;
+ const defaultConfig = SdkConfig.get("validated_server_config");
if (defaultConfig && defaultConfig.hsUrl === newState.serverConfig.hsUrl) {
newState.serverConfig.hsName = defaultConfig.hsName;
newState.serverConfig.hsNameIsDifferent = defaultConfig.hsNameIsDifferent;
@@ -957,33 +925,12 @@ export default class MatrixChat extends React.PureComponent {
});
}
- private async viewGroup(payload) {
- const groupId = payload.group_id;
-
- // Wait for the first sync to complete
- if (!this.firstSyncComplete) {
- if (!this.firstSyncPromise) {
- logger.warn('Cannot view a group before first sync. group_id:', groupId);
- return;
- }
- await this.firstSyncPromise.promise;
- }
-
- this.setState({
- view: Views.LOGGED_IN,
- currentGroupId: groupId,
- currentGroupIsNew: payload.group_is_new,
- });
- this.setPage(PageType.GroupView);
- this.notifyNewScreen('group/' + groupId);
- }
-
private viewSomethingBehindModal() {
if (this.state.view !== Views.LOGGED_IN) {
this.viewWelcome();
return;
}
- if (!this.state.currentGroupId && !this.state.currentRoomId && !this.state.currentUserId) {
+ if (!this.state.currentRoomId && !this.state.currentUserId) {
this.viewHome();
}
}
@@ -1039,20 +986,19 @@ export default class MatrixChat extends React.PureComponent {
});
}
- private async createRoom(defaultPublic = false, defaultName?: string) {
- const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId();
- if (communityId) {
- // double check the user will have permission to associate this room with the community
- if (!CommunityPrototypeStore.instance.isAdminOf(communityId)) {
- Modal.createTrackedDialog('Pre-failure to create room', '', ErrorDialog, {
- title: _t("Cannot create rooms in this community"),
- description: _t("You do not have permission to create rooms in this community."),
- });
- return;
- }
- }
+ private viewLegacyGroup(groupId: string) {
+ this.setStateForNewView({
+ view: Views.LOGGED_IN,
+ currentRoomId: null,
+ currentGroupId: groupId,
+ });
+ this.notifyNewScreen('group/' + groupId);
+ this.setPage(PageType.LegacyGroupView);
+ }
+ private async createRoom(defaultPublic = false, defaultName?: string, type?: RoomType) {
const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, {
+ type,
defaultPublic,
defaultName,
});
@@ -1064,11 +1010,12 @@ export default class MatrixChat extends React.PureComponent {
}
private chatCreateOrReuse(userId: string) {
+ const snakedConfig = new SnakedObject(this.props.config);
// Use a deferred action to reshow the dialog once the user has registered
if (MatrixClientPeg.get().isGuest()) {
// No point in making 2 DMs with welcome bot. This assumes view_set_mxid will
// result in a new DM with the welcome user.
- if (userId !== this.props.config.welcomeUserId) {
+ if (userId !== snakedConfig.get("welcome_user_id")) {
dis.dispatch>({
action: Action.DoAfterSyncPrepared,
deferred_action: {
@@ -1085,7 +1032,7 @@ export default class MatrixChat extends React.PureComponent {
// `_chatCreateOrReuse` again)
go_welcome_on_cancel: true,
screen_after: {
- screen: `user/${this.props.config.welcomeUserId}`,
+ screen: `user/${snakedConfig.get("welcome_user_id")}`,
params: { action: 'chat' },
},
});
@@ -1114,7 +1061,7 @@ export default class MatrixChat extends React.PureComponent {
private leaveRoomWarnings(roomId: string) {
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
- const isSpace = SpaceStore.spacesEnabled && roomToLeave?.isSpaceRoom();
+ const isSpace = roomToLeave?.isSpaceRoom();
// Show a warning if there are additional complications.
const warnings = [];
@@ -1152,7 +1099,7 @@ export default class MatrixChat extends React.PureComponent {
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
const warnings = this.leaveRoomWarnings(roomId);
- const isSpace = SpaceStore.spacesEnabled && roomToLeave?.isSpaceRoom();
+ const isSpace = roomToLeave?.isSpaceRoom();
Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, {
title: isSpace ? _t("Leave space") : _t("Leave room"),
description: (
@@ -1233,12 +1180,13 @@ export default class MatrixChat extends React.PureComponent {
}
await waitFor;
+ const snakedConfig = new SnakedObject(this.props.config);
const welcomeUserRooms = DMRoomMap.shared().getDMRoomsForUserId(
- this.props.config.welcomeUserId,
+ snakedConfig.get("welcome_user_id"),
);
if (welcomeUserRooms.length === 0) {
const roomId = await createRoom({
- dmUserId: this.props.config.welcomeUserId,
+ dmUserId: snakedConfig.get("welcome_user_id"),
// Only view the welcome user if we're NOT looking at a room
andView: !this.state.currentRoomId,
spinner: false, // we're already showing one: we don't need another one
@@ -1252,7 +1200,7 @@ export default class MatrixChat extends React.PureComponent {
// user room (it doesn't wait for new data from the server, just
// the saved sync to be loaded).
const saveWelcomeUser = (ev: MatrixEvent) => {
- if (ev.getType() === EventType.Direct && ev.getContent()[this.props.config.welcomeUserId]) {
+ if (ev.getType() === EventType.Direct && ev.getContent()[snakedConfig.get("welcome_user_id")]) {
MatrixClientPeg.get().store.save(true);
MatrixClientPeg.get().removeListener(ClientEvent.AccountData, saveWelcomeUser);
}
@@ -1282,7 +1230,8 @@ export default class MatrixChat extends React.PureComponent {
} else if (MatrixClientPeg.currentUserIsJustRegistered()) {
MatrixClientPeg.setJustRegisteredUserId(null);
- if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
+ const snakedConfig = new SnakedObject(this.props.config);
+ if (snakedConfig.get("welcome_user_id") && getCurrentLanguage().startsWith("en")) {
const welcomeUserRoom = await this.startWelcomeUserChat();
if (welcomeUserRoom === null) {
// We didn't redirect to the welcome user room, so show
@@ -1314,7 +1263,7 @@ export default class MatrixChat extends React.PureComponent {
showAnonymousAnalyticsOptInToast();
}
- if (SdkConfig.get().mobileGuideToast) {
+ if (SdkConfig.get("mobile_guide_toast")) {
// The toast contains further logic to detect mobile platforms,
// check if it has been dismissed before, etc.
showMobileGuideToast();
@@ -1463,6 +1412,36 @@ export default class MatrixChat extends React.PureComponent {
showNotificationsToast(false);
}
+ if (!localStorage.getItem("mx_seen_feature_thread_experimental")) {
+ setTimeout(() => {
+ if (SettingsStore.getValue("feature_thread") && SdkConfig.get("show_labs_settings")) {
+ Modal.createDialog(InfoDialog, {
+ title: _t("Threads Approaching Beta 🎉"),
+ description: <>
+
+ { _t("We're getting closer to releasing a public Beta for Threads.") }
+
+
+ { _t("As we prepare for it, we need to make some changes: threads created "
+ + "before this point will be displayed as regular replies.",
+ {}, {
+ "strong": sub => { sub },
+ }) }
+
+
+ { _t("This will be a one-off transition, as threads are now part "
+ + "of the Matrix specification.") }
+
+ >,
+ button: _t("Got it"),
+ onFinished: () => {
+ localStorage.setItem("mx_seen_feature_thread_experimental", "true");
+ },
+ });
+ }
+ }, 1 * 60 * 1000); // show after 1 minute to not overload user on launch
+ }
+
if (!localStorage.getItem("mx_seen_feature_spotlight_toast")) {
setTimeout(() => {
// Skip the toast if the beta is already enabled or the user has changed the setting from default
@@ -1747,14 +1726,6 @@ export default class MatrixChat extends React.PureComponent {
const type = screen === "start_sso" ? "sso" : "cas";
PlatformPeg.get().startSingleSignOn(cli, type, this.getFragmentAfterLogin());
- } else if (screen === 'groups') {
- if (SpaceStore.spacesEnabled) {
- dis.dispatch({ action: Action.ViewHomePage });
- return;
- }
- dis.dispatch({
- action: 'view_my_groups',
- });
} else if (screen.indexOf('room/') === 0) {
// Rooms can have the following formats:
// #room_alias:domain or !opaque_id:domain
@@ -1837,12 +1808,9 @@ export default class MatrixChat extends React.PureComponent {
});
} else if (screen.indexOf('group/') === 0) {
const groupId = screen.substring(6);
-
- // TODO: Check valid group ID
-
dis.dispatch({
- action: 'view_group',
- group_id: groupId,
+ action: 'view_legacy_group',
+ groupId: groupId,
});
} else {
logger.info("Ignoring showScreen for '%s'", screen);
@@ -2130,12 +2098,3 @@ export default class MatrixChat extends React.PureComponent {
;
}
}
-
-export function isLoggedIn(): boolean {
- // JRS: Maybe we should move the step that writes this to the window out of
- // `element-web` and into this file? Better yet, we should probably create a
- // store to hold this state.
- // See also https://github.com/vector-im/element-web/issues/15034.
- const app = window.matrixChat;
- return app && (app as MatrixChat).state.view === Views.LOGGED_IN;
-}
diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx
index 20af21a9e59..38d18e92f76 100644
--- a/src/components/structures/MessagePanel.tsx
+++ b/src/components/structures/MessagePanel.tsx
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, { createRef, KeyboardEvent, ReactNode, SyntheticEvent, TransitionEvent } from 'react';
+import React, { createRef, KeyboardEvent, ReactNode, TransitionEvent } from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import { Room } from 'matrix-js-sdk/src/models/room';
@@ -31,13 +31,12 @@ import SettingsStore from '../../settings/SettingsStore';
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import { Layout } from "../../settings/enums/Layout";
import { _t } from "../../languageHandler";
-import EventTile, { haveTileForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
+import EventTile, { UnwrappedEventTile, IReadReceiptProps } from "../views/rooms/EventTile";
import { hasText } from "../../TextForEvent";
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
import DMRoomMap from "../../utils/DMRoomMap";
import NewRoomIntro from "../views/rooms/NewRoomIntro";
import HistoryTile from "../views/rooms/HistoryTile";
-import { replaceableComponent } from "../../utils/replaceableComponent";
import defaultDispatcher from '../../dispatcher/dispatcher';
import CallEventGrouper from "./CallEventGrouper";
import WhoIsTypingTile from '../views/rooms/WhoIsTypingTile';
@@ -51,8 +50,10 @@ import Spinner from "../views/elements/Spinner";
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
import EditorStateTransfer from "../../utils/EditorStateTransfer";
import { Action } from '../../dispatcher/actions';
-import { getEventDisplayInfo } from "../../utils/EventUtils";
+import { getEventDisplayInfo } from "../../utils/EventRenderingUtils";
import { IReadReceiptInfo } from "../views/rooms/ReadReceiptMarker";
+import { haveRendererForEvent } from "../../events/EventTileFactory";
+import { editorRoomKey } from "../../Editing";
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
@@ -69,6 +70,7 @@ export function shouldFormContinuation(
prevEvent: MatrixEvent,
mxEvent: MatrixEvent,
showHiddenEvents: boolean,
+ threadsEnabled: boolean,
timelineRenderingType?: TimelineRenderingType,
): boolean {
if (timelineRenderingType === TimelineRenderingType.ThreadsList) return false;
@@ -90,8 +92,16 @@ export function shouldFormContinuation(
mxEvent.sender.name !== prevEvent.sender.name ||
mxEvent.sender.getMxcAvatarUrl() !== prevEvent.sender.getMxcAvatarUrl()) return false;
+ // Thread summaries in the main timeline should break up a continuation on both sides
+ if (threadsEnabled &&
+ (mxEvent.isThreadRoot || prevEvent.isThreadRoot) &&
+ timelineRenderingType !== TimelineRenderingType.Thread
+ ) {
+ return false;
+ }
+
// if we don't have tile for previous event then it was shown by showHiddenEvents and has no SenderProfile
- if (!haveTileForEvent(prevEvent, showHiddenEvents)) return false;
+ if (!haveRendererForEvent(prevEvent, showHiddenEvents)) return false;
return true;
}
@@ -157,9 +167,6 @@ interface IProps {
// which layout to use
layout?: Layout;
- // whether or not to show flair at all
- enableFlair?: boolean;
-
resizeNotifier: ResizeNotifier;
permalinkCreator?: RoomPermalinkCreator;
editState?: EditorStateTransfer;
@@ -167,9 +174,6 @@ interface IProps {
// callback which is called when the panel is scrolled.
onScroll?(event: Event): void;
- // callback which is called when the user interacts with the room timeline
- onUserScroll(event: SyntheticEvent): void;
-
// callback which is called when more content is needed.
onFillRequest?(backwards: boolean): Promise;
@@ -197,7 +201,6 @@ interface IReadReceiptForUser {
/* (almost) stateless UI component which builds the event tiles in the room timeline.
*/
-@replaceableComponent("structures.MessagePanel")
export default class MessagePanel extends React.Component {
static contextType = RoomContext;
public context!: React.ContextType;
@@ -243,7 +246,8 @@ export default class MessagePanel extends React.Component {
// displayed event in the current render cycle.
private readReceiptsByUserId: Record = {};
- private readonly showHiddenEventsInTimeline: boolean;
+ private readonly _showHiddenEvents: boolean;
+ private readonly threadsEnabled: boolean;
private isMounted = false;
private readMarkerNode = createRef();
@@ -251,7 +255,7 @@ export default class MessagePanel extends React.Component {
private scrollPanel = createRef();
private readonly showTypingNotificationsWatcherRef: string;
- private eventTiles: Record = {};
+ private eventTiles: Record = {};
// A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
public grouperKeyMap = new WeakMap();
@@ -267,10 +271,11 @@ export default class MessagePanel extends React.Component {
hideSender: this.shouldHideSender(),
};
- // Cache hidden events setting on mount since Settings is expensive to
- // query, and we check this in a hot code path. This is also cached in
- // our RoomContext, however we still need a fallback for roomless MessagePanels.
- this.showHiddenEventsInTimeline = SettingsStore.getValue("showHiddenEventsInTimeline");
+ // Cache these settings on mount since Settings is expensive to query,
+ // and we check this in a hot code path. This is also cached in our
+ // RoomContext, however we still need a fallback for roomless MessagePanels.
+ this._showHiddenEvents = SettingsStore.getValue("showHiddenEventsInTimeline");
+ this.threadsEnabled = SettingsStore.getValue("feature_thread");
this.showTypingNotificationsWatcherRef =
SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange);
@@ -303,9 +308,10 @@ export default class MessagePanel extends React.Component {
const pendingEditItem = this.pendingEditItem;
if (!this.props.editState && this.props.room && pendingEditItem) {
+ const event = this.props.room.findEventById(pendingEditItem);
defaultDispatcher.dispatch({
action: Action.EditEvent,
- event: this.props.room.findEventById(pendingEditItem),
+ event: !event?.isRedacted() ? event : null,
timelineRenderingType: this.context.timelineRenderingType,
});
}
@@ -336,7 +342,7 @@ export default class MessagePanel extends React.Component {
return this.eventTiles[eventId]?.ref?.current;
}
- public getTileForEventId(eventId: string): EventTile {
+ public getTileForEventId(eventId: string): UnwrappedEventTile {
if (!this.eventTiles) {
return undefined;
}
@@ -450,25 +456,17 @@ export default class MessagePanel extends React.Component {
}
}
- /* check the scroll state and send out pagination requests if necessary.
- */
- public checkFillState(): void {
- if (this.scrollPanel.current) {
- this.scrollPanel.current.checkFillState();
- }
- }
-
private isUnmounting = (): boolean => {
return !this.isMounted;
};
public get showHiddenEvents(): boolean {
- return this.context?.showHiddenEventsInTimeline ?? this.showHiddenEventsInTimeline;
+ return this.context?.showHiddenEvents ?? this._showHiddenEvents;
}
// TODO: Implement granular (per-room) hide options
public shouldShowEvent(mxEv: MatrixEvent, forceHideEvents = false): boolean {
- if (this.props.hideThreadedMessages && SettingsStore.getValue("feature_thread")) {
+ if (this.props.hideThreadedMessages && this.threadsEnabled) {
if (mxEv.isThreadRelation) {
return false;
}
@@ -486,7 +484,7 @@ export default class MessagePanel extends React.Component {
return true;
}
- if (!haveTileForEvent(mxEv, this.showHiddenEvents)) {
+ if (!haveRendererForEvent(mxEv, this.showHiddenEvents)) {
return false; // no tile = no show
}
@@ -609,13 +607,15 @@ export default class MessagePanel extends React.Component {
if (!this.props.room) {
return undefined;
}
+
try {
- return localStorage.getItem(`mx_edit_room_${this.props.room.roomId}_${this.context.timelineRenderingType}`);
+ return localStorage.getItem(editorRoomKey(this.props.room.roomId, this.context.timelineRenderingType));
} catch (err) {
logger.error(err);
return undefined;
}
}
+
private getEventTiles(): ReactNode[] {
let i;
@@ -718,10 +718,8 @@ export default class MessagePanel extends React.Component {
): ReactNode[] {
const ret = [];
- const isEditing = this.props.editState &&
- this.props.editState.getEvent().getId() === mxEv.getId();
- // local echoes have a fake date, which could even be yesterday. Treat them
- // as 'today' for the date separators.
+ const isEditing = this.props.editState?.getEvent().getId() === mxEv.getId();
+ // local echoes have a fake date, which could even be yesterday. Treat them as 'today' for the date separators.
let ts1 = mxEv.getTs();
let eventDate = mxEv.getDate();
if (mxEv.status) {
@@ -746,13 +744,17 @@ export default class MessagePanel extends React.Component {
const willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEv.getDate() || new Date());
lastInSection = willWantDateSeparator ||
mxEv.getSender() !== nextEv.getSender() ||
- getEventDisplayInfo(nextEv).isInfoMessage ||
- !shouldFormContinuation(mxEv, nextEv, this.showHiddenEvents, this.context.timelineRenderingType);
+ getEventDisplayInfo(nextEv, this.showHiddenEvents).isInfoMessage ||
+ !shouldFormContinuation(
+ mxEv, nextEv, this.showHiddenEvents, this.threadsEnabled, this.context.timelineRenderingType,
+ );
}
// is this a continuation of the previous message?
const continuation = !wantsDateSeparator &&
- shouldFormContinuation(prevEvent, mxEv, this.showHiddenEvents, this.context.timelineRenderingType);
+ shouldFormContinuation(
+ prevEvent, mxEv, this.showHiddenEvents, this.threadsEnabled, this.context.timelineRenderingType,
+ );
const eventId = mxEv.getId();
const highlight = (eventId === this.props.highlightedEventId);
@@ -811,7 +813,6 @@ export default class MessagePanel extends React.Component {
getRelationsForEvent={this.props.getRelationsForEvent}
showReactions={this.props.showReactions}
layout={this.props.layout}
- enableFlair={this.props.enableFlair}
showReadReceipts={this.props.showReadReceipts}
callEventGrouper={callEventGrouper}
hideSender={this.state.hideSender}
@@ -919,7 +920,7 @@ export default class MessagePanel extends React.Component {
return receiptsByEvent;
}
- private collectEventTile = (eventId: string, node: EventTile): void => {
+ private collectEventTile = (eventId: string, node: UnwrappedEventTile): void => {
this.eventTiles[eventId] = node;
};
@@ -1022,7 +1023,6 @@ export default class MessagePanel extends React.Component {
ref={this.scrollPanel}
className={classes}
onScroll={this.props.onScroll}
- onUserScroll={this.props.onUserScroll}
onFillRequest={this.props.onFillRequest}
onUnfillRequest={this.props.onUnfillRequest}
style={style}
diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js
deleted file mode 100644
index 7efa5335edb..00000000000
--- a/src/components/structures/MyGroups.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
-Copyright 2017 Vector Creations Ltd
-Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
-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.
-*/
-
-import React from 'react';
-
-import * as sdk from '../../index';
-import { _t } from '../../languageHandler';
-import SdkConfig from '../../SdkConfig';
-import dis from '../../dispatcher/dispatcher';
-import AccessibleButton from '../views/elements/AccessibleButton';
-import MatrixClientContext from "../../contexts/MatrixClientContext";
-import AutoHideScrollbar from "./AutoHideScrollbar";
-import { replaceableComponent } from "../../utils/replaceableComponent";
-
-@replaceableComponent("structures.MyGroups")
-export default class MyGroups extends React.Component {
- static contextType = MatrixClientContext;
-
- state = {
- groups: null,
- error: null,
- };
-
- componentDidMount() {
- this._fetch();
- }
-
- _onCreateGroupClick = () => {
- dis.dispatch({ action: 'view_create_group' });
- };
-
- _fetch() {
- this.context.getJoinedGroups().then((result) => {
- this.setState({ groups: result.groups, error: null });
- }, (err) => {
- if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
- // Indicate that the guest isn't in any groups (which should be true)
- this.setState({ groups: [], error: null });
- return;
- }
- this.setState({ groups: null, error: err });
- });
- }
-
- render() {
- const brand = SdkConfig.get().brand;
- const Loader = sdk.getComponent("elements.Spinner");
- const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
- const GroupTile = sdk.getComponent("groups.GroupTile");
-
- let content;
- let contentHeader;
- if (this.state.groups) {
- const groupNodes = [];
- this.state.groups.forEach((g) => {
- groupNodes.push();
- });
- contentHeader = groupNodes.length > 0 ?
{ _t('Your Communities') }
: ;
- content = groupNodes.length > 0 ?
-
-
-
- { _t(
- "Did you know: you can use communities to filter your %(brand)s experience!",
- { brand },
- ) }
-
-
- { _t(
- "You can click on an avatar in the " +
- "filter panel at any time to see only the rooms and people associated " +
- "with that community.",
- ) }
-
-
-
- { groupNodes }
-
- :
-
- { _t(
- "You're not currently a member of any communities.",
- ) }
-
;
- } else if (this.state.error) {
- content =
- { _t('Error whilst fetching joined communities') }
-
;
- } else {
- content = ;
- }
-
- return
-
-
-
-
-
-
- { _t('Create a new community') }
-
- { _t(
- 'Create a community to group together users and rooms! ' +
- 'Build a custom homepage to mark out your space in the Matrix universe.',
- ) }
-
-
- { /*
-
-
-
-
-
- { _t('Join an existing community') }
-
- { _t(
- 'To join an existing community you\'ll have to '+
- 'know its community identifier; this will look '+
- 'something like +example:matrix.org.',
- {},
- { 'i': (sub) => { sub } })
- }
-
-
*/ }
-
-
- { contentHeader }
- { content }
-
-
;
- }
-}
diff --git a/src/components/structures/NonUrgentToastContainer.tsx b/src/components/structures/NonUrgentToastContainer.tsx
index 7872c2596cd..10cb1ad427c 100644
--- a/src/components/structures/NonUrgentToastContainer.tsx
+++ b/src/components/structures/NonUrgentToastContainer.tsx
@@ -19,7 +19,6 @@ import * as React from "react";
import { ComponentClass } from "../../@types/common";
import NonUrgentToastStore from "../../stores/NonUrgentToastStore";
import { UPDATE_EVENT } from "../../stores/AsyncStore";
-import { replaceableComponent } from "../../utils/replaceableComponent";
interface IProps {
}
@@ -28,7 +27,6 @@ interface IState {
toasts: ComponentClass[];
}
-@replaceableComponent("structures.NonUrgentToastContainer")
export default class NonUrgentToastContainer extends React.PureComponent {
public constructor(props, context) {
super(props, context);
diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx
index 6af271bcc20..e5f094773bb 100644
--- a/src/components/structures/NotificationPanel.tsx
+++ b/src/components/structures/NotificationPanel.tsx
@@ -20,7 +20,6 @@ import { logger } from "matrix-js-sdk/src/logger";
import { _t } from '../../languageHandler';
import { MatrixClientPeg } from "../../MatrixClientPeg";
import BaseCard from "../views/right_panel/BaseCard";
-import { replaceableComponent } from "../../utils/replaceableComponent";
import TimelinePanel from "./TimelinePanel";
import Spinner from "../views/elements/Spinner";
import { Layout } from "../../settings/enums/Layout";
@@ -38,7 +37,6 @@ interface IState {
/*
* Component which shows the global notification list using a TimelinePanel
*/
-@replaceableComponent("structures.NotificationPanel")
export default class NotificationPanel extends React.PureComponent {
static contextType = RoomContext;
diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx
index bb772faa806..5c2eaaf9a4e 100644
--- a/src/components/structures/RightPanel.tsx
+++ b/src/components/structures/RightPanel.tsx
@@ -28,12 +28,8 @@ import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
import WidgetCard from "../views/right_panel/WidgetCard";
-import { replaceableComponent } from "../../utils/replaceableComponent";
import SettingsStore from "../../settings/SettingsStore";
import MemberList from "../views/rooms/MemberList";
-import GroupMemberList from "../views/groups/GroupMemberList";
-import GroupRoomList from "../views/groups/GroupRoomList";
-import GroupRoomInfo from "../views/groups/GroupRoomInfo";
import UserInfo from "../views/right_panel/UserInfo";
import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo";
import FilePanel from "./FilePanel";
@@ -51,7 +47,6 @@ import { Action } from '../../dispatcher/actions';
interface IProps {
room?: Room; // if showing panels for a given room, this is set
- groupId?: string; // if showing panels for a given group, this is set
overwriteCard?: IRightPanelCard; // used to display a custom card and ignoring the RightPanelStore (used for UserView)
resizeNotifier: ResizeNotifier;
permalinkCreator?: RoomPermalinkCreator;
@@ -64,7 +59,6 @@ interface IState {
cardState?: IRightPanelCardState;
}
-@replaceableComponent("structures.RightPanel")
export default class RightPanel extends React.Component {
static contextType = MatrixClientContext;
public context!: React.ContextType;
@@ -96,9 +90,6 @@ export default class RightPanel extends React.Component {
if (props.room) {
currentCard = RightPanelStore.instance.currentCardForRoom(props.room.roomId);
}
- if (props.groupId) {
- currentCard = RightPanelStore.instance.currentGroup;
- }
if (currentCard?.phase && !RightPanelStore.instance.isPhaseValid(currentCard.phase, !!props.room)) {
// XXX: We can probably get rid of this workaround once GroupView is dead, it's unmounting happens weirdly
@@ -186,16 +177,6 @@ export default class RightPanel extends React.Component {
/>;
break;
- case RightPanelPhases.GroupMemberList:
- if (this.props.groupId) {
- card = ;
- }
- break;
-
- case RightPanelPhases.GroupRoomList:
- card = ;
- break;
-
case RightPanelPhases.RoomMemberInfo:
case RightPanelPhases.SpaceMemberInfo:
case RightPanelPhases.EncryptionPanel: {
@@ -218,24 +199,6 @@ export default class RightPanel extends React.Component {
card = ;
break;
- case RightPanelPhases.GroupMemberInfo:
- card = ;
- break;
-
- case RightPanelPhases.GroupRoomInfo:
- card = ;
- break;
-
case RightPanelPhases.NotificationPanel:
card = ;
break;
@@ -268,6 +231,7 @@ export default class RightPanel extends React.Component {
mxEvent={cardState.threadHeadEvent}
initialEvent={cardState.initialEvent}
isInitialEventHighlighted={cardState.isInitialEventHighlighted}
+ initialEventScrollIntoView={cardState.initialEventScrollIntoView}
permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus}
/>;
diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx
index b29cc245fd3..99aeb6f5478 100644
--- a/src/components/structures/RoomDirectory.tsx
+++ b/src/components/structures/RoomDirectory.tsx
@@ -16,7 +16,7 @@ limitations under the License.
*/
import React from "react";
-import { IFieldType, IInstance, IProtocol, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client";
+import { IFieldType, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client";
import { Visibility } from "matrix-js-sdk/src/@types/partials";
import { IRoomDirectoryOptions } from "matrix-js-sdk/src/@types/requests";
import { logger } from "matrix-js-sdk/src/logger";
@@ -24,21 +24,14 @@ import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import dis from "../../dispatcher/dispatcher";
import Modal from "../../Modal";
-import { linkifyAndSanitizeHtml } from '../../HtmlUtils';
import { _t } from '../../languageHandler';
import SdkConfig from '../../SdkConfig';
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
import Analytics from '../../Analytics';
-import NetworkDropdown, { ALL_ROOMS, Protocols } from "../views/directory/NetworkDropdown";
+import NetworkDropdown from "../views/directory/NetworkDropdown";
import SettingsStore from "../../settings/SettingsStore";
-import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
-import GroupStore from "../../stores/GroupStore";
-import FlairStore from "../../stores/FlairStore";
-import { replaceableComponent } from "../../utils/replaceableComponent";
-import { mediaFromMxc } from "../../customisations/Media";
import { IDialogProps } from "../views/dialogs/IDialogProps";
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
-import BaseAvatar from "../views/avatars/BaseAvatar";
import ErrorDialog from "../views/dialogs/ErrorDialog";
import QuestionDialog from "../views/dialogs/QuestionDialog";
import BaseDialog from "../views/dialogs/BaseDialog";
@@ -46,12 +39,11 @@ import DirectorySearchBox from "../views/elements/DirectorySearchBox";
import ScrollPanel from "./ScrollPanel";
import Spinner from "../views/elements/Spinner";
import { getDisplayAliasForAliasSet } from "../../Rooms";
-import { Action } from "../../dispatcher/actions";
import PosthogTrackers from "../../PosthogTrackers";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
-
-const MAX_NAME_LENGTH = 80;
-const MAX_TOPIC_LENGTH = 800;
+import { PublicRoomTile } from "../views/rooms/PublicRoomTile";
+import { getFieldsForThirdPartyLocation, joinRoomByAlias, showRoom } from "../../utils/rooms";
+import { GenericError } from "../../utils/error";
+import { ALL_ROOMS, Protocols } from "../../utils/DirectoryUtils";
const LAST_SERVER_KEY = "mx_last_room_directory_server";
const LAST_INSTANCE_KEY = "mx_last_room_directory_instance";
@@ -72,13 +64,9 @@ interface IState {
instanceId: string;
roomServer: string;
filterString: string;
- selectedCommunityId?: string;
- communityName?: string;
}
-@replaceableComponent("structures.RoomDirectory")
export default class RoomDirectory extends React.Component {
- private readonly startTime: number;
private unmounted = false;
private nextBatch: string = null;
private filterTimeout: number;
@@ -87,15 +75,11 @@ export default class RoomDirectory extends React.Component {
constructor(props) {
super(props);
- const selectedCommunityId = SettingsStore.getValue("feature_communities_v2_prototypes")
- ? GroupFilterOrderStore.getSelectedTags()[0]
- : null;
-
let protocolsLoading = true;
if (!MatrixClientPeg.get()) {
// We may not have a client yet when invoked from welcome page
protocolsLoading = false;
- } else if (!selectedCommunityId) {
+ } else {
MatrixClientPeg.get().getThirdpartyProtocols().then((response) => {
this.protocols = response;
const myHomeserver = MatrixClientPeg.getHomeserverName();
@@ -104,7 +88,7 @@ export default class RoomDirectory extends React.Component {
let roomServer = myHomeserver;
if (
- SdkConfig.get().roomDirectory?.servers?.includes(lsRoomServer) ||
+ SdkConfig.getObject("room_directory")?.get("servers")?.includes(lsRoomServer) ||
SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer)
) {
roomServer = lsRoomServer;
@@ -148,14 +132,6 @@ export default class RoomDirectory extends React.Component {
),
});
});
- } else {
- // We don't use the protocols in the communities v2 prototype experience
- protocolsLoading = false;
-
- // Grab the profile info async
- FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => {
- this.setState({ communityName: profile.name });
- });
}
this.state = {
@@ -165,8 +141,6 @@ export default class RoomDirectory extends React.Component {
instanceId: localStorage.getItem(LAST_INSTANCE_KEY),
roomServer: localStorage.getItem(LAST_SERVER_KEY),
filterString: this.props.initialText || "",
- selectedCommunityId,
- communityName: null,
protocolsLoading,
};
}
@@ -183,33 +157,6 @@ export default class RoomDirectory extends React.Component {
}
private refreshRoomList = () => {
- if (this.state.selectedCommunityId) {
- this.setState({
- publicRooms: GroupStore.getGroupRooms(this.state.selectedCommunityId).map(r => {
- return {
- // Translate all the group properties to the directory format
- room_id: r.roomId,
- name: r.name,
- topic: r.topic,
- canonical_alias: r.canonicalAlias,
- num_joined_members: r.numJoinedMembers,
- avatarUrl: r.avatarUrl,
- world_readable: r.worldReadable,
- guest_can_join: r.guestsCanJoin,
- };
- }).filter(r => {
- const filterString = this.state.filterString;
- if (filterString) {
- const containedIn = (s: string) => (s || "").toLowerCase().includes(filterString.toLowerCase());
- return containedIn(r.name) || containedIn(r.topic) || containedIn(r.canonical_alias);
- }
- return true;
- }),
- loading: false,
- });
- return;
- }
-
this.nextBatch = null;
this.setState({
publicRooms: [],
@@ -219,7 +166,6 @@ export default class RoomDirectory extends React.Component {
};
private getMoreRooms(): Promise {
- if (this.state.selectedCommunityId) return Promise.resolve(false); // no more rooms
if (!MatrixClientPeg.get()) return Promise.resolve(false);
this.setState({
@@ -299,7 +245,7 @@ export default class RoomDirectory extends React.Component {
* HS admins to do this through the RoomSettings interface, but
* this needs SPEC-417.
*/
- private removeFromDirectory(room: IPublicRoomsChunkRoom) {
+ private removeFromDirectory = (room: IPublicRoomsChunkRoom) => {
const alias = getDisplayAliasForRoom(room);
const name = room.name || alias || _t('Unnamed room');
@@ -339,14 +285,6 @@ export default class RoomDirectory extends React.Component {
});
},
});
- }
-
- private onRoomClicked = (room: IPublicRoomsChunkRoom, ev: React.MouseEvent) => {
- // If room was shift-clicked, remove it from the room directory
- if (ev.shiftKey && !this.state.selectedCommunityId) {
- ev.preventDefault();
- this.removeFromDirectory(room);
- }
};
private onOptionChange = (server: string, instanceId?: string) => {
@@ -413,62 +351,26 @@ export default class RoomDirectory extends React.Component {
};
private onJoinFromSearchClick = (alias: string) => {
- // If we don't have a particular instance id selected, just show that rooms alias
- if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) {
- // If the user specified an alias without a domain, add on whichever server is selected
- // in the dropdown
- if (alias.indexOf(':') == -1) {
- alias = alias + ':' + this.state.roomServer;
- }
- this.showRoomAlias(alias, true);
- } else {
- // This is a 3rd party protocol. Let's see if we can join it
- const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
- const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
- const fields = protocolName
- ? this.getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance)
- : null;
- if (!fields) {
- const brand = SdkConfig.get().brand;
- Modal.createTrackedDialog('Unable to join network', '', ErrorDialog, {
- title: _t('Unable to join network'),
- description: _t('%(brand)s does not know how to join a room on this network', { brand }),
+ const cli = MatrixClientPeg.get();
+ try {
+ joinRoomByAlias(cli, alias, {
+ instanceId: this.state.instanceId,
+ roomServer: this.state.roomServer,
+ protocols: this.protocols,
+ metricsTrigger: "RoomDirectory",
+ });
+ } catch (e) {
+ if (e instanceof GenericError) {
+ Modal.createTrackedDialog(e.message, '', ErrorDialog, {
+ title: e.message,
+ description: e.description,
});
- return;
+ } else {
+ throw e;
}
- MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).then((resp) => {
- if (resp.length > 0 && resp[0].alias) {
- this.showRoomAlias(resp[0].alias, true);
- } else {
- Modal.createTrackedDialog('Room not found', '', ErrorDialog, {
- title: _t('Room not found'),
- description: _t('Couldn\'t find a matching Matrix room'),
- });
- }
- }, (e) => {
- Modal.createTrackedDialog('Fetching third party location failed', '', ErrorDialog, {
- title: _t('Fetching third party location failed'),
- description: _t('Unable to look up room ID from server'),
- });
- });
}
};
- private onPreviewClick = (ev: ButtonEvent, room: IPublicRoomsChunkRoom) => {
- this.showRoom(room, null, false, true);
- ev.stopPropagation();
- };
-
- private onViewClick = (ev: ButtonEvent, room: IPublicRoomsChunkRoom) => {
- this.showRoom(room);
- ev.stopPropagation();
- };
-
- private onJoinClick = (ev: ButtonEvent, room: IPublicRoomsChunkRoom) => {
- this.showRoom(room, null, true);
- ev.stopPropagation();
- };
-
private onCreateRoomClick = (ev: ButtonEvent) => {
this.onFinished();
dis.dispatch({
@@ -479,159 +381,17 @@ export default class RoomDirectory extends React.Component {
PosthogTrackers.trackInteraction("WebRoomDirectoryCreateRoomButton", ev);
};
- private showRoomAlias(alias: string, autoJoin = false) {
- this.showRoom(null, alias, autoJoin);
- }
-
- private showRoom(room: IPublicRoomsChunkRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) {
+ private onRoomClick = (room: IPublicRoomsChunkRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) => {
this.onFinished();
- const payload: ViewRoomPayload = {
- action: Action.ViewRoom,
- auto_join: autoJoin,
- should_peek: shouldPeek,
+ const cli = MatrixClientPeg.get();
+ showRoom(cli, room, {
+ roomAlias,
+ autoJoin,
+ shouldPeek,
+ roomServer: this.state.roomServer,
metricsTrigger: "RoomDirectory",
- };
- if (room) {
- // Don't let the user view a room they won't be able to either
- // peek or join: fail earlier so they don't have to click back
- // to the directory.
- if (MatrixClientPeg.get().isGuest()) {
- if (!room.world_readable && !room.guest_can_join) {
- dis.dispatch({ action: 'require_registration' });
- return;
- }
- }
-
- if (!roomAlias) {
- roomAlias = getDisplayAliasForRoom(room);
- }
-
- payload.oob_data = {
- avatarUrl: room.avatar_url,
- // XXX: This logic is duplicated from the JS SDK which
- // would normally decide what the name is.
- name: room.name || roomAlias || _t('Unnamed room'),
- };
-
- if (this.state.roomServer) {
- payload.via_servers = [this.state.roomServer];
- }
- }
- // It's not really possible to join Matrix rooms by ID because the HS has no way to know
- // which servers to start querying. However, there's no other way to join rooms in
- // this list without aliases at present, so if roomAlias isn't set here we have no
- // choice but to supply the ID.
- if (roomAlias) {
- payload.room_alias = roomAlias;
- } else {
- payload.room_id = room.room_id;
- }
- dis.dispatch(payload);
- }
-
- private createRoomCells(room: IPublicRoomsChunkRoom) {
- const client = MatrixClientPeg.get();
- const clientRoom = client.getRoom(room.room_id);
- const hasJoinedRoom = clientRoom && clientRoom.getMyMembership() === "join";
- const isGuest = client.isGuest();
- let previewButton;
- let joinOrViewButton;
-
- // Element Web currently does not allow guests to join rooms, so we
- // instead show them preview buttons for all rooms. If the room is not
- // world readable, a modal will appear asking you to register first. If
- // it is readable, the preview appears as normal.
- if (!hasJoinedRoom && (room.world_readable || isGuest)) {
- previewButton = (
- this.onPreviewClick(ev, room)}>
- { _t("Preview") }
-
- );
- }
- if (hasJoinedRoom) {
- joinOrViewButton = (
- this.onViewClick(ev, room)}>
- { _t("View") }
-
- );
- } else if (!isGuest) {
- joinOrViewButton = (
- this.onJoinClick(ev, room)}>
- { _t("Join") }
-
- );
- }
-
- let name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
- if (name.length > MAX_NAME_LENGTH) {
- name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
- }
-
- let topic = room.topic || '';
- // Additional truncation based on line numbers is done via CSS,
- // but to ensure that the DOM is not polluted with a huge string
- // we give it a hard limit before rendering.
- if (topic.length > MAX_TOPIC_LENGTH) {
- topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
- }
- topic = linkifyAndSanitizeHtml(topic);
- let avatarUrl = null;
- if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
-
- // We use onMouseDown instead of onClick, so that we can avoid text getting selected
- return
;
- }
+ });
+ };
private stringLooksLikeId(s: string, fieldType: IFieldType) {
let pat = /^#[^\s]+:[^\s]/;
@@ -642,27 +402,11 @@ export default class RoomDirectory extends React.Component {
return pat.test(s);
}
- private getFieldsForThirdPartyLocation(userInput: string, protocol: IProtocol, instance: IInstance) {
- // make an object with the fields specified by that protocol. We
- // require that the values of all but the last field come from the
- // instance. The last is the user input.
- const requiredFields = protocol.location_fields;
- if (!requiredFields) return null;
- const fields = {};
- for (let i = 0; i < requiredFields.length - 1; ++i) {
- const thisField = requiredFields[i];
- if (instance.fields[thisField] === undefined) return null;
- fields[thisField] = instance.fields[thisField];
- }
- fields[requiredFields[requiredFields.length - 1]] = userInput;
- return fields;
- }
-
private onFinished = () => {
this.props.onFinished(false);
};
- render() {
+ public render() {
let content;
if (this.state.error) {
content = this.state.error;
@@ -670,7 +414,14 @@ export default class RoomDirectory extends React.Component {
content = ;
} else {
const cells = (this.state.publicRooms || [])
- .reduce((cells, room) => cells.concat(this.createRoomCells(room)), []);
+ .map(room =>
+ ,
+ );
// we still show the scrollpanel, at least for now, because
// otherwise we don't fetch more because we don't get a fill
// request from the scrollpanel because there isn't one
@@ -747,7 +498,7 @@ export default class RoomDirectory extends React.Component {
let showJoinButton = this.stringLooksLikeId(this.state.filterString, instanceExpectedFieldType);
if (protocolName) {
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
- if (this.getFieldsForThirdPartyLocation(
+ if (getFieldsForThirdPartyLocation(
this.state.filterString,
this.protocols[protocolName],
instance,
@@ -756,18 +507,6 @@ export default class RoomDirectory extends React.Component {
}
}
- let dropdown = (
-
- );
- if (this.state.selectedCommunityId) {
- dropdown = null;
- }
-
listHeader =