diff --git a/playwright/e2e/permalinks/permalinks.spec.ts b/playwright/e2e/permalinks/permalinks.spec.ts index e7657b1394..3f6fddbd0d 100644 --- a/playwright/e2e/permalinks/permalinks.spec.ts +++ b/playwright/e2e/permalinks/permalinks.spec.ts @@ -100,3 +100,51 @@ test.describe("permalinks", () => { }); }); }); + +test.describe("triple-click message selection", () => { + test.use({ + displayName: "Alice", + }); + + test("should select entire message line when triple-clicking on message with pills", async ({ + page, + app, + user, + bot, + }) => { + await bot.prepareClient(); + + const roomId = await app.client.createRoom({ name: "Test Room" }); + await app.client.inviteUser(roomId, bot.credentials.userId); + await app.viewRoomByName("Test Room"); + + // Send a message with user and room pills + await app.client.sendMessage( + roomId, + `Testing triple-click message selection. ` + + `User: ${permalinkPrefix}${bot.credentials.userId}, ` + + `Room: ${permalinkPrefix}${roomId}, ` + + `Message: ${permalinkPrefix}${roomId}/$dummy-event, ` + + `and @room mention.`, + ); + + const timeline = page.locator(".mx_RoomView_timeline"); + const messageTile = timeline.locator(".mx_EventTile").last(); + + // Triple-click on the message body to select its entire content + const messageBody = messageTile.locator(".mx_EventTile_body"); + await messageBody.click({ clickCount: 3 }); + + // Get the expected text content of the message, including pills + const expectedText = await messageBody.innerText(); + + // Get the currently selected text from the page + const selectedText = await page.evaluate(() => { + const selection = window.getSelection(); + return selection ? selection.toString().trim() : ""; + }); + + // Verify that the selected text exactly matches the message content + expect(selectedText).toBe(expectedText); + }); +}); diff --git a/res/css/views/elements/_Pill.pcss b/res/css/views/elements/_Pill.pcss index d692f812a4..4b3fd3bb68 100644 --- a/res/css/views/elements/_Pill.pcss +++ b/res/css/views/elements/_Pill.pcss @@ -11,8 +11,7 @@ Please see LICENSE files in the repository root for full details. line-height: $font-17px; border-radius: $font-16px; vertical-align: text-top; - display: inline-flex; - align-items: center; + display: inline-block; box-sizing: border-box; max-width: 100%; overflow: hidden; @@ -57,6 +56,8 @@ Please see LICENSE files in the repository root for full details. margin-inline-start: -0.3em; /* Otherwise the gap is too large */ margin-inline-end: 0.2em; min-width: $font-16px; /* ensure the avatar is not compressed */ + user-select: text; + vertical-align: -2.5px; } .mx_Pill_text { diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 69b4127ea8..b276c2ded5 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -646,12 +646,12 @@ "mode_plain": "Skrýt formátování", "mode_rich_text": "Zobrazit formátování", "no_perms_notice": "Nemáte oprávnění zveřejňovat příspěvky v této místnosti", - "placeholder": "Odeslat zprávu…", - "placeholder_encrypted": "Odeslat šifrovanou zprávu…", - "placeholder_reply": "Odpovědět…", - "placeholder_reply_encrypted": "Odeslat šifrovanou odpověď…", - "placeholder_thread": "Odpovědět na vlákno…", - "placeholder_thread_encrypted": "Odpovědět na zašifrované vlákno…", + "placeholder": "Odeslat nešifrovanou zprávu…", + "placeholder_encrypted": "Odeslat zprávu...", + "placeholder_reply": "Odeslat nešifrovanou odpověď…", + "placeholder_reply_encrypted": "Odeslat odpověď…", + "placeholder_thread": "Odpovědět v nešifrovaném vláknu…", + "placeholder_thread_encrypted": "Odpovědět ve vláknu…", "poll_button": "Hlasování", "poll_button_no_perms_description": "Nemáte oprávnění zahajovat hlasování v této místnosti.", "poll_button_no_perms_title": "Vyžaduje oprávnění", @@ -922,7 +922,8 @@ }, "privacy_warning": "Ujistěte se, že tuto obrazovku nikdo nevidí!", "restoring": "Obnovení klíčů ze zálohy", - "security_key_title": "Klíč pro obnovení" + "security_key_label": "Klíč pro obnovení", + "security_key_title": "Zadejte klíč pro obnovení" }, "bootstrap_title": "Příprava klíčů", "confirm_encryption_setup_body": "Kliknutím na tlačítko níže potvrďte nastavení šifrování.", @@ -1367,6 +1368,10 @@ "name_email_mxid_share_space": "Pozvěte někoho pomocí jeho jména, e-mailové adresy, uživatelského jména (například ) nebo sdílejte tento prostor.", "name_mxid_share_room": "Pozvěte někoho pomocí svého jména, uživatelského jména (například ) nebo sdílejte tuto místnost.", "name_mxid_share_space": "Pozvěte někoho pomocí jeho jména, uživatelského jména (například ) nebo sdílejte tento prostor.", + "progress": { + "dont_close": "Nezavírejte aplikaci, dokud neskončíte.", + "preparing": "Příprava pozvánek..." + }, "recents_section": "Nedávné konverzace", "room_failed_partial": "Poslali jsme ostatním, ale níže uvedení lidé nemohli být pozváni do ", "room_failed_partial_title": "Některé pozvánky nebylo možné odeslat", @@ -1535,6 +1540,9 @@ "render_reaction_images_description": "Někdy se označují jako \"vlastní emoji\".", "report_to_moderators": "Nahlásit moderátorům", "report_to_moderators_description": "V místnostech, které podporují moderování, můžete pomocí tlačítka \"Nahlásit\" nahlásit zneužití moderátorům místnosti.", + "share_history_on_invite": "Sdílet šifrovanou historii s novými členy", + "share_history_on_invite_description": "Při pozvání uživatele do šifrované místnosti, u které je viditelnost historie nastavena na „sdílená“, sdílet šifrovanou historii s tímto uživatelem a přijmout šifrovanou historii, když jste pozváni do takové místnosti.", + "share_history_on_invite_warning": "Tato funkce je EXPERIMENTÁLNÍ a nejsou v ní implementována všechna bezpečnostní opatření. Neaktivujte ji na produkčních účtech.", "sliding_sync": "Režim klouzavé synchronizace", "sliding_sync_description": "V aktivním vývoji, nelze zakázat.", "sliding_sync_disabled_notice": "Pro vypnutí se odhlaste a znovu přihlaste", @@ -1657,6 +1665,7 @@ "filter_placeholder": "Najít člena místnosti", "invite_button_no_perms_tooltip": "Nemáte oprávnění zvát uživatele", "invited_label": "Pozván", + "list_title": "Seznam členů", "no_matches": "Žádné shody" }, "member_list_back_action_label": "Členové místnosti", @@ -1761,6 +1770,7 @@ }, "power_level": { "admin": "Správce", + "creator": "Vlastník", "custom": "Vlastní (%(level)s)", "custom_level": "Vlastní úroveň", "default": "Výchozí", @@ -1914,6 +1924,7 @@ "thread_list": { "context_menu_label": "Možnosti vláken" }, + "title": "Pravý panel", "video_room_chat": { "title": "Chatovat" } @@ -3400,6 +3411,7 @@ "unable_to_find": "Pokusili jste se načíst bod na časové ose místnosti, ale nepodařilo se ho najít." }, "m.audio": { + "audio_player": "Audio přehrávač", "error_downloading_audio": "Chyba při stahování audia", "error_processing_audio": "Došlo k chybě při zpracovávání hlasové zprávy", "error_processing_voice_message": "Chyba při zpracování hlasové zprávy", diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index bdaac9dc69..3a9119f107 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -654,6 +654,7 @@ "poll_button_no_perms_description": "Vous n’avez pas la permission de démarrer un sondage dans ce salon.", "poll_button_no_perms_title": "Autorisation requise", "replying_title": "Répond", + "room_unencrypted": "Les messages dans ce salon ne sont pas chiffrés de bout en bout", "room_upgraded_link": "La discussion continue ici.", "room_upgraded_notice": "Ce salon a été remplacé et n’est plus actif.", "send_button_title": "Envoyer le message", @@ -1366,6 +1367,10 @@ "name_email_mxid_share_space": "Invitez quelqu’un grâce à son nom, adresse e-mail, nom d’utilisateur (tel que ) ou partagez cet espace.", "name_mxid_share_room": "Invitez quelqu’un à partir de son nom, pseudo (comme ) ou partagez ce salon.", "name_mxid_share_space": "Invitez quelqu’un grâce à son nom, nom d’utilisateur (tel que ) ou partagez cet espace.", + "progress": { + "dont_close": "Ne fermez pas l\"application tant que l'opération est en cours", + "preparing": "Préparation des invitations..." + }, "recents_section": "Conversations récentes", "room_failed_partial": "Nous avons envoyé les invitations, mais les personnes ci-dessous n’ont pas pu être invitées à rejoindre ", "room_failed_partial_title": "Certaines invitations n’ont pas pu être envoyées", diff --git a/src/i18n/strings/nb_NO.json b/src/i18n/strings/nb_NO.json index 89843712dd..1e9b0f0220 100644 --- a/src/i18n/strings/nb_NO.json +++ b/src/i18n/strings/nb_NO.json @@ -654,6 +654,7 @@ "poll_button_no_perms_description": "Du har ikke tillatelse til å starte avstemninger i dette rommet.", "poll_button_no_perms_title": "Tillatelse kreves", "replying_title": "Svarer på", + "room_unencrypted": "Meldinger i dette rommet er ikke ende-til-ende krypterte", "room_upgraded_link": "Samtalen fortsetter her.", "room_upgraded_notice": "Dette rommet har blitt erstattet og er ikke lenger aktivt.", "send_button_title": "Send melding", diff --git a/test/unit-tests/__snapshots__/Terms-test.tsx.snap b/test/unit-tests/__snapshots__/Terms-test.tsx.snap index a35963cd51..7bd269d3ea 100644 --- a/test/unit-tests/__snapshots__/Terms-test.tsx.snap +++ b/test/unit-tests/__snapshots__/Terms-test.tsx.snap @@ -7,6 +7,7 @@ exports[`dialogTermsInteractionCallback should render a dialog with the expected class="" data-focus-lock-disabled="false" role="dialog" + tabindex="-1" >
when key backup is disabled 1`] = ` class="mx_KeyBackupFailedDialog mx_Dialog_fixedWidth" data-focus-lock-disabled="false" role="dialog" + tabindex="-1" >
when key backup is enabled 1`] = ` class="mx_KeyBackupFailedDialog mx_Dialog_fixedWidth" data-focus-lock-disabled="false" role="dialog" + tabindex="-1" >
with an existing session onAction() room actions leave_r class="mx_QuestionDialog mx_Dialog_fixedWidth" data-focus-lock-disabled="false" role="dialog" + tabindex="-1" >
with an existing session onAction() room actions leave_r class="mx_QuestionDialog mx_Dialog_fixedWidth" data-focus-lock-disabled="false" role="dialog" + tabindex="-1" >
{ it("calls onFinished when Escape is pressed", async () => { const onFinished = jest.fn(); - render(); + const { container } = render(); + // Autolock's autofocus in the empty dialog is focusing on the close button and bringing up the tooltip + // So we either need to call escape twice(one for the tooltip and one for the dialog) or focus + // on the dialog first. + const dialog = container.querySelector('[role="dialog"]') as HTMLElement; + dialog?.focus(); await userEvent.keyboard("{Escape}"); await userEvent.keyboard("{Escape}"); expect(onFinished).toHaveBeenCalled(); diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ChangelogDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ChangelogDialog-test.tsx.snap index b38ccbc804..2801837059 100644 --- a/test/unit-tests/components/views/dialogs/__snapshots__/ChangelogDialog-test.tsx.snap +++ b/test/unit-tests/components/views/dialogs/__snapshots__/ChangelogDialog-test.tsx.snap @@ -13,6 +13,7 @@ exports[` should fetch github proxy url for each repo with ol class="mx_QuestionDialog mx_Dialog_fixedWidth" data-focus-lock-disabled="false" role="dialog" + tabindex="-1" >