Commiting

This commit is contained in:
mgabdev
2020-11-15 12:48:32 -06:00
parent 62515bbaee
commit fb612f60c8
1011 changed files with 3507 additions and 49604 deletions

View File

@@ -1,82 +0,0 @@
import emojify from '../emoji';
describe('emoji', () => {
describe('.emojify', () => {
it('ignores unknown shortcodes', () => {
expect(emojify(':foobarbazfake:')).toEqual(':foobarbazfake:');
});
it('ignores shortcodes inside of tags', () => {
expect(emojify('<p data-foo=":smile:"></p>')).toEqual('<p data-foo=":smile:"></p>');
});
it('works with unclosed tags', () => {
expect(emojify('hello>')).toEqual('hello>');
expect(emojify('<hello')).toEqual('<hello');
});
it('works with unclosed shortcodes', () => {
expect(emojify('smile:')).toEqual('smile:');
expect(emojify(':smile')).toEqual(':smile');
});
it('does unicode', () => {
expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual(
'<img draggable="false" class="emojione" alt="👩‍👩‍👦‍👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg" />');
expect(emojify('👨‍👩‍👧‍👧')).toEqual(
'<img draggable="false" class="emojione" alt="👨‍👩‍👧‍👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg" />');
expect(emojify('👩‍👩‍👦')).toEqual('<img draggable="false" class="emojione" alt="👩‍👩‍👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg" />');
expect(emojify('\u2757')).toEqual(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />');
});
it('does multiple unicode', () => {
expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />');
expect(emojify('\u2757#\uFE0F\u20E3')).toEqual(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />');
expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />');
expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual(
'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> bar');
});
it('ignores unicode inside of tags', () => {
expect(emojify('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>')).toEqual('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>');
});
it('does multiple emoji properly (issue 5188)', () => {
expect(emojify('👌🌈💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />');
expect(emojify('👌 🌈 💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />');
});
it('does an emoji that has no shortcode', () => {
expect(emojify('👁‍🗨')).toEqual('<img draggable="false" class="emojione" alt="👁‍🗨" title="" src="/emoji/1f441-200d-1f5e8.svg" />');
});
it('does an emoji whose filename is irregular', () => {
expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />');
});
it('avoid emojifying on invisible text', () => {
expect(emojify('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>'))
.toEqual('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>');
expect(emojify('<span class="invisible">:luigi:</span>', { ':luigi:': { static_url: 'luigi.exe' } }))
.toEqual('<span class="invisible">:luigi:</span>');
});
it('avoid emojifying on invisible text with nested tags', () => {
expect(emojify('<span class="invisible">😄<span class="foo">bar</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
expect(emojify('<span class="invisible">😄<span class="invisible">😕</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
expect(emojify('<span class="invisible">😄<br/>😴</span>😇'))
.toEqual('<span class="invisible">😄<br/>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
});
it('skips the textual presentation VS15 character', () => {
expect(emojify('✴︎')) // This is U+2734 EIGHT POINTED BLACK STAR then U+FE0E VARIATION SELECTOR-15
.toEqual('<img draggable="false" class="emojione" alt="✴" title=":eight_pointed_black_star:" src="/emoji/2734.svg" />');
});
});
});

View File

@@ -1,176 +0,0 @@
import pick from 'lodash.pick'
import { emojiIndex } from 'emoji-mart';
import { search } from '../emoji_mart_search_light';
const trimEmojis = emoji => pick(emoji, ['id', 'unified', 'native', 'custom']);
describe('emoji_index', () => {
it('should give same result for emoji_index_light and emoji-mart', () => {
const expected = [
{
id: 'pineapple',
unified: '1f34d',
native: '🍍',
},
];
expect(search('pineapple').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('pineapple').map(trimEmojis)).toEqual(expected);
});
it('orders search results correctly', () => {
const expected = [
{
id: 'apple',
unified: '1f34e',
native: '🍎',
},
{
id: 'pineapple',
unified: '1f34d',
native: '🍍',
},
{
id: 'green_apple',
unified: '1f34f',
native: '🍏',
},
{
id: 'iphone',
unified: '1f4f1',
native: '📱',
},
];
expect(search('apple').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('apple').map(trimEmojis)).toEqual(expected);
});
it('can include/exclude categories', () => {
expect(search('flag', { include: ['people'] })).toEqual([]);
expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([]);
});
it('(different behavior from emoji-mart) do not erases custom emoji if not passed again', () => {
const custom = [
{
id: 'gabsocial',
name: 'gabsocial',
short_names: ['gabsocial'],
text: '',
emoticons: [],
keywords: ['gabsocial'],
imageUrl: 'http://example.com',
custom: true,
},
];
search('', { custom });
emojiIndex.search('', { custom });
const expected = [];
const lightExpected = [
{
id: 'gabsocial',
custom: true,
},
];
expect(search('masto').map(trimEmojis)).toEqual(lightExpected);
expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected);
});
it('(different behavior from emoji-mart) erases custom emoji if another is passed', () => {
const custom = [
{
id: 'gabsocial',
name: 'gabsocial',
short_names: ['gabsocial'],
text: '',
emoticons: [],
keywords: ['gabsocial'],
imageUrl: 'http://example.com',
custom: true,
},
];
search('', { custom });
emojiIndex.search('', { custom });
const expected = [];
expect(search('masto', { custom: [] }).map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected);
});
it('handles custom emoji', () => {
const custom = [
{
id: 'gabsocial',
name: 'gabsocial',
short_names: ['gabsocial'],
text: '',
emoticons: [],
keywords: ['gabsocial'],
imageUrl: 'http://example.com',
custom: true,
},
];
search('', { custom });
emojiIndex.search('', { custom });
const expected = [
{
id: 'gabsocial',
custom: true,
},
];
expect(search('masto', { custom }).map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('masto', { custom }).map(trimEmojis)).toEqual(expected);
});
it('should filter only emojis we care about, exclude pineapple', () => {
const emojisToShowFilter = emoji => emoji.unified !== '1F34D';
expect(search('apple', { emojisToShowFilter }).map((obj) => obj.id))
.not.toContain('pineapple');
expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id))
.not.toContain('pineapple');
});
it('does an emoji whose unified name is irregular', () => {
const expected = [
{
'id': 'water_polo',
'unified': '1f93d',
'native': '🤽',
},
{
'id': 'man-playing-water-polo',
'unified': '1f93d-200d-2642-fe0f',
'native': '🤽‍♂️',
},
{
'id': 'woman-playing-water-polo',
'unified': '1f93d-200d-2640-fe0f',
'native': '🤽‍♀️',
},
];
expect(search('polo').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('polo').map(trimEmojis)).toEqual(expected);
});
it('can search for thinking_face', () => {
const expected = [
{
id: 'thinking_face',
unified: '1f914',
native: '🤔',
},
];
expect(search('thinking_fac').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('thinking_fac').map(trimEmojis)).toEqual(expected);
});
it('can search for woman-facepalming', () => {
const expected = [
{
id: 'woman-facepalming',
unified: '1f926-200d-2640-fe0f',
native: '🤦‍♀️',
},
];
expect(search('woman-facep').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('woman-facep').map(trimEmojis)).toEqual(expected);
});
});

View File

@@ -54,7 +54,7 @@ class Image extends React.PureComponent {
<img
alt={alt}
className={classes}
{...otherProps}
{...otherProps} // : todo : remove
ref={imageRef}
src={src}
onError={this.handleOnError}

View File

@@ -1,112 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import api from '../../api'
import ModalLayout from './modal_layout'
import Divider from '../divider'
import Icon from '../icon'
import Input from '../input'
import Text from '../text'
class EmbedModal extends ImmutablePureComponent {
state = {
loading: false,
oembed: null,
}
componentDidMount() {
const { url } = this.props
this.setState({ loading: true })
api().post('/api/web/embed', { url }).then(res => {
this.setState({ loading: false, oembed: res.data })
const iframeDocument = this.iframe.contentWindow.document
iframeDocument.open()
iframeDocument.write(res.data.html)
iframeDocument.close()
iframeDocument.body.style.margin = 0
this.iframe.width = iframeDocument.body.scrollWidth
this.iframe.height = iframeDocument.body.scrollHeight
}).catch(error => {
this.props.onError(error)
})
}
setIframeRef = c => {
this.iframe = c
}
handleTextareaClick = (e) => {
e.target.select()
}
render() {
const { intl, onClose } = this.props
const { oembed } = this.state
return (
<ModalLayout
title={intl.formatMessage(messages.embed)}
onClose={onClose}
>
<div className={_s.d}>
<Text className={_s.my10}>
{intl.formatMessage(messages.instructions)}
</Text>
<div className={[_s.d, _s.mb10].join(' ')}>
<Input
readOnly
type='text'
value={oembed && oembed.html || ''}
onClick={this.handleTextareaClick}
/>
</div>
<Divider />
<Text className={_s.mb10}>
{intl.formatMessage(messages.preview)}
</Text>
<div className={[_s.d, _s.w100PC, _s.bgSubtle, _s.h220PX, _s.aiCenter, _s.jcCenter].join(' ')}>
<iframe
className={[_s.d, _s.w100PC, _s.h100PC, _s.z2].join(' ')}
frameBorder='0'
ref={this.setIframeRef}
sandbox='allow-same-origin'
title='preview'
/>
{
!oembed &&
<Icon id='loading' size='34px' className={[_s.posAbs, _s.z3].join(' ')} />
}
</div>
</div>
</ModalLayout>
)
}
}
const messages = defineMessages({
embed: { id: 'status.embed', defaultMessage: 'Embed' },
instructions: { id: 'embed.instructions', defaultMessage: 'Embed this status on your website by copying the code below.' },
preview: { id: 'embed.preview', defaultMessage: 'Here is what it will look like:' },
})
EmbedModal.propTypes = {
url: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onError: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
export default injectIntl(EmbedModal)

View File

@@ -17,7 +17,6 @@ import {
MODAL_EDIT_PROFILE,
MODAL_EDIT_SHORTCUTS,
MODAL_EMAIL_CONFIRMATION_REMINDER,
MODAL_EMBED,
MODAL_GROUP_CREATE,
MODAL_GROUP_DELETE,
MODAL_GROUP_PASSWORD,
@@ -50,7 +49,6 @@ import {
EditProfileModal,
EditShortcutsModal,
EmailConfirmationReminderModal,
EmbedModal,
GroupCreateModal,
GroupDeleteModal,
GroupMembersModal,
@@ -86,7 +84,6 @@ MODAL_COMPONENTS[MODAL_DISPLAY_OPTIONS] = DisplayOptionsModal
MODAL_COMPONENTS[MODAL_EDIT_SHORTCUTS] = EditShortcutsModal
MODAL_COMPONENTS[MODAL_EDIT_PROFILE] = EditProfileModal
MODAL_COMPONENTS[MODAL_EMAIL_CONFIRMATION_REMINDER] = EmailConfirmationReminderModal
MODAL_COMPONENTS[MODAL_EMBED] = EmbedModal
MODAL_COMPONENTS[MODAL_GROUP_CREATE] = GroupCreateModal
MODAL_COMPONENTS[MODAL_GROUP_DELETE] = GroupDeleteModal
MODAL_COMPONENTS[MODAL_GROUP_PASSWORD] = GroupPasswordModal

View File

@@ -12,19 +12,38 @@ import MediaGalleryPanelPlaceholder from '../placeholder/media_gallery_panel_pla
class MediaGalleryPanel extends ImmutablePureComponent {
state = {
fetched: false,
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.shouldLoad && !prevState.fetched) {
return { fetched: true }
}
return null
}
componentDidUpdate(prevProps, prevState) {
if (!prevState.fetched && this.state.fetched && this.props.isLazy) {
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { limit: 8 }))
}
}
componentDidMount() {
const { accountId } = this.props
const { accountId, isLazy } = this.props
if (accountId && accountId !== -1) {
if (!isLazy && !!accountId && accountId !== -1) {
this.props.dispatch(expandAccountMediaTimeline(accountId, { limit: 8 }))
this.setState({ fetched: true })
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, { limit: 8 }))
}
}
// componentWillReceiveProps(nextProps) {
// if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
// this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, { limit: 8 }))
// }
// }
render() {
const {
@@ -33,8 +52,9 @@ class MediaGalleryPanel extends ImmutablePureComponent {
intl,
isLoading,
} = this.props
const { fetched } = this.state
if (!attachments) return null
if (!attachments && fetched) return null
return (
<PanelLayout

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { me, favouritesCount } from '../../initial_state'
import { me } from '../../initial_state'
import { shortNumberFormat } from '../../utils/numbers'
import PanelLayout from './panel_layout'
import UserStat from '../user_stat'
@@ -56,7 +56,7 @@ class ProfileStatsPanel extends ImmutablePureComponent {
account.get('id') === me &&
<UserStat
title={intl.formatMessage(messages.likes)}
value={shortNumberFormat(favouritesCount)}
value={shortNumberFormat(0)}
to={`/${account.get('acct')}/likes`}
isCentered={noPanel}
/>

View File

@@ -14,8 +14,6 @@ import {
unbookmark,
} from '../../actions/interactions';
import {
muteStatus,
unmuteStatus,
deleteStatus,
editStatus,
} from '../../actions/statuses';
@@ -27,7 +25,6 @@ import {
pinGroupStatus,
unpinGroupStatus,
} from '../../actions/groups'
import { initMuteModal } from '../../actions/mutes'
import { initReport } from '../../actions/reports'
import { openModal } from '../../actions/modal'
import {
@@ -35,7 +32,6 @@ import {
openPopover,
} from '../../actions/popover'
import {
MODAL_EMBED,
MODAL_PRO_UPGRADE,
POPOVER_STATUS_SHARE,
} from '../../constants'
@@ -57,13 +53,12 @@ class StatusOptionsPopover extends ImmutablePureComponent {
componentDidMount() {
if (!this.props.groupRelationships && this.props.groupId) {
this.props.onFetchGroupRelationships(this.props.groupId)
// : todo :
// check if pin
// check if bookmark
}
}
handleConversationMuteClick = () => {
this.props.onMuteConversation(this.props.status)
}
handleGroupRemoveAccount = () => {
const { status } = this.props
@@ -144,15 +139,6 @@ class StatusOptionsPopover extends ImmutablePureComponent {
let menu = []
if (me) {
if (status.getIn(['account', 'id']) === me) {
menu.push({
icon: 'audio-mute',
hideArrow: true,
title: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation),
onClick: this.handleConversationMuteClick,
})
}
if (isReply) {
menu.push({
icon: 'repost',
@@ -296,8 +282,6 @@ const messages = defineMessages({
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
like: { id: 'status.like', defaultMessage: 'Like' },
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
groupPin: { id: 'status.group_pin', defaultMessage: 'Pin in group' },
@@ -327,16 +311,6 @@ const mapStateToProps = (state, { status }) => {
const mapDispatchToProps = (dispatch) => ({
onMuteConversation(status) {
dispatch(closePopover())
if (status.get('muted')) {
dispatch(unmuteStatus(status.get('id')))
} else {
dispatch(muteStatus(status.get('id')))
}
},
onPin(status) {
dispatch(closePopover())
@@ -411,11 +385,6 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(editStatus(status))
},
onMute(account) {
dispatch(closePopover())
dispatch(initMuteModal(account))
},
onBlock(status) {
dispatch(closePopover())
const account = status.get('account')
@@ -480,11 +449,9 @@ StatusOptionsPopover.propTypes = {
onMute: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
onReport: PropTypes.func.isRequired,
onMuteConversation: PropTypes.func.isRequired,
onPin: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
onFetchGroupRelationships: PropTypes.func.isRequired,
onOpenEmbedModal: PropTypes.func.isRequired,
onOpenProUpgradeModal: PropTypes.func.isRequired,
onClosePopover: PropTypes.func.isRequired,
isXS: PropTypes.bool,

View File

@@ -82,6 +82,7 @@ class DefaultSidebar extends ImmutablePureComponent {
</SidebarSectionTitle>
<SidebarSectionItem title='Home' icon='home' to='/home' count={homeItemsQueueCount} />
<SidebarSectionItem title='Notifications' icon='notifications' to='/notifications' count={notificationCount} />
<SidebarSectionItem title='Chats' icon='chat' to='/messages' />
<SidebarSectionItem title='Groups' icon='group' to='/groups' />
<SidebarSectionItem title='Lists' icon='list' to='/lists' />
<SidebarSectionItem title='Explore' icon='explore' to='/explore' />

View File

@@ -52,7 +52,7 @@ class TabBarItem extends React.PureComponent {
// Combine state, props, location to make absolutely
// sure of active status.
const active = isActive || (to === location.pathname && !location.search) || isCurrent
const active = (isActive === true || isCurrent || (to === location.pathname && !location.search))
const containerClasses = CX({
d: 1,

View File

@@ -0,0 +1,125 @@
import React from 'react'
import PropTypes from 'prop-types'
import {
CX,
TOAST_TYPE_ERROR,
TOAST_TYPE_SUCCESS,
// TOAST_TYPE_NOTIFICATION,
} from '../constants'
import Button from './button'
import Image from './image'
import RelativeTimestamp from './relative_timestamp'
import Text from './text'
class Toast extends React.PureComponent {
componentDidMount() {
this._timer = setTimeout(() => {
this.handleOnDismiss()
}, 5000)
}
componentWillUnmount() {
clearTimeout(this._timer)
}
handleOnDismiss = () => {
this.props.onDismiss(this.props.id)
}
render() {
const {
image,
isImageAccount,
title,
message,
date,
to,
type,
} = this.props
const contentClasses = CX({
default: 1,
mt5: 1,
pt2: 1,
maxWidth240PX: 1,
flexRow: !!image,
})
const innerContentClasses = CX({
default: 1,
flexNormal: 1,
pl10: !!image,
pt2: !!image,
displayInline: !!date && !image,
})
const imageClasses = CX({
radiusSmall: !isImageAccount,
circle: isImageAccount,
})
const dateClasses = CX({
mr5: 1,
mt2: !!image,
})
return (
<div className={[_s.default, _s.radiusSmall, _s.mb5, _s.px15, _s.pt10, _s.pb15, _s.bgPrimary, _s.boxShadowToast].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
<Text size='media' weight='medium' className={[_s.mr15, _s.minWidth160PX].join(' ')}>
{title}
</Text>
<Button
backgroundColor='secondary'
color='primary'
icon='close'
iconSize='6px'
onClick={this.handleOnDismiss}
className={[_s.mlAuto, _s.px10].join(' ')}
/>
</div>
<div className={contentClasses}>
{
!!image &&
<Image
src={image}
className={imageClasses}
height='52px'
width='52px'
/>
}
<div className={innerContentClasses}>
<Text size='small'>
{message}
</Text>
{
date &&
<Text color='secondary' size='extraSmall' className={dateClasses}>
<RelativeTimestamp timestamp={date} />
</Text>
}
</div>
</div>
</div>
)
}
}
Toast.propTypes = {
date: PropTypes.string.isRequired,
image: PropTypes.string,
isImageAccount: PropTypes.bool,
id: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
onDismiss: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
to: PropTypes.string,
type: PropTypes.oneOf([
TOAST_TYPE_ERROR,
TOAST_TYPE_SUCCESS,
]).isRequired,
}
export default Toast