Ver código fonte

improve post timestamp, add thread timestamp

Klaudiusz Dembler 4 anos atrás
pai
commit
d7b5673896

+ 1 - 0
pioneer/packages/joy-forum/src/CategoryList.tsx

@@ -301,6 +301,7 @@ function InnerCategoryThreads (props: CategoryThreadsProps) {
           <Table.HeaderCell>Thread</Table.HeaderCell>
           <Table.HeaderCell>Replies</Table.HeaderCell>
           <Table.HeaderCell>Creator</Table.HeaderCell>
+          <Table.HeaderCell>Created</Table.HeaderCell>
         </Table.Row>
       </Table.Header>
       <Table.Body>

+ 3 - 10
pioneer/packages/joy-forum/src/ViewReply.tsx

@@ -2,7 +2,6 @@ import React, { useState } from 'react';
 import styled from 'styled-components';
 import { Link } from 'react-router-dom';
 import ReactMarkdown from 'react-markdown';
-import Tooltip from 'react-tooltip';
 import { Segment, Button, Icon } from 'semantic-ui-react';
 
 import { Post, Category, Thread } from '@joystream/types/forum';
@@ -11,6 +10,7 @@ import { JoyWarn } from '@polkadot/joy-utils/JoyStatus';
 import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
 import { IfIAmForumSudo } from './ForumSudo';
 import { MemberPreview } from '@polkadot/joy-members/MemberPreview';
+import { TimeAgoDate } from './utils';
 
 const HORIZONTAL_PADDING = '1em';
 const ReplyMarkdown = styled(ReactMarkdown)`
@@ -41,7 +41,7 @@ const ReplyContent = styled.div`
 const ReplyFooter = styled.div`
   border-top: 1px solid rgba(34, 36, 38, .15);
   background-color: #fafcfc;
-  padding: 0.5em ${HORIZONTAL_PADDING};
+  padding: 0.35em ${HORIZONTAL_PADDING};
 `;
 const ReplyFooterActionsRow = styled.div`
   display: flex;
@@ -108,8 +108,6 @@ export function ViewReply (props: ViewReplyProps) {
     </ReplyFooterActionsRow>;
   };
 
-  const replyDate = reply.created_at.momentDate;
-
   return (
     <ReplyContainer>
       <ReplyHeader>
@@ -117,12 +115,7 @@ export function ViewReply (props: ViewReplyProps) {
           <MemberPreview accountId={reply.author_id} />
         </ReplyHeaderAuthorRow>
         <ReplyHeaderDetailsRow>
-          <span data-tip data-for="reply-full-date">
-            {replyDate.fromNow()}
-          </span>
-          <Tooltip id="reply-full-date" place="top" effect="solid">
-            {replyDate.toLocaleString()}
-          </Tooltip>
+          <TimeAgoDate date={reply.created_at.momentDate} id={reply.id} />
           <a>
             #{reply.nr_in_thread.toNumber()}
           </a>

+ 54 - 5
pioneer/packages/joy-forum/src/ViewThread.tsx

@@ -1,12 +1,13 @@
 import React, { useState, useEffect } from 'react';
 import { Link } from 'react-router-dom';
 import ReactMarkdown from 'react-markdown';
+import styled from 'styled-components';
 import { Table, Button, Label } from 'semantic-ui-react';
 import { History } from 'history';
 import BN from 'bn.js';
 
 import { Category, Thread, ThreadId, Post, PostId } from '@joystream/types/forum';
-import { Pagination, RepliesPerPage, CategoryCrumbs } from './utils';
+import { Pagination, RepliesPerPage, CategoryCrumbs, TimeAgoDate } from './utils';
 import { ViewReply } from './ViewReply';
 import { Moderate } from './Moderate';
 import { MutedSpan } from '@polkadot/joy-utils/MutedText';
@@ -18,6 +19,7 @@ import { orderBy } from 'lodash';
 import { bnToStr } from '@polkadot/joy-utils/index';
 import { IfIAmForumSudo } from './ForumSudo';
 import { MemberPreview } from '@polkadot/joy-members/MemberPreview';
+import { formatDate } from '@polkadot/joy-utils/functions/date';
 
 type ThreadTitleProps = {
   thread: Thread;
@@ -36,6 +38,40 @@ function ThreadTitle (props: ThreadTitleProps) {
   </span>;
 }
 
+const ThreadHeader = styled.div`
+  margin: 1rem 0;
+
+  h1 {
+    margin: 0;
+  }
+`;
+
+const ThreadInfoAndActions = styled.div`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  margin-top: .3rem;
+
+  h1 {
+    margin: 0;
+  }
+`;
+
+const ThreadInfo = styled.span`
+  display: inline-flex;
+  align-items: center;
+
+  font-size: .85rem;
+  color: rgba(0, 0, 0, 0.5);
+`;
+
+const ThreadInfoMemberPreview = styled(MemberPreview)`
+  && {
+    margin: 0 .2rem;
+  }
+`;
+
 type InnerViewThreadProps = {
   category: Category;
   thread: Thread;
@@ -90,6 +126,9 @@ function InnerViewThread (props: ViewThreadProps) {
         <Table.Cell>
           <MemberPreview accountId={thread.author_id} />
         </Table.Cell>
+        <Table.Cell>
+          {formatDate(thread.created_at.momentDate)}
+        </Table.Cell>
       </Table.Row>
     );
   }
@@ -211,10 +250,20 @@ function InnerViewThread (props: ViewThreadProps) {
 
   return <div style={{ marginBottom: '1rem' }}>
     <CategoryCrumbs categoryId={thread.category_id} />
-    <h1 className='ForumPageTitle'>
-      <ThreadTitle thread={thread} className='TitleText' />
-      {renderActions()}
-    </h1>
+    <ThreadHeader>
+      <h1 className='ForumPageTitle'>
+        <ThreadTitle thread={thread} className='TitleText' />
+      </h1>
+      <ThreadInfoAndActions>
+        <ThreadInfo>
+          Created by
+          <ThreadInfoMemberPreview accountId={thread.author_id} inline />
+          <TimeAgoDate date={thread.created_at.momentDate} id="thread" />
+        </ThreadInfo>
+        {renderActions()}
+      </ThreadInfoAndActions>
+    </ThreadHeader>
+
     {category.archived &&
       <JoyWarn title={'This thread is in archived category.'}>
         No new replies can be posted.

+ 18 - 0
pioneer/packages/joy-forum/src/utils.tsx

@@ -2,6 +2,8 @@ import React from 'react';
 import { Link } from 'react-router-dom';
 import { Breadcrumb, Pagination as SuiPagination } from 'semantic-ui-react';
 import styled from 'styled-components';
+import moment from 'moment';
+import Tooltip from 'react-tooltip';
 
 import { Category, CategoryId, Thread, ThreadId } from '@joystream/types/forum';
 import { withForumCalls } from './calls';
@@ -115,6 +117,22 @@ export const CategoryCrumbs = ({ categoryId, threadId, root }: CategoryCrumbsPro
   );
 };
 
+type TimeAgoDateProps = {
+  date: moment.Moment;
+  id: any;
+};
+
+export const TimeAgoDate: React.FC<TimeAgoDateProps> = ({ date, id }) => (
+  <>
+    <span data-tip data-for={`${id}-date-tooltip`}>
+      {date.fromNow()}
+    </span>
+    <Tooltip id={`${id}-date-tooltip`} place="top" effect="solid">
+      {date.toLocaleString()}
+    </Tooltip>
+  </>
+);
+
 // It's used on such routes as:
 //   /categories/:id
 //   /categories/:id/edit

+ 18 - 12
pioneer/packages/joy-members/src/MemberPreview.tsx

@@ -24,6 +24,7 @@ type MemberPreviewProps = ApiProps & I18nProps & {
   memberProfile?: Option<any>; // TODO refactor to Option<Profile>
   activeCouncil?: Seat[];
   prefixLabel?: string;
+  inline?: boolean;
   className?: string;
   style?: React.CSSProperties;
 };
@@ -37,7 +38,7 @@ class InnerMemberPreview extends React.PureComponent<MemberPreviewProps> {
   }
 
   private renderProfile (memberProfile: Profile) {
-    const { activeCouncil = [], accountId, prefixLabel, className, style } = this.props;
+    const { activeCouncil = [], accountId, prefixLabel, inline, className, style } = this.props;
     const { handle, avatar_uri } = memberProfile;
 
     const hasAvatar = avatar_uri && nonEmptyStr(avatar_uri.toString());
@@ -48,21 +49,26 @@ class InnerMemberPreview extends React.PureComponent<MemberPreviewProps> {
         {prefixLabel &&
           <MutedSpan className='PrefixLabel'>{prefixLabel}</MutedSpan>
         }
-        {hasAvatar
-          ? <img className='Avatar' src={avatar_uri.toString()} width={AvatarSizePx} height={AvatarSizePx} />
-          : <IdentityIcon className='Avatar' value={accountId} size={AvatarSizePx} />
-        }
+        {!inline && (
+          hasAvatar ? (
+            <img className="Avatar" src={avatar_uri.toString()} width={AvatarSizePx} height={AvatarSizePx} />
+          ) : (
+            <IdentityIcon className="Avatar" value={accountId} size={AvatarSizePx} />
+          )
+        )}
         <div className='Content'>
           <div className='Username'>
             <Link to={`/members/${handle.toString()}`} className='handle'>{handle.toString()}</Link>
           </div>
-          <div className='Details'>
-            {isCouncilor &&
-              <b className='muted text' style={{ color: '#607d8b' }}>
-                <i className='university icon'></i>
-                Council member
-              </b>}
-          </div>
+          {!inline && (
+            <div className='Details'>
+              {isCouncilor &&
+                <b className='muted text' style={{ color: '#607d8b' }}>
+                  <i className='university icon'></i>
+                  Council member
+                </b>}
+            </div>
+          )}
         </div>
       </FlexCenter>
     </div>;

+ 5 - 0
pioneer/packages/joy-utils/src/functions/date.ts

@@ -0,0 +1,5 @@
+import moment from 'moment';
+
+export function formatDate (date: moment.Moment): string {
+  return date.format('YYYY/MM/DD LT');
+}

+ 14 - 1
types/src/forum.ts

@@ -29,7 +29,20 @@ export class BlockchainTimestamp extends JoyStruct<BlockchainTimestampType> {
   }
 
   get momentDate (): moment.Moment {
-    return moment(this.time.toNumber());
+    const YEAR_2000_MILLISECONDS = 946684801000;
+
+    // overflowing in ~270,000 years
+    const timestamp = this.time.toNumber();
+
+    // TODO: remove once https://github.com/Joystream/joystream/issues/705 is resolved
+    // due to a bug, timestamp can be either in seconds or milliseconds
+    let timestampInMillis = timestamp;
+    if (timestamp < YEAR_2000_MILLISECONDS) {
+      // timestamp is in seconds
+      timestampInMillis = timestamp * 1000;
+    }
+
+    return moment(timestampInMillis);
   }
 
   static newEmpty (): BlockchainTimestamp {