diff options
Diffstat (limited to 'web')
| -rw-r--r-- | web/source/package.json | 1 | ||||
| -rw-r--r-- | web/source/settings/admin/emoji/local/new-emoji.js | 7 | ||||
| -rw-r--r-- | web/source/settings/lib/form/context.jsx | 33 | ||||
| -rw-r--r-- | web/source/settings/lib/form/field-array.jsx | 65 | ||||
| -rw-r--r-- | web/source/settings/lib/form/get-form-mutations.js | 47 | ||||
| -rw-r--r-- | web/source/settings/lib/form/index.js | 1 | ||||
| -rw-r--r-- | web/source/settings/lib/form/submit.js | 27 | ||||
| -rw-r--r-- | web/source/settings/lib/query/base.js | 22 | ||||
| -rw-r--r-- | web/source/settings/style.css | 11 | ||||
| -rw-r--r-- | web/source/settings/user/profile.js | 64 | ||||
| -rw-r--r-- | web/source/yarn.lock | 5 | 
11 files changed, 239 insertions, 44 deletions
| diff --git a/web/source/package.json b/web/source/package.json index bd082b6e7..abdb46159 100644 --- a/web/source/package.json +++ b/web/source/package.json @@ -21,6 +21,7 @@      "match-sorter": "^6.3.1",      "modern-normalize": "^1.1.0",      "nanoid": "^4.0.0", +    "object-to-formdata": "^4.4.2",      "papaparse": "^5.3.2",      "photoswipe": "^5.3.3",      "photoswipe-dynamic-caption-plugin": "^1.2.7", diff --git a/web/source/settings/admin/emoji/local/new-emoji.js b/web/source/settings/admin/emoji/local/new-emoji.js index 76f90b462..de796fe30 100644 --- a/web/source/settings/admin/emoji/local/new-emoji.js +++ b/web/source/settings/admin/emoji/local/new-emoji.js @@ -42,9 +42,14 @@ const MutationButton = require("../../../components/form/mutation-button");  module.exports = function NewEmojiForm() {  	const shortcode = useShortcode(); +	const { data: instance } = query.useInstanceQuery(); +	const emojiMaxSize = React.useMemo(() => { +		return instance?.configuration?.emojis?.emoji_size_limit ?? 50 * 1024; +	}, [instance]); +  	const image = useFileInput("image", {  		withPreview: true, -		maxSize: 50 * 1024 // TODO: get from instance api? +		maxSize: emojiMaxSize  	});  	const category = useComboBoxInput("category"); diff --git a/web/source/settings/lib/form/context.jsx b/web/source/settings/lib/form/context.jsx new file mode 100644 index 000000000..b25bb11b7 --- /dev/null +++ b/web/source/settings/lib/form/context.jsx @@ -0,0 +1,33 @@ +/* +	GoToSocial +	Copyright (C) GoToSocial Authors admin@gotosocial.org +	SPDX-License-Identifier: AGPL-3.0-or-later + +	This program is free software: you can redistribute it and/or modify +	it under the terms of the GNU Affero General Public License as published by +	the Free Software Foundation, either version 3 of the License, or +	(at your option) any later version. + +	This program is distributed in the hope that it will be useful, +	but WITHOUT ANY WARRANTY; without even the implied warranty of +	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +	GNU Affero General Public License for more details. + +	You should have received a copy of the GNU Affero General Public License +	along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +"use strict"; + +const React = require("react"); + +const FormContext = React.createContext({}); + +module.exports = { +	FormContext, +	useWithFormContext(index, form) { +		const formContainer = React.useContext(FormContext); +		formContainer[index] = form; +		return form; +	} +};
\ No newline at end of file diff --git a/web/source/settings/lib/form/field-array.jsx b/web/source/settings/lib/form/field-array.jsx new file mode 100644 index 000000000..beea0bc9b --- /dev/null +++ b/web/source/settings/lib/form/field-array.jsx @@ -0,0 +1,65 @@ +/* +	GoToSocial +	Copyright (C) GoToSocial Authors admin@gotosocial.org +	SPDX-License-Identifier: AGPL-3.0-or-later + +	This program is free software: you can redistribute it and/or modify +	it under the terms of the GNU Affero General Public License as published by +	the Free Software Foundation, either version 3 of the License, or +	(at your option) any later version. + +	This program is distributed in the hope that it will be useful, +	but WITHOUT ANY WARRANTY; without even the implied warranty of +	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +	GNU Affero General Public License for more details. + +	You should have received a copy of the GNU Affero General Public License +	along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +"use strict"; + +const React = require("react"); + +const getFormMutations = require("./get-form-mutations"); + +function parseFields(entries, length) { +	const fields = []; + +	for (let i = 0; i < length; i++) { +		if (entries[i] != undefined) { +			fields[i] = Object.assign({}, entries[i]); +		} else { +			fields[i] = {}; +		} +	} + +	return fields; +} + +module.exports = function useArrayInput({ name, _Name }, { initialValue, length = 0 }) { +	const fields = React.useRef({}); + +	const value = React.useMemo(() => parseFields(initialValue, length), [initialValue, length]); + +	return { +		name, +		value, +		ctx: fields.current, +		maxLength: length, +		selectedValues() { +			// if any form field changed, we need to re-send everything +			const hasUpdate = Object.values(fields.current).some((fieldSet) => { +				const { updatedFields } = getFormMutations(fieldSet, { changedOnly: true }); +				return updatedFields.length > 0; +			}); +			if (hasUpdate) { +				return Object.values(fields.current).map((fieldSet) => { +					return getFormMutations(fieldSet, { changedOnly: false }).mutationData; +				}); +			} else { +				return []; +			} +		} +	}; +};
\ No newline at end of file diff --git a/web/source/settings/lib/form/get-form-mutations.js b/web/source/settings/lib/form/get-form-mutations.js new file mode 100644 index 000000000..6bdc3e4cd --- /dev/null +++ b/web/source/settings/lib/form/get-form-mutations.js @@ -0,0 +1,47 @@ +/* +	GoToSocial +	Copyright (C) GoToSocial Authors admin@gotosocial.org +	SPDX-License-Identifier: AGPL-3.0-or-later + +	This program is free software: you can redistribute it and/or modify +	it under the terms of the GNU Affero General Public License as published by +	the Free Software Foundation, either version 3 of the License, or +	(at your option) any later version. + +	This program is distributed in the hope that it will be useful, +	but WITHOUT ANY WARRANTY; without even the implied warranty of +	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +	GNU Affero General Public License for more details. + +	You should have received a copy of the GNU Affero General Public License +	along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +"use strict"; + +const syncpipe = require("syncpipe"); + +module.exports = function getFormMutations(form, { changedOnly }) { +	let updatedFields = []; +	return { +		updatedFields, +		mutationData: syncpipe(form, [ +			(_) => Object.values(_), +			(_) => _.map((field) => { +				if (field.selectedValues != undefined) { +					let selected = field.selectedValues(); +					if (!changedOnly || selected.length > 0) { +						updatedFields.push(field); +						return [field.name, selected]; +					} +				} else if (!changedOnly || field.hasChanged()) { +					updatedFields.push(field); +					return [field.name, field.value]; +				} +				return null; +			}), +			(_) => _.filter((value) => value != null), +			(_) => Object.fromEntries(_) +		]) +	}; +};
\ No newline at end of file diff --git a/web/source/settings/lib/form/index.js b/web/source/settings/lib/form/index.js index 1bdb2a6d4..3d5f5238b 100644 --- a/web/source/settings/lib/form/index.js +++ b/web/source/settings/lib/form/index.js @@ -74,6 +74,7 @@ module.exports = {  	useRadioInput: makeHook(require("./radio")),  	useComboBoxInput: makeHook(require("./combo-box")),  	useCheckListInput: makeHook(require("./check-list")), +	useFieldArrayInput: makeHook(require("./field-array")),  	useValue: function (name, value) {  		return {  			name, diff --git a/web/source/settings/lib/form/submit.js b/web/source/settings/lib/form/submit.js index ee30df8a2..2d1d42b3a 100644 --- a/web/source/settings/lib/form/submit.js +++ b/web/source/settings/lib/form/submit.js @@ -21,7 +21,7 @@  const Promise = require("bluebird");  const React = require("react"); -const syncpipe = require("syncpipe"); +const getFormMutations = require("./get-form-mutations");  module.exports = function useFormSubmit(form, mutationQuery, { changedOnly = true, onFinish } = {}) {  	if (!Array.isArray(mutationQuery)) { @@ -44,25 +44,12 @@ module.exports = function useFormSubmit(form, mutationQuery, { changedOnly = tru  			}  			usedAction.current = action;  			// transform the field definitions into an object with just their values  -			let updatedFields = []; -			const mutationData = syncpipe(form, [ -				(_) => Object.values(_), -				(_) => _.map((field) => { -					if (field.selectedValues != undefined) { -						let selected = field.selectedValues(); -						if (!changedOnly || selected.length > 0) { -							updatedFields.push(field); -							return [field.name, selected]; -						} -					} else if (!changedOnly || field.hasChanged()) { -						updatedFields.push(field); -						return [field.name, field.value]; -					} -					return null; -				}), -				(_) => _.filter((value) => value != null), -				(_) => Object.fromEntries(_) -			]); + +			const { mutationData, updatedFields } = getFormMutations(form, { changedOnly }); + +			if (updatedFields.length == 0) { +				return; +			}  			mutationData.action = action; diff --git a/web/source/settings/lib/query/base.js b/web/source/settings/lib/query/base.js index f880853d2..653fc449b 100644 --- a/web/source/settings/lib/query/base.js +++ b/web/source/settings/lib/query/base.js @@ -20,23 +20,7 @@  "use strict";  const { createApi, fetchBaseQuery } = require("@reduxjs/toolkit/query/react"); -const { isPlainObject } = require("is-plain-object"); - -function convertToForm(obj) { -	const formData = new FormData(); -	Object.entries(obj).forEach(([key, val]) => { -		if (isPlainObject(val)) { -			Object.entries(val).forEach(([key2, val2]) => { -				if (val2 != undefined) { -					formData.set(`${key}[${key2}]`, val2); -				} -			}); -		} else if (val != undefined) { -			formData.set(key, val); -		} -	}); -	return formData; -} +const { serialize: serializeForm } = require("object-to-formdata");  function instanceBasedQuery(args, api, extraOptions) {  	const state = api.getState(); @@ -55,7 +39,9 @@ function instanceBasedQuery(args, api, extraOptions) {  	if (args.asForm) {  		delete args.asForm; -		args.body = convertToForm(args.body); +		args.body = serializeForm(args.body, { +			indices: true, // Array indices, for profile fields +		});  	}  	return fetchBaseQuery({ diff --git a/web/source/settings/style.css b/web/source/settings/style.css index 9392b76a5..84878728d 100644 --- a/web/source/settings/style.css +++ b/web/source/settings/style.css @@ -439,6 +439,17 @@ section.with-sidebar > div, section.with-sidebar > form {  			}  		}  	} + +	.fields { +		display: flex; +		flex-direction: column; +		gap: 0.5rem; +		 +		.entry { +			display: flex; +			gap: 0.5rem; +		} +	}  }  form { diff --git a/web/source/settings/user/profile.js b/web/source/settings/user/profile.js index 4b39d1822..9bae41f63 100644 --- a/web/source/settings/user/profile.js +++ b/web/source/settings/user/profile.js @@ -26,10 +26,12 @@ const query = require("../lib/query");  const {  	useTextInput,  	useFileInput, -	useBoolInput +	useBoolInput, +	useFieldArrayInput  } = require("../lib/form");  const useFormSubmit = require("../lib/form/submit"); +const { useWithFormContext, FormContext } = require("../lib/form/context");  const {  	TextInput, @@ -65,8 +67,11 @@ function UserProfileForm({ data: profile }) {  	*/  	const { data: instance } = query.useInstanceQuery(); -	const allowCustomCSS = React.useMemo(() => { -		return instance?.configuration?.accounts?.allow_custom_css === true; +	const instanceConfig = React.useMemo(() => { +		return { +			allowCustomCSS: instance?.configuration?.accounts?.allow_custom_css === true, +			maxPinnedFields: instance?.configuration?.accounts?.max_profile_fields ?? 6 +		};  	}, [instance]);  	const form = { @@ -78,9 +83,18 @@ function UserProfileForm({ data: profile }) {  		bot: useBoolInput("bot", { source: profile }),  		locked: useBoolInput("locked", { source: profile }),  		enableRSS: useBoolInput("enable_rss", { source: profile }), +		fields: useFieldArrayInput("fields_attributes", { +			defaultValue: profile?.source?.fields, +			length: instanceConfig.maxPinnedFields +		}),  	}; -	const [submitForm, result] = useFormSubmit(form, query.useUpdateCredentialsMutation()); +	const [submitForm, result] = useFormSubmit(form, query.useUpdateCredentialsMutation(), { +		onFinish: () => { +			form.avatar.reset(); +			form.header.reset(); +		} +	});  	return (  		<form className="user-profile" onSubmit={submitForm}> @@ -129,7 +143,11 @@ function UserProfileForm({ data: profile }) {  				field={form.enableRSS}  				label="Enable RSS feed of Public posts"  			/> -			{!allowCustomCSS ? null : +			<b>Profile fields</b> +			<ProfileFields +				field={form.fields} +			/> +			{!instanceConfig.allowCustomCSS ? null :  				<TextArea  					field={form.customCSS}  					label="Custom CSS" @@ -142,4 +160,40 @@ function UserProfileForm({ data: profile }) {  			<MutationButton label="Save profile info" result={result} />  		</form>  	); +} + +function ProfileFields({ field: formField }) { +	return ( +		<div className="fields"> +			<FormContext.Provider value={formField.ctx}> +				{formField.value.map((data, i) => ( +					<Field +						key={i} +						index={i} +						data={data} +					/> +				))} +			</FormContext.Provider> +		</div> +	); +} + +function Field({ index, data }) { +	const form = useWithFormContext(index, { +		name: useTextInput("name", { defaultValue: data.name }), +		value: useTextInput("value", { defaultValue: data.value }) +	}); + +	return ( +		<div className="entry"> +			<TextInput +				field={form.name} +				placeholder="Name" +			/> +			<TextInput +				field={form.value} +				placeholder="Value" +			/> +		</div> +	);  }
\ No newline at end of file diff --git a/web/source/yarn.lock b/web/source/yarn.lock index 7a06633eb..f792f115a 100644 --- a/web/source/yarn.lock +++ b/web/source/yarn.lock @@ -4137,6 +4137,11 @@ object-keys@^1.1.1:    resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"    integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== +object-to-formdata@^4.4.2: +  version "4.4.2" +  resolved "https://registry.yarnpkg.com/object-to-formdata/-/object-to-formdata-4.4.2.tgz#f89013f90493c58cb5f6ab9f50b7aeec30745ea6" +  integrity sha512-fu6UDjsqIfFUu/B3GXJ2IFnNAL/YbsC1PPzqDIFXcfkhdYjTD3K4zqhyD/lZ6+KdP9O/64YIPckIOiS5ouXwLA== +  object.assign@^4.1.3, object.assign@^4.1.4:    version "4.1.4"    resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" | 
