diff --git a/src/components/views/messages/MessageTimestamp.tsx b/src/components/views/messages/MessageTimestamp.tsx index 09f49ebea0f..fba6db726b9 100644 --- a/src/components/views/messages/MessageTimestamp.tsx +++ b/src/components/views/messages/MessageTimestamp.tsx @@ -19,9 +19,14 @@ import React from "react"; import { Tooltip } from "@vector-im/compound-web"; import { formatFullDate, formatTime, formatFullTime, formatRelativeTime } from "../../../DateUtils"; +import { _t } from "../../../languageHandler"; interface IProps { ts: number; + /** + * If specified will render both the sent-at and received-at timestamps in the tooltip + */ + receivedTs?: number; showTwelveHour?: boolean; showFullDate?: boolean; showSeconds?: boolean; @@ -42,8 +47,18 @@ export default class MessageTimestamp extends React.Component { timestamp = formatTime(date, this.props.showTwelveHour); } + let label = formatFullDate(date, this.props.showTwelveHour); + let caption: string | undefined; + if (this.props.receivedTs !== undefined) { + label = _t("timeline|message_timestamp_sent_at", { dateTime: label }); + const receivedDate = new Date(this.props.receivedTs); + caption = _t("timeline|message_timestamp_received_at", { + dateTime: formatFullDate(receivedDate, this.props.showTwelveHour), + }); + } + return ( - + {timestamp} diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 8961b5b0139..f5166f0c071 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1126,6 +1126,7 @@ export class UnwrappedEventTile extends React.Component showRelative={this.context.timelineRenderingType === TimelineRenderingType.ThreadsList} showTwelveHour={this.props.isTwelveHour} ts={ts} + receivedTs={this.props.mxEvent.getUnsigned()["io.element.late_event"]?.received_at} /> ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 58440cda411..3e14c2cbefb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3417,6 +3417,8 @@ "label": "Message Actions", "view_in_room": "View in room" }, + "message_timestamp_received_at": "Recovered at: %(dateTime)s", + "message_timestamp_sent_at": "Sent at: %(dateTime)s", "mjolnir": { "changed_rule_glob": "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", "changed_rule_rooms": "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", diff --git a/test/components/views/messages/MessageTimestamp-test.tsx b/test/components/views/messages/MessageTimestamp-test.tsx new file mode 100644 index 00000000000..a636e6f3e60 --- /dev/null +++ b/test/components/views/messages/MessageTimestamp-test.tsx @@ -0,0 +1,63 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import MessageTimestamp from "../../../../src/components/views/messages/MessageTimestamp"; + +jest.mock("../../../../src/settings/SettingsStore"); + +describe("MessageTimestamp", () => { + // Friday Dec 17 2021, 9:09am + const nowDate = new Date("2021-12-17T08:09:00.000Z"); + + const HOUR_MS = 3600000; + const DAY_MS = HOUR_MS * 24; + + it("should render HH:MM", () => { + const { asFragment } = render(); + expect(asFragment()).toMatchInlineSnapshot(` + + + + `); + }); + + it("should show full date & time on hover", async () => { + const { container } = render(); + await userEvent.hover(container.querySelector(".mx_MessageTimestamp")!); + expect((await screen.findByRole("tooltip")).textContent).toMatchInlineSnapshot(`"Fri, Dec 17, 2021, 08:09:00"`); + }); + + it("should show sent & received time on hover if passed", async () => { + const { container } = render( + , + ); + await userEvent.hover(container.querySelector(".mx_MessageTimestamp")!); + expect((await screen.findByRole("tooltip")).textContent).toMatchInlineSnapshot( + `"Sent at: Fri, Dec 17, 2021, 08:09:00Recovered at: Sat, Dec 18, 2021, 08:09:00"`, + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index 08becfbaf6d..f2cf6effb9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1286,14 +1286,14 @@ core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.10.2": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.13.10": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== @@ -1543,9 +1543,9 @@ "@floating-ui/dom" "^1.5.1" "@floating-ui/utils@^0.1.3": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.4.tgz#19654d1026cc410975d46445180e70a5089b3e7d" - integrity sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA== + version "0.1.6" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" + integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== "@humanwhocodes/config-array@^0.11.10": version "0.11.10" @@ -2301,10 +2301,10 @@ dependencies: "@babel/runtime" "^7.13.10" -"@radix-ui/react-dismissable-layer@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.4.tgz#883a48f5f938fa679427aa17fcba70c5494c6978" - integrity sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg== +"@radix-ui/react-dismissable-layer@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" + integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== dependencies: "@babel/runtime" "^7.13.10" "@radix-ui/primitive" "1.0.1" @@ -2342,10 +2342,10 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" -"@radix-ui/react-popper@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.2.tgz#4c0b96fcd188dc1f334e02dba2d538973ad842e9" - integrity sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg== +"@radix-ui/react-popper@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" + integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w== dependencies: "@babel/runtime" "^7.13.10" "@floating-ui/react-dom" "^2.0.0" @@ -2359,10 +2359,10 @@ "@radix-ui/react-use-size" "1.0.1" "@radix-ui/rect" "1.0.1" -"@radix-ui/react-portal@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.3.tgz#ffb961244c8ed1b46f039e6c215a6c4d9989bda1" - integrity sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA== +"@radix-ui/react-portal@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" + integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== dependencies: "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" @@ -2393,18 +2393,18 @@ "@radix-ui/react-compose-refs" "1.0.1" "@radix-ui/react-tooltip@^1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.6.tgz#87a7786cd9f2b4de957ac645afae1575339c58b0" - integrity sha512-DmNFOiwEc2UDigsYj6clJENma58OelxD24O4IODoZ+3sQc3Zb+L8w1EP+y9laTuKCLAysPw4fD6/v0j4KNV8rg== + version "1.0.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz#8f55070f852e7e7450cc1d9210b793d2e5a7686e" + integrity sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw== dependencies: "@babel/runtime" "^7.13.10" "@radix-ui/primitive" "1.0.1" "@radix-ui/react-compose-refs" "1.0.1" "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-dismissable-layer" "1.0.4" + "@radix-ui/react-dismissable-layer" "1.0.5" "@radix-ui/react-id" "1.0.1" - "@radix-ui/react-popper" "1.1.2" - "@radix-ui/react-portal" "1.0.3" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" "@radix-ui/react-presence" "1.0.1" "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-slot" "1.0.2" @@ -3136,9 +3136,9 @@ svg2vectordrawable "^2.9.1" "@vector-im/compound-web@^0.5.0": - version "0.5.3" - resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-0.5.3.tgz#5d0e22c6cf277a605151099d3850ddc74290078c" - integrity sha512-h8xIrMHTQklan2W7ImoFUnvMvcNcNaromcS0QffkttwLZmOjX2EXmPhlq5JhTm+ZRr4+WN7Hjok1F1vF08RStA== + version "0.5.4" + resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-0.5.4.tgz#a99b346fe8de34a6f8bcbf9e1040ce1d79615dbc" + integrity sha512-w4Nwzid5Y89Dt9GaxKO+kWPTjSitLpkmoAjMYHVUajNMCfUxluzu4eOgjPRCpubPH5lZUB6/95y43wOI+pEC1Q== dependencies: "@radix-ui/react-form" "^0.0.3" "@radix-ui/react-tooltip" "^1.0.6"