Normalize SoapboxConfig
This commit is contained in:
parent
2c0ce084c3
commit
483b28988f
8 changed files with 146 additions and 100 deletions
|
@ -2,6 +2,7 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { getHost } from 'soapbox/actions/instance';
|
import { getHost } from 'soapbox/actions/instance';
|
||||||
|
import { normalizeSoapboxConfig } from 'soapbox/normalizers';
|
||||||
import KVStore from 'soapbox/storage/kv_store';
|
import KVStore from 'soapbox/storage/kv_store';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
|
@ -33,79 +34,10 @@ const allowedEmojiRGI = ImmutableList([
|
||||||
'😩',
|
'😩',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const year = new Date().getFullYear();
|
|
||||||
|
|
||||||
export const makeDefaultConfig = features => {
|
export const makeDefaultConfig = features => {
|
||||||
return ImmutableMap({
|
return ImmutableMap({
|
||||||
logo: '',
|
|
||||||
banner: '',
|
|
||||||
brandColor: '', // Empty
|
|
||||||
accentColor: '',
|
|
||||||
colors: ImmutableMap({
|
|
||||||
gray: ImmutableMap({
|
|
||||||
50: '#f9fafb',
|
|
||||||
100: '#f3f4f6',
|
|
||||||
200: '#e5e7eb',
|
|
||||||
300: '#d1d5db',
|
|
||||||
400: '#9ca3af',
|
|
||||||
500: '#6b7280',
|
|
||||||
600: '#4b5563',
|
|
||||||
700: '#374151',
|
|
||||||
800: '#1f2937',
|
|
||||||
900: '#111827',
|
|
||||||
}),
|
|
||||||
success: ImmutableMap({
|
|
||||||
50: '#f0fdf4',
|
|
||||||
100: '#dcfce7',
|
|
||||||
200: '#bbf7d0',
|
|
||||||
300: '#86efac',
|
|
||||||
400: '#4ade80',
|
|
||||||
500: '#22c55e',
|
|
||||||
600: '#16a34a',
|
|
||||||
700: '#15803d',
|
|
||||||
800: '#166534',
|
|
||||||
900: '#14532d',
|
|
||||||
}),
|
|
||||||
danger: ImmutableMap({
|
|
||||||
50: '#fef2f2',
|
|
||||||
100: '#fee2e2',
|
|
||||||
200: '#fecaca',
|
|
||||||
300: '#fca5a5',
|
|
||||||
400: '#f87171',
|
|
||||||
500: '#ef4444',
|
|
||||||
600: '#dc2626',
|
|
||||||
700: '#b91c1c',
|
|
||||||
800: '#991b1b',
|
|
||||||
900: '#7f1d1d',
|
|
||||||
}),
|
|
||||||
'gradient-purple': '#b8a3f9',
|
|
||||||
'gradient-blue': '#9bd5ff',
|
|
||||||
'sea-blue': '#2feecc',
|
|
||||||
}),
|
|
||||||
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,
|
allowedEmoji: features.emojiReactsRGI ? allowedEmojiRGI : allowedEmoji,
|
||||||
verifiedIcon: '',
|
|
||||||
verifiedCanEditName: false,
|
|
||||||
displayFqn: Boolean(features.federating),
|
displayFqn: Boolean(features.federating),
|
||||||
cryptoAddresses: ImmutableList(),
|
|
||||||
cryptoDonatePanel: ImmutableMap({
|
|
||||||
limit: 1,
|
|
||||||
}),
|
|
||||||
aboutPages: ImmutableMap(),
|
|
||||||
betaPages: ImmutableMap(),
|
|
||||||
mobilePages: ImmutableMap(),
|
|
||||||
authenticatedProfile: true,
|
|
||||||
singleUserMode: false,
|
|
||||||
singleUserModeProfile: '',
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,7 +46,7 @@ export const getSoapboxConfig = createSelector([
|
||||||
state => getFeatures(state.get('instance')),
|
state => getFeatures(state.get('instance')),
|
||||||
], (soapbox, features) => {
|
], (soapbox, features) => {
|
||||||
const defaultConfig = makeDefaultConfig(features);
|
const defaultConfig = makeDefaultConfig(features);
|
||||||
return soapbox.mergeDeepWith((o, n) => o || n, defaultConfig);
|
return normalizeSoapboxConfig(soapbox).merge(defaultConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
export function rememberSoapboxConfig(host) {
|
export function rememberSoapboxConfig(host) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface ISiteWallet {
|
||||||
|
|
||||||
const SiteWallet: React.FC<ISiteWallet> = ({ limit }): JSX.Element => {
|
const SiteWallet: React.FC<ISiteWallet> = ({ limit }): JSX.Element => {
|
||||||
const addresses: ImmutableList<Address> =
|
const addresses: ImmutableList<Address> =
|
||||||
useSoapboxConfig().get('cryptoAddresses').map(normalizeAddress);
|
useSoapboxConfig().cryptoAddresses.map(normalizeAddress);
|
||||||
|
|
||||||
const coinList = typeof limit === 'number' ? addresses.take(limit) : addresses;
|
const coinList = typeof limit === 'number' ? addresses.take(limit) : addresses;
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ import { connect } from 'react-redux';
|
||||||
import { updateConfig } from 'soapbox/actions/admin';
|
import { updateConfig } from 'soapbox/actions/admin';
|
||||||
import { uploadMedia } from 'soapbox/actions/media';
|
import { uploadMedia } from 'soapbox/actions/media';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { makeDefaultConfig } from 'soapbox/actions/soapbox';
|
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
|
import { Column } from 'soapbox/components/ui';
|
||||||
import {
|
import {
|
||||||
SimpleForm,
|
SimpleForm,
|
||||||
FieldsGroup,
|
FieldsGroup,
|
||||||
|
@ -26,10 +26,9 @@ import {
|
||||||
} from 'soapbox/features/forms';
|
} from 'soapbox/features/forms';
|
||||||
import ThemeToggle from 'soapbox/features/ui/components/theme_toggle';
|
import ThemeToggle from 'soapbox/features/ui/components/theme_toggle';
|
||||||
import { isMobile } from 'soapbox/is_mobile';
|
import { isMobile } from 'soapbox/is_mobile';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { normalizeSoapboxConfig } from 'soapbox/normalizers';
|
||||||
|
|
||||||
import Accordion from '../ui/components/accordion';
|
import Accordion from '../ui/components/accordion';
|
||||||
import Column from '../ui/components/column';
|
|
||||||
|
|
||||||
import IconPickerDropdown from './components/icon_picker_dropdown';
|
import IconPickerDropdown from './components/icon_picker_dropdown';
|
||||||
import SitePreview from './components/site_preview';
|
import SitePreview from './components/site_preview';
|
||||||
|
@ -71,11 +70,8 @@ const templates = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const instance = state.get('instance');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
soapbox: state.get('soapbox'),
|
initialData: state.soapbox,
|
||||||
features: getFeatures(instance),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,37 +80,36 @@ export default @connect(mapStateToProps)
|
||||||
class SoapboxConfig extends ImmutablePureComponent {
|
class SoapboxConfig extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
soapbox: ImmutablePropTypes.map.isRequired,
|
initialData: ImmutablePropTypes.map.isRequired,
|
||||||
features: PropTypes.object.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
soapbox: this.props.soapbox,
|
data: this.props.initialData,
|
||||||
jsonEditorExpanded: false,
|
jsonEditorExpanded: false,
|
||||||
rawJSON: JSON.stringify(this.props.soapbox, null, 2),
|
rawJSON: JSON.stringify(this.props.soapbox, null, 2),
|
||||||
jsonValid: true,
|
jsonValid: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfig = (path, value) => {
|
setConfig = (path, value) => {
|
||||||
const { soapbox } = this.state;
|
const { data } = this.state;
|
||||||
const config = soapbox.setIn(path, value);
|
const newData = data.setIn(path, value);
|
||||||
this.setState({ soapbox: config, jsonValid: true });
|
this.setState({ data: newData, jsonValid: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
putConfig = config => {
|
putConfig = data => {
|
||||||
this.setState({ soapbox: config, jsonValid: true });
|
this.setState({ data, jsonValid: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
getParams = () => {
|
getParams = () => {
|
||||||
const { soapbox } = this.state;
|
const { data } = this.state;
|
||||||
return [{
|
return [{
|
||||||
group: ':pleroma',
|
group: ':pleroma',
|
||||||
key: ':frontend_configurations',
|
key: ':frontend_configurations',
|
||||||
value: [{
|
value: [{
|
||||||
tuple: [':soapbox_fe', soapbox.toJS()],
|
tuple: [':soapbox_fe', data.toJS()],
|
||||||
}],
|
}],
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -158,8 +153,8 @@ class SoapboxConfig extends ImmutablePureComponent {
|
||||||
|
|
||||||
handleDeleteItem = path => {
|
handleDeleteItem = path => {
|
||||||
return e => {
|
return e => {
|
||||||
const soapbox = this.state.soapbox.deleteIn(path);
|
const data = this.state.data.deleteIn(path);
|
||||||
this.setState({ soapbox });
|
this.setState({ data });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -195,20 +190,18 @@ class SoapboxConfig extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
getSoapboxConfig = () => {
|
getSoapboxConfig = () => {
|
||||||
const { features } = this.props;
|
return normalizeSoapboxConfig(this.state.data);
|
||||||
const { soapbox } = this.state;
|
|
||||||
return makeDefaultConfig(features).mergeDeep(soapbox);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleJSONEditor = (value) => this.setState({ jsonEditorExpanded: value });
|
toggleJSONEditor = (value) => this.setState({ jsonEditorExpanded: value });
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (prevProps.soapbox !== this.props.soapbox) {
|
if (prevProps.initialData !== this.props.initialData) {
|
||||||
this.putConfig(this.props.soapbox);
|
this.putConfig(this.props.initialData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevState.soapbox !== this.state.soapbox) {
|
if (prevState.data !== this.state.data) {
|
||||||
this.setState({ rawJSON: JSON.stringify(this.state.soapbox, null, 2) });
|
this.setState({ rawJSON: JSON.stringify(this.state.data, null, 2) });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevState.rawJSON !== this.state.rawJSON) {
|
if (prevState.rawJSON !== this.state.rawJSON) {
|
||||||
|
@ -226,7 +219,7 @@ class SoapboxConfig extends ImmutablePureComponent {
|
||||||
const soapbox = this.getSoapboxConfig();
|
const soapbox = this.getSoapboxConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='cog' heading={intl.formatMessage(messages.heading)}>
|
<Column label={intl.formatMessage(messages.heading)}>
|
||||||
<SimpleForm onSubmit={this.handleSubmit}>
|
<SimpleForm onSubmit={this.handleSubmit}>
|
||||||
<fieldset disabled={this.state.isLoading}>
|
<fieldset disabled={this.state.isLoading}>
|
||||||
<SitePreview soapbox={soapbox} />
|
<SitePreview soapbox={soapbox} />
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
import type { Map as ImmutableMap } from 'immutable';
|
import type { SoapboxConfig } from 'soapbox/types/soapbox';
|
||||||
|
|
||||||
/** Get the Soapbox config from the store */
|
/** Get the Soapbox config from the store */
|
||||||
export const useSoapboxConfig = (): ImmutableMap<string, any> => {
|
export const useSoapboxConfig = (): SoapboxConfig => {
|
||||||
return useAppSelector((state) => getSoapboxConfig(state));
|
return useAppSelector((state) => getSoapboxConfig(state));
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,3 +7,5 @@ export { MentionRecord, normalizeMention } from './mention';
|
||||||
export { NotificationRecord, normalizeNotification } from './notification';
|
export { NotificationRecord, normalizeNotification } from './notification';
|
||||||
export { PollRecord, PollOptionRecord, normalizePoll } from './poll';
|
export { PollRecord, PollOptionRecord, normalizePoll } from './poll';
|
||||||
export { StatusRecord, normalizeStatus } from './status';
|
export { StatusRecord, normalizeStatus } from './status';
|
||||||
|
|
||||||
|
export { SoapboxConfigRecord, normalizeSoapboxConfig } from './soapbox/soapbox_config';
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Record as ImmutableRecord } from 'immutable';
|
||||||
|
|
||||||
|
import { normalizeSoapboxConfig } from '../soapbox_config';
|
||||||
|
|
||||||
|
describe('normalizeSoapboxConfig()', () => {
|
||||||
|
it('adds base fields', () => {
|
||||||
|
const result = normalizeSoapboxConfig({});
|
||||||
|
expect(result.brandColor).toBe('');
|
||||||
|
expect(ImmutableRecord.isRecord(result)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
103
app/soapbox/normalizers/soapbox/soapbox_config.ts
Normal file
103
app/soapbox/normalizers/soapbox/soapbox_config.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import {
|
||||||
|
Map as ImmutableMap,
|
||||||
|
List as ImmutableList,
|
||||||
|
Record as ImmutableRecord,
|
||||||
|
fromJS,
|
||||||
|
} from 'immutable';
|
||||||
|
|
||||||
|
const DEFAULT_COLORS = ImmutableMap({
|
||||||
|
gray: ImmutableMap({
|
||||||
|
50: '#f9fafb',
|
||||||
|
100: '#f3f4f6',
|
||||||
|
200: '#e5e7eb',
|
||||||
|
300: '#d1d5db',
|
||||||
|
400: '#9ca3af',
|
||||||
|
500: '#6b7280',
|
||||||
|
600: '#4b5563',
|
||||||
|
700: '#374151',
|
||||||
|
800: '#1f2937',
|
||||||
|
900: '#111827',
|
||||||
|
}),
|
||||||
|
success: ImmutableMap({
|
||||||
|
50: '#f0fdf4',
|
||||||
|
100: '#dcfce7',
|
||||||
|
200: '#bbf7d0',
|
||||||
|
300: '#86efac',
|
||||||
|
400: '#4ade80',
|
||||||
|
500: '#22c55e',
|
||||||
|
600: '#16a34a',
|
||||||
|
700: '#15803d',
|
||||||
|
800: '#166534',
|
||||||
|
900: '#14532d',
|
||||||
|
}),
|
||||||
|
danger: ImmutableMap({
|
||||||
|
50: '#fef2f2',
|
||||||
|
100: '#fee2e2',
|
||||||
|
200: '#fecaca',
|
||||||
|
300: '#fca5a5',
|
||||||
|
400: '#f87171',
|
||||||
|
500: '#ef4444',
|
||||||
|
600: '#dc2626',
|
||||||
|
700: '#b91c1c',
|
||||||
|
800: '#991b1b',
|
||||||
|
900: '#7f1d1d',
|
||||||
|
}),
|
||||||
|
'gradient-purple': '#b8a3f9',
|
||||||
|
'gradient-blue': '#9bd5ff',
|
||||||
|
'sea-blue': '#2feecc',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SoapboxConfigRecord = ImmutableRecord({
|
||||||
|
logo: '',
|
||||||
|
banner: '',
|
||||||
|
brandColor: '', // Empty
|
||||||
|
accentColor: '',
|
||||||
|
colors: ImmutableMap(),
|
||||||
|
copyright: `♥${new Date().getFullYear()}. Copying is an act of love. Please copy and share.`,
|
||||||
|
customCss: ImmutableList<string>(),
|
||||||
|
defaultSettings: ImmutableMap(),
|
||||||
|
extensions: ImmutableMap(),
|
||||||
|
greentext: false,
|
||||||
|
promoPanel: ImmutableMap({
|
||||||
|
items: ImmutableList(),
|
||||||
|
}),
|
||||||
|
navlinks: ImmutableMap({
|
||||||
|
homeFooter: ImmutableList(),
|
||||||
|
}),
|
||||||
|
allowedEmoji: ImmutableList<string>([
|
||||||
|
'👍',
|
||||||
|
'❤️',
|
||||||
|
'😆',
|
||||||
|
'😮',
|
||||||
|
'😢',
|
||||||
|
'😩',
|
||||||
|
]),
|
||||||
|
verifiedIcon: '',
|
||||||
|
verifiedCanEditName: false,
|
||||||
|
displayFqn: true,
|
||||||
|
cryptoAddresses: ImmutableList<ImmutableMap<string, any>>(),
|
||||||
|
cryptoDonatePanel: ImmutableMap({
|
||||||
|
limit: 1,
|
||||||
|
}),
|
||||||
|
aboutPages: ImmutableMap(),
|
||||||
|
betaPages: ImmutableMap(),
|
||||||
|
mobilePages: ImmutableMap(),
|
||||||
|
authenticatedProfile: true,
|
||||||
|
singleUserMode: false,
|
||||||
|
singleUserModeProfile: '',
|
||||||
|
}, 'SoapboxConfig');
|
||||||
|
|
||||||
|
type SoapboxConfigMap = ImmutableMap<string, any>;
|
||||||
|
|
||||||
|
const normalizeColors = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => {
|
||||||
|
const colors = DEFAULT_COLORS.mergeDeep(soapboxConfig.get('colors'));
|
||||||
|
return soapboxConfig.set('colors', colors);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const normalizeSoapboxConfig = (soapboxConfig: Record<string, any>) => {
|
||||||
|
return SoapboxConfigRecord(
|
||||||
|
ImmutableMap(fromJS(soapboxConfig)).withMutations(soapboxConfig => {
|
||||||
|
normalizeColors(soapboxConfig);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
5
app/soapbox/types/soapbox.ts
Normal file
5
app/soapbox/types/soapbox.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { SoapboxConfigRecord } from 'soapbox/normalizers';
|
||||||
|
|
||||||
|
type SoapboxConfig = ReturnType<typeof SoapboxConfigRecord>;
|
||||||
|
|
||||||
|
export { SoapboxConfig };
|
Loading…
Reference in a new issue