Show full timestamp on hover (#714)

* Show full timestamp on hover

* Not always display time

* Always show full timestamp in search
This commit is contained in:
ginnyTheCat 2022-08-06 06:05:56 +02:00 committed by GitHub
parent 04f910ee03
commit 21726b63f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 17 deletions

View file

@ -0,0 +1,44 @@
import React from 'react';
import PropTypes from 'prop-types';
import dateFormat from 'dateformat';
import { isInSameDay } from '../../../util/common';
function Time({ timestamp, fullTime }) {
const date = new Date(timestamp);
const formattedFullTime = dateFormat(date, 'dd mmmm yyyy, hh:MM TT');
let formattedDate = formattedFullTime;
if (!fullTime) {
const compareDate = new Date();
const isToday = isInSameDay(date, compareDate);
compareDate.setDate(compareDate.getDate() - 1);
const isYesterday = isInSameDay(date, compareDate);
formattedDate = dateFormat(date, isToday || isYesterday ? 'hh:MM TT' : 'dd/mm/yyyy');
if (isYesterday) {
formattedDate = `Yesterday, ${formattedDate}`;
}
}
return (
<time
dateTime={date.toISOString()}
title={formattedFullTime}
>
{formattedDate}
</time>
);
}
Time.defaultProps = {
fullTime: false,
};
Time.propTypes = {
timestamp: PropTypes.number.isRequired,
fullTime: PropTypes.bool,
};
export default Time;

View file

@ -24,6 +24,7 @@ import Tooltip from '../../atoms/tooltip/Tooltip';
import Input from '../../atoms/input/Input';
import Avatar from '../../atoms/avatar/Avatar';
import IconButton from '../../atoms/button/IconButton';
import Time from '../../atoms/time/Time';
import ContextMenu, { MenuHeader, MenuItem, MenuBorder } from '../../atoms/context-menu/ContextMenu';
import * as Media from '../media/Media';
@ -67,7 +68,7 @@ const MessageAvatar = React.memo(({
));
const MessageHeader = React.memo(({
userId, username, time,
userId, username, timestamp, fullTime,
}) => (
<div className="message__header">
<Text
@ -81,14 +82,20 @@ const MessageHeader = React.memo(({
<span>{twemojify(userId)}</span>
</Text>
<div className="message__time">
<Text variant="b3">{time}</Text>
<Text variant="b3">
<Time timestamp={timestamp} fullTime={fullTime} />
</Text>
</div>
</div>
));
MessageHeader.defaultProps = {
fullTime: false,
};
MessageHeader.propTypes = {
userId: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
time: PropTypes.string.isRequired,
timestamp: PropTypes.number.isRequired,
fullTime: PropTypes.bool,
};
function MessageReply({ name, color, body }) {
@ -690,7 +697,7 @@ function getEditedBody(editedMEvent) {
}
function Message({
mEvent, isBodyOnly, roomTimeline, focus, time,
mEvent, isBodyOnly, roomTimeline, focus, fullTime,
}) {
const [isEditing, setIsEditing] = useState(false);
const roomId = mEvent.getRoomId();
@ -751,7 +758,12 @@ function Message({
}
<div className="message__main-container">
{!isBodyOnly && (
<MessageHeader userId={senderId} username={username} time={time} />
<MessageHeader
userId={senderId}
username={username}
timestamp={mEvent.getTs()}
fullTime={fullTime}
/>
)}
{roomTimeline && isReply && (
<MessageReplyWrapper
@ -799,13 +811,14 @@ Message.defaultProps = {
isBodyOnly: false,
focus: false,
roomTimeline: null,
fullTime: false,
};
Message.propTypes = {
mEvent: PropTypes.shape({}).isRequired,
isBodyOnly: PropTypes.bool,
roomTimeline: PropTypes.shape({}),
focus: PropTypes.bool,
time: PropTypes.string.isRequired,
fullTime: PropTypes.bool,
};
export { Message, MessageReply, PlaceholderMessage };

View file

@ -4,6 +4,7 @@ import './TimelineChange.scss';
import Text from '../../atoms/text/Text';
import RawIcon from '../../atoms/system-icons/RawIcon';
import Time from '../../atoms/time/Time';
import JoinArraowIC from '../../../../public/res/ic/outlined/join-arrow.svg';
import LeaveArraowIC from '../../../../public/res/ic/outlined/leave-arrow.svg';
@ -12,7 +13,7 @@ import InviteCancelArraowIC from '../../../../public/res/ic/outlined/invite-canc
import UserIC from '../../../../public/res/ic/outlined/user.svg';
function TimelineChange({
variant, content, time, onClick,
variant, content, timestamp, onClick,
}) {
let iconSrc;
@ -48,7 +49,9 @@ function TimelineChange({
</Text>
</div>
<div className="timeline-change__time">
<Text variant="b3">{time}</Text>
<Text variant="b3">
<Time timestamp={timestamp} />
</Text>
</div>
</button>
);
@ -68,7 +71,7 @@ TimelineChange.propTypes = {
PropTypes.string,
PropTypes.node,
]).isRequired,
time: PropTypes.string.isRequired,
timestamp: PropTypes.number.isRequired,
onClick: PropTypes.func,
};

View file

@ -120,14 +120,13 @@ function RoomSearch({ roomId }) {
const renderTimeline = (timeline) => (
<div className="room-search__result-item" key={timeline[0].getId()}>
{ timeline.map((mEvent) => {
const time = dateFormat(mEvent.getDate(), 'dd/mm/yyyy - hh:MM TT');
const id = mEvent.getId();
return (
<React.Fragment key={id}>
<Message
mEvent={mEvent}
isBodyOnly={false}
time={time}
fullTime
/>
<Button onClick={() => selectRoom(roomId, id)}>View</Button>
</React.Fragment>

View file

@ -125,10 +125,7 @@ function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
&& prevMEvent.getType() !== 'm.room.create'
&& diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES
);
const mDate = mEvent.getDate();
const isToday = isInSameDay(mDate, new Date());
const time = dateFormat(mDate, isToday ? 'hh:MM TT' : 'dd/mm/yyyy');
const timestamp = mEvent.getTs();
if (mEvent.getType() === 'm.room.member') {
const timelineChange = parseTimelineChange(mEvent);
@ -138,7 +135,7 @@ function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
key={mEvent.getId()}
variant={timelineChange.variant}
content={timelineChange.content}
time={time}
timestamp={timestamp}
/>
);
}
@ -149,7 +146,7 @@ function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
isBodyOnly={isBodyOnly}
roomTimeline={roomTimeline}
focus={isFocus}
time={time}
fullTime={false}
/>
);
}