Merge branch 'mastodont' into 'develop'
Conditionally display Chats and EmojiReacts depending on BE capabilties See merge request soapbox-pub/soapbox-fe!677
This commit is contained in:
commit
84c7b9df06
|
@ -2,6 +2,7 @@ import api from '../api';
|
||||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||||
import { favourite, unfavourite } from './interactions';
|
import { favourite, unfavourite } from './interactions';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
import { List as ImmutableList } from 'immutable';
|
||||||
|
|
||||||
export const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST';
|
export const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST';
|
||||||
export const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS';
|
export const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS';
|
||||||
|
@ -19,7 +20,7 @@ const noOp = () => () => new Promise(f => f());
|
||||||
|
|
||||||
export const simpleEmojiReact = (status, emoji) => {
|
export const simpleEmojiReact = (status, emoji) => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const emojiReacts = status.getIn(['pleroma', 'emoji_reactions']);
|
const emojiReacts = status.getIn(['pleroma', 'emoji_reactions'], ImmutableList());
|
||||||
|
|
||||||
if (emoji === '👍' && status.get('favourited')) return dispatch(unfavourite(status));
|
if (emoji === '👍' && status.get('favourited')) return dispatch(unfavourite(status));
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import EmojiSelector from 'soapbox/components/emoji_selector';
|
||||||
import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji_reacts';
|
import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji_reacts';
|
||||||
import { simpleEmojiReact } from 'soapbox/actions/emoji_reacts';
|
import { simpleEmojiReact } from 'soapbox/actions/emoji_reacts';
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
|
@ -93,6 +94,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
allowedEmoji: ImmutablePropTypes.list,
|
allowedEmoji: ImmutablePropTypes.list,
|
||||||
emojiSelectorFocused: PropTypes.bool,
|
emojiSelectorFocused: PropTypes.bool,
|
||||||
handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
|
handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
|
||||||
|
features: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -132,16 +134,26 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
isMobile = () => window.matchMedia('only screen and (max-width: 895px)').matches;
|
isMobile = () => window.matchMedia('only screen and (max-width: 895px)').matches;
|
||||||
|
|
||||||
handleLikeButtonHover = e => {
|
handleLikeButtonHover = e => {
|
||||||
if (!this.isMobile()) this.setState({ emojiSelectorVisible: true });
|
const { features } = this.props;
|
||||||
|
|
||||||
|
if (features.emojiReacts && !this.isMobile()) {
|
||||||
|
this.setState({ emojiSelectorVisible: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLikeButtonLeave = e => {
|
handleLikeButtonLeave = e => {
|
||||||
if (!this.isMobile()) this.setState({ emojiSelectorVisible: false });
|
const { features } = this.props;
|
||||||
|
|
||||||
|
if (features.emojiReacts && !this.isMobile()) {
|
||||||
|
this.setState({ emojiSelectorVisible: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLikeButtonClick = e => {
|
handleLikeButtonClick = e => {
|
||||||
|
const { features } = this.props;
|
||||||
const meEmojiReact = getReactForStatus(this.props.status, this.props.allowedEmoji) || '👍';
|
const meEmojiReact = getReactForStatus(this.props.status, this.props.allowedEmoji) || '👍';
|
||||||
if (this.isMobile()) {
|
|
||||||
|
if (features.emojiReacts && this.isMobile()) {
|
||||||
if (this.state.emojiSelectorVisible) {
|
if (this.state.emojiSelectorVisible) {
|
||||||
this.handleReactClick(meEmojiReact)();
|
this.handleReactClick(meEmojiReact)();
|
||||||
} else {
|
} else {
|
||||||
|
@ -362,7 +374,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { status, intl, allowedEmoji, emojiSelectorFocused, handleEmojiSelectorUnfocus } = this.props;
|
const { status, intl, allowedEmoji, emojiSelectorFocused, handleEmojiSelectorUnfocus, features } = 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'));
|
||||||
|
@ -427,7 +439,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
>
|
>
|
||||||
<EmojiSelector
|
<EmojiSelector
|
||||||
onReact={this.handleReactClick}
|
onReact={this.handleReactClick}
|
||||||
visible={emojiSelectorVisible}
|
visible={features.emojiReacts && emojiSelectorVisible}
|
||||||
focused={emojiSelectorFocused}
|
focused={emojiSelectorFocused}
|
||||||
onUnfocus={handleEmojiSelectorUnfocus}
|
onUnfocus={handleEmojiSelectorUnfocus}
|
||||||
/>
|
/>
|
||||||
|
@ -456,11 +468,13 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
const account = state.getIn(['accounts', me]);
|
const account = state.getIn(['accounts', me]);
|
||||||
|
const instance = state.get('instance');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
me,
|
me,
|
||||||
isStaff: account ? isStaff(account) : false,
|
isStaff: account ? isStaff(account) : false,
|
||||||
isAdmin: account ? isAdmin(account) : false,
|
isAdmin: account ? isAdmin(account) : false,
|
||||||
|
features: getFeatures(instance),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { isStaff, isAdmin } from 'soapbox/utils/accounts';
|
||||||
import { isUserTouching } from 'soapbox/is_mobile';
|
import { isUserTouching } from 'soapbox/is_mobile';
|
||||||
import EmojiSelector from 'soapbox/components/emoji_selector';
|
import EmojiSelector from 'soapbox/components/emoji_selector';
|
||||||
import { getReactForStatus } from 'soapbox/utils/emoji_reacts';
|
import { getReactForStatus } from 'soapbox/utils/emoji_reacts';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
|
@ -54,11 +55,13 @@ const messages = defineMessages({
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
const account = state.getIn(['accounts', me]);
|
const account = state.getIn(['accounts', me]);
|
||||||
|
const instance = state.get('instance');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
me,
|
me,
|
||||||
isStaff: account ? isStaff(account) : false,
|
isStaff: account ? isStaff(account) : false,
|
||||||
isAdmin: account ? isAdmin(account) : false,
|
isAdmin: account ? isAdmin(account) : false,
|
||||||
|
features: getFeatures(instance),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,6 +106,7 @@ class ActionBar extends React.PureComponent {
|
||||||
emojiSelectorFocused: PropTypes.bool,
|
emojiSelectorFocused: PropTypes.bool,
|
||||||
handleEmojiSelectorExpand: PropTypes.func.isRequired,
|
handleEmojiSelectorExpand: PropTypes.func.isRequired,
|
||||||
handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
|
handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
|
||||||
|
features: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -146,16 +150,26 @@ class ActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLikeButtonHover = e => {
|
handleLikeButtonHover = e => {
|
||||||
if (!isUserTouching()) this.setState({ emojiSelectorVisible: true });
|
const { features } = this.props;
|
||||||
|
|
||||||
|
if (features.emojiReacts && !isUserTouching()) {
|
||||||
|
this.setState({ emojiSelectorVisible: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLikeButtonLeave = e => {
|
handleLikeButtonLeave = e => {
|
||||||
if (!isUserTouching()) this.setState({ emojiSelectorVisible: false });
|
const { features } = this.props;
|
||||||
|
|
||||||
|
if (features.emojiReacts && !isUserTouching()) {
|
||||||
|
this.setState({ emojiSelectorVisible: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLikeButtonClick = e => {
|
handleLikeButtonClick = e => {
|
||||||
|
const { features } = this.props;
|
||||||
const meEmojiReact = getReactForStatus(this.props.status, this.props.allowedEmoji) || '👍';
|
const meEmojiReact = getReactForStatus(this.props.status, this.props.allowedEmoji) || '👍';
|
||||||
if (isUserTouching()) {
|
|
||||||
|
if (features.emojiReacts && isUserTouching()) {
|
||||||
if (this.state.emojiSelectorVisible) {
|
if (this.state.emojiSelectorVisible) {
|
||||||
this.handleReactClick(meEmojiReact)();
|
this.handleReactClick(meEmojiReact)();
|
||||||
} else {
|
} else {
|
||||||
|
@ -278,7 +292,7 @@ class ActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { status, intl, me, isStaff, isAdmin, allowedEmoji, emojiSelectorFocused, handleEmojiSelectorExpand, handleEmojiSelectorUnfocus } = this.props;
|
const { status, intl, me, isStaff, isAdmin, allowedEmoji, emojiSelectorFocused, handleEmojiSelectorExpand, handleEmojiSelectorUnfocus, features } = this.props;
|
||||||
const { emojiSelectorVisible } = this.state;
|
const { emojiSelectorVisible } = this.state;
|
||||||
const ownAccount = status.getIn(['account', 'id']) === me;
|
const ownAccount = status.getIn(['account', 'id']) === me;
|
||||||
|
|
||||||
|
@ -391,7 +405,7 @@ class ActionBar extends React.PureComponent {
|
||||||
>
|
>
|
||||||
<EmojiSelector
|
<EmojiSelector
|
||||||
onReact={this.handleReactClick}
|
onReact={this.handleReactClick}
|
||||||
visible={emojiSelectorVisible}
|
visible={features.emojiReacts && emojiSelectorVisible}
|
||||||
focused={emojiSelectorFocused}
|
focused={emojiSelectorFocused}
|
||||||
onUnfocus={handleEmojiSelectorUnfocus}
|
onUnfocus={handleEmojiSelectorUnfocus}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -15,6 +15,7 @@ import Icon from '../../../components/icon';
|
||||||
import ThemeToggle from '../../ui/components/theme_toggle_container';
|
import ThemeToggle from '../../ui/components/theme_toggle_container';
|
||||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
import { isStaff } from 'soapbox/utils/accounts';
|
import { isStaff } from 'soapbox/utils/accounts';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
post: { id: 'tabs_bar.post', defaultMessage: 'Post' },
|
post: { id: 'tabs_bar.post', defaultMessage: 'Post' },
|
||||||
|
@ -32,6 +33,7 @@ class TabsBar extends React.PureComponent {
|
||||||
dashboardCount: PropTypes.number,
|
dashboardCount: PropTypes.number,
|
||||||
notificationCount: PropTypes.number,
|
notificationCount: PropTypes.number,
|
||||||
chatsCount: PropTypes.number,
|
chatsCount: PropTypes.number,
|
||||||
|
features: PropTypes.object.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -52,7 +54,7 @@ class TabsBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
getNavLinks() {
|
getNavLinks() {
|
||||||
const { intl: { formatMessage }, logo, account, dashboardCount, notificationCount, chatsCount } = this.props;
|
const { intl: { formatMessage }, logo, account, dashboardCount, notificationCount, chatsCount, features } = this.props;
|
||||||
const links = [];
|
const links = [];
|
||||||
if (logo) {
|
if (logo) {
|
||||||
links.push(
|
links.push(
|
||||||
|
@ -73,7 +75,7 @@ class TabsBar extends React.PureComponent {
|
||||||
<span><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></span>
|
<span><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></span>
|
||||||
</NavLink>);
|
</NavLink>);
|
||||||
}
|
}
|
||||||
if (account) {
|
if (features.chats && account) {
|
||||||
links.push(
|
links.push(
|
||||||
<NavLink key='chats' className='tabs-bar__link tabs-bar__link--chats' to='/chats' data-preview-title-id='column.chats'>
|
<NavLink key='chats' className='tabs-bar__link tabs-bar__link--chats' to='/chats' data-preview-title-id='column.chats'>
|
||||||
<IconWithCounter icon='comment' count={chatsCount} />
|
<IconWithCounter icon='comment' count={chatsCount} />
|
||||||
|
@ -155,12 +157,15 @@ const mapStateToProps = state => {
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
const reportsCount = state.getIn(['admin', 'openReports']).count();
|
const reportsCount = state.getIn(['admin', 'openReports']).count();
|
||||||
const approvalCount = state.getIn(['admin', 'awaitingApproval']).count();
|
const approvalCount = state.getIn(['admin', 'awaitingApproval']).count();
|
||||||
|
const instance = state.get('instance');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account: state.getIn(['accounts', me]),
|
account: state.getIn(['accounts', me]),
|
||||||
logo: getSoapboxConfig(state).get('logo'),
|
logo: getSoapboxConfig(state).get('logo'),
|
||||||
notificationCount: state.getIn(['notifications', 'unread']),
|
notificationCount: state.getIn(['notifications', 'unread']),
|
||||||
chatsCount: state.get('chats').reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0),
|
chatsCount: state.get('chats').reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0),
|
||||||
dashboardCount: reportsCount + approvalCount,
|
dashboardCount: reportsCount + approvalCount,
|
||||||
|
features: getFeatures(instance),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ import Icon from 'soapbox/components/icon';
|
||||||
import { isStaff, isAdmin } from 'soapbox/utils/accounts';
|
import { isStaff, isAdmin } from 'soapbox/utils/accounts';
|
||||||
import ProfileHoverCard from 'soapbox/components/profile_hover_card';
|
import ProfileHoverCard from 'soapbox/components/profile_hover_card';
|
||||||
import { getAccessToken } from 'soapbox/utils/auth';
|
import { getAccessToken } from 'soapbox/utils/auth';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Status,
|
Status,
|
||||||
|
@ -115,6 +116,7 @@ const messages = defineMessages({
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
const account = state.getIn(['accounts', me]);
|
const account = state.getIn(['accounts', me]);
|
||||||
|
const instance = state.get('instance');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
|
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
|
||||||
|
@ -122,6 +124,7 @@ const mapStateToProps = state => {
|
||||||
streamingUrl: state.getIn(['instance', 'urls', 'streaming_api']),
|
streamingUrl: state.getIn(['instance', 'urls', 'streaming_api']),
|
||||||
me,
|
me,
|
||||||
account,
|
account,
|
||||||
|
features: getFeatures(instance),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -302,6 +305,7 @@ class UI extends React.PureComponent {
|
||||||
me: SoapboxPropTypes.me,
|
me: SoapboxPropTypes.me,
|
||||||
streamingUrl: PropTypes.string,
|
streamingUrl: PropTypes.string,
|
||||||
account: PropTypes.object,
|
account: PropTypes.object,
|
||||||
|
features: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -411,7 +415,7 @@ class UI extends React.PureComponent {
|
||||||
});
|
});
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { account } = this.props;
|
const { account, features } = this.props;
|
||||||
if (!account) return;
|
if (!account) return;
|
||||||
|
|
||||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||||
|
@ -432,7 +436,10 @@ class UI extends React.PureComponent {
|
||||||
if (account) {
|
if (account) {
|
||||||
this.props.dispatch(expandHomeTimeline());
|
this.props.dispatch(expandHomeTimeline());
|
||||||
this.props.dispatch(expandNotifications());
|
this.props.dispatch(expandNotifications());
|
||||||
|
|
||||||
|
if (features.chats) {
|
||||||
this.props.dispatch(fetchChats());
|
this.props.dispatch(fetchChats());
|
||||||
|
}
|
||||||
|
|
||||||
if (isStaff(account)) {
|
if (isStaff(account)) {
|
||||||
this.props.dispatch(fetchReports({ state: 'open' }));
|
this.props.dispatch(fetchReports({ state: 'open' }));
|
||||||
|
@ -577,7 +584,7 @@ class UI extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { streamingUrl } = this.props;
|
const { streamingUrl, features } = this.props;
|
||||||
const { draggingOver, mobile } = this.state;
|
const { draggingOver, mobile } = this.state;
|
||||||
const { intl, children, location, dropdownMenuIsOpen, me } = this.props;
|
const { intl, children, location, dropdownMenuIsOpen, me } = this.props;
|
||||||
|
|
||||||
|
@ -635,7 +642,7 @@ class UI extends React.PureComponent {
|
||||||
<ModalContainer />
|
<ModalContainer />
|
||||||
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
|
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
|
||||||
{me && <SidebarMenu />}
|
{me && <SidebarMenu />}
|
||||||
{me && !mobile && (
|
{me && features.chats && !mobile && (
|
||||||
<BundleContainer fetchComponent={ChatPanes}>
|
<BundleContainer fetchComponent={ChatPanes}>
|
||||||
{Component => <Component />}
|
{Component => <Component />}
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
|
|
|
@ -16,6 +16,7 @@ export const getFeatures = createSelector([
|
||||||
focalPoint: v.software === 'Mastodon' && gte(v.compatVersion, '2.3.0'),
|
focalPoint: v.software === 'Mastodon' && gte(v.compatVersion, '2.3.0'),
|
||||||
importMutes: v.software === 'Pleroma' && gte(v.version, '2.2.0'),
|
importMutes: v.software === 'Pleroma' && gte(v.version, '2.2.0'),
|
||||||
emailList: f.includes('email_list'),
|
emailList: f.includes('email_list'),
|
||||||
|
chats: v.software === 'Pleroma' && gte(v.version, '2.1.0'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue