diff --git a/app/soapbox/actions/soapbox.js b/app/soapbox/actions/soapbox.js
index 25e9dcecf..88109b7a0 100644
--- a/app/soapbox/actions/soapbox.js
+++ b/app/soapbox/actions/soapbox.js
@@ -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() {
diff --git a/app/soapbox/components/home_column_header.js b/app/soapbox/components/home_column_header.js
index 21bc3d331..9ed7fea2a 100644
--- a/app/soapbox/components/home_column_header.js
+++ b/app/soapbox/components/home_column_header.js
@@ -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 {
-
- {siteTitle}
+
+ {federating ? siteTitle : formatMessage(messages.allTitle)}
-
+ {federating &&
{formatMessage(messages.fediverseTitle)}
-
+ }
{collapseButton}
diff --git a/app/soapbox/features/account/components/header.js b/app/soapbox/features/account/components/header.js
index 5ca1eb34f..3e4d152dd 100644
--- a/app/soapbox/features/account/components/header.js
+++ b/app/soapbox/features/account/components/header.js
@@ -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);
diff --git a/app/soapbox/features/soapbox_config/index.js b/app/soapbox/features/soapbox_config/index.js
index 9754de627..b0199c449 100644
--- a/app/soapbox/features/soapbox_config/index.js
+++ b/app/soapbox/features/soapbox_config/index.js
@@ -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 });
diff --git a/app/soapbox/features/ui/components/link_footer.js b/app/soapbox/features/ui/components/link_footer.js
index 46b84a27c..29b22f8e0 100644
--- a/app/soapbox/features/ui/components/link_footer.js
+++ b/app/soapbox/features/ui/components/link_footer.js
@@ -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 }) => (
{account && <>
-
+ {federating && }
{isAdmin(account) && }
{isAdmin(account) && }
-
+ {federating && }
>}
@@ -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,
};
diff --git a/app/soapbox/features/ui/components/report_modal.js b/app/soapbox/features/ui/components/report_modal.js
index dca4e1118..fd34da406 100644
--- a/app/soapbox/features/ui/components/report_modal.js
+++ b/app/soapbox/features/ui/components/report_modal.js
@@ -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 (
@@ -120,13 +125,13 @@ class ReportModal extends ImmutablePureComponent {
autoFocus
/>
- {domain && (
+ {canForward && (
)}
diff --git a/app/soapbox/utils/accounts.js b/app/soapbox/utils/accounts.js
index 77e3f3997..461fca42e 100644
--- a/app/soapbox/utils/accounts.js
+++ b/app/soapbox/utils/accounts.js
@@ -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')
);
diff --git a/app/soapbox/utils/features.js b/app/soapbox/utils/features.js
index 2daa50546..32b37f85c 100644
--- a/app/soapbox/utils/features.js
+++ b/app/soapbox/utils/features.js
@@ -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
};
});