Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
process.env.TZ = 'UTC';

module.exports = {
rootDir: '..',
moduleNameMapper: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"i18next": "^19.3.2",
"immutable": "^4.0.0-rc.12",
"lodash": "^4.17.15",
"mockdate": "^3.0.2",
"prop-types": "^15.7.2",
"react": "^16.13.0",
"react-dom": "^16.13.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ exports[`Card renders correctly with default props 1`] = `
>
<Timeago
datetime="2019-02-20T11:46:19.000Z"
renderDate={null}
renderDays={null}
renderHours={null}
renderMinutes={null}
renderNow={null}
/>
</li>
<li>
Expand Down Expand Up @@ -332,6 +337,11 @@ exports[`Card renders correctly with isUnread set to true 1`] = `
>
<Timeago
datetime="2019-02-20T11:46:19.000Z"
renderDate={null}
renderDays={null}
renderHours={null}
renderMinutes={null}
renderNow={null}
/>
</li>
<li>
Expand Down Expand Up @@ -513,6 +523,11 @@ exports[`Card renders correctly with no title 1`] = `
>
<Timeago
datetime="2019-02-20T11:46:19.000Z"
renderDate={null}
renderDays={null}
renderHours={null}
renderMinutes={null}
renderNow={null}
/>
</li>
<li>
Expand Down Expand Up @@ -672,6 +687,11 @@ exports[`Card renders correctly with no title and announcement type 1`] = `
>
<Timeago
datetime="2019-02-20T11:46:19.000Z"
renderDate={null}
renderDays={null}
renderHours={null}
renderMinutes={null}
renderNow={null}
/>
</li>
<li>
Expand Down Expand Up @@ -848,6 +868,11 @@ exports[`Card renders correctly with two actors 1`] = `
>
<Timeago
datetime="2019-02-20T11:46:19.000Z"
renderDate={null}
renderDays={null}
renderHours={null}
renderMinutes={null}
renderNow={null}
/>
</li>
<li>
Expand Down
38 changes: 33 additions & 5 deletions source/components/Timeago/__snapshots__/index.spec.js.snap
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Renders Timeago component 1`] = `
<span
title={1582205173526}
exports[`Timeago now should render custom string 1`] = `
<time
dateTime="2018-09-17T12:58:43.000Z"
title="2018-09-17T12:58:43.000Z"
>
2/20/2020
</span>
customMessage
</time>
`;

exports[`Timeago now should render custom string 2`] = `
<time
dateTime="2018-09-17T12:58:43.000Z"
title="2018-09-17T12:58:43.000Z"
>
customMessage
</time>
`;

exports[`Timeago now should render default string 1`] = `
<time
dateTime="2018-09-17T12:58:43.000Z"
title="2018-09-17T12:58:43.000Z"
>
now
</time>
`;

exports[`Timeago now should render default string 2`] = `
<time
dateTime="2018-09-17T12:58:43.000Z"
title="2018-09-17T12:58:43.000Z"
>
now
</time>
`;
121 changes: 66 additions & 55 deletions source/components/Timeago/index.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,81 @@
import React from 'react';
import PropTypes from 'prop-types';

/* istanbul ignore next */
function getTimeDistanceString(datetime) {
const date = new Date(parseInt(datetime, 10));
const now = Date.now();
const diffInSeconds = (now - date) / 1000;
export const SECOND_MILLISECONDS = 1000;
export const MINUTE_SECONDS = 60;
export const HOUR_SECONDS = 60 * MINUTE_SECONDS;
export const DAY_SECONDS = 24 * HOUR_SECONDS;
export const FIVE_DAYS_SECONDS = 5 * DAY_SECONDS;

if (diffInSeconds > 432000) {
// more than 5 days ago - show date
return date.toLocaleDateString();
}

if (diffInSeconds > 86400) {
// more than a day ago
return `${Math.round(diffInSeconds / 60 / 60 / 24)}d`;
}

if (diffInSeconds > 3600) {
// more than an hour ago
return `${Math.round(diffInSeconds / 60 / 60)}h`;
}

if (diffInSeconds < 60) {
// less than a minute ago
return 'now';
}

return `${Math.round(diffInSeconds / 60)}m`;
}
export const TIMEAGO_SHOW_DATE_THRESHOLD = FIVE_DAYS_SECONDS;
export const TIMEAGO_NOW_THRESHOLD = MINUTE_SECONDS;

/**
* The Timeago component is a small component that
* shows the number of seconds/minutes/days from given datetime.
*
* It all happens after the component is mounted so it's safe to use this
* component on the Back-End without messing up the hydration.
*/
export default class Timeago extends React.Component {
state = {
display: this.props.datetime,
};
export default function Timeago({
datetime, renderNow, renderDate, renderDays, renderHours, renderMinutes,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can render* function return JSX/JS string? Ideally Id like to use renderNow={() => <strong>NOW!</string>}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can return anything because it will just call your render* function and return its result. Check this fragment out: return typeof (renderNow) === 'function' ? renderNow() : 'now'; So sure, you can use it like you did it above :)

}) {
const date = new Date(datetime);

static propTypes = {
datetime: PropTypes.oneOfType([
PropTypes.instanceOf(Date),
PropTypes.string,
]).isRequired,
};
const withCustomRender = (renderFunction, value, defaultElement = value) => (typeof (renderFunction) === 'function' ? renderFunction(value) : defaultElement);

const renderTime = () => {
const now = Date.now();
const diffInSeconds = (now - date.getTime()) / SECOND_MILLISECONDS;

componentDidMount() {
const { datetime } = this.props;
if (diffInSeconds > TIMEAGO_SHOW_DATE_THRESHOLD) {
const dateString = date.toLocaleDateString();

this.setState({
display: getTimeDistanceString(datetime),
});
}
return withCustomRender(renderDate, dateString);
}

render() {
const { datetime } = this.props;
const { display } = this.state;
if (diffInSeconds > DAY_SECONDS) {
// more than a day ago
const days = Math.round(diffInSeconds / DAY_SECONDS);
return withCustomRender(renderDays, days, `${days}d`);
}

return (
<span title={datetime}>
{display}
</span>
);
}
if (diffInSeconds > HOUR_SECONDS) {
// more than an hour ago
const hours = Math.round(diffInSeconds / HOUR_SECONDS);
return withCustomRender(renderHours, hours, `${hours}h`);
}

if (diffInSeconds > TIMEAGO_NOW_THRESHOLD) {
const minutes = Math.round(diffInSeconds / MINUTE_SECONDS);
return withCustomRender(renderMinutes, minutes, `${minutes}m`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this string (the default ones) be translated maybe? Just a guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think react-common doesn't handle translations. That's why I thought we need those custom renders to provide translations in specific apps. With this, we can create a wrapper for Timeago for example in Feeds which will handle translations there.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some components of design-system handles i18n, so I think it wouldn't be an issue to handle this in react-common as well. I see that we don't have any components in this repo that translates anything but maybe we should think about that.

}

// less than a minute ago
return typeof (renderNow) === 'function' ? renderNow() : 'now';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be translated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};


return (
<time dateTime={date.toLocaleString()} title={date.toLocaleString()}>
{renderTime()}
</time>
);
}

Timeago.propTypes = {
datetime: PropTypes.oneOfType([
PropTypes.instanceOf(Date),
PropTypes.string,
]).isRequired,
renderDate: PropTypes.func,
renderDays: PropTypes.func,
renderHours: PropTypes.func,
renderMinutes: PropTypes.func,
renderNow: PropTypes.func,
};

Timeago.defaultProps = {
renderDays: null,
renderHours: null,
renderMinutes: null,
renderDate: null,
renderNow: null,
};
Loading