Compare commits

...

47 Commits

Author SHA1 Message Date
Timo
656e4aed1b update js sdk to matrix-js-sdk/element-call-nov-preview 2024-11-12 15:21:16 +01:00
Timo
9cde9af706 Merge remote-tracking branch 'robintown/widget-feed-state' into element-call-nov-preview 2024-11-12 15:19:27 +01:00
Robin
54765b4473 Only feed state events to widgets if we believe they update the state
As part of the effort to make room state more reliable, we're looking at changing how state is communicated to widgets. Rather than passing all state events from the timeline through (and forcing the widget to assume that they belong to the server's view of the room state), let's try passing only those state events through that we believe actually belong to the server's view of the room state. When combined with a matrix-js-sdk that supports MSC4222, this improves the reliability of state tracking for widgets.

We would like to later explore a solution that has a separate 'state update' widget action in addition to the generic 'incoming timeline event' widget action, to make the widget API better reflect the shape of the data sources that drive it (like the sync responses of Simplified Sliding Sync or MSC4222).
2024-11-11 17:46:49 -05:00
Michael Telatynski
db30bc51a1 Deduplicate icons using Compound Design Tokens (#28419)
* Deduplicate icons using Compound Design Tokens

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update screenshots & snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-11 17:27:22 +00:00
Andrew Ferrazzutti
c8c107405f Let widget driver send error details (#28357)
* Let widget driver send error details

* Match new widget API types

* Don't @link across packages

in case web documentation generation disallows it.

* Update matrix-widget-api

* Update matrix-js-sdk

Include matrix-org/matrix-js-sdk#4507 to fix playwright CI
2024-11-11 16:39:17 +00:00
ElementRobot
17de66140d [create-pull-request] automated change (#28411)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-11-09 06:20:13 +00:00
Michael Telatynski
f3d0fc4d68 Merge remote-tracking branch 'origin/dbkr/stateafter' into dbkr/stateafter 2024-11-08 16:42:05 +00:00
Michael Telatynski
0b636aec4b Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-08 16:41:57 +00:00
Robin
d0cddc5b66 Feed events to widgets as they are decrypted (even if out of order) (#28376)
* Refactor feeding of events to widgets

This is a pure refactor with (hopefully) no behavior changes.

* Feed events to widgets as they are decrypted (even if out of order)

The code that feeds events to widgets tries to enforce that only events from the end of the timeline will be passed through. This is to prevent old, irrelevant events from being passed to widgets as the timeline is back-filled. However, since encrypted events need to be decrypted asynchronously, it's not possible to feed them to a widget in a strictly linear order without introducing some kind of blocking or unreliable delivery. This code has been dropping events when they're decrypted out of order, which we consider to be an undesirable behavior.

The solution provided here is that, to reflect the asynchronous nature of decryption, encrypted events that arrive at the end of the timeline will be fed to a widget whenever they finish decrypting, even if this means feeding them out of order. For now we're not aware of any widgets that care about knowing the exact order of events in the timeline, but if such a need reveals itself later, we can explore adding ordering information to this part of the widget API.

* Add braces to if
2024-11-08 14:35:47 +00:00
Michael Telatynski
cb0d72fc2f Merge branch 'develop' into dbkr/stateafter 2024-11-08 09:58:00 +00:00
ElementRobot
9a6be72c10 [create-pull-request] automated change (#28408)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-11-08 09:31:14 +00:00
ElementRobot
536d6ad360 [create-pull-request] automated change (#28409)
Co-authored-by: t3chguy <t3chguy@users.noreply.github.com>
2024-11-08 06:24:40 +00:00
Michael Telatynski
b604a6ea8c Enable enable_authenticated_media in Playwright tests (#28395)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-07 11:58:17 +00:00
Michael Telatynski
da4672d715 Handle authenticated media when downloading from ImageView (#28379)
* Handle authenticated media when downloading from ImageView

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-07 11:43:33 +00:00
Michael Telatynski
74a919cb65 Deduplicate icons using Compound Design Tokens (#28381)
* Deduplicate icons using Compound Design Tokens

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove unused test images

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshots & screenshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-07 11:39:18 +00:00
ElementRobot
b92101a3da [create-pull-request] automated change (#28403)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-11-07 09:34:13 +00:00
Timo
5057668705 Auto approvoce io.element.call.reaction capability for element call widgets (#28401) 2024-11-06 20:15:19 +00:00
Michael Telatynski
931edd7419 Merge branch 'develop' into dbkr/stateafter 2024-11-06 15:42:28 +00:00
Michael Telatynski
044eaf7eb5 Update calls to addEventToTimeline and addLiveEvents for new signature
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-06 14:55:21 +00:00
Michael Telatynski
88c72a1514 Ignore m.3pid_changes for Identity service 3PID changes (#28375)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-06 14:33:50 +00:00
Michael Telatynski
d06cf09bf0 Switch secondary React trees to the createRoot API (#28296)
* Switch secondary React trees to the createRoot API

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add comment

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-06 12:44:54 +00:00
ElementRobot
2f8e98242c [create-pull-request] automated change (#28391)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-11-06 09:02:22 +00:00
ElementRobot
3ff5a511e5 [create-pull-request] automated change (#28392)
Co-authored-by: t3chguy <t3chguy@users.noreply.github.com>
2024-11-06 06:23:22 +00:00
Richard van der Hoff
6042625198 Remove redundant switch_package_to_release call (#28384)
`switch_package_to_release` is now a no-op (there are no fields called
`matrix_lib_*` in matrix-js-sdk's package.json), so everything after that is
redundant.
2024-11-05 16:16:38 +00:00
Michael Telatynski
9d79a934bf Flatten Vector-override components (#28346)
* Flatten Vector-override components

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Ie

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-05 15:41:00 +00:00
Michael Telatynski
a355292a7f Merge remote-tracking branch 'origin/develop' into develop 2024-11-05 14:03:17 +00:00
Michael Telatynski
24fabfff89 Fix post release docker check
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-05 14:02:25 +00:00
RiotRobot
0b6ed44390 Reset matrix-js-sdk back to develop branch 2024-11-05 13:59:24 +00:00
RiotRobot
a6ce6dc7ab Merge branch 'master' into develop 2024-11-05 13:59:15 +00:00
RiotRobot
15984455af v1.11.84 2024-11-05 13:55:39 +00:00
RiotRobot
6e4bd564d5 Upgrade dependency to matrix-js-sdk@34.10.0 2024-11-05 13:50:59 +00:00
Michael Telatynski
aeabf3b188 Show message type prefix in thread root & reply previews (#28361)
* Extract EventPreview from PinnedMessageBanner

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Show message type prefix in thread root previews

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Show message type prefix in thread reply preview

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-05 11:34:42 +00:00
Michael Telatynski
c9d9c421bc Move navigator message listener registration to be synchronously attached (#28340)
to silence Chrome warning

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-05 11:04:14 +00:00
ElementRobot
d7d96b6b8b [create-pull-request] automated change (#28377)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-11-05 06:19:43 +00:00
Hubert Chathi
2631b908b6 Update the display of decryption failures due to failed trust requirement (#28300)
* update the display of decryption failures due to failed trust requirement

* add test for not showing shield
2024-11-04 12:46:38 +00:00
RiotRobot
849f2c9818 v1.11.84-rc.0 2024-10-29 13:02:40 +00:00
RiotRobot
8ebfaadeed Upgrade dependency to matrix-js-sdk@34.10.0-rc.0 2024-10-29 12:57:26 +00:00
Michael Telatynski
1d49a46dd2 Merge branch 'develop' into staging
# Conflicts:
#	package.json
#	yarn.lock
2024-10-29 12:49:13 +00:00
RiotRobot
dabe6722aa v1.11.83 2024-10-29 09:23:20 +00:00
Michael Telatynski
10a63b3c23 Merge pull request #28314 from element-hq/backport-28313-to-staging 2024-10-29 09:20:38 +00:00
Michael Telatynski
9ce515a646 Enable Element Call by default on release instances (#28313)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

(cherry picked from commit 79c956388f)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-10-28 15:45:59 +00:00
RiotRobot
1df72ce2d0 v1.11.82 2024-10-22 12:10:29 +00:00
RiotRobot
6a960204b3 Upgrade dependency to matrix-react-sdk@3.114.0 2024-10-22 12:07:41 +00:00
RiotRobot
26cd13ae3c Upgrade dependency to matrix-js-sdk@34.9.0 2024-10-22 11:59:48 +00:00
RiotRobot
3793c6daca v1.11.82-rc.0 2024-10-15 14:57:48 +00:00
RiotRobot
65f0d7930a Upgrade dependency to matrix-react-sdk@3.114.0-rc.0 2024-10-15 14:43:13 +00:00
RiotRobot
8b914c02d0 Upgrade dependency to matrix-js-sdk@34.9.0-rc.0 2024-10-15 14:33:13 +00:00
187 changed files with 1627 additions and 1265 deletions

View File

@@ -49,7 +49,7 @@ jobs:
ref: master
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
check-name: "Docker Buildx (vanilla)"
check-name: "Docker Buildx"
allowed-conclusions: success
- name: Wait for debian package

View File

@@ -34,27 +34,6 @@ jobs:
- name: Typecheck
run: "yarn run lint:types"
- name: Switch js-sdk to release mode
working-directory: node_modules/matrix-js-sdk
run: |
scripts/switch_package_to_release.cjs
yarn install
yarn run build:compile
yarn run build:types
- name: Typecheck (release mode)
run: "yarn run lint:types"
# Temporary while we directly import matrix-js-sdk/src/* which means we need
# certain @types/* packages to make sense of matrix-js-sdk types.
#- name: Typecheck (release mode; no yarn link)
# if: github.event_name != 'pull_request' && github.ref_name != 'master'
# run: |
# yarn unlink matrix-js-sdk
# yarn add github:matrix-org/matrix-js-sdk#develop
# yarn install --force
# yarn run lint:types
i18n_lint:
name: "i18n Check"
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main

View File

@@ -1,3 +1,68 @@
Changes in [1.11.84](https://github.com/element-hq/element-web/releases/tag/v1.11.84) (2024-11-05)
==================================================================================================
## ✨ Features
* Remove abandoned MSC3886, MSC3903, MSC3906 implementations ([#28274](https://github.com/element-hq/element-web/pull/28274)). Contributed by @t3chguy.
* Update to React 18 ([#24763](https://github.com/element-hq/element-web/pull/24763)). Contributed by @t3chguy.
* Deduplicate icons using Compound ([#28239](https://github.com/element-hq/element-web/pull/28239)). Contributed by @t3chguy.
* Replace legacy Tooltips with Compound tooltips ([#28231](https://github.com/element-hq/element-web/pull/28231)). Contributed by @t3chguy.
* Deduplicate icons using Compound Design Tokens ([#28219](https://github.com/element-hq/element-web/pull/28219)). Contributed by @t3chguy.
* Add reactions to html export ([#28210](https://github.com/element-hq/element-web/pull/28210)). Contributed by @langleyd.
* Remove feature\_dehydration ([#28173](https://github.com/element-hq/element-web/pull/28173)). Contributed by @florianduros.
## 🐛 Bug Fixes
* Remove upgrade encryption in `DeviceListener` and `SetupEncryptionToast` ([#28299](https://github.com/element-hq/element-web/pull/28299)). Contributed by @florianduros.
* Fix 'remove alias' button in room settings ([#28269](https://github.com/element-hq/element-web/pull/28269)). Contributed by @Dev-Gurjar.
* Add back unencrypted path in `StopGapWidgetDriver.sendToDevice` ([#28295](https://github.com/element-hq/element-web/pull/28295)). Contributed by @florianduros.
* Fix other devices not being decorated as such ([#28279](https://github.com/element-hq/element-web/pull/28279)). Contributed by @t3chguy.
* Fix pill contrast in invitation dialog ([#28250](https://github.com/element-hq/element-web/pull/28250)). Contributed by @florianduros.
* Close right panel chat when minimising maximised voip widget ([#28241](https://github.com/element-hq/element-web/pull/28241)). Contributed by @t3chguy.
* Fix develop changelog parsing ([#28232](https://github.com/element-hq/element-web/pull/28232)). Contributed by @t3chguy.
* Fix Ctrl+F shortcut not working with minimised room summary card ([#28223](https://github.com/element-hq/element-web/pull/28223)). Contributed by @t3chguy.
* Fix network dropdown missing checkbox \& aria-checked ([#28220](https://github.com/element-hq/element-web/pull/28220)). Contributed by @t3chguy.
Changes in [1.11.83](https://github.com/element-hq/element-web/releases/tag/v1.11.83) (2024-10-29)
==================================================================================================
## ✨ Features
* Enable Element Call by default on release instances ([#28314](https://github.com/element-hq/element-web/pull/28314)). Contributed by @t3chguy.
Changes in [1.11.82](https://github.com/element-hq/element-web/releases/tag/v1.11.82) (2024-10-22)
==================================================================================================
## ✨ Features
* Deduplicate more icons using Compound Design Tokens ([#132](https://github.com/element-hq/matrix-react-sdk/pull/132)). Contributed by @t3chguy.
* Always show link new device flow even if unsupported ([#147](https://github.com/element-hq/matrix-react-sdk/pull/147)). Contributed by @t3chguy.
* Update design of files list in right panel ([#144](https://github.com/element-hq/matrix-react-sdk/pull/144)). Contributed by @t3chguy.
* Remove feature\_dehydration ([#138](https://github.com/element-hq/matrix-react-sdk/pull/138)). Contributed by @florianduros.
* Upgrade emojibase-bindings and remove local handling of emoticon variations ([#127](https://github.com/element-hq/matrix-react-sdk/pull/127)). Contributed by @langleyd.
* Add support for rendering media captions ([#43](https://github.com/element-hq/matrix-react-sdk/pull/43)). Contributed by @tulir.
* Replace composer icons with Compound variants ([#123](https://github.com/element-hq/matrix-react-sdk/pull/123)). Contributed by @t3chguy.
* Tweak default right panel size to be 320px except for maximised widgets at 420px ([#110](https://github.com/element-hq/matrix-react-sdk/pull/110)). Contributed by @t3chguy.
* Add a pinned message badge under a pinned message ([#118](https://github.com/element-hq/matrix-react-sdk/pull/118)). Contributed by @florianduros.
* Ditch right panel tabs and re-add close button ([#99](https://github.com/element-hq/matrix-react-sdk/pull/99)). Contributed by @t3chguy.
* Force verification even for refreshed clients ([#44](https://github.com/element-hq/matrix-react-sdk/pull/44)). Contributed by @dbkr.
* Update emoji text, border and background colour in timeline ([#119](https://github.com/element-hq/matrix-react-sdk/pull/119)). Contributed by @florianduros.
* Disable ICE fallback based on well-known configuration ([#111](https://github.com/element-hq/matrix-react-sdk/pull/111)). Contributed by @t3chguy.
* Remove legacy room header and promote beta room header ([#105](https://github.com/element-hq/matrix-react-sdk/pull/105)). Contributed by @t3chguy.
* Respect `io.element.jitsi` `useFor1To1Calls` in well-known ([#112](https://github.com/element-hq/matrix-react-sdk/pull/112)). Contributed by @t3chguy.
* Use Compound close icon in favour of mishmash of x/close icons ([#108](https://github.com/element-hq/matrix-react-sdk/pull/108)). Contributed by @t3chguy.
## 🐛 Bug Fixes
* Correct typo in option documentation ([#28148](https://github.com/element-hq/element-web/pull/28148)). Contributed by @AndrewKvalheim.
* Revert #124 and #135 ([#139](https://github.com/element-hq/matrix-react-sdk/pull/139)). Contributed by @dbkr.
* Add aria-label to e2e icon ([#136](https://github.com/element-hq/matrix-react-sdk/pull/136)). Contributed by @florianduros.
* Fix bell icons on room list hover being black squares ([#135](https://github.com/element-hq/matrix-react-sdk/pull/135)). Contributed by @dbkr.
* Fix vertical overflow on the mobile register screen ([#137](https://github.com/element-hq/matrix-react-sdk/pull/137)). Contributed by @langleyd.
* Allow to unpin redacted event ([#98](https://github.com/element-hq/matrix-react-sdk/pull/98)). Contributed by @florianduros.
Changes in [1.11.81](https://github.com/element-hq/element-web/releases/tag/v1.11.81) (2024-10-15)
==================================================================================================
This release fixes High severity vulnerability CVE-2024-47771 / GHSA-963w-49j9-gxj6

View File

@@ -1,5 +1 @@
{
"src/components/views/auth/AuthFooter.tsx": "src/components/views/auth/VectorAuthFooter.tsx",
"src/components/views/auth/AuthHeaderLogo.tsx": "src/components/views/auth/VectorAuthHeaderLogo.tsx",
"src/components/views/auth/AuthPage.tsx": "src/components/views/auth/VectorAuthPage.tsx"
}
{}

View File

@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.81",
"version": "1.11.84",
"description": "A feature-rich client for Matrix.org",
"author": "New Vector Ltd.",
"repository": {
@@ -86,12 +86,12 @@
"@formatjs/intl-segmenter": "^11.5.7",
"@matrix-org/analytics-events": "^0.29.0",
"@matrix-org/emojibase-bindings": "^1.3.3",
"@vector-im/matrix-wysiwyg": "2.37.13",
"@matrix-org/react-sdk-module-api": "^2.4.0",
"@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^8.0.0",
"@vector-im/compound-design-tokens": "^1.8.0",
"@vector-im/compound-web": "^7.1.0",
"@vector-im/matrix-wysiwyg": "2.37.13",
"@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2",
@@ -114,8 +114,8 @@
"highlight.js": "^11.3.1",
"html-entities": "^2.0.0",
"is-ip": "^3.1.0",
"jsrsasign": "^11.0.0",
"js-xxhash": "^4.0.0",
"jsrsasign": "^11.0.0",
"jszip": "^3.7.0",
"katex": "^0.16.0",
"linkify-element": "4.1.3",
@@ -126,8 +126,8 @@
"maplibre-gl": "^2.0.0",
"matrix-encrypt-attachment": "^1.0.3",
"matrix-events-sdk": "0.0.1",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-widget-api": "^1.9.0",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#8e65c13d5019eb215fa99c86ddef463d9c383115",
"matrix-widget-api": "^1.10.0",
"memoize-one": "^6.0.0",
"oidc-client-ts": "^3.0.1",
"opus-recorder": "^8.0.3",

View File

@@ -51,6 +51,6 @@ test.describe("Invisible cryptography", () => {
/* 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 lastTile = page.locator(".mx_EventTile_last");
await expect(lastTile).toContainText("Verified identity has changed");
await expect(lastTile).toContainText("Sender's verified identity has changed");
});
});

View File

@@ -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:46a04410bafb6db707a5083d7377adbb84144c93dd917b276ae1913b5459e908";
const DOCKER_TAG = "develop@sha256:b90c4e10abfc6bb4fb9301d5b148ab7e1ab752298624a705e84e7e1ad6037d08";
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const templateDir = path.join(__dirname, "templates", opts.template);

View File

@@ -102,3 +102,5 @@ experimental_features:
# messages > non-joined historical messages.
# Can be removed after Synapse enables it by default
msc4115_membership_on_events: true
enable_authenticated_media: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -186,7 +186,7 @@ input[type="search"].mx_textinput_icon {
/* FIXME THEME - Tint by CSS rather than referencing a duplicate asset */
input[type="text"].mx_textinput_icon.mx_textinput_search,
input[type="search"].mx_textinput_icon.mx_textinput_search {
background-image: url("$(res)/img/feather-customised/search-input.svg");
background-image: url("@vector-im/compound-design-tokens/icons/search.svg");
}
/* dont search UI as not all browsers support it, */

View File

@@ -282,6 +282,7 @@
@import "./views/rooms/_EmojiButton.pcss";
@import "./views/rooms/_EntityTile.pcss";
@import "./views/rooms/_EventBubbleTile.pcss";
@import "./views/rooms/_EventPreview.pcss";
@import "./views/rooms/_EventTile.pcss";
@import "./views/rooms/_HistoryTile.pcss";
@import "./views/rooms/_IRCLayout.pcss";

View File

@@ -32,8 +32,8 @@ Please see LICENSE files in the repository root for full details.
}
.mx_DeviceExpandDetailsButton_icon {
height: 16px;
width: 16px;
height: 24px;
width: 24px;
transition: all 0.3s;
transform: var(--icon-transform);

View File

@@ -25,7 +25,7 @@ Please see LICENSE files in the repository root for full details.
width: 18px;
height: 18px;
background: currentColor;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
mask-size: 100%;
mask-repeat: no-repeat;
float: right;

View File

@@ -125,7 +125,7 @@ Please see LICENSE files in the repository root for full details.
padding-left: 34px; /* 28px from above, but +6px to account for the wider icon */
&::before {
mask-image: url("$(res)/img/element-icons/retry.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/restart.svg");
}
}
}

View File

@@ -62,7 +62,7 @@ Please see LICENSE files in the repository root for full details.
&::before {
background-color: $info-plinth-fg-color;
mask: url("$(res)/img/feather-customised/search-input.svg");
mask: url("@vector-im/compound-design-tokens/icons/search.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: 50px;

View File

@@ -77,7 +77,7 @@ Please see LICENSE files in the repository root for full details.
height: 16px;
width: 16px;
left: 0;
background-image: url("$(res)/img/element-icons/warning-badge.svg");
background-image: url("@vector-im/compound-design-tokens/icons/error.svg");
background-size: cover;
background-repeat: no-repeat;
}
@@ -121,7 +121,7 @@ Please see LICENSE files in the repository root for full details.
background-color: $tertiary-content;
mask-size: 16px;
transform: rotate(270deg);
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
}
&.mx_SpaceHierarchy_subspace_toggle_shown::before {

View File

@@ -48,7 +48,7 @@ Please see LICENSE files in the repository root for full details.
mask-size: contain;
mask-repeat: no-repeat;
background-color: $background;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
transform: rotate(270deg);
}
@@ -169,7 +169,7 @@ Please see LICENSE files in the repository root for full details.
mask-size: 20px;
mask-repeat: no-repeat;
background-color: $tertiary-content;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
}
.mx_SpaceButton_icon {

View File

@@ -29,7 +29,7 @@ Please see LICENSE files in the repository root for full details.
}
.mx_MessageContextMenu_iconReport::before {
mask-image: url("$(res)/img/element-icons/warning-badge.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/error.svg");
}
.mx_MessageContextMenu_iconLink::before {
@@ -61,7 +61,7 @@ Please see LICENSE files in the repository root for full details.
}
.mx_MessageContextMenu_iconResend::before {
mask-image: url("$(res)/img/element-icons/retry.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/restart.svg");
}
.mx_MessageContextMenu_iconSource::before {

View File

@@ -125,7 +125,7 @@ Please see LICENSE files in the repository root for full details.
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
mask-image: url("$(res)/img/element-icons/retry.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/restart.svg");
width: 18px;
height: 18px;
left: 0;

View File

@@ -36,9 +36,24 @@ Please see LICENSE files in the repository root for full details.
}
.mx_AnalyticsLearnMore_bullets li {
background: url("$(res)/img/tick-circle.svg") no-repeat;
list-style-type: none;
padding: 2px 0px 20px 32px;
padding: 2px 0 0 32px;
margin-bottom: 20px;
vertical-align: middle;
position: relative;
&::before {
content: "";
position: absolute;
width: 26px;
height: 26px;
left: 0;
top: 0;
background-color: #0dbd8b;
mask-image: url("@vector-im/compound-design-tokens/icons/check-circle.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
}
}
}

View File

@@ -21,7 +21,7 @@ Please see LICENSE files in the repository root for full details.
&.mx_AccessSecretStorageDialog_resetBadge::before {
/* The image isn't capable of masking, so we use a background instead. */
background-image: url("$(res)/img/element-icons/warning-badge.svg");
background-image: url("@vector-im/compound-design-tokens/icons/error.svg");
background-size: 24px;
background-color: transparent;
}
@@ -120,7 +120,7 @@ Please see LICENSE files in the repository root for full details.
width: 16px;
left: 0;
top: 2px; /* alignment */
background-image: url("$(res)/img/element-icons/warning-badge.svg");
background-image: url("@vector-im/compound-design-tokens/icons/error.svg");
background-size: contain;
}

View File

@@ -39,11 +39,13 @@ Please see LICENSE files in the repository root for full details.
}
.mx_Dropdown_arrow {
width: 10px;
height: 6px;
padding-right: 9px;
mask: url("$(res)/img/feather-customised/dropdown-arrow.svg");
width: 16px;
height: 16px;
margin-right: 4px;
mask: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: 18px;
background: $primary-content;
}

View File

@@ -51,12 +51,15 @@ Please see LICENSE files in the repository root for full details.
.mx_Field_select::before {
content: "";
position: absolute;
top: 15px;
right: 10px;
width: 10px;
height: 6px;
mask: url("$(res)/img/feather-customised/dropdown-arrow.svg");
top: 50%;
transform: translateY(-50%);
right: 4px;
width: 18px;
height: 18px;
mask: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
background-color: $primary-content;
z-index: 1;
pointer-events: none;

View File

@@ -29,5 +29,5 @@ Please see LICENSE files in the repository root for full details.
}
.mx_InfoTooltip_icon_warning::before {
mask-image: url("$(res)/img/element-icons/warning.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/error.svg");
}

View File

@@ -30,6 +30,6 @@ Please see LICENSE files in the repository root for full details.
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
background-color: var(--cpd-color-icon-secondary);
}

View File

@@ -11,22 +11,11 @@ Please see LICENSE files in the repository root for full details.
font-style: italic;
}
/* Formatting for the "Verified identity has changed" error */
.mx_DecryptionFailureVerifiedIdentityChanged > span {
/* Show it in red */
color: var(--cpd-color-text-critical-primary);
background-color: var(--cpd-color-bg-critical-subtle);
/* With a red border */
border: 1px solid var(--cpd-color-border-critical-subtle);
border-radius: $font-16px;
/* Some space inside the border */
padding: var(--cpd-space-1x) var(--cpd-space-3x) var(--cpd-space-1x) var(--cpd-space-2x);
/* some space between the (!) icon and text */
/* Formatting for errors due to sender trust requirement failures */
.mx_DecryptionFailureSenderTrustRequirement > span {
/* some space between the (/) icon and text */
display: inline-flex;
gap: var(--cpd-space-2x);
gap: var(--cpd-space-1x);
/* Center vertically */
align-items: center;

View File

@@ -108,6 +108,10 @@ Please see LICENSE files in the repository root for full details.
color: var(--cpd-color-icon-primary);
}
&.mx_MessageActionBar_retryButton {
--MessageActionBar-icon-size: 16px;
}
&.mx_MessageActionBar_downloadButton {
--MessageActionBar-icon-size: 14px;

View File

@@ -45,7 +45,7 @@ Please see LICENSE files in the repository root for full details.
width: 18px;
height: 18px;
background: currentColor;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
mask-size: 100%;
mask-repeat: no-repeat;
float: right;

View File

@@ -26,9 +26,9 @@ Please see LICENSE files in the repository root for full details.
height: 16px;
width: 16px;
padding: 4px;
mask-image: url("$(res)/img/minimise.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-left.svg");
mask-repeat: no-repeat;
mask-position: 7px center;
mask-position: center;
background-color: $header-panel-text-primary-color;
}
}

View File

@@ -31,8 +31,9 @@ Please see LICENSE files in the repository root for full details.
position: absolute;
top: calc(50% - 8px); /* center */
right: -8px;
mask: url("$(res)/img/member_chevron.png");
mask: url("@vector-im/compound-design-tokens/icons/chevron-right.svg");
mask-repeat: no-repeat;
mask-position: center;
width: 16px;
height: 16px;
background-color: $header-panel-text-primary-color;

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2024 New Vector Ltd.
* Copyright 2024 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_EventPreview {
font: var(--cpd-font-body-sm-regular);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.mx_EventPreview_prefix {
font: var(--cpd-font-body-sm-semibold);
}
}

View File

@@ -18,7 +18,7 @@ Please see LICENSE files in the repository root for full details.
}
}
&:hover .mx_LinkPreviewGroup_hide img,
&:hover .mx_LinkPreviewGroup_hide svg,
.mx_LinkPreviewGroup_hide:focus-visible:focus svg {
visibility: visible;
}

View File

@@ -81,15 +81,7 @@
.mx_PinnedMessageBanner_message {
grid-area: message;
font: var(--cpd-font-body-sm-regular);
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.mx_PinnedMessageBanner_prefix {
font: var(--cpd-font-body-sm-semibold);
}
}
.mx_PinnedMessageBanner_redactedMessage {

View File

@@ -42,7 +42,7 @@ Please see LICENSE files in the repository root for full details.
mask-size: contain;
mask-repeat: no-repeat;
background-color: $tertiary-content;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
}
&[aria-expanded="true"] {

View File

@@ -160,7 +160,7 @@ Please see LICENSE files in the repository root for full details.
mask-size: contain;
mask-repeat: no-repeat;
background-color: var(--cpd-color-icon-secondary);
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
}
&.mx_RoomSublist_collapseBtn_collapsed::before {
@@ -276,7 +276,7 @@ Please see LICENSE files in the repository root for full details.
.mx_RoomSublist_showMoreButtonChevron,
.mx_RoomSublist_showLessButtonChevron {
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
}
.mx_RoomSublist_showLessButtonChevron {

View File

@@ -53,11 +53,11 @@ Please see LICENSE files in the repository root for full details.
content: "";
position: absolute;
top: 50%;
right: $spacing-12;
right: var(--cpd-space-1x);
transform: translateY(-50%);
width: 12px;
height: 12px;
mask-image: url("$(res)/img/compound/chevron-right-12px.svg");
width: 24px;
height: 24px;
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-right.svg");
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;

View File

@@ -67,7 +67,7 @@ Please see LICENSE files in the repository root for full details.
mask-repeat: no-repeat;
mask-position: 2px 3px;
mask-size: 24px;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
}
}

View File

@@ -147,7 +147,7 @@ Please see LICENSE files in the repository root for full details.
&::before {
content: "";
display: inline-block;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
mask-size: 20px;
mask-position: center;
background-color: $call-primary-content;

View File

@@ -1,10 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1692_80)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.96967 10.7197C3.67678 10.4268 3.67601 9.95114 3.96795 9.6573L7.66823 5.933L3.95592 2.22069C3.66303 1.92779 3.66226 1.45215 3.9542 1.15831C4.24615 0.864473 4.72025 0.863706 5.01315 1.1566L9.25579 5.39924C9.54868 5.69213 9.54945 6.16777 9.2575 6.46161L5.02861 10.718C4.73667 11.0118 4.26256 11.0126 3.96967 10.7197Z" fill="#737D8C"/>
</g>
<defs>
<clipPath id="clip0_1692_80">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 629 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.02266 2.96455C5.11589 2.10004 6.49866 1.5835 8 1.5835C11.3187 1.5835 14.049 4.10294 14.3825 7.3335H15.6723C15.9336 7.3335 16.0894 7.62498 15.9445 7.8426L13.9388 10.8543C13.8094 11.0488 13.524 11.0488 13.3945 10.8543L11.3888 7.8426C11.2439 7.62498 11.3997 7.3335 11.661 7.3335H12.8719C12.5465 4.93343 10.4893 3.0835 8 3.0835C6.84828 3.0835 5.79092 3.47857 4.95308 4.14112C4.8969 4.18555 4.84851 4.22129 4.81295 4.24673C4.7951 4.2595 4.78032 4.26979 4.7692 4.27743L4.75529 4.28689L4.75051 4.2901L4.74868 4.29132L4.74791 4.29183L4.74756 4.29206L4.74739 4.29217L4.74731 4.29223L4.33341 3.66694L4.74723 4.29228C4.40181 4.52087 3.93648 4.42616 3.70788 4.08073C3.47976 3.736 3.57362 3.27185 3.91734 3.04277L3.92021 3.04081L3.94013 3.02682C3.95912 3.01323 3.988 2.99197 4.02266 2.96455ZM3.12815 8.66683H4.33901C4.60027 8.66683 4.7561 8.37534 4.61118 8.15772L2.60551 5.14598C2.47603 4.95156 2.19064 4.95156 2.06116 5.14598L0.0554881 8.15772C-0.0894338 8.37534 0.0663988 8.66683 0.327661 8.66683H1.61755C1.95103 11.8974 4.68129 14.4168 8 14.4168C9.56831 14.4168 11.0069 13.8532 12.1215 12.9184C12.4388 12.6522 12.4803 12.1791 12.2141 11.8617C11.9479 11.5444 11.4749 11.5029 11.1575 11.7691C10.303 12.4859 9.20281 12.9168 8 12.9168C5.51071 12.9168 3.4535 11.0669 3.12815 8.66683Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="16" fill="none">
<path
fill="currentColor"
fill-rule="evenodd"
d="M4.523 2.964a6.418 6.418 0 0 1 10.36 4.369h1.29c.26 0 .416.292.272.51l-2.006 3.011a.327.327 0 0 1-.544 0l-2.006-3.012a.327.327 0 0 1 .272-.509h1.21a4.918 4.918 0 0 0-7.918-3.192 3.684 3.684 0 0 1-.184.136l-.014.01-.004.003-.002.001h-.001v.001l-.415-.625.414.625a.75.75 0 0 1-.83-1.25l.003-.001.02-.014c.02-.014.048-.035.083-.063Zm-.895 5.703H4.84a.327.327 0 0 0 .272-.51L3.106 5.146a.327.327 0 0 0-.545 0L.555 8.157c-.144.218.011.51.273.51h1.29a6.418 6.418 0 0 0 10.503 4.251.75.75 0 0 0-.963-1.15 4.918 4.918 0 0 1-8.03-3.102Z"
clip-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 703 B

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
fill="none"
viewBox="0 0 24 24"
height="24"
width="24">
<metadata
id="metadata14">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs12" />
<path
id="path2"
d="M 12 2 C 6.47715 2 2 6.47715 2 12 C 2 17.5228 6.47715 22 12 22 C 17.5228 22 22 17.5228 22 12 C 22 6.47715 17.5228 2 12 2 z M 11.880859 5.5039062 C 12.720859 5.4439063 13.470547 6.0746875 13.560547 6.9296875 L 13.560547 7.1699219 L 13.080078 13.169922 C 13.035078 13.724922 12.570625 14.144531 12.015625 14.144531 L 11.925781 14.144531 C 11.400781 14.099531 10.996172 13.694922 10.951172 13.169922 L 10.470703 7.1699219 C 10.395703 6.3149219 11.025859 5.5639064 11.880859 5.5039062 z M 12 15.763672 C 12.729 15.763672 13.320312 16.354884 13.320312 17.083984 C 13.320313 17.812984 12.729 18.404297 12 18.404297 C 11.271 18.404297 10.679688 17.812984 10.679688 17.083984 C 10.679688 16.354884 11.271 15.763672 12 15.763672 z "
style="fill:#ff5b55;fill-opacity:1" />
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM6.9806 4.5101C6.9306 3.9401 7.3506 3.4401 7.9206 3.4001C8.4806 3.3601 8.9806 3.7801 9.0406 4.3501V4.5101L8.7206 8.5101C8.6906 8.8801 8.3806 9.1601 8.0106 9.1601H7.9506C7.6006 9.1301 7.3306 8.8601 7.3006 8.5101L6.9806 4.5101ZM8.88012 11.1202C8.88012 11.6062 8.48613 12.0002 8.00012 12.0002C7.51411 12.0002 7.12012 11.6062 7.12012 11.1202C7.12012 10.6342 7.51411 10.2402 8.00012 10.2402C8.48613 10.2402 8.88012 10.6342 8.88012 11.1202Z" fill="#8D99A5"/>
</svg>

Before

Width:  |  Height:  |  Size: 713 B

View File

@@ -1,3 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 7.5L9 10.5L12 7.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 217 B

View File

@@ -1,11 +0,0 @@
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1378.000000, -91.000000)" stroke="#61708b" stroke-width="1">
<g id="search-copy" transform="translate(1379.000000, 92.000000)">
<circle id="Oval" cx="6.22222222" cy="6.22222222" r="6.22222222"></circle>
<path d="M14,14 L10.6166667,10.6166667" id="Path"></path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 B

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="10px" height="16px" viewBox="-1 -1 10 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: sketchtool 3.5.1 (25234) - http://www.bohemiancoding.com/sketch -->
<title>minimise</title>
<desc>Created with sketchtool.</desc>
<defs></defs>
<g id="02-Chat" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="02_1-Chat-collapsed-w-topic" sketch:type="MSArtboardGroup" transform="translate(-176.000000, -27.000000)" stroke-width="2" stroke="#9FA9BA">
<g id="Room-list" sketch:type="MSLayerGroup">
<g id="Room-list/Header" sketch:type="MSShapeGroup">
<g id="minimise" transform="translate(172.000000, 25.000000)">
<path d="M7,5 L15,5 L15,13" id="Path-53-Copy" transform="translate(11.000000, 9.000000) scale(-1, -1) rotate(-315.000000) translate(-11.000000, -9.000000) "></path>
</g>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,4 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2V2Z" stroke="#0DBD8B" stroke-width="2" stroke-linecap="square"/>
<path d="M6.54549 12.8882L9.80306 16.2426L17.4546 8.36377" stroke="#0DBD8B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 442 B

View File

@@ -31,6 +31,8 @@ import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
import { IConfigOptions } from "./IConfigOptions";
import SdkConfig from "./SdkConfig";
import { buildAndEncodePickleKey, encryptPickleKey } from "./utils/tokens/pickling";
import Favicon from "./favicon.ts";
import { getVectorConfig } from "./vector/getconfig.ts";
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
@@ -66,14 +68,20 @@ const UPDATE_DEFER_KEY = "mx_defer_update";
export default abstract class BasePlatform {
protected notificationCount = 0;
protected errorDidOccur = false;
protected _favicon?: Favicon;
protected constructor() {
dis.register(this.onAction);
this.startUpdateCheck = this.startUpdateCheck.bind(this);
}
public abstract getConfig(): Promise<IConfigOptions | undefined>;
public async getConfig(): Promise<IConfigOptions | undefined> {
return getVectorConfig();
}
/**
* Get a sensible default display name for the device Element is running on
*/
public abstract getDefaultDeviceDisplayName(): string;
protected onAction = (payload: ActionPayload): void => {
@@ -89,11 +97,15 @@ export default abstract class BasePlatform {
public abstract getHumanReadableName(): string;
public setNotificationCount(count: number): void {
if (this.notificationCount === count) return;
this.notificationCount = count;
this.updateFavicon();
}
public setErrorStatus(errorDidOccur: boolean): void {
if (this.errorDidOccur === errorDidOccur) return;
this.errorDidOccur = errorDidOccur;
this.updateFavicon();
}
/**
@@ -456,4 +468,34 @@ export default abstract class BasePlatform {
url.hash = "";
return url;
}
/**
* Delay creating the `Favicon` instance until first use (on the first notification) as
* it uses canvas, which can trigger a permission prompt in Firefox's resist fingerprinting mode.
* See https://github.com/element-hq/element-web/issues/9605.
*/
public get favicon(): Favicon {
if (this._favicon) {
return this._favicon;
}
this._favicon = new Favicon();
return this._favicon;
}
private updateFavicon(): void {
let bgColor = "#d00";
let notif: string | number = this.notificationCount;
if (this.errorDidOccur) {
notif = notif || "×";
bgColor = "#f00";
}
this.favicon.badge(notif, { bgColor });
}
/**
* Begin update polling, if applicable
*/
public startUpdater(): void {}
}

View File

@@ -104,7 +104,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,
});
}
}

View File

@@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
import React, { ReactNode } from "react";
import { Tooltip } from "@vector-im/compound-web";
import { RestartIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import AccessibleButton from "../../../views/elements/AccessibleButton";
import { Icon as EMailPromptIcon } from "../../../../../res/img/element-icons/email-prompt.svg";
import { Icon as RetryIcon } from "../../../../../res/img/compound/retry-16px.svg";
import { _t } from "../../../../languageHandler";
import { useTimeoutToggle } from "../../../../hooks/useTimeoutToggle";
import { ErrorMessage } from "../../ErrorMessage";
@@ -60,7 +60,7 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
<Tooltip description={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
<RestartIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}
</AccessibleButton>
</Tooltip>

View File

@@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
import React, { ReactNode } from "react";
import { Tooltip } from "@vector-im/compound-web";
import { RestartIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../../languageHandler";
import AccessibleButton from "../../../views/elements/AccessibleButton";
import { Icon as RetryIcon } from "../../../../../res/img/compound/retry-16px.svg";
import { Icon as EmailPromptIcon } from "../../../../../res/img/element-icons/email-prompt.svg";
import { useTimeoutToggle } from "../../../../hooks/useTimeoutToggle";
import { ErrorMessage } from "../../ErrorMessage";
@@ -59,7 +59,7 @@ export const VerifyEmailModal: React.FC<Props> = ({
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
<Tooltip description={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
<RestartIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}
</AccessibleButton>
</Tooltip>

View File

@@ -7,18 +7,36 @@ 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 from "react";
import React, { ReactElement } from "react";
import SdkConfig from "../../../SdkConfig";
import { _t } from "../../../languageHandler";
export default class AuthFooter extends React.Component {
public render(): React.ReactNode {
return (
<footer className="mx_AuthFooter" role="contentinfo">
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
{_t("auth|footer_powered_by_matrix")}
</a>
</footer>
const AuthFooter = (): ReactElement => {
const brandingConfig = SdkConfig.getObject("branding");
const links = brandingConfig?.get("auth_footer_links") ?? [
{ text: "Blog", url: "https://element.io/blog" },
{ text: "Twitter", url: "https://twitter.com/element_hq" },
{ text: "GitHub", url: "https://github.com/element-hq/element-web" },
];
const authFooterLinks: JSX.Element[] = [];
for (const linkEntry of links) {
authFooterLinks.push(
<a href={linkEntry.url} key={linkEntry.text} target="_blank" rel="noreferrer noopener">
{linkEntry.text}
</a>,
);
}
}
return (
<footer className="mx_AuthFooter" role="contentinfo">
{authFooterLinks}
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
{_t("powered_by_matrix")}
</a>
</footer>
);
};
export default AuthFooter;

View File

@@ -1,5 +1,6 @@
/*
Copyright 2019-2024 New Vector Ltd.
Copyright 2015, 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
@@ -7,8 +8,17 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import SdkConfig from "../../../SdkConfig";
export default class AuthHeaderLogo extends React.PureComponent {
public render(): React.ReactNode {
return <aside className="mx_AuthHeaderLogo">Matrix</aside>;
public render(): React.ReactElement {
const brandingConfig = SdkConfig.getObject("branding");
const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg";
return (
<aside className="mx_AuthHeaderLogo">
<img src={logoUrl} alt="Element" />
</aside>
);
}
}

View File

@@ -7,15 +7,69 @@ 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, { ReactNode } from "react";
import React from "react";
import SdkConfig from "../../../SdkConfig";
import AuthFooter from "./AuthFooter";
export default class AuthPage extends React.PureComponent<{ children: ReactNode }> {
public render(): React.ReactNode {
export default class AuthPage extends React.PureComponent<React.PropsWithChildren> {
private static welcomeBackgroundUrl?: string;
// cache the url as a static to prevent it changing without refreshing
private static getWelcomeBackgroundUrl(): string {
if (AuthPage.welcomeBackgroundUrl) return AuthPage.welcomeBackgroundUrl;
const brandingConfig = SdkConfig.getObject("branding");
AuthPage.welcomeBackgroundUrl = "themes/element/img/backgrounds/lake.jpg";
const configuredUrl = brandingConfig?.get("welcome_background_url");
if (configuredUrl) {
if (Array.isArray(configuredUrl)) {
const index = Math.floor(Math.random() * configuredUrl.length);
AuthPage.welcomeBackgroundUrl = configuredUrl[index];
} else {
AuthPage.welcomeBackgroundUrl = configuredUrl;
}
}
return AuthPage.welcomeBackgroundUrl;
}
public render(): React.ReactElement {
const pageStyle = {
background: `center/cover fixed url(${AuthPage.getWelcomeBackgroundUrl()})`,
};
const modalStyle: React.CSSProperties = {
position: "relative",
background: "initial",
};
const blurStyle: React.CSSProperties = {
position: "absolute",
top: 0,
right: 0,
bottom: 0,
left: 0,
filter: "blur(40px)",
background: pageStyle.background,
};
const modalContentStyle: React.CSSProperties = {
display: "flex",
zIndex: 1,
background: "rgba(255, 255, 255, 0.59)",
borderRadius: "8px",
};
return (
<div className="mx_AuthPage">
<div className="mx_AuthPage_modal">{this.props.children}</div>
<div className="mx_AuthPage" style={pageStyle}>
<div className="mx_AuthPage_modal" style={modalStyle}>
<div className="mx_AuthPage_modalBlur" style={blurStyle} />
<div className="mx_AuthPage_modalContent" style={modalContentStyle}>
{this.props.children}
</div>
</div>
<AuthFooter />
</div>
);

View File

@@ -1,41 +0,0 @@
/*
Copyright 2019-2024 New Vector Ltd.
Copyright 2015, 2016 OpenMarket Ltd
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, { ReactElement } from "react";
import SdkConfig from "../../../SdkConfig";
import { _t } from "../../../languageHandler";
const VectorAuthFooter = (): ReactElement => {
const brandingConfig = SdkConfig.getObject("branding");
const links = brandingConfig?.get("auth_footer_links") ?? [
{ text: "Blog", url: "https://element.io/blog" },
{ text: "Twitter", url: "https://twitter.com/element_hq" },
{ text: "GitHub", url: "https://github.com/element-hq/element-web" },
];
const authFooterLinks: JSX.Element[] = [];
for (const linkEntry of links) {
authFooterLinks.push(
<a href={linkEntry.url} key={linkEntry.text} target="_blank" rel="noreferrer noopener">
{linkEntry.text}
</a>,
);
}
return (
<footer className="mx_AuthFooter" role="contentinfo">
{authFooterLinks}
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
{_t("powered_by_matrix")}
</a>
</footer>
);
};
export default VectorAuthFooter;

View File

@@ -1,24 +0,0 @@
/*
Copyright 2019-2024 New Vector Ltd.
Copyright 2015, 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import * as React from "react";
import SdkConfig from "../../../SdkConfig";
export default class VectorAuthHeaderLogo extends React.PureComponent {
public render(): React.ReactElement {
const brandingConfig = SdkConfig.getObject("branding");
const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg";
return (
<aside className="mx_AuthHeaderLogo">
<img src={logoUrl} alt="Element" />
</aside>
);
}
}

View File

@@ -1,75 +0,0 @@
/*
Copyright 2019-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.
*/
import * as React from "react";
import SdkConfig from "../../../SdkConfig";
import VectorAuthFooter from "./VectorAuthFooter";
export default class VectorAuthPage extends React.PureComponent<React.PropsWithChildren> {
private static welcomeBackgroundUrl?: string;
// cache the url as a static to prevent it changing without refreshing
private static getWelcomeBackgroundUrl(): string {
if (VectorAuthPage.welcomeBackgroundUrl) return VectorAuthPage.welcomeBackgroundUrl;
const brandingConfig = SdkConfig.getObject("branding");
VectorAuthPage.welcomeBackgroundUrl = "themes/element/img/backgrounds/lake.jpg";
const configuredUrl = brandingConfig?.get("welcome_background_url");
if (configuredUrl) {
if (Array.isArray(configuredUrl)) {
const index = Math.floor(Math.random() * configuredUrl.length);
VectorAuthPage.welcomeBackgroundUrl = configuredUrl[index];
} else {
VectorAuthPage.welcomeBackgroundUrl = configuredUrl;
}
}
return VectorAuthPage.welcomeBackgroundUrl;
}
public render(): React.ReactElement {
const pageStyle = {
background: `center/cover fixed url(${VectorAuthPage.getWelcomeBackgroundUrl()})`,
};
const modalStyle: React.CSSProperties = {
position: "relative",
background: "initial",
};
const blurStyle: React.CSSProperties = {
position: "absolute",
top: 0,
right: 0,
bottom: 0,
left: 0,
filter: "blur(40px)",
background: pageStyle.background,
};
const modalContentStyle: React.CSSProperties = {
display: "flex",
zIndex: 1,
background: "rgba(255, 255, 255, 0.59)",
borderRadius: "8px",
};
return (
<div className="mx_AuthPage" style={pageStyle}>
<div className="mx_AuthPage_modal" style={modalStyle}>
<div className="mx_AuthPage_modalBlur" style={blurStyle} />
<div className="mx_AuthPage_modalContent" style={modalContentStyle}>
{this.props.children}
</div>
</div>
<VectorAuthFooter />
</div>
);
}
}

View File

@@ -12,6 +12,7 @@ import { Room, EventType } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { sleep } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
@@ -34,7 +35,6 @@ import LazyRenderList from "../elements/LazyRenderList";
import { useSettingValue } from "../../../hooks/useSettings";
import { filterBoolean } from "../../../utils/arrays";
import { NonEmptyArray } from "../../../@types/common";
import WarningBadgeSvg from "../../../../res/img/element-icons/warning-badge.svg";
// These values match CSS
const ROW_HEIGHT = 32 + 12;
@@ -229,7 +229,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
if (error) {
footer = (
<>
<img src={WarningBadgeSvg} height="24" width="24" alt="" />
<ErrorIcon height="24px" width="24px" />
<span className="mx_AddExistingToSpaceDialog_error">
<div className="mx_AddExistingToSpaceDialog_errorHeading">

View File

@@ -22,6 +22,7 @@ import {
WidgetApiFromWidgetAction,
WidgetKind,
} from "matrix-widget-api";
import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import BaseDialog from "./BaseDialog";
import { _t, getUserLanguage } from "../../../languageHandler";
@@ -33,7 +34,6 @@ import { arrayFastClone } from "../../../utils/arrays";
import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
import { ELEMENT_CLIENT_ID } from "../../../identifiers";
import SettingsStore from "../../../settings/SettingsStore";
import WarningBadgeSvg from "../../../../res/img/element-icons/warning-badge.svg";
interface IProps {
widgetDefinition: IModalWidgetOpenRequestData;
@@ -186,7 +186,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
onFinished={this.props.onFinished}
>
<div className="mx_ModalWidgetDialog_warning">
<img src={WarningBadgeSvg} height="16" width="16" alt="" />
<ErrorIcon width="16px" height="16px" />
{_t("widget|modal_data_warning", {
widgetDomain: parsed.hostname,
})}

View File

@@ -8,9 +8,9 @@ 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, { createRef, CSSProperties } from "react";
import React, { createRef, CSSProperties, useRef, useState } from "react";
import FocusLock from "react-focus-lock";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MatrixEvent, parseErrorResponse } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../languageHandler";
import MemberAvatar from "../avatars/MemberAvatar";
@@ -30,6 +30,9 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { presentableTextForFile } from "../../../utils/FileUtils";
import AccessibleButton from "./AccessibleButton";
import Modal from "../../../Modal";
import ErrorDialog from "../dialogs/ErrorDialog";
import { FileDownloader } from "../../../utils/FileDownloader";
// Max scale to keep gaps around the image
const MAX_SCALE = 0.95;
@@ -309,15 +312,6 @@ export default class ImageView extends React.Component<IProps, IState> {
this.setZoomAndRotation(cur + 90);
};
private onDownloadClick = (): void => {
const a = document.createElement("a");
a.href = this.props.src;
if (this.props.name) a.download = this.props.name;
a.target = "_blank";
a.rel = "noreferrer noopener";
a.click();
};
private onOpenContextMenu = (): void => {
this.setState({
contextMenuDisplayed: true,
@@ -555,11 +549,7 @@ export default class ImageView extends React.Component<IProps, IState> {
title={_t("lightbox|rotate_right")}
onClick={this.onRotateClockwiseClick}
/>
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_download"
title={_t("action|download")}
onClick={this.onDownloadClick}
/>
<DownloadButton url={this.props.src} fileName={this.props.name} />
{contextMenuButton}
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_close"
@@ -591,3 +581,61 @@ export default class ImageView extends React.Component<IProps, IState> {
);
}
}
function DownloadButton({ url, fileName }: { url: string; fileName?: string }): JSX.Element {
const downloader = useRef(new FileDownloader()).current;
const [loading, setLoading] = useState(false);
const blobRef = useRef<Blob>();
function showError(e: unknown): void {
Modal.createDialog(ErrorDialog, {
title: _t("timeline|download_failed"),
description: (
<>
<div>{_t("timeline|download_failed_description")}</div>
<div>{e instanceof Error ? e.toString() : ""}</div>
</>
),
});
setLoading(false);
}
const onDownloadClick = async (): Promise<void> => {
try {
if (loading) return;
setLoading(true);
if (blobRef.current) {
// Cheat and trigger a download, again.
return downloadBlob(blobRef.current);
}
const res = await fetch(url);
if (!res.ok) {
throw parseErrorResponse(res, await res.text());
}
const blob = await res.blob();
blobRef.current = blob;
await downloadBlob(blob);
} catch (e) {
showError(e);
}
};
async function downloadBlob(blob: Blob): Promise<void> {
await downloader.download({
blob,
name: fileName ?? _t("common|image"),
});
setLoading(false);
}
return (
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_download"
title={loading ? _t("timeline|download_action_downloading") : _t("action|download")}
onClick={onDownloadClick}
disabled={loading}
/>
);
}

View File

@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { MutableRefObject, ReactNode, StrictMode } from "react";
import ReactDOM from "react-dom";
import { createRoot, Root } from "react-dom/client";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { TooltipProvider } from "@vector-im/compound-web";
@@ -24,7 +24,7 @@ export const getPersistKey = (appId: string): string => "widget_" + appId;
// We contain all persisted elements within a master container to allow them all to be within the same
// CSS stacking context, and thus be able to control their z-indexes relative to each other.
function getOrCreateMasterContainer(): HTMLDivElement {
let container = getContainer("mx_PersistedElement_container");
let container = document.getElementById("mx_PersistedElement_container") as HTMLDivElement;
if (!container) {
container = document.createElement("div");
container.id = "mx_PersistedElement_container";
@@ -34,18 +34,10 @@ function getOrCreateMasterContainer(): HTMLDivElement {
return container;
}
function getContainer(containerId: string): HTMLDivElement {
return document.getElementById(containerId) as HTMLDivElement;
}
function getOrCreateContainer(containerId: string): HTMLDivElement {
let container = getContainer(containerId);
if (!container) {
container = document.createElement("div");
container.id = containerId;
getOrCreateMasterContainer().appendChild(container);
}
const container = document.createElement("div");
container.id = containerId;
getOrCreateMasterContainer().appendChild(container);
return container;
}
@@ -83,6 +75,8 @@ export default class PersistedElement extends React.Component<IProps> {
private childContainer?: HTMLDivElement;
private child?: HTMLDivElement;
private static rootMap: Record<string, [root: Root, container: Element]> = {};
public constructor(props: IProps) {
super(props);
@@ -99,14 +93,16 @@ export default class PersistedElement extends React.Component<IProps> {
* @param {string} persistKey Key used to uniquely identify this PersistedElement
*/
public static destroyElement(persistKey: string): void {
const container = getContainer("mx_persistedElement_" + persistKey);
if (container) {
container.remove();
const pair = PersistedElement.rootMap[persistKey];
if (pair) {
pair[0].unmount();
pair[1].remove();
}
delete PersistedElement.rootMap[persistKey];
}
public static isMounted(persistKey: string): boolean {
return Boolean(getContainer("mx_persistedElement_" + persistKey));
return Boolean(PersistedElement.rootMap[persistKey]);
}
private collectChildContainer = (ref: HTMLDivElement): void => {
@@ -179,7 +175,14 @@ export default class PersistedElement extends React.Component<IProps> {
</StrictMode>
);
ReactDOM.render(content, getOrCreateContainer("mx_persistedElement_" + this.props.persistKey));
let rootPair = PersistedElement.rootMap[this.props.persistKey];
if (!rootPair) {
const container = getOrCreateContainer("mx_persistedElement_" + this.props.persistKey);
const root = createRoot(container);
rootPair = [root, container];
PersistedElement.rootMap[this.props.persistKey] = rootPair;
}
rootPair[0].render(content);
}
private updateChildVisibility(child?: HTMLDivElement, visible = false): void {

View File

@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import classNames from "classnames";
import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Icon as WarningBadge } from "../../../../res/img/element-icons/warning-badge.svg";
import { _t } from "../../../languageHandler";
import { getLocationShareErrorMessage, LocationShareError } from "../../../utils/location";
import AccessibleButton from "../elements/AccessibleButton";
@@ -29,7 +29,7 @@ export const MapError: React.FC<MapErrorProps> = ({ error, isMinimised, classNam
className={classNames("mx_MapError", className, { mx_MapError_isMinimised: isMinimised })}
onClick={onClick}
>
<WarningBadge className="mx_MapError_icon" />
<ErrorIcon className="mx_MapError_icon" />
<Heading className="mx_MapError_heading" size="3">
{_t("location_sharing|failed_load_map")}
</Heading>

View File

@@ -10,7 +10,7 @@ import classNames from "classnames";
import React, { forwardRef, ForwardRefExoticComponent, useContext } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";
import { WarningIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { BlockIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import { IBodyProps } from "./IBodyProps";
@@ -41,7 +41,7 @@ function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined):
case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
return (
<span>
<WarningIcon className="mx_Icon mx_Icon_16" />
<BlockIcon className="mx_Icon mx_Icon_16" />
{_t("timeline|decryption_failure|sender_identity_previously_verified")}
</span>
);
@@ -49,7 +49,12 @@ function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined):
case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
// TODO: event should be hidden instead of showing this error.
// To be revisited as part of https://github.com/element-hq/element-meta/issues/2449
return _t("timeline|decryption_failure|sender_unsigned_device");
return (
<span>
<BlockIcon className="mx_Icon mx_Icon_16" />
{_t("timeline|decryption_failure|sender_unsigned_device")}
</span>
);
}
return _t("timeline|decryption_failure|unable_to_decrypt");
}
@@ -58,7 +63,8 @@ function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined):
function errorClassName(mxEvent: MatrixEvent): string | null {
switch (mxEvent.decryptionFailureReason) {
case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
return "mx_DecryptionFailureVerifiedIdentityChanged";
case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
return "mx_DecryptionFailureSenderTrustRequirement";
default:
return null;

View File

@@ -13,8 +13,8 @@ import classNames from "classnames";
import * as HtmlUtils from "../../../HtmlUtils";
import { editBodyDiffToHtml } from "../../../utils/MessageDiffUtils";
import { formatTime } from "../../../DateUtils";
import { pillifyLinks, unmountPills } from "../../../utils/pillify";
import { tooltipifyLinks, unmountTooltips } from "../../../utils/tooltipify";
import { pillifyLinks } from "../../../utils/pillify";
import { tooltipifyLinks } from "../../../utils/tooltipify";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import RedactedBody from "./RedactedBody";
@@ -23,6 +23,7 @@ import ConfirmAndWaitRedactDialog from "../dialogs/ConfirmAndWaitRedactDialog";
import ViewSource from "../../structures/ViewSource";
import SettingsStore from "../../../settings/SettingsStore";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { ReactRootManager } from "../../../utils/react";
function getReplacedContent(event: MatrixEvent): IContent {
const originalContent = event.getOriginalContent();
@@ -47,8 +48,8 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
public declare context: React.ContextType<typeof MatrixClientContext>;
private content = createRef<HTMLDivElement>();
private pills: Element[] = [];
private tooltips: Element[] = [];
private pills = new ReactRootManager();
private tooltips = new ReactRootManager();
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
super(props, context);
@@ -103,7 +104,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
private tooltipifyLinks(): void {
// not present for redacted events
if (this.content.current) {
tooltipifyLinks(this.content.current.children, this.pills, this.tooltips);
tooltipifyLinks(this.content.current.children, this.pills.elements, this.tooltips);
}
}
@@ -113,8 +114,8 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
}
public componentWillUnmount(): void {
unmountPills(this.pills);
unmountTooltips(this.tooltips);
this.pills.unmount();
this.tooltips.unmount();
const event = this.props.mxEvent;
event.localRedactionEvent()?.off(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
}

View File

@@ -27,11 +27,11 @@ import {
OverflowHorizontalIcon,
ReplyIcon,
DeleteIcon,
RestartIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { Icon as EditIcon } from "../../../../res/img/element-icons/room/message-bar/edit.svg";
import { Icon as EmojiIcon } from "../../../../res/img/element-icons/room/message-bar/emoji.svg";
import { Icon as ResendIcon } from "../../../../res/img/element-icons/retry.svg";
import { Icon as ThreadIcon } from "../../../../res/img/element-icons/message/thread.svg";
import { Icon as ExpandMessageIcon } from "../../../../res/img/element-icons/expand-message.svg";
import { Icon as CollapseMessageIcon } from "../../../../res/img/element-icons/collapse-message.svg";
@@ -475,14 +475,14 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
0,
0,
<RovingAccessibleButton
className="mx_MessageActionBar_iconButton"
className="mx_MessageActionBar_iconButton mx_MessageActionBar_retryButton"
title={_t("action|retry")}
onClick={this.onResendClick}
onContextMenu={this.onResendClick}
key="resend"
placement="left"
>
<ResendIcon />
<RestartIcon />
</RovingAccessibleButton>,
);

View File

@@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { createRef, SyntheticEvent, MouseEvent, StrictMode } from "react";
import ReactDOM from "react-dom";
import { MsgType } from "matrix-js-sdk/src/matrix";
import { TooltipProvider } from "@vector-im/compound-web";
@@ -17,8 +16,8 @@ import Modal from "../../../Modal";
import dis from "../../../dispatcher/dispatcher";
import { _t } from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import { pillifyLinks, unmountPills } from "../../../utils/pillify";
import { tooltipifyLinks, unmountTooltips } from "../../../utils/tooltipify";
import { pillifyLinks } from "../../../utils/pillify";
import { tooltipifyLinks } from "../../../utils/tooltipify";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import { isPermalinkHost, tryTransformPermalinkToLocalHref } from "../../../utils/permalinks/Permalinks";
import { Action } from "../../../dispatcher/actions";
@@ -36,6 +35,7 @@ import { EditWysiwygComposer } from "../rooms/wysiwyg_composer";
import { IEventTileOps } from "../rooms/EventTile";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import CodeBlock from "./CodeBlock";
import { ReactRootManager } from "../../../utils/react";
interface IState {
// the URLs (if any) to be previewed with a LinkPreviewWidget inside this TextualBody.
@@ -48,9 +48,9 @@ interface IState {
export default class TextualBody extends React.Component<IBodyProps, IState> {
private readonly contentRef = createRef<HTMLDivElement>();
private pills: Element[] = [];
private tooltips: Element[] = [];
private reactRoots: Element[] = [];
private pills = new ReactRootManager();
private tooltips = new ReactRootManager();
private reactRoots = new ReactRootManager();
private ref = createRef<HTMLDivElement>();
@@ -82,7 +82,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// tooltipifyLinks AFTER calculateUrlPreview because the DOM inside the tooltip
// container is empty before the internal component has mounted so calculateUrlPreview
// won't find any anchors
tooltipifyLinks([content], this.pills, this.tooltips);
tooltipifyLinks([content], [...this.pills.elements, ...this.reactRoots.elements], this.tooltips);
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
// Handle expansion and add buttons
@@ -113,12 +113,11 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
private wrapPreInReact(pre: HTMLPreElement): void {
const root = document.createElement("div");
root.className = "mx_EventTile_pre_container";
this.reactRoots.push(root);
// Insert containing div in place of <pre> block
pre.parentNode?.replaceChild(root, pre);
ReactDOM.render(
this.reactRoots.render(
<StrictMode>
<CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock>
</StrictMode>,
@@ -137,16 +136,9 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
}
public componentWillUnmount(): void {
unmountPills(this.pills);
unmountTooltips(this.tooltips);
for (const root of this.reactRoots) {
ReactDOM.unmountComponentAtNode(root);
}
this.pills = [];
this.tooltips = [];
this.reactRoots = [];
this.pills.unmount();
this.tooltips.unmount();
this.reactRoots.unmount();
}
public shouldComponentUpdate(nextProps: Readonly<IBodyProps>, nextState: Readonly<IState>): boolean {
@@ -204,7 +196,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
</StrictMode>
);
ReactDOM.render(spoiler, spoilerContainer);
this.reactRoots.render(spoiler, spoilerContainer);
node.parentNode?.replaceChild(spoilerContainer, node);
node = spoilerContainer;

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2024 New Vector Ltd.
* Copyright 2024 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, { HTMLProps, JSX, useContext, useState } from "react";
import { IContent, M_POLL_START, MatrixEvent, MatrixEventEvent, MsgType } from "matrix-js-sdk/src/matrix";
import classNames from "classnames";
import { _t } from "../../../languageHandler";
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter.ts";
/**
* The props for the {@link EventPreview} component.
*/
interface Props extends HTMLProps<HTMLSpanElement> {
/**
* The event to display the preview for
*/
mxEvent: MatrixEvent;
}
/**
* A component that displays a preview for the given event.
* Wraps both `useEventPreview` & `EventPreviewTile`.
*/
export function EventPreview({ mxEvent, className, ...props }: Props): JSX.Element | null {
const preview = useEventPreview(mxEvent);
if (!preview) return null;
return <EventPreviewTile {...props} preview={preview} className={className} />;
}
/**
* The props for the {@link EventPreviewTile} component.
*/
interface EventPreviewTileProps extends HTMLProps<HTMLSpanElement> {
/**
* The preview to display
*/
preview: Preview;
}
/**
* A component that displays a preview given the output from `useEventPreview`.
*/
export function EventPreviewTile({
preview: [preview, prefix],
className,
...props
}: EventPreviewTileProps): JSX.Element | null {
const classes = classNames("mx_EventPreview", className);
if (!prefix)
return (
<span {...props} className={classes} title={preview}>
{preview}
</span>
);
return (
<span {...props} className={classes}>
{_t(
"event_preview|preview",
{
prefix,
preview,
},
{
bold: (sub) => <span className="mx_EventPreview_prefix">{sub}</span>,
},
)}
</span>
);
}
type Preview = [preview: string, prefix: string | null];
/**
* Hooks to generate a preview for the event.
* @param mxEvent
*/
export function useEventPreview(mxEvent: MatrixEvent | undefined): Preview | null {
const cli = useContext(MatrixClientContext);
// track the content as a means to regenerate the preview upon edits & decryption
const [content, setContent] = useState<IContent | undefined>(mxEvent?.getContent());
useTypedEventEmitter(mxEvent ?? undefined, MatrixEventEvent.Replaced, () => {
setContent(mxEvent!.getContent());
});
const awaitDecryption = mxEvent?.shouldAttemptDecryption() || mxEvent?.isBeingDecrypted();
useTypedEventEmitter(awaitDecryption ? (mxEvent ?? undefined) : undefined, MatrixEventEvent.Decrypted, () => {
setContent(mxEvent!.getContent());
});
return useAsyncMemo(
async () => {
if (!mxEvent || mxEvent.isRedacted() || mxEvent.isDecryptionFailure()) return null;
await cli.decryptEventIfNeeded(mxEvent);
return [
MessagePreviewStore.instance.generatePreviewForEvent(mxEvent),
getPreviewPrefix(mxEvent.getType(), content?.msgtype as MsgType),
];
},
[mxEvent, content],
null,
);
}
/**
* Get the prefix for the preview based on the type and the message type.
* @param type
* @param msgType
*/
function getPreviewPrefix(type: string, msgType: MsgType): string | null {
switch (type) {
case M_POLL_START.name:
return _t("event_preview|prefix|poll");
default:
}
switch (msgType) {
case MsgType.Audio:
return _t("event_preview|prefix|audio");
case MsgType.Image:
return _t("event_preview|prefix|image");
case MsgType.Video:
return _t("event_preview|prefix|video");
case MsgType.File:
return _t("event_preview|prefix|file");
default:
return null;
}
}

View File

@@ -28,6 +28,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { CallErrorCode } from "matrix-js-sdk/src/webrtc/call";
import {
CryptoEvent,
DecryptionFailureCode,
EventShieldColour,
EventShieldReason,
UserVerificationStatus,
@@ -60,7 +61,6 @@ import { IReadReceiptPosition } from "./ReadReceiptMarker";
import MessageActionBar from "../messages/MessageActionBar";
import ReactionsRow from "../messages/ReactionsRow";
import { getEventDisplayInfo } from "../../../utils/EventRenderingUtils";
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
import { ButtonEvent } from "../elements/AccessibleButton";
@@ -82,6 +82,7 @@ import { EventTileThreadToolbar } from "./EventTile/EventTileThreadToolbar";
import { getLateEventInfo } from "../../structures/grouper/LateEventGrouper";
import PinningUtils from "../../../utils/PinningUtils";
import { PinnedMessageBadge } from "../messages/PinnedMessageBadge";
import { EventPreview } from "./EventPreview";
export type GetRelationsForEvent = (
eventId: string,
@@ -719,7 +720,14 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
// event could not be decrypted
if (ev.isDecryptionFailure()) {
return <E2ePadlockDecryptionFailure />;
switch (ev.decryptionFailureReason) {
// These two errors get icons from DecryptionFailureBody, so we hide the padlock icon
case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
return null;
default:
return <E2ePadlockDecryptionFailure />;
}
}
if (this.state.shieldColour !== EventShieldColour.NONE) {
@@ -1333,7 +1341,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
) : this.props.mxEvent.isDecryptionFailure() ? (
<DecryptionFailureBody mxEvent={this.props.mxEvent} />
) : (
MessagePreviewStore.instance.generatePreviewForEvent(this.props.mxEvent)
<EventPreview mxEvent={this.props.mxEvent} />
)}
</div>
{this.renderThreadPanelSummary()}

View File

@@ -6,10 +6,10 @@
* Please see LICENSE files in the repository root for full details.
*/
import React, { JSX, useEffect, useMemo, useState } from "react";
import React, { JSX, useEffect, useState } from "react";
import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin-solid";
import { Button } from "@vector-im/compound-web";
import { M_POLL_START, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix";
import { Room } from "matrix-js-sdk/src/matrix";
import classNames from "classnames";
import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
@@ -19,12 +19,12 @@ import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePha
import { useEventEmitter } from "../../../hooks/useEventEmitter";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
import dis from "../../../dispatcher/dispatcher";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../../../dispatcher/actions";
import MessageEvent from "../messages/MessageEvent";
import PosthogTrackers from "../../../PosthogTrackers.ts";
import { EventPreview } from "./EventPreview.tsx";
/**
* The props for the {@link PinnedMessageBanner} component.
@@ -105,7 +105,11 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
)}
</div>
)}
<EventPreview pinnedEvent={pinnedEvent} />
<EventPreview
mxEvent={pinnedEvent}
className="mx_PinnedMessageBanner_message"
data-testid="banner-message"
/>
{/* In case of redacted event, we want to display the nice sentence of the message event like in the timeline or in the pinned message list */}
{shouldUseMessageEvent && (
<div className="mx_PinnedMessageBanner_redactedMessage">
@@ -124,84 +128,6 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
);
}
/**
* The props for the {@link EventPreview} component.
*/
interface EventPreviewProps {
/**
* The pinned event to display the preview for
*/
pinnedEvent: MatrixEvent;
}
/**
* A component that displays a preview for the pinned event.
*/
function EventPreview({ pinnedEvent }: EventPreviewProps): JSX.Element | null {
const preview = useEventPreview(pinnedEvent);
if (!preview) return null;
const prefix = getPreviewPrefix(pinnedEvent.getType(), pinnedEvent.getContent().msgtype as MsgType);
if (!prefix)
return (
<span className="mx_PinnedMessageBanner_message" data-testid="banner-message">
{preview}
</span>
);
return (
<span className="mx_PinnedMessageBanner_message" data-testid="banner-message">
{_t(
"room|pinned_message_banner|preview",
{
prefix,
preview,
},
{
bold: (sub) => <span className="mx_PinnedMessageBanner_prefix">{sub}</span>,
},
)}
</span>
);
}
/**
* Hooks to generate a preview for the pinned event.
* @param pinnedEvent
*/
function useEventPreview(pinnedEvent: MatrixEvent | null): string | null {
return useMemo(() => {
if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null;
return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent);
}, [pinnedEvent]);
}
/**
* Get the prefix for the preview based on the type and the message type.
* @param type
* @param msgType
*/
function getPreviewPrefix(type: string, msgType: MsgType): string | null {
switch (type) {
case M_POLL_START.name:
return _t("room|pinned_message_banner|prefix|poll");
default:
}
switch (msgType) {
case MsgType.Audio:
return _t("room|pinned_message_banner|prefix|audio");
case MsgType.Image:
return _t("room|pinned_message_banner|prefix|image");
case MsgType.Video:
return _t("room|pinned_message_banner|prefix|video");
case MsgType.File:
return _t("room|pinned_message_banner|prefix|file");
default:
return null;
}
}
const MAX_INDICATORS = 3;
/**

View File

@@ -6,8 +6,8 @@ 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, { useContext, useState } from "react";
import { Thread, ThreadEvent, IContent, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
import React, { useContext } from "react";
import { Thread, ThreadEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { IndicatorIcon } from "@vector-im/compound-web";
import ThreadIconSolid from "@vector-im/compound-design-tokens/assets/web/icons/threads-solid";
@@ -15,17 +15,15 @@ import { _t } from "../../../languageHandler";
import { CardContext } from "../right_panel/context";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import PosthogTrackers from "../../../PosthogTrackers";
import { useTypedEventEmitter, useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
import RoomContext from "../../../contexts/RoomContext";
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
import MemberAvatar from "../avatars/MemberAvatar";
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { Action } from "../../../dispatcher/actions";
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications";
import { notificationLevelToIndicator } from "../../../utils/notifications";
import { EventPreviewTile, useEventPreview } from "./EventPreview.tsx";
interface IProps {
mxEvent: MatrixEvent;
@@ -75,24 +73,9 @@ interface IPreviewProps {
}
export const ThreadMessagePreview: React.FC<IPreviewProps> = ({ thread, showDisplayname = false }) => {
const cli = useContext(MatrixClientContext);
const lastReply = useTypedEventEmitterState(thread, ThreadEvent.Update, () => thread.replyToEvent) ?? undefined;
// track the content as a means to regenerate the thread message preview upon edits & decryption
const [content, setContent] = useState<IContent | undefined>(lastReply?.getContent());
useTypedEventEmitter(lastReply, MatrixEventEvent.Replaced, () => {
setContent(lastReply!.getContent());
});
const awaitDecryption = lastReply?.shouldAttemptDecryption() || lastReply?.isBeingDecrypted();
useTypedEventEmitter(awaitDecryption ? lastReply : undefined, MatrixEventEvent.Decrypted, () => {
setContent(lastReply!.getContent());
});
const preview = useEventPreview(lastReply);
const preview = useAsyncMemo(async (): Promise<string | undefined> => {
if (!lastReply) return;
await cli.decryptEventIfNeeded(lastReply);
return MessagePreviewStore.instance.generatePreviewForEvent(lastReply);
}, [lastReply, content]);
if (!preview || !lastReply) {
return null;
}
@@ -114,14 +97,10 @@ export const ThreadMessagePreview: React.FC<IPreviewProps> = ({ thread, showDisp
className="mx_ThreadSummary_content mx_DecryptionFailureBody"
title={_t("timeline|decryption_failure|unable_to_decrypt")}
>
<span className="mx_ThreadSummary_message-preview">
{_t("timeline|decryption_failure|unable_to_decrypt")}
</span>
{_t("timeline|decryption_failure|unable_to_decrypt")}
</div>
) : (
<div className="mx_ThreadSummary_content" title={preview}>
<span className="mx_ThreadSummary_message-preview">{preview}</span>
</div>
<EventPreviewTile preview={preview} className="mx_ThreadSummary_content" />
)}
</>
);

View File

@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
import classNames from "classnames";
import React, { ComponentProps } from "react";
import { ChevronDownIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Icon as CaretIcon } from "../../../../../res/img/feather-customised/dropdown-arrow.svg";
import { _t } from "../../../../languageHandler";
import AccessibleButton from "../../elements/AccessibleButton";
@@ -38,7 +38,7 @@ export const DeviceExpandDetailsButton = <T extends keyof JSX.IntrinsicElements>
})}
onClick={onClick}
>
<CaretIcon className="mx_DeviceExpandDetailsButton_icon" />
<ChevronDownIcon className="mx_DeviceExpandDetailsButton_icon" />
</AccessibleButton>
);
};

View File

@@ -51,7 +51,6 @@ export const DiscoverySettings: React.FC = () => {
const [emails, setEmails] = useState<ThirdPartyIdentifier[]>([]);
const [phoneNumbers, setPhoneNumbers] = useState<ThirdPartyIdentifier[]>([]);
const [idServerName, setIdServerName] = useState<string | undefined>(abbreviateUrl(client.getIdentityServerUrl()));
const [canMake3pidChanges, setCanMake3pidChanges] = useState<boolean>(false);
const [requiredPolicyInfo, setRequiredPolicyInfo] = useState<RequiredPolicyInfo>({
// This object is passed along to a component for handling
@@ -88,11 +87,6 @@ export const DiscoverySettings: React.FC = () => {
try {
await getThreepidState();
const capabilities = await client.getCapabilities();
setCanMake3pidChanges(
!capabilities["m.3pid_changes"] || capabilities["m.3pid_changes"].enabled === true,
);
// By starting the terms flow we get the logic for checking which terms the user has signed
// for free. So we might as well use that for our own purposes.
const idServerUrl = client.getIdentityServerUrl();
@@ -166,7 +160,7 @@ export const DiscoverySettings: React.FC = () => {
medium={ThreepidMedium.Email}
threepids={emails}
onChange={getThreepidState}
disabled={!canMake3pidChanges}
disabled={!hasTerms}
isLoading={isLoadingThreepids}
/>
</SettingsSubsection>
@@ -180,7 +174,7 @@ export const DiscoverySettings: React.FC = () => {
medium={ThreepidMedium.Phone}
threepids={phoneNumbers}
onChange={getThreepidState}
disabled={!canMake3pidChanges}
disabled={!hasTerms}
isLoading={isLoadingThreepids}
/>
</SettingsSubsection>

View File

@@ -207,7 +207,6 @@
"failed_query_registration_methods": "Nepovedlo se načíst podporované způsoby přihlášení.",
"failed_soft_logout_auth": "Nepovedlo se autentifikovat",
"failed_soft_logout_homeserver": "Kvůli problémům s domovským server se nepovedlo autentifikovat znovu",
"footer_powered_by_matrix": "používá protokol Matrix",
"forgot_password_email_invalid": "E-mailová adresa se nezdá být platná.",
"forgot_password_email_required": "Musíte zadat e-mailovou adresu spojenou s vaším účtem.",
"forgot_password_prompt": "Zapomněli jste heslo?",
@@ -3525,7 +3524,6 @@
"truncated_list_n_more": {
"other": "A %(count)s dalších..."
},
"unknown_device": "Neznámé zařízení",
"unsupported_server_description": "Tento server používá starší verzi Matrix. Chcete-li používat %(brand)s bez možných problémů, aktualizujte Matrixu na %(version)s .",
"unsupported_server_title": "Váš server není podporován",
"update": {

View File

@@ -203,7 +203,6 @@
"failed_query_registration_methods": "Konnte unterstützte Registrierungsmethoden nicht abrufen.",
"failed_soft_logout_auth": "Erneute Authentifizierung fehlgeschlagen",
"failed_soft_logout_homeserver": "Erneute Authentifizierung aufgrund eines Problems des Heim-Servers fehlgeschlagen",
"footer_powered_by_matrix": "Betrieben mit Matrix",
"forgot_password_email_invalid": "E-Mail-Adresse scheint ungültig zu sein.",
"forgot_password_email_required": "Es muss die mit dem Benutzerkonto verbundene E-Mail-Adresse eingegeben werden.",
"forgot_password_prompt": "Passwort vergessen?",
@@ -3500,7 +3499,6 @@
"truncated_list_n_more": {
"other": "Und %(count)s weitere …"
},
"unknown_device": "Unbekanntes Gerät",
"unsupported_server_description": "Dieser Server nutzt eine ältere Matrix-Version. Aktualisiere auf Matrix %(version)s, um %(brand)s fehlerfrei nutzen zu können.",
"unsupported_server_title": "Dein Server wird nicht unterstützt",
"update": {

View File

@@ -182,7 +182,6 @@
"failed_query_registration_methods": "Αδυναμία λήψης των υποστηριζόμενων μεθόδων εγγραφής.",
"failed_soft_logout_auth": "Απέτυχε ο εκ νέου έλεγχος ταυτότητας",
"failed_soft_logout_homeserver": "Απέτυχε ο εκ νέου έλεγχος ταυτότητας λόγω προβλήματος με τον κεντρικό διακομιστή",
"footer_powered_by_matrix": "λειτουργεί με το Matrix",
"forgot_password_email_invalid": "Η διεύθυνση email δε φαίνεται να είναι έγκυρη.",
"forgot_password_email_required": "Πρέπει να εισηχθεί η διεύθυνση ηλ. αλληλογραφίας που είναι συνδεδεμένη με τον λογαριασμό σας.",
"forgot_password_prompt": "Ξεχάσετε τον κωδικό σας;",
@@ -2829,7 +2828,6 @@
"truncated_list_n_more": {
"other": "Και %(count)s ακόμα..."
},
"unknown_device": "Άγνωστη συσκευή",
"update": {
"changelog": "Αλλαγές",
"check_action": "Έλεγχος για ενημέρωση",

View File

@@ -209,7 +209,6 @@
"failed_query_registration_methods": "Unable to query for supported registration methods.",
"failed_soft_logout_auth": "Failed to re-authenticate",
"failed_soft_logout_homeserver": "Failed to re-authenticate due to a homeserver problem",
"footer_powered_by_matrix": "powered by Matrix",
"forgot_password_email_invalid": "The email address doesn't appear to be valid.",
"forgot_password_email_required": "The email address linked to your account must be entered.",
"forgot_password_prompt": "Forgotten your password?",
@@ -1110,7 +1109,15 @@
"you": "You reacted %(reaction)s to %(message)s"
},
"m.sticker": "%(senderName)s: %(stickerName)s",
"m.text": "%(senderName)s: %(message)s"
"m.text": "%(senderName)s: %(message)s",
"prefix": {
"audio": "Audio",
"file": "File",
"image": "Image",
"poll": "Poll",
"video": "Video"
},
"preview": "<bold>%(prefix)s:</bold> %(preview)s"
},
"export_chat": {
"cancelled": "Export Cancelled",
@@ -2037,14 +2044,6 @@
"button_view_all": "View all",
"description": "This room has pinned messages. Click to view them.",
"go_to_message": "View the pinned message in the timeline.",
"prefix": {
"audio": "Audio",
"file": "File",
"image": "Image",
"poll": "Poll",
"video": "Video"
},
"preview": "<bold>%(prefix)s:</bold> %(preview)s",
"title": "<bold>%(index)s of %(length)s</bold> Pinned messages"
},
"read_topic": "Click to read topic",
@@ -3266,8 +3265,8 @@
"historical_event_no_key_backup": "Historical messages are not available on this device",
"historical_event_unverified_device": "You need to verify this device for access to historical messages",
"historical_event_user_not_joined": "You don't have access to this message",
"sender_identity_previously_verified": "Verified identity has changed",
"sender_unsigned_device": "Encrypted by a device not verified by its owner.",
"sender_identity_previously_verified": "Sender's verified identity has changed",
"sender_unsigned_device": "Sent from an insecure device.",
"unable_to_decrypt": "Unable to decrypt message"
},
"disambiguated_profile": "%(displayName)s (%(matrixId)s)",
@@ -3706,7 +3705,6 @@
"truncated_list_n_more": {
"other": "And %(count)s more..."
},
"unknown_device": "Unknown device",
"unsupported_browser": {
"description": "If you continue, some features may stop working and there is a risk that you may lose data in the future. Update your browser to continue using %(brand)s.",
"title": "%(brand)s does not support this browser"

View File

@@ -171,7 +171,6 @@
"failed_query_registration_methods": "Ne povas peti subtenatajn registrajn metodojn.",
"failed_soft_logout_auth": "Malsukcesis reaŭtentikigi",
"failed_soft_logout_homeserver": "Malsukcesis reaŭtentikigi pro hejmservila problemo",
"footer_powered_by_matrix": "funkciigata de Matrix",
"forgot_password_email_invalid": "La retpoŝtadreso ŝajnas ne valida.",
"forgot_password_email_required": "Vi devas enigi retpoŝtadreson ligitan al via konto.",
"forgot_password_prompt": "Ĉu vi forgesis vian pasvorton?",
@@ -2544,7 +2543,6 @@
"truncated_list_n_more": {
"other": "Kaj %(count)s pliaj…"
},
"unknown_device": "Nekonata aparato",
"update": {
"changelog": "Protokolo de ŝanĝoj",
"check_action": "Kontroli ĝisdatigojn",

View File

@@ -193,7 +193,6 @@
"failed_query_registration_methods": "No se pueden consultar los métodos de registro admitidos.",
"failed_soft_logout_auth": "No se pudo volver a autenticar",
"failed_soft_logout_homeserver": "No ha sido posible volver a autenticarse debido a un problema con el servidor base",
"footer_powered_by_matrix": "con el poder de Matrix",
"forgot_password_email_invalid": "La dirección de correo no parece ser válida.",
"forgot_password_email_required": "Debes ingresar la dirección de correo electrónico vinculada a tu cuenta.",
"forgot_password_prompt": "¿Olvidaste tu contraseña?",
@@ -3224,7 +3223,6 @@
"truncated_list_n_more": {
"other": "Y %(count)s más…"
},
"unknown_device": "Dispositivo desconocido",
"update": {
"changelog": "Registro de cambios",
"check_action": "Comprobar si hay actualizaciones",

View File

@@ -207,7 +207,6 @@
"failed_query_registration_methods": "Ei õnnestunud pärida toetatud registreerimismeetodite loendit.",
"failed_soft_logout_auth": "Uuesti autentimine ei õnnestunud",
"failed_soft_logout_homeserver": "Uuesti autentimine ei õnnestunud koduserveri vea tõttu",
"footer_powered_by_matrix": "põhineb Matrix'il",
"forgot_password_email_invalid": "See e-posti aadress ei tundu olema korrektne.",
"forgot_password_email_required": "Sa pead sisestama oma kontoga seotud e-posti aadressi.",
"forgot_password_prompt": "Kas sa unustasid oma salasõna?",
@@ -3465,7 +3464,6 @@
"truncated_list_n_more": {
"other": "Ja %(count)s muud..."
},
"unknown_device": "Tundmatu seade",
"unsupported_server_description": "See server kasutab Matrixi vanemat versiooni. Selleks, et %(brand)s'i kasutamisel vigu ei tekiks palun uuenda serverit nii, et kasutusel oleks Matrixi %(version)s.",
"unsupported_server_title": "Sinu server ei ole toetatud",
"update": {

View File

@@ -165,7 +165,6 @@
"failed_query_registration_methods": "درخواست از روش‌های پشتیبانی‌شده‌ی ثبت‌نام میسر نیست.",
"failed_soft_logout_auth": "احراز هویت مجدد موفیت‌آمیز نبود",
"failed_soft_logout_homeserver": "به دلیل مشکلی که در سرور وجود دارد ، احراز هویت مجدد انجام نشد",
"footer_powered_by_matrix": "قدرت‌یافته از ماتریکس",
"forgot_password_email_required": "آدرس ایمیلی که به حساب کاربری شما متصل است، باید وارد شود.",
"forgot_password_prompt": "گذرواژه‌ی خود را فراموش کردید؟",
"identifier_label": "نحوه ورود",
@@ -2230,7 +2229,6 @@
"truncated_list_n_more": {
"other": "و %(count)s مورد بیشتر ..."
},
"unknown_device": "دستگاه ناشناخته",
"update": {
"changelog": "تغییراتِ به‌وجودآمده",
"check_action": "بررسی برای به‌روزرسانی جدید",

View File

@@ -194,7 +194,6 @@
"failed_query_registration_methods": "Tuettuja rekisteröitymistapoja ei voitu kysellä.",
"failed_soft_logout_auth": "Uudelleenautentikointi epäonnistui",
"failed_soft_logout_homeserver": "Uudelleenautentikointi epäonnistui kotipalvelinongelmasta johtuen",
"footer_powered_by_matrix": "moottorina Matrix",
"forgot_password_email_invalid": "Sähköpostiosoite ei vaikuta kelvolliselta.",
"forgot_password_email_required": "Sinun pitää syöttää tiliisi liitetty sähköpostiosoite.",
"forgot_password_prompt": "Unohditko salasanasi?",
@@ -3100,7 +3099,6 @@
"truncated_list_n_more": {
"other": "Ja %(count)s muuta..."
},
"unknown_device": "Tuntematon laite",
"update": {
"changelog": "Muutosloki",
"check_action": "Tarkista päivitykset",

View File

@@ -209,7 +209,6 @@
"failed_query_registration_methods": "Impossible de demander les méthodes dinscription prises en charge.",
"failed_soft_logout_auth": "Échec de la ré-authentification",
"failed_soft_logout_homeserver": "Échec de la ré-authentification à cause dun problème du serveur daccueil",
"footer_powered_by_matrix": "propulsé par Matrix",
"forgot_password_email_invalid": "Ladresse e-mail semble être invalide.",
"forgot_password_email_required": "Ladresse e-mail liée à votre compte doit être renseignée.",
"forgot_password_prompt": "Mot de passe oublié ?",
@@ -1625,7 +1624,7 @@
"download_f_droid": "Récupérez-le sur F-Droid",
"download_google_play": "Récupérez-le sur Google Play",
"enable_notifications": "Activer les notifications",
"enable_notifications_action": "Activer les notifications",
"enable_notifications_action": "Ouvrir les paramètres",
"enable_notifications_description": "Ne ratez pas une réponse ou un message important",
"explore_rooms": "Explorez les salons publics",
"find_community_members": "Trouvez et invitez les membres de votre communauté",
@@ -1804,7 +1803,7 @@
"restore_failed_error": "Impossible de restaurer la sauvegarde"
},
"right_panel": {
"add_integrations": "Ajouter des widgets, passerelles et robots",
"add_integrations": "Ajouter des extensions",
"add_topic": "Ajouter un sujet",
"files_button": "Fichiers",
"pinned_messages": {
@@ -1824,7 +1823,7 @@
"button": "Désépingler tous les messages"
}
},
"pinned_messages_button": "Épinglé",
"pinned_messages_button": "Messages épinglés",
"poll": {
"active_heading": "Sondages en cours",
"empty_active": "Il ny a aucun sondage en cours dans ce salon",
@@ -1849,7 +1848,7 @@
"view_in_timeline": "Consulter la chronologie des sondages",
"view_poll": "Voir le sondage"
},
"polls_button": "Historique des sondages",
"polls_button": "Sondages",
"room_summary_card": {
"title": "Information du salon"
},
@@ -3253,7 +3252,7 @@
},
"m.file": {
"error_decrypting": "Erreur lors du déchiffrement de la pièce jointe",
"error_invalid": "Fichier %(extra)s non valide"
"error_invalid": "Fichier invalide"
},
"m.image": {
"error": "Impossible dafficher limage à cause dune erreur",
@@ -3625,7 +3624,6 @@
"truncated_list_n_more": {
"other": "Et %(count)s autres…"
},
"unknown_device": "Appareil inconnu",
"unsupported_server_description": "Ce serveur utilise une ancienne version de Matrix. Mettez-le à jour vers Matrix %(version)s pour utiliser %(brand)s sans erreurs.",
"unsupported_server_title": "Votre serveur nest pas pris en charge",
"update": {
@@ -3990,7 +3988,7 @@
"title": "Autoriser ce widget à vérifier votre identité"
},
"popout": "Détacher le widget",
"set_room_layout": "Définir ma disposition de salon pour tout le monde",
"set_room_layout": "Définir la mise en page pour tout le monde",
"shared_data_avatar": "Votre URL dimage de profil",
"shared_data_device_id": "Votre ID dappareil",
"shared_data_lang": "Votre langue",

View File

@@ -184,7 +184,6 @@
"failed_query_registration_methods": "Non se puido consultar os métodos de rexistro soportados.",
"failed_soft_logout_auth": "Fallo na reautenticación",
"failed_soft_logout_homeserver": "Fallo ó reautenticar debido a un problema no servidor",
"footer_powered_by_matrix": "funciona grazas a Matrix",
"forgot_password_email_invalid": "O enderezo de email non semella ser válido.",
"forgot_password_email_required": "Debe introducir o correo electrónico ligado a súa conta.",
"forgot_password_prompt": "¿Esqueceches o contrasinal?",
@@ -2993,7 +2992,6 @@
"truncated_list_n_more": {
"other": "E %(count)s máis..."
},
"unknown_device": "Dispositivo descoñecido",
"update": {
"changelog": "Rexistro de cambios",
"check_action": "Comprobar actualización",

View File

@@ -167,7 +167,6 @@
"failed_query_registration_methods": "לא ניתן לשאול לשיטות רישום נתמכות.",
"failed_soft_logout_auth": "האימות מחדש נכשל",
"failed_soft_logout_homeserver": "האימות מחדש נכשל עקב בעיית שרת בית",
"footer_powered_by_matrix": "מופעל ע\"י Matrix",
"forgot_password_email_required": "יש להזין את כתובת הדוא\"ל המקושרת לחשבונך.",
"forgot_password_prompt": "שכחת את הסיסמה שלך?",
"forgot_password_send_email": "שלח אימייל",
@@ -2396,7 +2395,6 @@
"truncated_list_n_more": {
"other": "ו%(count)s עוד..."
},
"unknown_device": "מכשיר לא ידוע",
"update": {
"changelog": "דו\"ח שינויים",
"check_action": "בדוק עדכונים",

View File

@@ -202,7 +202,6 @@
"failed_query_registration_methods": "A támogatott regisztrációs módokat nem lehet lekérdezni.",
"failed_soft_logout_auth": "Újra bejelentkezés sikertelen",
"failed_soft_logout_homeserver": "Az újbóli hitelesítés a Matrix-kiszolgáló hibájából sikertelen",
"footer_powered_by_matrix": "a gépházban: Matrix",
"forgot_password_email_invalid": "Az e-mail cím nem tűnik érvényesnek.",
"forgot_password_email_required": "A fiókodhoz kötött e-mail címet add meg.",
"forgot_password_prompt": "Elfelejtetted a jelszavad?",
@@ -3436,7 +3435,6 @@
"truncated_list_n_more": {
"other": "És még %(count)s..."
},
"unknown_device": "Ismeretlen eszköz",
"unsupported_server_description": "Ez a kiszolgáló a Matrix régebbi verzióját használja. Frissítsen a Matrix %(version)s verzióra az %(brand)s hibamentes használatához.",
"unsupported_server_title": "A kiszolgálója nem támogatott",
"update": {

View File

@@ -202,7 +202,6 @@
"failed_query_registration_methods": "Tidak dapat menanyakan metode pendaftaran yang didukung.",
"failed_soft_logout_auth": "Gagal untuk mengautentikasi ulang",
"failed_soft_logout_homeserver": "Gagal untuk mengautentikasi ulang karena masalah homeserver",
"footer_powered_by_matrix": "diberdayakan oleh Matrix",
"forgot_password_email_invalid": "Alamat email ini tidak terlihat absah.",
"forgot_password_email_required": "Alamat email yang tertaut ke akun Anda harus dimasukkan.",
"forgot_password_prompt": "Lupa kata sandi Anda?",
@@ -3469,7 +3468,6 @@
"truncated_list_n_more": {
"other": "Dan %(count)s lagi..."
},
"unknown_device": "Perangkat tidak diketahui",
"unsupported_server_description": "Server ini menjalankan sebuah versi Matrix yang lama. Tingkatkan ke Matrix %(version)s untuk menggunakan %(brand)s tanpa eror.",
"unsupported_server_title": "Server Anda tidak didukung",
"update": {

View File

@@ -184,7 +184,6 @@
"failed_connect_identity_server": "Næ ekki sambandi við auðkennisþjón",
"failed_soft_logout_auth": "Tókst ekki að endurauðkenna",
"failed_soft_logout_homeserver": "Tókst ekki að endurauðkenna vegna vandamála með heimaþjón",
"footer_powered_by_matrix": "keyrt með Matrix",
"forgot_password_email_invalid": "Tölvupóstfangið lítur ekki út fyrir að vera í lagi.",
"forgot_password_email_required": "Það þarf að setja inn tölvupóstfangið sem tengt er notandaaðgangnum þínum.",
"forgot_password_prompt": "Gleymdirðu lykilorðinu þínu?",
@@ -2902,7 +2901,6 @@
"truncated_list_n_more": {
"other": "Og %(count)s til viðbótar..."
},
"unknown_device": "Óþekkt tæki",
"update": {
"changelog": "Breytingaskrá",
"check_action": "Athuga með uppfærslu",

View File

@@ -207,7 +207,6 @@
"failed_query_registration_methods": "Impossibile richiedere i metodi di registrazione supportati.",
"failed_soft_logout_auth": "Riautenticazione fallita",
"failed_soft_logout_homeserver": "Riautenticazione fallita per un problema dell'homeserver",
"footer_powered_by_matrix": "offerto da Matrix",
"forgot_password_email_invalid": "L'indirizzo email non sembra essere valido.",
"forgot_password_email_required": "Deve essere inserito l'indirizzo email collegato al tuo account.",
"forgot_password_prompt": "Hai dimenticato la password?",
@@ -3518,7 +3517,6 @@
"truncated_list_n_more": {
"other": "E altri %(count)s ..."
},
"unknown_device": "Dispositivo sconosciuto",
"unsupported_server_description": "Questo server usa una versione più vecchia di Matrix. Aggiorna a Matrix %(version)s per usare %(brand)s senza errori.",
"unsupported_server_title": "Il tuo server non è supportato",
"update": {

View File

@@ -3231,7 +3231,6 @@
"truncated_list_n_more": {
"other": "他%(count)s人以上…"
},
"unknown_device": "不明な端末",
"update": {
"changelog": "更新履歴",
"check_action": "更新を確認",

View File

@@ -181,7 +181,6 @@
"failed_query_registration_methods": "ບໍ່ສາມາດສອບຖາມວິທີການລົງທະບຽນໄດ້.",
"failed_soft_logout_auth": "ການພິສູດຢືນຢັນຄືນໃໝ່ບໍ່ສຳເລັດ",
"failed_soft_logout_homeserver": "ການພິສູດຢືນຢັນຄືນໃໝ່ເນື່ອງຈາກບັນຫາ homeserver ບໍ່ສຳເລັດ",
"footer_powered_by_matrix": "ຂັບເຄື່ອນໂດຍ Matrix",
"forgot_password_email_invalid": "ທີ່ຢູ່ອີເມວບໍ່ຖືກຕ້ອງ.",
"forgot_password_email_required": "ຕ້ອງໃສ່ທີ່ຢູ່ອີເມວທີ່ເຊື່ອມຕໍ່ກັບບັນຊີຂອງທ່ານ.",
"forgot_password_prompt": "ລືມລະຫັດຜ່ານຂອງທ່ານບໍ?",
@@ -2845,7 +2844,6 @@
"truncated_list_n_more": {
"other": "ແລະ %(count)sອີກ..."
},
"unknown_device": "ທີ່ບໍ່ຮູ້ຈັກອຸປະກອນນີ້",
"update": {
"changelog": "ບັນທຶກການປ່ຽນແປງ",
"check_action": "ກວດເບິ່ງເພຶ່ອອັບເດດ",

Some files were not shown because too many files have changed in this diff Show More