diff options
Diffstat (limited to 'web/source/settings/admin')
22 files changed, 794 insertions, 480 deletions
| diff --git a/web/source/settings/admin/accounts/detail.jsx b/web/source/settings/admin/accounts/detail.jsx index 0e906cd1c..63049c149 100644 --- a/web/source/settings/admin/accounts/detail.jsx +++ b/web/source/settings/admin/accounts/detail.jsx @@ -22,13 +22,13 @@ const { useRoute, Redirect } = require("wouter");  const query = require("../../lib/query"); -const FormWithData = require("../../lib/form/form-with-data"); +const FormWithData = require("../../lib/form/form-with-data").default;  const { useBaseUrl } = require("../../lib/navigation/util");  const FakeProfile = require("../../components/fake-profile");  const MutationButton = require("../../components/form/mutation-button"); -const useFormSubmit = require("../../lib/form/submit"); +const useFormSubmit = require("../../lib/form/submit").default;  const { useValue, useTextInput } = require("../../lib/form");  const { TextInput } = require("../../components/form/inputs"); @@ -77,7 +77,7 @@ function AccountDetailForm({ data: account }) {  function ModifyAccount({ account }) {  	const form = {  		id: useValue("id", account.id), -		reason: useTextInput("text", {}) +		reason: useTextInput("text")  	};  	const [modifyAccount, result] = useFormSubmit(form, query.useActionAccountMutation()); diff --git a/web/source/settings/admin/domain-permissions/detail.tsx b/web/source/settings/admin/domain-permissions/detail.tsx new file mode 100644 index 000000000..f74802666 --- /dev/null +++ b/web/source/settings/admin/domain-permissions/detail.tsx @@ -0,0 +1,254 @@ +/* +	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/>. +*/ + +import React from "react"; + +import { useMemo } from "react"; +import { useLocation } from "wouter"; + +import { useTextInput, useBoolInput } from "../../lib/form"; + +import useFormSubmit from "../../lib/form/submit"; + +import { TextInput, Checkbox, TextArea } from "../../components/form/inputs"; + +import Loading from "../../components/loading"; +import BackButton from "../../components/back-button"; +import MutationButton from "../../components/form/mutation-button"; + +import { useDomainAllowsQuery, useDomainBlocksQuery } from "../../lib/query/admin/domain-permissions/get"; +import { useAddDomainAllowMutation, useAddDomainBlockMutation, useRemoveDomainAllowMutation, useRemoveDomainBlockMutation } from "../../lib/query/admin/domain-permissions/update"; +import { DomainPerm, PermType } from "../../lib/types/domain-permission"; +import { NoArg } from "../../lib/types/query"; +import { Error } from "../../components/error"; + +export interface DomainPermDetailProps { +	baseUrl: string; +	permType: PermType; +	domain: string; +} + +export default function DomainPermDetail({ baseUrl, permType, domain }: DomainPermDetailProps) { +	const { data: domainBlocks = {}, isLoading: isLoadingDomainBlocks } = useDomainBlocksQuery(NoArg, { skip: permType !== "block" }); +	const { data: domainAllows = {}, isLoading: isLoadingDomainAllows } = useDomainAllowsQuery(NoArg, { skip: permType !== "allow" }); + +	let isLoading; +	switch (permType) { +		case "block": +			isLoading = isLoadingDomainBlocks; +			break; +		case "allow": +			isLoading = isLoadingDomainAllows; +			break; +		default: +			throw "perm type unknown"; +	} + +	if (domain == "view") { +		// Retrieve domain from form field submission. +		domain = (new URL(document.location.toString())).searchParams.get("domain")?? "unknown"; +	} + +	if (domain == "unknown") { +		throw "unknown domain"; +	} + +	// Normalize / decode domain (it may be URL-encoded). +	domain = decodeURIComponent(domain); + +	// Check if we already have a perm of the desired type for this domain. +	const existingPerm: DomainPerm | undefined = useMemo(() => { +		if (permType == "block") { +			return domainBlocks[domain]; +		} else { +			return domainAllows[domain]; +		} +	}, [domainBlocks, domainAllows, domain, permType]); + +	let infoContent: React.JSX.Element; + +	if (isLoading) { +		infoContent = <Loading />; +	} else if (existingPerm == undefined) { +		infoContent = <span>No stored {permType} yet, you can add one below:</span>; +	} else { +		infoContent = ( +			<div className="info"> +				<i className="fa fa-fw fa-exclamation-triangle" aria-hidden="true"></i> +				<b>Editing domain permissions isn't implemented yet, <a href="https://github.com/superseriousbusiness/gotosocial/issues/1198" target="_blank" rel="noopener noreferrer">check here for progress</a></b> +			</div> +		); +	} + +	return ( +		<div> +			<h1 className="text-cutoff"><BackButton to={baseUrl} /> Domain {permType} for: <span title={domain}>{domain}</span></h1> +			{infoContent} +			<DomainPermForm +				defaultDomain={domain} +				perm={existingPerm} +				permType={permType} +				baseUrl={baseUrl} +			/> +		</div> +	); +} + +interface DomainPermFormProps { +	defaultDomain: string; +	perm?: DomainPerm; +	permType: PermType; +	baseUrl: string; +} + +function DomainPermForm({ defaultDomain, perm, permType, baseUrl }: DomainPermFormProps) { +	const isExistingPerm = perm !== undefined; +	const disabledForm = isExistingPerm +		? { +			disabled: true, +			title: "Domain permissions currently cannot be edited." +		} +		: { +			disabled: false, +			title: "", +		}; + +	const form = { +		domain: useTextInput("domain", { source: perm, defaultValue: defaultDomain }), +		obfuscate: useBoolInput("obfuscate", { source: perm }), +		commentPrivate: useTextInput("private_comment", { source: perm }), +		commentPublic: useTextInput("public_comment", { source: perm }) +	}; + +	// Check which perm type we're meant to be handling +	// here, and use appropriate mutations and results. +	// We can't call these hooks conditionally because +	// react is like "weh" (mood), but we can decide +	// which ones to use conditionally. +	const [ addBlock, addBlockResult ] = useAddDomainBlockMutation(); +	const [ removeBlock, removeBlockResult] = useRemoveDomainBlockMutation({ fixedCacheKey: perm?.id }); +	const [ addAllow, addAllowResult ] = useAddDomainAllowMutation(); +	const [ removeAllow, removeAllowResult ] = useRemoveDomainAllowMutation({ fixedCacheKey: perm?.id }); +	 +	const [ +		addTrigger, +		addResult, +		removeTrigger, +		removeResult, +	] = useMemo(() => { +		return permType == "block" +			? [ +				addBlock, +				addBlockResult, +				removeBlock, +				removeBlockResult, +			] +			: [ +				addAllow, +				addAllowResult, +				removeAllow, +				removeAllowResult, +			]; +	}, [permType, +		addBlock, addBlockResult, removeBlock, removeBlockResult, +		addAllow, addAllowResult, removeAllow, removeAllowResult, +	]); + +	// Use appropriate submission params for this permType. +	const [submitForm, submitFormResult] = useFormSubmit(form, [addTrigger, addResult], { changedOnly: false }); + +	// Uppercase first letter of given permType. +	const permTypeUpper = useMemo(() => { +		return permType.charAt(0).toUpperCase() + permType.slice(1);  +	}, [permType]); + +	const [location, setLocation] = useLocation(); + +	function verifyUrlThenSubmit(e) { +		// Adding a new domain permissions happens on a url like +		// "/settings/admin/domain-permissions/:permType/domain.com", +		// but if domain input changes, that doesn't match anymore +		// and causes issues later on so, before submitting the form, +		// silently change url, and THEN submit. +		let correctUrl = `${baseUrl}/${form.domain.value}`; +		if (location != correctUrl) { +			setLocation(correctUrl); +		} +		return submitForm(e); +	} + +	return ( +		<form onSubmit={verifyUrlThenSubmit}> +			<TextInput +				field={form.domain} +				label="Domain" +				placeholder="example.com" +				{...disabledForm} +			/> + +			<Checkbox +				field={form.obfuscate} +				label="Obfuscate domain in public lists" +				{...disabledForm} +			/> + +			<TextArea +				field={form.commentPrivate} +				label="Private comment" +				rows={3} +				{...disabledForm} +			/> + +			<TextArea +				field={form.commentPublic} +				label="Public comment" +				rows={3} +				{...disabledForm} +			/> + +			<div className="action-buttons row"> +				<MutationButton +					label={permTypeUpper} +					result={submitFormResult} +					showError={false} +					{...disabledForm} +				/> + +				{ +					isExistingPerm && +					<MutationButton +						type="button" +						onClick={() => removeTrigger(perm.id?? "")} +						label="Remove" +						result={removeResult} +						className="button danger" +						showError={false} +						disabled={!isExistingPerm} +					/> +				} +			</div> + +			<> +				{addResult.error && <Error error={addResult.error} />} +				{removeResult.error && <Error error={removeResult.error} />} +			</> + +		</form> +	); +} diff --git a/web/source/settings/admin/federation/import-export/export-format-table.jsx b/web/source/settings/admin/domain-permissions/export-format-table.jsx index 7fcffa348..7fcffa348 100644 --- a/web/source/settings/admin/federation/import-export/export-format-table.jsx +++ b/web/source/settings/admin/domain-permissions/export-format-table.jsx diff --git a/web/source/settings/admin/federation/import-export/form.jsx b/web/source/settings/admin/domain-permissions/form.tsx index 2086739e3..fb639202d 100644 --- a/web/source/settings/admin/federation/import-export/form.jsx +++ b/web/source/settings/admin/domain-permissions/form.tsx @@ -17,34 +17,57 @@  	along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -const React = require("react"); +import React from "react"; -const query = require("../../../lib/query"); -const useFormSubmit = require("../../../lib/form/submit"); +import { useEffect } from "react"; -const { +import { useExportDomainListMutation } from "../../lib/query/admin/domain-permissions/export"; +import useFormSubmit from "../../lib/form/submit"; + +import { +	RadioGroup,  	TextArea,  	Select, -} = require("../../../components/form/inputs"); +} from "../../components/form/inputs"; + +import MutationButton from "../../components/form/mutation-button"; + +import { Error } from "../../components/error"; +import ExportFormatTable from "./export-format-table"; -const MutationButton = require("../../../components/form/mutation-button"); +import type { +	FormSubmitFunction, +	FormSubmitResult, +	RadioFormInputHook, +	TextFormInputHook, +} from "../../lib/form/types"; -const { Error } = require("../../../components/error"); -const ExportFormatTable = require("./export-format-table"); +export interface ImportExportFormProps { +	form: { +		domains: TextFormInputHook; +		exportType: TextFormInputHook; +		permType: RadioFormInputHook; +	}; +	submitParse: FormSubmitFunction; +	parseResult: FormSubmitResult; +}  -module.exports = function ImportExportForm({ form, submitParse, parseResult }) { -	const [submitExport, exportResult] = useFormSubmit(form, query.useExportDomainListMutation()); +export default function ImportExportForm({ form, submitParse, parseResult }: ImportExportFormProps) { +	const [submitExport, exportResult] = useFormSubmit(form, useExportDomainListMutation());  	function fileChanged(e) {  		const reader = new FileReader();  		reader.onload = function (read) { -			form.domains.value = read.target.result; -			submitParse(); +			const res = read.target?.result; +			if (typeof res === "string") { +				form.domains.value = res; +				submitParse(); +			}  		};  		reader.readAsText(e.target.files[0]);  	} -	React.useEffect(() => { +	useEffect(() => {  		if (exportResult.isSuccess) {  			form.domains.setter(exportResult.data);  		} @@ -53,12 +76,10 @@ module.exports = function ImportExportForm({ form, submitParse, parseResult }) {  	return (  		<> -			<h1>Import / Export suspended domains</h1> -			<p> -				This page can be used to import and export lists of domains to suspend. -				Exports can be done in various formats, with varying functionality and support in other software. -				Imports will automatically detect what format is being processed. -			</p> +			<h1>Import / Export domain permissions</h1> +			<p>This page can be used to import and export lists of domain permissions.</p> +			<p>Exports can be done in various formats, with varying functionality and support in other software.</p> +			<p>Imports will automatically detect what format is being processed.</p>  			<ExportFormatTable />  			<div className="import-export">  				<TextArea @@ -68,6 +89,10 @@ module.exports = function ImportExportForm({ form, submitParse, parseResult }) {  					rows={8}  				/> +				<RadioGroup +					field={form.permType} +				/> +  				<div className="button-grid">  					<MutationButton  						label="Import" @@ -75,6 +100,7 @@ module.exports = function ImportExportForm({ form, submitParse, parseResult }) {  						onClick={() => submitParse()}  						result={parseResult}  						showError={false} +						disabled={false}  					/>  					<label className="button with-icon">  						<i className="fa fa-fw " aria-hidden="true" /> @@ -92,6 +118,7 @@ module.exports = function ImportExportForm({ form, submitParse, parseResult }) {  						type="button"  						onClick={() => submitExport("export")}  						result={exportResult} showError={false} +						disabled={false}  					/>  					<MutationButton  						label="Export to file" @@ -100,6 +127,7 @@ module.exports = function ImportExportForm({ form, submitParse, parseResult }) {  						onClick={() => submitExport("export-file")}  						result={exportResult}  						showError={false} +						disabled={false}  					/>  					<div className="export-file">  						<span> @@ -121,4 +149,4 @@ module.exports = function ImportExportForm({ form, submitParse, parseResult }) {  			</div>  		</>  	); -};
\ No newline at end of file +} diff --git a/web/source/settings/admin/domain-permissions/import-export.tsx b/web/source/settings/admin/domain-permissions/import-export.tsx new file mode 100644 index 000000000..871bca131 --- /dev/null +++ b/web/source/settings/admin/domain-permissions/import-export.tsx @@ -0,0 +1,90 @@ +/* +	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/>. +*/ + +import React from "react"; + +import { Switch, Route, Redirect, useLocation } from "wouter"; + +import { useProcessDomainPermissionsMutation } from "../../lib/query/admin/domain-permissions/process"; + +import { useTextInput, useRadioInput } from "../../lib/form"; + +import useFormSubmit from "../../lib/form/submit"; + +import { ProcessImport } from "./process"; +import ImportExportForm from "./form"; + +export default function ImportExport({ baseUrl }) { +	const form = { +		domains: useTextInput("domains"), +		exportType: useTextInput("exportType", { defaultValue: "plain", dontReset: true }), +		permType: useRadioInput("permType", {  +			options: { +				block: "Domain blocks", +				allow: "Domain allows", +			} +		}) +	}; + +	const [submitParse, parseResult] = useFormSubmit(form, useProcessDomainPermissionsMutation(), { changedOnly: false }); + +	const [_location, setLocation] = useLocation(); + +	return ( +		<Switch> +			<Route path={`${baseUrl}/process`}> +				{ +					parseResult.isSuccess  +						? ( +							<> +								<h1> +									<span +										className="button" +										onClick={() => { +											parseResult.reset(); +											setLocation(baseUrl); +										}} +									> +										< back +									</span> +									  Confirm import of domain {form.permType.value}s: +								</h1> +								<ProcessImport +									list={parseResult.data} +									permType={form.permType} +								/> +							</> +						) +						: <Redirect to={baseUrl} /> +				} +			</Route> +			<Route> +				{ +					parseResult.isSuccess +						? <Redirect to={`${baseUrl}/process`} /> +						: <ImportExportForm +							form={form} +							submitParse={submitParse} +							parseResult={parseResult} +						/> +				} +			</Route> +		</Switch> +	); +} diff --git a/web/source/settings/admin/federation/index.js b/web/source/settings/admin/domain-permissions/index.tsx index ec536c0be..7d790cfc8 100644 --- a/web/source/settings/admin/federation/index.js +++ b/web/source/settings/admin/domain-permissions/index.tsx @@ -17,25 +17,33 @@  	along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -const React = require("react"); -const { Switch, Route } = require("wouter"); +import React from "react"; +import { Switch, Route } from "wouter"; -const InstanceOverview = require("./overview"); -const InstanceDetail = require("./detail"); -const InstanceImportExport = require("./import-export"); +import DomainPermissionsOverview from "./overview"; +import { PermType } from "../../lib/types/domain-permission"; +import DomainPermDetail from "./detail"; -module.exports = function Federation({ baseUrl }) { +export default function DomainPermissions({ baseUrl }: { baseUrl: string }) {  	return (  		<Switch> -			<Route path={`${baseUrl}/import-export/:list?`}> -				<InstanceImportExport /> +			<Route path="/settings/admin/domain-permissions/:permType/:domain"> +				{params => ( +					<DomainPermDetail +						permType={params.permType as PermType} +						baseUrl={baseUrl} +						domain={params.domain} +					/> +				)}  			</Route> - -			<Route path={`${baseUrl}/:domain`}> -				<InstanceDetail baseUrl={baseUrl} /> +			<Route path="/settings/admin/domain-permissions/:permType"> +				{params => ( +					<DomainPermissionsOverview +						permType={params.permType as PermType} +						baseUrl={baseUrl} +					/> +				)}  			</Route> - -			<InstanceOverview baseUrl={baseUrl} />  		</Switch>  	); -};
\ No newline at end of file +} diff --git a/web/source/settings/admin/domain-permissions/overview.tsx b/web/source/settings/admin/domain-permissions/overview.tsx new file mode 100644 index 000000000..a37ec9184 --- /dev/null +++ b/web/source/settings/admin/domain-permissions/overview.tsx @@ -0,0 +1,198 @@ +/* +	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/>. +*/ + +import React from "react"; + +import { useMemo } from "react"; +import { Link, useLocation } from "wouter"; +import { matchSorter } from "match-sorter"; + +import { useTextInput } from "../../lib/form"; + +import { TextInput } from "../../components/form/inputs"; + +import Loading from "../../components/loading"; +import { useDomainAllowsQuery, useDomainBlocksQuery } from "../../lib/query/admin/domain-permissions/get"; +import type { MappedDomainPerms, PermType } from "../../lib/types/domain-permission"; +import { NoArg } from "../../lib/types/query"; + +export interface DomainPermissionsOverviewProps { +	// Params injected by +	// the wouter router. +	permType: PermType; +	baseUrl: string, +} + +export default function DomainPermissionsOverview({ permType, baseUrl }: DomainPermissionsOverviewProps) {	 +	if (permType !== "block" && permType !== "allow") { +		throw "unrecognized perm type " + permType; +	} + +	// Uppercase first letter of given permType. +	const permTypeUpper = useMemo(() => { +		return permType.charAt(0).toUpperCase() + permType.slice(1);  +	}, [permType]); + +	// Fetch / wait for desired perms to load. +	const { data: blocks, isLoading: isLoadingBlocks } = useDomainBlocksQuery(NoArg, { skip: permType !== "block" }); +	const { data: allows, isLoading: isLoadingAllows } = useDomainAllowsQuery(NoArg, { skip: permType !== "allow" }); +	 +	let data: MappedDomainPerms | undefined; +	let isLoading: boolean; + +	if (permType == "block") { +		data = blocks; +		isLoading = isLoadingBlocks; +	} else { +		data = allows; +		isLoading = isLoadingAllows; +	} + +	if (isLoading || data === undefined) { +		return <Loading />; +	} +	 +	return ( +		<div> +			<h1>Domain {permTypeUpper}s</h1> +			{ permType == "block" ? <BlockHelperText/> : <AllowHelperText/> } +			<DomainPermsList +				data={data} +				baseUrl={baseUrl} +				permType={permType} +				permTypeUpper={permTypeUpper} +			/> +			<Link to={`${baseUrl}/import-export`}> +				<a>Or use the bulk import/export interface</a> +			</Link> +		</div> +	); +} + +interface DomainPermsListProps { +	data: MappedDomainPerms; +	baseUrl: string; +	permType: PermType; +	permTypeUpper: string; +} + +function DomainPermsList({ data, baseUrl, permType, permTypeUpper }: DomainPermsListProps) { +	// Format perms into a list. +	const perms = useMemo(() => { +		return Object.values(data); +	}, [data]); + +	const [_location, setLocation] = useLocation(); +	const filterField = useTextInput("filter"); +	 +	function filterFormSubmit(e) { +		e.preventDefault(); +		setLocation(`${baseUrl}/${filter}`); +	} +	 +	const filter = filterField.value ?? ""; +	const filteredPerms = useMemo(() => { +		return matchSorter(perms, filter, { keys: ["domain"] }); +	}, [perms, filter]); +	const filtered = perms.length - filteredPerms.length; +	 +	const filterInfo = ( +		<span> +			{perms.length} {permType}ed domain{perms.length != 1 ? "s" : ""} {filtered > 0 && `(${filtered} filtered by search)`} +		</span> +	); + +	const entries = filteredPerms.map((entry) => { +		return ( +			<Link key={entry.domain} to={`${baseUrl}/${entry.domain}`}> +				<a className="entry nounderline"> +					<span id="domain">{entry.domain}</span> +					<span id="date">{new Date(entry.created_at ?? "").toLocaleString()}</span> +				</a> +			</Link> +		); +	}); + +	return ( +		<div className="domain-permissions-list"> +			<form className="filter" role="search" onSubmit={filterFormSubmit}> +				<TextInput +					field={filterField} +					placeholder="example.org" +					label={`Search or add domain ${permType}`} +				/> +				<Link to={`${baseUrl}/${filter}`}> +					<a className="button">{permTypeUpper} {filter}</a> +				</Link> +			</form> +			<div> +				{filterInfo} +				<div className="list"> +					<div className="entries scrolling"> +						{entries} +					</div> +				</div> +			</div> +		</div> +	); +} + +function BlockHelperText() { +	return ( +		<p> +			Blocking a domain blocks interaction between your instance, and all current and future accounts on +			instance(s) running on the blocked domain. Stored content will be removed, and no more data is sent to +			the remote server. This extends to all subdomains as well, so blocking 'example.com' also blocks 'social.example.com'. +			<br/> +			<a +				href="https://docs.gotosocial.org/en/latest/admin/domain_blocks/" +				target="_blank" +				className="docslink" +				rel="noreferrer" +			> +				Learn more about domain blocks (opens in a new tab) +			</a> +			<br/> +		</p> +	); +} + +function AllowHelperText() { +	return ( +		<p> +			Allowing a domain explicitly allows instance(s) running on that domain to interact with your instance. +			If you're running in allowlist mode, this is how you "allow" instances through. +			If you're running in blocklist mode (the default federation mode), you can use explicit domain allows +			to override domain blocks. In blocklist mode, explicitly allowed instances will be able to interact with +			your instance regardless of any domain blocks in place.  This extends to all subdomains as well, so allowing +			'example.com' also allows 'social.example.com'. This is useful when you're importing a block list but +			there are some domains on the list you don't want to block: just create an explicit allow for those domains +			before importing the list. +			<br/> +			<a +				href="https://docs.gotosocial.org/en/latest/admin/federation_modes/" +				target="_blank" +				className="docslink" +				rel="noreferrer" +			> +				Learn more about federation modes (opens in a new tab) +			</a> +		</p> +	); +} diff --git a/web/source/settings/admin/federation/import-export/process.jsx b/web/source/settings/admin/domain-permissions/process.tsx index b39410605..bb9411b9d 100644 --- a/web/source/settings/admin/federation/import-export/process.jsx +++ b/web/source/settings/admin/domain-permissions/process.tsx @@ -17,57 +17,81 @@  	along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -const React = require("react"); +import React from "react"; -const query = require("../../../lib/query"); -const { isValidDomainBlock, hasBetterScope } = require("../../../lib/domain-block"); +import { memo, useMemo, useCallback, useEffect } from "react"; -const { +import { isValidDomainPermission, hasBetterScope } from "../../lib/util/domain-permission"; + +import {  	useTextInput,  	useBoolInput,  	useRadioInput, -	useCheckListInput -} = require("../../../lib/form"); - -const useFormSubmit = require("../../../lib/form/submit"); +	useCheckListInput, +} from "../../lib/form"; -const { -	TextInput, +import { +	Select,  	TextArea, +	RadioGroup,  	Checkbox, -	Select, -	RadioGroup -} = require("../../../components/form/inputs"); +	TextInput, +} from "../../components/form/inputs"; + +import useFormSubmit from "../../lib/form/submit"; -const CheckList = require("../../../components/check-list"); -const MutationButton = require("../../../components/form/mutation-button"); -const FormWithData = require("../../../lib/form/form-with-data"); +import CheckList from "../../components/check-list"; +import MutationButton from "../../components/form/mutation-button"; +import FormWithData from "../../lib/form/form-with-data"; -module.exports = React.memo( -	function ProcessImport({ list }) { +import { useImportDomainPermsMutation } from "../../lib/query/admin/domain-permissions/import"; +import { +	useDomainAllowsQuery, +	useDomainBlocksQuery +} from "../../lib/query/admin/domain-permissions/get"; + +import type { DomainPerm, MappedDomainPerms } from "../../lib/types/domain-permission"; +import type { ChecklistInputHook, RadioFormInputHook } from "../../lib/form/types"; + +export interface ProcessImportProps { +	list: DomainPerm[], +	permType: RadioFormInputHook, +} + +export const ProcessImport = memo( +	function ProcessImport({ list, permType }: ProcessImportProps) {  		return (  			<div className="without-border">  				<FormWithData -					dataQuery={query.useInstanceBlocksQuery} +					dataQuery={permType.value == "allow" +						? useDomainAllowsQuery +						: useDomainBlocksQuery +					}  					DataForm={ImportList} -					list={list} +					{...{ list, permType }}  				/>  			</div>  		);  	}  ); -function ImportList({ list, data: blockedInstances }) { -	const hasComment = React.useMemo(() => { +export interface ImportListProps { +	list: Array<DomainPerm>, +	data: MappedDomainPerms, +	permType: RadioFormInputHook, +} + +function ImportList({ list, data: domainPerms, permType }: ImportListProps) { +	const hasComment = useMemo(() => {  		let hasPublic = false;  		let hasPrivate = false;  		list.some((entry) => { -			if (entry.public_comment?.length > 0) { +			if (entry.public_comment) {  				hasPublic = true;  			} -			if (entry.private_comment?.length > 0) { +			if (entry.private_comment) {  				hasPrivate = true;  			} @@ -88,7 +112,7 @@ function ImportList({ list, data: blockedInstances }) {  	const showComment = useTextInput("showComment", { defaultValue: hasComment.type ?? "public_comment" });  	const form = { -		domains: useCheckListInput("domains", { entries: list }), +		domains: useCheckListInput("domains", { entries: list }), // DomainPerm is actually also a Checkable.  		obfuscate: useBoolInput("obfuscate"),  		privateComment: useTextInput("private_comment", {  			defaultValue: `Imported on ${new Date().toLocaleString()}` @@ -108,13 +132,17 @@ function ImportList({ list, data: blockedInstances }) {  				replace: "Replace"  			}  		}), +		permType: permType,  	}; -	const [importDomains, importResult] = useFormSubmit(form, query.useImportDomainListMutation(), { changedOnly: false }); +	const [importDomains, importResult] = useFormSubmit(form, useImportDomainPermsMutation(), { changedOnly: false });  	return (  		<> -			<form onSubmit={importDomains} className="suspend-import-list"> +			<form +				onSubmit={importDomains} +				className="domain-perm-import-list" +			>  				<span>{list.length} domain{list.length != 1 ? "s" : ""} in this list</span>  				{hasComment.both && @@ -129,8 +157,9 @@ function ImportList({ list, data: blockedInstances }) {  				<div className="checkbox-list-wrapper">  					<DomainCheckList  						field={form.domains} -						blockedInstances={blockedInstances} -						commentType={showComment.value} +						domainPerms={domainPerms} +						commentType={showComment.value as "public_comment" | "private_comment"} +						permType={form.permType}  					/>  				</div> @@ -159,28 +188,41 @@ function ImportList({ list, data: blockedInstances }) {  					label="Obfuscate domains in public lists"  				/> -				<MutationButton label="Import" result={importResult} /> +				<MutationButton +					label="Import" +					disabled={false} +					result={importResult} +				/>  			</form>  		</>  	);  } -function DomainCheckList({ field, blockedInstances, commentType }) { -	const getExtraProps = React.useCallback((entry) => { +interface DomainCheckListProps { +	field: ChecklistInputHook, +	domainPerms: MappedDomainPerms, +	commentType: "public_comment" | "private_comment", +	permType: RadioFormInputHook, +} + +function DomainCheckList({ field, domainPerms, commentType, permType }: DomainCheckListProps) { +	const getExtraProps = useCallback((entry: DomainPerm) => {  		return {  			comment: entry[commentType], -			alreadyExists: blockedInstances[entry.domain] != undefined +			alreadyExists: entry.domain in domainPerms, +			permType: permType,  		}; -	}, [blockedInstances, commentType]); +	}, [domainPerms, commentType, permType]); -	const entriesWithSuggestions = React.useMemo(() => ( -		Object.values(field.value).filter((entry) => entry.suggest) -	), [field.value]); +	const entriesWithSuggestions = useMemo(() => { +		const fieldValue = (field.value ?? {}) as { [k: string]: DomainPerm; }; +		return Object.values(fieldValue).filter((entry) => entry.suggest); +	}, [field.value]);  	return (  		<>  			<CheckList -				field={field} +				field={field as ChecklistInputHook}  				header={<>  					<b>Domain</b>  					<b> @@ -200,8 +242,14 @@ function DomainCheckList({ field, blockedInstances, commentType }) {  	);  } -const UpdateHint = React.memo( -	function UpdateHint({ entries, updateEntry, updateMultiple }) { +interface UpdateHintProps { +	entries, +	updateEntry, +	updateMultiple, +} + +const UpdateHint = memo( +	function UpdateHint({ entries, updateEntry, updateMultiple }: UpdateHintProps) {  		if (entries.length == 0) {  			return null;  		} @@ -229,8 +277,13 @@ const UpdateHint = React.memo(  	}  ); -const UpdateableEntry = React.memo( -	function UpdateableEntry({ entry, updateEntry }) { +interface UpdateableEntryProps { +	entry, +	updateEntry, +} + +const UpdateableEntry = memo( +	function UpdateableEntry({ entry, updateEntry }: UpdateableEntryProps) {  		return (  			<>  				<span className="text-cutoff">{entry.domain}</span> @@ -248,21 +301,31 @@ function domainValidationError(isValid) {  	return isValid ? "" : "Invalid domain";  } -function DomainEntry({ entry, onChange, extraProps: { alreadyExists, comment } }) { +interface DomainEntryProps { +	entry; +	onChange; +	extraProps: { +		alreadyExists: boolean; +		comment: string; +		permType: RadioFormInputHook; +	}; +} + +function DomainEntry({ entry, onChange, extraProps: { alreadyExists, comment, permType } }: DomainEntryProps) {  	const domainField = useTextInput("domain", {  		defaultValue: entry.domain,  		showValidation: entry.checked,  		initValidation: domainValidationError(entry.valid), -		validator: (value) => domainValidationError(isValidDomainBlock(value)) +		validator: (value) => domainValidationError(isValidDomainPermission(value))  	}); -	React.useEffect(() => { +	useEffect(() => {  		if (entry.valid != domainField.valid) {  			onChange({ valid: domainField.valid });  		}  	}, [onChange, entry.valid, domainField.valid]); -	React.useEffect(() => { +	useEffect(() => {  		if (entry.domain != domainField.value) {  			domainField.setter(entry.domain);  		} @@ -270,8 +333,8 @@ function DomainEntry({ entry, onChange, extraProps: { alreadyExists, comment } }  		// eslint-disable-next-line react-hooks/exhaustive-deps  	}, [entry.domain, domainField.setter]); -	React.useEffect(() => { -		onChange({ suggest: hasBetterScope(domainField.value) }); +	useEffect(() => { +		onChange({ suggest: hasBetterScope(domainField.value ?? "") });  		// only need this update if it's the entry.checked that updated, not onChange  		// eslint-disable-next-line react-hooks/exhaustive-deps  	}, [domainField.value]); @@ -296,7 +359,11 @@ function DomainEntry({ entry, onChange, extraProps: { alreadyExists, comment } }  					}}  				/>  				<span id="icon" onClick={clickIcon}> -					<DomainEntryIcon alreadyExists={alreadyExists} suggestion={entry.suggest} onChange={onChange} /> +					<DomainEntryIcon +						alreadyExists={alreadyExists} +						suggestion={entry.suggest} +						permTypeString={permType.value?? ""} +					/>  				</span>  			</div>  			<p>{comment}</p> @@ -304,7 +371,13 @@ function DomainEntry({ entry, onChange, extraProps: { alreadyExists, comment } }  	);  } -function DomainEntryIcon({ alreadyExists, suggestion }) { +interface DomainEntryIconProps { +	alreadyExists: boolean; +	suggestion: string; +	permTypeString: string;  +} + +function DomainEntryIcon({ alreadyExists, suggestion, permTypeString }: DomainEntryIconProps) {  	let icon;  	let text; @@ -312,8 +385,8 @@ function DomainEntryIcon({ alreadyExists, suggestion }) {  		icon = "fa-info-circle suggest-changes";  		text = `Entry targets a specific subdomain, consider changing it to '${suggestion}'.`;  	} else if (alreadyExists) { -		icon = "fa-history already-blocked"; -		text = "Domain block already exists."; +		icon = "fa-history permission-already-exists"; +		text = `Domain ${permTypeString} already exists.`;  	}  	if (!icon) { diff --git a/web/source/settings/admin/emoji/category-select.jsx b/web/source/settings/admin/emoji/category-select.jsx index da2604602..e5cf29939 100644 --- a/web/source/settings/admin/emoji/category-select.jsx +++ b/web/source/settings/admin/emoji/category-select.jsx @@ -22,9 +22,8 @@ const splitFilterN = require("split-filter-n");  const syncpipe = require('syncpipe');  const { matchSorter } = require("match-sorter"); -const query = require("../../lib/query"); -  const ComboBox = require("../../components/combo-box"); +const { useListEmojiQuery } = require("../../lib/query/admin/custom-emoji");  function useEmojiByCategory(emoji) {  	// split all emoji over an object keyed by the category names (or Unsorted) @@ -43,7 +42,7 @@ function CategorySelect({ field, children }) {  		isLoading,  		isSuccess,  		error -	} = query.useListEmojiQuery({ filter: "domain:local" }); +	} = useListEmojiQuery({ filter: "domain:local" });  	const emojiByCategory = useEmojiByCategory(emoji); diff --git a/web/source/settings/admin/emoji/local/detail.js b/web/source/settings/admin/emoji/local/detail.js index daf7a2dac..18a681b6e 100644 --- a/web/source/settings/admin/emoji/local/detail.js +++ b/web/source/settings/admin/emoji/local/detail.js @@ -20,21 +20,25 @@  const React = require("react");  const { useRoute, Link, Redirect } = require("wouter"); -const query = require("../../../lib/query"); -  const { useComboBoxInput, useFileInput, useValue } = require("../../../lib/form");  const { CategorySelect } = require("../category-select"); -const useFormSubmit = require("../../../lib/form/submit"); +const useFormSubmit = require("../../../lib/form/submit").default;  const { useBaseUrl } = require("../../../lib/navigation/util");  const FakeToot = require("../../../components/fake-toot"); -const FormWithData = require("../../../lib/form/form-with-data"); +const FormWithData = require("../../../lib/form/form-with-data").default;  const Loading = require("../../../components/loading");  const { FileInput } = require("../../../components/form/inputs");  const MutationButton = require("../../../components/form/mutation-button");  const { Error } = require("../../../components/error"); +const { +	useGetEmojiQuery, +	useEditEmojiMutation, +	useDeleteEmojiMutation, +} = require("../../../lib/query/admin/custom-emoji"); +  module.exports = function EmojiDetailRoute({ }) {  	const baseUrl = useBaseUrl();  	let [_match, params] = useRoute(`${baseUrl}/:emojiId`); @@ -44,7 +48,7 @@ module.exports = function EmojiDetailRoute({ }) {  		return (  			<div className="emoji-detail">  				<Link to={baseUrl}><a>< go back</a></Link> -				<FormWithData dataQuery={query.useGetEmojiQuery} queryArg={params.emojiId} DataForm={EmojiDetailForm} /> +				<FormWithData dataQuery={useGetEmojiQuery} queryArg={params.emojiId} DataForm={EmojiDetailForm} />  			</div>  		);  	} @@ -61,7 +65,7 @@ function EmojiDetailForm({ data: emoji }) {  		})  	}; -	const [modifyEmoji, result] = useFormSubmit(form, query.useEditEmojiMutation()); +	const [modifyEmoji, result] = useFormSubmit(form, useEditEmojiMutation());  	// Automatic submitting of category change  	React.useEffect(() => { @@ -74,7 +78,7 @@ function EmojiDetailForm({ data: emoji }) {  		/* eslint-disable-next-line react-hooks/exhaustive-deps */  	}, [form.category.hasChanged(), form.category.isNew, form.category.state.open]); -	const [deleteEmoji, deleteResult] = query.useDeleteEmojiMutation(); +	const [deleteEmoji, deleteResult] = useDeleteEmojiMutation();  	if (deleteResult.isSuccess) {  		return <Redirect to={baseUrl} />; diff --git a/web/source/settings/admin/emoji/local/new-emoji.js b/web/source/settings/admin/emoji/local/new-emoji.js index 439d09e62..ecb0465cb 100644 --- a/web/source/settings/admin/emoji/local/new-emoji.js +++ b/web/source/settings/admin/emoji/local/new-emoji.js @@ -19,15 +19,13 @@  const React = require("react"); -const query = require("../../../lib/query"); -  const {  	useFileInput,  	useComboBoxInput  } = require("../../../lib/form");  const useShortcode = require("./use-shortcode"); -const useFormSubmit = require("../../../lib/form/submit"); +const useFormSubmit = require("../../../lib/form/submit").default;  const {  	TextInput, FileInput @@ -36,11 +34,13 @@ const {  const { CategorySelect } = require('../category-select');  const FakeToot = require("../../../components/fake-toot");  const MutationButton = require("../../../components/form/mutation-button"); +const { useAddEmojiMutation } = require("../../../lib/query/admin/custom-emoji"); +const { useInstanceV1Query } = require("../../../lib/query");  module.exports = function NewEmojiForm() {  	const shortcode = useShortcode(); -	const { data: instance } = query.useInstanceQuery(); +	const { data: instance } = useInstanceV1Query();  	const emojiMaxSize = React.useMemo(() => {  		return instance?.configuration?.emojis?.emoji_size_limit ?? 50 * 1024;  	}, [instance]); @@ -54,7 +54,7 @@ module.exports = function NewEmojiForm() {  	const [submitForm, result] = useFormSubmit({  		shortcode, image, category -	}, query.useAddEmojiMutation()); +	}, useAddEmojiMutation());  	React.useEffect(() => {  		if (shortcode.value.length == 0) { diff --git a/web/source/settings/admin/emoji/local/overview.js b/web/source/settings/admin/emoji/local/overview.js index 38dc1feba..757f07c43 100644 --- a/web/source/settings/admin/emoji/local/overview.js +++ b/web/source/settings/admin/emoji/local/overview.js @@ -25,13 +25,13 @@ const { matchSorter } = require("match-sorter");  const NewEmojiForm = require("./new-emoji");  const { useTextInput } = require("../../../lib/form"); -const query = require("../../../lib/query");  const { useEmojiByCategory } = require("../category-select");  const { useBaseUrl } = require("../../../lib/navigation/util");  const Loading = require("../../../components/loading");  const { Error } = require("../../../components/error");  const { TextInput } = require("../../../components/form/inputs"); +const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji");  module.exports = function EmojiOverview({ }) {  	const { @@ -39,7 +39,7 @@ module.exports = function EmojiOverview({ }) {  		isLoading,  		isError,  		error -	} = query.useListEmojiQuery({ filter: "domain:local" }); +	} = useListEmojiQuery({ filter: "domain:local" });  	let content = null; diff --git a/web/source/settings/admin/emoji/local/use-shortcode.js b/web/source/settings/admin/emoji/local/use-shortcode.js index 7e1bae0ad..67255860f 100644 --- a/web/source/settings/admin/emoji/local/use-shortcode.js +++ b/web/source/settings/admin/emoji/local/use-shortcode.js @@ -19,15 +19,15 @@  const React = require("react"); -const query = require("../../../lib/query");  const { useTextInput } = require("../../../lib/form"); +const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji");  const shortcodeRegex = /^\w{2,30}$/;  module.exports = function useShortcode() { -	const { -		data: emoji = [] -	} = query.useListEmojiQuery({ filter: "domain:local" }); +	const { data: emoji = [] } = useListEmojiQuery({ +		filter: "domain:local" +	});  	const emojiCodes = React.useMemo(() => {  		return new Set(emoji.map((e) => e.shortcode)); diff --git a/web/source/settings/admin/emoji/remote/index.js b/web/source/settings/admin/emoji/remote/index.js index e877efb89..1a8c719dd 100644 --- a/web/source/settings/admin/emoji/remote/index.js +++ b/web/source/settings/admin/emoji/remote/index.js @@ -21,9 +21,9 @@ const React = require("react");  const ParseFromToot = require("./parse-from-toot"); -const query = require("../../../lib/query");  const Loading = require("../../../components/loading");  const { Error } = require("../../../components/error"); +const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji");  module.exports = function RemoteEmoji() {  	// local emoji are queried for shortcode collision detection @@ -31,7 +31,7 @@ module.exports = function RemoteEmoji() {  		data: emoji = [],  		isLoading,  		error -	} = query.useListEmojiQuery({ filter: "domain:local" }); +	} = useListEmojiQuery({ filter: "domain:local" });  	const emojiCodes = React.useMemo(() => {  		return new Set(emoji.map((e) => e.shortcode)); diff --git a/web/source/settings/admin/emoji/remote/parse-from-toot.js b/web/source/settings/admin/emoji/remote/parse-from-toot.js index e6438a4d2..503a341c8 100644 --- a/web/source/settings/admin/emoji/remote/parse-from-toot.js +++ b/web/source/settings/admin/emoji/remote/parse-from-toot.js @@ -19,25 +19,27 @@  const React = require("react"); -const query = require("../../../lib/query"); -  const {  	useTextInput,  	useComboBoxInput,  	useCheckListInput  } = require("../../../lib/form"); -const useFormSubmit = require("../../../lib/form/submit"); +const useFormSubmit = require("../../../lib/form/submit").default; -const CheckList = require("../../../components/check-list"); +const CheckList = require("../../../components/check-list").default;  const { CategorySelect } = require('../category-select');  const { TextInput } = require("../../../components/form/inputs");  const MutationButton = require("../../../components/form/mutation-button");  const { Error } = require("../../../components/error"); +const { +	useSearchItemForEmojiMutation, +	usePatchRemoteEmojisMutation +} = require("../../../lib/query/admin/custom-emoji");  module.exports = function ParseFromToot({ emojiCodes }) { -	const [searchStatus, result] = query.useSearchStatusForEmojiMutation(); +	const [searchStatus, result] = useSearchItemForEmojiMutation();  	const [onURLChange, _resetURL, { url }] = useTextInput("url"); @@ -121,7 +123,7 @@ function CopyEmojiForm({ localEmojiCodes, type, emojiList }) {  	const [formSubmit, result] = useFormSubmit(  		form, -		query.usePatchRemoteEmojisMutation(), +		usePatchRemoteEmojisMutation(),  		{  			changedOnly: false,  			onFinish: ({ data }) => { diff --git a/web/source/settings/admin/federation/detail.js b/web/source/settings/admin/federation/detail.js deleted file mode 100644 index 7bdee66cf..000000000 --- a/web/source/settings/admin/federation/detail.js +++ /dev/null @@ -1,168 +0,0 @@ -/* -	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/>. -*/ - -const React = require("react"); -const { useRoute, Redirect, useLocation } = require("wouter"); - -const query = require("../../lib/query"); - -const { useTextInput, useBoolInput } = require("../../lib/form"); - -const useFormSubmit = require("../../lib/form/submit"); - -const { TextInput, Checkbox, TextArea } = require("../../components/form/inputs"); - -const Loading = require("../../components/loading"); -const BackButton = require("../../components/back-button"); -const MutationButton = require("../../components/form/mutation-button"); - -module.exports = function InstanceDetail({ baseUrl }) { -	const { data: blockedInstances = {}, isLoading } = query.useInstanceBlocksQuery(); - -	let [_match, { domain }] = useRoute(`${baseUrl}/:domain`); -	if (domain == "view") { -		// Retrieve domain from form field submission. -		domain = (new URL(document.location)).searchParams.get("domain"); -	} - -	// Normalize / decode domain (it may be URL-encoded). -	domain = decodeURIComponent(domain); - -	const existingBlock = React.useMemo(() => { -		return blockedInstances[domain]; -	}, [blockedInstances, domain]); - -	if (domain == undefined) { -		return <Redirect to={baseUrl} />; -	} - -	let infoContent = null; - -	if (isLoading) { -		infoContent = <Loading />; -	} else if (existingBlock == undefined) { -		infoContent = <span>No stored block yet, you can add one below:</span>; -	} else { -		infoContent = ( -			<div className="info"> -				<i className="fa fa-fw fa-exclamation-triangle" aria-hidden="true"></i> -				<b>Editing domain blocks isn't implemented yet, <a href="https://github.com/superseriousbusiness/gotosocial/issues/1198" target="_blank" rel="noopener noreferrer">check here for progress</a></b> -			</div> -		); -	} - -	return ( -		<div> -			<h1 className="text-cutoff"><BackButton to={baseUrl} /> Federation settings for: <span title={domain}>{domain}</span></h1> -			{infoContent} -			<DomainBlockForm defaultDomain={domain} block={existingBlock} baseUrl={baseUrl} /> -		</div> -	); -}; - -function DomainBlockForm({ defaultDomain, block = {}, baseUrl }) { -	const isExistingBlock = block.domain != undefined; - -	const disabledForm = isExistingBlock -		? { -			disabled: true, -			title: "Domain suspensions currently cannot be edited." -		} -		: {}; - -	const form = { -		domain: useTextInput("domain", { source: block, defaultValue: defaultDomain }), -		obfuscate: useBoolInput("obfuscate", { source: block }), -		commentPrivate: useTextInput("private_comment", { source: block }), -		commentPublic: useTextInput("public_comment", { source: block }) -	}; - -	const [submitForm, addResult] = useFormSubmit(form, query.useAddInstanceBlockMutation(), { changedOnly: false }); - -	const [removeBlock, removeResult] = query.useRemoveInstanceBlockMutation({ fixedCacheKey: block.id }); - -	const [location, setLocation] = useLocation(); - -	function verifyUrlThenSubmit(e) { -		// Adding a new block happens on /settings/admin/federation/domain.com -		// but if domain input changes, that doesn't match anymore and causes issues later on -		// so, before submitting the form, silently change url, then submit -		let correctUrl = `${baseUrl}/${form.domain.value}`; -		if (location != correctUrl) { -			setLocation(correctUrl); -		} -		return submitForm(e); -	} - -	return ( -		<form onSubmit={verifyUrlThenSubmit}> -			<TextInput -				field={form.domain} -				label="Domain" -				placeholder="example.com" -				{...disabledForm} -			/> - -			<Checkbox -				field={form.obfuscate} -				label="Obfuscate domain in public lists" -				{...disabledForm} -			/> - -			<TextArea -				field={form.commentPrivate} -				label="Private comment" -				rows={3} -				{...disabledForm} -			/> - -			<TextArea -				field={form.commentPublic} -				label="Public comment" -				rows={3} -				{...disabledForm} -			/> - -			<div className="action-buttons row"> -				<MutationButton -					label="Suspend" -					result={addResult} -					showError={false} -					{...disabledForm} -				/> - -				{ -					isExistingBlock && -					<MutationButton -						type="button" -						onClick={() => removeBlock(block.id)} -						label="Remove" -						result={removeResult} -						className="button danger" -						showError={false} -					/> -				} -			</div> - -			{addResult.error && <Error error={addResult.error} />} -			{removeResult.error && <Error error={removeResult.error} />} - -		</form> -	); -}
\ No newline at end of file diff --git a/web/source/settings/admin/federation/import-export/index.jsx b/web/source/settings/admin/federation/import-export/index.jsx deleted file mode 100644 index bff14b939..000000000 --- a/web/source/settings/admin/federation/import-export/index.jsx +++ /dev/null @@ -1,75 +0,0 @@ -/* -	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/>. -*/ - -const React = require("react"); -const { Switch, Route, Redirect, useLocation } = require("wouter"); - -const query = require("../../../lib/query"); - -const { -	useTextInput, -} = require("../../../lib/form"); - -const useFormSubmit = require("../../../lib/form/submit"); - -const ProcessImport = require("./process"); -const ImportExportForm = require("./form"); - -module.exports = function ImportExport({ baseUrl }) { -	const form = { -		domains: useTextInput("domains"), -		exportType: useTextInput("exportType", { defaultValue: "plain", dontReset: true }) -	}; - -	const [submitParse, parseResult] = useFormSubmit(form, query.useProcessDomainListMutation(), { changedOnly: false }); - -	const [_location, setLocation] = useLocation(); - -	return ( -		<Switch> -			<Route path={`${baseUrl}/process`}> -				{parseResult.isSuccess ? ( -					<> -						<h1> -							<span className="button" onClick={() => { -								parseResult.reset(); -								setLocation(baseUrl); -							}}> -								< back -							</span> Confirm import: -						</h1> -						<ProcessImport -							list={parseResult.data} -						/> -					</> -				) : <Redirect to={baseUrl} />} -			</Route> - -			<Route> -				{!parseResult.isSuccess ? ( -					<ImportExportForm -						form={form} -						submitParse={submitParse} -						parseResult={parseResult} -					/> -				) : <Redirect to={`${baseUrl}/process`} />} -			</Route> -		</Switch> -	); -};
\ No newline at end of file diff --git a/web/source/settings/admin/federation/overview.js b/web/source/settings/admin/federation/overview.js deleted file mode 100644 index c09289284..000000000 --- a/web/source/settings/admin/federation/overview.js +++ /dev/null @@ -1,101 +0,0 @@ -/* -	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/>. -*/ - -const React = require("react"); -const { Link, useLocation } = require("wouter"); -const { matchSorter } = require("match-sorter"); - -const { useTextInput } = require("../../lib/form"); - -const { TextInput } = require("../../components/form/inputs"); - -const query = require("../../lib/query"); - -const Loading = require("../../components/loading"); - -module.exports = function InstanceOverview({ baseUrl }) { -	const { data: blockedInstances = [], isLoading } = query.useInstanceBlocksQuery(); - -	const [_location, setLocation] = useLocation(); - -	const filterField = useTextInput("filter"); -	const filter = filterField.value; - -	const blockedInstancesList = React.useMemo(() => { -		return Object.values(blockedInstances); -	}, [blockedInstances]); - -	const filteredInstances = React.useMemo(() => { -		return matchSorter(blockedInstancesList, filter, { keys: ["domain"] }); -	}, [blockedInstancesList, filter]); - -	let filtered = blockedInstancesList.length - filteredInstances.length; - -	function filterFormSubmit(e) { -		e.preventDefault(); -		setLocation(`${baseUrl}/${filter}`); -	} - -	if (isLoading) { -		return <Loading />; -	} - -	return ( -		<> -			<h1>Federation</h1> - -			<div className="instance-list"> -				<h2>Suspended instances</h2> -				<p> -					Suspending a domain blocks all current and future accounts on that instance. Stored content will be removed, -					and no more data is sent to the remote server.<br /> -					This extends to all subdomains as well, so blocking 'example.com' also includes 'social.example.com'. -				</p> -				<form className="filter" role="search" onSubmit={filterFormSubmit}> -					<TextInput field={filterField} placeholder="example.com" label="Search or add domain suspension" /> -					<Link to={`${baseUrl}/${filter}`}><a className="button">Suspend</a></Link> -				</form> -				<div> -					<span> -						{blockedInstancesList.length} blocked instance{blockedInstancesList.length != 1 ? "s" : ""} {filtered > 0 && `(${filtered} filtered by search)`} -					</span> -					<div className="list"> -						<div className="entries scrolling"> -							{filteredInstances.map((entry) => { -								return ( -									<Link key={entry.domain} to={`${baseUrl}/${entry.domain}`}> -										<a className="entry nounderline"> -											<span id="domain"> -												{entry.domain} -											</span> -											<span id="date"> -												{new Date(entry.created_at).toLocaleString()} -											</span> -										</a> -									</Link> -								); -							})} -						</div> -					</div> -				</div> -			</div> -			<Link to={`${baseUrl}/import-export`}><a>Or use the bulk import/export interface</a></Link> -		</> -	); -};
\ No newline at end of file diff --git a/web/source/settings/admin/reports/detail.jsx b/web/source/settings/admin/reports/detail.jsx index 6b85872c4..d686b92bd 100644 --- a/web/source/settings/admin/reports/detail.jsx +++ b/web/source/settings/admin/reports/detail.jsx @@ -20,19 +20,21 @@  const React = require("react");  const { useRoute, Redirect } = require("wouter"); -const query = require("../../lib/query"); - -const FormWithData = require("../../lib/form/form-with-data"); +const FormWithData = require("../../lib/form/form-with-data").default;  const BackButton = require("../../components/back-button");  const { useValue, useTextInput } = require("../../lib/form"); -const useFormSubmit = require("../../lib/form/submit"); +const useFormSubmit = require("../../lib/form/submit").default;  const { TextArea } = require("../../components/form/inputs");  const MutationButton = require("../../components/form/mutation-button");  const Username = require("./username");  const { useBaseUrl } = require("../../lib/navigation/util"); +const { +	useGetReportQuery, +	useResolveReportMutation, +} = require("../../lib/query/admin/reports");  module.exports = function ReportDetail({ }) {  	const baseUrl = useBaseUrl(); @@ -46,7 +48,7 @@ module.exports = function ReportDetail({ }) {  					<BackButton to={baseUrl} /> Report Details  				</h1>  				<FormWithData -					dataQuery={query.useGetReportQuery} +					dataQuery={useGetReportQuery}  					queryArg={params.reportId}  					DataForm={ReportDetailForm}  				/> @@ -115,7 +117,7 @@ function ReportActionForm({ report }) {  		comment: useTextInput("action_taken_comment")  	}; -	const [submit, result] = useFormSubmit(form, query.useResolveReportMutation(), { changedOnly: false }); +	const [submit, result] = useFormSubmit(form, useResolveReportMutation(), { changedOnly: false });  	return (  		<form onSubmit={submit} className="info-block"> diff --git a/web/source/settings/admin/reports/index.jsx b/web/source/settings/admin/reports/index.jsx index 2f7a09517..5ffbfd3a0 100644 --- a/web/source/settings/admin/reports/index.jsx +++ b/web/source/settings/admin/reports/index.jsx @@ -20,13 +20,12 @@  const React = require("react");  const { Link, Switch, Route } = require("wouter"); -const query = require("../../lib/query"); - -const FormWithData = require("../../lib/form/form-with-data"); +const FormWithData = require("../../lib/form/form-with-data").default;  const ReportDetail = require("./detail");  const Username = require("./username");  const { useBaseUrl } = require("../../lib/navigation/util"); +const { useListReportsQuery } = require("../../lib/query/admin/reports");  module.exports = function Reports({ baseUrl }) {  	return ( @@ -51,7 +50,7 @@ function ReportOverview({ }) {  				</p>  			</div>  			<FormWithData -				dataQuery={query.useListReportsQuery} +				dataQuery={useListReportsQuery}  				DataForm={ReportsList}  			/>  		</> diff --git a/web/source/settings/admin/settings/index.jsx b/web/source/settings/admin/settings/index.jsx index 5ea227fb1..c0da83a2a 100644 --- a/web/source/settings/admin/settings/index.jsx +++ b/web/source/settings/admin/settings/index.jsx @@ -19,14 +19,12 @@  const React = require("react"); -const query = require("../../lib/query"); -  const {  	useTextInput,  	useFileInput  } = require("../../lib/form"); -const useFormSubmit = require("../../lib/form/submit"); +const useFormSubmit = require("../../lib/form/submit").default;  const {  	TextInput, @@ -34,13 +32,16 @@ const {  	FileInput  } = require("../../components/form/inputs"); -const FormWithData = require("../../lib/form/form-with-data"); +const FormWithData = require("../../lib/form/form-with-data").default;  const MutationButton = require("../../components/form/mutation-button"); +const { useInstanceV1Query } = require("../../lib/query"); +const { useUpdateInstanceMutation } = require("../../lib/query/admin"); +  module.exports = function AdminSettings() {  	return (  		<FormWithData -			dataQuery={query.useInstanceQuery} +			dataQuery={useInstanceV1Query}  			DataForm={AdminSettingsForm}  		/>  	); @@ -61,7 +62,7 @@ function AdminSettingsForm({ data: instance }) {  		terms: useTextInput("terms", { source: instance })  	}; -	const [submitForm, result] = useFormSubmit(form, query.useUpdateInstanceMutation()); +	const [submitForm, result] = useFormSubmit(form, useUpdateInstanceMutation());  	return (  		<form onSubmit={submitForm}> diff --git a/web/source/settings/admin/settings/rules.jsx b/web/source/settings/admin/settings/rules.jsx index 7c78e6601..4280ccea7 100644 --- a/web/source/settings/admin/settings/rules.jsx +++ b/web/source/settings/admin/settings/rules.jsx @@ -21,11 +21,11 @@ const React = require("react");  const { Switch, Route, Link, Redirect, useRoute } = require("wouter");  const query = require("../../lib/query"); -const FormWithData = require("../../lib/form/form-with-data"); +const FormWithData = require("../../lib/form/form-with-data").default;  const { useBaseUrl } = require("../../lib/navigation/util");  const { useValue, useTextInput } = require("../../lib/form"); -const useFormSubmit = require("../../lib/form/submit"); +const useFormSubmit = require("../../lib/form/submit").default;  const { TextArea } = require("../../components/form/inputs");  const MutationButton = require("../../components/form/mutation-button"); | 
