Show hover elements when keyboard focus is within an event tile (#31078)
* Show timestamps when keyboard focus is within an event tile Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Ensure toolbar navigation pattern works in MessageActionBar This requires all buttons within to be roving by using the ref callback given by useRovingTabIndex Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Use PureComponent in EventTile to avoid mass re-rendering due to transitive onFocus/onBlur calls Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove unused timestamp event tile prop Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Use MessageTimestamp to generate the wrapping anchor so that focusing it brings up the tooltip Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Tweak MessageTimestamp Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Switch back to Component as we specify a shouldComponentUpdate already Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update jest tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update playwright timestamp masks Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Revert snapshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix IRC layout Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Use PureComponent in EventTile to avoid mass re-rendering due to transitive onFocus/onBlur calls Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove unused timestamp event tile prop Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Use MessageTimestamp to generate the wrapping anchor so that focusing it brings up the tooltip Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Tweak MessageTimestamp Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Switch back to Component as we specify a shouldComponentUpdate already Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update jest tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update playwright timestamp masks Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Revert snapshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix IRC layout Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Lint styles Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix layout picker Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update screenshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix pcss comment Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Playwright Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Playwright Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
@@ -92,7 +92,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
|||||||
css: `
|
css: `
|
||||||
/* The timestamp is of inconsistent width depending on the time the test runs at */
|
/* The timestamp is of inconsistent width depending on the time the test runs at */
|
||||||
.mx_MessageTimestamp {
|
.mx_MessageTimestamp {
|
||||||
display: none !important;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
/* The MAB showing up on hover is not needed for the test */
|
/* The MAB showing up on hover is not needed for the test */
|
||||||
.mx_MessageActionBar {
|
.mx_MessageActionBar {
|
||||||
|
|||||||
@@ -124,11 +124,12 @@ test.describe("HTML Export", () => {
|
|||||||
const zip = await extractZipFileToPath(zipPath, dirPath);
|
const zip = await extractZipFileToPath(zipPath, dirPath);
|
||||||
await page.goto(`file://${dirPath}/${Object.keys(zip.files)[0]}/messages.html`);
|
await page.goto(`file://${dirPath}/${Object.keys(zip.files)[0]}/messages.html`);
|
||||||
await expect(page).toMatchScreenshot("html-export.png", {
|
await expect(page).toMatchScreenshot("html-export.png", {
|
||||||
mask: [
|
mask: [page.locator(".mx_TimelineSeparator")],
|
||||||
// We need to mask the whole thing because the width of the time part changes
|
css: `
|
||||||
page.locator(".mx_TimelineSeparator"),
|
.mx_MessageTimestamp {
|
||||||
page.locator(".mx_MessageTimestamp"),
|
visibility: hidden;
|
||||||
],
|
}
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -138,7 +138,11 @@ test.describe("Editing", () => {
|
|||||||
|
|
||||||
// Take a snapshot of the dialog
|
// Take a snapshot of the dialog
|
||||||
await expect(dialog).toMatchScreenshot("message-edit-history-dialog.png", {
|
await expect(dialog).toMatchScreenshot("message-edit-history-dialog.png", {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -59,9 +59,11 @@ async function editMessage(page: Page, message: Locator, newMsg: string): Promis
|
|||||||
}
|
}
|
||||||
|
|
||||||
const screenshotOptions = (page?: Page) => ({
|
const screenshotOptions = (page?: Page) => ({
|
||||||
mask: page ? [page.locator(".mx_MessageTimestamp")] : undefined,
|
|
||||||
// Hide the jump to bottom button in the timeline to avoid flakiness
|
// Hide the jump to bottom button in the timeline to avoid flakiness
|
||||||
css: `
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
.mx_JumpToBottomButton {
|
.mx_JumpToBottomButton {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import fs from "node:fs";
|
|||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
|
|
||||||
const screenshotOptions = (page: Page) => ({
|
const screenshotOptions = (page: Page) => ({
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
// Hide the jump to bottom button in the timeline to avoid flakiness
|
// Hide the jump to bottom button in the timeline to avoid flakiness
|
||||||
// Exclude timestamp and read marker from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
css: `
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
.mx_JumpToBottomButton {
|
.mx_JumpToBottomButton {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,10 +93,12 @@ test.describe("permalinks", () => {
|
|||||||
getPill(timeline, danielleId);
|
getPill(timeline, danielleId);
|
||||||
|
|
||||||
await expect(timeline).toMatchScreenshot("permalink-rendering.png", {
|
await expect(timeline).toMatchScreenshot("permalink-rendering.png", {
|
||||||
mask: [
|
// Exclude timestamps from the snapshot, for consistency.
|
||||||
// Exclude timestamps from the snapshot, for consistency.
|
css: `
|
||||||
page.locator(".mx_MessageTimestamp"),
|
.mx_MessageTimestamp {
|
||||||
],
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -121,7 +121,11 @@ test.describe("Polls", () => {
|
|||||||
.filter({ hasText: pollParams.title })
|
.filter({ hasText: pollParams.title })
|
||||||
.getAttribute("data-scroll-tokens");
|
.getAttribute("data-scroll-tokens");
|
||||||
await expect(getPollTile(page, pollId)).toMatchScreenshot("Polls_Timeline_tile_no_votes.png", {
|
await expect(getPollTile(page, pollId)).toMatchScreenshot("Polls_Timeline_tile_no_votes.png", {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bot votes 'Maybe' in the poll
|
// Bot votes 'Maybe' in the poll
|
||||||
@@ -293,7 +297,11 @@ test.describe("Polls", () => {
|
|||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
||||||
"ThreadView_with_a_poll_on_bubble_layout.png",
|
"ThreadView_with_a_poll_on_bubble_layout.png",
|
||||||
{
|
{
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -303,7 +311,11 @@ test.describe("Polls", () => {
|
|||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
||||||
"ThreadView_with_a_poll_on_group_layout.png",
|
"ThreadView_with_a_poll_on_group_layout.png",
|
||||||
{
|
{
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,12 @@ test.describe("FilePanel", () => {
|
|||||||
// Take a snapshot of file tiles list on FilePanel
|
// Take a snapshot of file tiles list on FilePanel
|
||||||
await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", {
|
await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", {
|
||||||
// Exclude timestamps & flaky seek bar from snapshot
|
// Exclude timestamps & flaky seek bar from snapshot
|
||||||
mask: [page.locator(".mx_MessageTimestamp"), page.getByTestId("audio-player-seek")],
|
mask: [page.getByTestId("audio-player-seek")],
|
||||||
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -361,7 +361,9 @@ test.describe("Sliding Sync", () => {
|
|||||||
await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
|
await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
|
||||||
|
|
||||||
// now click on the permalink for Permalink me
|
// now click on the permalink for Permalink me
|
||||||
await page.locator(".mx_EventTile").filter({ hasText: "Permalink me" }).locator("a").dispatchEvent("click");
|
const tile = page.locator(".mx_EventTile").filter({ hasText: "Permalink me" });
|
||||||
|
await tile.hover();
|
||||||
|
await tile.locator("a").dispatchEvent("click");
|
||||||
|
|
||||||
// make sure it is now selected with the little green |
|
// make sure it is now selected with the little green |
|
||||||
await expect(page.locator(".mx_EventTile_selected").filter({ hasText: "Permalink me" })).toBeVisible();
|
await expect(page.locator(".mx_EventTile_selected").filter({ hasText: "Permalink me" })).toBeVisible();
|
||||||
|
|||||||
@@ -39,7 +39,12 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
const ThreadViewGroupSpacingStart = "56px"; // --ThreadView_group_spacing-start
|
const ThreadViewGroupSpacingStart = "56px"; // --ThreadView_group_spacing-start
|
||||||
// Exclude timestamp and read marker from snapshots
|
// Exclude timestamp and read marker from snapshots
|
||||||
const mask = [page.locator(".mx_MessageTimestamp"), page.locator(".mx_MessagePanel_myReadMarker")];
|
const mask = [page.locator(".mx_MessagePanel_myReadMarker")];
|
||||||
|
const css = `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const roomViewLocator = page.locator(".mx_RoomView_body");
|
const roomViewLocator = page.locator(".mx_RoomView_body");
|
||||||
// User sends message
|
// User sends message
|
||||||
@@ -74,13 +79,15 @@ test.describe("Threads", () => {
|
|||||||
|
|
||||||
// Take snapshots in group layout and bubble layout (IRC layout is not available on ThreadView)
|
// Take snapshots in group layout and bubble layout (IRC layout is not available on ThreadView)
|
||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("Initial_ThreadView_on_group_layout.png", {
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("Initial_ThreadView_on_group_layout.png", {
|
||||||
mask: mask,
|
mask,
|
||||||
|
css,
|
||||||
});
|
});
|
||||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
||||||
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='bubble']")).toHaveCount(2);
|
await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='bubble']")).toHaveCount(2);
|
||||||
|
|
||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("Initial_ThreadView_on_bubble_layout.png", {
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("Initial_ThreadView_on_bubble_layout.png", {
|
||||||
mask: mask,
|
mask,
|
||||||
|
css,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set the group layout
|
// Set the group layout
|
||||||
@@ -154,7 +161,8 @@ test.describe("Threads", () => {
|
|||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
||||||
"ThreadView_with_reaction_and_a_hidden_event_on_group_layout.png",
|
"ThreadView_with_reaction_and_a_hidden_event_on_group_layout.png",
|
||||||
{
|
{
|
||||||
mask: mask,
|
mask,
|
||||||
|
css,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -178,7 +186,8 @@ test.describe("Threads", () => {
|
|||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
||||||
"ThreadView_with_reaction_and_a_hidden_event_on_bubble_layout.png",
|
"ThreadView_with_reaction_and_a_hidden_event_on_bubble_layout.png",
|
||||||
{
|
{
|
||||||
mask: mask,
|
mask,
|
||||||
|
css,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -214,7 +223,8 @@ test.describe("Threads", () => {
|
|||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
||||||
"ThreadView_with_redacted_messages_on_group_layout.png",
|
"ThreadView_with_redacted_messages_on_group_layout.png",
|
||||||
{
|
{
|
||||||
mask: mask,
|
mask,
|
||||||
|
css,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
||||||
@@ -222,7 +232,8 @@ test.describe("Threads", () => {
|
|||||||
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
await expect(page.locator(".mx_ThreadView")).toMatchScreenshot(
|
||||||
"ThreadView_with_redacted_messages_on_bubble_layout.png",
|
"ThreadView_with_redacted_messages_on_bubble_layout.png",
|
||||||
{
|
{
|
||||||
mask: mask,
|
mask,
|
||||||
|
css,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -181,8 +181,10 @@ test.describe("Timeline", () => {
|
|||||||
await expect(gels.getByRole("button", { name: "Collapse" })).toBeVisible();
|
await expect(gels.getByRole("button", { name: "Collapse" })).toBeVisible();
|
||||||
|
|
||||||
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-irc-layout.png", {
|
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-irc-layout.png", {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
css: `
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@@ -215,8 +217,10 @@ test.describe("Timeline", () => {
|
|||||||
await expect(gels.getByRole("button", { name: "Collapse" })).toBeVisible();
|
await expect(gels.getByRole("button", { name: "Collapse" })).toBeVisible();
|
||||||
|
|
||||||
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-modern-layout.png", {
|
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-modern-layout.png", {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
css: `
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@@ -255,7 +259,11 @@ test.describe("Timeline", () => {
|
|||||||
// Save snapshot of expanded generic event list summary on bubble layout
|
// Save snapshot of expanded generic event list summary on bubble layout
|
||||||
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-bubble-layout.png", {
|
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-bubble-layout.png", {
|
||||||
// Exclude timestamp from snapshot
|
// Exclude timestamp from snapshot
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Click "collapse" link button on the first hovered info event line
|
// Click "collapse" link button on the first hovered info event line
|
||||||
@@ -271,7 +279,11 @@ test.describe("Timeline", () => {
|
|||||||
|
|
||||||
// Save snapshot of collapsed generic event list summary on bubble layout
|
// Save snapshot of collapsed generic event list summary on bubble layout
|
||||||
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("collapsed-gels-bubble-layout.png", {
|
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("collapsed-gels-bubble-layout.png", {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -312,12 +324,14 @@ test.describe("Timeline", () => {
|
|||||||
"event-line-inline-start-margin-irc-layout.png",
|
"event-line-inline-start-margin-irc-layout.png",
|
||||||
{
|
{
|
||||||
// Exclude timestamp and read marker from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
css: `
|
css: `
|
||||||
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
.mx_MessageTimestamp {
|
||||||
display: none !important;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
`,
|
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await expect(axe).toHaveNoViolations();
|
await expect(axe).toHaveNoViolations();
|
||||||
@@ -409,7 +423,11 @@ test.describe("Timeline", () => {
|
|||||||
"collapsed-gels-and-messages-irc-layout.png",
|
"collapsed-gels-and-messages-irc-layout.png",
|
||||||
{
|
{
|
||||||
// Exclude timestamp from snapshot of mx_MainSplit
|
// Exclude timestamp from snapshot of mx_MainSplit
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -428,7 +446,11 @@ test.describe("Timeline", () => {
|
|||||||
"expanded-gels-and-messages-irc-layout.png",
|
"expanded-gels-and-messages-irc-layout.png",
|
||||||
{
|
{
|
||||||
// Exclude timestamp from snapshot of mx_MainSplit
|
// Exclude timestamp from snapshot of mx_MainSplit
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -453,7 +475,11 @@ test.describe("Timeline", () => {
|
|||||||
"expanded-gels-redaction-placeholder.png",
|
"expanded-gels-redaction-placeholder.png",
|
||||||
{
|
{
|
||||||
// Exclude timestamp from snapshot of mx_MainSplit
|
// Exclude timestamp from snapshot of mx_MainSplit
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -481,7 +507,11 @@ test.describe("Timeline", () => {
|
|||||||
// Record alignment of expanded GELS, placeholder of deleted message, and emote
|
// Record alignment of expanded GELS, placeholder of deleted message, and emote
|
||||||
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-emote-irc-layout.png", {
|
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-emote-irc-layout.png", {
|
||||||
// Exclude timestamp from snapshot of mx_MainSplit
|
// Exclude timestamp from snapshot of mx_MainSplit
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -492,12 +522,14 @@ test.describe("Timeline", () => {
|
|||||||
async ({ page, app, room }) => {
|
async ({ page, app, room }) => {
|
||||||
const screenshotOptions = {
|
const screenshotOptions = {
|
||||||
// Hide because flaky - See https://github.com/vector-im/element-web/issues/24957
|
// Hide because flaky - See https://github.com/vector-im/element-web/issues/24957
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
css: `
|
css: `
|
||||||
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
.mx_MessageTimestamp {
|
||||||
display: none !important;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
`,
|
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
await sendEvent(app.client, room.roomId);
|
await sendEvent(app.client, room.roomId);
|
||||||
@@ -605,12 +637,10 @@ test.describe("Timeline", () => {
|
|||||||
await messageEdit(page);
|
await messageEdit(page);
|
||||||
|
|
||||||
// Click timestamp to highlight hidden event line
|
// Click timestamp to highlight hidden event line
|
||||||
const timestamp = page.locator(".mx_RoomView_body .mx_EventTile_info a", {
|
const timestamp = page.locator(".mx_RoomView_body .mx_EventTile_info a.mx_MessageTimestamp");
|
||||||
has: page.locator(".mx_MessageTimestamp"),
|
|
||||||
});
|
|
||||||
// wait for the remote echo otherwise we get an error modal due to a 404 on the /event/ API
|
// wait for the remote echo otherwise we get an error modal due to a 404 on the /event/ API
|
||||||
await expect(timestamp).not.toHaveAttribute("href", /~!/);
|
await expect(timestamp).not.toHaveAttribute("href", /~!/);
|
||||||
await timestamp.locator(".mx_MessageTimestamp").click();
|
await timestamp.click();
|
||||||
|
|
||||||
// should not add inline start padding to a hidden event line on IRC layout
|
// should not add inline start padding to a hidden event line on IRC layout
|
||||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
@@ -620,12 +650,14 @@ test.describe("Timeline", () => {
|
|||||||
|
|
||||||
// Exclude timestamp and read marker from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
const screenshotOptions = {
|
const screenshotOptions = {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
css: `
|
css: `
|
||||||
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
.mx_MessageTimestamp {
|
||||||
display: none !important;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
`,
|
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
|
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
|
||||||
@@ -654,7 +686,11 @@ test.describe("Timeline", () => {
|
|||||||
|
|
||||||
// Exclude timestamp from snapshot
|
// Exclude timestamp from snapshot
|
||||||
const screenshotOptions = {
|
const screenshotOptions = {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
await sendEvent(app.client, room.roomId);
|
await sendEvent(app.client, room.roomId);
|
||||||
@@ -795,8 +831,10 @@ test.describe("Timeline", () => {
|
|||||||
await app.timeline.scrollToBottom();
|
await app.timeline.scrollToBottom();
|
||||||
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot("url-preview.png", {
|
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot("url-preview.png", {
|
||||||
// Exclude timestamp and read marker from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
css: `
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@@ -892,7 +930,13 @@ test.describe("Timeline", () => {
|
|||||||
|
|
||||||
const tile = page.locator(".mx_EventTile");
|
const tile = page.locator(".mx_EventTile");
|
||||||
await expect(tile).toBeVisible();
|
await expect(tile).toBeVisible();
|
||||||
await expect(tile).toMatchScreenshot("code-block.png", { mask: [page.locator(".mx_MessageTimestamp")] });
|
await expect(tile).toMatchScreenshot("code-block.png", {
|
||||||
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
// Edit a code block and assert the edited code block has been correctly rendered
|
// Edit a code block and assert the edited code block has been correctly rendered
|
||||||
await tile.hover();
|
await tile.hover();
|
||||||
@@ -904,7 +948,11 @@ test.describe("Timeline", () => {
|
|||||||
|
|
||||||
const newTile = page.locator(".mx_EventTile");
|
const newTile = page.locator(".mx_EventTile");
|
||||||
await expect(newTile).toMatchScreenshot("edited-code-block.png", {
|
await expect(newTile).toMatchScreenshot("edited-code-block.png", {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1107,8 +1155,10 @@ test.describe("Timeline", () => {
|
|||||||
|
|
||||||
// Exclude timestamp and read marker from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
const screenshotOptions = {
|
const screenshotOptions = {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
css: `
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@@ -1233,12 +1283,14 @@ test.describe("Timeline", () => {
|
|||||||
|
|
||||||
// Exclude timestamp and read marker from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
const screenshotOptions = {
|
const screenshotOptions = {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
css: `
|
css: `
|
||||||
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
.mx_MessageTimestamp {
|
||||||
display: none !important;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
`,
|
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make sure the strings do not overflow on IRC layout
|
// Make sure the strings do not overflow on IRC layout
|
||||||
@@ -1297,8 +1349,10 @@ test.describe("Timeline", () => {
|
|||||||
|
|
||||||
// Exclude timestamp and read marker from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
const screenshotOptions = {
|
const screenshotOptions = {
|
||||||
mask: [page.locator(".mx_MessageTimestamp")],
|
|
||||||
css: `
|
css: `
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 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: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -13,13 +13,14 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageTimestamp {
|
.mx_MessageTimestamp {
|
||||||
color: var(--MessageTimestamp-color);
|
color: var(--MessageTimestamp-color) !important; /* override anchor color */
|
||||||
font-size: $font-10px;
|
font-size: $font-10px;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
display: block; /* enable the width setting below */
|
display: block; /* enable the width setting below */
|
||||||
width: var(--MessageTimestamp-width);
|
width: var(--MessageTimestamp-width);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageTimestamp_lateIcon {
|
.mx_MessageTimestamp_lateIcon {
|
||||||
|
|||||||
@@ -200,10 +200,6 @@ $left-gutter: 64px;
|
|||||||
Replaces margin-top: -6px. This interacts better with a read
|
Replaces margin-top: -6px. This interacts better with a read
|
||||||
marker being in between. Content overflows. */
|
marker being in between. Content overflows. */
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_EventTile_highlight,
|
&.mx_EventTile_highlight,
|
||||||
@@ -273,9 +269,9 @@ $left-gutter: 64px;
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|
||||||
> a {
|
.mx_MessageTimestamp {
|
||||||
text-decoration: none; /* timestamps are links which shouldn't be underlined */
|
|
||||||
min-width: var(--MessageTimestamp-width); /* ensure space for EventTile without timestamp */
|
min-width: var(--MessageTimestamp-width); /* ensure space for EventTile without timestamp */
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
@@ -403,10 +399,6 @@ $left-gutter: 64px;
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageTimestamp {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EditMessageComposer_buttons {
|
.mx_EditMessageComposer_buttons {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-layout="irc"] .mx_MessageTimestamp {
|
||||||
|
display: none; /* hide the container used for spacing consistency */
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile_line {
|
.mx_EventTile_line {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import MessageContextMenu from "../context_menus/MessageContextMenu";
|
|||||||
import { aboveLeftOf } from "../../structures/ContextMenu";
|
import { aboveLeftOf } from "../../structures/ContextMenu";
|
||||||
import MessageTimestamp from "../messages/MessageTimestamp";
|
import MessageTimestamp from "../messages/MessageTimestamp";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { formatFullDate } from "../../../DateUtils";
|
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
@@ -457,18 +456,15 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||||||
const senderName = mxEvent.sender?.name ?? mxEvent.getSender();
|
const senderName = mxEvent.sender?.name ?? mxEvent.getSender();
|
||||||
const sender = <div className="mx_ImageView_info_sender">{senderName}</div>;
|
const sender = <div className="mx_ImageView_info_sender">{senderName}</div>;
|
||||||
const messageTimestamp = (
|
const messageTimestamp = (
|
||||||
<a
|
<MessageTimestamp
|
||||||
href={permalink}
|
href={permalink}
|
||||||
onClick={this.onPermalinkClicked}
|
onClick={this.onPermalinkClicked}
|
||||||
aria-label={formatFullDate(new Date(mxEvent.getTs()), showTwelveHour, false)}
|
showFullDate={true}
|
||||||
>
|
showTwelveHour={showTwelveHour}
|
||||||
<MessageTimestamp
|
ts={mxEvent.getTs()}
|
||||||
showFullDate={true}
|
showSeconds={false}
|
||||||
showTwelveHour={showTwelveHour}
|
inhibitTooltip
|
||||||
ts={mxEvent.getTs()}
|
/>
|
||||||
showSeconds={false}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
);
|
);
|
||||||
const avatar = (
|
const avatar = (
|
||||||
<MemberAvatar
|
<MemberAvatar
|
||||||
|
|||||||
@@ -24,6 +24,24 @@ interface IProps {
|
|||||||
showFullDate?: boolean;
|
showFullDate?: boolean;
|
||||||
showSeconds?: boolean;
|
showSeconds?: boolean;
|
||||||
showRelative?: boolean;
|
showRelative?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to true then no tooltip will be shown
|
||||||
|
*/
|
||||||
|
inhibitTooltip?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If specified, will be rendered as an anchor bearing the href, a `span` element will be used otherwise
|
||||||
|
*/
|
||||||
|
href?: string;
|
||||||
|
/**
|
||||||
|
* Optional onClick handler to attach to the DOM element
|
||||||
|
*/
|
||||||
|
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||||
|
/**
|
||||||
|
* Optional onContextMenu handler to attach to the DOM element
|
||||||
|
*/
|
||||||
|
onContextMenu?: React.MouseEventHandler<HTMLElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MessageTimestamp extends React.Component<IProps> {
|
export default class MessageTimestamp extends React.Component<IProps> {
|
||||||
@@ -52,12 +70,41 @@ export default class MessageTimestamp extends React.Component<IProps> {
|
|||||||
icon = <LateIcon className="mx_MessageTimestamp_lateIcon" width="16" height="16" />;
|
icon = <LateIcon className="mx_MessageTimestamp_lateIcon" width="16" height="16" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
let content;
|
||||||
<Tooltip description={label} caption={caption}>
|
if (this.props.href) {
|
||||||
<span className="mx_MessageTimestamp" aria-hidden={true} aria-live="off">
|
content = (
|
||||||
|
<a
|
||||||
|
href={this.props.href}
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
onContextMenu={this.props.onContextMenu}
|
||||||
|
className="mx_MessageTimestamp"
|
||||||
|
aria-live="off"
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{timestamp}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<span
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
onContextMenu={this.props.onContextMenu}
|
||||||
|
className="mx_MessageTimestamp"
|
||||||
|
aria-hidden={true}
|
||||||
|
aria-live="off"
|
||||||
|
tabIndex={this.props.onClick || !this.props.inhibitTooltip ? 0 : undefined}
|
||||||
|
>
|
||||||
{icon}
|
{icon}
|
||||||
{timestamp}
|
{timestamp}
|
||||||
</span>
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.inhibitTooltip) return content;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip description={label} caption={caption}>
|
||||||
|
{content}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import ReplyChain from "../elements/ReplyChain";
|
|||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import { Layout } from "../../../settings/enums/Layout";
|
import { Layout } from "../../../settings/enums/Layout";
|
||||||
import { formatTime } from "../../../DateUtils";
|
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { DecryptionFailureBody } from "../messages/DecryptionFailureBody";
|
import { DecryptionFailureBody } from "../messages/DecryptionFailureBody";
|
||||||
import RoomAvatar from "../avatars/RoomAvatar";
|
import RoomAvatar from "../avatars/RoomAvatar";
|
||||||
@@ -253,6 +252,7 @@ interface IState {
|
|||||||
reactions?: Relations | null | undefined;
|
reactions?: Relations | null | undefined;
|
||||||
|
|
||||||
hover: boolean;
|
hover: boolean;
|
||||||
|
focusWithin: boolean;
|
||||||
|
|
||||||
// Position of the context menu
|
// Position of the context menu
|
||||||
contextMenu?: {
|
contextMenu?: {
|
||||||
@@ -299,6 +299,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
declare public context: React.ContextType<typeof RoomContext>;
|
declare public context: React.ContextType<typeof RoomContext>;
|
||||||
|
|
||||||
private unmounted = false;
|
private unmounted = false;
|
||||||
|
private readonly id = uniqueId();
|
||||||
|
|
||||||
public constructor(props: EventTileProps, context: React.ContextType<typeof RoomContext>) {
|
public constructor(props: EventTileProps, context: React.ContextType<typeof RoomContext>) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
@@ -316,6 +317,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
reactions: this.getReactions(),
|
reactions: this.getReactions(),
|
||||||
|
|
||||||
hover: false,
|
hover: false,
|
||||||
|
focusWithin: false,
|
||||||
|
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
@@ -922,7 +924,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
public render(): ReactNode {
|
public render(): ReactNode {
|
||||||
const msgtype = this.props.mxEvent.getContent().msgtype;
|
const msgtype = this.props.mxEvent.getContent().msgtype;
|
||||||
const eventType = this.props.mxEvent.getType();
|
const eventType = this.props.mxEvent.getType();
|
||||||
const id = uniqueId();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasRenderer,
|
hasRenderer,
|
||||||
@@ -1122,6 +1123,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
(this.props.alwaysShowTimestamps ||
|
(this.props.alwaysShowTimestamps ||
|
||||||
this.props.last ||
|
this.props.last ||
|
||||||
this.state.hover ||
|
this.state.hover ||
|
||||||
|
this.state.focusWithin ||
|
||||||
this.state.actionBarFocused ||
|
this.state.actionBarFocused ||
|
||||||
Boolean(this.state.contextMenu));
|
Boolean(this.state.contextMenu));
|
||||||
|
|
||||||
@@ -1135,20 +1137,32 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
ts = this.props.mxEvent.getTs();
|
ts = this.props.mxEvent.getTs();
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageTimestamp = (
|
const messageTimestampProps = {
|
||||||
|
showRelative: this.context.timelineRenderingType === TimelineRenderingType.ThreadsList,
|
||||||
|
showTwelveHour: this.props.isTwelveHour,
|
||||||
|
ts,
|
||||||
|
receivedTs: getLateEventInfo(this.props.mxEvent)?.received_ts,
|
||||||
|
};
|
||||||
|
const messageTimestamp = <MessageTimestamp {...messageTimestampProps} />;
|
||||||
|
const linkedMessageTimestamp = (
|
||||||
<MessageTimestamp
|
<MessageTimestamp
|
||||||
showRelative={this.context.timelineRenderingType === TimelineRenderingType.ThreadsList}
|
{...messageTimestampProps}
|
||||||
showTwelveHour={this.props.isTwelveHour}
|
href={permalink}
|
||||||
ts={ts}
|
onClick={this.onPermalinkClicked}
|
||||||
receivedTs={getLateEventInfo(this.props.mxEvent)?.received_ts}
|
onContextMenu={this.onTimestampContextMenu}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const timestamp = showTimestamp && ts ? messageTimestamp : null;
|
const useIRCLayout = this.props.layout === Layout.IRC;
|
||||||
|
// Used to simplify the UI layout where necessary by not conditionally rendering an element at the start
|
||||||
|
const dummyTimestamp = useIRCLayout ? <span className="mx_MessageTimestamp" /> : null;
|
||||||
|
const timestamp = showTimestamp && ts ? messageTimestamp : dummyTimestamp;
|
||||||
|
const linkedTimestamp =
|
||||||
|
timestamp !== dummyTimestamp && !this.props.hideTimestamp ? linkedMessageTimestamp : dummyTimestamp;
|
||||||
|
|
||||||
let pinnedMessageBadge: JSX.Element | undefined;
|
let pinnedMessageBadge: JSX.Element | undefined;
|
||||||
if (PinningUtils.isPinned(MatrixClientPeg.safeGet(), this.props.mxEvent)) {
|
if (PinningUtils.isPinned(MatrixClientPeg.safeGet(), this.props.mxEvent)) {
|
||||||
pinnedMessageBadge = <PinnedMessageBadge aria-describedby={id} tabIndex={0} />;
|
pinnedMessageBadge = <PinnedMessageBadge aria-describedby={this.id} tabIndex={0} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reactionsRow: JSX.Element | undefined;
|
let reactionsRow: JSX.Element | undefined;
|
||||||
@@ -1165,21 +1179,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
// If we have reactions or a pinned message badge, we need a footer
|
// If we have reactions or a pinned message badge, we need a footer
|
||||||
const hasFooter = Boolean((reactionsRow && this.state.reactions) || pinnedMessageBadge);
|
const hasFooter = Boolean((reactionsRow && this.state.reactions) || pinnedMessageBadge);
|
||||||
|
|
||||||
const linkedTimestamp = !this.props.hideTimestamp ? (
|
|
||||||
<a
|
|
||||||
href={permalink}
|
|
||||||
onClick={this.onPermalinkClicked}
|
|
||||||
aria-label={formatTime(new Date(this.props.mxEvent.getTs()), this.props.isTwelveHour)}
|
|
||||||
onContextMenu={this.onTimestampContextMenu}
|
|
||||||
>
|
|
||||||
{timestamp}
|
|
||||||
</a>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
const useIRCLayout = this.props.layout === Layout.IRC;
|
|
||||||
const groupTimestamp = !useIRCLayout ? linkedTimestamp : null;
|
const groupTimestamp = !useIRCLayout ? linkedTimestamp : null;
|
||||||
const ircTimestamp = useIRCLayout ? linkedTimestamp : null;
|
const ircTimestamp = useIRCLayout ? linkedTimestamp : null;
|
||||||
const bubbleTimestamp = this.props.layout === Layout.Bubble ? messageTimestamp : undefined;
|
|
||||||
const groupPadlock = !useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
|
const groupPadlock = !useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
|
||||||
const ircPadlock = useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
|
const ircPadlock = useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
|
||||||
|
|
||||||
@@ -1210,7 +1211,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
forExport={this.props.forExport}
|
forExport={this.props.forExport}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
layout={this.props.layout}
|
layout={this.props.layout}
|
||||||
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.hover}
|
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.hover || this.state.focusWithin}
|
||||||
isQuoteExpanded={isQuoteExpanded}
|
isQuoteExpanded={isQuoteExpanded}
|
||||||
setQuoteExpanded={this.setQuoteExpanded}
|
setQuoteExpanded={this.setQuoteExpanded}
|
||||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||||
@@ -1237,13 +1238,20 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
"data-event-id": this.props.mxEvent.getId(),
|
"data-event-id": this.props.mxEvent.getId(),
|
||||||
"onMouseEnter": () => this.setState({ hover: true }),
|
"onMouseEnter": () => this.setState({ hover: true }),
|
||||||
"onMouseLeave": () => this.setState({ hover: false }),
|
"onMouseLeave": () => this.setState({ hover: false }),
|
||||||
|
"onFocus": () => this.setState({ focusWithin: true }),
|
||||||
|
"onBlur": () => this.setState({ focusWithin: false }),
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
<div className="mx_EventTile_senderDetails" key="mx_EventTile_senderDetails">
|
<div className="mx_EventTile_senderDetails" key="mx_EventTile_senderDetails">
|
||||||
{avatar}
|
{avatar}
|
||||||
{sender}
|
{sender}
|
||||||
</div>,
|
</div>,
|
||||||
<div id={id} className={lineClasses} key="mx_EventTile_line" onContextMenu={this.onContextMenu}>
|
<div
|
||||||
|
id={this.id}
|
||||||
|
className={lineClasses}
|
||||||
|
key="mx_EventTile_line"
|
||||||
|
onContextMenu={this.onContextMenu}
|
||||||
|
>
|
||||||
{this.renderContextMenu()}
|
{this.renderContextMenu()}
|
||||||
{replyChain}
|
{replyChain}
|
||||||
{renderTile(TimelineRenderingType.Thread, {
|
{renderTile(TimelineRenderingType.Thread, {
|
||||||
@@ -1260,9 +1268,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
showHiddenEvents: this.context.showHiddenEvents,
|
showHiddenEvents: this.context.showHiddenEvents,
|
||||||
})}
|
})}
|
||||||
{actionBar}
|
{actionBar}
|
||||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
{linkedTimestamp}
|
||||||
{timestamp}
|
|
||||||
</a>
|
|
||||||
{msgOption}
|
{msgOption}
|
||||||
</div>,
|
</div>,
|
||||||
hasFooter && (
|
hasFooter && (
|
||||||
@@ -1294,6 +1300,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
"data-has-reply": !!replyChain,
|
"data-has-reply": !!replyChain,
|
||||||
"onMouseEnter": () => this.setState({ hover: true }),
|
"onMouseEnter": () => this.setState({ hover: true }),
|
||||||
"onMouseLeave": () => this.setState({ hover: false }),
|
"onMouseLeave": () => this.setState({ hover: false }),
|
||||||
|
"onFocus": () => this.setState({ focusWithin: true }),
|
||||||
|
"onBlur": () => this.setState({ focusWithin: false }),
|
||||||
"onClick": (ev: MouseEvent) => {
|
"onClick": (ev: MouseEvent) => {
|
||||||
const target = ev.currentTarget as HTMLElement;
|
const target = ev.currentTarget as HTMLElement;
|
||||||
let index = -1;
|
let index = -1;
|
||||||
@@ -1425,13 +1433,20 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
"data-has-reply": !!replyChain,
|
"data-has-reply": !!replyChain,
|
||||||
"onMouseEnter": () => this.setState({ hover: true }),
|
"onMouseEnter": () => this.setState({ hover: true }),
|
||||||
"onMouseLeave": () => this.setState({ hover: false }),
|
"onMouseLeave": () => this.setState({ hover: false }),
|
||||||
|
"onFocus": () => this.setState({ focusWithin: true }),
|
||||||
|
"onBlur": () => this.setState({ focusWithin: false }),
|
||||||
},
|
},
|
||||||
<>
|
<>
|
||||||
{ircTimestamp}
|
{ircTimestamp}
|
||||||
{sender}
|
{sender}
|
||||||
{ircPadlock}
|
{ircPadlock}
|
||||||
{avatar}
|
{avatar}
|
||||||
<div id={id} className={lineClasses} key="mx_EventTile_line" onContextMenu={this.onContextMenu}>
|
<div
|
||||||
|
id={this.id}
|
||||||
|
className={lineClasses}
|
||||||
|
key="mx_EventTile_line"
|
||||||
|
onContextMenu={this.onContextMenu}
|
||||||
|
>
|
||||||
{this.renderContextMenu()}
|
{this.renderContextMenu()}
|
||||||
{groupTimestamp}
|
{groupTimestamp}
|
||||||
{groupPadlock}
|
{groupPadlock}
|
||||||
@@ -1442,7 +1457,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
// overrides
|
// overrides
|
||||||
ref: this.tile,
|
ref: this.tile,
|
||||||
isSeeingThroughMessageHiddenForModeration,
|
isSeeingThroughMessageHiddenForModeration,
|
||||||
timestamp: bubbleTimestamp,
|
|
||||||
|
|
||||||
// appease TS
|
// appease TS
|
||||||
highlights: this.props.highlights,
|
highlights: this.props.highlights,
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ export interface EventTileTypeProps
|
|||||||
| "inhibitInteraction"
|
| "inhibitInteraction"
|
||||||
> {
|
> {
|
||||||
ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
|
ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
|
||||||
timestamp?: JSX.Element;
|
|
||||||
maxImageHeight?: number; // pixels
|
maxImageHeight?: number; // pixels
|
||||||
overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
|
overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
|
||||||
overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;
|
overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;
|
||||||
@@ -288,7 +287,6 @@ export function renderTile(
|
|||||||
callEventGrouper,
|
callEventGrouper,
|
||||||
getRelationsForEvent,
|
getRelationsForEvent,
|
||||||
isSeeingThroughMessageHiddenForModeration,
|
isSeeingThroughMessageHiddenForModeration,
|
||||||
timestamp,
|
|
||||||
inhibitInteraction,
|
inhibitInteraction,
|
||||||
showHiddenEvents,
|
showHiddenEvents,
|
||||||
} = props;
|
} = props;
|
||||||
@@ -336,7 +334,6 @@ export function renderTile(
|
|||||||
callEventGrouper,
|
callEventGrouper,
|
||||||
getRelationsForEvent,
|
getRelationsForEvent,
|
||||||
isSeeingThroughMessageHiddenForModeration,
|
isSeeingThroughMessageHiddenForModeration,
|
||||||
timestamp,
|
|
||||||
inhibitInteraction,
|
inhibitInteraction,
|
||||||
showHiddenEvents,
|
showHiddenEvents,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
|||||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
import { searchPagination, SearchScope } from "../../../../src/Searching";
|
import { searchPagination, SearchScope } from "../../../../src/Searching";
|
||||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
||||||
|
import SettingsStore from "../../../../src/settings/SettingsStore.ts";
|
||||||
|
import { SettingLevel } from "../../../../src/settings/SettingLevel.ts";
|
||||||
|
|
||||||
jest.mock("../../../../src/Searching", () => ({
|
jest.mock("../../../../src/Searching", () => ({
|
||||||
searchPagination: jest.fn(),
|
searchPagination: jest.fn(),
|
||||||
@@ -438,6 +440,7 @@ describe("<RoomSearchView/>", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should pass appropriate permalink creator for all rooms search", async () => {
|
it("should pass appropriate permalink creator for all rooms search", async () => {
|
||||||
|
await SettingsStore.setValue("alwaysShowTimestamps", null, SettingLevel.DEVICE, true);
|
||||||
const room2 = new Room("!room2:server", client, client.getSafeUserId());
|
const room2 = new Room("!room2:server", client, client.getSafeUserId());
|
||||||
const room3 = new Room("!room3:server", client, client.getSafeUserId());
|
const room3 = new Room("!room3:server", client, client.getSafeUserId());
|
||||||
mocked(client.getRoom).mockImplementation(
|
mocked(client.getRoom).mockImplementation(
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ describe("MessageTimestamp", () => {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-live="off"
|
aria-live="off"
|
||||||
class="mx_MessageTimestamp"
|
class="mx_MessageTimestamp"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
08:09
|
08:09
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mx_EventTile_line"
|
class="mx_EventTile_line"
|
||||||
id="4"
|
id="1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_MTextBody mx_EventTile_content"
|
class="mx_MTextBody mx_EventTile_content"
|
||||||
@@ -224,7 +224,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mx_EventTile_line"
|
class="mx_EventTile_line"
|
||||||
id="5"
|
id="2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_MTextBody mx_EventTile_content"
|
class="mx_MTextBody mx_EventTile_content"
|
||||||
@@ -324,6 +324,9 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
|||||||
data-self="true"
|
data-self="true"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="mx_MessageTimestamp"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="mx_DisambiguatedProfile"
|
class="mx_DisambiguatedProfile"
|
||||||
>
|
>
|
||||||
@@ -351,7 +354,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mx_EventTile_line"
|
class="mx_EventTile_line"
|
||||||
id="6"
|
id="3"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_MTextBody mx_EventTile_content"
|
class="mx_MTextBody mx_EventTile_content"
|
||||||
|
|||||||
@@ -467,6 +467,9 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
|||||||
data-self="true"
|
data-self="true"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="mx_MessageTimestamp"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="mx_DisambiguatedProfile"
|
class="mx_DisambiguatedProfile"
|
||||||
>
|
>
|
||||||
|
|||||||