Merge branch 'develop' of ssh://github.com/element-hq/element-web into t3chguy/merge-modules
# Conflicts: # pnpm-lock.yaml # pnpm-workspace.yaml
@@ -88,7 +88,7 @@
|
||||
"app-builder-lib": "26.12.1",
|
||||
"chokidar": "^5.0.0",
|
||||
"detect-libc": "^2.0.0",
|
||||
"electron": "42.3.0",
|
||||
"electron": "42.3.3",
|
||||
"electron-builder": "26.12.1",
|
||||
"electron-builder-squirrel-windows": "26.12.1",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
|
||||
@@ -14,8 +14,8 @@ WORKDIR /src
|
||||
# Install dependencies
|
||||
COPY --parents package.json pnpm-lock.yaml pnpm-workspace.yaml patches scripts **/package.json /src/
|
||||
RUN corepack enable
|
||||
RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-link-repos.sh
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-link-repos.sh
|
||||
|
||||
# Build
|
||||
COPY --link --exclude=.git --exclude=apps/web/docker . /src
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
"opus-recorder": "^8.0.3",
|
||||
"pako": "^2.0.3",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"posthog-js": "1.376.6",
|
||||
"posthog-js": "1.381.0",
|
||||
"qrcode": "1.5.4",
|
||||
"re-resizable": "6.11.2",
|
||||
"react": "catalog:",
|
||||
|
||||
@@ -371,5 +371,33 @@ test.describe("Room list custom sections", () => {
|
||||
// Room is back in the Chats section
|
||||
await assertRoomInSection(page, "Chats", "my room");
|
||||
});
|
||||
|
||||
test("should remove a room from a custom section via the 'Remove from section' menu entry", async ({
|
||||
page,
|
||||
app,
|
||||
}) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
|
||||
// Move the room to the Work section
|
||||
let roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
await page.getByRole("menuitem", { name: "Move to" }).hover();
|
||||
await page.getByRole("menuitem", { name: "Work" }).click();
|
||||
|
||||
await assertRoomInSection(page, "Work", "my room");
|
||||
|
||||
// Open the More Options menu and click "Remove from section"
|
||||
roomItem = roomList.getByRole("row", { name: "Open room my room" });
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
await page.getByRole("menuitem", { name: "Remove from section" }).click();
|
||||
|
||||
// Room is back in the Chats section
|
||||
await assertRoomInSection(page, "Chats", "my room");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js";
|
||||
|
||||
const DOCKER_IMAGE =
|
||||
"ghcr.io/element-hq/synapse:develop@sha256:3e079baccf55f491bd40c8b218789f0e8b31f466f6e23b14baab4a52ca845284";
|
||||
"ghcr.io/element-hq/synapse:develop@sha256:e5e02e25d3f3c38ae399acf74c359e971c2fd1381b88e0031a1faa9c785a0d91";
|
||||
|
||||
/**
|
||||
* SynapseContainer which freezes the docker digest to stabilise tests,
|
||||
|
||||
@@ -21,3 +21,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CreateSectionDialog_edition > .mx_Dialog_header {
|
||||
margin-bottom: var(--cpd-space-6x);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
.mx_DevTools_content {
|
||||
overflow-y: auto;
|
||||
/* Give room for focus outlines */
|
||||
padding-inline-start: var(--cpd-space-0-5x);
|
||||
padding-block-end: var(--cpd-space-0-5x);
|
||||
}
|
||||
|
||||
.mx_DevTools_RoomStateExplorer_query {
|
||||
|
||||
@@ -202,8 +202,18 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
|
||||
OwnProfileStore.instance.on(UPDATE_EVENT, this.refreshBackgroundImage);
|
||||
this.refreshBackgroundImage();
|
||||
}
|
||||
|
||||
this.resizerViewModel = new ResizerViewModel();
|
||||
private getResizerViewModel(): ResizerViewModel {
|
||||
if (!this.resizerViewModel) {
|
||||
this.resizerViewModel = new ResizerViewModel();
|
||||
}
|
||||
return this.resizerViewModel;
|
||||
}
|
||||
|
||||
private disposeResizerViewModel(): void {
|
||||
this.resizerViewModel?.dispose();
|
||||
this.resizerViewModel = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -739,6 +749,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
break;
|
||||
default: {
|
||||
if (moduleRenderer) {
|
||||
// Since the view will be removed, remove the vm as well
|
||||
this.disposeResizerViewModel();
|
||||
pageElement = moduleRenderer();
|
||||
} else {
|
||||
console.warn(`Couldn't render page type "${this.props.page_type}"`);
|
||||
@@ -797,14 +809,15 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
const roomView = <div className="mx_RoomView_wrapper">{pageElement}</div>;
|
||||
|
||||
let content: React.ReactNode;
|
||||
if (useNewRoomList && this.resizerViewModel && !moduleRenderer) {
|
||||
const resizerViewModel = !moduleRenderer ? this.getResizerViewModel() : undefined;
|
||||
if (useNewRoomList && resizerViewModel && !moduleRenderer) {
|
||||
// New room list owned by element-web: resizable layout with a draggable separator.
|
||||
// The SpacePanel lives inside GroupView (leftPanel omits it when the new room list is enabled).
|
||||
content = (
|
||||
<GroupView vm={this.resizerViewModel}>
|
||||
<GroupView vm={resizerViewModel}>
|
||||
<SpacePanel />
|
||||
<LeftResizablePanelView
|
||||
vm={this.resizerViewModel}
|
||||
vm={resizerViewModel}
|
||||
className="mx_LeftPanel_panel"
|
||||
minSize="200px"
|
||||
maxSize="370px"
|
||||
@@ -812,7 +825,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
>
|
||||
{leftPanel}
|
||||
</LeftResizablePanelView>
|
||||
<SeparatorView className="mx_Separator" vm={this.resizerViewModel} />
|
||||
<SeparatorView className="mx_Separator" vm={resizerViewModel} />
|
||||
<Panel className="mx_LeftPanel_panel">{roomView}</Panel>
|
||||
</GroupView>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import React, { useState, type JSX } from "react";
|
||||
import { Flex } from "@element-hq/web-shared-components";
|
||||
import { Form, Text } from "@vector-im/compound-web";
|
||||
import classNames from "classnames";
|
||||
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
@@ -37,15 +38,19 @@ export function CreateSectionDialog({ onFinished, sectionToEdit }: CreateSection
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className="mx_CreateSectionDialog"
|
||||
className={classNames("mx_CreateSectionDialog", {
|
||||
mx_CreateSectionDialog_edition: isEdition,
|
||||
})}
|
||||
onFinished={() => onFinished(false, value)}
|
||||
title={isEdition ? _t("create_section_dialog|title_edition") : _t("create_section_dialog|title")}
|
||||
hasCancel={true}
|
||||
>
|
||||
<Flex gap="var(--cpd-space-6x)" direction="column" className="mx_CreateSectionDialog_content">
|
||||
<Text as="span" weight="medium">
|
||||
{_t("create_section_dialog|description")}
|
||||
</Text>
|
||||
{!isEdition && (
|
||||
<Text as="span" weight="medium">
|
||||
{_t("create_section_dialog|description")}
|
||||
</Text>
|
||||
)}
|
||||
<Form.Root
|
||||
className="mx_CreateSectionDialog_form"
|
||||
onSubmit={(e) => {
|
||||
|
||||
@@ -246,7 +246,9 @@
|
||||
"check_code_heading": "Sisesta teises seadmes kuvatav number",
|
||||
"check_code_input_label": "2-kohaline kood",
|
||||
"check_code_mismatch": "Numbrid ei klapi",
|
||||
"choose_desktop_computer": "Vali „%(desktopComputer)s“",
|
||||
"completing_setup": "Lõpetame uue seadme seadistamise",
|
||||
"desktop_computer": "Lauaarvuti",
|
||||
"error_etag_missing": "Tekkis ootamatu viga. Selle põhjuseks võivad olla brauseri lisamoodul, proksiserveri seadistused või koduserveri vigased seadistused.",
|
||||
"error_expired": "Sisselogimine aegus. Palun proovi uuesti.",
|
||||
"error_expired_title": "Sisselogimine ei jõudnud õigeaegselt lõpule",
|
||||
@@ -267,15 +269,22 @@
|
||||
"error_user_declined": "Sa keeldusid teises seadmes sisselogimispäringust.",
|
||||
"error_user_declined_title": "Sa keeldusid sisselogimast",
|
||||
"follow_remaining_instructions": "Teise seadme verifitseerimiseks järgi ülejäänud juhiseid",
|
||||
"open_element_mobile_device": "Ava oma nutiseadmes %(brand)s",
|
||||
"open_element_other_device": "Ava %(brand)s oma teises seadmes",
|
||||
"point_the_camera": "Suuna kaamera siin näidatud QR-koodi peale",
|
||||
"scan_code_instruction": "Skaneeri QR-koodi teise seadmega",
|
||||
"ready_to_scan": "Skaneerimiseks valmis",
|
||||
"scan_code_instruction": "Skaneeri seda QR-koodi",
|
||||
"scan_code_instruction_reciprocate": "Skaneeri seda QR-koodi teise seadmega",
|
||||
"scan_qr_code": "Logi sisse QR-koodi alusel",
|
||||
"security_code_prompt": "Kui seda küsitakse, sisesta teises seadmes allolev kood.",
|
||||
"security_code_prompt": "Sinu teenusepakkuja võib sisselogimise verifitseerimiseks küsida seda koodi.",
|
||||
"security_code_title": "Sinu verifitseerimiskood",
|
||||
"select_qr_code": "Val i „%(scanQRCode)s“",
|
||||
"select_ready_to_scan": "Vali „%(readyToScan)s“ ja skaneeri siinnäidatud QR-koodi",
|
||||
"start_over": "Alusta otsast peale",
|
||||
"tap_avatar_link_new_device": "Klõpsa oma tunnuspilti ja vali „%(linkNewDevice)s“",
|
||||
"unsupported_explainer": "Sinu teenusepakkuja ei toeta võimalust logida sisse QR-koodi abil.",
|
||||
"unsupported_heading": "QR-koodi kasutamine pole toetatud",
|
||||
"waiting_for_device": "Ootame, et teine seade logiks võrku"
|
||||
"waiting_for_device": "Ootame sinu teise seadme järgi"
|
||||
},
|
||||
"register_action": "Loo konto",
|
||||
"registration": {
|
||||
@@ -334,8 +343,10 @@
|
||||
"sign_in_description": "Jätkamaks kasuta oma kontot.",
|
||||
"sign_in_instead": "Pigem logi sisse",
|
||||
"sign_in_instead_prompt": "Sul juba on kasutajakonto olemas? <a>Siis logi siin sisse</a>",
|
||||
"sign_in_manually": "Logi sisse käsitsi",
|
||||
"sign_in_or_register": "Logi sisse või loo uus konto",
|
||||
"sign_in_or_register_description": "Jätkamaks kasuta oma kontot või loo uus konto.",
|
||||
"sign_in_with_qr": "Logi sisse QR-koodi alusel",
|
||||
"sign_in_with_sso": "Logi sisse ühekordse sisselogimise abil",
|
||||
"signing_in": "Login sisse…",
|
||||
"soft_logout": {
|
||||
@@ -1517,6 +1528,7 @@
|
||||
"bridge_state_manager": "Seda võrgusilda haldab <user />.",
|
||||
"bridge_state_workspace": "Tööruum: <networkLink/>",
|
||||
"click_for_info": "Lisateabe jaoks klõpsi",
|
||||
"config_only": "Saab sisselülitada ainult config.json failist.",
|
||||
"currently_experimental": "Parasjagu katsejärgus.",
|
||||
"custom_themes": "Kohandatud kujunduste lisamise võimalus",
|
||||
"dynamic_room_predecessors": "Jututoa dünaamilised eellased",
|
||||
@@ -1561,6 +1573,7 @@
|
||||
"leave_beta_reload": "Beeta-funktsionaalsuste kasutamise lõpetamisel laadime uuesti rakenduse %(brand)s.",
|
||||
"location_share_live": "Asukoha jagamine reaalajas",
|
||||
"location_share_live_description": "Tegemist on ajutise ja esialgse lahendusega: asukohad on jututoa ajaloos näha.",
|
||||
"login_with_qr": "Logi sisse QR-koodiga",
|
||||
"mjolnir": "Uued võimalused osalejate eiramiseks",
|
||||
"msc3531_hide_messages_pending_moderation": "Luba modereerimist ootavate sõnumite peitmist.",
|
||||
"new_room_list": "Võta kasutusele uus jututubade loend",
|
||||
|
||||
@@ -270,11 +270,11 @@
|
||||
"point_the_camera": "Scannez le QR code affiché ici",
|
||||
"scan_code_instruction": "Scannez le code QR",
|
||||
"scan_qr_code": "Se connecter avec un QR code",
|
||||
"security_code_prompt": "Si vous y êtes invité, saisissez le code ci-dessous sur votre autre appareil.",
|
||||
"security_code_prompt": "Votre fournisseur de compte peut vous demander le code suivant pour vérifier la connexion.",
|
||||
"select_qr_code": "Sélectionnez « %(scanQRCode)s »",
|
||||
"unsupported_explainer": "Votre fournisseur de compte ne prend pas en charge la connexion à un nouvel appareil à l’aide d’un code QR.",
|
||||
"unsupported_heading": "Le code QR n'est pas pris en charge",
|
||||
"waiting_for_device": "En attente de connexion de l’appareil"
|
||||
"waiting_for_device": "En attente de votre autre appareil"
|
||||
},
|
||||
"register_action": "Créer un compte",
|
||||
"registration": {
|
||||
|
||||
@@ -246,7 +246,9 @@
|
||||
"check_code_heading": "Wprowadź numer wyświetlany na drugim urządzeniu",
|
||||
"check_code_input_label": "2-cyfrowy kod",
|
||||
"check_code_mismatch": "Liczby się nie zgadzają",
|
||||
"choose_desktop_computer": "Wybierz \"%(desktopComputer)s\"",
|
||||
"completing_setup": "Kończenie konfiguracji nowego urządzenia",
|
||||
"desktop_computer": "Komputer stacjonarny",
|
||||
"error_etag_missing": "Wystąpił nieoczekiwany błąd. Może to być spowodowane rozszerzeniem przeglądarki, serwerem proxy lub błędną konfiguracją serwera.",
|
||||
"error_expired": "Logowanie wygasło. Spróbuj ponownie.",
|
||||
"error_expired_title": "Logowanie nie zostało zakończone na czas",
|
||||
@@ -267,15 +269,22 @@
|
||||
"error_user_declined": "Ty lub Twój dostawca konta odrzucił żądanie logowania.",
|
||||
"error_user_declined_title": "Logowanie odrzucone",
|
||||
"follow_remaining_instructions": "Postępuj zgodnie z pozostałymi instrukcjami, aby zweryfikować Twoje inne urządzenie",
|
||||
"open_element_mobile_device": "Otwórz %(brand)s na swoim urządzeniu mobilnym",
|
||||
"open_element_other_device": "Otwórz %(brand)s na innym urządzeniu",
|
||||
"point_the_camera": "Skieruj kamerę na pokazany tutaj kod QR",
|
||||
"scan_code_instruction": "Skanuj kod QR za pomocą innego urządzenia",
|
||||
"ready_to_scan": "Gotowy do skanowania",
|
||||
"scan_code_instruction": "Skanuj kod QR",
|
||||
"scan_code_instruction_reciprocate": "Zeskanuj kod QR innym urządzeniem",
|
||||
"scan_qr_code": "Zaloguj się kodem QR",
|
||||
"security_code_prompt": "Jeśli zostaniesz poproszony, wprowadź poniższy kod na drugim urządzeniu.",
|
||||
"security_code_prompt": "Twój dostawca konta może poprosić o podany kod w celu weryfikacji logowania.",
|
||||
"security_code_title": "Twój kod weryfikacyjny",
|
||||
"select_qr_code": "Wybierz '%(scanQRCode)s'",
|
||||
"select_ready_to_scan": "Wybierz \"%(readyToScan)s\" i zeskanuj kod QR widoczny tutaj",
|
||||
"start_over": "Zacznij od nowa",
|
||||
"tap_avatar_link_new_device": "Kliknij swój awatar i wybierz \"%(linkNewDevice)s\"",
|
||||
"unsupported_explainer": "Twój dostawca konta nie obsługuje logowania nowego urządzenia za pomocą kodu QR.",
|
||||
"unsupported_heading": "Kod QR nie jest wspierany",
|
||||
"waiting_for_device": "Oczekiwanie na logowanie urządzenia"
|
||||
"waiting_for_device": "Oczekiwanie na drugie urządzenie"
|
||||
},
|
||||
"register_action": "Utwórz konto",
|
||||
"registration": {
|
||||
@@ -334,8 +343,10 @@
|
||||
"sign_in_description": "Użyj swojego konta, aby kontynuować.",
|
||||
"sign_in_instead": "Zamiast tego zaloguj się",
|
||||
"sign_in_instead_prompt": "Masz już konto? <a>Zaloguj się tutaj </a>",
|
||||
"sign_in_manually": "Zaloguj się ręcznie",
|
||||
"sign_in_or_register": "Zaloguj się lub utwórz konto",
|
||||
"sign_in_or_register_description": "Użyj konta lub utwórz nowe, aby kontynuować.",
|
||||
"sign_in_with_qr": "Zaloguj się kodem QR",
|
||||
"sign_in_with_sso": "Zaloguj się za pomocą pojedynczego logowania",
|
||||
"signing_in": "Logowanie…",
|
||||
"soft_logout": {
|
||||
@@ -1528,6 +1539,7 @@
|
||||
"bridge_state_manager": "Ten mostek jest zarządzany przez <user />.",
|
||||
"bridge_state_workspace": "Obszar roboczy: <networkLink/>",
|
||||
"click_for_info": "Kliknij po więcej informacji",
|
||||
"config_only": "Może zostać włączony tylko poprzez config.json.",
|
||||
"currently_experimental": "Aktualnie eksperymentalne.",
|
||||
"custom_themes": "Obsługa dodawania niestandardowych motywów",
|
||||
"dynamic_room_predecessors": "Dynamiczne poprzedniki pokoju",
|
||||
@@ -1572,6 +1584,7 @@
|
||||
"leave_beta_reload": "Opuszczenie bety, wczyta ponownie %(brand)s.",
|
||||
"location_share_live": "Udostępnianie lokalizacji na żywo",
|
||||
"location_share_live_description": "Implementacja tymczasowa. Lokalizacje są zapisywane w historii pokoju.",
|
||||
"login_with_qr": "Zaloguj się za pomocą kodu QR",
|
||||
"mjolnir": "Nowe sposoby na ignorowanie osób",
|
||||
"msc3531_hide_messages_pending_moderation": "Daj moderatorom ukrycie wiadomości które są sprawdzane.",
|
||||
"new_room_list": "Włącz nową listę pokojów",
|
||||
|
||||
@@ -1140,7 +1140,7 @@ export const SETTINGS: Settings = {
|
||||
"urlPreviewsEnabled": {
|
||||
// Enabled by default and client configurable as this setting only allows unencrypted
|
||||
// messages to be previewed.
|
||||
supportedLevels: [SettingLevel.DEVICE, SettingLevel.ACCOUNT, SettingLevel.CONFIG],
|
||||
supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.DEVICE, SettingLevel.ACCOUNT, SettingLevel.CONFIG],
|
||||
supportedLevelsAreOrdered: true,
|
||||
displayName: _td("settings|inline_url_previews_default"),
|
||||
default: true,
|
||||
|
||||
@@ -37,6 +37,8 @@ export const PREVIEW_WIDTH_PX = 478;
|
||||
export const PREVIEW_HEIGHT_PX = 200;
|
||||
export const MIN_PREVIEW_PX = 96;
|
||||
export const MIN_IMAGE_SIZE_BYTES = 8192;
|
||||
// From https://github.com/matrix-org/matrix-spec-proposals/pull/4095
|
||||
export const BUNDLED_LINK_PREVIEWS = "com.beeper.linkpreviews";
|
||||
|
||||
export enum PreviewVisibility {
|
||||
/**
|
||||
@@ -387,6 +389,18 @@ export class UrlPreviewGroupViewModel
|
||||
* for the previously-calculated links.
|
||||
*/
|
||||
private async computeSnapshot(): Promise<void> {
|
||||
// This uses MSC4095. If the sender has sent us an empty URL previews bundle
|
||||
// then they do not want to have URL previews be visible.
|
||||
const bundledLinkPreviews = this.props.mxEvent.getContent()[BUNDLED_LINK_PREVIEWS];
|
||||
if (Array.isArray(bundledLinkPreviews) && bundledLinkPreviews.length === 0) {
|
||||
return this.snapshot.merge({
|
||||
previews: [],
|
||||
totalPreviewCount: 0,
|
||||
previewsLimited: false,
|
||||
overPreviewLimit: false,
|
||||
});
|
||||
} // otherwise, we do not support bundled previews yet so will fallback to old behaviour.
|
||||
|
||||
const previews =
|
||||
this.visibility <= PreviewVisibility.UserHidden
|
||||
? []
|
||||
|
||||
@@ -415,6 +415,14 @@ export class RoomListItemViewModel
|
||||
tagRoom(this.props.room, tag);
|
||||
};
|
||||
|
||||
public onRemoveFromSection = (): void => {
|
||||
const roomTags = this.props.room.tags;
|
||||
const sectionTag = RoomListStoreV3.instance.orderedSectionTags.find((tag) => Boolean(roomTags[tag]));
|
||||
if (sectionTag) {
|
||||
tagRoom(this.props.room, sectionTag);
|
||||
}
|
||||
};
|
||||
|
||||
private onOrderedCustomSectionsChange = (): void => {
|
||||
// Rebuild sections list to reflect new order
|
||||
const sections = RoomListItemViewModel.buildSections(this.props.room.tags);
|
||||
|
||||
@@ -9,7 +9,10 @@ import { expect } from "@jest/globals";
|
||||
|
||||
import type { MockedObject } from "jest-mock";
|
||||
import type { MatrixClient, IPreviewUrlResponse } from "matrix-js-sdk/src/matrix";
|
||||
import { UrlPreviewGroupViewModel } from "../../../src/viewmodels/message-body/UrlPreviewGroupViewModel";
|
||||
import {
|
||||
BUNDLED_LINK_PREVIEWS,
|
||||
UrlPreviewGroupViewModel,
|
||||
} from "../../../src/viewmodels/message-body/UrlPreviewGroupViewModel";
|
||||
import type { UrlPreview } from "@element-hq/web-shared-components";
|
||||
import { getMockClientWithEventEmitter, mkEvent } from "../../test-utils";
|
||||
|
||||
@@ -22,7 +25,9 @@ const BASIC_PREVIEW_OGDATA = {
|
||||
"og:site_name": "Example.org",
|
||||
};
|
||||
|
||||
function getViewModel({ mediaVisible, visible } = { mediaVisible: true, visible: true }): {
|
||||
function getViewModel(
|
||||
{ mediaVisible, visible, showPreview } = { mediaVisible: true, visible: true, showPreview: true },
|
||||
): {
|
||||
vm: UrlPreviewGroupViewModel;
|
||||
client: MockedObject<MatrixClient>;
|
||||
onImageClicked: jest.Mock<void, [UrlPreview]>;
|
||||
@@ -41,7 +46,9 @@ function getViewModel({ mediaVisible, visible } = { mediaVisible: true, visible:
|
||||
event: true,
|
||||
user: "@foo:bar",
|
||||
type: "m.room.message",
|
||||
content: {},
|
||||
content: {
|
||||
...(showPreview ? undefined : { [BUNDLED_LINK_PREVIEWS]: [] }),
|
||||
},
|
||||
id: "$id",
|
||||
}),
|
||||
});
|
||||
@@ -94,7 +101,7 @@ describe("UrlPreviewGroupViewModel", () => {
|
||||
]);
|
||||
});
|
||||
it("should hide preview when invisible", async () => {
|
||||
const { vm, client } = getViewModel({ visible: false, mediaVisible: true });
|
||||
const { vm, client } = getViewModel({ visible: false, mediaVisible: true, showPreview: true });
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = '<a href="https://example.org">Test</a>';
|
||||
await vm.updateEventElement(msg);
|
||||
@@ -152,7 +159,7 @@ describe("UrlPreviewGroupViewModel", () => {
|
||||
expect(vm.getSnapshot().previews[0].siteIcon).toBeTruthy();
|
||||
});
|
||||
it("should ignore media when mediaVisible is false", async () => {
|
||||
const { vm, client } = getViewModel({ mediaVisible: false, visible: true });
|
||||
const { vm, client } = getViewModel({ mediaVisible: false, visible: true, showPreview: true });
|
||||
client.getUrlPreview.mockResolvedValueOnce({
|
||||
"og:title": "This is an example!",
|
||||
"og:type": "document",
|
||||
@@ -225,6 +232,21 @@ describe("UrlPreviewGroupViewModel", () => {
|
||||
await vm.onShowClick();
|
||||
expect(vm.getSnapshot()).toMatchSnapshot();
|
||||
});
|
||||
it("should hide a preview if the message requests it", async () => {
|
||||
const { vm, client } = getViewModel({ showPreview: false, mediaVisible: true, visible: true });
|
||||
client.getUrlPreview.mockResolvedValueOnce(BASIC_PREVIEW_OGDATA);
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = '<a href="https://example.org">Test</a>';
|
||||
await vm.updateEventElement(msg);
|
||||
expect(vm.getSnapshot()).toMatchInlineSnapshot(`
|
||||
{
|
||||
"overPreviewLimit": false,
|
||||
"previews": [],
|
||||
"previewsLimited": false,
|
||||
"totalPreviewCount": 0,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
describe("calculates author", () => {
|
||||
it("should use the profile:username if provided", async () => {
|
||||
|
||||
@@ -55,7 +55,15 @@ export default {
|
||||
// Brought in via hak scripts
|
||||
"matrix-seshat",
|
||||
],
|
||||
ignoreBinaries: ["scripts/in-docker.sh"],
|
||||
ignoreBinaries: [
|
||||
"scripts/in-docker.sh",
|
||||
// Used to build seshat (optional)
|
||||
"rustc",
|
||||
// Used by the fetch-package script (optional)
|
||||
"gpg",
|
||||
// Used for the macOS universal builds
|
||||
"lipo",
|
||||
],
|
||||
},
|
||||
"modules/*": {
|
||||
entry: "src/index.ts{x,}",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"cronstrue": "^3.0.0",
|
||||
"eslint-plugin-matrix-org": "^3.0.0",
|
||||
"husky": "^9.0.0",
|
||||
"knip": "6.15.0",
|
||||
"knip": "6.16.0",
|
||||
"lint-staged": "^17.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mermaid": "^11.13.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@@ -110,7 +110,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||
"@typescript-eslint/parser": "^8.53.1",
|
||||
"@vector-im/compound-web": "catalog:",
|
||||
"@vitest/browser-playwright": "^4.0.17",
|
||||
"@vitest/browser-playwright": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"eslint": "8",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
|
||||
@@ -150,7 +150,8 @@
|
||||
"low_priority": "Low priority",
|
||||
"mark_read": "Mark as read",
|
||||
"mark_unread": "Mark as unread",
|
||||
"move_to_section": "Move to"
|
||||
"move_to_section": "Move to",
|
||||
"remove_from_section": "Remove from section"
|
||||
},
|
||||
"notification_options": "Notification options",
|
||||
"open_space_menu": "Open space menu",
|
||||
|
||||
@@ -32,6 +32,12 @@ const meta = {
|
||||
</div>
|
||||
),
|
||||
],
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://www.figma.com/design/qurBlLqjf3mRNpyZ1ffamm/ER-213---Sections?node-id=1233-22137&t=ftTEpAma7PgRaaqB-4",
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof RoomListToast>;
|
||||
|
||||
export default meta;
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type ComponentType, type JSX, type MouseEventHandler } from "react";
|
||||
import React, { type JSX, type MouseEventHandler } from "react";
|
||||
import { Toast } from "@vector-im/compound-web";
|
||||
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
|
||||
|
||||
import styles from "./RoomListToast.module.css";
|
||||
import { useI18n } from "../../../core/i18n/i18nContext";
|
||||
@@ -32,19 +31,19 @@ interface RoomListToastProps {
|
||||
export function RoomListToast({ type, onClose }: Readonly<RoomListToastProps>): JSX.Element {
|
||||
const { translate: _t } = useI18n();
|
||||
|
||||
let content: { text: string; icon: ComponentType<React.SVGAttributes<SVGElement>> };
|
||||
let text: string;
|
||||
switch (type) {
|
||||
case "section_created":
|
||||
content = { text: _t("room_list|section_created"), icon: CheckIcon };
|
||||
text = _t("room_list|section_created");
|
||||
break;
|
||||
case "chat_moved":
|
||||
content = { text: _t("room_list|chat_moved"), icon: CheckIcon };
|
||||
text = _t("room_list|chat_moved");
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Toast className={styles.toast} Icon={content.icon} onClose={onClose} tooltip={_t("action|close")}>
|
||||
{content.text}
|
||||
<Toast className={styles.toast} onClose={onClose} tooltip={_t("action|close")}>
|
||||
{text}
|
||||
</Toast>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,19 +11,6 @@ exports[`<RoomListToast /> > renders ChatMoved story 1`] = `
|
||||
<div
|
||||
class="_content_1ysb3_34"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_1ysb3_26"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
Chat moved
|
||||
</div>
|
||||
<button
|
||||
@@ -67,19 +54,6 @@ exports[`<RoomListToast /> > renders SectionCreated story 1`] = `
|
||||
<div
|
||||
class="_content_1ysb3_34"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_1ysb3_26"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
Section created
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -16868,19 +16868,6 @@ exports[`<RoomListView /> > renders Toast story 1`] = `
|
||||
<div
|
||||
class="_content_1ysb3_34"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_1ysb3_26"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
Section created
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -37,6 +37,7 @@ const RoomListItemDragOverlayWrapperImpl = ({
|
||||
onSetRoomNotifState,
|
||||
onCreateSection,
|
||||
onToggleSection,
|
||||
onRemoveFromSection,
|
||||
renderAvatar: renderAvatarProp,
|
||||
...rest
|
||||
}: RoomListItemDragOverlayProps): JSX.Element => {
|
||||
@@ -52,6 +53,7 @@ const RoomListItemDragOverlayWrapperImpl = ({
|
||||
onSetRoomNotifState,
|
||||
onCreateSection,
|
||||
onToggleSection,
|
||||
onRemoveFromSection,
|
||||
});
|
||||
return <RoomListItemDragOverlayView vm={vm} renderAvatar={renderAvatarProp} />;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.sectionLabel {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
color: var(--cpd-color-text-primary);
|
||||
}
|
||||
@@ -8,28 +8,15 @@
|
||||
import React, { type JSX } from "react";
|
||||
import { render, screen } from "@test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { RoomListItemMoreOptionsMenu, MoreOptionContent } from "./RoomListItemMoreOptionsMenu";
|
||||
import { useMockedViewModel } from "../../../../core/viewmodel";
|
||||
import type { RoomListItemViewSnapshot } from "./RoomListItemView";
|
||||
import { defaultSnapshot } from "./default-snapshot";
|
||||
import { mockedActions as mockCallbacks } from "./mocked-actions";
|
||||
|
||||
describe("<RoomListItemMoreOptionsMenu />", () => {
|
||||
const mockCallbacks = {
|
||||
onOpenRoom: vi.fn(),
|
||||
onMarkAsRead: vi.fn(),
|
||||
onMarkAsUnread: vi.fn(),
|
||||
onToggleFavorite: vi.fn(),
|
||||
onToggleLowPriority: vi.fn(),
|
||||
onInvite: vi.fn(),
|
||||
onCopyRoomLink: vi.fn(),
|
||||
onLeaveRoom: vi.fn(),
|
||||
onSetRoomNotifState: vi.fn(),
|
||||
onCreateSection: vi.fn(),
|
||||
onToggleSection: vi.fn(),
|
||||
};
|
||||
|
||||
const renderMenu = (overrides: Partial<RoomListItemViewSnapshot> = {}): ReturnType<typeof render> => {
|
||||
const TestComponent = (): JSX.Element => {
|
||||
const vm = useMockedViewModel(
|
||||
@@ -242,6 +229,20 @@ describe("<RoomListItemMoreOptionsMenu />", () => {
|
||||
expect(mockCallbacks.onCreateSection).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call onRemoveFromSection when Remove from section is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const TestComponent = (): JSX.Element => {
|
||||
const vm = useMockedViewModel({ ...defaultSnapshot, isInSection: true }, mockCallbacks);
|
||||
return <MoreOptionContent vm={vm} />;
|
||||
};
|
||||
render(<TestComponent />);
|
||||
|
||||
const removeFromSection = screen.getByRole("menuitem", { name: "Remove from section" });
|
||||
await user.click(removeFromSection);
|
||||
|
||||
expect(mockCallbacks.onRemoveFromSection).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should render section items in move to section submenu", () => {
|
||||
const sections = [
|
||||
{ tag: "m.favourite", name: "Favourites", isSelected: false },
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useState, type JSX } from "react";
|
||||
import React, { useMemo, useState, type JSX } from "react";
|
||||
import { IconButton, Menu, MenuItem, Separator, SubMenu, ToggleMenuItem } from "@vector-im/compound-web";
|
||||
import {
|
||||
MarkAsReadIcon,
|
||||
@@ -18,11 +18,13 @@ import {
|
||||
OverflowHorizontalIcon,
|
||||
ArrowRightIcon,
|
||||
CheckIcon,
|
||||
MinusIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../../core/i18n/i18n";
|
||||
import { useViewModel, type ViewModel } from "../../../../core/viewmodel";
|
||||
import type { RoomListItemViewSnapshot, RoomListItemViewActions } from "./RoomListItemView";
|
||||
import styles from "./RoomListItemMoreOptionsMenu.module.css";
|
||||
|
||||
/**
|
||||
* View model type for room list item
|
||||
@@ -73,6 +75,8 @@ interface MoreOptionContentProps {
|
||||
|
||||
export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element {
|
||||
const snapshot = useViewModel(vm);
|
||||
const hasSections = snapshot.sections.length > 0;
|
||||
const isInSection = useMemo(() => snapshot.sections.some((section) => section.isSelected), [snapshot.sections]);
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div onKeyDown={(e) => e.stopPropagation()}>
|
||||
@@ -141,6 +145,7 @@ export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element {
|
||||
<MenuItem
|
||||
key={section.tag}
|
||||
label={section.name}
|
||||
labelProps={{ className: styles.sectionLabel }}
|
||||
onSelect={() => vm.onToggleSection(section.tag)}
|
||||
onClick={(evt) => evt.stopPropagation()}
|
||||
hideChevron={true}
|
||||
@@ -151,10 +156,19 @@ export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element {
|
||||
)}
|
||||
</MenuItem>
|
||||
))}
|
||||
<Separator />
|
||||
{hasSections && <Separator />}
|
||||
<MenuItem label={_t("action|new_section")} onSelect={vm.onCreateSection} hideChevron={true} />
|
||||
</SubMenu>
|
||||
)}
|
||||
{isInSection && (
|
||||
<MenuItem
|
||||
Icon={MinusIcon}
|
||||
label={_t("room_list|more_options|remove_from_section")}
|
||||
onSelect={vm.onRemoveFromSection}
|
||||
onClick={(evt) => evt.stopPropagation()}
|
||||
hideChevron={true}
|
||||
/>
|
||||
)}
|
||||
<Separator />
|
||||
<MenuItem
|
||||
kind="critical"
|
||||
|
||||
@@ -8,29 +8,16 @@
|
||||
import React, { type JSX } from "react";
|
||||
import { render, screen } from "@test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu";
|
||||
import { RoomNotifState } from "./RoomNotifs";
|
||||
import { useMockedViewModel } from "../../../../core/viewmodel";
|
||||
import type { RoomListItemViewSnapshot } from "./RoomListItemView";
|
||||
import { defaultSnapshot } from "./default-snapshot";
|
||||
import { mockedActions as mockCallbacks } from "./mocked-actions";
|
||||
|
||||
describe("<RoomListItemNotificationMenu />", () => {
|
||||
const mockCallbacks = {
|
||||
onOpenRoom: vi.fn(),
|
||||
onMarkAsRead: vi.fn(),
|
||||
onMarkAsUnread: vi.fn(),
|
||||
onToggleFavorite: vi.fn(),
|
||||
onToggleLowPriority: vi.fn(),
|
||||
onInvite: vi.fn(),
|
||||
onCopyRoomLink: vi.fn(),
|
||||
onLeaveRoom: vi.fn(),
|
||||
onSetRoomNotifState: vi.fn(),
|
||||
onCreateSection: vi.fn(),
|
||||
onToggleSection: vi.fn(),
|
||||
};
|
||||
|
||||
const renderMenu = (roomNotifState: RoomNotifState = RoomNotifState.AllMessages): ReturnType<typeof render> => {
|
||||
const TestComponent = (): JSX.Element => {
|
||||
const vm = useMockedViewModel(
|
||||
|
||||
@@ -72,7 +72,12 @@
|
||||
|
||||
.dragging {
|
||||
outline: 1px solid var(--cpd-color-border-interactive-hovered);
|
||||
background-color: color-mix(in srgb, var(--cpd-color-bg-action-tertiary-hovered) 90%, transparent);
|
||||
background-color: color-mix(in srgb, var(--cpd-color-bg-action-tertiary-hovered) 95%, transparent);
|
||||
color: var(--cpd-color-text-primary);
|
||||
}
|
||||
|
||||
.dragSource .container {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -40,6 +40,7 @@ const RoomListItemWrapperImpl = ({
|
||||
onSetRoomNotifState,
|
||||
onCreateSection,
|
||||
onToggleSection,
|
||||
onRemoveFromSection,
|
||||
isSelected,
|
||||
isFocused,
|
||||
onFocus,
|
||||
@@ -60,6 +61,7 @@ const RoomListItemWrapperImpl = ({
|
||||
onSetRoomNotifState,
|
||||
onCreateSection,
|
||||
onToggleSection,
|
||||
onRemoveFromSection,
|
||||
});
|
||||
return (
|
||||
<RoomListItemView
|
||||
|
||||
@@ -125,6 +125,8 @@ export interface RoomListItemViewActions {
|
||||
onCreateSection: () => void;
|
||||
/** Called when toggling a room's membership in a section */
|
||||
onToggleSection: (tag: string) => void;
|
||||
/** Called when removing the room from a section */
|
||||
onRemoveFromSection: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +152,8 @@ export interface RoomListItemViewProps extends Omit<React.HTMLAttributes<HTMLBut
|
||||
isLastItem: boolean;
|
||||
/** Function to render the room avatar */
|
||||
renderAvatar: (room: Room) => ReactNode;
|
||||
/** Whether this item is the source of an active drag operation */
|
||||
isDragSource?: boolean;
|
||||
ref?: Ref<Element>;
|
||||
}
|
||||
|
||||
@@ -165,6 +169,7 @@ export const RoomListItemView = memo(function RoomListItemView({
|
||||
isFirstItem,
|
||||
isLastItem,
|
||||
renderAvatar,
|
||||
isDragSource = false,
|
||||
ref,
|
||||
...props
|
||||
}: RoomListItemViewProps): JSX.Element {
|
||||
@@ -191,6 +196,7 @@ export const RoomListItemView = memo(function RoomListItemView({
|
||||
[styles.bold]: item.isBold,
|
||||
[styles.firstItem]: isFirstItem,
|
||||
[styles.lastItem]: isLastItem,
|
||||
[styles.dragSource]: isDragSource,
|
||||
mx_RoomListItemView_selected: isSelected,
|
||||
})}
|
||||
gap="var(--cpd-space-3x)"
|
||||
|
||||
@@ -45,7 +45,7 @@ export const defaultSnapshot: RoomListItemViewSnapshot = {
|
||||
},
|
||||
{
|
||||
tag: "element.io.section.work",
|
||||
name: "Work",
|
||||
name: "Work with a very long name that should be truncated",
|
||||
isSelected: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -21,4 +21,5 @@ export const mockedActions: RoomListItemViewActions = {
|
||||
onSetRoomNotifState: fn(),
|
||||
onCreateSection: fn(),
|
||||
onToggleSection: fn(),
|
||||
onRemoveFromSection: fn(),
|
||||
};
|
||||
|
||||
@@ -55,12 +55,16 @@ export const RoomListItemWrapper = memo(function RoomListItemWrapper({
|
||||
*/
|
||||
function DraggableWrapper(props: RoomListItemViewProps): JSX.Element {
|
||||
const item = useViewModel(props.vm);
|
||||
const { ref: draggableRef, handleRef } = useDraggable({
|
||||
const {
|
||||
ref: draggableRef,
|
||||
handleRef,
|
||||
isDragSource,
|
||||
} = useDraggable({
|
||||
id: item.id,
|
||||
// We clone the item in the dnd overlay to avoid putting a hole in the list
|
||||
plugins: [Feedback.configure({ feedback: "clone" })],
|
||||
modifiers: [RestrictToVerticalAxis],
|
||||
});
|
||||
const dndRef = useMergeRefs([draggableRef, handleRef]);
|
||||
return <RoomListItemView {...props} ref={dndRef} />;
|
||||
return <RoomListItemView {...props} ref={dndRef} isDragSource={isDragSource} />;
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ export function createMockRoomItemViewModel(roomId: string, name: string, index:
|
||||
onSetRoomNotifState: fn(),
|
||||
onCreateSection: fn(),
|
||||
onToggleSection: fn(),
|
||||
onRemoveFromSection: fn(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -27,15 +27,16 @@ catalog:
|
||||
# fonts
|
||||
"@fontsource/inter": 5.2.8
|
||||
# vite
|
||||
vite: 8.0.15
|
||||
vite: 8.0.16
|
||||
vite-plugin-node-polyfills: 0.28.0
|
||||
vite-plugin-svgr: 5.2.0
|
||||
vitest: 4.1.7
|
||||
"@vitest/coverage-v8": 4.1.7
|
||||
"@vitest/browser-playwright": 4.1.7
|
||||
vitest: 4.1.8
|
||||
"@vitest/coverage-v8": 4.1.8
|
||||
"@vitest/browser-playwright": 4.1.8
|
||||
"@vitejs/plugin-react": 6.0.2
|
||||
unplugin-dts: 1.0.1
|
||||
|
||||
|
||||
packageExtensions:
|
||||
fdir:
|
||||
dependencies:
|
||||
|
||||