Show full timestamp on hover (#714)
authorginnyTheCat <ginnythecat@lelux.net>
Sat, 6 Aug 2022 04:05:56 +0000 (06:05 +0200)
committerGitHub <noreply@github.com>
Sat, 6 Aug 2022 04:05:56 +0000 (09:35 +0530)
* Show full timestamp on hover

* Not always display time

* Always show full timestamp in search

src/app/atoms/time/Time.jsx [new file with mode: 0644]
src/app/molecules/message/Message.jsx
src/app/molecules/message/TimelineChange.jsx
src/app/molecules/room-search/RoomSearch.jsx
src/app/organisms/room/RoomViewContent.jsx

diff --git a/src/app/atoms/time/Time.jsx b/src/app/atoms/time/Time.jsx
new file mode 100644 (file)
index 0000000..750b958
--- /dev/null
@@ -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;
index e94e5a46027c624d5d0af2d37cfcd11e67780aa2..2f32bafa6d69a1f507777ef2d617c94560ffa340 100644 (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 };
index 5ad33d31b3c7a0e6e1fe0fe8b525dd50631fbba3..bc6e913f34a54ae557954b64d340521aeac75558 100644 (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,
 };
 
index f6bdf2424e91a98f465e928bf4d74a64a320611b..bd1cdfe95bdb2abb5239ac5e571a487afc9b0f06 100644 (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>
index ab1dfbab675be81c6c15476d8909d71c938dd96c..521996483e6093bf14643577d53905e5b513d3ef 100644 (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}
     />
   );
 }