Merge branch 'develop' into florianduros/rip-out-legacy-crypto/2-remove-isRoomEncrypted
|
@ -117,10 +117,6 @@ module.exports = {
|
|||
"!matrix-js-sdk/src/extensible_events_v1/PollResponseEvent",
|
||||
"!matrix-js-sdk/src/extensible_events_v1/PollEndEvent",
|
||||
"!matrix-js-sdk/src/extensible_events_v1/InvalidEventError",
|
||||
"!matrix-js-sdk/src/crypto",
|
||||
"!matrix-js-sdk/src/crypto/keybackup",
|
||||
"!matrix-js-sdk/src/crypto/deviceinfo",
|
||||
"!matrix-js-sdk/src/crypto/dehydration",
|
||||
"!matrix-js-sdk/src/oidc",
|
||||
"!matrix-js-sdk/src/oidc/discovery",
|
||||
"!matrix-js-sdk/src/oidc/authorize",
|
||||
|
|
1
.github/workflows/end-to-end-tests.yaml
vendored
|
@ -69,7 +69,6 @@ jobs:
|
|||
VERSION: "${{ steps.layered_build.outputs.VERSION }}"
|
||||
run: |
|
||||
yarn build
|
||||
echo $VERSION > webapp/version
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module.exports = {
|
||||
extends: ["stylelint-config-standard"],
|
||||
customSyntax: "postcss-scss",
|
||||
plugins: ["stylelint-scss"],
|
||||
plugins: ["stylelint-scss", "stylelint-value-no-unknown-custom-properties"],
|
||||
rules: {
|
||||
"comment-empty-line-before": null,
|
||||
"declaration-empty-line-before": null,
|
||||
|
@ -46,5 +46,33 @@ module.exports = {
|
|||
"number-max-precision": null,
|
||||
"no-invalid-double-slash-comments": true,
|
||||
"media-feature-range-notation": null,
|
||||
"csstools/value-no-unknown-custom-properties": [
|
||||
true,
|
||||
{
|
||||
importFrom: [
|
||||
{ from: "res/css/_common.pcss", type: "css" },
|
||||
{ from: "res/themes/light/css/_light.pcss", type: "css" },
|
||||
// Right now our styles share vars all over the place, this is not ideal but acceptable for now
|
||||
{ from: "res/css/views/rooms/_EventTile.pcss", type: "css" },
|
||||
{ from: "res/css/views/rooms/_IRCLayout.pcss", type: "css" },
|
||||
{ from: "res/css/views/rooms/_EventBubbleTile.pcss", type: "css" },
|
||||
{ from: "res/css/views/rooms/_ReadReceiptGroup.pcss", type: "css" },
|
||||
{ from: "res/css/views/rooms/_EditMessageComposer.pcss", type: "css" },
|
||||
{ from: "res/css/views/right_panel/_BaseCard.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_MessageTimestamp.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_EventTileBubble.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_MessageActionBar.pcss", type: "css" },
|
||||
{ from: "res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss", type: "css" },
|
||||
{ from: "res/css/views/elements/_ToggleSwitch.pcss", type: "css" },
|
||||
{ from: "res/css/views/settings/tabs/_SettingsTab.pcss", type: "css" },
|
||||
{ from: "res/css/structures/_RoomView.pcss", type: "css" },
|
||||
// Compound vars
|
||||
"node_modules/@vector-im/compound-design-tokens/assets/web/css/cpd-common-base.css",
|
||||
"node_modules/@vector-im/compound-design-tokens/assets/web/css/cpd-common-semantic.css",
|
||||
"node_modules/@vector-im/compound-design-tokens/assets/web/css/cpd-theme-light-base-mq.css",
|
||||
"node_modules/@vector-im/compound-design-tokens/assets/web/css/cpd-theme-light-semantic-mq.css",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -85,8 +85,8 @@
|
|||
"@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/compound-design-tokens": "^2.0.1",
|
||||
"@vector-im/compound-web": "^7.3.0",
|
||||
"@vector-im/matrix-wysiwyg": "2.37.13",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||
|
@ -276,6 +276,7 @@
|
|||
"stylelint": "^16.1.0",
|
||||
"stylelint-config-standard": "^36.0.0",
|
||||
"stylelint-scss": "^6.0.0",
|
||||
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"ts-node": "^10.9.1",
|
||||
"ts-prune": "^0.10.3",
|
||||
|
@ -286,6 +287,7 @@
|
|||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
"webpack-cli": "^5.0.0",
|
||||
"webpack-dev-server": "^5.0.0",
|
||||
"webpack-version-file-plugin": "^0.5.0",
|
||||
"yaml": "^2.3.3"
|
||||
},
|
||||
"@casualbot/jest-sonar-reporter": {
|
||||
|
|
|
@ -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:27e36370a0422d275b54d0292c8bf87e925dc004d0fe5b10dbee7ea4ffd27289";
|
||||
const DOCKER_TAG = "develop@sha256:d947f40999b060ad4856c0af741b8619fa131430a29922606e374fdba532082b";
|
||||
|
||||
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
|
||||
const templateDir = path.join(__dirname, "templates", opts.template);
|
||||
|
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
@ -11,7 +11,8 @@ Please see LICENSE files in the repository root for full details.
|
|||
font-size: $font-12px;
|
||||
width: 100%; /* make mx_AppPermission fill width of mx_AppTileBody so that scroll bar appears on the edge */
|
||||
overflow-y: scroll;
|
||||
.mx_AppPermission_bolder {
|
||||
.mx_AppPermission_bolder,
|
||||
.mx_AppPermission_content_bolder {
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
}
|
||||
.mx_AppPermission_content {
|
||||
|
@ -21,10 +22,6 @@ Please see LICENSE files in the repository root for full details.
|
|||
margin-block: 12px;
|
||||
}
|
||||
|
||||
.mx_AppPermission_content_bolder {
|
||||
font-weight: var(--font-semi-bold);
|
||||
}
|
||||
|
||||
.mx_TextWithTooltip_target--helpIcon {
|
||||
display: inline-block;
|
||||
height: $font-14px; /* align with characters on the same line */
|
||||
|
|
|
@ -27,7 +27,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
/** Fixme - factor this out with the main header **/
|
||||
|
||||
.mx_RightPanel_threadsButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/thread.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/threads-solid.svg");
|
||||
}
|
||||
|
||||
.mx_RightPanel_notifsButton::before {
|
||||
|
@ -36,7 +36,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_RightPanel_roomSummaryButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/room-summary.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/info-solid.svg");
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
|
|
|
@ -207,62 +207,3 @@ Please see LICENSE files in the repository root for full details.
|
|||
min-height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mx_Indicator_pulse {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mx_Indicator_pulse_shadow {
|
||||
0% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(2.2);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_Indicator {
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: -3px;
|
||||
width: var(--RoomHeader-indicator-dot-size);
|
||||
height: var(--RoomHeader-indicator-dot-size);
|
||||
border-radius: 50%;
|
||||
transform: scale(1);
|
||||
background: var(--RoomHeader-indicator-pulseColor);
|
||||
box-shadow: 0 0 0 0 var(--RoomHeader-indicator-pulseColor);
|
||||
animation: mx_Indicator_pulse 2s infinite;
|
||||
animation-iteration-count: 1;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: scale(1);
|
||||
transform-origin: center center;
|
||||
animation-name: mx_Indicator_pulse_shadow;
|
||||
animation-duration: inherit;
|
||||
animation-iteration-count: inherit;
|
||||
border-radius: 50%;
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,11 +211,11 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
&.mx_SpaceButton_favourites .mx_SpaceButton_icon::before {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/favorite.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/favourite-solid.svg");
|
||||
}
|
||||
|
||||
&.mx_SpaceButton_people .mx_SpaceButton_icon::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/members.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
|
||||
}
|
||||
|
||||
&.mx_SpaceButton_orphans .mx_SpaceButton_icon::before {
|
||||
|
@ -426,11 +426,11 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_SpacePanel_iconLeave::before {
|
||||
mask-image: url("$(res)/img/element-icons/leave.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/leave.svg");
|
||||
}
|
||||
|
||||
.mx_SpacePanel_iconMembers::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/members.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
|
||||
}
|
||||
|
||||
.mx_SpacePanel_iconPlus::before {
|
||||
|
|
|
@ -39,7 +39,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--cpd-color-bg-interactive-primary-rest);
|
||||
border-color: var(--cpd-color-bg-action-primary-rest);
|
||||
|
||||
&::before {
|
||||
background-color: var(--cpd-color-icon-primary);
|
||||
|
@ -248,7 +248,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_SpaceRoomView_privateScope_justMeButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/members.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_privateScope_meAndMyTeammatesButton::before {
|
||||
|
|
|
@ -197,7 +197,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_UserMenu_iconSignOut::before {
|
||||
mask-image: url("$(res)/img/element-icons/leave.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/leave.svg");
|
||||
}
|
||||
|
||||
.mx_UserMenu_iconQr::before {
|
||||
|
|
|
@ -28,10 +28,11 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
/* Waveforms are present in live recording only */
|
||||
.mx_Waveform {
|
||||
/* default, overridden in JS */
|
||||
--barHeight: 1;
|
||||
.mx_Waveform_bar {
|
||||
background-color: $quaternary-content;
|
||||
height: 100%;
|
||||
/* Variable set by a JS component */
|
||||
transform: scaleY(max(0.05, var(--barHeight)));
|
||||
|
||||
&.mx_Waveform_bar_100pct {
|
||||
|
|
|
@ -12,6 +12,9 @@ Please see LICENSE files in the repository root for full details.
|
|||
/* * https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/ */
|
||||
|
||||
.mx_SeekBar {
|
||||
/* default, overridden in JS */
|
||||
--fillTo: 1;
|
||||
|
||||
/* Dev note: we deliberately do not have the -ms-track (and friends) selectors because we don't */
|
||||
/* need to support IE. */
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_MessageContextMenu_iconLink::before {
|
||||
mask-image: url("$(res)/img/element-icons/link.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/link.svg");
|
||||
}
|
||||
|
||||
.mx_MessageContextMenu_iconPermalink::before {
|
||||
|
@ -53,7 +53,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_MessageContextMenu_iconForward::before {
|
||||
mask-image: url("$(res)/img/element-icons/message/fwd.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/forward.svg");
|
||||
}
|
||||
|
||||
.mx_MessageContextMenu_iconRedact::before {
|
||||
|
@ -96,7 +96,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_MessageContextMenu_iconReplyInThread::before {
|
||||
mask-image: url("$(res)/img/element-icons/message/thread.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/threads.svg");
|
||||
}
|
||||
|
||||
.mx_MessageContextMenu_iconReact::before {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.mx_RoomGeneralContextMenu_iconStar::before {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/favorite.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/favourite-solid.svg");
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconArrowDown::before {
|
||||
|
@ -31,7 +31,7 @@
|
|||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconPeople::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/members.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconFiles::before {
|
||||
|
@ -43,7 +43,7 @@
|
|||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconWidgets::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/apps.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/extensions-solid.svg");
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconSettings::before {
|
||||
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconExport::before {
|
||||
mask-image: url("$(res)/img/element-icons/export.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/export-archive.svg");
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconDeveloperTools::before {
|
||||
|
@ -59,7 +59,7 @@
|
|||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconCopyLink::before {
|
||||
mask-image: url("$(res)/img/element-icons/link.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/link.svg");
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconInvite::before {
|
||||
|
@ -67,5 +67,5 @@
|
|||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconSignOut::before {
|
||||
mask-image: url("$(res)/img/element-icons/leave.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/leave.svg");
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
background-color: $secondary-content;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-image: url("$(res)/img/element-icons/room/room-summary.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/info-solid.svg");
|
||||
mask-position: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
background-color: $secondary-content;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-image: url("$(res)/img/element-icons/room/room-summary.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/info-solid.svg");
|
||||
mask-position: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
background-color: $secondary-content;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-image: url("$(res)/img/element-icons/room/room-summary.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/info-solid.svg");
|
||||
mask-position: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
&.mx_SpotlightDialog_filterPeople::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/members.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
|
||||
}
|
||||
|
||||
&.mx_SpotlightDialog_filterPublicRooms::before {
|
||||
|
@ -400,7 +400,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_SpotlightDialog_inviteLink .mx_AccessibleButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/link.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/link.svg");
|
||||
}
|
||||
|
||||
.mx_SpotlightDialog_createRoom .mx_AccessibleButton::before {
|
||||
|
@ -432,7 +432,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_SpotlightDialog_startChat::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/members.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
|
||||
}
|
||||
|
||||
.mx_SpotlightDialog_joinRoomAlias::before {
|
||||
|
@ -512,11 +512,11 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
&.mx_SpotlightDialog_metaspaceResult_favourites-space {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/favorite.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/favourite-solid.svg");
|
||||
}
|
||||
|
||||
&.mx_SpotlightDialog_metaspaceResult_people-space {
|
||||
mask-image: url("$(res)/img/element-icons/room/members.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
|
||||
}
|
||||
|
||||
&.mx_SpotlightDialog_metaspaceResult_orphans-space {
|
||||
|
|
|
@ -25,7 +25,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_InfoTooltip_icon_info::before {
|
||||
mask-image: url("$(res)/img/element-icons/info.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/info.svg");
|
||||
}
|
||||
|
||||
.mx_InfoTooltip_icon_warning::before {
|
||||
|
|
|
@ -16,16 +16,7 @@ progress.mx_ProgressBar {
|
|||
@mixin ProgressBarBorderRadius 6px;
|
||||
@mixin ProgressBarColour var(--cpd-color-icon-accent-tertiary);
|
||||
@mixin ProgressBarBgColour $progressbar-bg-color;
|
||||
::-webkit-progress-value {
|
||||
&::-webkit-progress-value {
|
||||
transition: width 1s;
|
||||
}
|
||||
::-moz-progress-bar {
|
||||
transition: padding-bottom 1s;
|
||||
padding-bottom: var(--value);
|
||||
transform-origin: 0 0;
|
||||
transform: rotate(-90deg) translateX(-15px);
|
||||
padding-left: 15px;
|
||||
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
border-radius: 4px 0;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--cpd-color-text-placeholder);
|
||||
color: var(--cpd-color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -108,12 +108,16 @@ Please see LICENSE files in the repository root for full details.
|
|||
color: var(--cpd-color-icon-primary);
|
||||
}
|
||||
|
||||
&.mx_MessageActionBar_threadButton {
|
||||
--MessageActionBar-icon-size: 20px;
|
||||
}
|
||||
|
||||
&.mx_MessageActionBar_retryButton {
|
||||
--MessageActionBar-icon-size: 16px;
|
||||
}
|
||||
|
||||
&.mx_MessageActionBar_downloadButton {
|
||||
--MessageActionBar-icon-size: 14px;
|
||||
--MessageActionBar-icon-size: 20px;
|
||||
|
||||
&.mx_MessageActionBar_downloadSpinnerButton {
|
||||
svg {
|
||||
|
|
|
@ -165,7 +165,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_ThreadPanel_copyLinkToThread::before {
|
||||
mask-image: url("$(res)/img/element-icons/link.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/link.svg");
|
||||
}
|
||||
|
||||
.mx_ContextualMenu_wrapper {
|
||||
|
|
|
@ -7,6 +7,11 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
.mx_BasicMessageComposer {
|
||||
/* These are set in Javascript */
|
||||
--avatar-letter: "";
|
||||
--avatar-background: unset;
|
||||
--placeholder: "";
|
||||
|
||||
position: relative;
|
||||
|
||||
.mx_BasicMessageComposer_inputEmpty > :first-child::before {
|
||||
|
|
|
@ -334,7 +334,6 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
.mx_MImageBody {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.mx_MImageBody_thumbnail.mx_MImageBody_thumbnail--blurhash {
|
||||
position: unset;
|
||||
|
|
|
@ -1017,16 +1017,6 @@ $left-gutter: 64px;
|
|||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Inverse of the above to *disable* the animation on any indicators. This approach */
|
||||
/* is less pretty, but is easier to target because otherwise we need to define the */
|
||||
/* animation for when it's shown which means duplicating the style definition in */
|
||||
/* multiple places. */
|
||||
.mx_EventTile:not(:hover):not(.mx_EventTile_actionBarFocused):not([data-whatinput="keyboard"] :focus-within) {
|
||||
&:not(:focus-visible:focus-within) .mx_MessageActionBar .mx_Indicator {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_EventTile[data-shape="ThreadsList"],
|
||||
.mx_EventTile[data-shape="Notification"] {
|
||||
--topOffset: $spacing-12;
|
||||
|
|
|
@ -29,7 +29,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
mask-image: url("$(res)/img/element-icons/roomlist/dialpad.svg");
|
||||
}
|
||||
.mx_RoomList_iconStartChat::before {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/member-plus.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg");
|
||||
}
|
||||
.mx_RoomList_iconInvite::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/share.svg");
|
||||
|
|
|
@ -92,7 +92,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
mask-image: url("$(res)/img/element-icons/room/invite.svg");
|
||||
}
|
||||
.mx_RoomListHeader_iconStartChat::before {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/member-plus.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg");
|
||||
}
|
||||
.mx_RoomListHeader_iconNewRoom::before {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg");
|
||||
|
|
|
@ -34,7 +34,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/room/room-summary.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/info-solid.svg");
|
||||
background-color: $secondary-content;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,7 +182,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
.mx_RoomTile_contextMenu {
|
||||
.mx_RoomTile_iconStar::before {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/favorite.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/favourite-solid.svg");
|
||||
}
|
||||
|
||||
.mx_RoomTile_iconArrowDown::before {
|
||||
|
@ -206,7 +206,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_RoomTile_iconPeople::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/members.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
|
||||
}
|
||||
|
||||
.mx_RoomTile_iconFiles::before {
|
||||
|
@ -218,7 +218,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_RoomTile_iconWidgets::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/apps.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/extensions-solid.svg");
|
||||
}
|
||||
|
||||
.mx_RoomTile_iconSettings::before {
|
||||
|
@ -226,11 +226,11 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_RoomTile_iconExport::before {
|
||||
mask-image: url("$(res)/img/element-icons/export.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/export-archive.svg");
|
||||
}
|
||||
|
||||
.mx_RoomTile_iconCopyLink::before {
|
||||
mask-image: url("$(res)/img/element-icons/link.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/link.svg");
|
||||
}
|
||||
|
||||
.mx_RoomTile_iconInvite::before {
|
||||
|
@ -238,6 +238,6 @@ Please see LICENSE files in the repository root for full details.
|
|||
}
|
||||
|
||||
.mx_RoomTile_iconSignOut::before {
|
||||
mask-image: url("$(res)/img/element-icons/leave.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/leave.svg");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,11 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
.mx_WysiwygComposer_Editor_container {
|
||||
/* These are set in Javascript */
|
||||
--avatar-letter: "";
|
||||
--avatar-background: unset;
|
||||
--placeholder: "";
|
||||
|
||||
@keyframes visualbell {
|
||||
from {
|
||||
background-color: $visual-bell-bg-color;
|
||||
|
@ -155,7 +160,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
display: inline-block;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
color: var(--cpd-color-text-placeholder);
|
||||
color: var(--cpd-color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
@mixin SpacePillButton;
|
||||
|
||||
&.mx_SpacePublicShare_shareButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/link.svg");
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/link.svg");
|
||||
}
|
||||
|
||||
&.mx_SpacePublicShare_inviteButton::before {
|
||||
|
|
|
@ -1,224 +1,3 @@
|
|||
<<<<<<<< HEAD:res/jitsi_external_api.min.js.LICENSE.txt
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
|
||||
Note:
|
||||
|
||||
This project was originally contributed to the community under the MIT license and with the following notice:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 ESTOS GmbH
|
||||
Copyright (c) 2013 BlueJimp SARL
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
========
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
@ -421,4 +200,3 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
>>>>>>>> repomerge/t3chguy/repomerge:res/fonts/Open_Sans/LICENSE.txt
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="hole">
|
||||
<rect width="100%" height="100%" fill="white"/>
|
||||
<circle cx="13" cy="11" r="5" fill="black"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.20757 14.9221C12.1427 14.9221 15.3327 11.7309 15.3327 7.79434C15.3327 3.85781 12.1427 0.666626 8.20757 0.666626C4.27246 0.666626 1.08243 3.85781 1.08243 7.79434C1.08243 8.89706 1.33275 9.9413 1.77965 10.8733L0.90483 13.7175C0.644577 14.5636 1.43951 15.3549 2.28444 15.0908L5.10044 14.2104C6.03948 14.6664 7.09367 14.9221 8.20757 14.9221Z" fill="#737D8C" mask="url(#hole)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6 8.19998C13.6 7.8686 13.3314 7.59998 13 7.59998C12.6686 7.59998 12.4 7.8686 12.4 8.19998L12.4 12.3514L10.8243 10.7757C10.59 10.5414 10.2101 10.5414 9.97574 10.7757C9.74142 11.01 9.74142 11.3899 9.97574 11.6242L12.5757 14.2242C12.8101 14.4585 13.19 14.4585 13.4243 14.2242L16.0243 11.6242C16.2586 11.3899 16.2586 11.01 16.0243 10.7757C15.79 10.5414 15.4101 10.5414 15.1757 10.7757L13.6 12.3514L13.6 8.19998Z" fill="#7C7C7C"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,4 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="9.5" stroke="#787878"/>
|
||||
<path d="M9.79248 14H11.2065V8H9.79248V14ZM10.5034 7.14844C10.9526 7.14844 11.3198 6.80469 11.3198 6.38281C11.3198 5.95703 10.9526 5.61328 10.5034 5.61328C10.0503 5.61328 9.68311 5.95703 9.68311 6.38281C9.68311 6.80469 10.0503 7.14844 10.5034 7.14844Z" fill="#787878"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 424 B |
|
@ -1,7 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 4L18.2139 2.44654C19.1211 2.21972 20 2.90592 20 3.84112V20.1589C20 21.0941 19.1211 21.7803 18.2139 21.5535L12 20V4Z" fill="black"/>
|
||||
<mask id="path-2-inside-1" fill="white">
|
||||
<rect x="4" y="4" width="10" height="16" rx="1.4375"/>
|
||||
</mask>
|
||||
<rect x="4" y="4" width="10" height="16" rx="1.4375" stroke="black" stroke-width="4" mask="url(#path-2-inside-1)"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 468 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5285 6.54089L13.0273 6.04207C14.4052 4.66426 16.6259 4.65104 17.9874 6.01253C19.349 7.37402 19.3357 9.59466 17.9579 10.9725L15.5878 13.3425C14.21 14.7203 11.9893 14.7335 10.6277 13.372M11.4717 17.4589L10.9727 17.9579C9.59481 19.3357 7.37409 19.349 6.01256 17.9875C4.65102 16.626 4.66426 14.4053 6.04211 13.0275L8.41203 10.6577C9.78988 9.27988 12.0106 9.26665 13.3721 10.6281" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 556 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 1.99999C8.13 1.99999 5 5.2152 5 9.19054C5 13.4741 9.42 19.3806 11.24 21.6302C11.64 22.1233 12.37 22.1233 12.77 21.6302C14.58 19.3806 19 13.4741 19 9.19054C19 5.2152 15.87 1.99999 12 1.99999ZM12 11.7586C10.62 11.7586 9.5 10.6081 9.5 9.19054C9.5 7.77298 10.62 6.62249 12 6.62249C13.38 6.62249 14.5 7.77298 14.5 9.19054C14.5 10.6081 13.38 11.7586 12 11.7586Z" fill="currentColor"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 495 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 fill-rule="evenodd" clip-rule="evenodd" d="M10.9454 4.27941C10.653 3.98601 10.6539 3.51114 10.9472 3.21875C11.2406 2.92637 11.7155 2.92719 12.0079 3.22059L15.5312 6.75612C15.8229 7.0488 15.8229 7.52226 15.5312 7.81494L12.0079 11.3505C11.7155 11.6439 11.2407 11.6447 10.9473 11.3523C10.6539 11.06 10.653 10.5851 10.9454 10.2917L13.2292 8H6.36588C4.95064 8 3.75282 9.20272 3.75282 10.75C3.75282 12.2973 4.95064 13.5 6.36588 13.5H7.93524C8.34945 13.5 8.68524 13.8358 8.68524 14.25C8.68524 14.6642 8.34945 15 7.93524 15H6.36588C4.06634 15 2.25282 13.0687 2.25282 10.75C2.25282 8.43128 4.06634 6.5 6.36588 6.5H13.1583L10.9454 4.27941Z" fill="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 755 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 18 18"><path fill="currentColor" d="M5 5.25a.75.75 0 0 0 0 1.5h8a.75.75 0 0 0 0-1.5H5ZM5 8.25a.75.75 0 0 0 0 1.5h4a.75.75 0 1 0 0-1.5H5Z"/><path fill="currentColor" fill-rule="evenodd" d="M3 .25A2.75 2.75 0 0 0 .25 3v14a.75.75 0 0 0 1.2.6L4.916 15c.217-.162.48-.25.75-.25H15A2.75 2.75 0 0 0 17.75 12V3A2.75 2.75 0 0 0 15 .25H3ZM1.75 3c0-.69.56-1.25 1.25-1.25h12c.69 0 1.25.56 1.25 1.25v9c0 .69-.56 1.25-1.25 1.25H5.666a2.75 2.75 0 0 0-1.65.55L1.75 15.5V3Z" clip-rule="evenodd"/></svg>
|
Before Width: | Height: | Size: 572 B |
|
@ -1,6 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="14" y="2" width="8" height="8" rx="2" fill="#0DBD8B"/>
|
||||
<rect x="14" y="14" width="8" height="8" rx="2" fill="#0DBD8B"/>
|
||||
<rect x="2" y="14" width="8" height="8" rx="2" fill="#0DBD8B"/>
|
||||
<rect x="2" y="2" width="8" height="8" rx="2" fill="#0DBD8B"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 359 B |
|
@ -1,7 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="path-1-inside-1" fill="white">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5911 20.2922C15.9951 21.3704 14.0711 22 12 22C9.74879 22 7.67132 21.2561 6 20.0007C3.5711 18.1763 2 15.2716 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 15.4518 20.2511 18.4951 17.5911 20.2922ZM12 12.5C13.6569 12.5 15 11.0449 15 9.25C15 7.45507 13.6569 6 12 6C10.3431 6 9 7.45507 9 9.25C9 11.0449 10.3431 12.5 12 12.5ZM12 20C14.162 20 16.1236 19.1424 17.5634 17.7488C16.673 15.5506 14.5176 14 12 14C9.48242 14 7.32699 15.5506 6.43662 17.7488C7.87635 19.1424 9.83802 20 12 20Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5911 20.2922C15.9951 21.3704 14.0711 22 12 22C9.74879 22 7.67132 21.2561 6 20.0007C3.5711 18.1763 2 15.2716 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 15.4518 20.2511 18.4951 17.5911 20.2922ZM12 12.5C13.6569 12.5 15 11.0449 15 9.25C15 7.45507 13.6569 6 12 6C10.3431 6 9 7.45507 9 9.25C9 11.0449 10.3431 12.5 12 12.5ZM12 20C14.162 20 16.1236 19.1424 17.5634 17.7488C16.673 15.5506 14.5176 14 12 14C9.48242 14 7.32699 15.5506 6.43662 17.7488C7.87635 19.1424 9.83802 20 12 20Z" fill="currentColor"/>
|
||||
<path d="M17.5911 20.2922L16.4715 18.6349L17.5911 20.2922ZM6 20.0007L4.79885 21.5999L4.79885 21.5999L6 20.0007ZM17.5634 17.7488L18.9544 19.1859L19.9234 18.2479L19.4171 16.998L17.5634 17.7488ZM6.43662 17.7488L4.5829 16.998L4.07662 18.2479L5.04563 19.1859L6.43662 17.7488ZM12 24C14.4825 24 16.7945 23.244 18.7107 21.9494L16.4715 18.6349C15.1957 19.4968 13.6596 20 12 20V24ZM4.79885 21.5999C6.80462 23.1065 9.30085 24 12 24V20C10.1967 20 8.53802 19.4058 7.20115 18.4016L4.79885 21.5999ZM0 12C0 15.9273 1.88868 19.414 4.79885 21.5999L7.20115 18.4016C5.25353 16.9387 4 14.616 4 12H0ZM12 0C5.37258 0 0 5.37258 0 12H4C4 7.58172 7.58172 4 12 4V0ZM24 12C24 5.37258 18.6274 0 12 0V4C16.4183 4 20 7.58172 20 12H24ZM18.7107 21.9494C21.8977 19.7963 24 16.144 24 12H20C20 14.7596 18.6045 17.1939 16.4715 18.6349L18.7107 21.9494ZM13 9.25C13 10.0941 12.4046 10.5 12 10.5V14.5C14.9091 14.5 17 11.9958 17 9.25H13ZM12 8C12.4046 8 13 8.4059 13 9.25H17C17 6.50425 14.9091 4 12 4V8ZM11 9.25C11 8.4059 11.5954 8 12 8V4C9.09086 4 7 6.50425 7 9.25H11ZM12 10.5C11.5954 10.5 11 10.0941 11 9.25H7C7 11.9958 9.09086 14.5 12 14.5V10.5ZM16.1724 16.3118C15.0906 17.3588 13.6223 18 12 18V22C14.7017 22 17.1567 20.926 18.9544 19.1859L16.1724 16.3118ZM12 16C13.6752 16 15.1146 17.0305 15.7097 18.4996L19.4171 16.998C18.2314 14.0707 15.3599 12 12 12V16ZM8.29033 18.4996C8.88541 17.0305 10.3248 16 12 16V12C8.64008 12 5.76858 14.0707 4.5829 16.998L8.29033 18.4996ZM12 18C10.3777 18 8.90936 17.3588 7.82761 16.3118L5.04563 19.1859C6.84334 20.926 9.2983 22 12 22V18Z" fill="currentColor" mask="url(#path-1-inside-1)"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.8 KiB |
|
@ -1,4 +0,0 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0663 14.25H11.636C13.4938 14.25 14.9998 12.683 14.9998 10.75C14.9998 8.817 13.4938 7.25 11.636 7.25H4.53369" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M6.52417 3.75L3.00006 7.28553L6.52417 10.8211" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 437 B |
|
@ -1,3 +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="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM10 12C10 12.5523 10.4477 13 11 13V16.5C11 17.0523 11.4477 17.5 12 17.5H13.5C14.0523 17.5 14.5 17.0523 14.5 16.5C14.5 15.9477 14.0523 15.5 13.5 15.5H13V12C13 11.4477 12.5523 11 12 11H11C10.4477 11 10 11.4477 10 12ZM12 10C12.8284 10 13.5 9.32843 13.5 8.5C13.5 7.67157 12.8284 7 12 7C11.1716 7 10.5 7.67157 10.5 8.5C10.5 9.32843 11.1716 10 12 10Z" fill="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 630 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="#17191C" fill-rule="evenodd" d="M2 5a3 3 0 0 1 3-3h14a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H7.667a1 1 0 0 0-.6.2L3.6 22.8A1 1 0 0 1 2 22V5Zm3 4a1 1 0 0 1 1-1h12a1 1 0 1 1 0 2H6a1 1 0 0 1-1-1Zm1 3a1 1 0 1 0 0 2h6a1 1 0 1 0 0-2H6Z" clip-rule="evenodd"/></svg>
|
Before Width: | Height: | Size: 357 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="M8.41411 1.43218C8.59217 0.855939 9.40783 0.855941 9.58589 1.43218L11.1715 6.56319H16.3856C16.9721 6.56319 17.224 7.30764 16.7578 7.66373L12.5135 10.9061L14.1185 16.1001C14.2948 16.6705 13.6348 17.1309 13.1604 16.7684L9 13.5902L4.83965 16.7684C4.3652 17.1309 3.70521 16.6705 3.88148 16.1001L5.4865 10.9061L1.24216 7.66373C0.776033 7.30764 1.02785 6.56319 1.61443 6.56319H6.82854L8.41411 1.43218Z" fill="currentColor"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 531 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="M11.3831 1C11.7973 1 12.1331 1.33579 12.1331 1.75V3.30005L13.5833 3.30005C13.9975 3.30005 14.3333 3.63584 14.3333 4.05005C14.3333 4.46426 13.9975 4.80005 13.5833 4.80005L12.1331 4.80005V6.25C12.1331 6.66421 11.7973 7 11.3831 7C10.9689 7 10.6331 6.66421 10.6331 6.25V4.80005L9.08331 4.80005C8.6691 4.80005 8.33331 4.46426 8.33331 4.05005C8.33331 3.63583 8.6691 3.30005 9.08331 3.30005L10.6331 3.30005V1.75C10.6331 1.33579 10.9689 1 11.3831 1ZM5.74076 9.00014C6.88624 9.00014 7.81483 8.07155 7.81483 6.92607C7.81483 5.78059 6.88624 4.85199 5.74076 4.85199C4.59528 4.85199 3.66669 5.78059 3.66669 6.92607C3.66669 8.07155 4.59528 9.00014 5.74076 9.00014ZM5.88889 10.7778C3.00889 10.7778 1 12.2415 1 14.3333V14.926H10.7778V14.3333C10.7778 12.2356 8.76889 10.7778 5.88889 10.7778Z" fill="#737D8C"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 945 B |
|
@ -136,7 +136,7 @@ $input-border-color: rgba(231, 231, 231, 0.2);
|
|||
$input-darker-bg-color: #181b21;
|
||||
$input-darker-fg-color: #61708b;
|
||||
$input-lighter-bg-color: #f2f5f8;
|
||||
$input-placeholder: var(--cpd-color-text-placeholder);
|
||||
$input-placeholder: var(--cpd-color-text-secondary);
|
||||
/* ******************** */
|
||||
|
||||
/* Dialog */
|
||||
|
|
|
@ -54,7 +54,7 @@ $input-border-color: #e7e7e7;
|
|||
$input-darker-bg-color: #181b21;
|
||||
$input-darker-fg-color: #61708b;
|
||||
$input-lighter-bg-color: #f2f5f8;
|
||||
$input-placeholder: var(--cpd-color-text-placeholder);
|
||||
$input-placeholder: var(--cpd-color-text-secondary);
|
||||
|
||||
$resend-button-divider-color: $muted-fg-color;
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ $strong-input-border-color: #c7c7c7;
|
|||
/* used for UserSettings EditableText */
|
||||
$input-underline-color: rgba(151, 151, 151, 0.5);
|
||||
$input-fg-color: rgba(74, 74, 74, 0.9);
|
||||
$input-placeholder: var(--cpd-color-text-placeholder);
|
||||
$input-placeholder: var(--cpd-color-text-secondary);
|
||||
/* scrollbars */
|
||||
$scrollbar-thumb-color: rgba(0, 0, 0, 0.2);
|
||||
/* context menus */
|
||||
|
|
|
@ -184,7 +184,7 @@ $input-darker-fg-color: #9fa9ba;
|
|||
$input-lighter-bg-color: $secondary-accent-color;
|
||||
$input-underline-color: rgba(151, 151, 151, 0.5);
|
||||
$input-fg-color: rgba(74, 74, 74, 0.9);
|
||||
$input-placeholder: var(--cpd-color-text-placeholder);
|
||||
$input-placeholder: var(--cpd-color-text-secondary);
|
||||
/* ******************** */
|
||||
|
||||
/* Dialog */
|
||||
|
|
|
@ -17,4 +17,3 @@ fi
|
|||
|
||||
DIST_VERSION=$("$DIR"/normalize-version.sh "$DIST_VERSION")
|
||||
VERSION=$DIST_VERSION yarn build
|
||||
echo "$DIST_VERSION" > /src/webapp/version
|
||||
|
|
|
@ -21,8 +21,6 @@ cp -r webapp element-$version
|
|||
# Just in case you have a local config, remove it before packaging
|
||||
rm element-$version/config.json || true
|
||||
|
||||
$(dirname $0)/normalize-version.sh ${version} > element-$version/version
|
||||
|
||||
# GNU/BSD compatibility workaround
|
||||
tar_perms=(--owner=0 --group=0) && [ "$(uname)" == "Darwin" ] && tar_perms=(--uid=0 --gid=0)
|
||||
tar "${tar_perms[@]}" -chvzf dist/element-$version.tar.gz element-$version
|
||||
|
|
|
@ -46,6 +46,7 @@ import SettingsStore, { CallbackFn } from "./settings/SettingsStore";
|
|||
import { UIFeature } from "./settings/UIFeature";
|
||||
import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder";
|
||||
import { getUserDeviceIds } from "./utils/crypto/deviceInfo";
|
||||
import { asyncSomeParallel } from "./utils/arrays.ts";
|
||||
|
||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
|
@ -240,13 +241,16 @@ export default class DeviceListener {
|
|||
return this.keyBackupInfo;
|
||||
}
|
||||
|
||||
private shouldShowSetupEncryptionToast(): boolean {
|
||||
private async shouldShowSetupEncryptionToast(): Promise<boolean> {
|
||||
// If we're in the middle of a secret storage operation, we're likely
|
||||
// modifying the state involved here, so don't add new toasts to setup.
|
||||
if (isSecretStorageBeingAccessed()) return false;
|
||||
// Show setup toasts once the user is in at least one encrypted room.
|
||||
const cli = this.client;
|
||||
return cli?.getRooms().some((r) => cli.isRoomEncrypted(r.roomId)) ?? false;
|
||||
const cryptoApi = cli?.getCrypto();
|
||||
if (!cli || !cryptoApi) return false;
|
||||
|
||||
return await asyncSomeParallel(cli.getRooms(), ({ roomId }) => cryptoApi.isEncryptionEnabledInRoom(roomId));
|
||||
}
|
||||
|
||||
private recheck(): void {
|
||||
|
@ -283,7 +287,7 @@ export default class DeviceListener {
|
|||
hideSetupEncryptionToast();
|
||||
|
||||
this.checkKeyBackupStatus();
|
||||
} else if (this.shouldShowSetupEncryptionToast()) {
|
||||
} else if (await this.shouldShowSetupEncryptionToast()) {
|
||||
// make sure our keys are finished downloading
|
||||
await crypto.getUserDeviceInfo([cli.getSafeUserId()]);
|
||||
|
||||
|
|
|
@ -596,7 +596,7 @@ async function combinedPagination(
|
|||
return result;
|
||||
}
|
||||
|
||||
function eventIndexSearch(
|
||||
async function eventIndexSearch(
|
||||
client: MatrixClient,
|
||||
term: string,
|
||||
roomId?: string,
|
||||
|
@ -605,7 +605,7 @@ function eventIndexSearch(
|
|||
let searchPromise: Promise<ISearchResults>;
|
||||
|
||||
if (roomId !== undefined) {
|
||||
if (client.isRoomEncrypted(roomId)) {
|
||||
if (await client.getCrypto()?.isEncryptionEnabledInRoom(roomId)) {
|
||||
// The search is for a single encrypted room, use our local
|
||||
// search method.
|
||||
searchPromise = localSearchProcess(client, term, roomId);
|
||||
|
|
|
@ -186,6 +186,15 @@ export async function withSecretStorageKeyCache<T>(func: () => Promise<T>): Prom
|
|||
}
|
||||
}
|
||||
|
||||
export interface AccessSecretStorageOpts {
|
||||
/** Reset secret storage even if it's already set up. */
|
||||
forceReset?: boolean;
|
||||
/** Create new cross-signing keys. Only applicable if `forceReset` is `true`. */
|
||||
resetCrossSigning?: boolean;
|
||||
/** The cached account password, if available. */
|
||||
accountPassword?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper should be used whenever you need to access secret storage. It
|
||||
* ensures that secret storage (and also cross-signing since they each depend on
|
||||
|
@ -205,14 +214,17 @@ export async function withSecretStorageKeyCache<T>(func: () => Promise<T>): Prom
|
|||
*
|
||||
* @param {Function} [func] An operation to perform once secret storage has been
|
||||
* bootstrapped. Optional.
|
||||
* @param {bool} [forceReset] Reset secret storage even if it's already set up
|
||||
* @param [opts] The options to use when accessing secret storage.
|
||||
*/
|
||||
export async function accessSecretStorage(func = async (): Promise<void> => {}, forceReset = false): Promise<void> {
|
||||
await withSecretStorageKeyCache(() => doAccessSecretStorage(func, forceReset));
|
||||
export async function accessSecretStorage(
|
||||
func = async (): Promise<void> => {},
|
||||
opts: AccessSecretStorageOpts = {},
|
||||
): Promise<void> {
|
||||
await withSecretStorageKeyCache(() => doAccessSecretStorage(func, opts));
|
||||
}
|
||||
|
||||
/** Helper for {@link #accessSecretStorage} */
|
||||
async function doAccessSecretStorage(func: () => Promise<void>, forceReset: boolean): Promise<void> {
|
||||
async function doAccessSecretStorage(func: () => Promise<void>, opts: AccessSecretStorageOpts): Promise<void> {
|
||||
try {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const crypto = cli.getCrypto();
|
||||
|
@ -221,7 +233,7 @@ async function doAccessSecretStorage(func: () => Promise<void>, forceReset: bool
|
|||
}
|
||||
|
||||
let createNew = false;
|
||||
if (forceReset) {
|
||||
if (opts.forceReset) {
|
||||
logger.debug("accessSecretStorage: resetting 4S");
|
||||
createNew = true;
|
||||
} else if (!(await cli.secretStorage.hasKey())) {
|
||||
|
@ -234,9 +246,7 @@ async function doAccessSecretStorage(func: () => Promise<void>, forceReset: bool
|
|||
// passphrase creation.
|
||||
const { finished } = Modal.createDialog(
|
||||
lazy(() => import("./async-components/views/dialogs/security/CreateSecretStorageDialog")),
|
||||
{
|
||||
forceReset,
|
||||
},
|
||||
opts,
|
||||
undefined,
|
||||
/* priority = */ false,
|
||||
/* static = */ true,
|
||||
|
|
|
@ -229,7 +229,7 @@ export class SlidingSyncManager {
|
|||
subscriptions.delete(roomId);
|
||||
}
|
||||
const room = this.client?.getRoom(roomId);
|
||||
let shouldLazyLoad = !this.client?.isRoomEncrypted(roomId);
|
||||
let shouldLazyLoad = !(await this.client?.getCrypto()?.isEncryptionEnabledInRoom(roomId));
|
||||
if (!room) {
|
||||
// default to safety: request all state if we can't work it out. This can happen if you
|
||||
// refresh the app whilst viewing a room: we call setRoomVisible before we know anything
|
||||
|
|
|
@ -58,6 +58,7 @@ interface IProps {
|
|||
hasCancel?: boolean;
|
||||
accountPassword?: string;
|
||||
forceReset?: boolean;
|
||||
resetCrossSigning?: boolean;
|
||||
onFinished(ok?: boolean): void;
|
||||
}
|
||||
|
||||
|
@ -91,6 +92,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
public static defaultProps: Partial<IProps> = {
|
||||
hasCancel: true,
|
||||
forceReset: false,
|
||||
resetCrossSigning: false,
|
||||
};
|
||||
private recoveryKey?: GeneratedSecretStorageKey;
|
||||
private recoveryKeyNode = createRef<HTMLElement>();
|
||||
|
@ -270,7 +272,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
private bootstrapSecretStorage = async (): Promise<void> => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const crypto = cli.getCrypto()!;
|
||||
const { forceReset } = this.props;
|
||||
const { forceReset, resetCrossSigning } = this.props;
|
||||
|
||||
let backupInfo;
|
||||
// First, unless we know we want to do a reset, we see if there is an existing key backup
|
||||
|
@ -292,12 +294,28 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
|
||||
try {
|
||||
if (forceReset) {
|
||||
/* Resetting cross-signing requires secret storage to be reset
|
||||
* (otherwise it will try to store the cross-signing keys in the
|
||||
* old secret storage, and may prompt for the old key, which is
|
||||
* probably not available), and resetting key backup requires
|
||||
* cross-signing to be reset (so that the new backup can be
|
||||
* signed by the new cross-signing key). So we reset secret
|
||||
* storage first, then cross-signing, then key backup.
|
||||
*/
|
||||
logger.log("Forcing secret storage reset");
|
||||
await crypto.bootstrapSecretStorage({
|
||||
createSecretStorageKey: async () => this.recoveryKey!,
|
||||
setupNewKeyBackup: true,
|
||||
setupNewSecretStorage: true,
|
||||
});
|
||||
if (resetCrossSigning) {
|
||||
logger.log("Resetting cross signing");
|
||||
await crypto.bootstrapCrossSigning({
|
||||
authUploadDeviceSigningKeys: this.doBootstrapUIAuth,
|
||||
setupNewCrossSigning: true,
|
||||
});
|
||||
}
|
||||
logger.log("Resetting key backup");
|
||||
await crypto.resetKeyBackup();
|
||||
} else {
|
||||
// For password authentication users after 2020-09, this cross-signing
|
||||
// step will be a no-op since it is now setup during registration or login
|
||||
|
|
|
@ -37,7 +37,7 @@ const USER_REGEX = /\B@\S*/g;
|
|||
|
||||
// used when you hit 'tab' - we allow some separator chars at the beginning
|
||||
// to allow you to tab-complete /mat into /(matthew)
|
||||
const FORCED_USER_REGEX = /[^/,:; \t\n]\S*/g;
|
||||
const FORCED_USER_REGEX = /[^/,.():; \t\n]\S*/g;
|
||||
|
||||
export default class UserProvider extends AutocompleteProvider {
|
||||
public matcher: QueryMatcher<RoomMember>;
|
||||
|
|
|
@ -427,7 +427,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
} else if (
|
||||
(await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) &&
|
||||
!shouldSkipSetupEncryption(cli)
|
||||
!(await shouldSkipSetupEncryption(cli))
|
||||
) {
|
||||
// if cross-signing is not yet set up, do so now if possible.
|
||||
this.setStateForNewView({ view: Views.E2E_SETUP });
|
||||
|
|
|
@ -43,6 +43,10 @@ interface IState {
|
|||
// If we know it, the nature of the abuse, as specified by MSC3215.
|
||||
nature?: ExtendedNature;
|
||||
ignoreUserToo: boolean; // if true, user will be ignored/blocked on submit
|
||||
/*
|
||||
* Whether the room is encrypted.
|
||||
*/
|
||||
isRoomEncrypted: boolean;
|
||||
}
|
||||
|
||||
const MODERATED_BY_STATE_EVENT_TYPE = [
|
||||
|
@ -188,9 +192,20 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
// If specified, the nature of the abuse, as specified by MSC3215.
|
||||
nature: undefined,
|
||||
ignoreUserToo: false, // default false, for now. Could easily be argued as default true
|
||||
isRoomEncrypted: false, // async, will be set later
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount = async (): Promise<void> => {
|
||||
const crypto = MatrixClientPeg.safeGet().getCrypto();
|
||||
const roomId = this.props.mxEvent.getRoomId();
|
||||
if (!crypto || !roomId) return;
|
||||
|
||||
this.setState({
|
||||
isRoomEncrypted: await crypto.isEncryptionEnabledInRoom(roomId),
|
||||
});
|
||||
};
|
||||
|
||||
private onIgnoreUserTooChanged = (newVal: boolean): void => {
|
||||
this.setState({ ignoreUserToo: newVal });
|
||||
};
|
||||
|
@ -319,7 +334,6 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
if (this.moderation) {
|
||||
// Display report-to-moderator dialog.
|
||||
// We let the user pick a nature.
|
||||
const client = MatrixClientPeg.safeGet();
|
||||
const homeServerName = SdkConfig.get("validated_server_config")!.hsName;
|
||||
let subtitle: string;
|
||||
switch (this.state.nature) {
|
||||
|
@ -336,7 +350,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
subtitle = _t("report_content|nature_spam");
|
||||
break;
|
||||
case NonStandardValue.Admin:
|
||||
if (client.isRoomEncrypted(this.props.mxEvent.getRoomId()!)) {
|
||||
if (this.state.isRoomEncrypted) {
|
||||
subtitle = _t("report_content|nature_nonstandard_admin_encrypted", {
|
||||
homeserver: homeServerName,
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ import { determineUnreadState } from "../../../../RoomNotifs";
|
|||
import { humanReadableNotificationLevel } from "../../../../stores/notifications/NotificationLevel";
|
||||
import { doesRoomOrThreadHaveUnreadMessages } from "../../../../Unread";
|
||||
import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
|
||||
import { useIsEncrypted } from "../../../../hooks/useIsEncrypted.ts";
|
||||
|
||||
function UserReadUpTo({ target }: { target: ReadReceipt<any, any> }): JSX.Element {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
@ -59,6 +60,7 @@ function UserReadUpTo({ target }: { target: ReadReceipt<any, any> }): JSX.Elemen
|
|||
export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Element {
|
||||
const { room } = useContext(DevtoolsContext);
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const isRoomEncrypted = useIsEncrypted(cli, room);
|
||||
|
||||
const { level, count } = determineUnreadState(room, undefined, false);
|
||||
const [notificationState] = useNotificationState(room);
|
||||
|
@ -93,9 +95,7 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
|
|||
</li>
|
||||
<li>
|
||||
{_t(
|
||||
cli.isRoomEncrypted(room.roomId!)
|
||||
? _td("devtools|room_encrypted")
|
||||
: _td("devtools|room_not_encrypted"),
|
||||
isRoomEncrypted ? _td("devtools|room_encrypted") : _td("devtools|room_not_encrypted"),
|
||||
{},
|
||||
{
|
||||
strong: (sub) => <strong>{sub}</strong>,
|
||||
|
|
|
@ -19,7 +19,6 @@ import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
|
|||
import { _t } from "../../../../languageHandler";
|
||||
import { accessSecretStorage } from "../../../../SecurityManager";
|
||||
import Modal from "../../../../Modal";
|
||||
import InteractiveAuthDialog from "../InteractiveAuthDialog";
|
||||
import DialogButtons from "../../elements/DialogButtons";
|
||||
import BaseDialog from "../BaseDialog";
|
||||
import { chromeFileInputFix } from "../../../../utils/BrowserWorkarounds";
|
||||
|
@ -226,28 +225,14 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
|
||||
try {
|
||||
// Force reset secret storage (which resets the key backup)
|
||||
await accessSecretStorage(async (): Promise<void> => {
|
||||
// Now reset cross-signing so everything Just Works™ again.
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
await cli.getCrypto()?.bootstrapCrossSigning({
|
||||
authUploadDeviceSigningKeys: async (makeRequest): Promise<void> => {
|
||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||
title: _t("encryption|bootstrap_title"),
|
||||
matrixClient: cli,
|
||||
makeRequest,
|
||||
});
|
||||
const [confirmed] = await finished;
|
||||
if (!confirmed) {
|
||||
throw new Error("Cross-signing key upload auth canceled");
|
||||
}
|
||||
},
|
||||
setupNewCrossSigning: true,
|
||||
});
|
||||
|
||||
await accessSecretStorage(
|
||||
async (): Promise<void> => {
|
||||
// Now we can indicate that the user is done pressing buttons, finally.
|
||||
// Upstream flows will detect the new secret storage, key backup, etc and use it.
|
||||
this.props.onFinished({});
|
||||
}, true);
|
||||
},
|
||||
{ forceReset: true, resetCrossSigning: true },
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
this.props.onFinished(false);
|
||||
|
|
|
@ -109,7 +109,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
|||
|
||||
private onResetRecoveryClick = (): void => {
|
||||
this.props.onFinished(false);
|
||||
accessSecretStorage(async (): Promise<void> => {}, /* forceReset = */ true);
|
||||
accessSecretStorage(async (): Promise<void> => {}, { forceReset: true });
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,7 @@ import { useTimeout } from "../../../hooks/useTimeout";
|
|||
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import Spinner from "./Spinner";
|
||||
import { getFileChanged } from "../settings/AvatarSetting.tsx";
|
||||
|
||||
export const AVATAR_SIZE = "52px";
|
||||
|
||||
|
@ -72,11 +73,12 @@ const MiniAvatarUploader: React.FC<IProps> = ({
|
|||
onClick?.(ev);
|
||||
}}
|
||||
onChange={async (ev): Promise<void> => {
|
||||
if (!ev.target.files?.length) return;
|
||||
setBusy(true);
|
||||
const file = ev.target.files[0];
|
||||
const file = getFileChanged(ev);
|
||||
if (file) {
|
||||
const { content_uri: uri } = await cli.uploadContent(file);
|
||||
await setAvatarUrl(uri);
|
||||
}
|
||||
setBusy(false);
|
||||
}}
|
||||
accept="image/*"
|
||||
|
|
|
@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import LocationMarkerIcon from "@vector-im/compound-design-tokens/assets/web/icons/location-pin-solid";
|
||||
|
||||
import { Icon as LocationMarkerIcon } from "../../../../res/img/element-icons/location.svg";
|
||||
import { Icon as MapFallbackImage } from "../../../../res/img/location/map.svg";
|
||||
import Spinner from "../elements/Spinner";
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||
import React, { ReactNode, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import LocationIcon from "@vector-im/compound-design-tokens/assets/web/icons/location-pin-solid";
|
||||
|
||||
import { Icon as LocationIcon } from "../../../../res/img/element-icons/location.svg";
|
||||
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import React, { HTMLAttributes, useContext } from "react";
|
||||
import LocationIcon from "@vector-im/compound-design-tokens/assets/web/icons/location-pin-solid";
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
@ -14,7 +15,6 @@ import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
|||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import Heading from "../typography/Heading";
|
||||
import { Icon as LocationIcon } from "../../../../res/img/element-icons/location.svg";
|
||||
import { LocationShareType } from "./shareLocation";
|
||||
import StyledLiveBeaconIcon from "../beacon/StyledLiveBeaconIcon";
|
||||
|
||||
|
|
|
@ -6,18 +6,18 @@ 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, { forwardRef, useContext } from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import EventTileBubble from "./EventTileBubble";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { objectHasDiff } from "../../../utils/objects";
|
||||
import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
|
||||
import { MEGOLM_ENCRYPTION_ALGORITHM } from "../../../utils/crypto";
|
||||
import { useIsEncrypted } from "../../../hooks/useIsEncrypted.ts";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -25,9 +25,9 @@ interface IProps {
|
|||
}
|
||||
|
||||
const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp }, ref) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const cli = useMatrixClientContext();
|
||||
const roomId = mxEvent.getRoomId()!;
|
||||
const isRoomEncrypted = MatrixClientPeg.safeGet().isRoomEncrypted(roomId);
|
||||
const isRoomEncrypted = useIsEncrypted(cli, cli.getRoom(roomId) || undefined);
|
||||
|
||||
const prevContent = mxEvent.getPrevContent() as RoomEncryptionEventContent;
|
||||
const content = mxEvent.getContent<RoomEncryptionEventContent>();
|
||||
|
|
|
@ -28,11 +28,11 @@ import {
|
|||
ReplyIcon,
|
||||
DeleteIcon,
|
||||
RestartIcon,
|
||||
ThreadsIcon,
|
||||
} 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 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";
|
||||
import type { Relations } from "matrix-js-sdk/src/matrix";
|
||||
|
@ -243,7 +243,7 @@ const ReplyInThreadButton: React.FC<IReplyInThreadButton> = ({ mxEvent }) => {
|
|||
onContextMenu={onClick}
|
||||
placement="left"
|
||||
>
|
||||
<ThreadIcon />
|
||||
<ThreadsIcon />
|
||||
</RovingAccessibleButton>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
|
||||
|
||||
import { RovingAccessibleButton } from "../../../../accessibility/RovingTabIndex";
|
||||
import Toolbar from "../../../../accessibility/Toolbar";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { Icon as LinkIcon } from "../../../../../res/img/element-icons/link.svg";
|
||||
import { Icon as ViewInRoomIcon } from "../../../../../res/img/element-icons/view-in-room.svg";
|
||||
import { ButtonEvent } from "../../elements/AccessibleButton";
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
|
|||
import { useId } from "../../../utils/useId";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import Modal from "../../../Modal.tsx";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog.tsx";
|
||||
|
||||
interface MenuProps {
|
||||
trigger: ReactNode;
|
||||
|
@ -103,6 +105,18 @@ interface IProps {
|
|||
placeholderName: string;
|
||||
}
|
||||
|
||||
export function getFileChanged(e: React.ChangeEvent<HTMLInputElement>): File | null {
|
||||
if (!e.target.files?.length) return null;
|
||||
const file = e.target.files[0];
|
||||
if (file.type.startsWith("image/")) return file;
|
||||
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("upload_failed_title"),
|
||||
description: _t("upload_file|not_image"),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for setting or removing an avatar on something (eg. a user or a room)
|
||||
*/
|
||||
|
@ -139,7 +153,10 @@ const AvatarSetting: React.FC<IProps> = ({
|
|||
|
||||
const onFileChanged = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) onChange?.(e.target.files[0]);
|
||||
const file = getFileChanged(e);
|
||||
if (file) {
|
||||
onChange?.(file);
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
|
|
@ -209,7 +209,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
|||
private resetSecretStorage = async (): Promise<void> => {
|
||||
this.setState({ error: false });
|
||||
try {
|
||||
await accessSecretStorage(async (): Promise<void> => {}, /* forceReset = */ true);
|
||||
await accessSecretStorage(async (): Promise<void> => {}, { forceReset: true });
|
||||
} catch (e) {
|
||||
logger.error("Error resetting secret storage", e);
|
||||
if (this.unmounted) return;
|
||||
|
|
|
@ -7,10 +7,13 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import React, { ChangeEvent, useMemo } from "react";
|
||||
import { VideoCallSolidIcon, HomeSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import {
|
||||
VideoCallSolidIcon,
|
||||
HomeSolidIcon,
|
||||
UserProfileSolidIcon,
|
||||
FavouriteSolidIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { Icon as FavoriteIcon } from "../../../../../../res/img/element-icons/roomlist/favorite.svg";
|
||||
import { Icon as MembersIcon } from "../../../../../../res/img/element-icons/room/members.svg";
|
||||
import { Icon as HashCircleIcon } from "../../../../../../res/img/element-icons/roomlist/hash-circle.svg";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
|
@ -112,7 +115,7 @@ const SidebarUserSettingsTab: React.FC = () => {
|
|||
className="mx_SidebarUserSettingsTab_checkbox"
|
||||
>
|
||||
<SettingsSubsectionText>
|
||||
<FavoriteIcon />
|
||||
<FavouriteSolidIcon />
|
||||
{_t("common|favourites")}
|
||||
</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>
|
||||
|
@ -126,7 +129,7 @@ const SidebarUserSettingsTab: React.FC = () => {
|
|||
className="mx_SidebarUserSettingsTab_checkbox"
|
||||
>
|
||||
<SettingsSubsectionText>
|
||||
<MembersIcon />
|
||||
<UserProfileSolidIcon />
|
||||
{_t("common|people")}
|
||||
</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>
|
||||
|
|
|
@ -8,7 +8,11 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import EllipsisIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal";
|
||||
import {
|
||||
OverflowHorizontalIcon,
|
||||
UserProfileSolidIcon,
|
||||
FavouriteSolidIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import ContextMenu, { alwaysAboveRightOf, ChevronFace, useContextMenu } from "../../structures/ContextMenu";
|
||||
|
@ -22,8 +26,6 @@ import { Action } from "../../../dispatcher/actions";
|
|||
import { UserTab } from "../dialogs/UserTab";
|
||||
import QuickThemeSwitcher from "./QuickThemeSwitcher";
|
||||
import { Icon as PinUprightIcon } from "../../../../res/img/element-icons/room/pin-upright.svg";
|
||||
import { Icon as MembersIcon } from "../../../../res/img/element-icons/room/members.svg";
|
||||
import { Icon as FavoriteIcon } from "../../../../res/img/element-icons/roomlist/favorite.svg";
|
||||
import Modal from "../../../Modal";
|
||||
import DevtoolsDialog from "../dialogs/DevtoolsDialog";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
|
@ -89,7 +91,7 @@ const QuickSettingsButton: React.FC<{
|
|||
checked={!!favouritesEnabled}
|
||||
onChange={onMetaSpaceChangeFactory(MetaSpace.Favourites, "WebQuickSettingsPinToSidebarCheckbox")}
|
||||
>
|
||||
<FavoriteIcon className="mx_QuickSettingsButton_icon" />
|
||||
<FavouriteSolidIcon className="mx_QuickSettingsButton_icon" />
|
||||
{_t("common|favourites")}
|
||||
</StyledCheckbox>
|
||||
<StyledCheckbox
|
||||
|
@ -97,7 +99,7 @@ const QuickSettingsButton: React.FC<{
|
|||
checked={!!peopleEnabled}
|
||||
onChange={onMetaSpaceChangeFactory(MetaSpace.People, "WebQuickSettingsPinToSidebarCheckbox")}
|
||||
>
|
||||
<MembersIcon className="mx_QuickSettingsButton_icon" />
|
||||
<UserProfileSolidIcon className="mx_QuickSettingsButton_icon" />
|
||||
{_t("common|people")}
|
||||
</StyledCheckbox>
|
||||
<AccessibleButton
|
||||
|
@ -110,7 +112,7 @@ const QuickSettingsButton: React.FC<{
|
|||
});
|
||||
}}
|
||||
>
|
||||
<EllipsisIcon className="mx_QuickSettingsButton_icon" />
|
||||
<OverflowHorizontalIcon className="mx_QuickSettingsButton_icon" />
|
||||
{_t("quick_settings|sidebar_settings")}
|
||||
</AccessibleButton>
|
||||
|
||||
|
|
|
@ -10,11 +10,12 @@ import { KeyboardEvent } from "react";
|
|||
|
||||
import { Part, CommandPartCreator, PartCreator } from "./parts";
|
||||
import DocumentPosition from "./position";
|
||||
import { ICompletion } from "../autocomplete/Autocompleter";
|
||||
import { ICompletion, ISelectionRange } from "../autocomplete/Autocompleter";
|
||||
import Autocomplete from "../components/views/rooms/Autocomplete";
|
||||
|
||||
export interface ICallback {
|
||||
replaceParts?: Part[];
|
||||
range?: ISelectionRange;
|
||||
close?: boolean;
|
||||
}
|
||||
|
||||
|
@ -82,6 +83,7 @@ export default class AutocompleteWrapperModel {
|
|||
this.updateCallback({
|
||||
replaceParts: this.partForCompletion(completion),
|
||||
close: true,
|
||||
range: completion.range,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -250,14 +250,24 @@ export default class EditorModel {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private onAutoComplete = ({ replaceParts, close }: ICallback): void => {
|
||||
private onAutoComplete = ({ replaceParts, close, range }: ICallback): void => {
|
||||
let pos: DocumentPosition | undefined;
|
||||
if (replaceParts) {
|
||||
const autoCompletePartIdx = this.autoCompletePartIdx || 0;
|
||||
this._parts.splice(autoCompletePartIdx, this.autoCompletePartCount, ...replaceParts);
|
||||
|
||||
this.replaceRange(
|
||||
new DocumentPosition(autoCompletePartIdx, range?.start ?? 0),
|
||||
new DocumentPosition(
|
||||
autoCompletePartIdx + this.autoCompletePartCount - 1,
|
||||
range?.end ?? this.parts[autoCompletePartIdx + this.autoCompletePartCount - 1].text.length,
|
||||
),
|
||||
replaceParts,
|
||||
);
|
||||
|
||||
this.autoCompletePartCount = replaceParts.length;
|
||||
const lastPart = replaceParts[replaceParts.length - 1];
|
||||
const lastPartIndex = autoCompletePartIdx + replaceParts.length - 1;
|
||||
// `replaceRange` merges adjacent parts so we need to find it in the new parts list
|
||||
const lastPartIndex = this.parts.indexOf(lastPart);
|
||||
pos = new DocumentPosition(lastPartIndex, lastPart.text.length);
|
||||
}
|
||||
if (close) {
|
||||
|
|
|
@ -6,21 +6,40 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CryptoEvent, MatrixClient, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { throttle } from "lodash";
|
||||
|
||||
import { E2EStatus, shieldStatusForRoom } from "../utils/ShieldUtils";
|
||||
import { useTypedEventEmitter } from "./useEventEmitter";
|
||||
|
||||
export function useEncryptionStatus(client: MatrixClient, room: Room): E2EStatus | null {
|
||||
const [e2eStatus, setE2eStatus] = useState<E2EStatus | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const updateEncryptionStatus = useMemo(
|
||||
() =>
|
||||
throttle(
|
||||
() => {
|
||||
if (client.getCrypto()) {
|
||||
shieldStatusForRoom(client, room).then((e2eStatus) => {
|
||||
setE2eStatus(e2eStatus);
|
||||
});
|
||||
}
|
||||
}, [client, room]);
|
||||
},
|
||||
250,
|
||||
{ leading: true, trailing: true },
|
||||
),
|
||||
[client, room],
|
||||
);
|
||||
|
||||
useEffect(updateEncryptionStatus, [updateEncryptionStatus]);
|
||||
|
||||
// shieldStatusForRoom depends on the room membership, each member's trust
|
||||
// status for each member, and each member's devices, so we update the
|
||||
// status whenever any of those changes.
|
||||
useTypedEventEmitter(room, RoomStateEvent.Members, updateEncryptionStatus);
|
||||
useTypedEventEmitter(client, CryptoEvent.UserTrustStatusChanged, updateEncryptionStatus);
|
||||
useTypedEventEmitter(client, CryptoEvent.DevicesUpdated, updateEncryptionStatus);
|
||||
|
||||
return e2eStatus;
|
||||
}
|
||||
|
|
|
@ -3742,6 +3742,7 @@
|
|||
"error_files_too_large": "These files are <b>too large</b> to upload. The file size limit is %(limit)s.",
|
||||
"error_some_files_too_large": "Some files are <b>too large</b> to be uploaded. The file size limit is %(limit)s.",
|
||||
"error_title": "Upload Error",
|
||||
"not_image": "The file you have chosen is not a valid image file.",
|
||||
"title": "Upload files",
|
||||
"title_progress": "Upload files (%(current)s of %(total)s)",
|
||||
"upload_all_button": "Upload all",
|
||||
|
|
|
@ -19,9 +19,6 @@ import { Device, SecretStorage } from "matrix-js-sdk/src/matrix";
|
|||
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import { AccessCancelledError, accessSecretStorage } from "../SecurityManager";
|
||||
import Modal from "../Modal";
|
||||
import InteractiveAuthDialog from "../components/views/dialogs/InteractiveAuthDialog";
|
||||
import { _t } from "../languageHandler";
|
||||
import { SdkContextClass } from "../contexts/SDKContext";
|
||||
import { asyncSome } from "../utils/arrays";
|
||||
import { initialiseDehydration } from "../utils/device/dehydration";
|
||||
|
@ -230,42 +227,16 @@ export class SetupEncryptionStore extends EventEmitter {
|
|||
// secret storage key if they had one. Start by resetting
|
||||
// secret storage and setting up a new recovery key, then
|
||||
// create new cross-signing keys once that succeeds.
|
||||
await accessSecretStorage(async (): Promise<void> => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
await cli.getCrypto()?.bootstrapCrossSigning({
|
||||
authUploadDeviceSigningKeys: async (makeRequest): Promise<void> => {
|
||||
const cachedPassword = SdkContextClass.instance.accountPasswordStore.getPassword();
|
||||
|
||||
if (cachedPassword) {
|
||||
await makeRequest({
|
||||
type: "m.login.password",
|
||||
identifier: {
|
||||
type: "m.id.user",
|
||||
user: cli.getSafeUserId(),
|
||||
},
|
||||
user: cli.getSafeUserId(),
|
||||
password: cachedPassword,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||
title: _t("encryption|bootstrap_title"),
|
||||
matrixClient: cli,
|
||||
makeRequest,
|
||||
});
|
||||
const [confirmed] = await finished;
|
||||
if (!confirmed) {
|
||||
throw new Error("Cross-signing key upload auth canceled");
|
||||
}
|
||||
},
|
||||
setupNewCrossSigning: true,
|
||||
});
|
||||
|
||||
await initialiseDehydration(true);
|
||||
|
||||
await accessSecretStorage(
|
||||
async (): Promise<void> => {
|
||||
this.phase = Phase.Finished;
|
||||
}, true);
|
||||
},
|
||||
{
|
||||
forceReset: true,
|
||||
resetCrossSigning: true,
|
||||
accountPassword: SdkContextClass.instance.accountPasswordStore.getPassword(),
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error("Error resetting cross-signing", e);
|
||||
this.phase = Phase.Intro;
|
||||
|
|
|
@ -328,6 +328,28 @@ export async function asyncSome<T>(values: Iterable<T>, predicate: (value: T) =>
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async version of Array.some that runs all promises in parallel.
|
||||
* @param values
|
||||
* @param predicate
|
||||
*/
|
||||
export async function asyncSomeParallel<T>(
|
||||
values: Array<T>,
|
||||
predicate: (value: T) => Promise<boolean>,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
return await Promise.any<boolean>(
|
||||
values.map((value) =>
|
||||
predicate(value).then((result) => (result ? Promise.resolve(true) : Promise.reject(false))),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
// If the array is empty or all the promises are false, Promise.any will reject an AggregateError
|
||||
if (e instanceof AggregateError) return false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Async version of Array.filter.
|
||||
* @param values
|
||||
|
|
|
@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { shouldForceDisableEncryption } from "./shouldForceDisableEncryption";
|
||||
import { asyncSomeParallel } from "../arrays.ts";
|
||||
|
||||
/**
|
||||
* If encryption is force disabled AND the user is not in any encrypted rooms
|
||||
|
@ -16,7 +17,13 @@ import { shouldForceDisableEncryption } from "./shouldForceDisableEncryption";
|
|||
* @param client
|
||||
* @returns {boolean} true when we can skip settings up encryption
|
||||
*/
|
||||
export const shouldSkipSetupEncryption = (client: MatrixClient): boolean => {
|
||||
export const shouldSkipSetupEncryption = async (client: MatrixClient): Promise<boolean> => {
|
||||
const isEncryptionForceDisabled = shouldForceDisableEncryption(client);
|
||||
return isEncryptionForceDisabled && !client.getRooms().some((r) => client.isRoomEncrypted(r.roomId));
|
||||
const crypto = client.getCrypto();
|
||||
if (!crypto) return true;
|
||||
|
||||
return (
|
||||
isEncryptionForceDisabled &&
|
||||
!(await asyncSomeParallel(client.getRooms(), ({ roomId }) => crypto.isEncryptionEnabledInRoom(roomId)))
|
||||
);
|
||||
};
|
||||
|
|
|
@ -151,10 +151,7 @@ export async function setMarkedUnreadState(room: Room, client: MatrixClient, unr
|
|||
const currentState = getMarkedUnreadState(room);
|
||||
|
||||
if (Boolean(currentState) !== unread) {
|
||||
// Assuming MSC2867 passes FCP with no changes, we should update to start writing
|
||||
// the flag to the stable prefix (or both) and then ultimately use only the
|
||||
// stable prefix.
|
||||
await client.setRoomAccountData(room.roomId, MARKED_UNREAD_TYPE_UNSTABLE, { unread });
|
||||
await client.setRoomAccountData(room.roomId, MARKED_UNREAD_TYPE_STABLE, { unread });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -162,6 +162,7 @@ export const mockClientMethodsCrypto = (): Partial<
|
|||
getVersion: jest.fn().mockReturnValue("Version 0"),
|
||||
getOwnDeviceKeys: jest.fn().mockReturnValue(new Promise(() => {})),
|
||||
getCrossSigningKeyId: jest.fn(),
|
||||
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ describe("DeviceListener", () => {
|
|||
},
|
||||
}),
|
||||
getSessionBackupPrivateKey: jest.fn(),
|
||||
isEncryptionEnabledInRoom: jest.fn(),
|
||||
} as unknown as Mocked<CryptoApi>;
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
isGuest: jest.fn(),
|
||||
|
@ -105,7 +106,6 @@ describe("DeviceListener", () => {
|
|||
isVersionSupported: jest.fn().mockResolvedValue(true),
|
||||
isInitialSyncComplete: jest.fn().mockReturnValue(true),
|
||||
waitForClientWellKnown: jest.fn(),
|
||||
isRoomEncrypted: jest.fn(),
|
||||
getClientWellKnown: jest.fn(),
|
||||
getDeviceId: jest.fn().mockReturnValue(deviceId),
|
||||
setAccountData: jest.fn(),
|
||||
|
@ -292,7 +292,7 @@ describe("DeviceListener", () => {
|
|||
mockCrypto!.isCrossSigningReady.mockResolvedValue(false);
|
||||
mockCrypto!.isSecretStorageReady.mockResolvedValue(false);
|
||||
mockClient!.getRooms.mockReturnValue(rooms);
|
||||
mockClient!.isRoomEncrypted.mockReturnValue(true);
|
||||
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it("hides setup encryption toast when cross signing and secret storage are ready", async () => {
|
||||
|
@ -317,7 +317,7 @@ describe("DeviceListener", () => {
|
|||
});
|
||||
|
||||
it("does not show any toasts when no rooms are encrypted", async () => {
|
||||
mockClient!.isRoomEncrypted.mockReturnValue(false);
|
||||
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
await createAndStart();
|
||||
|
||||
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalled();
|
||||
|
|
|
@ -68,7 +68,7 @@ describe("SecurityManager", () => {
|
|||
stubClient();
|
||||
|
||||
const func = jest.fn();
|
||||
accessSecretStorage(func, true);
|
||||
accessSecretStorage(func, { forceReset: true });
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
await expect(spy.mock.lastCall![0]).resolves.toEqual(expect.objectContaining({ __test: true }));
|
||||
|
|
|
@ -146,7 +146,6 @@ describe("<MatrixChat />", () => {
|
|||
matrixRTC: createStubMatrixRTC(),
|
||||
getDehydratedDevice: jest.fn(),
|
||||
whoami: jest.fn(),
|
||||
isRoomEncrypted: jest.fn(),
|
||||
logout: jest.fn(),
|
||||
getDeviceId: jest.fn(),
|
||||
getKeyBackupVersion: jest.fn().mockResolvedValue(null),
|
||||
|
@ -1011,6 +1010,7 @@ describe("<MatrixChat />", () => {
|
|||
userHasCrossSigningKeys: jest.fn().mockResolvedValue(false),
|
||||
// This needs to not finish immediately because we need to test the screen appears
|
||||
bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise),
|
||||
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
|
||||
};
|
||||
loginClient.getCrypto.mockReturnValue(mockCrypto as any);
|
||||
});
|
||||
|
@ -1058,9 +1058,11 @@ describe("<MatrixChat />", () => {
|
|||
},
|
||||
});
|
||||
|
||||
loginClient.isRoomEncrypted.mockImplementation((roomId) => {
|
||||
jest.spyOn(loginClient.getCrypto()!, "isEncryptionEnabledInRoom").mockImplementation(
|
||||
async (roomId) => {
|
||||
return roomId === encryptedRoom.roomId;
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("should go straight to logged in view when user is not in any encrypted rooms", async () => {
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
createTestClient,
|
||||
getMockClientWithEventEmitter,
|
||||
makeBeaconInfoEvent,
|
||||
mockClientMethodsCrypto,
|
||||
mockClientMethodsEvents,
|
||||
mockClientMethodsUser,
|
||||
} from "../../../test-utils";
|
||||
|
@ -42,6 +43,7 @@ describe("MessagePanel", function () {
|
|||
const client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsEvents(),
|
||||
...mockClientMethodsCrypto(),
|
||||
getAccountData: jest.fn(),
|
||||
isUserIgnored: jest.fn().mockReturnValue(false),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
SearchResult,
|
||||
IEvent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { fireEvent, render, screen, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
@ -72,6 +73,7 @@ describe("RoomView", () => {
|
|||
let rooms: Map<string, Room>;
|
||||
let roomCount = 0;
|
||||
let stores: SdkContextClass;
|
||||
let crypto: CryptoApi;
|
||||
|
||||
// mute some noise
|
||||
filterConsole("RVS update", "does not have an m.room.create event", "Current version: 1", "Version capability");
|
||||
|
@ -97,6 +99,7 @@ describe("RoomView", () => {
|
|||
stores.rightPanelStore.useUnitTestClient(cli);
|
||||
|
||||
jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(undefined);
|
||||
crypto = cli.getCrypto()!;
|
||||
jest.spyOn(cli, "getCrypto").mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
|
@ -341,7 +344,13 @@ describe("RoomView", () => {
|
|||
|
||||
describe("that is encrypted", () => {
|
||||
beforeEach(() => {
|
||||
// Not all the calls to cli.isRoomEncrypted are migrated, so we need to mock both.
|
||||
mocked(cli.isRoomEncrypted).mockReturnValue(true);
|
||||
jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
|
||||
jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
jest.spyOn(cli.getCrypto()!, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, true, false),
|
||||
);
|
||||
localRoom.encrypted = true;
|
||||
localRoom.currentState.setStateEvents([
|
||||
new MatrixEvent({
|
||||
|
@ -360,7 +369,7 @@ describe("RoomView", () => {
|
|||
|
||||
it("should match the snapshot", async () => {
|
||||
const { container } = await renderRoomView();
|
||||
expect(container).toMatchSnapshot();
|
||||
await waitFor(() => expect(container).toMatchSnapshot());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,9 +8,18 @@ exports[`<BeaconViewDialog /> renders a fallback when there are no locations 1`]
|
|||
<div
|
||||
class="mx_MapFallback_bg"
|
||||
/>
|
||||
<div
|
||||
<svg
|
||||
class="mx_MapFallback_icon"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_BeaconViewDialog_mapFallbackMessage"
|
||||
>
|
||||
|
|
|
@ -150,7 +150,7 @@ describe("RoomGeneralContextMenu", () => {
|
|||
|
||||
await sleep(0);
|
||||
|
||||
expect(mockClient.setRoomAccountData).toHaveBeenCalledWith(ROOM_ID, "com.famedly.marked_unread", {
|
||||
expect(mockClient.setRoomAccountData).toHaveBeenCalledWith(ROOM_ID, "m.marked_unread", {
|
||||
unread: true,
|
||||
});
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
|
|
|
@ -122,4 +122,34 @@ describe("AccessSecretStorageDialog", () => {
|
|||
|
||||
expect(screen.getByPlaceholderText("Security Phrase")).toHaveFocus();
|
||||
});
|
||||
|
||||
it("Can reset secret storage", async () => {
|
||||
jest.spyOn(mockClient.secretStorage, "checkKey").mockResolvedValue(true);
|
||||
|
||||
const onFinished = jest.fn();
|
||||
const checkPrivateKey = jest.fn().mockResolvedValue(true);
|
||||
renderComponent({ onFinished, checkPrivateKey });
|
||||
|
||||
await userEvent.click(screen.getByText("Reset all"), { delay: null });
|
||||
|
||||
// It will prompt the user to confirm resetting
|
||||
expect(screen.getByText("Reset everything")).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText("Reset"), { delay: null });
|
||||
|
||||
// Then it will prompt the user to create a key/passphrase
|
||||
await screen.findByText("Set up Secure Backup");
|
||||
document.execCommand = jest.fn().mockReturnValue(true);
|
||||
jest.spyOn(mockClient.getCrypto()!, "createRecoveryKeyFromPassphrase").mockResolvedValue({
|
||||
privateKey: new Uint8Array(),
|
||||
encodedPrivateKey: securityKey,
|
||||
});
|
||||
screen.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await screen.findByText(/Save your Security Key/);
|
||||
screen.getByRole("button", { name: "Copy" }).click();
|
||||
await screen.findByText("Copied!");
|
||||
screen.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await screen.findByText("Secure Backup successful");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -97,4 +97,38 @@ describe("CreateSecretStorageDialog", () => {
|
|||
await screen.findByText("Your keys are now being backed up from this device.");
|
||||
});
|
||||
});
|
||||
|
||||
it("resets keys in the right order when resetting secret storage and cross-signing", async () => {
|
||||
const result = renderComponent({ forceReset: true, resetCrossSigning: true });
|
||||
|
||||
await result.findByText(/Set up Secure Backup/);
|
||||
jest.spyOn(mockClient.getCrypto()!, "createRecoveryKeyFromPassphrase").mockResolvedValue({
|
||||
privateKey: new Uint8Array(),
|
||||
encodedPrivateKey: "abcd efgh ijkl",
|
||||
});
|
||||
result.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await result.findByText(/Save your Security Key/);
|
||||
result.getByRole("button", { name: "Copy" }).click();
|
||||
|
||||
// Resetting should reset secret storage, cross signing, and key
|
||||
// backup. We make sure that all three are reset, and done in the
|
||||
// right order.
|
||||
const resetFunctionCallLog: string[] = [];
|
||||
jest.spyOn(mockClient.getCrypto()!, "bootstrapSecretStorage").mockImplementation(async () => {
|
||||
resetFunctionCallLog.push("bootstrapSecretStorage");
|
||||
});
|
||||
jest.spyOn(mockClient.getCrypto()!, "bootstrapCrossSigning").mockImplementation(async () => {
|
||||
resetFunctionCallLog.push("bootstrapCrossSigning");
|
||||
});
|
||||
jest.spyOn(mockClient.getCrypto()!, "resetKeyBackup").mockImplementation(async () => {
|
||||
resetFunctionCallLog.push("resetKeyBackup");
|
||||
});
|
||||
|
||||
result.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await result.findByText("Your keys are now being backed up from this device.");
|
||||
|
||||
expect(resetFunctionCallLog).toEqual(["bootstrapSecretStorage", "bootstrapCrossSigning", "resetKeyBackup"]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import MiniAvatarUploader from "../../../../../src/components/views/elements/MiniAvatarUploader.tsx";
|
||||
import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
|
||||
|
||||
const BASE64_GIF = "R0lGODlhAQABAAAAACw=";
|
||||
const AVATAR_FILE = new File([Uint8Array.from(atob(BASE64_GIF), (c) => c.charCodeAt(0))], "avatar.gif", {
|
||||
type: "image/gif",
|
||||
});
|
||||
|
||||
describe("<MiniAvatarUploader />", () => {
|
||||
it("calls setAvatarUrl when a file is uploaded", async () => {
|
||||
const cli = stubClient();
|
||||
mocked(cli.uploadContent).mockResolvedValue({ content_uri: "mxc://example.com/1234" });
|
||||
|
||||
const setAvatarUrl = jest.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
const { container, findByText } = render(
|
||||
<MiniAvatarUploader hasAvatar={false} noAvatarLabel="Upload" setAvatarUrl={setAvatarUrl} isUserAvatar />,
|
||||
withClientContextRenderOptions(cli),
|
||||
);
|
||||
|
||||
await findByText("Upload");
|
||||
await user.upload(container.querySelector("input")!, AVATAR_FILE);
|
||||
|
||||
expect(cli.uploadContent).toHaveBeenCalledWith(AVATAR_FILE);
|
||||
expect(setAvatarUrl).toHaveBeenCalledWith("mxc://example.com/1234");
|
||||
});
|
||||
});
|
|
@ -13,9 +13,18 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
|||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
|