Basic custom profile fields
This commit is contained in:
parent
b66b68b37c
commit
cd1a404351
2 changed files with 100 additions and 73 deletions
57
app/soapbox/components/ui/streamfield/streamfield.tsx
Normal file
57
app/soapbox/components/ui/streamfield/streamfield.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import React from 'react';
|
||||
|
||||
import Button from '../button/button';
|
||||
import Stack from '../stack/stack';
|
||||
|
||||
interface IStreamfield {
|
||||
values: any[],
|
||||
onAddItem?: () => void,
|
||||
onChange: (values: any[]) => void,
|
||||
component: React.ComponentType<{ onChange: (value: any) => void, value: any }>,
|
||||
maxItems?: number,
|
||||
}
|
||||
|
||||
/** List of inputs that can be added or removed. */
|
||||
const Streamfield: React.FC<IStreamfield> = ({
|
||||
values,
|
||||
onAddItem,
|
||||
onChange,
|
||||
component: Component,
|
||||
maxItems = Infinity,
|
||||
}) => {
|
||||
|
||||
const handleChange = (i: number) => {
|
||||
return (value: any) => {
|
||||
const newData = [...values];
|
||||
newData[i] = value;
|
||||
onChange(newData);
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack space={4}>
|
||||
|
||||
<Stack>
|
||||
{values.map((value, i) => {
|
||||
return (
|
||||
<Component key={i} onChange={handleChange(i)} value={value} />
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
|
||||
{onAddItem && (
|
||||
<Button
|
||||
icon={require('@tabler/icons/icons/plus.svg')}
|
||||
onClick={onAddItem}
|
||||
disabled={values.length >= maxItems}
|
||||
theme='ghost'
|
||||
block
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Streamfield;
|
|
@ -7,11 +7,12 @@ import snackbar from 'soapbox/actions/snackbar';
|
|||
import {
|
||||
Checkbox,
|
||||
} from 'soapbox/features/forms';
|
||||
import { useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks';
|
||||
import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks';
|
||||
import { normalizeAccount } from 'soapbox/normalizers';
|
||||
import resizeImage from 'soapbox/utils/resize_image';
|
||||
|
||||
import { Button, Column, Form, FormActions, FormGroup, Input, Textarea } from '../../components/ui';
|
||||
import { Button, Column, Form, FormActions, FormGroup, Input, Textarea, HStack } from '../../components/ui';
|
||||
import Streamfield from '../../components/ui/streamfield/streamfield';
|
||||
|
||||
import ProfilePreview from './components/profile_preview';
|
||||
|
||||
|
@ -40,13 +41,6 @@ const messages = defineMessages({
|
|||
cancel: { id: 'common.cancel', defaultMessage: 'Cancel' },
|
||||
});
|
||||
|
||||
// /** Forces fields to be maxFields size, filling empty values. */
|
||||
// const normalizeFields = (fields, maxFields: number) => (
|
||||
// ImmutableList(fields).setSize(Math.max(fields.size, maxFields)).map(field =>
|
||||
// field ? field : ImmutableMap({ name: '', value: '' }),
|
||||
// )
|
||||
// );
|
||||
|
||||
/**
|
||||
* Profile metadata `name` and `value`.
|
||||
* (By default, max 4 fields and 255 characters per property/value)
|
||||
|
@ -121,7 +115,7 @@ const accountToCredentials = (account: Account): AccountCredentials => {
|
|||
display_name: account.display_name,
|
||||
note: account.source.get('note'),
|
||||
locked: account.locked,
|
||||
fields_attributes: [...account.source.get<Iterable<AccountCredentialsField>>('fields', [])],
|
||||
fields_attributes: [...account.source.get<Iterable<AccountCredentialsField>>('fields', []).toJS()],
|
||||
stranger_notifications: account.getIn(['pleroma', 'notification_settings', 'block_from_strangers']) === true,
|
||||
accepts_email_list: account.getIn(['pleroma', 'accepts_email_list']) === true,
|
||||
hide_followers: hideNetwork,
|
||||
|
@ -134,6 +128,27 @@ const accountToCredentials = (account: Account): AccountCredentials => {
|
|||
};
|
||||
};
|
||||
|
||||
interface IProfileField {
|
||||
value: AccountCredentialsField,
|
||||
onChange: (field: AccountCredentialsField) => void,
|
||||
}
|
||||
|
||||
const ProfileField: React.FC<IProfileField> = ({ value, onChange }) => {
|
||||
|
||||
const handleChange = (key: string): React.ChangeEventHandler<HTMLInputElement> => {
|
||||
return e => {
|
||||
onChange({ ...value, [key]: e.currentTarget.value });
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<HStack space={2} grow>
|
||||
<Input type='text' value={value.name} onChange={handleChange('name')} />
|
||||
<Input type='text' value={value.value} onChange={handleChange('value')} />
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
/** Edit profile page. */
|
||||
const EditProfile: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
|
@ -141,7 +156,7 @@ const EditProfile: React.FC = () => {
|
|||
|
||||
const account = useOwnAccount();
|
||||
const features = useFeatures();
|
||||
// const maxFields = useAppSelector(state => state.instance.pleroma.getIn(['metadata', 'fields_limits', 'max_fields'], 4) as number);
|
||||
const maxFields = useAppSelector(state => state.instance.pleroma.getIn(['metadata', 'fields_limits', 'max_fields'], 4) as number);
|
||||
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<AccountCredentials>({});
|
||||
|
@ -229,27 +244,15 @@ const EditProfile: React.FC = () => {
|
|||
};
|
||||
};
|
||||
|
||||
// handleFieldChange = (i, key) => {
|
||||
// return (e) => {
|
||||
// this.setState({
|
||||
// fields: this.state.fields.setIn([i, key], e.target.value),
|
||||
// });
|
||||
// };
|
||||
// };
|
||||
//
|
||||
// handleAddField = () => {
|
||||
// this.setState({
|
||||
// fields: this.state.fields.push(ImmutableMap({ name: '', value: '' })),
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// handleDeleteField = i => {
|
||||
// return () => {
|
||||
// this.setState({
|
||||
// fields: normalizeFields(this.state.fields.delete(i), Math.min(this.props.maxFields, 4)),
|
||||
// });
|
||||
// };
|
||||
// };
|
||||
const handleFieldsChange = (fields: AccountCredentialsField[]) => {
|
||||
updateData('fields_attributes', fields);
|
||||
};
|
||||
|
||||
const handleAddField = () => {
|
||||
const oldFields = data.fields_attributes || [];
|
||||
const fields = [...oldFields, { name: '', value: '' }];
|
||||
updateData('fields_attributes', fields);
|
||||
};
|
||||
|
||||
/** Memoized avatar preview URL. */
|
||||
const avatarUrl = useMemo(() => {
|
||||
|
@ -412,47 +415,14 @@ const EditProfile: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* </FieldsGroup> */}
|
||||
{/*<FieldsGroup>
|
||||
<div className='fields-row__column fields-group'>
|
||||
<div className='input with_block_label'>
|
||||
<label><FormattedMessage id='edit_profile.fields.meta_fields_label' defaultMessage='Profile metadata' /></label>
|
||||
<span className='hint'>
|
||||
<FormattedMessage id='edit_profile.hints.meta_fields' defaultMessage='You can have up to {count, plural, one {# item} other {# items}} displayed as a table on your profile' values={{ count: maxFields }} />
|
||||
</span>
|
||||
{
|
||||
this.state.fields.map((field, i) => (
|
||||
<div className='row' key={i}>
|
||||
<TextInput
|
||||
placeholder={intl.formatMessage(messages.metaFieldLabel)}
|
||||
value={field.get('name')}
|
||||
onChange={this.handleFieldChange(i, 'name')}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={intl.formatMessage(messages.metaFieldContent)}
|
||||
value={field.get('value')}
|
||||
onChange={this.handleFieldChange(i, 'value')}
|
||||
/>
|
||||
{
|
||||
this.state.fields.size > 4 && <Icon className='delete-field' src={require('@tabler/icons/icons/circle-x.svg')} onClick={this.handleDeleteField(i)} />
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{
|
||||
this.state.fields.size < maxFields && (
|
||||
<div className='actions add-row'>
|
||||
<div name='button' type='button' role='presentation' className='btn button button-secondary' onClick={this.handleAddField}>
|
||||
<Icon src={require('@tabler/icons/icons/circle-plus.svg')} />
|
||||
<FormattedMessage id='edit_profile.meta_fields.add' defaultMessage='Add new item' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</FieldsGroup>*/}
|
||||
{/* </fieldset> */}
|
||||
<Streamfield
|
||||
values={data.fields_attributes || []}
|
||||
onChange={handleFieldsChange}
|
||||
onAddItem={handleAddField}
|
||||
component={ProfileField}
|
||||
maxItems={maxFields}
|
||||
/>
|
||||
|
||||
<FormActions>
|
||||
<Button to='/settings' theme='ghost'>
|
||||
{intl.formatMessage(messages.cancel)}
|
||||
|
|
Loading…
Reference in a new issue