Compare commits
47 Commits
t3chguy/re
...
element-ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
656e4aed1b | ||
|
|
9cde9af706 | ||
|
|
54765b4473 | ||
|
|
db30bc51a1 | ||
|
|
c8c107405f | ||
|
|
17de66140d | ||
|
|
f3d0fc4d68 | ||
|
|
0b636aec4b | ||
|
|
d0cddc5b66 | ||
|
|
cb0d72fc2f | ||
|
|
9a6be72c10 | ||
|
|
536d6ad360 | ||
|
|
b604a6ea8c | ||
|
|
da4672d715 | ||
|
|
74a919cb65 | ||
|
|
b92101a3da | ||
|
|
5057668705 | ||
|
|
931edd7419 | ||
|
|
044eaf7eb5 | ||
|
|
88c72a1514 | ||
|
|
d06cf09bf0 | ||
|
|
2f8e98242c | ||
|
|
3ff5a511e5 | ||
|
|
6042625198 | ||
|
|
9d79a934bf | ||
|
|
a355292a7f | ||
|
|
24fabfff89 | ||
|
|
0b6ed44390 | ||
|
|
a6ce6dc7ab | ||
|
|
15984455af | ||
|
|
6e4bd564d5 | ||
|
|
aeabf3b188 | ||
|
|
c9d9c421bc | ||
|
|
d7d96b6b8b | ||
|
|
2631b908b6 | ||
|
|
849f2c9818 | ||
|
|
8ebfaadeed | ||
|
|
1d49a46dd2 | ||
|
|
dabe6722aa | ||
|
|
10a63b3c23 | ||
|
|
9ce515a646 | ||
|
|
1df72ce2d0 | ||
|
|
6a960204b3 | ||
|
|
26cd13ae3c | ||
|
|
3793c6daca | ||
|
|
65f0d7930a | ||
|
|
8b914c02d0 |
2
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
21
.github/workflows/static_analysis.yaml
vendored
@@ -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
|
||||
|
||||
65
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
{}
|
||||
|
||||
10
package.json
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 4.8 KiB |
@@ -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, */
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
18
res/css/views/rooms/_EventPreview.pcss
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"] {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
|
Before Width: | Height: | Size: 271 B |
@@ -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 |
@@ -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 |
@@ -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 {}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
})}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
138
src/components/views/rooms/EventPreview.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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()}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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" />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": "Έλεγχος για ενημέρωση",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": "بررسی برای بهروزرسانی جدید",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -209,7 +209,6 @@
|
||||
"failed_query_registration_methods": "Impossible de demander les méthodes d’inscription prises en charge.",
|
||||
"failed_soft_logout_auth": "Échec de la ré-authentification",
|
||||
"failed_soft_logout_homeserver": "Échec de la ré-authentification à cause d’un problème du serveur d’accueil",
|
||||
"footer_powered_by_matrix": "propulsé par Matrix",
|
||||
"forgot_password_email_invalid": "L’adresse e-mail semble être invalide.",
|
||||
"forgot_password_email_required": "L’adresse 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 n’y 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 d’afficher l’image à cause d’une 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 n’est 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 d’image de profil",
|
||||
"shared_data_device_id": "Votre ID d’appareil",
|
||||
"shared_data_lang": "Votre langue",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "בדוק עדכונים",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -3231,7 +3231,6 @@
|
||||
"truncated_list_n_more": {
|
||||
"other": "他%(count)s人以上…"
|
||||
},
|
||||
"unknown_device": "不明な端末",
|
||||
"update": {
|
||||
"changelog": "更新履歴",
|
||||
"check_action": "更新を確認",
|
||||
|
||||
@@ -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": "ກວດເບິ່ງເພຶ່ອອັບເດດ",
|
||||
|
||||