Merge branch 'develop' into dbkr/key_backup_by_default
33
.github/actions/download-verify-element-tarball/action.yml
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: Upload release assets
|
||||
description: Uploads assets to an existing release and optionally signs them
|
||||
inputs:
|
||||
tag:
|
||||
description: GitHub release tag to fetch assets from.
|
||||
required: true
|
||||
out-file-path:
|
||||
description: Path to where the webapp should be extracted to.
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Download current version for its old bundles
|
||||
id: current_download
|
||||
uses: robinraju/release-downloader@a96f54c1b5f5e09e47d9504526e96febd949d4c2 # v1
|
||||
with:
|
||||
tag: steps.current_version.outputs.version
|
||||
fileName: element-*.tar.gz*
|
||||
out-file-path: ${{ runner.temp }}/download-verify-element-tarball
|
||||
|
||||
- name: Verify tarball
|
||||
run: gpg --verify element-*.tar.gz.asc element-*.tar.gz
|
||||
working-directory: ${{ runner.temp }}/download-verify-element-tarball
|
||||
|
||||
- name: Extract tarball
|
||||
run: tar xvzf element-*.tar.gz -C webapp --strip-components=1
|
||||
working-directory: ${{ runner.temp }}/download-verify-element-tarball
|
||||
|
||||
- name: Move webapp to out-file-path
|
||||
run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp ${{ inputs.out-file-path }}
|
||||
|
||||
- name: Clean up temp directory
|
||||
run: rm -R ${{ runner.temp }}/download-verify-element-tarball
|
3
.github/labels.yml
vendored
|
@ -232,6 +232,9 @@
|
|||
- name: "Z-Flaky-Test"
|
||||
description: "A test is raising false alarms"
|
||||
color: "ededed"
|
||||
- name: "Z-Flaky-Jest-Test"
|
||||
description: "A Jest test is raising false alarms"
|
||||
color: "ededed"
|
||||
- name: "Z-FOSDEM"
|
||||
description: "Issues in chat.fosdem.org"
|
||||
color: "ededed"
|
||||
|
|
2
.github/workflows/backport.yml
vendored
|
@ -7,6 +7,8 @@ on:
|
|||
branches:
|
||||
- develop
|
||||
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
name: Backport
|
||||
|
|
1
.github/workflows/build.yml
vendored
|
@ -10,6 +10,7 @@ env:
|
|||
# These must be set for fetchdep.sh to get the right branch
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
build:
|
||||
name: "Build on ${{ matrix.image }}"
|
||||
|
|
1
.github/workflows/build_debian.yaml
vendored
|
@ -3,6 +3,7 @@ on:
|
|||
release:
|
||||
types: [published]
|
||||
concurrency: ${{ github.workflow }}
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
jobs:
|
||||
build:
|
||||
name: Build package
|
||||
|
|
5
.github/workflows/build_develop.yml
vendored
|
@ -9,6 +9,7 @@ on:
|
|||
concurrency:
|
||||
group: ${{ github.repository_owner }}-${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
permissions: {}
|
||||
jobs:
|
||||
build:
|
||||
name: "Build & Deploy develop.element.io"
|
||||
|
@ -16,6 +17,10 @@ jobs:
|
|||
if: github.repository == 'element-hq/element-web'
|
||||
runs-on: ubuntu-24.04
|
||||
environment: develop
|
||||
permissions:
|
||||
checks: read
|
||||
pages: write
|
||||
deployments: write
|
||||
env:
|
||||
R2_BUCKET: "element-web-develop"
|
||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||
|
|
88
.github/workflows/deploy.yml
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
# Manual deploy workflow for deploying to app.element.io & staging.element.io
|
||||
# Runs automatically for staging.element.io when an RC or Release is published
|
||||
# Note: Does *NOT* run automatically for app.element.io so that it gets tested on staging.element.io beforehand
|
||||
name: Build and Deploy ${{ inputs.site || 'staging.element.io' }}
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
site:
|
||||
description: Which site to deploy to
|
||||
required: true
|
||||
default: staging.element.io
|
||||
type: choice
|
||||
options:
|
||||
- staging.element.io
|
||||
- app.element.io
|
||||
concurrency: ${{ inputs.site || 'staging.element.io' }}
|
||||
permissions: {}
|
||||
jobs:
|
||||
deploy:
|
||||
name: "Deploy to Cloudflare Pages"
|
||||
runs-on: ubuntu-24.04
|
||||
environment: ${{ inputs.site || 'staging.element.io' }}
|
||||
permissions:
|
||||
checks: read
|
||||
deployments: write
|
||||
env:
|
||||
SITE: ${{ inputs.site || 'staging.element.io' }}
|
||||
steps:
|
||||
- name: Load GPG key
|
||||
run: |
|
||||
curl https://packages.element.io/element-release-key.gpg | gpg --import
|
||||
gpg -k "$GPG_FINGERPRINT"
|
||||
env:
|
||||
GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }}
|
||||
|
||||
- name: Check current version on deployment
|
||||
id: current_version
|
||||
run: |
|
||||
echo "version=$(curl -s https://$SITE/version)" >> $GITHUB_OUTPUT
|
||||
|
||||
# The current version bundle melding dance is skipped if the version we're deploying is the same
|
||||
# as then we're just doing a re-deploy of the same version with potentially different configs.
|
||||
- name: Download current version for its old bundles
|
||||
id: current_download
|
||||
if: steps.current_version.outputs.version != github.ref_name
|
||||
uses: element-hq/element-web/.github/actions/download-verify-element-tarball@${{ github.ref_name }}
|
||||
with:
|
||||
tag: steps.current_version.outputs.version
|
||||
out-file-path: current_version
|
||||
|
||||
- name: Download target version
|
||||
uses: element-hq/element-web/.github/actions/download-verify-element-tarball@${{ github.ref_name }}
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
out-file-path: _deploy
|
||||
|
||||
- name: Merge current bundles into target
|
||||
if: steps.current_download.outcome == 'success'
|
||||
run: cp -vnpr current_version/bundles/* _deploy/bundles/
|
||||
|
||||
- name: Copy config
|
||||
run: cp element.io/app/config.json _deploy/config.json
|
||||
|
||||
- name: Populate 404.html
|
||||
run: echo "404 Not Found" > _deploy/404.html
|
||||
|
||||
- name: Populate _headers
|
||||
run: cp .github/cfp_headers _deploy/_headers
|
||||
|
||||
- name: Wait for other steps to succeed
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
running-workflow-name: "Build and Deploy ${{ env.SITE }}"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages).)*$
|
||||
|
||||
- name: Deploy to Cloudflare Pages
|
||||
uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
|
||||
accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }}
|
||||
projectName: ${{ env.SITE == 'staging.element.io' && 'element-web-staging' || 'element-web' }}
|
||||
directory: _deploy
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
10
.github/workflows/dockerhub.yaml
vendored
|
@ -7,14 +7,14 @@ on:
|
|||
# This job can take a while, and we have usage limits, so just publish develop only twice a day
|
||||
- cron: "0 7/12 * * *"
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
|
||||
permissions:
|
||||
id-token: write # needed for signing the images with GitHub OIDC Token
|
||||
permissions: {}
|
||||
jobs:
|
||||
buildx:
|
||||
name: Docker Buildx
|
||||
runs-on: ubuntu-24.04
|
||||
environment: dockerhub
|
||||
permissions:
|
||||
id-token: write # needed for signing the images with GitHub OIDC Token
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
@ -39,7 +39,7 @@ jobs:
|
|||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5
|
||||
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5
|
||||
with:
|
||||
images: |
|
||||
vectorim/element-web
|
||||
|
@ -51,7 +51,7 @@ jobs:
|
|||
|
||||
- name: Build and push
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6
|
||||
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
|
8
.github/workflows/docs.yml
vendored
|
@ -5,10 +5,7 @@ on:
|
|||
branches: [develop]
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
|
@ -100,6 +97,9 @@ jobs:
|
|||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
|
|
|
@ -11,6 +11,8 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event.workflow_run.event == 'pull_request' }}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
report:
|
||||
if: github.event.workflow_run.conclusion != 'cancelled'
|
||||
|
@ -20,11 +22,12 @@ jobs:
|
|||
permissions:
|
||||
statuses: write
|
||||
deployments: write
|
||||
actions: read
|
||||
steps:
|
||||
- name: Download HTML report
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
name: html-report
|
||||
path: playwright-report
|
||||
|
|
2
.github/workflows/end-to-end-tests.yaml
vendored
|
@ -33,6 +33,8 @@ env:
|
|||
# fetchdep.sh needs to know our PR number
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
permissions: {} # No permissions required
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build Element-Web"
|
||||
|
|
1
.github/workflows/issue_closed.yml
vendored
|
@ -4,6 +4,7 @@
|
|||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
jobs:
|
||||
tidy:
|
||||
name: Tidy closed issues
|
||||
|
|
1
.github/workflows/localazy_download.yaml
vendored
|
@ -3,6 +3,7 @@ on:
|
|||
workflow_dispatch: {}
|
||||
schedule:
|
||||
- cron: "0 6 * * 1,3,5" # Every Monday, Wednesday and Friday at 6am UTC
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
jobs:
|
||||
download:
|
||||
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_download.yaml@main
|
||||
|
|
1
.github/workflows/localazy_upload.yaml
vendored
|
@ -4,6 +4,7 @@ on:
|
|||
branches: [develop]
|
||||
paths:
|
||||
- "src/i18n/strings/en_EN.json"
|
||||
permissions: {} # No permissions needed
|
||||
jobs:
|
||||
upload:
|
||||
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_upload.yaml@main
|
||||
|
|
5
.github/workflows/netlify.yaml
vendored
|
@ -11,6 +11,9 @@ jobs:
|
|||
if: github.event.workflow_run.conclusion != 'cancelled' && github.event.workflow_run.event == 'pull_request'
|
||||
runs-on: ubuntu-24.04
|
||||
environment: Netlify
|
||||
permissions:
|
||||
actions: read
|
||||
deployments: write
|
||||
steps:
|
||||
- name: 📝 Create Deployment
|
||||
uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8 # v1
|
||||
|
@ -27,7 +30,7 @@ jobs:
|
|||
- name: 📥 Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
name: webapp
|
||||
path: webapp
|
||||
|
|
1
.github/workflows/pending-reviews.yaml
vendored
|
@ -6,6 +6,7 @@ on:
|
|||
#schedule:
|
||||
# - cron: "*/10 * * * *"
|
||||
concurrency: ${{ github.workflow }}
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
jobs:
|
||||
bot:
|
||||
name: Pending reviews bot
|
||||
|
|
|
@ -3,9 +3,12 @@ on:
|
|||
workflow_dispatch: {}
|
||||
schedule:
|
||||
- cron: "0 6 * * *" # Every day at 6am UTC
|
||||
permissions: {}
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
|
3
.github/workflows/pull_request.yaml
vendored
|
@ -4,8 +4,11 @@ on:
|
|||
types: [opened, edited, labeled, unlabeled, synchronize]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
permissions: {}
|
||||
jobs:
|
||||
action:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
|
||||
permissions:
|
||||
pull-requests: write
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
|
|
@ -2,6 +2,7 @@ name: Pull Request Base Branch
|
|||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize]
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
check_base_branch:
|
||||
name: Check PR base branch
|
||||
|
|
3
.github/workflows/release-drafter.yml
vendored
|
@ -4,6 +4,9 @@ on:
|
|||
branches: [staging]
|
||||
workflow_dispatch: {}
|
||||
concurrency: ${{ github.workflow }}
|
||||
permissions: {}
|
||||
jobs:
|
||||
draft:
|
||||
permissions:
|
||||
contents: write
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/release-drafter-workflow.yml@develop
|
||||
|
|
1
.github/workflows/release-gitflow.yml
vendored
|
@ -4,6 +4,7 @@ on:
|
|||
push:
|
||||
branches: [master]
|
||||
concurrency: ${{ github.repository }}-${{ github.workflow }}
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
jobs:
|
||||
merge:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/release-gitflow.yml@develop
|
||||
|
|
7
.github/workflows/release.yml
vendored
|
@ -11,9 +11,14 @@ on:
|
|||
- rc
|
||||
- final
|
||||
concurrency: ${{ github.workflow }}
|
||||
permissions: {}
|
||||
jobs:
|
||||
release:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/release-make.yml@develop
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: read
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
@ -42,6 +47,8 @@ jobs:
|
|||
name: Post release checks
|
||||
needs: release
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
checks: read
|
||||
steps:
|
||||
- name: Wait for dockerhub
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
|
|
16
.github/workflows/release_prepare.yml
vendored
|
@ -17,9 +17,25 @@ on:
|
|||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
permissions: {} # Uses ELEMENT_BOT_TOKEN instead
|
||||
jobs:
|
||||
checks:
|
||||
name: Sanity checks
|
||||
strategy:
|
||||
matrix:
|
||||
repo:
|
||||
- matrix-org/matrix-js-sdk
|
||||
- element-hq/element-web
|
||||
- element-hq/element-desktop
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/release-checks.yml@develop
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
with:
|
||||
repository: ${{ matrix.repo }}
|
||||
|
||||
prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: checks
|
||||
env:
|
||||
# The order is specified bottom-up to avoid any races for allchange
|
||||
REPOS: matrix-js-sdk element-web element-desktop
|
||||
|
|
5
.github/workflows/sonarqube.yml
vendored
|
@ -7,11 +7,16 @@ on:
|
|||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }}
|
||||
cancel-in-progress: true
|
||||
permissions: {}
|
||||
jobs:
|
||||
sonarqube:
|
||||
name: 🩻 SonarQube
|
||||
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'merge_group'
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop
|
||||
permissions:
|
||||
actions: read
|
||||
statuses: write
|
||||
id-token: write # sonar
|
||||
secrets:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
|
4
.github/workflows/static_analysis.yaml
vendored
|
@ -16,6 +16,8 @@ env:
|
|||
REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
permissions: {} # No permissions required
|
||||
|
||||
jobs:
|
||||
ts_lint:
|
||||
name: "Typescript Syntax Check"
|
||||
|
@ -37,6 +39,8 @@ jobs:
|
|||
i18n_lint:
|
||||
name: "i18n Check"
|
||||
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
|
||||
permissions:
|
||||
pull-requests: read
|
||||
with:
|
||||
hardcoded-words: "Element"
|
||||
allowed-hardcoded-keys: |
|
||||
|
|
3
.github/workflows/sync-labels.yml
vendored
|
@ -8,6 +8,9 @@ on:
|
|||
- develop
|
||||
paths:
|
||||
- .github/labels.yml
|
||||
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
|
||||
jobs:
|
||||
sync-labels:
|
||||
uses: element-hq/element-meta/.github/workflows/sync-labels.yml@develop
|
||||
|
|
6
.github/workflows/tests.yml
vendored
|
@ -26,6 +26,8 @@ env:
|
|||
# fetchdep.sh needs to know our PR number
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
jest:
|
||||
name: Jest
|
||||
|
@ -94,13 +96,15 @@ jobs:
|
|||
needs: jest
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
statuses: write
|
||||
steps:
|
||||
- if: needs.jest.result != 'skipped' && needs.jest.result != 'success'
|
||||
run: exit 1
|
||||
|
||||
- name: Skip SonarCloud in merge queue
|
||||
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
|
||||
uses: Sibz/github-status-action@faaa4d96fecf273bd762985e0e7f9f933c774918 # v1
|
||||
uses: guibranco/github-status-action-v2@66088c44e212a906c32a047529a213d81809ec1c
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
state: success
|
||||
|
|
2
.github/workflows/triage-assigned.yml
vendored
|
@ -4,6 +4,8 @@ on:
|
|||
issues:
|
||||
types: [assigned]
|
||||
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
|
||||
jobs:
|
||||
web-app-team:
|
||||
runs-on: ubuntu-24.04
|
||||
|
|
2
.github/workflows/triage-incoming.yml
vendored
|
@ -4,6 +4,8 @@ on:
|
|||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
|
||||
jobs:
|
||||
automate-project-columns:
|
||||
runs-on: ubuntu-24.04
|
||||
|
|
2
.github/workflows/triage-labelled.yml
vendored
|
@ -8,6 +8,8 @@ on:
|
|||
ELEMENT_BOT_TOKEN:
|
||||
required: true
|
||||
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
|
||||
jobs:
|
||||
apply_Z-Labs_label:
|
||||
name: Add Z-Labs label for features behind labs flags
|
||||
|
|
|
@ -3,6 +3,7 @@ on:
|
|||
pull_request_target:
|
||||
types: [review_requested]
|
||||
|
||||
permissions: {} # Uses ELEMENT_BOT_TOKEN instead
|
||||
jobs:
|
||||
add_design_pr_to_project:
|
||||
name: Move PRs asking for design review to the design board
|
||||
|
|
|
@ -2,6 +2,7 @@ name: Close stale flaky issues
|
|||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
permissions: {}
|
||||
jobs:
|
||||
close:
|
||||
runs-on: ubuntu-24.04
|
||||
|
|
4
.github/workflows/triage-unlabelled.yml
vendored
|
@ -3,11 +3,13 @@ name: Move unlabelled from needs info columns to triaged
|
|||
on:
|
||||
issues:
|
||||
types: [unlabeled]
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
Move_Unabeled_Issue_On_Project_Board:
|
||||
name: Move no longer X-Needs-Info issues to Triaged
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
repository-projects: read
|
||||
if: >
|
||||
${{
|
||||
!contains(github.event.issue.labels.*.name, 'X-Needs-Info') }}
|
||||
|
|
1
.github/workflows/update-jitsi.yml
vendored
|
@ -4,6 +4,7 @@ on:
|
|||
workflow_dispatch: {}
|
||||
schedule:
|
||||
- cron: "0 3 * * 0" # 3am every Sunday
|
||||
permissions: {} # We use ELEMENT_BOT_TOKEN instead
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-24.04
|
||||
|
|
1
.github/workflows/update-topics.yaml
vendored
|
@ -15,6 +15,7 @@ on:
|
|||
required: true
|
||||
type: string
|
||||
concurrency: ${{ github.workflow }}
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
bot:
|
||||
name: Release topic update
|
||||
|
|
20
CHANGELOG.md
|
@ -1,3 +1,23 @@
|
|||
Changes in [1.11.86](https://github.com/element-hq/element-web/releases/tag/v1.11.86) (2024-11-19)
|
||||
==================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Deduplicate icons using Compound Design Tokens ([#28419](https://github.com/element-hq/element-web/pull/28419)). Contributed by @t3chguy.
|
||||
* Let widget driver send error details ([#28357](https://github.com/element-hq/element-web/pull/28357)). Contributed by @AndrewFerr.
|
||||
* Deduplicate icons using Compound Design Tokens ([#28381](https://github.com/element-hq/element-web/pull/28381)). Contributed by @t3chguy.
|
||||
* Auto approvoce `io.element.call.reaction` capability for element call widgets ([#28401](https://github.com/element-hq/element-web/pull/28401)). Contributed by @toger5.
|
||||
* Show message type prefix in thread root \& reply previews ([#28361](https://github.com/element-hq/element-web/pull/28361)). Contributed by @t3chguy.
|
||||
* Support sending encrypted to device messages from widgets ([#28315](https://github.com/element-hq/element-web/pull/28315)). Contributed by @hughns.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Feed events to widgets as they are decrypted (even if out of order) ([#28376](https://github.com/element-hq/element-web/pull/28376)). Contributed by @robintown.
|
||||
* Handle authenticated media when downloading from ImageView ([#28379](https://github.com/element-hq/element-web/pull/28379)). Contributed by @t3chguy.
|
||||
* Ignore `m.3pid_changes` for Identity service 3PID changes ([#28375](https://github.com/element-hq/element-web/pull/28375)). Contributed by @t3chguy.
|
||||
* Fix markdown escaping wrongly passing html through ([#28363](https://github.com/element-hq/element-web/pull/28363)). Contributed by @t3chguy.
|
||||
* Remove "Upgrade your encryption" flow in `CreateSecretStorageDialog` ([#28290](https://github.com/element-hq/element-web/pull/28290)). Contributed by @florianduros.
|
||||
|
||||
|
||||
Changes in [1.11.85](https://github.com/element-hq/element-web/releases/tag/v1.11.85) (2024-11-12)
|
||||
==================================================================================================
|
||||
# Security
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
// Stub out FontManager for tests as it doesn't validate anything we don't already know given
|
||||
// our fixed test environment and it requires the installation of node-canvas.
|
||||
|
||||
module.exports = {
|
||||
fixupColorFonts: () => Promise.resolve(),
|
||||
};
|
|
@ -592,4 +592,3 @@ The following are undocumented or intended for developer use only.
|
|||
2. `sync_timeline_limit`
|
||||
3. `dangerously_allow_unsafe_and_insecure_passwords`
|
||||
4. `latex_maths_delims`: An optional setting to override the default delimiters used for maths parsing. See https://github.com/matrix-org/matrix-react-sdk/pull/5939 for details. Only used when `feature_latex_maths` is enabled.
|
||||
5. `voice_broadcast.chunk_length`: Target chunk length in seconds for the Voice Broadcast feature currently under development.
|
||||
|
|
|
@ -32,7 +32,6 @@ const config: Config = {
|
|||
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
|
||||
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
||||
"context-filter-polyfill": "<rootDir>/__mocks__/empty.js",
|
||||
"FontManager.ts": "<rootDir>/__mocks__/FontManager.js",
|
||||
"workers/(.+)Factory": "<rootDir>/__mocks__/workerFactoryMock.js",
|
||||
"^!!raw-loader!.*": "jest-raw-loader",
|
||||
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
||||
|
|
17
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "element-web",
|
||||
"version": "1.11.85",
|
||||
"version": "1.11.86",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
|
@ -73,7 +73,7 @@
|
|||
"resolutions": {
|
||||
"oidc-client-ts": "3.1.0",
|
||||
"jwt-decode": "4.0.0",
|
||||
"caniuse-lite": "1.0.30001679",
|
||||
"caniuse-lite": "1.0.30001684",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
||||
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
|
@ -86,7 +86,7 @@
|
|||
"@matrix-org/spec": "^1.7.0",
|
||||
"@sentry/browser": "^8.0.0",
|
||||
"@vector-im/compound-design-tokens": "^2.0.1",
|
||||
"@vector-im/compound-web": "^7.3.0",
|
||||
"@vector-im/compound-web": "^7.4.0",
|
||||
"@vector-im/matrix-wysiwyg": "2.37.13",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||
|
@ -114,10 +114,10 @@
|
|||
"jsrsasign": "^11.0.0",
|
||||
"jszip": "^3.7.0",
|
||||
"katex": "^0.16.0",
|
||||
"linkify-element": "4.1.3",
|
||||
"linkify-react": "4.1.3",
|
||||
"linkify-string": "4.1.3",
|
||||
"linkifyjs": "4.1.3",
|
||||
"linkify-element": "4.1.4",
|
||||
"linkify-react": "4.1.4",
|
||||
"linkify-string": "4.1.4",
|
||||
"linkifyjs": "4.1.4",
|
||||
"lodash": "^4.17.21",
|
||||
"maplibre-gl": "^4.0.0",
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
|
@ -268,11 +268,12 @@
|
|||
"postcss-preset-env": "^10.0.0",
|
||||
"postcss-scss": "^4.0.4",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"prettier": "3.3.3",
|
||||
"prettier": "3.4.1",
|
||||
"process": "^0.11.10",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^6.0.0",
|
||||
"semver": "^7.5.2",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"stylelint": "^16.1.0",
|
||||
"stylelint-config-standard": "^36.0.0",
|
||||
"stylelint-scss": "^6.0.0",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM mcr.microsoft.com/playwright:v1.48.2-jammy
|
||||
FROM mcr.microsoft.com/playwright:v1.49.0-jammy
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
|
|
|
@ -67,6 +67,9 @@ test.describe("Cryptography", function () {
|
|||
await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click();
|
||||
await app.viewRoomByName("Test room");
|
||||
|
||||
// In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// There should be two historical events in the timeline
|
||||
const tiles = await page.locator(".mx_EventTile").all();
|
||||
expect(tiles.length).toBeGreaterThanOrEqual(2);
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
logOutOfElement,
|
||||
verify,
|
||||
} from "./utils";
|
||||
import { bootstrapCrossSigningForClient } from "../../pages/client.ts";
|
||||
|
||||
test.describe("Cryptography", function () {
|
||||
test.use({
|
||||
|
@ -307,5 +308,30 @@ test.describe("Cryptography", function () {
|
|||
const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" });
|
||||
await expect(penultimate.locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("should show correct shields on events sent by users with changed identity", async ({
|
||||
page,
|
||||
app,
|
||||
bot: bob,
|
||||
homeserver,
|
||||
}) => {
|
||||
// Verify Bob
|
||||
await verify(app, bob);
|
||||
|
||||
// Bob logs in a new device and resets cross-signing
|
||||
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
|
||||
await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true);
|
||||
|
||||
/* should show an error for a message from a previously verified device */
|
||||
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified");
|
||||
const last = page.locator(".mx_EventTile_last");
|
||||
await expect(last).toContainText("test encrypted from user that was previously verified");
|
||||
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||
"Sender's verified identity has changed",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
|
|||
// Docker tag to use for synapse docker image.
|
||||
// We target a specific digest as every now and then a Synapse update will break our CI.
|
||||
// This digest is updated by the playwright-image-updates.yaml workflow periodically.
|
||||
const DOCKER_TAG = "develop@sha256:d947f40999b060ad4856c0af741b8619fa131430a29922606e374fdba532082b";
|
||||
const DOCKER_TAG = "develop@sha256:489fe921e03440af87e001106c41c70ffc55a1e8078d1a7f45e16fbaddc5088a";
|
||||
|
||||
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
|
||||
const templateDir = path.join(__dirname, "templates", opts.template);
|
||||
|
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 975 KiB After Width: | Height: | Size: 975 KiB |
Before Width: | Height: | Size: 1 MiB After Width: | Height: | Size: 1 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
@ -319,6 +319,7 @@
|
|||
@import "./views/rooms/_ThirdPartyMemberInfo.pcss";
|
||||
@import "./views/rooms/_ThreadSummary.pcss";
|
||||
@import "./views/rooms/_TopUnreadMessagesBar.pcss";
|
||||
@import "./views/rooms/_UserIdentityWarning.pcss";
|
||||
@import "./views/rooms/_VoiceRecordComposerTile.pcss";
|
||||
@import "./views/rooms/_WhoIsTypingTile.pcss";
|
||||
@import "./views/rooms/wysiwyg_composer/_EditWysiwygComposer.pcss";
|
||||
|
@ -392,9 +393,3 @@
|
|||
@import "./views/voip/_LegacyCallViewHeader.pcss";
|
||||
@import "./views/voip/_LegacyCallViewSidebar.pcss";
|
||||
@import "./views/voip/_VideoFeed.pcss";
|
||||
@import "./voice-broadcast/atoms/_LiveBadge.pcss";
|
||||
@import "./voice-broadcast/atoms/_VoiceBroadcastControl.pcss";
|
||||
@import "./voice-broadcast/atoms/_VoiceBroadcastHeader.pcss";
|
||||
@import "./voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss";
|
||||
@import "./voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss";
|
||||
@import "./voice-broadcast/molecules/_VoiceBroadcastBody.pcss";
|
||||
|
|
|
@ -22,20 +22,6 @@ Please see LICENSE files in the repository root for full details.
|
|||
pointer-events: none; /* makes the avatar non-draggable */
|
||||
}
|
||||
}
|
||||
|
||||
.mx_UserMenu_userAvatarLive {
|
||||
align-items: center;
|
||||
background-color: $alert;
|
||||
border-radius: 6px;
|
||||
color: $live-badge-color;
|
||||
display: flex;
|
||||
height: 12px;
|
||||
justify-content: center;
|
||||
left: 25px;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_UserMenu_contextMenuButton {
|
||||
|
|
|
@ -256,10 +256,6 @@ Please see LICENSE files in the repository root for full details.
|
|||
mask-image: url("@vector-im/compound-design-tokens/icons/mic-on-solid.svg");
|
||||
}
|
||||
|
||||
.mx_MessageComposer_voiceBroadcast::before {
|
||||
mask-image: url("$(res)/img/element-icons/live.svg");
|
||||
}
|
||||
|
||||
.mx_MessageComposer_plain_text::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/composer/plain_text.svg");
|
||||
}
|
||||
|
|
28
res/css/views/rooms/_UserIdentityWarning.pcss
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_UserIdentityWarning {
|
||||
/* 42px is the padding-left of .mx_MessageComposer_wrapper in res/css/views/rooms/_MessageComposer.pcss */
|
||||
margin-left: calc(-42px + var(--RoomView_MessageList-padding));
|
||||
|
||||
.mx_UserIdentityWarning_row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_BaseAvatar {
|
||||
margin-left: var(--cpd-space-2x);
|
||||
}
|
||||
.mx_UserIdentityWarning_main {
|
||||
margin-left: var(--cpd-space-6x);
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MessageComposer.mx_MessageComposer--compact > .mx_UserIdentityWarning {
|
||||
margin-left: calc(-25px + var(--RoomView_MessageList-padding));
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_LiveBadge {
|
||||
align-items: center;
|
||||
background-color: $alert;
|
||||
border-radius: 2px;
|
||||
color: $live-badge-color;
|
||||
display: inline-flex;
|
||||
font-size: $font-12px;
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
gap: $spacing-4;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.mx_LiveBadge--grey {
|
||||
background-color: $quaternary-content;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_VoiceBroadcastControl {
|
||||
align-items: center;
|
||||
background-color: $background;
|
||||
border-radius: 50%;
|
||||
color: $secondary-content;
|
||||
display: flex;
|
||||
flex: 0 0 32px;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastControl-recording {
|
||||
color: $alert;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastControl-play .mx_Icon {
|
||||
left: 1px;
|
||||
position: relative;
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_VoiceBroadcastHeader {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: $spacing-8;
|
||||
line-height: 20px;
|
||||
margin-bottom: $spacing-16;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastHeader_content {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastHeader_room_wrapper {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastHeader_room {
|
||||
font-size: $font-12px;
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastHeader_line {
|
||||
align-items: center;
|
||||
color: $secondary-content;
|
||||
font-size: $font-12px;
|
||||
display: flex;
|
||||
gap: $spacing-4;
|
||||
|
||||
.mx_Spinner {
|
||||
flex: 0 0 14px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastHeader_mic--clickable {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_VoiceBroadcastRecordingConnectionError {
|
||||
align-items: center;
|
||||
color: $alert;
|
||||
display: flex;
|
||||
gap: $spacing-12;
|
||||
|
||||
svg path {
|
||||
fill: $alert;
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_RoomTile .mx_RoomTile_titleContainer .mx_RoomTile_subtitle.mx_RoomTile_subtitle--voice-broadcast {
|
||||
align-items: center;
|
||||
color: $alert;
|
||||
display: flex;
|
||||
gap: $spacing-4;
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_VoiceBroadcastBody {
|
||||
background-color: $quinary-content;
|
||||
border-radius: 8px;
|
||||
color: $secondary-content;
|
||||
display: inline-block;
|
||||
font-size: $font-12px;
|
||||
padding: $spacing-12;
|
||||
width: 271px;
|
||||
|
||||
.mx_Clock {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastBody--pip {
|
||||
background-color: $system;
|
||||
box-shadow: 0 2px 8px 0 #0000004a;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastBody--small {
|
||||
display: flex;
|
||||
gap: $spacing-8;
|
||||
width: 192px;
|
||||
|
||||
.mx_VoiceBroadcastHeader {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastControl {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.mx_LiveBadge {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastBody_divider {
|
||||
background-color: $quinary-content;
|
||||
border: 0;
|
||||
height: 1px;
|
||||
margin: $spacing-12 0;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastBody_controls {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: $spacing-32;
|
||||
justify-content: center;
|
||||
margin-bottom: $spacing-8;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastBody_timerow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton.mx_VoiceBroadcastBody_blockButton {
|
||||
display: flex;
|
||||
gap: $spacing-8;
|
||||
}
|
||||
|
||||
.mx_VoiceBroadcastBody__small-close {
|
||||
right: 8px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
}
|
|
@ -240,11 +240,6 @@ $location-live-secondary-color: #deddfd;
|
|||
}
|
||||
/* ******************** */
|
||||
|
||||
/* Voice Broadcast */
|
||||
/* ******************** */
|
||||
$live-badge-color: #ffffff;
|
||||
/* ******************** */
|
||||
|
||||
/* One-off colors */
|
||||
/* ******************** */
|
||||
$progressbar-bg-color: var(--cpd-color-gray-200);
|
||||
|
|
|
@ -226,11 +226,6 @@ $location-live-color: #5c56f5;
|
|||
$location-live-secondary-color: #deddfd;
|
||||
/* ******************** */
|
||||
|
||||
/* Voice Broadcast */
|
||||
/* ******************** */
|
||||
$live-badge-color: #ffffff;
|
||||
/* ******************** */
|
||||
|
||||
body {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
|
|
@ -325,11 +325,6 @@ $location-live-color: #5c56f5;
|
|||
$location-live-secondary-color: #deddfd;
|
||||
/* ******************** */
|
||||
|
||||
/* Voice Broadcast */
|
||||
/* ******************** */
|
||||
$live-badge-color: #ffffff;
|
||||
/* ******************** */
|
||||
|
||||
body {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
|
|
@ -143,3 +143,21 @@ $inter-unicode-range: U+0000-20e2, U+20e4-23ce, U+23d0-24c1, U+24c3-259f, U+25c2
|
|||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
|
||||
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* Twemoji COLR */
|
||||
@font-face {
|
||||
font-family: "Twemoji";
|
||||
font-weight: 400;
|
||||
src: url("$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2") format("woff2");
|
||||
}
|
||||
/* For at least Chrome on Windows 10, we have to explictly add extra weights for the emoji to appear in bold messages, etc. */
|
||||
@font-face {
|
||||
font-family: "Twemoji";
|
||||
font-weight: 600;
|
||||
src: url("$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Twemoji";
|
||||
font-weight: 700;
|
||||
src: url("$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2") format("woff2");
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
/* 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", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif,
|
||||
"Noto Color Emoji";
|
||||
$font-family: "Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica",
|
||||
sans-serif, "Noto Color Emoji";
|
||||
|
||||
$monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier",
|
||||
monospace, "Noto Color Emoji";
|
||||
|
@ -355,11 +355,6 @@ $location-live-color: var(--cpd-color-purple-900);
|
|||
$location-live-secondary-color: var(--cpd-color-purple-600);
|
||||
/* ******************** */
|
||||
|
||||
/* Voice Broadcast */
|
||||
/* ******************** */
|
||||
$live-badge-color: var(--cpd-color-icon-on-solid-primary);
|
||||
/* ******************** */
|
||||
|
||||
body {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print "Usage: %s <source> <dest>" % (sys.argv[0],)
|
||||
print "eg. %s pt_BR.json pt.json" % (sys.argv[0],)
|
||||
print
|
||||
print "Adds any translations to <dest> that exist in <source> but not <dest>"
|
||||
sys.exit(1)
|
||||
|
||||
srcpath = sys.argv[1]
|
||||
dstpath = sys.argv[2]
|
||||
tmppath = dstpath + ".tmp"
|
||||
|
||||
with open(srcpath) as f:
|
||||
src = json.load(f)
|
||||
|
||||
with open(dstpath) as f:
|
||||
dst = json.load(f)
|
||||
|
||||
toAdd = {}
|
||||
for k,v in src.iteritems():
|
||||
if k not in dst:
|
||||
print "Adding %s" % (k,)
|
||||
toAdd[k] = v
|
||||
|
||||
# don't just json.dumps as we'll probably re-order all the keys (and they're
|
||||
# not in any given order so we can't just sort_keys). Append them to the end.
|
||||
with open(dstpath) as ifp:
|
||||
with open(tmppath, 'w') as ofp:
|
||||
for line in ifp:
|
||||
strippedline = line.strip()
|
||||
if strippedline in ('{', '}'):
|
||||
ofp.write(line)
|
||||
elif strippedline.endswith(','):
|
||||
ofp.write(line)
|
||||
else:
|
||||
ofp.write(' '+strippedline+',')
|
||||
toAddStr = json.dumps(toAdd, indent=4, separators=(',', ': '), ensure_ascii=False, encoding="utf8").strip("{}\n")
|
||||
ofp.write("\n")
|
||||
ofp.write(toAddStr.encode('utf8'))
|
||||
ofp.write("\n")
|
||||
|
||||
os.rename(tmppath, dstpath)
|
|
@ -1,84 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Fetches the js-sdk dependency for development or testing purposes
|
||||
# If there exists a branch of that dependency with the same name as
|
||||
# the branch the current checkout is on, use that branch. Otherwise,
|
||||
# use develop.
|
||||
|
||||
set -x
|
||||
|
||||
GIT_CLONE_ARGS=("$@")
|
||||
[ -z "$defbranch" ] && defbranch="develop"
|
||||
|
||||
# clone a specific branch of a github repo
|
||||
function clone() {
|
||||
org=$1
|
||||
repo=$2
|
||||
branch=$3
|
||||
|
||||
# Chop 'origin' off the start as jenkins ends up using
|
||||
# branches on the origin, but this doesn't work if we
|
||||
# specify the branch when cloning.
|
||||
branch=${branch#origin/}
|
||||
|
||||
if [ -n "$branch" ]
|
||||
then
|
||||
echo "Trying to use $org/$repo#$branch"
|
||||
# Disable auth prompts: https://serverfault.com/a/665959
|
||||
GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch $branch \
|
||||
"${GIT_CLONE_ARGS[@]}"
|
||||
return $?
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function dodep() {
|
||||
deforg=$1
|
||||
defrepo=$2
|
||||
rm -rf $defrepo
|
||||
|
||||
# Try the PR author's branch in case it exists on the deps as well.
|
||||
# Try the target branch of the push or PR.
|
||||
# Use the default branch as the last resort.
|
||||
if [[ "$BUILDKITE" == true ]]; then
|
||||
# If BUILDKITE_BRANCH is set, it will contain either:
|
||||
# * "branch" when the author's branch and target branch are in the same repo
|
||||
# * "author:branch" when the author's branch is in their fork
|
||||
# We can split on `:` into an array to check.
|
||||
BUILDKITE_BRANCH_ARRAY=(${BUILDKITE_BRANCH//:/ })
|
||||
if [[ "${#BUILDKITE_BRANCH_ARRAY[@]}" == "2" ]]; then
|
||||
prAuthor=${BUILDKITE_BRANCH_ARRAY[0]}
|
||||
prBranch=${BUILDKITE_BRANCH_ARRAY[1]}
|
||||
else
|
||||
prAuthor=$deforg
|
||||
prBranch=$BUILDKITE_BRANCH
|
||||
fi
|
||||
clone $prAuthor $defrepo $prBranch ||
|
||||
clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH ||
|
||||
clone $deforg $defrepo $defbranch ||
|
||||
return $?
|
||||
else
|
||||
clone $deforg $defrepo $ghprbSourceBranch ||
|
||||
clone $deforg $defrepo $GIT_BRANCH ||
|
||||
clone $deforg $defrepo `git rev-parse --abbrev-ref HEAD` ||
|
||||
clone $deforg $defrepo $defbranch ||
|
||||
return $?
|
||||
fi
|
||||
|
||||
echo "$defrepo set to branch "`git -C "$defrepo" rev-parse --abbrev-ref HEAD`
|
||||
}
|
||||
|
||||
##############################
|
||||
|
||||
echo 'Setting up matrix-js-sdk'
|
||||
|
||||
dodep matrix-org matrix-js-sdk
|
||||
|
||||
pushd matrix-js-sdk
|
||||
yarn link
|
||||
yarn install --frozen-lockfile
|
||||
popd
|
||||
|
||||
yarn link matrix-js-sdk
|
||||
|
||||
##############################
|
|
@ -1,64 +0,0 @@
|
|||
# Copyright 2017-2024 New Vector Ltd.
|
||||
|
||||
# SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
# Please see LICENSE in the repository root for full details.
|
||||
|
||||
|
||||
# genflags.sh - Generates pngs for use with CountryDropdown.js
|
||||
#
|
||||
# Dependencies:
|
||||
# - imagemagick --with-rsvg (because default imagemagick SVG
|
||||
# renderer does not produce accurate results)
|
||||
#
|
||||
# on macOS, this is most easily done with:
|
||||
# brew install imagemagick --with-librsvg
|
||||
#
|
||||
# This will clone the googlei18n flag repo before converting
|
||||
# all phonenumber.js-supported country flags (as SVGs) into
|
||||
# PNGs that can be used by CountryDropdown.js.
|
||||
|
||||
set -e
|
||||
|
||||
# Allow CTRL+C to terminate the script
|
||||
trap "echo Exited!; exit;" SIGINT SIGTERM
|
||||
|
||||
# git clone the google repo to get flag SVGs
|
||||
git clone git@github.com:googlei18n/region-flags
|
||||
for f in region-flags/svg/*.svg; do
|
||||
# Skip state flags
|
||||
if [[ $f =~ [A-Z]{2}-[A-Z]{2,3}.svg ]] ; then
|
||||
echo "Skipping state flag "$f
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip countries not included in phonenumber.js
|
||||
if [[ $f =~ (AC|CP|DG|EA|EU|IC|TA|UM|UN|XK).svg ]] ; then
|
||||
echo "Skipping non-phonenumber supported flag "$f
|
||||
continue
|
||||
fi
|
||||
|
||||
# Run imagemagick convert
|
||||
# -background none : transparent background
|
||||
# -resize 50x30 : resize the flag to have a height of 15px (2x)
|
||||
# By default, aspect ratio is respected so the width will
|
||||
# be correct and not necessarily 25px.
|
||||
# -filter Lanczos : use sharper resampling to avoid muddiness
|
||||
# -gravity Center : keep the image central when adding an -extent
|
||||
# -border 1 : add a 1px border around the flag
|
||||
# -bordercolor : set the border colour
|
||||
# -extent 54x54 : surround the image with padding so that it
|
||||
# has the dimensions 27x27px (2x).
|
||||
convert $f -background none -filter Lanczos -resize 50x30 \
|
||||
-gravity Center -border 1 -bordercolor \#e0e0e0 \
|
||||
-extent 54x54 $f.png
|
||||
|
||||
# $f.png will be region-flags/svg/XX.svg.png at this point
|
||||
|
||||
# Extract filename from path $f
|
||||
newname=${f##*/}
|
||||
# Replace .svg with .png
|
||||
newname=${newname%.svg}.png
|
||||
# Move the file to flags directory
|
||||
mv $f.png ../res/flags/$newname
|
||||
echo "Generated res/flags/"$newname
|
||||
done
|
13
src/@types/matrix-js-sdk.d.ts
vendored
|
@ -10,7 +10,6 @@ import type { IWidget } from "matrix-widget-api";
|
|||
import type { BLURHASH_FIELD } from "../utils/image-media";
|
||||
import type { JitsiCallMemberEventType, JitsiCallMemberContent } from "../call-types";
|
||||
import type { ILayoutStateEvent, WIDGET_LAYOUT_EVENT_TYPE } from "../stores/widgets/types";
|
||||
import type { VoiceBroadcastInfoEventContent, VoiceBroadcastInfoEventType } from "../voice-broadcast/types";
|
||||
import type { EncryptedFile } from "matrix-js-sdk/src/types";
|
||||
|
||||
// Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types
|
||||
|
@ -22,6 +21,13 @@ declare module "matrix-js-sdk/src/types" {
|
|||
[BLURHASH_FIELD]?: string;
|
||||
}
|
||||
|
||||
export interface ImageInfo {
|
||||
/**
|
||||
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/4230
|
||||
*/
|
||||
"org.matrix.msc4230.is_animated"?: boolean;
|
||||
}
|
||||
|
||||
export interface StateEvents {
|
||||
// Jitsi-backed video room state events
|
||||
[JitsiCallMemberEventType]: JitsiCallMemberContent;
|
||||
|
@ -30,9 +36,6 @@ declare module "matrix-js-sdk/src/types" {
|
|||
"im.vector.modular.widgets": IWidget | {};
|
||||
[WIDGET_LAYOUT_EVENT_TYPE]: ILayoutStateEvent;
|
||||
|
||||
// Unstable voice broadcast state events
|
||||
[VoiceBroadcastInfoEventType]: VoiceBroadcastInfoEventContent;
|
||||
|
||||
// Element custom state events
|
||||
"im.vector.web.settings": Record<string, any>;
|
||||
"org.matrix.room.preview_urls": { disable: boolean };
|
||||
|
@ -71,7 +74,5 @@ declare module "matrix-js-sdk/src/types" {
|
|||
waveform?: number[];
|
||||
};
|
||||
"org.matrix.msc3245.voice"?: {};
|
||||
|
||||
"io.element.voice_broadcast_chunk"?: { sequence: number };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ import { createThumbnail } from "./utils/image-media";
|
|||
import { attachMentions, attachRelation } from "./components/views/rooms/SendMessageComposer";
|
||||
import { doMaybeLocalRoomAction } from "./utils/local-room";
|
||||
import { SdkContextClass } from "./contexts/SDKContext";
|
||||
import { blobIsAnimated } from "./utils/Image.ts";
|
||||
|
||||
// scraped out of a macOS hidpi (5660ppm) screenshot png
|
||||
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
|
||||
|
@ -150,15 +151,20 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag
|
|||
thumbnailType = "image/jpeg";
|
||||
}
|
||||
|
||||
// We don't await this immediately so it can happen in the background
|
||||
const isAnimatedPromise = blobIsAnimated(imageFile.type, imageFile);
|
||||
|
||||
const imageElement = await loadImageElement(imageFile);
|
||||
|
||||
const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType);
|
||||
const imageInfo = result.info;
|
||||
|
||||
imageInfo["org.matrix.msc4230.is_animated"] = await isAnimatedPromise;
|
||||
|
||||
// For lesser supported image types, always include the thumbnail even if it is larger
|
||||
if (!ALWAYS_INCLUDE_THUMBNAIL.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;
|
||||
const sizeDifference = imageFile.size - imageInfo.thumbnail_info!.size!;
|
||||
if (
|
||||
// image is small enough already
|
||||
imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL ||
|
||||
|
|
|
@ -230,12 +230,15 @@ export default class DeviceListener {
|
|||
private async getKeyBackupInfo(): Promise<KeyBackupInfo | null> {
|
||||
if (!this.client) return null;
|
||||
const now = new Date().getTime();
|
||||
const crypto = this.client.getCrypto();
|
||||
if (!crypto) return null;
|
||||
|
||||
if (
|
||||
!this.keyBackupInfo ||
|
||||
!this.keyBackupFetchedAt ||
|
||||
this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL
|
||||
) {
|
||||
this.keyBackupInfo = await this.client.getKeyBackupVersion();
|
||||
this.keyBackupInfo = await crypto.getKeyBackupInfo();
|
||||
this.keyBackupFetchedAt = now;
|
||||
}
|
||||
return this.keyBackupInfo;
|
||||
|
|
|
@ -175,13 +175,6 @@ export interface IConfigOptions {
|
|||
sync_timeline_limit?: number;
|
||||
dangerously_allow_unsafe_and_insecure_passwords?: boolean; // developer option
|
||||
|
||||
voice_broadcast?: {
|
||||
// length per voice chunk in seconds
|
||||
chunk_length?: number;
|
||||
// max voice broadcast length in seconds
|
||||
max_length?: number;
|
||||
};
|
||||
|
||||
user_notice?: {
|
||||
title: string;
|
||||
description: string;
|
||||
|
|
|
@ -55,8 +55,6 @@ import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogP
|
|||
import { findDMForUser } from "./utils/dm/findDMForUser";
|
||||
import { getJoinedNonFunctionalMembers } from "./utils/room/getJoinedNonFunctionalMembers";
|
||||
import { localNotificationsAreSilenced } from "./utils/notifications";
|
||||
import { SdkContextClass } from "./contexts/SDKContext";
|
||||
import { showCantStartACallDialog } from "./voice-broadcast/utils/showCantStartACallDialog";
|
||||
import { isNotNull } from "./Typeguards";
|
||||
import { BackgroundAudio } from "./audio/BackgroundAudio";
|
||||
import { Jitsi } from "./widgets/Jitsi.ts";
|
||||
|
@ -859,15 +857,6 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||
return;
|
||||
}
|
||||
|
||||
// Pause current broadcast, if any
|
||||
SdkContextClass.instance.voiceBroadcastPlaybacksStore.getCurrent()?.pause();
|
||||
|
||||
if (SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent()) {
|
||||
// Do not start a call, if recording a broadcast
|
||||
showCantStartACallDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
// We might be using managed hybrid widgets
|
||||
if (isManagedHybridWidgetEnabled(room)) {
|
||||
await addManagedHybridWidget(room);
|
||||
|
|
|
@ -35,13 +35,11 @@ import IdentityAuthClient from "./IdentityAuthClient";
|
|||
import { crossSigningCallbacks } from "./SecurityManager";
|
||||
import { SlidingSyncManager } from "./SlidingSyncManager";
|
||||
import { _t, UserFriendlyError } from "./languageHandler";
|
||||
import { SettingLevel } from "./settings/SettingLevel";
|
||||
import MatrixClientBackedController from "./settings/controllers/MatrixClientBackedController";
|
||||
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||
import PlatformPeg from "./PlatformPeg";
|
||||
import { formatList } from "./utils/FormattingUtils";
|
||||
import SdkConfig from "./SdkConfig";
|
||||
import { Features } from "./settings/Settings";
|
||||
import { setDeviceIsolationMode } from "./settings/controllers/DeviceIsolationModeController.ts";
|
||||
|
||||
export interface IMatrixClientCreds {
|
||||
|
@ -333,11 +331,6 @@ class MatrixClientPegClass implements IMatrixClientPeg {
|
|||
logger.error("Warning! Not using an encryption key for rust crypto store.");
|
||||
}
|
||||
|
||||
// Record the fact that we used the Rust crypto stack with this client. This just guards against people
|
||||
// rolling back to versions of EW that did not default to Rust crypto (which would lead to an error, since
|
||||
// we cannot migrate from Rust to Legacy crypto).
|
||||
await SettingsStore.setValue(Features.RustCrypto, null, SettingLevel.DEVICE, true);
|
||||
|
||||
await this.matrixClient.initRustCrypto({
|
||||
storageKey: rustCryptoStoreKey,
|
||||
storagePassword: rustCryptoStorePassword,
|
||||
|
|
|
@ -49,8 +49,6 @@ import { SdkContextClass } from "./contexts/SDKContext";
|
|||
import { localNotificationsAreSilenced, createLocalNotificationSettingsIfNeeded } from "./utils/notifications";
|
||||
import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
|
||||
import ToastStore from "./stores/ToastStore";
|
||||
import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoEventType } from "./voice-broadcast";
|
||||
import { getSenderName } from "./utils/event/getSenderName";
|
||||
import { stripPlainReply } from "./utils/Reply";
|
||||
import { BackgroundAudio } from "./audio/BackgroundAudio";
|
||||
|
||||
|
@ -81,17 +79,6 @@ const msgTypeHandlers: Record<string, (event: MatrixEvent) => string | null> = {
|
|||
return TextForEvent.textForLocationEvent(event)();
|
||||
},
|
||||
[MsgType.Audio]: (event: MatrixEvent): string | null => {
|
||||
if (event.getContent()?.[VoiceBroadcastChunkEventType]) {
|
||||
if (event.getContent()?.[VoiceBroadcastChunkEventType]?.sequence === 1) {
|
||||
// Show a notification for the first broadcast chunk.
|
||||
// At this point a user received something to listen to.
|
||||
return _t("notifier|io.element.voice_broadcast_chunk", { senderName: getSenderName(event) });
|
||||
}
|
||||
|
||||
// Mute other broadcast chunks
|
||||
return null;
|
||||
}
|
||||
|
||||
return TextForEvent.textForEvent(event, MatrixClientPeg.safeGet());
|
||||
},
|
||||
};
|
||||
|
@ -460,8 +447,6 @@ class NotifierClass extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents
|
|||
|
||||
// XXX: exported for tests
|
||||
public evaluateEvent(ev: MatrixEvent): void {
|
||||
// Mute notifications for broadcast info events
|
||||
if (ev.getType() === VoiceBroadcastInfoEventType) return;
|
||||
let roomId = ev.getRoomId()!;
|
||||
if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
|
||||
// Attempt to translate a virtual room to a native one
|
||||
|
|
|
@ -514,7 +514,7 @@ function getWidgets(event: MessageEvent<any>, roomId: string | null): void {
|
|||
sendResponse(event, widgetStateEvents);
|
||||
}
|
||||
|
||||
function getRoomEncState(event: MessageEvent<any>, roomId: string): void {
|
||||
async function getRoomEncState(event: MessageEvent<any>, roomId: string): Promise<void> {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
sendError(event, _t("widget|error_need_to_be_logged_in"));
|
||||
|
@ -525,7 +525,7 @@ function getRoomEncState(event: MessageEvent<any>, roomId: string): void {
|
|||
sendError(event, _t("scalar|error_room_unknown"));
|
||||
return;
|
||||
}
|
||||
const roomIsEncrypted = MatrixClientPeg.safeGet().isRoomEncrypted(roomId);
|
||||
const roomIsEncrypted = Boolean(await client.getCrypto()?.isEncryptionEnabledInRoom(roomId));
|
||||
|
||||
sendResponse(event, roomIsEncrypted);
|
||||
}
|
||||
|
|
|
@ -46,10 +46,6 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
|
|||
logo: require("../res/img/element-desktop-logo.svg").default,
|
||||
url: "https://element.io/get-started",
|
||||
},
|
||||
voice_broadcast: {
|
||||
chunk_length: 2 * 60, // two minutes
|
||||
max_length: 4 * 60 * 60, // four hours
|
||||
},
|
||||
|
||||
feedback: {
|
||||
existing_issues_url:
|
||||
|
|
|
@ -36,7 +36,6 @@ import AccessibleButton from "./components/views/elements/AccessibleButton";
|
|||
import RightPanelStore from "./stores/right-panel/RightPanelStore";
|
||||
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
|
||||
import { ElementCall } from "./models/Call";
|
||||
import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoEventType } from "./voice-broadcast";
|
||||
import { getSenderName } from "./utils/event/getSenderName";
|
||||
import PosthogTrackers from "./PosthogTrackers.ts";
|
||||
|
||||
|
@ -906,7 +905,6 @@ const stateHandlers: IHandlers = {
|
|||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
"im.vector.modular.widgets": textForWidgetEvent,
|
||||
[WIDGET_LAYOUT_EVENT_TYPE]: textForWidgetLayoutEvent,
|
||||
[VoiceBroadcastInfoEventType]: textForVoiceBroadcastStoppedEvent,
|
||||
};
|
||||
|
||||
// Add all the Mjolnir stuff to the renderer
|
||||
|
|
|
@ -279,7 +279,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
if (!forceReset) {
|
||||
try {
|
||||
this.setState({ phase: Phase.Loading });
|
||||
backupInfo = await cli.getKeyBackupVersion();
|
||||
backupInfo = await crypto.getKeyBackupInfo();
|
||||
} catch (e) {
|
||||
logger.error("Error fetching backup data from server", e);
|
||||
this.setState({ phase: Phase.LoadError });
|
||||
|
|
|
@ -36,7 +36,7 @@ interface IState {
|
|||
|
||||
export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
public declare context: React.ContextType<typeof MatrixClientContext>;
|
||||
declare public context: React.ContextType<typeof MatrixClientContext>;
|
||||
private unmounted = false;
|
||||
private dispatcherRef?: string;
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import { Layout } from "../../settings/enums/Layout";
|
|||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import Measured from "../views/elements/Measured";
|
||||
import EmptyState from "../views/right_panel/EmptyState";
|
||||
import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
|
@ -51,7 +52,7 @@ interface IState {
|
|||
*/
|
||||
class FilePanel extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public declare context: React.ContextType<typeof RoomContext>;
|
||||
declare public context: React.ContextType<typeof RoomContext>;
|
||||
|
||||
// This is used to track if a decrypted event was a live event and should be
|
||||
// added to the timeline.
|
||||
|
@ -104,7 +105,11 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
if (!this.state.timelineSet.eventIdToTimeline(ev.getId()!)) {
|
||||
this.state.timelineSet.addEventToTimeline(ev, timeline, false);
|
||||
this.state.timelineSet.addEventToTimeline(ev, timeline, {
|
||||
fromCache: false,
|
||||
addToState: false,
|
||||
toStartOfTimeline: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,12 +274,10 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
|
||||
if (this.state.timelineSet) {
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
timelineRenderingType: TimelineRenderingType.File,
|
||||
narrow: this.state.narrow,
|
||||
}}
|
||||
<ScopedRoomContextProvider
|
||||
{...this.context}
|
||||
timelineRenderingType={TimelineRenderingType.File}
|
||||
narrow={this.state.narrow}
|
||||
>
|
||||
<BaseCard
|
||||
className="mx_FilePanel"
|
||||
|
@ -298,16 +301,11 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
layout={Layout.Group}
|
||||
/>
|
||||
</BaseCard>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
timelineRenderingType: TimelineRenderingType.File,
|
||||
}}
|
||||
>
|
||||
<ScopedRoomContextProvider {...this.context} timelineRenderingType={TimelineRenderingType.File}>
|
||||
<BaseCard
|
||||
className="mx_FilePanel"
|
||||
onClose={this.props.onClose}
|
||||
|
@ -315,7 +313,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
>
|
||||
<Spinner />
|
||||
</BaseCard>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import classNames from "classnames";
|
|||
import { isOnlyCtrlOrCmdKeyEvent, Key } from "../../Keyboard";
|
||||
import PageTypes from "../../PageTypes";
|
||||
import MediaDeviceHandler from "../../MediaDeviceHandler";
|
||||
import { fixupColorFonts } from "../../utils/FontManager";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { IMatrixClientCreds } from "../../MatrixClientPeg";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
|
@ -149,8 +148,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
|
||||
MediaDeviceHandler.loadDevices();
|
||||
|
||||
fixupColorFonts();
|
||||
|
||||
this._roomView = React.createRef();
|
||||
this._resizeContainer = React.createRef();
|
||||
this.resizeHandler = React.createRef();
|
||||
|
|
|
@ -119,7 +119,6 @@ import { ValidatedServerConfig } from "../../utils/ValidatedServerConfig";
|
|||
import { isLocalRoom } from "../../utils/localRoom/isLocalRoom";
|
||||
import { SDKContext, SdkContextClass } from "../../contexts/SDKContext";
|
||||
import { viewUserDeviceSettings } from "../../actions/handlers/viewUserDeviceSettings";
|
||||
import { cleanUpBroadcasts, VoiceBroadcastResumer } from "../../voice-broadcast";
|
||||
import GenericToast from "../views/toasts/GenericToast";
|
||||
import RovingSpotlightDialog from "../views/dialogs/spotlight/SpotlightDialog";
|
||||
import { findDMForUser } from "../../utils/dm/findDMForUser";
|
||||
|
@ -227,7 +226,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
private focusNext: FocusNextType;
|
||||
private subTitleStatus: string;
|
||||
private prevWindowWidth: number;
|
||||
private voiceBroadcastResumer?: VoiceBroadcastResumer;
|
||||
|
||||
private readonly loggedInView = createRef<LoggedInViewType>();
|
||||
private dispatcherRef?: string;
|
||||
|
@ -501,7 +499,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
window.removeEventListener("resize", this.onWindowResized);
|
||||
|
||||
this.stores.accountPasswordStore.clearPassword();
|
||||
this.voiceBroadcastResumer?.destroy();
|
||||
}
|
||||
|
||||
private onWindowResized = (): void => {
|
||||
|
@ -651,10 +648,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
break;
|
||||
case "logout":
|
||||
LegacyCallHandler.instance.hangupAllCalls();
|
||||
Promise.all([
|
||||
...[...CallStore.instance.connectedCalls].map((call) => call.disconnect()),
|
||||
cleanUpBroadcasts(this.stores),
|
||||
]).finally(() => Lifecycle.logout(this.stores.oidcClientStore));
|
||||
Promise.all([...[...CallStore.instance.connectedCalls].map((call) => call.disconnect())]).finally(() =>
|
||||
Lifecycle.logout(this.stores.oidcClientStore),
|
||||
);
|
||||
break;
|
||||
case "require_registration":
|
||||
startAnyRegistrationFlow(payload as any);
|
||||
|
@ -1638,7 +1634,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
} else {
|
||||
// otherwise check the server to see if there's a new one
|
||||
try {
|
||||
newVersionInfo = await cli.getKeyBackupVersion();
|
||||
newVersionInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
|
||||
if (newVersionInfo !== null) haveNewVersion = true;
|
||||
} catch (e) {
|
||||
logger.error("Saw key backup error but failed to check backup version!", e);
|
||||
|
@ -1679,8 +1675,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.voiceBroadcastResumer = new VoiceBroadcastResumer(cli);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -196,7 +196,7 @@ interface IReadReceiptForUser {
|
|||
*/
|
||||
export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public declare context: React.ContextType<typeof RoomContext>;
|
||||
declare public context: React.ContextType<typeof RoomContext>;
|
||||
|
||||
public static defaultProps = {
|
||||
disableGrouping: false,
|
||||
|
|
|
@ -19,6 +19,7 @@ import { Layout } from "../../settings/enums/Layout";
|
|||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import Measured from "../views/elements/Measured";
|
||||
import EmptyState from "../views/right_panel/EmptyState";
|
||||
import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";
|
||||
|
||||
interface IProps {
|
||||
onClose(): void;
|
||||
|
@ -33,7 +34,7 @@ interface IState {
|
|||
*/
|
||||
export default class NotificationPanel extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public declare context: React.ContextType<typeof RoomContext>;
|
||||
declare public context: React.ContextType<typeof RoomContext>;
|
||||
|
||||
private card = React.createRef<HTMLDivElement>();
|
||||
|
||||
|
@ -79,12 +80,10 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
|||
}
|
||||
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
timelineRenderingType: TimelineRenderingType.Notification,
|
||||
narrow: this.state.narrow,
|
||||
}}
|
||||
<ScopedRoomContextProvider
|
||||
{...this.context}
|
||||
timelineRenderingType={TimelineRenderingType.Notification}
|
||||
narrow={this.state.narrow}
|
||||
>
|
||||
<BaseCard
|
||||
header={_t("notifications|enable_prompt_toast_title")}
|
||||
|
@ -99,7 +98,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
|||
{this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />}
|
||||
{content}
|
||||
</BaseCard>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { MutableRefObject, ReactNode, useContext, useRef } from "react";
|
||||
import React, { MutableRefObject, ReactNode, useRef } from "react";
|
||||
import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
@ -21,19 +21,7 @@ import { WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
|||
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../stores/ActiveWidgetStore";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import { SDKContext, SdkContextClass } from "../../contexts/SDKContext";
|
||||
import {
|
||||
useCurrentVoiceBroadcastPreRecording,
|
||||
useCurrentVoiceBroadcastRecording,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybackBody,
|
||||
VoiceBroadcastPreRecording,
|
||||
VoiceBroadcastPreRecordingPip,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingPip,
|
||||
VoiceBroadcastSmallPlaybackBody,
|
||||
} from "../../voice-broadcast";
|
||||
import { useCurrentVoiceBroadcastPlayback } from "../../voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
import { WidgetPip } from "../views/pips/WidgetPip";
|
||||
|
||||
const SHOW_CALL_IN_STATES = [
|
||||
|
@ -46,9 +34,6 @@ const SHOW_CALL_IN_STATES = [
|
|||
];
|
||||
|
||||
interface IProps {
|
||||
voiceBroadcastRecording: Optional<VoiceBroadcastRecording>;
|
||||
voiceBroadcastPreRecording: Optional<VoiceBroadcastPreRecording>;
|
||||
voiceBroadcastPlayback: Optional<VoiceBroadcastPlayback>;
|
||||
movePersistedElement: MutableRefObject<(() => void) | undefined>;
|
||||
}
|
||||
|
||||
|
@ -245,52 +230,9 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
|||
this.setState({ showWidgetInPip, persistentWidgetId, persistentRoomId });
|
||||
}
|
||||
|
||||
private createVoiceBroadcastPlaybackPipContent(voiceBroadcastPlayback: VoiceBroadcastPlayback): CreatePipChildren {
|
||||
const content =
|
||||
this.state.viewedRoomId === voiceBroadcastPlayback.infoEvent.getRoomId() ? (
|
||||
<VoiceBroadcastPlaybackBody playback={voiceBroadcastPlayback} pip={true} />
|
||||
) : (
|
||||
<VoiceBroadcastSmallPlaybackBody playback={voiceBroadcastPlayback} />
|
||||
);
|
||||
|
||||
return ({ onStartMoving }) => (
|
||||
<div key={`vb-playback-${voiceBroadcastPlayback.infoEvent.getId()}`} onMouseDown={onStartMoving}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private createVoiceBroadcastPreRecordingPipContent(
|
||||
voiceBroadcastPreRecording: VoiceBroadcastPreRecording,
|
||||
): CreatePipChildren {
|
||||
return ({ onStartMoving }) => (
|
||||
<div key="vb-pre-recording" onMouseDown={onStartMoving}>
|
||||
<VoiceBroadcastPreRecordingPip voiceBroadcastPreRecording={voiceBroadcastPreRecording} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private createVoiceBroadcastRecordingPipContent(
|
||||
voiceBroadcastRecording: VoiceBroadcastRecording,
|
||||
): CreatePipChildren {
|
||||
return ({ onStartMoving }) => (
|
||||
<div key={`vb-recording-${voiceBroadcastRecording.infoEvent.getId()}`} onMouseDown={onStartMoving}>
|
||||
<VoiceBroadcastRecordingPip recording={voiceBroadcastRecording} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
const pipMode = true;
|
||||
let pipContent: Array<CreatePipChildren> = [];
|
||||
|
||||
if (this.props.voiceBroadcastRecording) {
|
||||
pipContent = [this.createVoiceBroadcastRecordingPipContent(this.props.voiceBroadcastRecording)];
|
||||
} else if (this.props.voiceBroadcastPreRecording) {
|
||||
pipContent = [this.createVoiceBroadcastPreRecordingPipContent(this.props.voiceBroadcastPreRecording)];
|
||||
} else if (this.props.voiceBroadcastPlayback) {
|
||||
pipContent = [this.createVoiceBroadcastPlaybackPipContent(this.props.voiceBroadcastPlayback)];
|
||||
}
|
||||
const pipContent: Array<CreatePipChildren> = [];
|
||||
|
||||
if (this.state.primaryCall) {
|
||||
// get a ref to call inside the current scope
|
||||
|
@ -338,24 +280,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
export const PipContainer: React.FC = () => {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const voiceBroadcastPreRecordingStore = sdkContext.voiceBroadcastPreRecordingStore;
|
||||
const { currentVoiceBroadcastPreRecording } = useCurrentVoiceBroadcastPreRecording(voiceBroadcastPreRecordingStore);
|
||||
|
||||
const voiceBroadcastRecordingsStore = sdkContext.voiceBroadcastRecordingsStore;
|
||||
const { currentVoiceBroadcastRecording } = useCurrentVoiceBroadcastRecording(voiceBroadcastRecordingsStore);
|
||||
|
||||
const voiceBroadcastPlaybacksStore = sdkContext.voiceBroadcastPlaybacksStore;
|
||||
const { currentVoiceBroadcastPlayback } = useCurrentVoiceBroadcastPlayback(voiceBroadcastPlaybacksStore);
|
||||
|
||||
const movePersistedElement = useRef<() => void>();
|
||||
|
||||
return (
|
||||
<PipContainerInner
|
||||
voiceBroadcastPlayback={currentVoiceBroadcastPlayback}
|
||||
voiceBroadcastPreRecording={currentVoiceBroadcastPreRecording}
|
||||
voiceBroadcastRecording={currentVoiceBroadcastRecording}
|
||||
movePersistedElement={movePersistedElement}
|
||||
/>
|
||||
);
|
||||
return <PipContainerInner movePersistedElement={movePersistedElement} />;
|
||||
};
|
||||
|
|
|
@ -63,7 +63,7 @@ interface IState {
|
|||
|
||||
export default class RightPanel extends React.Component<Props, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
public declare context: React.ContextType<typeof MatrixClientContext>;
|
||||
declare public context: React.ContextType<typeof MatrixClientContext>;
|
||||
|
||||
public constructor(props: Props, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
super(props, context);
|
||||
|
@ -109,10 +109,10 @@ export default class RightPanel extends React.Component<Props, IState> {
|
|||
}
|
||||
|
||||
// redraw the badge on the membership list
|
||||
if (this.state.phase === RightPanelPhases.RoomMemberList) {
|
||||
if (this.state.phase === RightPanelPhases.MemberList) {
|
||||
this.delayedUpdate();
|
||||
} else if (
|
||||
this.state.phase === RightPanelPhases.RoomMemberInfo &&
|
||||
this.state.phase === RightPanelPhases.MemberInfo &&
|
||||
member.userId === this.state.cardState?.member?.userId
|
||||
) {
|
||||
// refresh the member info (e.g. new power level)
|
||||
|
@ -157,7 +157,7 @@ export default class RightPanel extends React.Component<Props, IState> {
|
|||
const phase = this.props.overwriteCard?.phase ?? this.state.phase;
|
||||
const cardState = this.props.overwriteCard?.state ?? this.state.cardState;
|
||||
switch (phase) {
|
||||
case RightPanelPhases.RoomMemberList:
|
||||
case RightPanelPhases.MemberList:
|
||||
if (!!roomId) {
|
||||
card = (
|
||||
<MemberList
|
||||
|
@ -170,22 +170,8 @@ export default class RightPanel extends React.Component<Props, IState> {
|
|||
);
|
||||
}
|
||||
break;
|
||||
case RightPanelPhases.SpaceMemberList:
|
||||
if (!!cardState?.spaceId || !!roomId) {
|
||||
card = (
|
||||
<MemberList
|
||||
roomId={cardState?.spaceId ?? roomId!}
|
||||
key={cardState?.spaceId ?? roomId!}
|
||||
onClose={this.onClose}
|
||||
searchQuery={this.state.searchQuery}
|
||||
onSearchQueryChanged={this.onSearchQueryChanged}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case RightPanelPhases.RoomMemberInfo:
|
||||
case RightPanelPhases.SpaceMemberInfo:
|
||||
case RightPanelPhases.MemberInfo:
|
||||
case RightPanelPhases.EncryptionPanel: {
|
||||
if (!!cardState?.member) {
|
||||
const roomMember = cardState.member instanceof RoomMember ? cardState.member : undefined;
|
||||
|
@ -203,8 +189,7 @@ export default class RightPanel extends React.Component<Props, IState> {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case RightPanelPhases.Room3pidMemberInfo:
|
||||
case RightPanelPhases.Space3pidMemberInfo:
|
||||
case RightPanelPhases.ThreePidMemberInfo:
|
||||
if (!!cardState?.memberInfoEvent) {
|
||||
card = (
|
||||
<ThirdPartyMemberInfo event={cardState.memberInfoEvent} key={roomId} onClose={this.onClose} />
|
||||
|
|
|
@ -26,7 +26,7 @@ import ErrorDialog from "../views/dialogs/ErrorDialog";
|
|||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
|
||||
import RoomContext from "../../contexts/RoomContext";
|
||||
import { useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function (msg: string): void {};
|
||||
|
@ -53,7 +53,7 @@ interface Props {
|
|||
export const RoomSearchView = forwardRef<ScrollPanel, Props>(
|
||||
({ term, scope, promise, abortController, resizeNotifier, className, onUpdate, inProgress }: Props, ref) => {
|
||||
const client = useContext(MatrixClientContext);
|
||||
const roomContext = useContext(RoomContext);
|
||||
const roomContext = useScopedRoomContext("showHiddenEvents");
|
||||
const [highlights, setHighlights] = useState<string[] | null>(null);
|
||||
const [results, setResults] = useState<ISearchResults | null>(null);
|
||||
const aborted = useRef(false);
|
||||
|
|
|
@ -89,7 +89,7 @@ interface IState {
|
|||
export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
||||
private unmounted = false;
|
||||
public static contextType = MatrixClientContext;
|
||||
public declare context: React.ContextType<typeof MatrixClientContext>;
|
||||
declare public context: React.ContextType<typeof MatrixClientContext>;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
super(props, context);
|
||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent, ComponentProps, createRef, ReactElement, ReactNode, RefObject, useContext } from "react";
|
||||
import React, { ChangeEvent, ComponentProps, createRef, ReactElement, ReactNode, RefObject, JSX } from "react";
|
||||
import classNames from "classnames";
|
||||
import {
|
||||
IRecommendedVersion,
|
||||
|
@ -29,6 +29,7 @@ import {
|
|||
MatrixError,
|
||||
ISearchResults,
|
||||
THREAD_RELATION_TYPE,
|
||||
MatrixClient,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
@ -54,7 +55,7 @@ import WidgetEchoStore from "../../stores/WidgetEchoStore";
|
|||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import RoomContext, { TimelineRenderingType, MainSplitContentType } from "../../contexts/RoomContext";
|
||||
import { TimelineRenderingType, MainSplitContentType } from "../../contexts/RoomContext";
|
||||
import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { IMatrixClientCreds } from "../../MatrixClientPeg";
|
||||
|
@ -126,6 +127,7 @@ import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
|||
import { onView3pidInvite } from "../../stores/right-panel/action-handlers";
|
||||
import RoomSearchAuxPanel from "../views/rooms/RoomSearchAuxPanel";
|
||||
import { PinnedMessageBanner } from "../views/rooms/PinnedMessageBanner";
|
||||
import { ScopedRoomContextProvider, useScopedRoomContext } from "../../contexts/ScopedRoomContext";
|
||||
|
||||
const DEBUG = false;
|
||||
const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000;
|
||||
|
@ -233,6 +235,11 @@ export interface IRoomState {
|
|||
liveTimeline?: EventTimeline;
|
||||
narrow: boolean;
|
||||
msc3946ProcessDynamicPredecessor: boolean;
|
||||
/**
|
||||
* Whether the room is encrypted or not.
|
||||
* If null, we are still determining the encryption status.
|
||||
*/
|
||||
isRoomEncrypted: boolean | null;
|
||||
|
||||
canAskToJoin: boolean;
|
||||
promptAskToJoin: boolean;
|
||||
|
@ -246,6 +253,7 @@ interface LocalRoomViewProps {
|
|||
permalinkCreator: RoomPermalinkCreator;
|
||||
roomView: RefObject<HTMLElement>;
|
||||
onFileDrop: (dataTransfer: DataTransfer) => Promise<void>;
|
||||
mainSplitContentType: MainSplitContentType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -255,7 +263,7 @@ interface LocalRoomViewProps {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
function LocalRoomView(props: LocalRoomViewProps): ReactElement {
|
||||
const context = useContext(RoomContext);
|
||||
const context = useScopedRoomContext("room");
|
||||
const room = context.room as LocalRoom;
|
||||
const encryptionEvent = props.localRoom.currentState.getStateEvents(EventType.RoomEncryption)[0];
|
||||
let encryptionTile: ReactNode;
|
||||
|
@ -323,6 +331,7 @@ interface ILocalRoomCreateLoaderProps {
|
|||
localRoom: LocalRoom;
|
||||
names: string;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
mainSplitContentType: MainSplitContentType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -363,7 +372,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
private roomViewBody = createRef<HTMLDivElement>();
|
||||
|
||||
public static contextType = SDKContext;
|
||||
public declare context: React.ContextType<typeof SDKContext>;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
public constructor(props: IRoomProps, context: React.ContextType<typeof SDKContext>) {
|
||||
super(props, context);
|
||||
|
@ -417,6 +426,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
canAskToJoin: this.askToJoinEnabled,
|
||||
promptAskToJoin: false,
|
||||
viewRoomOpts: { buttons: [] },
|
||||
isRoomEncrypted: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -655,6 +665,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
// the RoomView instance
|
||||
if (initial) {
|
||||
newState.room = this.context.client!.getRoom(newState.roomId) || undefined;
|
||||
newState.isRoomEncrypted = null;
|
||||
if (newState.room) {
|
||||
newState.showApps = this.shouldShowApps(newState.room);
|
||||
this.onRoomLoaded(newState.room);
|
||||
|
@ -697,6 +708,14 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
if (initial) {
|
||||
this.setupRoom(newState.room, newState.roomId, !!newState.joining, !!newState.shouldPeek);
|
||||
}
|
||||
|
||||
// We don't block the initial setup but we want to make it early to not block the timeline rendering
|
||||
const isRoomEncrypted = await this.getIsRoomEncrypted(newState.roomId);
|
||||
this.setState({
|
||||
isRoomEncrypted,
|
||||
...(isRoomEncrypted &&
|
||||
newState.roomId && { e2eStatus: RoomView.e2eStatusCache.get(newState.roomId) ?? E2EStatus.Warning }),
|
||||
});
|
||||
};
|
||||
|
||||
private onConnectedCalls = (): void => {
|
||||
|
@ -1214,18 +1233,18 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
if (payload.member) {
|
||||
if (payload.push) {
|
||||
RightPanelStore.instance.pushCard({
|
||||
phase: RightPanelPhases.RoomMemberInfo,
|
||||
phase: RightPanelPhases.MemberInfo,
|
||||
state: { member: payload.member },
|
||||
});
|
||||
} else {
|
||||
RightPanelStore.instance.setCards([
|
||||
{ phase: RightPanelPhases.RoomSummary },
|
||||
{ phase: RightPanelPhases.RoomMemberList },
|
||||
{ phase: RightPanelPhases.RoomMemberInfo, state: { member: payload.member } },
|
||||
{ phase: RightPanelPhases.MemberList },
|
||||
{ phase: RightPanelPhases.MemberInfo, state: { member: payload.member } },
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomMemberList);
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.MemberList);
|
||||
}
|
||||
break;
|
||||
case Action.View3pidInvite:
|
||||
|
@ -1342,13 +1361,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
this.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange);
|
||||
|
||||
this.calculatePeekRules(room);
|
||||
this.updatePreviewUrlVisibility(room);
|
||||
this.loadMembersIfJoined(room);
|
||||
this.calculateRecommendedVersion(room);
|
||||
this.updateE2EStatus(room);
|
||||
this.updatePermissions(room);
|
||||
this.checkWidgets(room);
|
||||
this.loadVirtualRoom(room);
|
||||
this.updateRoomEncrypted(room);
|
||||
|
||||
if (
|
||||
this.getMainSplitContentType(room) !== MainSplitContentType.Timeline &&
|
||||
|
@ -1377,6 +1395,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
return room?.currentState.getStateEvents(EventType.RoomTombstone, "") ?? undefined;
|
||||
}
|
||||
|
||||
private async getIsRoomEncrypted(roomId = this.state.roomId): Promise<boolean> {
|
||||
const crypto = this.context.client?.getCrypto();
|
||||
if (!crypto || !roomId) return false;
|
||||
|
||||
return await crypto.isEncryptionEnabledInRoom(roomId);
|
||||
}
|
||||
|
||||
private async calculateRecommendedVersion(room: Room): Promise<void> {
|
||||
const upgradeRecommendation = await room.getRecommendedVersion();
|
||||
if (this.unmounted) return;
|
||||
|
@ -1409,12 +1434,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
});
|
||||
}
|
||||
|
||||
private updatePreviewUrlVisibility({ roomId }: Room): void {
|
||||
// URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
|
||||
const key = this.context.client?.isRoomEncrypted(roomId) ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled";
|
||||
this.setState({
|
||||
showUrlPreview: SettingsStore.getValue(key, roomId),
|
||||
});
|
||||
private updatePreviewUrlVisibility(room: Room): void {
|
||||
this.setState(({ isRoomEncrypted }) => ({
|
||||
showUrlPreview: this.getPreviewUrlVisibility(room, isRoomEncrypted),
|
||||
}));
|
||||
}
|
||||
|
||||
private getPreviewUrlVisibility({ roomId }: Room, isRoomEncrypted: boolean | null): boolean {
|
||||
const key = isRoomEncrypted ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled";
|
||||
return SettingsStore.getValue(key, roomId);
|
||||
}
|
||||
|
||||
private onRoom = (room: Room): void => {
|
||||
|
@ -1456,22 +1484,20 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
};
|
||||
|
||||
private async updateE2EStatus(room: Room): Promise<void> {
|
||||
if (!this.context.client?.isRoomEncrypted(room.roomId)) return;
|
||||
|
||||
// If crypto is not currently enabled, we aren't tracking devices at all,
|
||||
// so we don't know what the answer is. Let's error on the safe side and show
|
||||
// a warning for this case.
|
||||
let e2eStatus = RoomView.e2eStatusCache.get(room.roomId) ?? E2EStatus.Warning;
|
||||
// set the state immediately then update, so we don't scare the user into thinking the room is unencrypted
|
||||
this.setState({ e2eStatus });
|
||||
|
||||
if (this.context.client.getCrypto()) {
|
||||
/* At this point, the user has encryption on and cross-signing on */
|
||||
e2eStatus = await shieldStatusForRoom(this.context.client, room);
|
||||
RoomView.e2eStatusCache.set(room.roomId, e2eStatus);
|
||||
if (!this.context.client || !this.state.isRoomEncrypted) return;
|
||||
const e2eStatus = await this.cacheAndGetE2EStatus(room, this.context.client);
|
||||
if (this.unmounted) return;
|
||||
this.setState({ e2eStatus });
|
||||
}
|
||||
|
||||
private async cacheAndGetE2EStatus(room: Room, client: MatrixClient): Promise<E2EStatus> {
|
||||
let e2eStatus = RoomView.e2eStatusCache.get(room.roomId);
|
||||
// set the state immediately then update, so we don't scare the user into thinking the room is unencrypted
|
||||
if (e2eStatus) this.setState({ e2eStatus });
|
||||
|
||||
e2eStatus = await shieldStatusForRoom(client, room);
|
||||
RoomView.e2eStatusCache.set(room.roomId, e2eStatus);
|
||||
return e2eStatus;
|
||||
}
|
||||
|
||||
private onUrlPreviewsEnabledChange = (): void => {
|
||||
|
@ -1480,20 +1506,36 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
}
|
||||
};
|
||||
|
||||
private onRoomStateEvents = (ev: MatrixEvent, state: RoomState): void => {
|
||||
private onRoomStateEvents = async (ev: MatrixEvent, state: RoomState): Promise<void> => {
|
||||
// ignore if we don't have a room yet
|
||||
if (!this.state.room || this.state.room.roomId !== state.roomId) return;
|
||||
if (!this.state.room || this.state.room.roomId !== state.roomId || !this.context.client) return;
|
||||
|
||||
switch (ev.getType()) {
|
||||
case EventType.RoomTombstone:
|
||||
this.setState({ tombstone: this.getRoomTombstone() });
|
||||
break;
|
||||
|
||||
case EventType.RoomEncryption: {
|
||||
await this.updateRoomEncrypted();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this.updatePermissions(this.state.room);
|
||||
}
|
||||
};
|
||||
|
||||
private async updateRoomEncrypted(room = this.state.room): Promise<void> {
|
||||
if (!room || !this.context.client) return;
|
||||
|
||||
const isRoomEncrypted = await this.getIsRoomEncrypted(room.roomId);
|
||||
const newE2EStatus = isRoomEncrypted ? await this.cacheAndGetE2EStatus(room, this.context.client) : null;
|
||||
|
||||
this.setState({
|
||||
isRoomEncrypted,
|
||||
showUrlPreview: this.getPreviewUrlVisibility(room, isRoomEncrypted),
|
||||
...(newE2EStatus && { e2eStatus: newE2EStatus }),
|
||||
});
|
||||
}
|
||||
|
||||
private onRoomStateUpdate = (state: RoomState): void => {
|
||||
// ignore members in other rooms
|
||||
if (state.roomId !== this.state.room?.roomId) {
|
||||
|
@ -1959,35 +2001,41 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
if (!this.state.room || !this.context?.client) return null;
|
||||
const names = this.state.room.getDefaultRoomName(this.context.client.getSafeUserId());
|
||||
return (
|
||||
<RoomContext.Provider value={this.state}>
|
||||
<LocalRoomCreateLoader localRoom={localRoom} names={names} resizeNotifier={this.props.resizeNotifier} />
|
||||
</RoomContext.Provider>
|
||||
<ScopedRoomContextProvider {...this.state}>
|
||||
<LocalRoomCreateLoader
|
||||
localRoom={localRoom}
|
||||
names={names}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
mainSplitContentType={this.state.mainSplitContentType}
|
||||
/>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLocalRoomView(localRoom: LocalRoom): ReactNode {
|
||||
return (
|
||||
<RoomContext.Provider value={this.state}>
|
||||
<ScopedRoomContextProvider {...this.state}>
|
||||
<LocalRoomView
|
||||
localRoom={localRoom}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
permalinkCreator={this.permalinkCreator}
|
||||
roomView={this.roomView}
|
||||
onFileDrop={this.onFileDrop}
|
||||
mainSplitContentType={this.state.mainSplitContentType}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
private renderWaitingForThirdPartyRoomView(inviteEvent: MatrixEvent): ReactNode {
|
||||
return (
|
||||
<RoomContext.Provider value={this.state}>
|
||||
<ScopedRoomContextProvider {...this.state}>
|
||||
<WaitingForThirdPartyRoomView
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
roomView={this.roomView}
|
||||
inviteEvent={inviteEvent}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2027,6 +2075,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
public render(): ReactNode {
|
||||
if (!this.context.client) return null;
|
||||
const { isRoomEncrypted } = this.state;
|
||||
const isRoomEncryptionLoading = isRoomEncrypted === null;
|
||||
|
||||
if (this.state.room instanceof LocalRoom) {
|
||||
if (this.state.room.state === LocalRoomState.CREATING) {
|
||||
|
@ -2242,14 +2292,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
let aux: JSX.Element | undefined;
|
||||
let previewBar;
|
||||
if (this.state.timelineRenderingType === TimelineRenderingType.Search) {
|
||||
if (!isRoomEncryptionLoading) {
|
||||
aux = (
|
||||
<RoomSearchAuxPanel
|
||||
searchInfo={this.state.search}
|
||||
onCancelClick={this.onCancelSearchClick}
|
||||
onSearchScopeChange={this.onSearchScopeChange}
|
||||
isRoomEncrypted={this.context.client.isRoomEncrypted(this.state.room.roomId)}
|
||||
isRoomEncrypted={isRoomEncrypted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (showRoomUpgradeBar) {
|
||||
aux = <RoomUpgradeWarningBar room={this.state.room} />;
|
||||
} else if (myMembership !== KnownMembership.Join) {
|
||||
|
@ -2325,8 +2377,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
let messageComposer;
|
||||
const showComposer =
|
||||
!isRoomEncryptionLoading &&
|
||||
// joined and not showing search results
|
||||
myMembership === KnownMembership.Join && !this.state.search;
|
||||
myMembership === KnownMembership.Join &&
|
||||
!this.state.search;
|
||||
if (showComposer) {
|
||||
messageComposer = (
|
||||
<MessageComposer
|
||||
|
@ -2367,7 +2421,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
highlightedEventId = this.state.initialEventId;
|
||||
}
|
||||
|
||||
const messagePanel = (
|
||||
let messagePanel: JSX.Element | undefined;
|
||||
if (!isRoomEncryptionLoading) {
|
||||
messagePanel = (
|
||||
<TimelinePanel
|
||||
ref={this.gatherTimelinePanelRef}
|
||||
timelineSet={this.state.room.getUnfilteredTimelineSet()}
|
||||
|
@ -2395,6 +2451,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
editState={this.state.editState}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let topUnreadMessagesBar: JSX.Element | undefined;
|
||||
// Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense
|
||||
|
@ -2415,7 +2472,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
);
|
||||
}
|
||||
|
||||
const showRightPanel = this.state.room && this.state.showRightPanel;
|
||||
const showRightPanel = !isRoomEncryptionLoading && this.state.room && this.state.showRightPanel;
|
||||
|
||||
const rightPanel = showRightPanel ? (
|
||||
<RightPanel
|
||||
|
@ -2516,7 +2573,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
}
|
||||
|
||||
return (
|
||||
<RoomContext.Provider value={this.state}>
|
||||
<ScopedRoomContextProvider {...this.state}>
|
||||
<div className={mainClasses} ref={this.roomView} onKeyDown={this.onReactKeyDown}>
|
||||
{showChatEffects && this.roomView.current && (
|
||||
<EffectsOverlay roomWidth={this.roomView.current.offsetWidth} />
|
||||
|
@ -2543,7 +2600,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
</MainSplit>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,7 +208,7 @@ const SpaceLanding: React.FC<{ space: Room }> = ({ space }) => {
|
|||
const storeIsShowingSpaceMembers = useCallback(
|
||||
() =>
|
||||
RightPanelStore.instance.isOpenForRoom(space.roomId) &&
|
||||
RightPanelStore.instance.currentCardForRoom(space.roomId)?.phase === RightPanelPhases.SpaceMemberList,
|
||||
RightPanelStore.instance.currentCardForRoom(space.roomId)?.phase === RightPanelPhases.MemberList,
|
||||
[space.roomId],
|
||||
);
|
||||
const isShowingMembers = useEventEmitterState(RightPanelStore.instance, UPDATE_EVENT, storeIsShowingSpaceMembers);
|
||||
|
@ -251,7 +251,7 @@ const SpaceLanding: React.FC<{ space: Room }> = ({ space }) => {
|
|||
}
|
||||
|
||||
const onMembersClick = (): void => {
|
||||
RightPanelStore.instance.setCard({ phase: RightPanelPhases.SpaceMemberList });
|
||||
RightPanelStore.instance.setCard({ phase: RightPanelPhases.MemberList });
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -597,7 +597,7 @@ const SpaceSetupPrivateInvite: React.FC<{
|
|||
|
||||
export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
public declare context: React.ContextType<typeof MatrixClientContext>;
|
||||
declare public context: React.ContextType<typeof MatrixClientContext>;
|
||||
|
||||
private dispatcherRef?: string;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import MatrixClientContext, { useMatrixClientContext } from "../../contexts/Matr
|
|||
import { _t } from "../../languageHandler";
|
||||
import { ContextMenuButton } from "../../accessibility/context_menu/ContextMenuButton";
|
||||
import ContextMenu, { ChevronFace, MenuItemRadio, useContextMenu } from "./ContextMenu";
|
||||
import RoomContext, { TimelineRenderingType, useRoomContext } from "../../contexts/RoomContext";
|
||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import TimelinePanel from "./TimelinePanel";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
|
||||
|
@ -30,6 +30,7 @@ import { ButtonEvent } from "../views/elements/AccessibleButton";
|
|||
import Spinner from "../views/elements/Spinner";
|
||||
import { clearRoomNotification } from "../../utils/notifications";
|
||||
import EmptyState from "../views/right_panel/EmptyState";
|
||||
import { ScopedRoomContextProvider, useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
|
@ -68,7 +69,7 @@ export const ThreadPanelHeader: React.FC<{
|
|||
setFilterOption: (filterOption: ThreadFilterType) => void;
|
||||
}> = ({ filterOption, setFilterOption }) => {
|
||||
const mxClient = useMatrixClientContext();
|
||||
const roomContext = useRoomContext();
|
||||
const roomContext = useScopedRoomContext("room");
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>();
|
||||
const options: readonly ThreadPanelHeaderOption[] = [
|
||||
{
|
||||
|
@ -184,13 +185,11 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
}, [timelineSet, timelinePanel]);
|
||||
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
...roomContext,
|
||||
timelineRenderingType: TimelineRenderingType.ThreadsList,
|
||||
showHiddenEvents: true,
|
||||
narrow,
|
||||
}}
|
||||
<ScopedRoomContextProvider
|
||||
{...roomContext}
|
||||
timelineRenderingType={TimelineRenderingType.ThreadsList}
|
||||
showHiddenEvents={true}
|
||||
narrow={narrow}
|
||||
>
|
||||
<BaseCard
|
||||
header={
|
||||
|
@ -241,7 +240,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
</div>
|
||||
)}
|
||||
</BaseCard>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
};
|
||||
export default ThreadPanel;
|
||||
|
|
|
@ -51,6 +51,7 @@ import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/C
|
|||
import Heading from "../views/typography/Heading";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
import { ThreadPayload } from "../../dispatcher/payloads/ThreadPayload";
|
||||
import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -75,7 +76,7 @@ interface IState {
|
|||
|
||||
export default class ThreadView extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public declare context: React.ContextType<typeof RoomContext>;
|
||||
declare public context: React.ContextType<typeof RoomContext>;
|
||||
|
||||
private dispatcherRef?: string;
|
||||
private layoutWatcherRef?: string;
|
||||
|
@ -422,14 +423,12 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
timelineRenderingType: TimelineRenderingType.Thread,
|
||||
threadId: this.state.thread?.id,
|
||||
liveTimeline: this.state?.thread?.timelineSet?.getLiveTimeline(),
|
||||
narrow: this.state.narrow,
|
||||
}}
|
||||
<ScopedRoomContextProvider
|
||||
{...this.context}
|
||||
timelineRenderingType={TimelineRenderingType.Thread}
|
||||
threadId={this.state.thread?.id}
|
||||
liveTimeline={this.state?.thread?.timelineSet?.getLiveTimeline()}
|
||||
narrow={this.state.narrow}
|
||||
>
|
||||
<BaseCard
|
||||
className={classNames("mx_ThreadView mx_ThreadPanel", {
|
||||
|
@ -463,7 +462,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
/>
|
||||
)}
|
||||
</BaseCard>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,7 +229,7 @@ interface IEventIndexOpts {
|
|||
*/
|
||||
class TimelinePanel extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public declare context: React.ContextType<typeof RoomContext>;
|
||||
declare public context: React.ContextType<typeof RoomContext>;
|
||||
|
||||
// a map from room id to read marker event timestamp
|
||||
public static roomReadMarkerTsMap: Record<string, number> = {};
|
||||
|
|
|
@ -40,8 +40,6 @@ import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
|
|||
import UserIdentifierCustomisations from "../../customisations/UserIdentifier";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
|
||||
import { Icon as LiveIcon } from "../../../res/img/compound/live-8px.svg";
|
||||
import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStoreEvent } from "../../voice-broadcast";
|
||||
import { SDKContext } from "../../contexts/SDKContext";
|
||||
import { shouldShowFeedback } from "../../utils/Feedback";
|
||||
import DarkLightModeSvg from "../../../res/img/element-icons/roomlist/dark-light-mode.svg";
|
||||
|
@ -58,7 +56,6 @@ interface IState {
|
|||
isDarkTheme: boolean;
|
||||
isHighContrast: boolean;
|
||||
selectedSpace?: Room | null;
|
||||
showLiveAvatarAddon: boolean;
|
||||
}
|
||||
|
||||
const toRightOf = (rect: PartialDOMRect): MenuProps => {
|
||||
|
@ -79,7 +76,7 @@ const below = (rect: PartialDOMRect): MenuProps => {
|
|||
|
||||
export default class UserMenu extends React.Component<IProps, IState> {
|
||||
public static contextType = SDKContext;
|
||||
public declare context: React.ContextType<typeof SDKContext>;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
private dispatcherRef?: string;
|
||||
private themeWatcherRef?: string;
|
||||
|
@ -94,7 +91,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
isDarkTheme: this.isUserOnDarkTheme(),
|
||||
isHighContrast: this.isUserOnHighContrastTheme(),
|
||||
selectedSpace: SpaceStore.instance.activeSpaceRoom,
|
||||
showLiveAvatarAddon: this.context.voiceBroadcastRecordingsStore.hasCurrent(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -102,19 +98,9 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
return !!getHomePageUrl(SdkConfig.get(), this.context.client!);
|
||||
}
|
||||
|
||||
private onCurrentVoiceBroadcastRecordingChanged = (recording: VoiceBroadcastRecording | null): void => {
|
||||
this.setState({
|
||||
showLiveAvatarAddon: recording !== null,
|
||||
});
|
||||
};
|
||||
|
||||
public componentDidMount(): void {
|
||||
OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate);
|
||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
|
||||
this.context.voiceBroadcastRecordingsStore.on(
|
||||
VoiceBroadcastRecordingsStoreEvent.CurrentChanged,
|
||||
this.onCurrentVoiceBroadcastRecordingChanged,
|
||||
);
|
||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||
this.themeWatcherRef = SettingsStore.watchSetting("theme", null, this.onThemeChanged);
|
||||
}
|
||||
|
@ -125,10 +111,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate);
|
||||
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
|
||||
this.context.voiceBroadcastRecordingsStore.off(
|
||||
VoiceBroadcastRecordingsStoreEvent.CurrentChanged,
|
||||
this.onCurrentVoiceBroadcastRecordingChanged,
|
||||
);
|
||||
}
|
||||
|
||||
private isUserOnDarkTheme(): boolean {
|
||||
|
@ -435,12 +417,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
name = <div className="mx_UserMenu_name">{displayName}</div>;
|
||||
}
|
||||
|
||||
const liveAvatarAddon = this.state.showLiveAvatarAddon ? (
|
||||
<div className="mx_UserMenu_userAvatarLive" data-testid="user-menu-live-vb">
|
||||
<LiveIcon className="mx_Icon_8" />
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div className="mx_UserMenu">
|
||||
<ContextMenuButton
|
||||
|
@ -459,7 +435,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
size={avatarSize + "px"}
|
||||
className="mx_UserMenu_userAvatar_BaseAvatar"
|
||||
/>
|
||||
{liveAvatarAddon}
|
||||
</div>
|
||||
{name}
|
||||
{this.renderContextMenu()}
|
||||
|
|
|
@ -32,7 +32,7 @@ interface IState {
|
|||
|
||||
export default class UserView extends React.Component<IProps, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
public declare context: React.ContextType<typeof MatrixClientContext>;
|
||||
declare public context: React.ContextType<typeof MatrixClientContext>;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
super(props, context);
|
||||
|
@ -82,7 +82,7 @@ export default class UserView extends React.Component<IProps, IState> {
|
|||
} else if (this.state.member) {
|
||||
const panel = (
|
||||
<RightPanel
|
||||
overwriteCard={{ phase: RightPanelPhases.RoomMemberInfo, state: { member: this.state.member } }}
|
||||
overwriteCard={{ phase: RightPanelPhases.MemberInfo, state: { member: this.state.member } }}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
|||
import React, { RefObject } from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useRoomContext } from "../../contexts/RoomContext";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import ErrorBoundary from "../views/elements/ErrorBoundary";
|
||||
import RoomHeader from "../views/rooms/RoomHeader";
|
||||
|
@ -19,6 +18,7 @@ import NewRoomIntro from "../views/rooms/NewRoomIntro";
|
|||
import { UnwrappedEventTile } from "../views/rooms/EventTile";
|
||||
import { _t } from "../../languageHandler";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import { useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
|
||||
|
||||
interface Props {
|
||||
roomView: RefObject<HTMLElement>;
|
||||
|
@ -32,7 +32,7 @@ interface Props {
|
|||
* To avoid UTDs, users are shown a waiting room until the others have joined.
|
||||
*/
|
||||
export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resizeNotifier, inviteEvent }) => {
|
||||
const context = useRoomContext();
|
||||
const context = useScopedRoomContext("room");
|
||||
const brand = SdkConfig.get().brand;
|
||||
|
||||
return (
|
||||
|
|
|
@ -75,6 +75,7 @@ interface State {
|
|||
}
|
||||
|
||||
export default class ForgotPassword extends React.Component<Props, State> {
|
||||
private unmounted = false;
|
||||
private reset: PasswordReset;
|
||||
private fieldPassword: Field | null = null;
|
||||
private fieldPasswordConfirm: Field | null = null;
|
||||
|
@ -108,14 +109,20 @@ export default class ForgotPassword extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
private async checkServerLiveliness(serverConfig: ValidatedServerConfig): Promise<void> {
|
||||
public componentWillUnmount(): void {
|
||||
this.unmounted = true;
|
||||
}
|
||||
|
||||
private async checkServerLiveliness(serverConfig: ValidatedServerConfig): Promise<boolean> {
|
||||
try {
|
||||
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(serverConfig.hsUrl, serverConfig.isUrl);
|
||||
if (this.unmounted) return false;
|
||||
|
||||
this.setState({
|
||||
serverIsAlive: true,
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (this.unmounted) return false;
|
||||
const { serverIsAlive, serverDeadError } = AutoDiscoveryUtils.authComponentStateForError(
|
||||
e,
|
||||
"forgot_password",
|
||||
|
@ -124,7 +131,9 @@ export default class ForgotPassword extends React.Component<Props, State> {
|
|||
serverIsAlive,
|
||||
errorText: serverDeadError,
|
||||
});
|
||||
return serverIsAlive;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async onPhaseEmailInputSubmit(): Promise<void> {
|
||||
|
@ -292,10 +301,10 @@ export default class ForgotPassword extends React.Component<Props, State> {
|
|||
});
|
||||
|
||||
// Refresh the server errors. Just in case the server came back online of went offline.
|
||||
await this.checkServerLiveliness(this.props.serverConfig);
|
||||
const serverIsAlive = await this.checkServerLiveliness(this.props.serverConfig);
|
||||
|
||||
// Server error
|
||||
if (!this.state.serverIsAlive) return;
|
||||
if (!serverIsAlive) return;
|
||||
|
||||
switch (this.state.phase) {
|
||||
case Phase.EnterEmail:
|
||||
|
|
|
@ -64,7 +64,7 @@ interface IState {
|
|||
|
||||
export default class SoftLogout extends React.Component<IProps, IState> {
|
||||
public static contextType = SDKContext;
|
||||
public declare context: React.ContextType<typeof SDKContext>;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
|
||||
super(props, context);
|
||||
|
|
|
@ -12,7 +12,6 @@ import { KnownMembership } from "matrix-js-sdk/src/types";
|
|||
|
||||
import { BaseGrouper } from "./BaseGrouper";
|
||||
import MessagePanel, { WrappedEvent } from "../MessagePanel";
|
||||
import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import DateSeparator from "../../views/messages/DateSeparator";
|
||||
|
@ -53,11 +52,6 @@ export class CreationGrouper extends BaseGrouper {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (VoiceBroadcastInfoEventType === eventType) {
|
||||
// always show voice broadcast info events in timeline
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.isState() && event.getSender() === createEvent.getSender()) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { MutableRefObject } from "react";
|
||||
|
||||
import { toLeftOrRightOf } from "../../structures/ContextMenu";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuOptionList,
|
||||
IconizedContextMenuRadio,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
|
||||
interface Props {
|
||||
containerRef: MutableRefObject<HTMLElement | null>;
|
||||
currentDevice: MediaDeviceInfo | null;
|
||||
devices: MediaDeviceInfo[];
|
||||
onDeviceSelect: (device: MediaDeviceInfo) => void;
|
||||
}
|
||||
|
||||
export const DevicesContextMenu: React.FC<Props> = ({ containerRef, currentDevice, devices, onDeviceSelect }) => {
|
||||
const deviceOptions = devices.map((d: MediaDeviceInfo) => {
|
||||
return (
|
||||
<IconizedContextMenuRadio
|
||||
key={d.deviceId}
|
||||
active={d.deviceId === currentDevice?.deviceId}
|
||||
onClick={() => onDeviceSelect(d)}
|
||||
label={d.label}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<IconizedContextMenu
|
||||
mountAsChild={false}
|
||||
onFinished={() => {}}
|
||||
{...(containerRef.current ? toLeftOrRightOf(containerRef.current.getBoundingClientRect(), 0) : {})}
|
||||
>
|
||||
<IconizedContextMenuOptionList>{deviceOptions}</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>
|
||||
);
|
||||
};
|