Federating conditional UI
This commit is contained in:
parent
c18224f911
commit
6b19f39d51
|
@ -27,42 +27,37 @@ const allowedEmojiRGI = ImmutableList([
|
|||
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
export const defaultConfig = ImmutableMap({
|
||||
logo: '',
|
||||
banner: '',
|
||||
brandColor: '', // Empty
|
||||
customCss: ImmutableList(),
|
||||
promoPanel: ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
}),
|
||||
extensions: ImmutableMap(),
|
||||
defaultSettings: ImmutableMap(),
|
||||
copyright: `♥${year}. Copying is an act of love. Please copy and share.`,
|
||||
navlinks: ImmutableMap({
|
||||
homeFooter: ImmutableList(),
|
||||
}),
|
||||
allowedEmoji: allowedEmoji,
|
||||
verifiedCanEditName: false,
|
||||
displayFqn: true,
|
||||
cryptoAddresses: ImmutableList(),
|
||||
cryptoDonatePanel: ImmutableMap({
|
||||
limit: 1,
|
||||
}),
|
||||
aboutPages: ImmutableMap(),
|
||||
});
|
||||
export const makeDefaultConfig = features => {
|
||||
return ImmutableMap({
|
||||
logo: '',
|
||||
banner: '',
|
||||
brandColor: '', // Empty
|
||||
customCss: ImmutableList(),
|
||||
promoPanel: ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
}),
|
||||
extensions: ImmutableMap(),
|
||||
defaultSettings: ImmutableMap(),
|
||||
copyright: `♥${year}. Copying is an act of love. Please copy and share.`,
|
||||
navlinks: ImmutableMap({
|
||||
homeFooter: ImmutableList(),
|
||||
}),
|
||||
allowedEmoji: features.emojiReactsRGI ? allowedEmojiRGI : allowedEmoji,
|
||||
verifiedCanEditName: false,
|
||||
displayFqn: Boolean(features.federating),
|
||||
cryptoAddresses: ImmutableList(),
|
||||
cryptoDonatePanel: ImmutableMap({
|
||||
limit: 1,
|
||||
}),
|
||||
aboutPages: ImmutableMap(),
|
||||
});
|
||||
};
|
||||
|
||||
export const getSoapboxConfig = createSelector([
|
||||
state => state.get('soapbox'),
|
||||
state => getFeatures(state.get('instance')).emojiReactsRGI,
|
||||
], (soapbox, emojiReactsRGI) => {
|
||||
// https://git.pleroma.social/pleroma/pleroma/-/issues/2355
|
||||
if (emojiReactsRGI) {
|
||||
return defaultConfig
|
||||
.set('allowedEmoji', allowedEmojiRGI)
|
||||
.merge(soapbox);
|
||||
} else {
|
||||
return defaultConfig.merge(soapbox);
|
||||
}
|
||||
state => getFeatures(state.get('instance')),
|
||||
], (soapbox, features) => {
|
||||
return makeDefaultConfig(features).merge(soapbox);
|
||||
});
|
||||
|
||||
export function fetchSoapboxConfig() {
|
||||
|
|
|
@ -10,11 +10,13 @@ import { Link } from 'react-router-dom';
|
|||
import Icon from 'soapbox/components/icon';
|
||||
import { fetchLists } from 'soapbox/actions/lists';
|
||||
import { createSelector } from 'reselect';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
const messages = defineMessages({
|
||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
|
||||
homeTitle: { id: 'home_column_header.home', defaultMessage: 'Home' },
|
||||
allTitle: { id: 'home_column_header.all', defaultMessage: 'All' },
|
||||
fediverseTitle: { id: 'home_column_header.fediverse', defaultMessage: 'Fediverse' },
|
||||
listTitle: { id: 'home_column.lists', defaultMessage: 'Lists' },
|
||||
});
|
||||
|
@ -28,9 +30,13 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
|||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const instance = state.get('instance');
|
||||
const features = getFeatures(instance);
|
||||
|
||||
return {
|
||||
lists: getOrderedLists(state),
|
||||
siteTitle: state.getIn(['instance', 'title']),
|
||||
federating: features.federating,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -49,6 +55,7 @@ class ColumnHeader extends React.PureComponent {
|
|||
activeSubItem: PropTypes.string,
|
||||
lists: ImmutablePropTypes.list,
|
||||
siteTitle: PropTypes.string,
|
||||
federating: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -77,7 +84,7 @@ class ColumnHeader extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { active, children, intl: { formatMessage }, activeItem, activeSubItem, lists, siteTitle } = this.props;
|
||||
const { active, children, intl: { formatMessage }, activeItem, activeSubItem, lists, siteTitle, federating } = this.props;
|
||||
const { collapsed, animating, expandedFor } = this.state;
|
||||
|
||||
const wrapperClassName = classNames('column-header__wrapper', {
|
||||
|
@ -143,14 +150,14 @@ class ColumnHeader extends React.PureComponent {
|
|||
</Link>
|
||||
|
||||
<Link to='/timeline/local' className={classNames('btn grouped', { 'active': 'local' === activeItem })}>
|
||||
<Icon id='users' fixedWidth className='column-header__icon' />
|
||||
{siteTitle}
|
||||
<Icon id={federating ? 'users' : 'globe-w'} fixedWidth className='column-header__icon' />
|
||||
{federating ? siteTitle : formatMessage(messages.allTitle)}
|
||||
</Link>
|
||||
|
||||
<Link to='/timeline/fediverse' className={classNames('btn grouped', { 'active': 'fediverse' === activeItem })}>
|
||||
{federating && <Link to='/timeline/fediverse' className={classNames('btn grouped', { 'active': 'fediverse' === activeItem })}>
|
||||
<Icon id='fediverse' fixedWidth className='column-header__icon' />
|
||||
{formatMessage(messages.fediverseTitle)}
|
||||
</Link>
|
||||
</Link>}
|
||||
|
||||
<div className='column-header__buttons'>
|
||||
{collapseButton}
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
isModerator,
|
||||
isVerified,
|
||||
isLocal,
|
||||
isRemote,
|
||||
getDomain,
|
||||
} from 'soapbox/utils/accounts';
|
||||
import { parseVersion } from 'soapbox/utils/features';
|
||||
import classNames from 'classnames';
|
||||
|
@ -195,8 +197,8 @@ class Header extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
|
||||
}
|
||||
|
||||
if (account.get('acct') !== account.get('username')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
if (isRemote(account)) {
|
||||
const domain = getDomain(account);
|
||||
|
||||
menu.push(null);
|
||||
|
||||
|
|
|
@ -18,7 +18,8 @@ import {
|
|||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
import { updateConfig } from 'soapbox/actions/admin';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { defaultConfig } from 'soapbox/actions/soapbox';
|
||||
import { makeDefaultConfig } from 'soapbox/actions/soapbox';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import { uploadMedia } from 'soapbox/actions/media';
|
||||
import { SketchPicker } from 'react-color';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
|
@ -60,9 +61,14 @@ const templates = {
|
|||
cryptoAddress: ImmutableMap({ ticker: '', address: '', note: '' }),
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
soapbox: state.get('soapbox'),
|
||||
});
|
||||
const mapStateToProps = state => {
|
||||
const instance = state.get('instance');
|
||||
|
||||
return {
|
||||
soapbox: state.get('soapbox'),
|
||||
features: getFeatures(instance),
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
|
@ -70,6 +76,7 @@ class SoapboxConfig extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
soapbox: ImmutablePropTypes.map.isRequired,
|
||||
features: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
@ -179,7 +186,9 @@ class SoapboxConfig extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
getSoapboxConfig = () => {
|
||||
return defaultConfig.mergeDeep(this.state.soapbox);
|
||||
const { features } = this.props;
|
||||
const { soapbox } = this.state;
|
||||
return makeDefaultConfig(features).mergeDeep(soapbox);
|
||||
}
|
||||
|
||||
toggleJSONEditor = (value) => this.setState({ jsonEditorExpanded: value });
|
||||
|
|
|
@ -8,11 +8,16 @@ import { openModal } from '../../../actions/modal';
|
|||
import { logOut } from 'soapbox/actions/auth';
|
||||
import { isAdmin } from 'soapbox/utils/accounts';
|
||||
import sourceCode from 'soapbox/utils/code';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const me = state.get('me');
|
||||
const instance = state.get('instance');
|
||||
const features = getFeatures(instance);
|
||||
|
||||
return {
|
||||
account: state.getIn(['accounts', me]),
|
||||
federating: features.federating,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -26,19 +31,19 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
},
|
||||
});
|
||||
|
||||
const LinkFooter = ({ onOpenHotkeys, account, onClickLogOut }) => (
|
||||
const LinkFooter = ({ onOpenHotkeys, account, federating, onClickLogOut }) => (
|
||||
<div className='getting-started__footer'>
|
||||
<ul>
|
||||
{account && <>
|
||||
<li><Link to='/blocks'><FormattedMessage id='navigation_bar.blocks' defaultMessage='Blocks' /></Link></li>
|
||||
<li><Link to='/mutes'><FormattedMessage id='navigation_bar.mutes' defaultMessage='Mutes' /></Link></li>
|
||||
<li><Link to='/filters'><FormattedMessage id='navigation_bar.filters' defaultMessage='Filters' /></Link></li>
|
||||
<li><Link to='/domain_blocks'><FormattedMessage id='navigation_bar.domain_blocks' defaultMessage='Domain blocks' /></Link></li>
|
||||
{federating && <li><Link to='/domain_blocks'><FormattedMessage id='navigation_bar.domain_blocks' defaultMessage='Domain blocks' /></Link></li>}
|
||||
<li><Link to='/follow_requests'><FormattedMessage id='navigation_bar.follow_requests' defaultMessage='Follow requests' /></Link></li>
|
||||
{isAdmin(account) && <li><a href='/pleroma/admin'><FormattedMessage id='navigation_bar.admin_settings' defaultMessage='AdminFE' /></a></li>}
|
||||
{isAdmin(account) && <li><Link to='/soapbox/config'><FormattedMessage id='navigation_bar.soapbox_config' defaultMessage='Soapbox config' /></Link></li>}
|
||||
<li><Link to='/settings/import'><FormattedMessage id='navigation_bar.import_data' defaultMessage='Import data' /></Link></li>
|
||||
<li><Link to='/settings/aliases'><FormattedMessage id='navigation_bar.account_aliases' defaultMessage='Account aliases' /></Link></li>
|
||||
{federating && <li><Link to='/settings/aliases'><FormattedMessage id='navigation_bar.account_aliases' defaultMessage='Account aliases' /></Link></li>}
|
||||
<li><a href='#' onClick={onOpenHotkeys}><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></a></li>
|
||||
</>}
|
||||
<li><Link to='/about'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></Link></li>
|
||||
|
@ -61,6 +66,7 @@ const LinkFooter = ({ onOpenHotkeys, account, onClickLogOut }) => (
|
|||
|
||||
LinkFooter.propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
federating: PropTypes.bool,
|
||||
onOpenHotkeys: PropTypes.func.isRequired,
|
||||
onClickLogOut: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
|
@ -13,6 +13,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import Button from '../../../components/button';
|
||||
import Toggle from 'react-toggle';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import { isRemote, getDomain } from 'soapbox/utils/accounts';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
|
@ -25,14 +27,18 @@ const makeMapStateToProps = () => {
|
|||
|
||||
const mapStateToProps = state => {
|
||||
const accountId = state.getIn(['reports', 'new', 'account_id']);
|
||||
const account = getAccount(state, accountId);
|
||||
const instance = state.get('instance');
|
||||
const features = getFeatures(instance);
|
||||
|
||||
return {
|
||||
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
|
||||
account: getAccount(state, accountId),
|
||||
account,
|
||||
comment: state.getIn(['reports', 'new', 'comment']),
|
||||
forward: state.getIn(['reports', 'new', 'forward']),
|
||||
block: state.getIn(['reports', 'new', 'block']),
|
||||
statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
|
||||
canForward: isRemote(account) && features.federating,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -50,6 +56,7 @@ class ReportModal extends ImmutablePureComponent {
|
|||
comment: PropTypes.string.isRequired,
|
||||
forward: PropTypes.bool,
|
||||
block: PropTypes.bool,
|
||||
canForward: PropTypes.bool,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
@ -91,14 +98,12 @@ class ReportModal extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { account, comment, intl, statusIds, isSubmitting, forward, block, onClose } = this.props;
|
||||
const { account, comment, intl, statusIds, isSubmitting, forward, block, canForward, onClose } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal report-modal'>
|
||||
<div className='report-modal__target'>
|
||||
|
@ -120,13 +125,13 @@ class ReportModal extends ImmutablePureComponent {
|
|||
autoFocus
|
||||
/>
|
||||
|
||||
{domain && (
|
||||
{canForward && (
|
||||
<div>
|
||||
<p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send a copy of the report there as well?' /></p>
|
||||
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
|
||||
<label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
|
||||
<label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: getDomain(account) }} /></label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
const guessDomain = account => {
|
||||
const getDomainFromURL = account => {
|
||||
try {
|
||||
const re = /https?:\/\/(.*?)\//i;
|
||||
return re.exec(account.get('url'))[1];
|
||||
} catch(e) {
|
||||
return null;
|
||||
const url = account.get('url');
|
||||
return new URL(url).host;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const getDomain = account => {
|
||||
let domain = account.get('acct').split('@')[1];
|
||||
if (!domain) domain = guessDomain(account);
|
||||
return domain;
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
return domain ? domain : getDomainFromURL(account);
|
||||
};
|
||||
|
||||
export const guessFqn = account => {
|
||||
const [user, domain] = account.get('acct').split('@');
|
||||
if (!domain) return [user, guessDomain(account)].join('@');
|
||||
if (!domain) return [user, getDomainFromURL(account)].join('@');
|
||||
return account.get('acct');
|
||||
};
|
||||
|
||||
|
@ -54,6 +53,8 @@ export const isLocal = account => {
|
|||
return domain === undefined ? true : false;
|
||||
};
|
||||
|
||||
export const isRemote = account => !isLocal(account);
|
||||
|
||||
export const isVerified = account => (
|
||||
account.getIn(['pleroma', 'tags'], ImmutableList()).includes('verified')
|
||||
);
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
// Detect backend features to conditionally render elements
|
||||
import gte from 'semver/functions/gte';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
export const getFeatures = createSelector([
|
||||
instance => parseVersion(instance.get('version')),
|
||||
instance => instance.getIn(['pleroma', 'metadata', 'features'], ImmutableList()),
|
||||
], (v, f) => {
|
||||
instance => instance.getIn(['pleroma', 'metadata', 'federation'], ImmutableMap()),
|
||||
], (v, features, federation) => {
|
||||
return {
|
||||
suggestions: v.software === 'Mastodon' && gte(v.compatVersion, '2.4.3'),
|
||||
trends: v.software === 'Mastodon' && gte(v.compatVersion, '3.0.0'),
|
||||
|
@ -15,9 +16,10 @@ export const getFeatures = createSelector([
|
|||
attachmentLimit: v.software === 'Pleroma' ? Infinity : 4,
|
||||
focalPoint: v.software === 'Mastodon' && gte(v.compatVersion, '2.3.0'),
|
||||
importMutes: v.software === 'Pleroma' && gte(v.version, '2.2.0'),
|
||||
emailList: f.includes('email_list'),
|
||||
emailList: features.includes('email_list'),
|
||||
chats: v.software === 'Pleroma' && gte(v.version, '2.1.0'),
|
||||
scopes: v.software === 'Pleroma' ? 'read write follow push admin' : 'read write follow push',
|
||||
federating: federation.get('enabled', true), // Assume true unless explicitly false
|
||||
};
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue