Merge branch 'develop' into andybalaam/stas-demydiuk-membership-type3

This commit is contained in:
Andy Balaam 2024-03-20 17:25:23 +00:00
commit d7bdbee8d2
124 changed files with 2399 additions and 1052 deletions

View file

@ -67,7 +67,7 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/analytics-events": "^0.10.0",
"@matrix-org/analytics-events": "^0.12.0",
"@matrix-org/emojibase-bindings": "^1.1.2",
"@matrix-org/matrix-wysiwyg": "2.17.0",
"@matrix-org/olm": "3.2.15",
@ -116,7 +116,7 @@
"opus-recorder": "^8.0.3",
"pako": "^2.0.3",
"png-chunks-extract": "^1.0.0",
"posthog-js": "1.110.0",
"posthog-js": "1.116.0",
"proposal-temporal": "^0.9.0",
"qrcode": "1.5.3",
"re-resizable": "^6.9.0",
@ -223,7 +223,7 @@
"stylelint-config-standard": "^36.0.0",
"stylelint-scss": "^6.0.0",
"ts-node": "^10.9.1",
"typescript": "5.3.3"
"typescript": "5.4.2"
},
"peerDependencies": {
"postcss": "^8.4.19",

View file

@ -18,6 +18,7 @@ import { Page } from "@playwright/test";
import { test, expect } from "../../element-web-test";
import { ElementAppPage } from "../../pages/ElementAppPage";
import type { Container } from "../../../src/stores/widgets/types";
test.describe("Room Header", () => {
test.use({
@ -227,7 +228,7 @@ test.describe("Room Header", () => {
{
widgets: {
[id]: {
container: "top",
container: "top" as Container,
index: 1,
width: 100,
height: 0,

View file

@ -0,0 +1,61 @@
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { test, expect } from "../../element-web-test";
const TEST_ROOM_NAME = "The mark unread test room";
test.describe("Mark as Unread", () => {
test.use({
displayName: "Tom",
botCreateOpts: {
displayName: "BotBob",
autoAcceptInvites: true,
},
});
test("should mark a room as unread", async ({ page, app, bot }) => {
const roomId = await app.client.createRoom({
name: TEST_ROOM_NAME,
});
const dummyRoomId = await app.client.createRoom({
name: "Room of no consequence",
});
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
await bot.sendMessage(roomId, "I am a robot. Beep.");
// Regular notification on new message
await expect(page.getByLabel(TEST_ROOM_NAME + " 1 unread message.")).toBeVisible();
await expect(page).toHaveTitle("Element [1]");
await page.goto("/#/room/" + roomId);
// should now be read, since we viewed the room (we have to assert the page title:
// the room badge isn't visible since we're viewing the room)
await expect(page).toHaveTitle("Element | " + TEST_ROOM_NAME);
// navigate away from the room again
await page.goto("/#/room/" + dummyRoomId);
const roomTile = page.getByLabel(TEST_ROOM_NAME);
await roomTile.focus();
await roomTile.getByRole("button", { name: "Room options" }).click();
await page.getByRole("menuitem", { name: "Mark as unread" }).click();
expect(page.getByLabel(TEST_ROOM_NAME + " Unread messages.")).toBeVisible();
});
});

View file

@ -0,0 +1,58 @@
/*
*
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
import { Locator } from "@playwright/test";
import { test, expect } from "../../element-web-test";
test.describe("Roles & Permissions room settings tab", () => {
const roomName = "Test room";
test.use({
displayName: "Alice",
});
let settings: Locator;
test.beforeEach(async ({ user, app }) => {
await app.client.createRoom({ name: roomName });
await app.viewRoomByName(roomName);
settings = await app.settings.openRoomSettings("Roles & Permissions");
});
test("should be able to change the role of a user", async ({ page, app, user }) => {
const privilegedUserSection = settings.locator(".mx_SettingsFieldset").first();
const applyButton = privilegedUserSection.getByRole("button", { name: "Apply" });
// Alice is admin (100) and the Apply button should be disabled
await expect(applyButton).toBeDisabled();
let combobox = privilegedUserSection.getByRole("combobox", { name: user.userId });
await expect(combobox).toHaveValue("100");
// Change the role of Alice to Moderator (50)
await combobox.selectOption("Moderator");
await expect(combobox).toHaveValue("50");
await applyButton.click();
// Reload and check Alice is still Moderator (50)
await page.reload();
settings = await app.settings.openRoomSettings("Roles & Permissions");
combobox = privilegedUserSection.getByRole("combobox", { name: user.userId });
await expect(combobox).toHaveValue("50");
});
});

View file

@ -31,3 +31,10 @@ declare global {
matrixcs: typeof Matrix;
}
}
// Workaround for lack of strict mode not resolving complex types correctly
declare module "matrix-js-sdk/src/http-api/index.ts" {
interface UploadResponse {
json(): Promise<object>;
}
}

View file

@ -31,6 +31,7 @@ import type {
Visibility,
UploadOpts,
Upload,
StateEvents,
} from "matrix-js-sdk/src/matrix";
import { Credentials } from "../plugins/homeserver";
@ -407,7 +408,7 @@ export class Client {
const client = await this.prepareClient();
return client.evaluate(
async (client, { roomId, eventType, content, stateKey }) => {
return client.sendStateEvent(roomId, eventType, content, stateKey);
return client.sendStateEvent(roomId, eventType as keyof StateEvents, content, stateKey);
},
{ roomId, eventType, content, stateKey },
);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -10,6 +10,7 @@
},
"include": [
"**/*.ts",
"../src/@types/matrix-js-sdk.d.ts",
"../node_modules/matrix-js-sdk/src/@types/*.d.ts",
"../node_modules/matrix-js-sdk/node_modules/@matrix-org/olm/index.d.ts"
]

View file

@ -646,7 +646,7 @@ legend {
.mx_Dialog button.warning:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
.mx_Dialog input[type="submit"].warning {
border: solid 1px var(--cpd-color-border-critical-primary);
border: solid 1px var(--cpd-color-border-critical-subtle);
color: var(--cpd-color-text-critical-primary);
}

View file

@ -335,6 +335,7 @@
@import "./views/settings/_NotificationSettings2.pcss";
@import "./views/settings/_Notifications.pcss";
@import "./views/settings/_PhoneNumbers.pcss";
@import "./views/settings/_PowerLevelSelector.pcss";
@import "./views/settings/_ProfileSettings.pcss";
@import "./views/settings/_SecureBackupPanel.pcss";
@import "./views/settings/_SetIdServer.pcss";

View file

@ -10,6 +10,10 @@
mask-image: url("$(res)/img/element-icons/roomlist/mark-as-read.svg");
}
.mx_RoomGeneralContextMenu_iconMarkAsUnread::before {
mask-image: url("$(res)/img/element-icons/roomlist/mark-as-unread.svg");
}
.mx_RoomGeneralContextMenu_iconNotificationsDefault::before {
mask-image: url("$(res)/img/element-icons/notifications.svg");
}

View file

@ -26,7 +26,7 @@ limitations under the License.
overflow: hidden;
cursor: pointer;
color: $accent-fg-color !important; /* To override .markdown-body */
color: var(--cpd-color-text-on-solid-primary) !important; /* To override .markdown-body */
background-color: $pill-bg-color !important; /* To override .markdown-body */
> * {
@ -35,7 +35,7 @@ limitations under the License.
&.mx_UserPill_me,
&.mx_AtRoomPill {
background-color: $alert !important; /* To override .markdown-body */
background-color: var(--cpd-color-bg-critical-primary) !important; /* To override .markdown-body */
}
&:hover {
@ -43,12 +43,14 @@ limitations under the License.
}
&.mx_UserPill_me:hover {
background-color: #ff6b75 !important; /* To override .markdown-body | same on both themes */
background-color: var(
--cpd-color-bg-critical-hovered
) !important; /* To override .markdown-body | same on both themes */
}
/* We don't want to indicate clickability */
&.mx_AtRoomPill:hover {
background-color: $alert !important; /* To override .markdown-body */
background-color: var(--cpd-color-bg-critical-primary) !important; /* To override .markdown-body */
cursor: unset;
}

View file

@ -71,8 +71,8 @@ limitations under the License.
max-width: 300px;
word-break: break-word;
background-color: #21262c; /* Same on both themes */
color: $accent-fg-color;
background-color: var(--cpd-color-alpha-gray-1400);
color: var(--cpd-color-text-on-solid-primary);
border: 0;
text-align: center;

View file

@ -39,5 +39,5 @@ limitations under the License.
mask-size: contain;
mask-repeat: no-repeat;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
background-color: $tertiary-content;
background-color: var(--cpd-color-icon-secondary);
}

View file

@ -20,12 +20,12 @@ limitations under the License.
display: flex;
align-items: center;
font: var(--cpd-font-body-md-regular);
color: $roomtopic-color;
color: var(--cpd-color-text-secondary);
}
.mx_TimelineSeparator > hr {
flex: 1 1 0;
height: 0;
border: none;
border-bottom: 1px solid $menu-selected-color;
border-bottom: 1px solid var(--cpd-color-gray-400);
}

View file

@ -20,7 +20,7 @@ limitations under the License.
.mx_BasicMessageComposer_inputEmpty > :first-child::before {
content: var(--placeholder);
opacity: 0.333;
color: var(--cpd-color-text-secondary);
width: 0;
height: 0;
overflow: visible;

View file

@ -37,15 +37,6 @@ limitations under the License.
}
}
/* white infill for the transparency */
.mx_E2EIcon::before {
background-color: #ffffff;
mask-image: url("$(res)/img/e2e/normal.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: 80%;
}
/* transparent-looking border surrounding the shield for when overlain over avatars */
.mx_E2EIcon_bordered {
mask-image: url("$(res)/img/e2e/normal.svg");
@ -59,6 +50,7 @@ limitations under the License.
/* shrink the infill of the badge */
&::before {
mask-size: 60%;
background: var(--cpd-color-bg-canvas-default);
}
}
@ -69,7 +61,7 @@ limitations under the License.
.mx_E2EIcon_normal::after {
mask-image: url("$(res)/img/e2e/normal.svg");
background-color: $header-panel-text-primary-color;
background-color: var(--cpd-color-icon-tertiary);
}
.mx_E2EIcon_verified::after {

View file

@ -53,7 +53,7 @@ $left-gutter: 64px;
height: 16px;
&::before {
background-color: $tertiary-content;
background-color: var(--cpd-color-icon-tertiary);
mask-repeat: no-repeat;
mask-position: center;
mask-size: 16px;
@ -858,12 +858,12 @@ $left-gutter: 64px;
&.mx_EventTile_e2eIcon_normal::after {
mask-image: url("$(res)/img/e2e/normal.svg"); /* regular shield */
background-color: $header-panel-text-primary-color; /* grey */
background-color: var(--cpd-color-icon-tertiary); /* grey */
}
&.mx_EventTile_e2eIcon_decryption_failure::after {
mask-image: url("$(res)/img/e2e/decryption-failure.svg"); /* key in a circle */
background-color: $secondary-content;
background-color: var(--cpd-color-icon-tertiary);
}
}

View file

@ -39,13 +39,12 @@ limitations under the License.
/* with text-align in parent */
display: inline-block;
padding: 0 4px;
color: $accent-fg-color;
background-color: $muted-fg-color;
color: var(--cpd-color-text-on-solid-primary);
background-color: var(--cpd-color-icon-secondary);
}
.mx_JumpToBottomButton_highlight .mx_JumpToBottomButton_badge {
color: $secondary-accent-color;
background-color: $alert;
background-color: var(--cpd-color-icon-critical-primary);
}
.mx_JumpToBottomButton_scrollDown {
@ -55,7 +54,7 @@ limitations under the License.
border-radius: 19px;
box-sizing: border-box;
background: $background;
border: 1.3px solid $muted-fg-color;
border: 1.3px solid var(--cpd-color-icon-tertiary);
cursor: pointer;
}
@ -68,5 +67,5 @@ limitations under the License.
mask-size: 20px;
mask-position: center 6px;
transform: rotate(180deg);
background: $muted-fg-color;
background: var(--cpd-color-icon-tertiary);
}

View file

@ -185,10 +185,10 @@ limitations under the License.
}
&:hover {
background: $accent-300;
background: var(--cpd-color-bg-subtle-primary);
&::before {
background-color: $accent;
background-color: var(--cpd-color-icon-primary);
}
}
}
@ -213,8 +213,8 @@ limitations under the License.
margin: 4px;
&.mx_Indicator_highlight {
background: $alert;
box-shadow: $alert;
background: var(--cpd-color-icon-critical-primary);
box-shadow: var(--cpd-color-icon-critical-primary);
}
&.mx_Indicator_notification {
@ -223,8 +223,8 @@ limitations under the License.
}
&.mx_Indicator_activity {
background: $primary-content;
box-shadow: $primary-content;
background: var(--cpd-color-icon-primary);
box-shadow: var(--cpd-color-icon-primary);
}
}
@ -234,10 +234,9 @@ limitations under the License.
}
}
.mx_LegacyRoomHeader_button--highlight,
.mx_LegacyRoomHeader_button:hover {
.mx_LegacyRoomHeader_button--highlight {
&::before {
background-color: $accent !important;
background-color: var(--cpd-color-icon-primary) !important;
}
}

View file

@ -191,7 +191,7 @@ limitations under the License.
}
.mx_MessageComposer_button {
@mixin composerButton 50%, var(--cpd-color-icon-secondary), var(--cpd-color-bg-subtle-secondary);
@mixin composerButton 50%, var(--cpd-color-icon-primary), var(--cpd-color-bg-subtle-primary);
&:last-child {
margin-right: auto;

View file

@ -39,7 +39,7 @@ limitations under the License.
width: 8px;
height: 8px;
border-radius: 8px;
background-color: var(--cpd-color-text-primary);
background-color: var(--cpd-color-icon-primary);
.mx_NotificationBadge_count {
display: none;
@ -86,7 +86,8 @@ limitations under the License.
.mx_NotificationBadge_count {
font-size: $font-10px;
line-height: $font-14px;
color: #fff; /* TODO: Variable */
font-weight: var(--cpd-font-weight-semibold);
color: var(--cpd-color-text-on-solid-primary);
}
}
}

View file

@ -118,7 +118,7 @@ limitations under the License.
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background: $muted-fg-color;
background: var(--cpd-color-icon-secondary);
}
}
@ -167,7 +167,7 @@ limitations under the License.
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background-color: $tertiary-content;
background-color: var(--cpd-color-icon-secondary);
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
}

View file

@ -131,7 +131,7 @@ limitations under the License.
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background: $primary-content;
background: var(--cpd-color-icon-primary);
}
}

View file

@ -40,7 +40,7 @@ limitations under the License.
border-radius: 19px;
box-sizing: border-box;
background: $background;
border: 1.3px solid $muted-fg-color;
border: 1.3px solid var(--cpd-color-icon-tertiary);
cursor: pointer;
}
@ -53,7 +53,7 @@ limitations under the License.
mask-repeat: no-repeat;
mask-size: 20px;
mask-position: center;
background: $muted-fg-color;
background: var(--cpd-color-icon-tertiary);
}
.mx_TopUnreadMessagesBar_markAsRead {
@ -61,7 +61,7 @@ limitations under the License.
width: 18px;
height: 18px;
background: $background;
border: 1.3px solid $muted-fg-color;
border: 1.3px solid var(--cpd-color-icon-tertiary);
border-radius: 10px;
margin: 5px auto;
}
@ -75,5 +75,5 @@ limitations under the License.
mask-repeat: no-repeat;
mask-size: 10px;
mask-position: 4px 4px;
background: $muted-fg-color;
background: var(--cpd-color-icon-tertiary);
}

View file

@ -115,7 +115,7 @@ limitations under the License.
max-width: 100%;
overflow: hidden;
color: $accent-fg-color;
color: var(--cpd-color-text-on-solid-primary);
background-color: $pill-bg-color;
/* ...with the overrides from _BasicMessageComposer.pcss */

View file

@ -0,0 +1,21 @@
/*
*
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
.mx_PowerLevelSelector_Button {
align-self: flex-start;
}

View file

@ -0,0 +1,4 @@
<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="M17 5H5C3.89543 5 3 5.89543 3 7V17.4C3 18.5046 3.89543 19.4 5 19.4H19C20.1046 19.4 21 18.5046 21 17.4V7.82929C20.6872 7.93985 20.3506 8 20 8C18.3431 8 17 6.65685 17 5ZM5.26294 10.3869C4.97723 10.2282 4.80002 9.92703 4.80002 9.60018C4.80002 8.91395 5.53722 8.48018 6.1371 8.81344L12 12.0706L17.8629 8.81344C18.4628 8.48018 19.2 8.91395 19.2 9.60018C19.2 9.92703 19.0228 10.2282 18.7371 10.3869L12.4371 13.8869C12.1653 14.0379 11.8348 14.0379 11.5629 13.8869L5.26294 10.3869Z" fill="#1B1D22"/>
<path d="M22 5C22 6.10457 21.1046 7 20 7C18.8954 7 18 6.10457 18 5C18 3.89543 18.8954 3 20 3C21.1046 3 22 3.89543 22 5Z" fill="#1B1D22"/>
</svg>

After

Width:  |  Height:  |  Size: 782 B

View file

@ -22,7 +22,7 @@ $separator: var(--cpd-color-alpha-gray-400);
/* ******************** */
$roomlist-bg-color: rgba(38, 40, 45, 0.9);
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, $background 0%, #ffffff00 100%);
$roomtile-default-badge-bg-color: $muted-fg-color;
$roomtile-default-badge-bg-color: var(--cpd-color-icon-secondary);
/* ******************** */
/**
@ -125,8 +125,8 @@ $roomheader-addroom-fg-color: $primary-content;
/* Rich-text-editor */
/* ******************** */
$pill-bg-color: $room-highlight-color;
$pill-hover-bg-color: #545a66;
$pill-bg-color: var(--cpd-color-gray-1000);
$pill-hover-bg-color: var(--cpd-color-gray-1100);
/* ******************** */
/* Inputs */
@ -150,7 +150,7 @@ $dialog-close-external-color: $primary-content;
/* ******************** */
$system: var(--cpd-color-bg-subtle-secondary);
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
$roomtile-default-badge-bg-color: $input-darker-fg-color;
$roomtile-default-badge-bg-color: var(--cpd-color-icon-secondary);
/* ******************** */
/* Tabbed views */
@ -199,10 +199,9 @@ $voice-record-icon-color: $quaternary-content;
/* Bubble tiles */
/* ******************** */
$eventbubble-self-bg: #133a34;
$eventbubble-others-bg: #21262c;
$eventbubble-bg-hover: #1c2026;
$eventbubble-reply-color: #c1c6cd;
$eventbubble-self-bg: var(--cpd-color-green-300);
$eventbubble-others-bg: var(--cpd-color-gray-300);
$eventbubble-bg-hover: var(--cpd-color-bg-subtle-secondary);
/* ******************** */
/* Lightbox */

View file

@ -28,8 +28,8 @@ $light-fg-color: $header-panel-text-secondary-color;
/* used for focusing form controls */
$focus-bg-color: $room-highlight-color;
$pill-bg-color: $room-highlight-color;
$pill-hover-bg-color: #545a66;
$pill-bg-color: var(--cpd-color-gray-1000);
$pill-hover-bg-color: var(--cpd-color-gray-1100);
/* informational plinth */
$info-plinth-bg-color: $header-panel-bg-color;
@ -217,7 +217,6 @@ $inlinecode-background-color: #2a3039;
$eventbubble-self-bg: #14322e;
$eventbubble-others-bg: $event-selected-color;
$eventbubble-bg-hover: #1c2026;
$eventbubble-reply-color: #c1c6cd;
/* Location sharing */
/* ******************** */

View file

@ -123,8 +123,8 @@ $rte-code-bg-color: rgba(0, 0, 0, 0.04);
$header-panel-text-primary-color: #91a1c0;
$pill-bg-color: #aaa;
$pill-hover-bg-color: #ccc;
$pill-bg-color: var(--cpd-color-gray-1000);
$pill-hover-bg-color: var(--cpd-color-gray-1100);
$topleftmenu-color: #212121;
$roomheader-bg-color: $primary-bg-color;
@ -311,7 +311,6 @@ $inlinecode-background-color: $header-panel-bg-color;
$eventbubble-self-bg: #f0fbf8;
$eventbubble-others-bg: $system;
$eventbubble-bg-hover: #fafbfd;
$eventbubble-reply-color: #c1c6cd;
/* pinned events indicator */
$pinned-color: $tertiary-content;

View file

@ -116,7 +116,6 @@ $settings-grey-fg-color: $primary-content;
$eventbubble-self-bg: var(--eventbubble-self-bg, $eventbubble-self-bg);
$eventbubble-others-bg: var(--eventbubble-others-bg, $eventbubble-others-bg);
$eventbubble-bg-hover: var(--eventbubble-bg-hover, $eventbubble-bg-hover);
$eventbubble-reply-color: var(--eventbubble-reply-color, $eventbubble-reply-color);
$reaction-row-button-selected-bg-color: var(
--reaction-row-button-selected-bg-color,

View file

@ -31,7 +31,6 @@ $button-secondary-bg-color: $accent-fg-color;
$message-action-bar-fg-color: $primary-content;
$voice-record-stop-border-color: $quinary-content;
$voice-record-icon-color: $tertiary-content;
$eventbubble-reply-color: $quaternary-content;
$roomtopic-color: $secondary-content;
/**

View file

@ -159,8 +159,8 @@ $roomheader-addroom-fg-color: #5c6470;
/* Rich-text-editor */
/* ******************** */
$pill-bg-color: #aaa;
$pill-hover-bg-color: #ccc;
$pill-bg-color: var(--cpd-color-gray-1000);
$pill-hover-bg-color: var(--cpd-color-gray-1100);
$rte-bg-color: #e9e9e9;
$rte-code-bg-color: rgba(0, 0, 0, 0.04);
/* ******************** */
@ -205,13 +205,13 @@ $imagebody-giflabel-color: $accent-fg-color;
/* ******************** */
$roomlist-bg-color: rgba(245, 245, 245, 0.9);
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, $background 0%, #ffffff00 100%);
$roomtile-default-badge-bg-color: $muted-fg-color;
$roomtile-default-badge-bg-color: var(--cpd-color-icon-secondary);
/* ******************** */
/* e2e */
/* ******************** */
$e2e-verified-color: var(--cpd-color-green-900);
$e2e-warning-color: var(--cpd-color-red-900);
$e2e-verified-color: var(--cpd-color-icon-success-primary);
$e2e-warning-color: var(--cpd-color-icon-critical-primary);
$e2e-verified-color-light: var(--cpd-color-green-300);
$e2e-warning-color-light: var(--cpd-color-red-300);
/* ******************** */
@ -274,10 +274,9 @@ $voice-record-icon-color: $tertiary-content;
/* Bubble tiles */
/* ******************** */
$eventbubble-self-bg: #e7f8f3;
$eventbubble-others-bg: #e8edf4;
$eventbubble-bg-hover: #f6f7f8;
$eventbubble-reply-color: $quaternary-content;
$eventbubble-self-bg: var(--cpd-color-green-300);
$eventbubble-others-bg: var(--cpd-color-gray-300);
$eventbubble-bg-hover: var(--cpd-color-bg-subtle-secondary);
/* ******************** */
/* Lightbox */

View file

@ -16,12 +16,7 @@ limitations under the License.
import { JSXElementConstructor } from "react";
export type { NonEmptyArray } from "matrix-js-sdk/src/matrix";
// Based on https://stackoverflow.com/a/53229857/3532235
export type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
export type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
export type { NonEmptyArray, XOR, Writeable } from "matrix-js-sdk/src/matrix";
export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;

View file

@ -151,16 +151,10 @@ declare global {
interface HTMLAudioElement {
type?: string;
// sinkId & setSinkId are experimental and typescript doesn't know about them
sinkId: string;
setSinkId(outputId: string): void;
}
interface HTMLVideoElement {
type?: string;
// sinkId & setSinkId are experimental and typescript doesn't know about them
sinkId: string;
setSinkId(outputId: string): void;
}
// Add Chrome-specific `instant` ScrollBehaviour

View file

@ -14,14 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { BLURHASH_FIELD } from "../utils/image-media";
import type { IWidget } from "matrix-widget-api";
import type { BLURHASH_FIELD } from "../utils/image-media";
import type { JitsiCallMemberEventType, JitsiCallMemberContent } from "../call-types";
import type { ILayoutStateEvent, WIDGET_LAYOUT_EVENT_TYPE } from "../stores/widgets/types";
import type { VoiceBroadcastInfoEventContent, VoiceBroadcastInfoEventType } from "../voice-broadcast/types";
// Matrix JS SDK extensions
declare module "matrix-js-sdk" {
declare module "matrix-js-sdk/src/types" {
export interface FileInfo {
/**
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/2448
*/
[BLURHASH_FIELD]?: string;
}
export interface StateEvents {
// Jitsi-backed video room state events
[JitsiCallMemberEventType]: JitsiCallMemberContent;
// Unstable widgets state events
"im.vector.modular.widgets": IWidget | {};
[WIDGET_LAYOUT_EVENT_TYPE]: ILayoutStateEvent;
// Unstable voice broadcast state events
[VoiceBroadcastInfoEventType]: VoiceBroadcastInfoEventContent;
// Element custom state events
"im.vector.web.settings": Record<string, any>;
"org.matrix.room.preview_urls": { disable: boolean };
// XXX unspecced usages of `m.room.*` events
"m.room.plumbing": {
status: string;
};
"m.room.bot.options": unknown;
}
}

View file

@ -29,6 +29,7 @@ import { getUnsentMessages } from "./components/structures/RoomStatusBar";
import { doesRoomHaveUnreadMessages, doesRoomOrThreadHaveUnreadMessages } from "./Unread";
import { EffectiveMembership, getEffectiveMembership, isKnockDenied } from "./utils/membership";
import SettingsStore from "./settings/SettingsStore";
import { getMarkedUnreadState } from "./utils/notifications";
export enum RoomNotifState {
AllMessagesLoud = "all_messages_loud",
@ -279,7 +280,8 @@ export function determineUnreadState(
return { symbol: null, count: trueCount, level: NotificationLevel.Highlight };
}
if (greyNotifs > 0) {
const markedUnreadState = getMarkedUnreadState(room);
if (greyNotifs > 0 || markedUnreadState) {
return { symbol: null, count: trueCount, level: NotificationLevel.Notification };
}

View file

@ -291,7 +291,7 @@ Response:
*/
import { IContent, MatrixEvent, IEvent } from "matrix-js-sdk/src/matrix";
import { IContent, MatrixEvent, IEvent, StateEvents } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger";
@ -725,7 +725,7 @@ async function getOpenIdToken(event: MessageEvent<any>): Promise<void> {
async function sendEvent(
event: MessageEvent<{
type: string;
type: keyof StateEvents;
state_key?: string;
content?: IContent;
}>,

View file

@ -18,9 +18,9 @@ limitations under the License.
*/
import * as React from "react";
import { User, IContent, Direction, ContentHelpers, MRoomTopicEventContent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { ContentHelpers, Direction, EventType, IContent, MRoomTopicEventContent, User } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { KnownMembership, RoomMemberEventContent } from "matrix-js-sdk/src/types";
import dis from "./dispatcher/dispatcher";
import { _t, _td, UserFriendlyError } from "./languageHandler";
@ -240,12 +240,12 @@ export const Commands = [
isEnabled: (cli) => !isCurrentLocalRoom(cli),
runFn: function (cli, roomId, threadId, args) {
if (args) {
const ev = cli.getRoom(roomId)?.currentState.getStateEvents("m.room.member", cli.getSafeUserId());
const content = {
const ev = cli.getRoom(roomId)?.currentState.getStateEvents(EventType.RoomMember, cli.getSafeUserId());
const content: RoomMemberEventContent = {
...(ev ? ev.getContent() : { membership: KnownMembership.Join }),
displayname: args,
};
return success(cli.sendStateEvent(roomId, "m.room.member", content, cli.getSafeUserId()));
return success(cli.sendStateEvent(roomId, EventType.RoomMember, content, cli.getSafeUserId()));
}
return reject(this.getUsage());
},
@ -266,7 +266,7 @@ export const Commands = [
return success(
promise.then((url) => {
if (!url) return;
return cli.sendStateEvent(roomId, "m.room.avatar", { url }, "");
return cli.sendStateEvent(roomId, EventType.RoomAvatar, { url }, "");
}),
);
},
@ -290,12 +290,12 @@ export const Commands = [
return success(
promise.then((url) => {
if (!url) return;
const ev = room?.currentState.getStateEvents("m.room.member", userId);
const content = {
const ev = room?.currentState.getStateEvents(EventType.RoomMember, userId);
const content: RoomMemberEventContent = {
...(ev ? ev.getContent() : { membership: KnownMembership.Join }),
avatar_url: url,
};
return cli.sendStateEvent(roomId, "m.room.member", content, userId);
return cli.sendStateEvent(roomId, EventType.RoomMember, content, userId);
}),
);
},

View file

@ -17,3 +17,12 @@ limitations under the License.
// Event type for room account data and room creation content used to mark rooms as virtual rooms
// (and store the ID of their native room)
export const VIRTUAL_ROOM_EVENT_TYPE = "im.vector.is_virtual_room";
export const JitsiCallMemberEventType = "io.element.video.member";
export interface JitsiCallMemberContent {
// Connected device IDs
devices: string[];
// Time at which this state event should be considered stale
expires_ts: number;
}

View file

@ -47,6 +47,7 @@ import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
import classNames from "classnames";
import { sortBy, uniqBy } from "lodash";
import { logger } from "matrix-js-sdk/src/logger";
import { SpaceChildEventContent } from "matrix-js-sdk/src/types";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { _t } from "../../languageHandler";
@ -727,7 +728,7 @@ const ManageButtons: React.FC<IManageButtonsProps> = ({ hierarchy, selected, set
const existingContent = hierarchy.getRelation(parentId, childId)?.content;
if (!existingContent || existingContent.suggested === suggested) continue;
const content = {
const content: SpaceChildEventContent = {
...existingContent,
suggested: !selectionAllSuggested,
};

View file

@ -1046,7 +1046,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
}
if (
!(await client.doesServerSupportUnstableFeature("org.matrix.msc2285.stable")) ||
!(await client.doesServerSupportUnstableFeature("org.matrix.msc2285.stable")) &&
!(await client.isVersionSupported("v1.4"))
) {
logger.warn(

View file

@ -20,7 +20,7 @@ import { Beacon, BeaconEvent, LocationAssetType } from "matrix-js-sdk/src/matrix
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import SmartMarker from "../location/SmartMarker";
import { SmartMarker } from "../location";
interface Props {
map: maplibregl.Map;

View file

@ -36,7 +36,7 @@ import MapFallback from "../location/MapFallback";
import { MapError } from "../location/MapError";
import { LocationShareError } from "../../../utils/location";
interface IProps {
export interface IProps {
roomId: Room["roomId"];
matrixClient: MatrixClient;
// open the map centered on this beacon's location

View file

@ -0,0 +1,31 @@
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Exports beacon components which touch maplibre-gs wrapped in React Suspense to enable code splitting
import React, { ComponentProps, lazy, Suspense } from "react";
import Spinner from "../elements/Spinner";
const BeaconViewDialogComponent = lazy(() => import("./BeaconViewDialog"));
export function BeaconViewDialog(props: ComponentProps<typeof BeaconViewDialogComponent>): JSX.Element {
return (
<Suspense fallback={<Spinner />}>
<BeaconViewDialogComponent {...props} />
</Suspense>
);
}

View file

@ -30,7 +30,7 @@ import { NotificationLevel } from "../../../stores/notifications/NotificationLev
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import DMRoomMap from "../../../utils/DMRoomMap";
import { clearRoomNotification } from "../../../utils/notifications";
import { clearRoomNotification, setMarkedUnreadState } from "../../../utils/notifications";
import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
import IconizedContextMenu, {
IconizedContextMenuCheckbox,
@ -45,13 +45,60 @@ import { useSettingValue } from "../../../hooks/useSettings";
export interface RoomGeneralContextMenuProps extends IContextMenuProps {
room: Room;
/**
* Called when the 'favourite' option is selected, after the menu has processed
* the mouse or keyboard event.
* @param event The event that caused the option to be selected.
*/
onPostFavoriteClick?: (event: ButtonEvent) => void;
/**
* Called when the 'low priority' option is selected, after the menu has processed
* the mouse or keyboard event.
* @param event The event that caused the option to be selected.
*/
onPostLowPriorityClick?: (event: ButtonEvent) => void;
/**
* Called when the 'invite' option is selected, after the menu has processed
* the mouse or keyboard event.
* @param event The event that caused the option to be selected.
*/
onPostInviteClick?: (event: ButtonEvent) => void;
/**
* Called when the 'copy link' option is selected, after the menu has processed
* the mouse or keyboard event.
* @param event The event that caused the option to be selected.
*/
onPostCopyLinkClick?: (event: ButtonEvent) => void;
/**
* Called when the 'settings' option is selected, after the menu has processed
* the mouse or keyboard event.
* @param event The event that caused the option to be selected.
*/
onPostSettingsClick?: (event: ButtonEvent) => void;
/**
* Called when the 'forget room' option is selected, after the menu has processed
* the mouse or keyboard event.
* @param event The event that caused the option to be selected.
*/
onPostForgetClick?: (event: ButtonEvent) => void;
/**
* Called when the 'leave' option is selected, after the menu has processed
* the mouse or keyboard event.
* @param event The event that caused the option to be selected.
*/
onPostLeaveClick?: (event: ButtonEvent) => void;
/**
* Called when the 'mark as read' option is selected, after the menu has processed
* the mouse or keyboard event.
* @param event The event that caused the option to be selected.
*/
onPostMarkAsReadClick?: (event: ButtonEvent) => void;
/**
* Called when the 'mark as unread' option is selected, after the menu has processed
* the mouse or keyboard event.
* @param event The event that caused the option to be selected.
*/
onPostMarkAsUnreadClick?: (event: ButtonEvent) => void;
}
/**
@ -67,6 +114,8 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
onPostSettingsClick,
onPostLeaveClick,
onPostForgetClick,
onPostMarkAsReadClick,
onPostMarkAsUnreadClick,
...props
}) => {
const cli = useContext(MatrixClientContext);
@ -213,18 +262,33 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
}
const { level } = useUnreadNotifications(room);
const markAsReadOption: JSX.Element | null =
level > NotificationLevel.None ? (
<IconizedContextMenuCheckbox
onClick={() => {
clearRoomNotification(room, cli);
onFinished?.();
}}
active={false}
label={_t("room|context_menu|mark_read")}
iconClassName="mx_RoomGeneralContextMenu_iconMarkAsRead"
/>
) : null;
const markAsReadOption: JSX.Element | null = (() => {
if (level > NotificationLevel.None) {
return (
<IconizedContextMenuOption
onClick={wrapHandler(() => {
clearRoomNotification(room, cli);
onFinished?.();
}, onPostMarkAsReadClick)}
label={_t("room|context_menu|mark_read")}
iconClassName="mx_RoomGeneralContextMenu_iconMarkAsRead"
/>
);
} else if (!roomTags.includes(DefaultTagID.Archived)) {
return (
<IconizedContextMenuOption
onClick={wrapHandler(() => {
setMarkedUnreadState(room, cli, true);
onFinished?.();
}, onPostMarkAsUnreadClick)}
label={_t("room|context_menu|mark_unread")}
iconClassName="mx_RoomGeneralContextMenu_iconMarkAsUnread"
/>
);
} else {
return null;
}
})();
const developerModeEnabled = useSettingValue<boolean>("developerMode");
const developerToolsOption = developerModeEnabled ? (

View file

@ -60,7 +60,7 @@ const BaseTool: React.FC<XOR<IMinProps, IProps>> = ({
let actionButton: ReactNode = null;
if (message) {
children = message;
} else if (onAction) {
} else if (onAction && actionLabel) {
const onActionClick = (): void => {
onAction().then((msg) => {
if (typeof msg === "string") {

View file

@ -38,7 +38,7 @@ export const StateEventEditor: React.FC<IEditorProps> = ({ mxEvent, onBack }) =>
);
const onSend = async ([eventType, stateKey]: string[], content: IContent): Promise<void> => {
await cli.sendStateEvent(context.room.roomId, eventType, content, stateKey);
await cli.sendStateEvent(context.room.roomId, eventType as any, content, stateKey);
};
const defaultContent = mxEvent ? stringify(mxEvent.getContent()) : undefined;

View file

@ -430,7 +430,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
onPrimaryButtonClick={this.onRecoveryKeyNext}
hasCancel={true}
cancelButton={_t("action|go_back")}
cancelButtonClass="danger"
cancelButtonClass="warning"
onCancel={this.onCancel}
focus={false}
primaryDisabled={!this.state.recoveryKeyValid}

View file

@ -16,22 +16,23 @@ limitations under the License.
*/
import React from "react";
import hljs from "highlight.js";
interface IProps {
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
interface Props {
language?: string;
children: string;
}
export default class SyntaxHighlight extends React.PureComponent<IProps> {
public render(): React.ReactNode {
const { children: content, language } = this.props;
const highlighted = language ? hljs.highlight(content, { language }) : hljs.highlightAuto(content);
export default function SyntaxHighlight({ children, language }: Props): JSX.Element {
const highlighted = useAsyncMemo(async () => {
const { default: highlight } = await import("highlight.js");
return language ? highlight.highlight(children, { language }) : highlight.highlightAuto(children);
}, [language, children]);
return (
<pre className={`mx_SyntaxHighlight hljs language-${highlighted.language}`}>
<code dangerouslySetInnerHTML={{ __html: highlighted.value }} />
</pre>
);
}
return (
<pre className={`mx_SyntaxHighlight hljs language-${highlighted?.language}`}>
{highlighted ? <code dangerouslySetInnerHTML={{ __html: highlighted.value }} /> : children}
</pre>
);
}

View file

@ -24,7 +24,7 @@ import { aboveLeftOf, useContextMenu, MenuProps } from "../../structures/Context
import { OverflowMenuContext } from "../rooms/MessageComposerButtons";
import LocationShareMenu from "./LocationShareMenu";
interface IProps {
export interface IProps {
roomId: string;
sender: RoomMember;
menuPosition?: MenuProps;

View file

@ -139,7 +139,7 @@ const onGeolocateError = (e: GeolocationPositionError): void => {
});
};
interface MapProps {
export interface MapProps {
id: string;
interactive?: boolean;
/**

View file

@ -18,7 +18,8 @@ import React, { ReactNode, useCallback, useEffect, useState } from "react";
import * as maplibregl from "maplibre-gl";
import { RoomMember } from "matrix-js-sdk/src/matrix";
import { createMarker, parseGeoUri } from "../../../utils/location";
import { parseGeoUri } from "../../../utils/location";
import { createMarker } from "../../../utils/location/map";
import Marker from "./Marker";
const useMapMarker = (
@ -66,7 +67,7 @@ const useMapMarker = (
};
};
interface SmartMarkerProps {
export interface SmartMarkerProps {
map: maplibregl.Map;
geoUri: string;
id?: string;

View file

@ -0,0 +1,71 @@
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Exports location components which touch maplibre-gs wrapped in React Suspense to enable code splitting
import React, { ComponentProps, lazy, Suspense } from "react";
import Spinner from "../elements/Spinner";
const MapComponent = lazy(() => import("./Map"));
export function Map(props: ComponentProps<typeof MapComponent>): JSX.Element {
return (
<Suspense fallback={<Spinner />}>
<MapComponent {...props} />
</Suspense>
);
}
const LocationPickerComponent = lazy(() => import("./LocationPicker"));
export function LocationPicker(props: ComponentProps<typeof LocationPickerComponent>): JSX.Element {
return (
<Suspense fallback={<Spinner />}>
<LocationPickerComponent {...props} />
</Suspense>
);
}
const SmartMarkerComponent = lazy(() => import("./SmartMarker"));
export function SmartMarker(props: ComponentProps<typeof SmartMarkerComponent>): JSX.Element {
return (
<Suspense fallback={<Spinner />}>
<SmartMarkerComponent {...props} />
</Suspense>
);
}
const LocationButtonComponent = lazy(() => import("./LocationButton"));
export function LocationButton(props: ComponentProps<typeof LocationButtonComponent>): JSX.Element {
return (
<Suspense fallback={<Spinner />}>
<LocationButtonComponent {...props} />
</Suspense>
);
}
const LocationViewDialogComponent = lazy(() => import("./LocationViewDialog"));
export function LocationViewDialog(props: ComponentProps<typeof LocationViewDialogComponent>): JSX.Element {
return (
<Suspense fallback={<Spinner />}>
<LocationViewDialogComponent {...props} />
</Suspense>
);
}

View file

@ -38,12 +38,11 @@ import { isSelfLocation, LocationShareError } from "../../../utils/location";
import { BeaconDisplayStatus, getBeaconDisplayStatus } from "../beacon/displayStatus";
import BeaconStatus from "../beacon/BeaconStatus";
import OwnBeaconStatus from "../beacon/OwnBeaconStatus";
import Map from "../location/Map";
import { Map, SmartMarker } from "../location";
import { MapError } from "../location/MapError";
import MapFallback from "../location/MapFallback";
import SmartMarker from "../location/SmartMarker";
import { GetRelationsForEvent } from "../rooms/EventTile";
import BeaconViewDialog from "../beacon/BeaconViewDialog";
import { BeaconViewDialog } from "../beacon";
import { IBodyProps } from "./IBodyProps";
const useBeaconState = (

View file

@ -29,9 +29,7 @@ import {
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import TooltipTarget from "../elements/TooltipTarget";
import { Alignment } from "../elements/Tooltip";
import LocationViewDialog from "../location/LocationViewDialog";
import Map from "../location/Map";
import SmartMarker from "../location/SmartMarker";
import { SmartMarker, Map, LocationViewDialog } from "../location";
import { IBodyProps } from "./IBodyProps";
import { createReconnectedListener } from "../../../utils/connection";

View file

@ -16,7 +16,6 @@ limitations under the License.
import React, { createRef, SyntheticEvent, MouseEvent } from "react";
import ReactDOM from "react-dom";
import highlight from "highlight.js";
import { MsgType } from "matrix-js-sdk/src/matrix";
import { TooltipProvider } from "@vector-im/compound-web";
@ -238,7 +237,9 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
pre.append(document.createElement("span"));
}
private highlightCode(code: HTMLElement): void {
private async highlightCode(code: HTMLElement): Promise<void> {
const { default: highlight } = await import("highlight.js");
if (code.textContent && code.textContent.length > MAX_HIGHLIGHT_LENGTH) {
console.log(
"Code block is bigger than highlight limit (" +

View file

@ -15,8 +15,9 @@ limitations under the License.
*/
import React, { ChangeEvent, ContextType, createRef, SyntheticEvent } from "react";
import { IContent, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { RoomCanonicalAliasEventContent } from "matrix-js-sdk/src/types";
import EditableItemList from "../elements/EditableItemList";
import { _t } from "../../../languageHandler";
@ -169,7 +170,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
updatingCanonicalAlias: true,
});
const eventContent: IContent = {
const eventContent: RoomCanonicalAliasEventContent = {
alt_aliases: this.state.altAliases,
};
@ -197,7 +198,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
updatingCanonicalAlias: true,
});
const eventContent: IContent = {};
const eventContent: RoomCanonicalAliasEventContent = {};
if (this.state.canonicalAlias) {
eventContent["alias"] = this.state.canonicalAlias;

View file

@ -24,7 +24,7 @@ import { CollapsibleButton } from "./CollapsibleButton";
import { MenuProps } from "../../structures/ContextMenu";
import dis from "../../../dispatcher/dispatcher";
import ErrorDialog from "../dialogs/ErrorDialog";
import LocationButton from "../location/LocationButton";
import { LocationButton } from "../location";
import Modal from "../../../Modal";
import PollCreateDialog from "../elements/PollCreateDialog";
import { MatrixClientPeg } from "../../../MatrixClientPeg";

View file

@ -102,7 +102,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
if (notification.isIdle && !notification.knocked) return null;
if (hideIfDot && notification.level < NotificationLevel.Notification) {
// This would just be a dot and we've been told not to show dots, so don't show it
if (!notification.hasUnreadCount) return null;
return null;
}
const commonProps: React.ComponentProps<typeof StatelessNotificationBadge> = {

View file

@ -70,6 +70,16 @@ export const StatelessNotificationBadge = forwardRef<HTMLDivElement, XOR<Props,
symbol = formatCount(count);
}
// We show a dot if either:
// * The props force us to, or
// * It's just an activity-level notification or (in theory) lower and the room isn't knocked
const badgeType =
forceDot || (level <= NotificationLevel.Activity && !knocked)
? "dot"
: !symbol || symbol.length < 3
? "badge_2char"
: "badge_3char";
const classes = classNames({
mx_NotificationBadge: true,
mx_NotificationBadge_visible: isEmptyBadge || knocked ? true : hasUnreadCount,
@ -77,10 +87,10 @@ export const StatelessNotificationBadge = forwardRef<HTMLDivElement, XOR<Props,
mx_NotificationBadge_level_highlight: level >= NotificationLevel.Highlight,
mx_NotificationBadge_knocked: knocked,
// At most one of mx_NotificationBadge_dot, mx_NotificationBadge_2char, mx_NotificationBadge_3char
mx_NotificationBadge_dot: (isEmptyBadge && !knocked) || forceDot,
mx_NotificationBadge_2char: !forceDot && symbol && symbol.length > 0 && symbol.length < 3,
mx_NotificationBadge_3char: !forceDot && symbol && symbol.length > 2,
// Exactly one of mx_NotificationBadge_dot, mx_NotificationBadge_2char, mx_NotificationBadge_3char
mx_NotificationBadge_dot: badgeType === "dot",
mx_NotificationBadge_2char: badgeType === "badge_2char",
mx_NotificationBadge_3char: badgeType === "badge_3char",
});
if (props.onClick) {

View file

@ -363,6 +363,12 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
onPostLeaveClick={(ev: ButtonEvent) =>
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem", ev)
}
onPostMarkAsReadClick={(ev: ButtonEvent) =>
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkRead", ev)
}
onPostMarkAsUnreadClick={(ev: ButtonEvent) =>
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkUnread", ev)
}
/>
)}
</React.Fragment>

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { MatrixEvent, Room, RoomStateEvent, EventType } from "matrix-js-sdk/src/matrix";
import { EventType, MatrixEvent, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { Button, Text } from "@vector-im/compound-web";
@ -101,7 +101,7 @@ export default class ThirdPartyMemberInfo extends React.Component<IProps, IState
public onKickClick = (): void => {
MatrixClientPeg.safeGet()
.sendStateEvent(this.state.roomId, "m.room.third_party_invite", {}, this.state.stateKey)
.sendStateEvent(this.state.roomId, EventType.RoomThirdPartyInvite, {}, this.state.stateKey)
.catch((err) => {
logger.error(err);

View file

@ -0,0 +1,142 @@
/*
*
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
import React, { useState, JSX, PropsWithChildren } from "react";
import { Button } from "@vector-im/compound-web";
import { compare } from "matrix-js-sdk/src/utils";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import PowerSelector from "../elements/PowerSelector";
import { _t } from "../../../languageHandler";
import SettingsFieldset from "./SettingsFieldset";
/**
* Display in a fieldset, the power level of the users and allow to change them.
* The apply button is disabled until the power level of an user is changed.
* If there is no user to display, the children is displayed instead.
*/
interface PowerLevelSelectorProps {
/**
* The power levels of the users
* The key is the user id and the value is the power level
*/
userLevels: Record<string, number>;
/**
* Whether the user can change the power levels of other users
*/
canChangeLevels: boolean;
/**
* The current user power level
*/
currentUserLevel: number;
/**
* The callback when the apply button is clicked
* @param value - new power level for the user
* @param userId - the user id
*/
onClick: (value: number, userId: string) => void;
/**
* Filter the users to display
* @param user
*/
filter: (user: string) => boolean;
/**
* The title of the fieldset
*/
title: string;
}
export function PowerLevelSelector({
userLevels,
canChangeLevels,
currentUserLevel,
onClick,
filter,
title,
children,
}: PropsWithChildren<PowerLevelSelectorProps>): JSX.Element | null {
const matrixClient = useMatrixClientContext();
const [currentPowerLevel, setCurrentPowerLevel] = useState<{ value: number; userId: string } | null>(null);
// If the power level has changed, we need to enable the apply button
const powerLevelChanged = Boolean(
currentPowerLevel && currentPowerLevel.value !== userLevels[currentPowerLevel?.userId],
);
// We sort the users by power level, then we filter them
const users = Object.keys(userLevels)
.sort((userA, userB) => sortUser(userA, userB, userLevels))
.filter(filter);
// No user to display, we return the children into fragment to convert it to JSX.Element type
if (!users.length) return <>{children}</>;
return (
<SettingsFieldset legend={title}>
{users.map((userId) => {
// We only want to display users with a valid power level aka an integer
if (!Number.isInteger(userLevels[userId])) return;
const isMe = userId === matrixClient.getUserId();
// If I can change levels, I can change the level of anyone with a lower level than mine
const canChange = canChangeLevels && (userLevels[userId] < currentUserLevel || isMe);
// When the new power level is selected, the fields are rerendered and we need to keep the current value
const userLevel = currentPowerLevel?.userId === userId ? currentPowerLevel?.value : userLevels[userId];
return (
<PowerSelector
value={userLevel}
disabled={!canChange}
label={userId}
key={userId}
onChange={(value) => setCurrentPowerLevel({ value, userId })}
/>
);
})}
<Button
size="sm"
kind="primary"
// mx_Dialog_nonDialogButton is necessary to avoid the Dialog CSS to override the button style
className="mx_Dialog_nonDialogButton mx_PowerLevelSelector_Button"
onClick={() => {
if (currentPowerLevel !== null) {
onClick(currentPowerLevel.value, currentPowerLevel.userId);
setCurrentPowerLevel(null);
}
}}
disabled={!powerLevelChanged}
aria-label={_t("action|apply")}
>
{_t("action|apply")}
</Button>
</SettingsFieldset>
);
}
/**
* Sort the users by power level, then by name
* @param userA
* @param userB
* @param userLevels
*/
function sortUser(userA: string, userB: string, userLevels: PowerLevelSelectorProps["userLevels"]): number {
const powerLevelDiff = userLevels[userA] - userLevels[userB];
return powerLevelDiff !== 0 ? powerLevelDiff : compare(userA.toLocaleLowerCase(), userB.toLocaleLowerCase());
}

View file

@ -16,7 +16,7 @@ limitations under the License.
*/
import React from "react";
import { ThreepidMedium } from "matrix-js-sdk/src/matrix";
import { IAuthData, ThreepidMedium } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { _t, UserFriendlyError } from "../../../../languageHandler";
@ -216,7 +216,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
const address = this.state.verifyMsisdn;
this.state.addTask
?.haveMsisdnToken(token)
.then(([finished] = []) => {
.then(([finished]: [success?: boolean, result?: IAuthData | Error | null] = []) => {
let newPhoneNumber = this.state.newPhoneNumber;
if (finished !== false) {
const msisdns = [...this.props.msisdns, { address, medium: ThreepidMedium.Phone }];

View file

@ -19,7 +19,7 @@ import { EventType, RoomMember, RoomState, RoomStateEvent, Room, IContent } from
import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger";
import { throttle, get } from "lodash";
import { compare } from "matrix-js-sdk/src/utils";
import { RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types";
import { _t, _td, TranslationKey } from "../../../../../languageHandler";
import AccessibleButton from "../../../elements/AccessibleButton";
@ -35,6 +35,7 @@ import { AddPrivilegedUsers } from "../../AddPrivilegedUsers";
import SettingsTab from "../SettingsTab";
import { SettingsSection } from "../../shared/SettingsSection";
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
import { PowerLevelSelector } from "../../PowerLevelSelector";
interface IEventShowOpts {
isState?: boolean;
@ -179,7 +180,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
const client = this.context;
const room = this.props.room;
const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
let plContent = plEvent?.getContent() ?? {};
let plContent = plEvent?.getContent<RoomPowerLevelsEventContent>() ?? {};
// Clone the power levels just in case
plContent = Object.assign({}, plContent);
@ -193,7 +194,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
} else {
const keyPath = powerLevelKey.split(".");
let parentObj: IContent = {};
let currentObj = plContent;
let currentObj: IContent = plContent;
for (const key of keyPath) {
if (!currentObj[key]) {
currentObj[key] = {};
@ -223,7 +224,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
const client = this.context;
const room = this.props.room;
const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
let plContent = plEvent?.getContent() ?? {};
let plContent = plEvent?.getContent<RoomPowerLevelsEventContent>() ?? {};
// Clone the power levels just in case
plContent = Object.assign({}, plContent);
@ -241,9 +242,6 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
title: _t("room_settings|permissions|error_changing_pl_title"),
description: _t("room_settings|permissions|error_changing_pl_description"),
});
// Rethrow so that the PowerSelector can roll back
throw e;
}
};
@ -353,65 +351,29 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
let privilegedUsersSection = <div>{_t("room_settings|permissions|no_privileged_users")}</div>;
let mutedUsersSection;
if (Object.keys(userLevels).length) {
const privilegedUsers: JSX.Element[] = [];
const mutedUsers: JSX.Element[] = [];
privilegedUsersSection = (
<PowerLevelSelector
title={_t("room_settings|permissions|privileged_users_section")}
userLevels={userLevels}
canChangeLevels={canChangeLevels}
currentUserLevel={currentUserLevel}
onClick={this.onUserPowerLevelChanged}
filter={(user) => userLevels[user] > defaultUserLevel}
>
<div>{_t("room_settings|permissions|no_privileged_users")}</div>
</PowerLevelSelector>
);
Object.keys(userLevels).forEach((user) => {
if (!Number.isInteger(userLevels[user])) return;
const isMe = user === client.getUserId();
const canChange = canChangeLevels && (userLevels[user] < currentUserLevel || isMe);
if (userLevels[user] > defaultUserLevel) {
// privileged
privilegedUsers.push(
<PowerSelector
value={userLevels[user]}
disabled={!canChange}
label={user}
key={user}
powerLevelKey={user} // Will be sent as the second parameter to `onChange`
onChange={this.onUserPowerLevelChanged}
/>,
);
} else if (userLevels[user] < defaultUserLevel) {
// muted
mutedUsers.push(
<PowerSelector
value={userLevels[user]}
disabled={!canChange}
label={user}
key={user}
powerLevelKey={user} // Will be sent as the second parameter to `onChange`
onChange={this.onUserPowerLevelChanged}
/>,
);
}
});
// comparator for sorting PL users lexicographically on PL descending, MXID ascending. (case-insensitive)
const comparator = (a: JSX.Element, b: JSX.Element): number => {
const aKey = a.key as string;
const bKey = b.key as string;
const plDiff = userLevels[bKey] - userLevels[aKey];
return plDiff !== 0 ? plDiff : compare(aKey.toLocaleLowerCase(), bKey.toLocaleLowerCase());
};
privilegedUsers.sort(comparator);
mutedUsers.sort(comparator);
if (privilegedUsers.length) {
privilegedUsersSection = (
<SettingsFieldset legend={_t("room_settings|permissions|privileged_users_section")}>
{privilegedUsers}
</SettingsFieldset>
);
}
if (mutedUsers.length) {
mutedUsersSection = (
<SettingsFieldset legend={_t("room_settings|permissions|muted_users_section")}>
{mutedUsers}
</SettingsFieldset>
);
}
mutedUsersSection = (
<PowerLevelSelector
title={_t("room_settings|permissions|muted_users_section")}
userLevels={userLevels}
canChangeLevels={canChangeLevels}
currentUserLevel={currentUserLevel}
onClick={this.onUserPowerLevelChanged}
filter={(user) => userLevels[user] < defaultUserLevel}
/>
);
}
const banned = room.getMembersWithMembership(KnownMembership.Ban);

View file

@ -1892,6 +1892,7 @@
"forget": "Forget Room",
"low_priority": "Low Priority",
"mark_read": "Mark as read",
"mark_unread": "Mark as unread",
"mentions_only": "Mentions only",
"notifications_default": "Match default setting",
"notifications_mute": "Mute room",

View file

@ -16,12 +16,14 @@ limitations under the License.
// Inspiration largely taken from Mjolnir itself
import { EventType } from "matrix-js-sdk/src/matrix";
import { ListRule, RECOMMENDATION_BAN, recommendationToStable } from "./ListRule";
import { MatrixClientPeg } from "../MatrixClientPeg";
export const RULE_USER = "m.policy.rule.user";
export const RULE_ROOM = "m.policy.rule.room";
export const RULE_SERVER = "m.policy.rule.server";
export const RULE_USER = EventType.PolicyRuleUser;
export const RULE_ROOM = EventType.PolicyRuleRoom;
export const RULE_SERVER = EventType.PolicyRuleServer;
// m.room.* events are legacy from when MSC2313 changed to m.policy.* last minute.
export const USER_RULE_TYPES = [RULE_USER, "m.room.rule.user", "org.matrix.mjolnir.rule.user"];
@ -29,7 +31,9 @@ export const ROOM_RULE_TYPES = [RULE_ROOM, "m.room.rule.room", "org.matrix.mjoln
export const SERVER_RULE_TYPES = [RULE_SERVER, "m.room.rule.server", "org.matrix.mjolnir.rule.server"];
export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES];
export function ruleTypeToStable(rule: string): string | null {
export function ruleTypeToStable(
rule: string,
): EventType.PolicyRuleUser | EventType.PolicyRuleRoom | EventType.PolicyRuleServer | null {
if (USER_RULE_TYPES.includes(rule)) {
return RULE_USER;
}
@ -72,7 +76,7 @@ export class BanList {
{
entity: entity,
reason: reason,
recommendation: recommendationToStable(RECOMMENDATION_BAN, true),
recommendation: recommendationToStable(RECOMMENDATION_BAN, true)!,
},
"rule:" + entity,
);

View file

@ -14,14 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// We are using experimental APIs here, so we need to disable the linter
// eslint-disable-next-line no-restricted-imports
import { PolicyRecommendation } from "matrix-js-sdk/src/models/invites-ignorer";
import { MatrixGlob } from "../utils/MatrixGlob";
// Inspiration largely taken from Mjolnir itself
export const RECOMMENDATION_BAN = "m.ban";
export const RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"];
export const RECOMMENDATION_BAN = PolicyRecommendation.Ban;
export const RECOMMENDATION_BAN_TYPES: PolicyRecommendation[] = [
RECOMMENDATION_BAN,
"org.matrix.mjolnir.ban" as PolicyRecommendation,
];
export function recommendationToStable(recommendation: string, unstable = true): string | null {
export function recommendationToStable(
recommendation: PolicyRecommendation,
unstable = true,
): PolicyRecommendation | null {
if (RECOMMENDATION_BAN_TYPES.includes(recommendation)) {
return unstable ? RECOMMENDATION_BAN_TYPES[RECOMMENDATION_BAN_TYPES.length - 1] : RECOMMENDATION_BAN;
}
@ -35,7 +45,7 @@ export class ListRule {
private readonly _reason: string;
private readonly _kind: string;
public constructor(entity: string, action: string, reason: string, kind: string) {
public constructor(entity: string, action: PolicyRecommendation, reason: string, kind: string) {
this._glob = new MatrixGlob(entity);
this._entity = entity;
this._action = recommendationToStable(action, false);

View file

@ -58,6 +58,7 @@ import { UPDATE_EVENT } from "../stores/AsyncStore";
import { getJoinedNonFunctionalMembers } from "../utils/room/getJoinedNonFunctionalMembers";
import { isVideoRoom } from "../utils/video-rooms";
import { FontWatcher } from "../settings/watchers/FontWatcher";
import { JitsiCallMemberContent, JitsiCallMemberEventType } from "../call-types";
const TIMEOUT_MS = 16000;
@ -322,18 +323,13 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
private beforeUnload = (): void => this.setDisconnected();
}
export interface JitsiCallMemberContent {
// Connected device IDs
devices: string[];
// Time at which this state event should be considered stale
expires_ts: number;
}
export type { JitsiCallMemberContent };
/**
* A group call using Jitsi as a backend.
*/
export class JitsiCall extends Call {
public static readonly MEMBER_EVENT_TYPE = "io.element.video.member";
public static readonly MEMBER_EVENT_TYPE = JitsiCallMemberEventType;
public readonly STUCK_DEVICE_TIMEOUT_MS = 1000 * 60 * 60; // 1 hour
private resendDevicesTimer: number | null = null;

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, MatrixEvent, RoomState, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { MatrixClient, MatrixEvent, RoomState, RoomStateEvent, StateEvents } from "matrix-js-sdk/src/matrix";
import { defer } from "matrix-js-sdk/src/utils";
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
@ -24,6 +24,9 @@ import { SettingLevel } from "../SettingLevel";
import { WatchManager } from "../WatchManager";
const DEFAULT_SETTINGS_EVENT_TYPE = "im.vector.web.settings";
const PREVIEW_URLS_EVENT_TYPE = "org.matrix.room.preview_urls";
type RoomSettingsEventType = typeof DEFAULT_SETTINGS_EVENT_TYPE | typeof PREVIEW_URLS_EVENT_TYPE;
/**
* Gets and sets settings at the "room" level.
@ -88,7 +91,12 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
}
// helper function to send state event then await it being echoed back
private async sendStateEvent(roomId: string, eventType: string, field: string, value: any): Promise<void> {
private async sendStateEvent<K extends RoomSettingsEventType, F extends keyof StateEvents[K]>(
roomId: string,
eventType: K,
field: F,
value: StateEvents[K][F],
): Promise<void> {
const content = this.getSettings(roomId, eventType) || {};
content[field] = value;

View file

@ -63,6 +63,7 @@ import { ActionPayload } from "../dispatcher/payloads";
import { CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJoinPayload";
import { SubmitAskToJoinPayload } from "../dispatcher/payloads/SubmitAskToJoinPayload";
import { ModuleRunner } from "../modules/ModuleRunner";
import { setMarkedUnreadState } from "../utils/notifications";
const NUM_JOIN_RETRY = 5;
@ -498,6 +499,8 @@ export class RoomViewStore extends EventEmitter {
if (room) {
pauseNonLiveBroadcastFromOtherRoom(room, this.stores.voiceBroadcastPlaybacksStore);
this.doMaybeSetCurrentVoiceBroadcastPlayback(room);
await setMarkedUnreadState(room, MatrixClientPeg.safeGet(), false);
}
} else if (payload.room_alias) {
// Try the room alias to room ID navigation cache first to avoid

View file

@ -24,6 +24,7 @@ import { readReceiptChangeIsFor } from "../../utils/read-receipts";
import * as RoomNotifs from "../../RoomNotifs";
import { NotificationState } from "./NotificationState";
import SettingsStore from "../../settings/SettingsStore";
import { MARKED_UNREAD_TYPE_STABLE, MARKED_UNREAD_TYPE_UNSTABLE } from "../../utils/notifications";
export class RoomNotificationState extends NotificationState implements IDestroyable {
public constructor(
@ -37,6 +38,7 @@ export class RoomNotificationState extends NotificationState implements IDestroy
this.room.on(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated);
this.room.on(RoomEvent.Timeline, this.handleRoomEventUpdate);
this.room.on(RoomEvent.Redaction, this.handleRoomEventUpdate);
this.room.on(RoomEvent.AccountData, this.handleRoomAccountDataUpdate);
this.room.on(RoomEvent.UnreadNotifications, this.handleNotificationCountUpdate); // for server-sent counts
cli.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
@ -52,6 +54,7 @@ export class RoomNotificationState extends NotificationState implements IDestroy
this.room.removeListener(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated);
this.room.removeListener(RoomEvent.Timeline, this.handleRoomEventUpdate);
this.room.removeListener(RoomEvent.Redaction, this.handleRoomEventUpdate);
this.room.removeListener(RoomEvent.AccountData, this.handleRoomAccountDataUpdate);
cli.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
cli.removeListener(ClientEvent.AccountData, this.handleAccountDataUpdate);
}
@ -91,6 +94,12 @@ export class RoomNotificationState extends NotificationState implements IDestroy
}
};
private handleRoomAccountDataUpdate = (ev: MatrixEvent): void => {
if ([MARKED_UNREAD_TYPE_STABLE, MARKED_UNREAD_TYPE_UNSTABLE].includes(ev.getType())) {
this.updateNotificationState();
}
};
private updateNotificationState(): void {
const snapshot = this.snapshot();

View file

@ -43,6 +43,7 @@ import {
Room,
Direction,
THREAD_RELATION_TYPE,
StateEvents,
} from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import {
@ -241,7 +242,19 @@ export class StopGapWidgetDriver extends WidgetDriver {
return allAllowed;
}
public async sendEvent<K extends keyof StateEvents>(
eventType: K,
content: StateEvents[K],
stateKey?: string,
targetRoomId?: string,
): Promise<ISendEventDetails>;
public async sendEvent(
eventType: Exclude<EventType, keyof StateEvents>,
content: IContent,
stateKey: null,
targetRoomId?: string,
): Promise<ISendEventDetails>;
public async sendEvent<K extends keyof StateEvents>(
eventType: string,
content: IContent,
stateKey?: string | null,
@ -255,7 +268,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
let r: { event_id: string } | null;
if (stateKey !== null) {
// state event
r = await client.sendStateEvent(roomId, eventType, content, stateKey);
r = await client.sendStateEvent(roomId, eventType as K, content as StateEvents[K], stateKey);
} else if (eventType === EventType.RoomRedaction) {
// special case: extract the `redacts` property and call redact
r = await client.redactEvent(roomId, content["redacts"]);

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