Compare commits

...

2 commits

Author SHA1 Message Date
Dean Bassett 0138c6d775
Merge branch 'main' into native_api_dates_glitch 2023-01-06 05:31:50 -08:00
Dean Bassett cb33696f8d Replace all date formatting to use native APIs (#1)
* use date.toLocaleString instead of react-intl

Mastodon shows 24:00 instead of 00:00 for midnight and later times, due to an issue with older versions of react-intl. Upgrading react-intl from v2=>v3 includes breaking changes. Instead, stop using react-intl and instead use native browser APIs, since they have stabilized.
2022-12-29 20:37:42 -08:00
14 changed files with 68 additions and 35 deletions

View file

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl } from 'react-intl';
import { FormattedMessage } from 'react-intl';
import Icon from 'flavours/glitch/components/icon';
import DropdownMenu from './containers/dropdown_menu_container';
import { connect } from 'react-redux';
@ -17,7 +17,6 @@ const mapDispatchToProps = (dispatch, { statusId }) => ({
});
export default @connect(null, mapDispatchToProps)
@injectIntl
class EditedTimestamp extends React.PureComponent {
static propTypes = {
@ -56,12 +55,12 @@ class EditedTimestamp extends React.PureComponent {
}
render () {
const { timestamp, intl, statusId } = this.props;
const { timestamp, statusId } = this.props;
return (
<DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}>
<button className='dropdown-menu__text-button'>
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' />
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: new Date(timestamp).toLocaleString(undefined, { hourCycle: 'h23', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' />
</button>
</DropdownMenu>
);

View file

@ -22,7 +22,7 @@ const messages = defineMessages({
});
const dateFormatOptions = {
hour12: false,
hourCycle: 'h23',
year: 'numeric',
month: 'short',
day: '2-digit',
@ -91,9 +91,9 @@ export const timeAgoString = (intl, date, now, year, timeGiven, short) => {
relativeTime = intl.formatMessage(short ? messages.days : messages.days_full, { number: Math.floor(delta / DAY) });
}
} else if (date.getFullYear() === year) {
relativeTime = intl.formatDate(date, shortDateFormatOptions);
relativeTime = date.toLocaleString(undefined, shortDateFormatOptions);
} else {
relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' });
relativeTime = date.toLocaleString(undefined, { ...shortDateFormatOptions, year: 'numeric' });
}
return relativeTime;
@ -190,7 +190,7 @@ class RelativeTimestamp extends React.Component {
const relativeTime = futureDate ? timeRemainingString(intl, date, this.state.now, timeGiven) : timeAgoString(intl, date, this.state.now, year, timeGiven, short);
return (
<time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}>
<time dateTime={timestamp} title={date.toLocaleString(undefined, dateFormatOptions)}>
{relativeTime}
</time>
);

View file

@ -332,7 +332,7 @@ class StatusActionBar extends ImmutablePureComponent {
<div className='status__action-bar-spacer' />
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'>
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: new Date(status.get('edited_at')).toLocaleString(undefined, { hourCycle: 'h23', year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
</a>
</div>
);

View file

@ -70,7 +70,7 @@ const dateFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
hour12: false,
hourCycle: 'h23',
hour: '2-digit',
minute: '2-digit',
};
@ -319,6 +319,7 @@ class Header extends ImmutablePureComponent {
badge = null;
}
return (
<div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
{!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) && <FollowRequestNoteContainer account={account} />}
@ -372,7 +373,7 @@ class Header extends ImmutablePureComponent {
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} className='translate' />
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: new Date(pair.get('verified_at')).toLocaleString(undefined, dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} className='translate' />
</dd>
</dl>
))}
@ -381,7 +382,7 @@ class Header extends ImmutablePureComponent {
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
<div className='account__header__joined'><FormattedMessage id='account.joined' defaultMessage='Joined {date}' values={{ date: intl.formatDate(account.get('created_at'), { year: 'numeric', month: 'short', day: '2-digit' }) }} /></div>
<div className='account__header__joined'><FormattedMessage id='account.joined' defaultMessage='Joined {date}' values={{ date: new Date(account.get('created_at')).toLocaleString(undefined, { year: 'numeric', month: 'short', day: '2-digit' }) }} /></div>
</div>
</div>
)}

View file

@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import IconButton from 'flavours/glitch/components/icon_button';
import Icon from 'flavours/glitch/components/icon';
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { autoPlayGif, reduceMotion, disableSwiping } from 'flavours/glitch/initial_state';
import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
import { mascot } from 'flavours/glitch/initial_state';
@ -332,11 +332,29 @@ class Announcement extends ImmutablePureComponent {
const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
const skipTime = announcement.get('all_day');
const formattedStartsAt = startsAt?.toLocaleString(undefined, {
hourCycle: 'h23',
year: (skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric',
month: 'short',
day: '2-digit',
hour: skipTime ? undefined : '2-digit',
minute: skipTime ? undefined : '2-digit',
});
const formattedEndsAt = endsAt?.toLocaleString(undefined, {
hourCycle: 'h23',
year: (skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric',
month: skipEndDate ? undefined : 'short',
day: skipEndDate ? undefined : '2-digit',
hour: skipTime ? undefined : '2-digit',
minute: skipTime ? undefined : '2-digit',
});
return (
<div className='announcements__item'>
<strong className='announcements__item__range'>
<FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
{hasTimeRange && <span> · <FormattedDate value={startsAt} hour12={false} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} hour12={false} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>}
{hasTimeRange && <span> · {formattedStartsAt} - {formattedEndsAt}</span>}
</strong>
<Content announcement={announcement} />

View file

@ -7,7 +7,6 @@ import StatusContent from 'flavours/glitch/components/status_content';
import MediaGallery from 'flavours/glitch/components/media_gallery';
import AttachmentList from 'flavours/glitch/components/attachment_list';
import { Link } from 'react-router-dom';
import { injectIntl, FormattedDate } from 'react-intl';
import Card from './card';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Video from 'flavours/glitch/features/video';
@ -21,8 +20,7 @@ import AnimatedNumber from 'flavours/glitch/components/animated_number';
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
import EditedTimestamp from 'flavours/glitch/components/edited_timestamp';
export default @injectIntl
class DetailedStatus extends ImmutablePureComponent {
export default class DetailedStatus extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@ -123,7 +121,7 @@ class DetailedStatus extends ImmutablePureComponent {
render () {
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
const { expanded, onToggleHidden, settings, pictureInPicture, intl } = this.props;
const { expanded, onToggleHidden, settings, pictureInPicture } = this.props;
const outerStyle = { boxSizing: 'border-box' };
const { compact } = this.props;
@ -324,7 +322,7 @@ class DetailedStatus extends ImmutablePureComponent {
<div className='detailed-status__meta'>
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener noreferrer'>
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
{new Date(status.get('created_at')).toLocaleString(undefined, { hourCycle: 'h23', year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
</a>{edited}{visibilityLink}{applicationLink}{reblogLink} · {favouriteLink}
</div>
</div>

View file

@ -51,7 +51,7 @@ function main() {
const timeFormat = new Intl.DateTimeFormat(locale, {
timeStyle: 'short',
hour12: false,
hourCycle: 'h23',
});
[].forEach.call(document.querySelectorAll('.emojify'), (content) => {

View file

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl } from 'react-intl';
import { FormattedMessage } from 'react-intl';
import Icon from 'mastodon/components/icon';
import DropdownMenu from './containers/dropdown_menu_container';
import { connect } from 'react-redux';
@ -17,7 +17,6 @@ const mapDispatchToProps = (dispatch, { statusId }) => ({
});
export default @connect(null, mapDispatchToProps)
@injectIntl
class EditedTimestamp extends React.PureComponent {
static propTypes = {
@ -56,12 +55,12 @@ class EditedTimestamp extends React.PureComponent {
}
render () {
const { timestamp, intl, statusId } = this.props;
const { timestamp, statusId } = this.props;
return (
<DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}>
<button className='dropdown-menu__text-button'>
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' />
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: new Date(timestamp).toLocaleString(undefined, { hourCycle: 'h23', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' />
</button>
</DropdownMenu>
);

View file

@ -22,7 +22,7 @@ const messages = defineMessages({
});
const dateFormatOptions = {
hour12: false,
hourCycle: 'h23',
year: 'numeric',
month: 'short',
day: '2-digit',
@ -190,7 +190,7 @@ class RelativeTimestamp extends React.Component {
const relativeTime = futureDate ? timeRemainingString(intl, date, this.state.now, timeGiven) : timeAgoString(intl, date, this.state.now, year, timeGiven, short);
return (
<time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}>
<time dateTime={timestamp} title={date.toLocaleString(undefined, dateFormatOptions)}>
{relativeTime}
</time>
);

View file

@ -28,7 +28,7 @@ export const textForScreenReader = (intl, status, rebloggedByText = false) => {
const values = [
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
status.get('spoiler_text') && status.get('hidden') ? status.get('spoiler_text') : status.get('search_index').slice(status.get('spoiler_text').length),
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
new Date(status.get('created_at')).toLocaleString(undefined, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
status.getIn(['account', 'acct']),
];
@ -513,7 +513,7 @@ class Status extends ImmutablePureComponent {
<div className='status__info'>
<a onClick={this.handleClick} href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: new Date(status.get('edited_at')).toLocaleString(undefined, { hourCycle: 'h23', year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
</a>
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>

View file

@ -70,7 +70,7 @@ const dateFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
hour12: false,
hourCycle: 'h23',
hour: '2-digit',
minute: '2-digit',
};
@ -367,7 +367,7 @@ class Header extends ImmutablePureComponent {
<div className='account__header__fields'>
<dl>
<dt><FormattedMessage id='account.joined_short' defaultMessage='Joined' /></dt>
<dd>{intl.formatDate(account.get('created_at'), { year: 'numeric', month: 'short', day: '2-digit' })}</dd>
<dd>{new Date(account.get('created_at')).toLocaleString(undefined, { year: 'numeric', month: 'short', day: '2-digit' })}</dd>
</dl>
{fields.map((pair, i) => (
@ -375,7 +375,7 @@ class Header extends ImmutablePureComponent {
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
<dd className='translate' title={pair.get('value_plain')}>
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: new Date(pair.get('verified_at')).toLocaleString(undefined, dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
</dd>
</dl>
))}

View file

@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import IconButton from 'mastodon/components/icon_button';
import Icon from 'mastodon/components/icon';
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { autoPlayGif, reduceMotion, disableSwiping } from 'mastodon/initial_state';
import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
import { mascot } from 'mastodon/initial_state';
@ -332,11 +332,29 @@ class Announcement extends ImmutablePureComponent {
const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
const skipTime = announcement.get('all_day');
const formattedStartsAt = startsAt?.toLocaleString(undefined, {
hourCycle: 'h23',
year: (skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric',
month: 'short',
day: '2-digit',
hour: skipTime ? undefined : '2-digit',
minute: skipTime ? undefined : '2-digit',
});
const formattedEndsAt = endsAt?.toLocaleString(undefined, {
hourCycle: 'h23',
year: (skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric',
month: skipEndDate ? undefined : 'short',
day: skipEndDate ? undefined : '2-digit',
hour: skipTime ? undefined : '2-digit',
minute: skipTime ? undefined : '2-digit',
});
return (
<div className='announcements__item'>
<strong className='announcements__item__range'>
<FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
{hasTimeRange && <span> · <FormattedDate value={startsAt} hour12={false} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} hour12={false} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>}
{hasTimeRange && <span> · {formattedStartsAt} - {formattedEndsAt}</span>}
</strong>
<Content announcement={announcement} />

View file

@ -277,7 +277,7 @@ class DetailedStatus extends ImmutablePureComponent {
<div className='detailed-status__meta'>
<a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
{new Date(status.get('created_at')).toLocaleString(undefined, { hourCycle: 'h23', year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
</a>{edited}{visibilityLink}{applicationLink}{reblogLink} · {favouriteLink}
</div>
</div>

View file

@ -55,7 +55,7 @@ function main() {
const timeFormat = new Intl.DateTimeFormat(locale, {
timeStyle: 'short',
hour12: false,
hourCycle: 'h23',
});
[].forEach.call(document.querySelectorAll('.emojify'), (content) => {