Federating conditional UI

reply-to-ui
Alex Gleason 2021-08-23 14:14:47 -05:00
rodzic c18224f911
commit 6b19f39d51
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 7211D1F99744FBB7
8 zmienionych plików z 93 dodań i 66 usunięć

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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}

Wyświetl plik

@ -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);

Wyświetl plik

@ -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 });

Wyświetl plik

@ -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,
};

Wyświetl plik

@ -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>
)}

Wyświetl plik

@ -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')
);

Wyświetl plik

@ -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
};
});