sforkowany z mirror/soapbox
Merge branch 'developers-stuff' into 'develop'
Add advanced settings under Developers section, refactor See merge request soapbox-pub/soapbox-fe!1625update-emoji-mart^2
commit
3377f1dcd1
|
@ -18,7 +18,7 @@ export const OAUTH_TOKEN_REVOKE_REQUEST = 'OAUTH_TOKEN_REVOKE_REQUEST';
|
||||||
export const OAUTH_TOKEN_REVOKE_SUCCESS = 'OAUTH_TOKEN_REVOKE_SUCCESS';
|
export const OAUTH_TOKEN_REVOKE_SUCCESS = 'OAUTH_TOKEN_REVOKE_SUCCESS';
|
||||||
export const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL';
|
export const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL';
|
||||||
|
|
||||||
export const obtainOAuthToken = (params: Record<string, string>, baseURL?: string) =>
|
export const obtainOAuthToken = (params: Record<string, string | undefined>, baseURL?: string) =>
|
||||||
(dispatch: AppDispatch) => {
|
(dispatch: AppDispatch) => {
|
||||||
dispatch({ type: OAUTH_TOKEN_CREATE_REQUEST, params });
|
dispatch({ type: OAUTH_TOKEN_CREATE_REQUEST, params });
|
||||||
return baseClient(null, baseURL).post('/oauth/token', params).then(({ data: token }) => {
|
return baseClient(null, baseURL).post('/oauth/token', params).then(({ data: token }) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength' | 'onChange' | 'required' | 'disabled' | 'rows'> {
|
interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength' | 'onChange' | 'required' | 'disabled' | 'rows' | 'readOnly'> {
|
||||||
/** Put the cursor into the input on mount. */
|
/** Put the cursor into the input on mount. */
|
||||||
autoFocus?: boolean,
|
autoFocus?: boolean,
|
||||||
/** The initial text in the input. */
|
/** The initial text in the input. */
|
||||||
|
|
|
@ -1,231 +0,0 @@
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { createApp } from 'soapbox/actions/apps';
|
|
||||||
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
|
||||||
import { Button, Form, FormActions, FormGroup, Input, Stack, Text, Textarea } from 'soapbox/components/ui';
|
|
||||||
import Column from 'soapbox/features/ui/components/column';
|
|
||||||
import { getBaseURL } from 'soapbox/utils/accounts';
|
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
heading: { id: 'column.app_create', defaultMessage: 'Create app' },
|
|
||||||
namePlaceholder: { id: 'app_create.name_placeholder', defaultMessage: 'e.g. \'Soapbox\'' },
|
|
||||||
scopesPlaceholder: { id: 'app_create.scopes_placeholder', defaultMessage: 'e.g. \'read write follow\'' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const me = state.get('me');
|
|
||||||
const account = state.getIn(['accounts', me]);
|
|
||||||
|
|
||||||
const instance = state.get('instance');
|
|
||||||
const features = getFeatures(instance);
|
|
||||||
|
|
||||||
return {
|
|
||||||
account,
|
|
||||||
defaultScopes: features.scopes,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
|
||||||
@injectIntl
|
|
||||||
class CreateApp extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
account: ImmutablePropTypes.record.isRequired,
|
|
||||||
defaultScopes: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
initialState = () => {
|
|
||||||
return {
|
|
||||||
params: ImmutableMap({
|
|
||||||
client_name: '',
|
|
||||||
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
|
|
||||||
scopes: '',
|
|
||||||
website: '',
|
|
||||||
}),
|
|
||||||
app: null,
|
|
||||||
token: null,
|
|
||||||
isLoading: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
state = this.initialState()
|
|
||||||
|
|
||||||
handleCreateApp = () => {
|
|
||||||
const { dispatch, account } = this.props;
|
|
||||||
const { params } = this.state;
|
|
||||||
const baseURL = getBaseURL(account);
|
|
||||||
|
|
||||||
return dispatch(createApp(params.toJS(), baseURL))
|
|
||||||
.then(app => this.setState({ app }));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCreateToken = () => {
|
|
||||||
const { dispatch, account } = this.props;
|
|
||||||
const { app, params: appParams } = this.state;
|
|
||||||
const baseURL = getBaseURL(account);
|
|
||||||
|
|
||||||
const tokenParams = {
|
|
||||||
client_id: app.client_id,
|
|
||||||
client_secret: app.client_secret,
|
|
||||||
redirect_uri: appParams.get('redirect_uri'),
|
|
||||||
grant_type: 'client_credentials',
|
|
||||||
scope: appParams.get('scopes'),
|
|
||||||
};
|
|
||||||
|
|
||||||
return dispatch(obtainOAuthToken(tokenParams, baseURL))
|
|
||||||
.then(token => this.setState({ token }));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = e => {
|
|
||||||
this.setState({ isLoading: true });
|
|
||||||
|
|
||||||
this.handleCreateApp()
|
|
||||||
.then(this.handleCreateToken)
|
|
||||||
.then(() => {
|
|
||||||
this.scrollToTop();
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
}).catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setParam = (key, value) => {
|
|
||||||
const { params } = this.state;
|
|
||||||
const newParams = params.set(key, value);
|
|
||||||
|
|
||||||
this.setState({ params: newParams });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleParamChange = key => {
|
|
||||||
return e => {
|
|
||||||
this.setParam(key, e.target.value);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
resetState = () => {
|
|
||||||
this.setState(this.initialState());
|
|
||||||
}
|
|
||||||
|
|
||||||
handleReset = e => {
|
|
||||||
this.resetState();
|
|
||||||
this.scrollToTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollToTop = () => {
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
}
|
|
||||||
|
|
||||||
renderResults = () => {
|
|
||||||
const { intl } = this.props;
|
|
||||||
const { app, token } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
|
||||||
<Form>
|
|
||||||
<Stack>
|
|
||||||
<Text size='lg' weight='medium'>
|
|
||||||
<FormattedMessage id='app_create.results.explanation_title' defaultMessage='App created successfully' />
|
|
||||||
</Text>
|
|
||||||
<Text theme='muted'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='app_create.results.explanation_text'
|
|
||||||
defaultMessage='You created a new app and token! Please copy the credentials somewhere; you will not see them again after navigating away from this page.'
|
|
||||||
/>
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<FormGroup labelText={<FormattedMessage id='app_create.results.app_label' defaultMessage='App' />}>
|
|
||||||
<Textarea
|
|
||||||
value={JSON.stringify(app, null, 2)}
|
|
||||||
rows={10}
|
|
||||||
readOnly
|
|
||||||
isCodeEditor
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup labelText={<FormattedMessage id='app_create.results.token_label' defaultMessage='OAuth token' />}>
|
|
||||||
<Textarea
|
|
||||||
value={JSON.stringify(token, null, 2)}
|
|
||||||
rows={10}
|
|
||||||
readOnly
|
|
||||||
isCodeEditor
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button theme='primary' type='button' onClick={this.handleReset}>
|
|
||||||
<FormattedMessage id='app_create.restart' defaultMessage='Create another' />
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl } = this.props;
|
|
||||||
const { params, app, token, isLoading } = this.state;
|
|
||||||
|
|
||||||
if (app && token) {
|
|
||||||
return this.renderResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
|
||||||
<Form onSubmit={this.handleSubmit}>
|
|
||||||
<FormGroup labelText={<FormattedMessage id='app_create.name_label' defaultMessage='App name' />}>
|
|
||||||
<Input
|
|
||||||
placeholder={intl.formatMessage(messages.namePlaceholder)}
|
|
||||||
onChange={this.handleParamChange('client_name')}
|
|
||||||
value={params.get('client_name')}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup labelText={<FormattedMessage id='app_create.website_label' defaultMessage='Website' />}>
|
|
||||||
<Input
|
|
||||||
placeholder='https://soapbox.pub'
|
|
||||||
onChange={this.handleParamChange('website')}
|
|
||||||
value={params.get('website')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup labelText={<FormattedMessage id='app_create.redirect_uri_label' defaultMessage='Redirect URIs' />}>
|
|
||||||
<Input
|
|
||||||
placeholder='https://example.com'
|
|
||||||
onChange={this.handleParamChange('redirect_uris')}
|
|
||||||
value={params.get('redirect_uris')}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup labelText={<FormattedMessage id='app_create.scopes_label' defaultMessage='Scopes' />}>
|
|
||||||
<Input
|
|
||||||
placeholder={intl.formatMessage(messages.scopesPlaceholder)}
|
|
||||||
onChange={this.handleParamChange('scopes')}
|
|
||||||
value={params.get('scopes')}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button theme='primary' type='submit' disabled={isLoading}>
|
|
||||||
<FormattedMessage id='app_create.submit' defaultMessage='Create app' />
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import { createApp } from 'soapbox/actions/apps';
|
||||||
|
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
||||||
|
import { Button, Form, FormActions, FormGroup, Input, Stack, Text, Textarea } from 'soapbox/components/ui';
|
||||||
|
import Column from 'soapbox/features/ui/components/column';
|
||||||
|
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
||||||
|
import { getBaseURL } from 'soapbox/utils/accounts';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.app_create', defaultMessage: 'Create app' },
|
||||||
|
namePlaceholder: { id: 'app_create.name_placeholder', defaultMessage: 'e.g. \'Soapbox\'' },
|
||||||
|
scopesPlaceholder: { id: 'app_create.scopes_placeholder', defaultMessage: 'e.g. \'read write follow\'' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const BLANK_PARAMS = {
|
||||||
|
client_name: '',
|
||||||
|
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
|
scopes: '',
|
||||||
|
website: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
type Params = typeof BLANK_PARAMS;
|
||||||
|
|
||||||
|
const CreateApp: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const account = useOwnAccount();
|
||||||
|
|
||||||
|
const [app, setApp] = useState<Record<string, any> | null>(null);
|
||||||
|
const [token, setToken] = useState<string | null>(null);
|
||||||
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
const [params, setParams] = useState<Params>(BLANK_PARAMS);
|
||||||
|
|
||||||
|
const handleCreateApp = () => {
|
||||||
|
const baseURL = getBaseURL(account!);
|
||||||
|
|
||||||
|
return dispatch(createApp(params, baseURL))
|
||||||
|
.then(app => {
|
||||||
|
setApp(app);
|
||||||
|
return app;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateToken = (app: Record<string, string>) => {
|
||||||
|
const baseURL = getBaseURL(account!);
|
||||||
|
|
||||||
|
const tokenParams = {
|
||||||
|
client_id: app!.client_id,
|
||||||
|
client_secret: app!.client_secret,
|
||||||
|
redirect_uri: params.redirect_uris,
|
||||||
|
grant_type: 'client_credentials',
|
||||||
|
scope: params.scopes,
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch(obtainOAuthToken(tokenParams, baseURL))
|
||||||
|
.then(setToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
handleCreateApp()
|
||||||
|
.then(handleCreateToken)
|
||||||
|
.then(() => {
|
||||||
|
scrollToTop();
|
||||||
|
setLoading(false);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setParam = (key: string, value: string) => {
|
||||||
|
setParams({ ...params, [key]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleParamChange = (key: string): React.ChangeEventHandler<HTMLInputElement> => {
|
||||||
|
return e => {
|
||||||
|
setParam(key, e.target.value);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetState = () => {
|
||||||
|
setApp(null);
|
||||||
|
setToken(null);
|
||||||
|
setLoading(false);
|
||||||
|
setParams(BLANK_PARAMS);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
resetState();
|
||||||
|
scrollToTop();
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToTop = (): void => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderResults = () => {
|
||||||
|
return (
|
||||||
|
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
||||||
|
<Form>
|
||||||
|
<Stack>
|
||||||
|
<Text size='lg' weight='medium'>
|
||||||
|
<FormattedMessage id='app_create.results.explanation_title' defaultMessage='App created successfully' />
|
||||||
|
</Text>
|
||||||
|
<Text theme='muted'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='app_create.results.explanation_text'
|
||||||
|
defaultMessage='You created a new app and token! Please copy the credentials somewhere; you will not see them again after navigating away from this page.'
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<FormGroup labelText={<FormattedMessage id='app_create.results.app_label' defaultMessage='App' />}>
|
||||||
|
<Textarea
|
||||||
|
value={JSON.stringify(app, null, 2)}
|
||||||
|
rows={10}
|
||||||
|
readOnly
|
||||||
|
isCodeEditor
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup labelText={<FormattedMessage id='app_create.results.token_label' defaultMessage='OAuth token' />}>
|
||||||
|
<Textarea
|
||||||
|
value={JSON.stringify(token, null, 2)}
|
||||||
|
rows={10}
|
||||||
|
readOnly
|
||||||
|
isCodeEditor
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormActions>
|
||||||
|
<Button theme='primary' type='button' onClick={handleReset}>
|
||||||
|
<FormattedMessage id='app_create.restart' defaultMessage='Create another' />
|
||||||
|
</Button>
|
||||||
|
</FormActions>
|
||||||
|
</Form>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (app && token) {
|
||||||
|
return renderResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<FormGroup labelText={<FormattedMessage id='app_create.name_label' defaultMessage='App name' />}>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
placeholder={intl.formatMessage(messages.namePlaceholder)}
|
||||||
|
onChange={handleParamChange('client_name')}
|
||||||
|
value={params.client_name}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup labelText={<FormattedMessage id='app_create.website_label' defaultMessage='Website' />}>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
placeholder='https://soapbox.pub'
|
||||||
|
onChange={handleParamChange('website')}
|
||||||
|
value={params.website}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup labelText={<FormattedMessage id='app_create.redirect_uri_label' defaultMessage='Redirect URIs' />}>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
placeholder='https://example.com'
|
||||||
|
onChange={handleParamChange('redirect_uris')}
|
||||||
|
value={params.redirect_uris}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup labelText={<FormattedMessage id='app_create.scopes_label' defaultMessage='Scopes' />}>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
placeholder={intl.formatMessage(messages.scopesPlaceholder)}
|
||||||
|
onChange={handleParamChange('scopes')}
|
||||||
|
value={params.scopes}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormActions>
|
||||||
|
<Button theme='primary' type='submit' disabled={isLoading}>
|
||||||
|
<FormattedMessage id='app_create.submit' defaultMessage='Create app' />
|
||||||
|
</Button>
|
||||||
|
</FormActions>
|
||||||
|
</Form>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateApp;
|
|
@ -3,8 +3,8 @@ import React from 'react';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
import DevelopersChallenge from './developers_challenge';
|
import DevelopersChallenge from './developers-challenge';
|
||||||
import DevelopersMenu from './developers_menu';
|
import DevelopersMenu from './developers-menu';
|
||||||
|
|
||||||
const Developers: React.FC = () => {
|
const Developers: React.FC = () => {
|
||||||
const isDeveloper = useAppSelector((state) => getSettings(state).get('isDeveloper'));
|
const isDeveloper = useAppSelector((state) => getSettings(state).get('isDeveloper'));
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import { showAlertForError } from 'soapbox/actions/alerts';
|
||||||
|
import { patchMe } from 'soapbox/actions/me';
|
||||||
|
import { FE_NAME, SETTINGS_UPDATE, changeSetting } from 'soapbox/actions/settings';
|
||||||
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
|
import {
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
Column,
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
FormActions,
|
||||||
|
FormGroup,
|
||||||
|
Textarea,
|
||||||
|
} from 'soapbox/components/ui';
|
||||||
|
import SettingToggle from 'soapbox/features/notifications/components/setting_toggle';
|
||||||
|
import { useAppSelector, useAppDispatch, useSettings } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const isJSONValid = (text: any): boolean => {
|
||||||
|
try {
|
||||||
|
JSON.parse(text);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.settings_store', defaultMessage: 'Settings store' },
|
||||||
|
hint: { id: 'developers.settings_store.hint', defaultMessage: 'It is possible to directly edit your user settings here. BE CAREFUL! Editing this section can break your account, and you will only be able to recover through the API.' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const SettingsStore: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const settings = useSettings();
|
||||||
|
const settingsStore = useAppSelector(state => state.get('settings'));
|
||||||
|
|
||||||
|
const [rawJSON, setRawJSON] = useState<string>(JSON.stringify(settingsStore, null, 2));
|
||||||
|
const [jsonValid, setJsonValid] = useState(true);
|
||||||
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleEditJSON: React.ChangeEventHandler<HTMLTextAreaElement> = ({ target }) => {
|
||||||
|
const rawJSON = target.value;
|
||||||
|
setRawJSON(rawJSON);
|
||||||
|
setJsonValid(isJSONValid(rawJSON));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onToggleChange = (key: string[], checked: boolean) => {
|
||||||
|
dispatch(changeSetting(key, checked, { showAlert: true }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit: React.FormEventHandler = e => {
|
||||||
|
const settings = JSON.parse(rawJSON);
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
dispatch(patchMe({
|
||||||
|
pleroma_settings_store: {
|
||||||
|
[FE_NAME]: settings,
|
||||||
|
},
|
||||||
|
})).then(response => {
|
||||||
|
dispatch({ type: SETTINGS_UPDATE, settings });
|
||||||
|
setLoading(false);
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(showAlertForError(error));
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRawJSON(JSON.stringify(settingsStore, null, 2));
|
||||||
|
setJsonValid(true);
|
||||||
|
}, [settingsStore]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<FormGroup
|
||||||
|
hintText={intl.formatMessage(messages.hint)}
|
||||||
|
errors={jsonValid ? [] : ['is invalid']}
|
||||||
|
>
|
||||||
|
<Textarea
|
||||||
|
value={rawJSON}
|
||||||
|
onChange={handleEditJSON}
|
||||||
|
disabled={isLoading}
|
||||||
|
rows={12}
|
||||||
|
isCodeEditor
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormActions>
|
||||||
|
<Button theme='primary' type='submit' disabled={!jsonValid || isLoading}>
|
||||||
|
<FormattedMessage id='soapbox_config.save' defaultMessage='Save' />
|
||||||
|
</Button>
|
||||||
|
</FormActions>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle title='Advanced settings' />
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<List>
|
||||||
|
<ListItem label={<FormattedMessage id='preferences.notifications.advanced' defaultMessage='Show all notification categories' />}>
|
||||||
|
<SettingToggle settings={settings} settingPath={['notifications', 'quickFilter', 'advanced']} onChange={onToggleChange} />
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem label={<FormattedMessage id='preferences.fields.unfollow_modal_label' defaultMessage='Show confirmation dialog before unfollowing someone' />}>
|
||||||
|
<SettingToggle settings={settings} settingPath={['unfollowModal']} onChange={onToggleChange} />
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem label={<FormattedMessage id='preferences.fields.missing_description_modal_label' defaultMessage='Show confirmation dialog before sending a post without media descriptions' />}>
|
||||||
|
<SettingToggle settings={settings} settingPath={['missingDescriptionModal']} onChange={onToggleChange} />
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem label={<FormattedMessage id='preferences.fields.reduce_motion_label' defaultMessage='Reduce motion in animations' />}>
|
||||||
|
<SettingToggle settings={settings} settingPath={['reduceMotion']} onChange={onToggleChange} />
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem label={<FormattedMessage id='preferences.fields.underline_links_label' defaultMessage='Always underline links in posts' />}>
|
||||||
|
<SettingToggle settings={settings} settingPath={['underlineLinks']} onChange={onToggleChange} />
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem label={<FormattedMessage id='preferences.fields.system_font_label' defaultMessage="Use system's default font" />}>
|
||||||
|
<SettingToggle settings={settings} settingPath={['systemFont']} onChange={onToggleChange} />
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<div className='dyslexic'>
|
||||||
|
<ListItem label={<FormattedMessage id='preferences.fields.dyslexic_font_label' defaultMessage='Dyslexic mode' />}>
|
||||||
|
<SettingToggle settings={settings} settingPath={['dyslexicFont']} onChange={onToggleChange} />
|
||||||
|
</ListItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <ListItem
|
||||||
|
label={<FormattedMessage id='preferences.fields.halloween_label' defaultMessage='Halloween mode' />}
|
||||||
|
hint={<FormattedMessage id='preferences.hints.halloween' defaultMessage='Beware: SPOOKY! Supports light/dark toggle.' />}
|
||||||
|
>
|
||||||
|
<SettingToggle settings={settings} settingPath={['halloween']} onChange={onToggleChange} />
|
||||||
|
</ListItem> */}
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
label={<FormattedMessage id='preferences.fields.demetricator_label' defaultMessage='Use Demetricator' />}
|
||||||
|
hint={<FormattedMessage id='preferences.hints.demetricator' defaultMessage='Decrease social media anxiety by hiding all numbers from the site.' />}
|
||||||
|
>
|
||||||
|
<SettingToggle settings={settings} settingPath={['demetricator']} onChange={onToggleChange} />
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsStore;
|
|
@ -1,116 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { showAlertForError } from 'soapbox/actions/alerts';
|
|
||||||
import { patchMe } from 'soapbox/actions/me';
|
|
||||||
import { FE_NAME, SETTINGS_UPDATE } from 'soapbox/actions/settings';
|
|
||||||
import { Button, Form, FormActions, FormGroup, Textarea } from 'soapbox/components/ui';
|
|
||||||
import Column from 'soapbox/features/ui/components/column';
|
|
||||||
|
|
||||||
const isJSONValid = text => {
|
|
||||||
try {
|
|
||||||
JSON.parse(text);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
heading: { id: 'column.settings_store', defaultMessage: 'Settings store' },
|
|
||||||
hint: { id: 'developers.settings_store.hint', defaultMessage: 'It is possible to directly edit your user settings here. BE CAREFUL! Editing this section can break your account, and you will only be able to recover through the API.' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
return {
|
|
||||||
settingsStore: state.get('settings'),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
|
||||||
@injectIntl
|
|
||||||
class SettingsStore extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
settingsStore: ImmutablePropTypes.map.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
rawJSON: JSON.stringify(this.props.settingsStore, null, 2),
|
|
||||||
jsonValid: true,
|
|
||||||
isLoading: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const { settingsStore } = this.props;
|
|
||||||
|
|
||||||
if (settingsStore !== prevProps.settingsStore) {
|
|
||||||
this.setState({
|
|
||||||
rawJSON: JSON.stringify(settingsStore, null, 2),
|
|
||||||
jsonValid: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEditJSON = ({ target }) => {
|
|
||||||
const rawJSON = target.value;
|
|
||||||
this.setState({ rawJSON, jsonValid: isJSONValid(rawJSON) });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = e => {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
const { rawJSON } = this.state;
|
|
||||||
|
|
||||||
const settings = JSON.parse(rawJSON);
|
|
||||||
|
|
||||||
this.setState({ isLoading: true });
|
|
||||||
dispatch(patchMe({
|
|
||||||
pleroma_settings_store: {
|
|
||||||
[FE_NAME]: settings,
|
|
||||||
},
|
|
||||||
})).then(response => {
|
|
||||||
dispatch({ type: SETTINGS_UPDATE, settings });
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(showAlertForError(error));
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl } = this.props;
|
|
||||||
const { rawJSON, jsonValid, isLoading } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
|
||||||
<Form onSubmit={this.handleSubmit}>
|
|
||||||
<FormGroup
|
|
||||||
hintText={intl.formatMessage(messages.hint)}
|
|
||||||
errors={jsonValid ? [] : ['is invalid']}
|
|
||||||
>
|
|
||||||
<Textarea
|
|
||||||
value={rawJSON}
|
|
||||||
onChange={this.handleEditJSON}
|
|
||||||
disabled={isLoading}
|
|
||||||
rows={12}
|
|
||||||
isCodeEditor
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button theme='primary' type='submit' disabled={!jsonValid || isLoading}>
|
|
||||||
<FormattedMessage id='soapbox_config.save' defaultMessage='Save' />
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -2,13 +2,12 @@ import React from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { getSettings, changeSetting } from 'soapbox/actions/settings';
|
import { changeSetting } from 'soapbox/actions/settings';
|
||||||
import List, { ListItem } from 'soapbox/components/list';
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
import { Form } from 'soapbox/components/ui';
|
import { Form } from 'soapbox/components/ui';
|
||||||
import { SelectDropdown } from 'soapbox/features/forms';
|
import { SelectDropdown } from 'soapbox/features/forms';
|
||||||
import SettingToggle from 'soapbox/features/notifications/components/setting_toggle';
|
import SettingToggle from 'soapbox/features/notifications/components/setting_toggle';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useFeatures, useSettings } from 'soapbox/hooks';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
|
||||||
|
|
||||||
import ThemeToggle from '../ui/components/theme-toggle';
|
import ThemeToggle from '../ui/components/theme-toggle';
|
||||||
|
|
||||||
|
@ -91,9 +90,8 @@ const messages = defineMessages({
|
||||||
const Preferences = () => {
|
const Preferences = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const features = useFeatures();
|
||||||
const features = useAppSelector((state) => getFeatures(state.instance));
|
const settings = useSettings();
|
||||||
const settings = useAppSelector((state) => getSettings(state));
|
|
||||||
|
|
||||||
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>, path: string[]) => {
|
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>, path: string[]) => {
|
||||||
dispatch(changeSetting(path, event.target.value, { showAlert: true }));
|
dispatch(changeSetting(path, event.target.value, { showAlert: true }));
|
||||||
|
@ -159,75 +157,28 @@ const Preferences = () => {
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{features.privacyScopes && <ListItem label={<FormattedMessage id='preferences.fields.privacy_label' defaultMessage='Default post privacy' />}>
|
{features.privacyScopes && (
|
||||||
<SelectDropdown
|
<ListItem label={<FormattedMessage id='preferences.fields.privacy_label' defaultMessage='Default post privacy' />}>
|
||||||
items={defaultPrivacyOptions}
|
<SelectDropdown
|
||||||
defaultValue={settings.get('defaultPrivacy') as string | undefined}
|
items={defaultPrivacyOptions}
|
||||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultPrivacy'])}
|
defaultValue={settings.get('defaultPrivacy') as string | undefined}
|
||||||
/>
|
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultPrivacy'])}
|
||||||
</ListItem>}
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
{features.richText && <ListItem label={<FormattedMessage id='preferences.fields.content_type_label' defaultMessage='Default post format' />}>
|
{features.richText && (
|
||||||
<SelectDropdown
|
<ListItem label={<FormattedMessage id='preferences.fields.content_type_label' defaultMessage='Default post format' />}>
|
||||||
items={defaultContentTypeOptions}
|
<SelectDropdown
|
||||||
defaultValue={settings.get('defaultContentType') as string | undefined}
|
items={defaultContentTypeOptions}
|
||||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultContentType'])}
|
defaultValue={settings.get('defaultContentType') as string | undefined}
|
||||||
/>
|
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultContentType'])}
|
||||||
</ListItem>}
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
{/* <FieldsGroup>
|
|
||||||
<RadioGroup
|
|
||||||
label={<FormattedMessage id='preferences.fields.privacy_label' defaultMessage='Post privacy' />}
|
|
||||||
onChange={this.onDefaultPrivacyChange}
|
|
||||||
>
|
|
||||||
<RadioItem
|
|
||||||
label={<FormattedMessage id='preferences.options.privacy_public' defaultMessage='Public' />}
|
|
||||||
hint={<FormattedMessage id='preferences.hints.privacy_public' defaultMessage='Everyone can see' />}
|
|
||||||
checked={settings.get('defaultPrivacy') === 'public'}
|
|
||||||
value='public'
|
|
||||||
/>
|
|
||||||
<RadioItem
|
|
||||||
label={<FormattedMessage id='preferences.options.privacy_unlisted' defaultMessage='Unlisted' />}
|
|
||||||
hint={<FormattedMessage id='preferences.hints.privacy_unlisted' defaultMessage='Everyone can see, but not listed on public timelines' />}
|
|
||||||
checked={settings.get('defaultPrivacy') === 'unlisted'}
|
|
||||||
value='unlisted'
|
|
||||||
/>
|
|
||||||
<RadioItem
|
|
||||||
label={<FormattedMessage id='preferences.options.privacy_followers_only' defaultMessage='Followers-only' />}
|
|
||||||
hint={<FormattedMessage id='preferences.hints.privacy_followers_only' defaultMessage='Only show to followers' />}
|
|
||||||
checked={settings.get('defaultPrivacy') === 'private'}
|
|
||||||
value='private'
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
</FieldsGroup> */}
|
|
||||||
|
|
||||||
{/* {features.richText && (
|
|
||||||
<FieldsGroup>
|
|
||||||
<RadioGroup
|
|
||||||
label={<FormattedMessage id='preferences.fields.content_type_label' defaultMessage='Post format' />}
|
|
||||||
onChange={onDefaultContentTypeChange}
|
|
||||||
>
|
|
||||||
<RadioItem
|
|
||||||
label={<FormattedMessage id='preferences.options.content_type_plaintext' defaultMessage='Plain text' />}
|
|
||||||
checked={settings.get('defaultContentType') === 'text/plain'}
|
|
||||||
value='text/plain'
|
|
||||||
/>
|
|
||||||
<RadioItem
|
|
||||||
label={<FormattedMessage id='preferences.options.content_type_markdown' defaultMessage='Markdown' />}
|
|
||||||
hint={<FormattedMessage id='preferences.hints.content_type_markdown' defaultMessage='Warning: experimental!' />}
|
|
||||||
checked={settings.get('defaultContentType') === 'text/markdown'}
|
|
||||||
value='text/markdown'
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
</FieldsGroup>
|
|
||||||
)} */}
|
|
||||||
|
|
||||||
<List>
|
<List>
|
||||||
{/* <ListItem label={<FormattedMessage id='preferences.fields.unfollow_modal_label' defaultMessage='Show confirmation dialog before unfollowing someone' />}>
|
|
||||||
<SettingToggle settings={settings} settingPath={['unfollowModal']} onChange={onToggleChange} />
|
|
||||||
</ListItem> */}
|
|
||||||
|
|
||||||
<ListItem label={<FormattedMessage id='preferences.fields.boost_modal_label' defaultMessage='Show confirmation dialog before reposting' />}>
|
<ListItem label={<FormattedMessage id='preferences.fields.boost_modal_label' defaultMessage='Show confirmation dialog before reposting' />}>
|
||||||
<SettingToggle settings={settings} settingPath={['boostModal']} onChange={onToggleChange} />
|
<SettingToggle settings={settings} settingPath={['boostModal']} onChange={onToggleChange} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
@ -235,10 +186,6 @@ const Preferences = () => {
|
||||||
<ListItem label={<FormattedMessage id='preferences.fields.delete_modal_label' defaultMessage='Show confirmation dialog before deleting a post' />}>
|
<ListItem label={<FormattedMessage id='preferences.fields.delete_modal_label' defaultMessage='Show confirmation dialog before deleting a post' />}>
|
||||||
<SettingToggle settings={settings} settingPath={['deleteModal']} onChange={onToggleChange} />
|
<SettingToggle settings={settings} settingPath={['deleteModal']} onChange={onToggleChange} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* <ListItem label={<FormattedMessage id='preferences.fields.missing_description_modal_label' defaultMessage='Show confirmation dialog before sending a post without media descriptions' />}>
|
|
||||||
<SettingToggle settings={settings} settingPath={['missingDescriptionModal']} onChange={onToggleChange} />
|
|
||||||
</ListItem> */}
|
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<List>
|
<List>
|
||||||
|
@ -250,10 +197,6 @@ const Preferences = () => {
|
||||||
<SettingToggle settings={settings} settingPath={['expandSpoilers']} onChange={onToggleChange} />
|
<SettingToggle settings={settings} settingPath={['expandSpoilers']} onChange={onToggleChange} />
|
||||||
</ListItem>}
|
</ListItem>}
|
||||||
|
|
||||||
{/* <ListItem label={<FormattedMessage id='preferences.fields.reduce_motion_label' defaultMessage='Reduce motion in animations' />}>
|
|
||||||
<SettingToggle settings={settings} settingPath={['reduceMotion']} onChange={onToggleChange} />
|
|
||||||
</ListItem> */}
|
|
||||||
|
|
||||||
<ListItem label={<FormattedMessage id='preferences.fields.autoload_timelines_label' defaultMessage='Automatically load new posts when scrolled to the top of the page' />}>
|
<ListItem label={<FormattedMessage id='preferences.fields.autoload_timelines_label' defaultMessage='Automatically load new posts when scrolled to the top of the page' />}>
|
||||||
<SettingToggle settings={settings} settingPath={['autoloadTimelines']} onChange={onToggleChange} />
|
<SettingToggle settings={settings} settingPath={['autoloadTimelines']} onChange={onToggleChange} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
@ -261,32 +204,6 @@ const Preferences = () => {
|
||||||
<ListItem label={<FormattedMessage id='preferences.fields.autoload_more_label' defaultMessage='Automatically load more items when scrolled to the bottom of the page' />}>
|
<ListItem label={<FormattedMessage id='preferences.fields.autoload_more_label' defaultMessage='Automatically load more items when scrolled to the bottom of the page' />}>
|
||||||
<SettingToggle settings={settings} settingPath={['autoloadMore']} onChange={onToggleChange} />
|
<SettingToggle settings={settings} settingPath={['autoloadMore']} onChange={onToggleChange} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* <ListItem label={<FormattedMessage id='preferences.fields.underline_links_label' defaultMessage='Always underline links in posts' />}>
|
|
||||||
<SettingToggle settings={settings} settingPath={['underlineLinks']} onChange={onToggleChange} />
|
|
||||||
</ListItem> */}
|
|
||||||
|
|
||||||
{/* <ListItem label={<FormattedMessage id='preferences.fields.system_font_label' defaultMessage="Use system's default font" />}>
|
|
||||||
<SettingToggle settings={settings} settingPath={['systemFont']} onChange={onToggleChange} />
|
|
||||||
</ListItem> */}
|
|
||||||
|
|
||||||
{/* <div className='dyslexic'>
|
|
||||||
<SettingsCheckbox
|
|
||||||
label={<FormattedMessage id='preferences.fields.dyslexic_font_label' defaultMessage='Dyslexic mode' />}
|
|
||||||
path={['dyslexicFont']}
|
|
||||||
/>
|
|
||||||
</div> */}
|
|
||||||
{/* <SettingsCheckbox
|
|
||||||
label={<FormattedMessage id='preferences.fields.halloween_label' defaultMessage='Halloween mode' />}
|
|
||||||
hint={<FormattedMessage id='preferences.hints.halloween' defaultMessage='Beware: SPOOKY! Supports light/dark toggle.' />}
|
|
||||||
path={['halloween']}
|
|
||||||
/> */}
|
|
||||||
{/* <ListItem
|
|
||||||
label={<FormattedMessage id='preferences.fields.demetricator_label' defaultMessage='Use Demetricator' />}
|
|
||||||
hint={<FormattedMessage id='preferences.hints.demetricator' defaultMessage='Decrease social media anxiety by hiding all numbers from the site.' />}
|
|
||||||
>
|
|
||||||
<SettingToggle settings={settings} settingPath={['demetricator']} onChange={onToggleChange} />
|
|
||||||
</ListItem> */}
|
|
||||||
</List>
|
</List>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -487,7 +487,7 @@ export function CreateApp() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsStore() {
|
export function SettingsStore() {
|
||||||
return import(/* webpackChunkName: "features/developers" */'../../developers/settings_store');
|
return import(/* webpackChunkName: "features/developers" */'../../developers/settings-store');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TestTimeline() {
|
export function TestTimeline() {
|
||||||
|
|
Ładowanie…
Reference in New Issue