Merge branch 'develop' into andybalaam/stas-demydiuk-membership-type3
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
61
playwright/e2e/room_options/marked_unread.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
|
@ -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");
|
||||
});
|
||||
});
|
7
playwright/global.d.ts
vendored
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
);
|
||||
|
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
@ -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"
|
||||
]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
21
res/css/views/settings/_PowerLevelSelector.pcss
Normal 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;
|
||||
}
|
4
res/img/element-icons/roomlist/mark-as-unread.svg
Normal 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 |
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
/* ******************** */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
6
src/@types/global.d.ts
vendored
|
@ -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
|
||||
|
|
30
src/@types/matrix-js-sdk.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}>,
|
||||
|
|
|
@ -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);
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
31
src/components/views/beacon/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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 ? (
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -139,7 +139,7 @@ const onGeolocateError = (e: GeolocationPositionError): void => {
|
|||
});
|
||||
};
|
||||
|
||||
interface MapProps {
|
||||
export interface MapProps {
|
||||
id: string;
|
||||
interactive?: boolean;
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
71
src/components/views/location/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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 = (
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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 (" +
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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> = {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
142
src/components/views/settings/PowerLevelSelector.tsx
Normal 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());
|
||||
}
|
|
@ -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 }];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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"]);
|
||||
|
|