Add emoji reaction bar into status view
This commit is contained in:
parent
092f9916b0
commit
df6de7daf5
5 changed files with 129 additions and 0 deletions
|
@ -66,6 +66,10 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||||
normalStatus.filtered = status.filtered.map(normalizeFilterResult);
|
normalStatus.filtered = status.filtered.map(normalizeFilterResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.emoji_reactions) {
|
||||||
|
normalStatus.emojiReactions = status.emoji_reactions;
|
||||||
|
}
|
||||||
|
|
||||||
// Only calculate these values when status first encountered and
|
// Only calculate these values when status first encountered and
|
||||||
// when the underlying values change. Otherwise keep the ones
|
// when the underlying values change. Otherwise keep the ones
|
||||||
// already in the reducer
|
// already in the reducer
|
||||||
|
|
|
@ -7,6 +7,7 @@ import RelativeTimestamp from './relative_timestamp';
|
||||||
import DisplayName from './display_name';
|
import DisplayName from './display_name';
|
||||||
import StatusContent from './status_content';
|
import StatusContent from './status_content';
|
||||||
import StatusActionBar from './status_action_bar';
|
import StatusActionBar from './status_action_bar';
|
||||||
|
import StatusEmojiReactionsBar from './status_emoji_reactions_bar';
|
||||||
import AttachmentList from './attachment_list';
|
import AttachmentList from './attachment_list';
|
||||||
import Card from '../features/status/components/card';
|
import Card from '../features/status/components/card';
|
||||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
@ -505,6 +506,12 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||||
|
|
||||||
|
let emojiReactionsBar = null;
|
||||||
|
if (status.get('emoji_reactions')) {
|
||||||
|
const emojiReactions = status.get('emoji_reactions');
|
||||||
|
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} statusId={status.get('id')} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
||||||
|
@ -538,6 +545,8 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
{media}
|
{media}
|
||||||
|
|
||||||
|
{emojiReactionsBar}
|
||||||
|
|
||||||
<StatusActionBar scrollKey={scrollKey} status={status} account={account} onFilter={matchedFilters ? this.handleFilterClick : null} {...other} />
|
<StatusActionBar scrollKey={scrollKey} status={status} account={account} onFilter={matchedFilters ? this.handleFilterClick : null} {...other} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||||
|
import emojify from '../features/emoji/emoji';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
class EmojiReactionButton extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
name: ImmutablePropTypes.map,
|
||||||
|
url: PropTypes.string,
|
||||||
|
staticUrl: PropTypes.string,
|
||||||
|
count: PropTypes.number.isRequired,
|
||||||
|
me: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { name, url, staticUrl, count, me } = this.props;
|
||||||
|
|
||||||
|
let emojiHtml = null;
|
||||||
|
if (url) {
|
||||||
|
let customEmojis = {};
|
||||||
|
customEmojis[name] = { url, static_url: staticUrl };
|
||||||
|
emojiHtml = emojify(`:${name}:`, customEmojis);
|
||||||
|
} else {
|
||||||
|
emojiHtml = emojify(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const classList = {
|
||||||
|
'emoji-reactions-bar__button': true,
|
||||||
|
'toggled': me,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={classNames(classList)} type='button'>
|
||||||
|
<span className='emoji' dangerouslySetInnerHTML={{ __html: emojiHtml }} />
|
||||||
|
<span className='count'>{count}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default @injectIntl
|
||||||
|
class StatusEmojiReactionsBar extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
emojiReactions: ImmutablePropTypes.map.isRequired,
|
||||||
|
statusId: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { emojiReactions, statusId } = this.props;
|
||||||
|
|
||||||
|
const emojiButtons = React.Children.map(emojiReactions, (emoji) => (
|
||||||
|
<EmojiReactionButton
|
||||||
|
key={emoji.get('id')}
|
||||||
|
name={emoji.get('name')}
|
||||||
|
count={emoji.get('count')}
|
||||||
|
me={emoji.get('me')}
|
||||||
|
url={emoji.get('url')}
|
||||||
|
staticUrl={emoji.get('static_url')}
|
||||||
|
/>));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='status__emoji-reactions-bar'>
|
||||||
|
{emojiButtons}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import Avatar from '../../../components/avatar';
|
import Avatar from '../../../components/avatar';
|
||||||
import DisplayName from '../../../components/display_name';
|
import DisplayName from '../../../components/display_name';
|
||||||
import StatusContent from '../../../components/status_content';
|
import StatusContent from '../../../components/status_content';
|
||||||
|
import StatusEmojiReactionsBar from '../../../components/status_emoji_reactions_bar';
|
||||||
import MediaGallery from '../../../components/media_gallery';
|
import MediaGallery from '../../../components/media_gallery';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { injectIntl, defineMessages, FormattedDate } from 'react-intl';
|
import { injectIntl, defineMessages, FormattedDate } from 'react-intl';
|
||||||
|
@ -187,6 +188,12 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
media = <Card sensitive={status.get('sensitive')} onOpenMedia={this.props.onOpenMedia} card={status.get('card', null)} />;
|
media = <Card sensitive={status.get('sensitive')} onOpenMedia={this.props.onOpenMedia} card={status.get('card', null)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let emojiReactionsBar = null;
|
||||||
|
if (status.get('emoji_reactions')) {
|
||||||
|
const emojiReactions = status.get('emoji_reactions');
|
||||||
|
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} statusId={status.get('id')} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (status.get('application')) {
|
if (status.get('application')) {
|
||||||
applicationLink = <React.Fragment> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></React.Fragment>;
|
applicationLink = <React.Fragment> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></React.Fragment>;
|
||||||
}
|
}
|
||||||
|
@ -275,6 +282,8 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
|
|
||||||
{media}
|
{media}
|
||||||
|
|
||||||
|
{emojiReactionsBar}
|
||||||
|
|
||||||
<div className='detailed-status__meta'>
|
<div className='detailed-status__meta'>
|
||||||
<a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>
|
<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' />
|
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||||
|
|
|
@ -1262,6 +1262,39 @@ body > [data-popper-placement] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status__emoji-reactions-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 8px 0 2px 4px;
|
||||||
|
|
||||||
|
.emoji-reactions-bar__button {
|
||||||
|
background: lighten($ui-base-color, 16%);
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
&.toggled {
|
||||||
|
background: darken($ui-primary-color, 16%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
display: block;
|
||||||
|
height: 16px;
|
||||||
|
img {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.count {
|
||||||
|
display: block;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status__action-bar {
|
.status__action-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue