Merge branch 'heart-react' into 'develop'
Fix heart emoji react for Pleroma develop branch; let emojis be customizable the admin Closes #538 See merge request soapbox-pub/soapbox-fe!398
This commit is contained in:
commit
bdbc6eb79c
13 changed files with 55 additions and 20 deletions
|
@ -18,10 +18,18 @@ export const defaultConfig = ImmutableMap({
|
||||||
navlinks: ImmutableMap({
|
navlinks: ImmutableMap({
|
||||||
homeFooter: ImmutableList(),
|
homeFooter: ImmutableList(),
|
||||||
}),
|
}),
|
||||||
|
allowedEmoji: ImmutableList([
|
||||||
|
'👍',
|
||||||
|
'❤️',
|
||||||
|
'😆',
|
||||||
|
'😮',
|
||||||
|
'😢',
|
||||||
|
'😩',
|
||||||
|
]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function getSoapboxConfig(state) {
|
export function getSoapboxConfig(state) {
|
||||||
return defaultConfig.mergeDeep(state.get('soapbox'));
|
return defaultConfig.merge(state.get('soapbox'));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchSoapboxConfig() {
|
export function fetchSoapboxConfig() {
|
||||||
|
|
|
@ -16,7 +16,7 @@ exports[`<EmojiSelector /> renders correctly 1`] = `
|
||||||
className="emoji-react-selector__emoji"
|
className="emoji-react-selector__emoji"
|
||||||
dangerouslySetInnerHTML={
|
dangerouslySetInnerHTML={
|
||||||
Object {
|
Object {
|
||||||
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"❤\\" title=\\":heart:\\" src=\\"/emoji/2764.svg\\" />",
|
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"❤️\\" title=\\":heart:\\" src=\\"/emoji/2764.svg\\" />",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import { createComponent } from 'soapbox/test_helpers';
|
||||||
import EmojiSelector from '../emoji_selector';
|
import EmojiSelector from '../emoji_selector';
|
||||||
|
|
||||||
describe('<EmojiSelector />', () => {
|
describe('<EmojiSelector />', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const component = renderer.create(<EmojiSelector />);
|
const component = createComponent(<EmojiSelector />);
|
||||||
const tree = component.toJSON();
|
const tree = component.toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ALLOWED_EMOJI } from 'soapbox/utils/emoji_reacts';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import emojify from 'soapbox/features/emoji/emoji';
|
import emojify from 'soapbox/features/emoji/emoji';
|
||||||
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class EmojiSelector extends React.Component {
|
const mapStateToProps = state => ({
|
||||||
|
allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
class EmojiSelector extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onReact: PropTypes.func.isRequired,
|
onReact: PropTypes.func.isRequired,
|
||||||
|
@ -17,11 +24,11 @@ export default class EmojiSelector extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { onReact, visible } = this.props;
|
const { onReact, visible, allowedEmoji } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('emoji-react-selector', { 'emoji-react-selector--visible': visible })}>
|
<div className={classNames('emoji-react-selector', { 'emoji-react-selector--visible': visible })}>
|
||||||
{ALLOWED_EMOJI.map((emoji, i) => (
|
{allowedEmoji.map((emoji, i) => (
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
className='emoji-react-selector__emoji'
|
className='emoji-react-selector__emoji'
|
||||||
|
|
|
@ -93,6 +93,7 @@ class Status extends ImmutablePureComponent {
|
||||||
cachedMediaWidth: PropTypes.number,
|
cachedMediaWidth: PropTypes.number,
|
||||||
group: ImmutablePropTypes.map,
|
group: ImmutablePropTypes.map,
|
||||||
displayMedia: PropTypes.string,
|
displayMedia: PropTypes.string,
|
||||||
|
allowedEmoji: ImmutablePropTypes.list,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Avoid checking props that are functions (and whose equality will always
|
// Avoid checking props that are functions (and whose equality will always
|
||||||
|
|
|
@ -74,6 +74,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
me: SoapboxPropTypes.me,
|
me: SoapboxPropTypes.me,
|
||||||
isStaff: PropTypes.bool.isRequired,
|
isStaff: PropTypes.bool.isRequired,
|
||||||
|
allowedEmoji: ImmutablePropTypes.list,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -120,7 +121,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLikeButtonClick = e => {
|
handleLikeButtonClick = e => {
|
||||||
const meEmojiReact = getReactForStatus(this.props.status) || '👍';
|
const meEmojiReact = getReactForStatus(this.props.status, this.props.allowedEmoji) || '👍';
|
||||||
if (this.isMobile()) {
|
if (this.isMobile()) {
|
||||||
if (this.state.emojiSelectorVisible) {
|
if (this.state.emojiSelectorVisible) {
|
||||||
this.handleReactClick(meEmojiReact)();
|
this.handleReactClick(meEmojiReact)();
|
||||||
|
@ -314,7 +315,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { status, intl } = this.props;
|
const { status, intl, allowedEmoji } = this.props;
|
||||||
const { emojiSelectorVisible } = this.state;
|
const { emojiSelectorVisible } = this.state;
|
||||||
|
|
||||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||||
|
@ -326,8 +327,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
status.getIn(['pleroma', 'emoji_reactions'], ImmutableList()),
|
status.getIn(['pleroma', 'emoji_reactions'], ImmutableList()),
|
||||||
favouriteCount,
|
favouriteCount,
|
||||||
status.get('favourited'),
|
status.get('favourited'),
|
||||||
|
allowedEmoji,
|
||||||
).reduce((acc, cur) => acc + cur.get('count'), 0);
|
).reduce((acc, cur) => acc + cur.get('count'), 0);
|
||||||
const meEmojiReact = getReactForStatus(status);
|
const meEmojiReact = getReactForStatus(status, allowedEmoji);
|
||||||
|
|
||||||
let menu = this._makeMenu(publicStatus);
|
let menu = this._makeMenu(publicStatus);
|
||||||
let reblogIcon = 'retweet';
|
let reblogIcon = 'retweet';
|
||||||
|
|
|
@ -35,6 +35,7 @@ import {
|
||||||
groupRemoveStatus,
|
groupRemoveStatus,
|
||||||
} from '../actions/groups';
|
} from '../actions/groups';
|
||||||
import { getSettings } from '../actions/settings';
|
import { getSettings } from '../actions/settings';
|
||||||
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||||
|
@ -53,6 +54,7 @@ const makeMapStateToProps = () => {
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
status: getStatus(state, props),
|
status: getStatus(state, props),
|
||||||
displayMedia: getSettings(state).get('displayMedia'),
|
displayMedia: getSettings(state).get('displayMedia'),
|
||||||
|
allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'),
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
|
|
|
@ -78,6 +78,7 @@ class ActionBar extends React.PureComponent {
|
||||||
onOpenUnauthorizedModal: PropTypes.func.isRequired,
|
onOpenUnauthorizedModal: PropTypes.func.isRequired,
|
||||||
me: SoapboxPropTypes.me,
|
me: SoapboxPropTypes.me,
|
||||||
isStaff: PropTypes.bool.isRequired,
|
isStaff: PropTypes.bool.isRequired,
|
||||||
|
allowedEmoji: ImmutablePropTypes.list,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -130,7 +131,7 @@ class ActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLikeButtonClick = e => {
|
handleLikeButtonClick = e => {
|
||||||
const meEmojiReact = getReactForStatus(this.props.status) || '👍';
|
const meEmojiReact = getReactForStatus(this.props.status, this.props.allowedEmoji) || '👍';
|
||||||
if (this.isMobile()) {
|
if (this.isMobile()) {
|
||||||
if (this.state.emojiSelectorVisible) {
|
if (this.state.emojiSelectorVisible) {
|
||||||
this.handleReactClick(meEmojiReact)();
|
this.handleReactClick(meEmojiReact)();
|
||||||
|
@ -232,12 +233,12 @@ class ActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { status, intl, me, isStaff } = this.props;
|
const { status, intl, me, isStaff, allowedEmoji } = this.props;
|
||||||
const { emojiSelectorVisible } = this.state;
|
const { emojiSelectorVisible } = this.state;
|
||||||
|
|
||||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||||
const mutingConversation = status.get('muted');
|
const mutingConversation = status.get('muted');
|
||||||
const meEmojiReact = getReactForStatus(status);
|
const meEmojiReact = getReactForStatus(status, allowedEmoji);
|
||||||
|
|
||||||
let menu = [];
|
let menu = [];
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import scheduleIdleTask from '../../ui/util/schedule_idle_task';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
import PollContainer from 'soapbox/containers/poll_container';
|
import PollContainer from 'soapbox/containers/poll_container';
|
||||||
import { StatusInteractionBar } from './status_interaction_bar';
|
import StatusInteractionBar from './status_interaction_bar';
|
||||||
import { getDomain } from 'soapbox/utils/accounts';
|
import { getDomain } from 'soapbox/utils/accounts';
|
||||||
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
|
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { FormattedNumber } from 'react-intl';
|
import { FormattedNumber } from 'react-intl';
|
||||||
import emojify from 'soapbox/features/emoji/emoji';
|
import emojify from 'soapbox/features/emoji/emoji';
|
||||||
import { reduceEmoji } from 'soapbox/utils/emoji_reacts';
|
import { reduceEmoji } from 'soapbox/utils/emoji_reacts';
|
||||||
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
|
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
|
|
||||||
export class StatusInteractionBar extends React.Component {
|
const mapStateToProps = state => ({
|
||||||
|
allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
class StatusInteractionBar extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
me: SoapboxPropTypes.me,
|
me: SoapboxPropTypes.me,
|
||||||
|
allowedEmoji: ImmutablePropTypes.list,
|
||||||
}
|
}
|
||||||
|
|
||||||
getNormalizedReacts = () => {
|
getNormalizedReacts = () => {
|
||||||
|
@ -20,6 +29,7 @@ export class StatusInteractionBar extends React.Component {
|
||||||
status.getIn(['pleroma', 'emoji_reactions']),
|
status.getIn(['pleroma', 'emoji_reactions']),
|
||||||
status.get('favourites_count'),
|
status.get('favourites_count'),
|
||||||
status.get('favourited'),
|
status.get('favourited'),
|
||||||
|
this.props.allowedEmoji,
|
||||||
).reverse();
|
).reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from
|
||||||
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||||
|
@ -111,6 +112,7 @@ const makeMapStateToProps = () => {
|
||||||
domain: state.getIn(['meta', 'domain']),
|
domain: state.getIn(['meta', 'domain']),
|
||||||
me: state.get('me'),
|
me: state.get('me'),
|
||||||
displayMedia: getSettings(state).get('displayMedia'),
|
displayMedia: getSettings(state).get('displayMedia'),
|
||||||
|
allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -521,6 +523,7 @@ class Status extends ImmutablePureComponent {
|
||||||
onPin={this.handlePin}
|
onPin={this.handlePin}
|
||||||
onBookmark={this.handleBookmark}
|
onBookmark={this.handleBookmark}
|
||||||
onEmbed={this.handleEmbed}
|
onEmbed={this.handleEmbed}
|
||||||
|
allowedEmoji={this.props.allowedEmoji}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
|
|
|
@ -158,7 +158,7 @@ describe('getReactForStatus', () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(getReactForStatus(status)).toEqual('❤');
|
expect(getReactForStatus(status, ALLOWED_EMOJI)).toEqual('❤');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a thumbs-up for a favourite', () => {
|
it('returns a thumbs-up for a favourite', () => {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
// I've customized them.
|
// I've customized them.
|
||||||
export const ALLOWED_EMOJI = [
|
export const ALLOWED_EMOJI = [
|
||||||
'👍',
|
'👍',
|
||||||
'❤',
|
'❤️',
|
||||||
'😆',
|
'😆',
|
||||||
'😮',
|
'😮',
|
||||||
'😢',
|
'😢',
|
||||||
|
@ -75,11 +75,12 @@ export const reduceEmoji = (emojiReacts, favouritesCount, favourited, allowedEmo
|
||||||
emojiReacts, favouritesCount, favourited,
|
emojiReacts, favouritesCount, favourited,
|
||||||
))), allowedEmoji));
|
))), allowedEmoji));
|
||||||
|
|
||||||
export const getReactForStatus = status => {
|
export const getReactForStatus = (status, allowedEmoji=ALLOWED_EMOJI) => {
|
||||||
return reduceEmoji(
|
return reduceEmoji(
|
||||||
status.getIn(['pleroma', 'emoji_reactions'], ImmutableList()),
|
status.getIn(['pleroma', 'emoji_reactions'], ImmutableList()),
|
||||||
status.get('favourites_count'),
|
status.get('favourites_count', 0),
|
||||||
status.get('favourited'),
|
status.get('favourited'),
|
||||||
|
allowedEmoji,
|
||||||
).filter(e => e.get('me') === true)
|
).filter(e => e.get('me') === true)
|
||||||
.getIn([0, 'name']);
|
.getIn([0, 'name']);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue