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"); |