summaryrefslogtreecommitdiff
path: root/web/source/settings/admin
diff options
context:
space:
mode:
Diffstat (limited to 'web/source/settings/admin')
-rw-r--r--web/source/settings/admin/accounts/detail/actions.tsx89
-rw-r--r--web/source/settings/admin/accounts/detail/handlesignup.tsx118
-rw-r--r--web/source/settings/admin/accounts/detail/index.tsx179
-rw-r--r--web/source/settings/admin/accounts/index.tsx49
-rw-r--r--web/source/settings/admin/accounts/pending/index.tsx40
-rw-r--r--web/source/settings/admin/accounts/search/index.tsx125
-rw-r--r--web/source/settings/admin/actions/keys/expireremote.tsx63
-rw-r--r--web/source/settings/admin/actions/keys/index.tsx30
-rw-r--r--web/source/settings/admin/actions/media/cleanup.tsx61
-rw-r--r--web/source/settings/admin/actions/media/index.tsx30
-rw-r--r--web/source/settings/admin/domain-permissions/detail.tsx254
-rw-r--r--web/source/settings/admin/domain-permissions/export-format-table.jsx65
-rw-r--r--web/source/settings/admin/domain-permissions/form.tsx153
-rw-r--r--web/source/settings/admin/domain-permissions/import-export.tsx90
-rw-r--r--web/source/settings/admin/domain-permissions/index.tsx49
-rw-r--r--web/source/settings/admin/domain-permissions/overview.tsx198
-rw-r--r--web/source/settings/admin/domain-permissions/process.tsx402
-rw-r--r--web/source/settings/admin/emoji/category-select.jsx96
-rw-r--r--web/source/settings/admin/emoji/local/detail.js146
-rw-r--r--web/source/settings/admin/emoji/local/index.tsx35
-rw-r--r--web/source/settings/admin/emoji/local/new-emoji.tsx116
-rw-r--r--web/source/settings/admin/emoji/local/overview.js153
-rw-r--r--web/source/settings/admin/emoji/local/use-shortcode.js56
-rw-r--r--web/source/settings/admin/emoji/remote/index.tsx54
-rw-r--r--web/source/settings/admin/emoji/remote/parse-from-toot.tsx235
-rw-r--r--web/source/settings/admin/reports/detail.tsx252
-rw-r--r--web/source/settings/admin/reports/index.tsx103
-rw-r--r--web/source/settings/admin/reports/username.tsx54
-rw-r--r--web/source/settings/admin/settings/index.tsx190
-rw-r--r--web/source/settings/admin/settings/rules.tsx174
30 files changed, 0 insertions, 3659 deletions
diff --git a/web/source/settings/admin/accounts/detail/actions.tsx b/web/source/settings/admin/accounts/detail/actions.tsx
deleted file mode 100644
index 75ab8db6e..000000000
--- a/web/source/settings/admin/accounts/detail/actions.tsx
+++ /dev/null
@@ -1,89 +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/>.
-*/
-
-import React from "react";
-
-import { useActionAccountMutation } from "../../../lib/query";
-
-import MutationButton from "../../../components/form/mutation-button";
-
-import useFormSubmit from "../../../lib/form/submit";
-import {
- useValue,
- useTextInput,
- useBoolInput,
-} from "../../../lib/form";
-
-import { Checkbox, TextInput } from "../../../components/form/inputs";
-import { AdminAccount } from "../../../lib/types/account";
-
-export interface AccountActionsProps {
- account: AdminAccount,
-}
-
-export function AccountActions({ account }: AccountActionsProps) {
- const form = {
- id: useValue("id", account.id),
- reason: useTextInput("text")
- };
-
- const reallySuspend = useBoolInput("reallySuspend");
- const [accountAction, result] = useFormSubmit(form, useActionAccountMutation());
-
- return (
- <form
- onSubmit={accountAction}
- aria-labelledby="account-moderation-actions"
- >
- <h3 id="account-moderation-actions">Account Moderation Actions</h3>
- <div>
- Currently only the "suspend" action is implemented.<br/>
- Suspending an account will delete it from your server, and remove all of its media, posts, relationships, etc.<br/>
- If the suspended account is local, suspending will also send out a "delete" message to other servers, requesting them to remove its data from their instance as well.<br/>
- <b>Account suspension cannot be reversed.</b>
- </div>
- <TextInput
- field={form.reason}
- placeholder="Reason for this action"
- />
- <div className="action-buttons">
- {/* <MutationButton
- label="Disable"
- name="disable"
- result={result}
- />
- <MutationButton
- label="Silence"
- name="silence"
- result={result}
- /> */}
- <MutationButton
- disabled={account.suspended || reallySuspend.value === undefined || reallySuspend.value === false}
- label="Suspend"
- name="suspend"
- result={result}
- />
- <Checkbox
- label="Really suspend"
- field={reallySuspend}
- ></Checkbox>
- </div>
- </form>
- );
-}
diff --git a/web/source/settings/admin/accounts/detail/handlesignup.tsx b/web/source/settings/admin/accounts/detail/handlesignup.tsx
deleted file mode 100644
index a61145a22..000000000
--- a/web/source/settings/admin/accounts/detail/handlesignup.tsx
+++ /dev/null
@@ -1,118 +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/>.
-*/
-
-import React from "react";
-import { useLocation } from "wouter";
-
-import { useHandleSignupMutation } from "../../../lib/query";
-
-import MutationButton from "../../../components/form/mutation-button";
-
-import useFormSubmit from "../../../lib/form/submit";
-import {
- useValue,
- useTextInput,
- useBoolInput,
-} from "../../../lib/form";
-
-import { Checkbox, Select, TextInput } from "../../../components/form/inputs";
-import { AdminAccount } from "../../../lib/types/account";
-
-export interface HandleSignupProps {
- account: AdminAccount,
- accountsBaseUrl: string,
-}
-
-export function HandleSignup({account, accountsBaseUrl}: HandleSignupProps) {
- const form = {
- id: useValue("id", account.id),
- approveOrReject: useTextInput("approve_or_reject", { defaultValue: "approve" }),
- privateComment: useTextInput("private_comment"),
- message: useTextInput("message"),
- sendEmail: useBoolInput("send_email"),
- };
-
- const [_location, setLocation] = useLocation();
-
- const [handleSignup, result] = useFormSubmit(form, useHandleSignupMutation(), {
- changedOnly: false,
- // After submitting the form, redirect back to
- // /settings/admin/accounts if rejecting, since
- // account will no longer be available at
- // /settings/admin/accounts/:accountID endpoint.
- onFinish: (res) => {
- if (form.approveOrReject.value === "approve") {
- // An approve request:
- // stay on this page and
- // serve updated details.
- return;
- }
-
- if (res.data) {
- // "reject" successful,
- // redirect to accounts page.
- setLocation(accountsBaseUrl);
- }
- }
- });
-
- return (
- <form
- onSubmit={handleSignup}
- aria-labelledby="account-handle-signup"
- >
- <h3 id="account-handle-signup">Handle Account Sign-Up</h3>
- <Select
- field={form.approveOrReject}
- label="Approve or Reject"
- options={
- <>
- <option value="approve">Approve</option>
- <option value="reject">Reject</option>
- </>
- }
- >
- </Select>
- { form.approveOrReject.value === "reject" &&
- // Only show form fields relevant
- // to "reject" if rejecting.
- // On "approve" these fields will
- // be ignored anyway.
- <>
- <TextInput
- field={form.privateComment}
- label="(Optional) private comment on why sign-up was rejected (shown to other admins only)"
- />
- <Checkbox
- field={form.sendEmail}
- label="Send email to applicant"
- />
- <TextInput
- field={form.message}
- label={"(Optional) message to include in email to applicant, if send email is checked"}
- />
- </> }
- <MutationButton
- disabled={false}
- label={form.approveOrReject.value === "approve" ? "Approve" : "Reject"}
- result={result}
- />
- </form>
- );
-}
diff --git a/web/source/settings/admin/accounts/detail/index.tsx b/web/source/settings/admin/accounts/detail/index.tsx
deleted file mode 100644
index 79eb493de..000000000
--- a/web/source/settings/admin/accounts/detail/index.tsx
+++ /dev/null
@@ -1,179 +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/>.
-*/
-
-import React from "react";
-import { useRoute, Redirect } from "wouter";
-
-import { useGetAccountQuery } from "../../../lib/query";
-
-import FormWithData from "../../../lib/form/form-with-data";
-
-import { useBaseUrl } from "../../../lib/navigation/util";
-import FakeProfile from "../../../components/fake-profile";
-
-import { AdminAccount } from "../../../lib/types/account";
-import { HandleSignup } from "./handlesignup";
-import { AccountActions } from "./actions";
-import BackButton from "../../../components/back-button";
-
-export default function AccountDetail() {
- // /settings/admin/accounts
- const accountsBaseUrl = useBaseUrl();
-
- let [_match, params] = useRoute(`${accountsBaseUrl}/:accountId`);
-
- if (params?.accountId == undefined) {
- return <Redirect to={accountsBaseUrl} />;
- } else {
- return (
- <div className="account-detail">
- <h1 className="text-cutoff">
- <BackButton to={accountsBaseUrl} /> Account Details
- </h1>
- <FormWithData
- dataQuery={useGetAccountQuery}
- queryArg={params.accountId}
- DataForm={AccountDetailForm}
- {...{accountsBaseUrl}}
- />
- </div>
- );
- }
-}
-
-interface AccountDetailFormProps {
- accountsBaseUrl: string,
- data: AdminAccount,
-}
-
-function AccountDetailForm({ data: adminAcct, accountsBaseUrl }: AccountDetailFormProps) {
- let yesOrNo = (b: boolean) => {
- return b ? "yes" : "no";
- };
-
- let created = new Date(adminAcct.created_at).toDateString();
- let lastPosted = "never";
- if (adminAcct.account.last_status_at) {
- lastPosted = new Date(adminAcct.account.last_status_at).toDateString();
- }
- const local = !adminAcct.domain;
-
- return (
- <>
- <FakeProfile {...adminAcct.account} />
- <h3>General Account Details</h3>
- { adminAcct.suspended &&
- <div className="info">
- <i className="fa fa-fw fa-info-circle" aria-hidden="true"></i>
- <b>Account is suspended.</b>
- </div>
- }
- <dl className="info-list">
- { !local &&
- <div className="info-list-entry">
- <dt>Domain</dt>
- <dd>{adminAcct.domain}</dd>
- </div>}
- <div className="info-list-entry">
- <dt>Created</dt>
- <dd><time dateTime={adminAcct.created_at}>{created}</time></dd>
- </div>
- <div className="info-list-entry">
- <dt>Last posted</dt>
- <dd>{lastPosted}</dd>
- </div>
- <div className="info-list-entry">
- <dt>Suspended</dt>
- <dd>{yesOrNo(adminAcct.suspended)}</dd>
- </div>
- <div className="info-list-entry">
- <dt>Silenced</dt>
- <dd>{yesOrNo(adminAcct.silenced)}</dd>
- </div>
- <div className="info-list-entry">
- <dt>Statuses</dt>
- <dd>{adminAcct.account.statuses_count}</dd>
- </div>
- <div className="info-list-entry">
- <dt>Followers</dt>
- <dd>{adminAcct.account.followers_count}</dd>
- </div>
- <div className="info-list-entry">
- <dt>Following</dt>
- <dd>{adminAcct.account.following_count}</dd>
- </div>
- </dl>
- { local &&
- // Only show local account details
- // if this is a local account!
- <>
- <h3>Local Account Details</h3>
- { !adminAcct.approved &&
- <div className="info">
- <i className="fa fa-fw fa-info-circle" aria-hidden="true"></i>
- <b>Account is pending.</b>
- </div>
- }
- { !adminAcct.confirmed &&
- <div className="info">
- <i className="fa fa-fw fa-info-circle" aria-hidden="true"></i>
- <b>Account email not yet confirmed.</b>
- </div>
- }
- <dl className="info-list">
- <div className="info-list-entry">
- <dt>Email</dt>
- <dd>{adminAcct.email} {<b>{adminAcct.confirmed ? "(confirmed)" : "(not confirmed)"}</b> }</dd>
- </div>
- <div className="info-list-entry">
- <dt>Disabled</dt>
- <dd>{yesOrNo(adminAcct.disabled)}</dd>
- </div>
- <div className="info-list-entry">
- <dt>Approved</dt>
- <dd>{yesOrNo(adminAcct.approved)}</dd>
- </div>
- <div className="info-list-entry">
- <dt>Sign-Up Reason</dt>
- <dd>{adminAcct.invite_request ?? <i>none provided</i>}</dd>
- </div>
- { (adminAcct.ip && adminAcct.ip !== "0.0.0.0") &&
- <div className="info-list-entry">
- <dt>Sign-Up IP</dt>
- <dd>{adminAcct.ip}</dd>
- </div> }
- { adminAcct.locale &&
- <div className="info-list-entry">
- <dt>Locale</dt>
- <dd>{adminAcct.locale}</dd>
- </div> }
- </dl>
- </> }
- { local && !adminAcct.approved
- ?
- <HandleSignup
- account={adminAcct}
- accountsBaseUrl={accountsBaseUrl}
- />
- :
- <AccountActions account={adminAcct} />
- }
- </>
- );
-}
diff --git a/web/source/settings/admin/accounts/index.tsx b/web/source/settings/admin/accounts/index.tsx
deleted file mode 100644
index 3c69f7406..000000000
--- a/web/source/settings/admin/accounts/index.tsx
+++ /dev/null
@@ -1,49 +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/>.
-*/
-
-import React from "react";
-import { Switch, Route } from "wouter";
-
-import AccountDetail from "./detail";
-import { AccountSearchForm } from "./search";
-
-export default function Accounts({ baseUrl }) {
- return (
- <Switch>
- <Route path={`${baseUrl}/:accountId`}>
- <AccountDetail />
- </Route>
- <AccountOverview />
- </Switch>
- );
-}
-
-function AccountOverview({ }) {
- return (
- <div className="accounts-view">
- <h1>Accounts Overview</h1>
- <span>
- You can perform actions on an account by clicking
- its name in a report, or by searching for the account
- using the form below and clicking on its name.
- </span>
- <AccountSearchForm />
- </div>
- );
-}
diff --git a/web/source/settings/admin/accounts/pending/index.tsx b/web/source/settings/admin/accounts/pending/index.tsx
deleted file mode 100644
index 459472147..000000000
--- a/web/source/settings/admin/accounts/pending/index.tsx
+++ /dev/null
@@ -1,40 +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/>.
-*/
-
-import React from "react";
-import { useSearchAccountsQuery } from "../../../lib/query";
-import { AccountList } from "../../../components/account-list";
-
-export default function AccountsPending() {
- const searchRes = useSearchAccountsQuery({status: "pending"});
-
- return (
- <div className="accounts-view">
- <h1>Pending Accounts</h1>
- <AccountList
- isLoading={searchRes.isLoading}
- isSuccess={searchRes.isSuccess}
- data={searchRes.data}
- isError={searchRes.isError}
- error={searchRes.error}
- emptyMessage="No pending account sign-ups."
- />
- </div>
- );
-}
diff --git a/web/source/settings/admin/accounts/search/index.tsx b/web/source/settings/admin/accounts/search/index.tsx
deleted file mode 100644
index 560bbb76a..000000000
--- a/web/source/settings/admin/accounts/search/index.tsx
+++ /dev/null
@@ -1,125 +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/>.
-*/
-
-import React from "react";
-
-import { useLazySearchAccountsQuery } from "../../../lib/query";
-import { useTextInput } from "../../../lib/form";
-
-import { AccountList } from "../../../components/account-list";
-import { SearchAccountParams } from "../../../lib/types/account";
-import { Select, TextInput } from "../../../components/form/inputs";
-import MutationButton from "../../../components/form/mutation-button";
-
-export function AccountSearchForm() {
- const [searchAcct, searchRes] = useLazySearchAccountsQuery();
-
- const form = {
- origin: useTextInput("origin"),
- status: useTextInput("status"),
- permissions: useTextInput("permissions"),
- username: useTextInput("username"),
- display_name: useTextInput("display_name"),
- by_domain: useTextInput("by_domain"),
- email: useTextInput("email"),
- ip: useTextInput("ip"),
- };
-
- function submitSearch(e) {
- e.preventDefault();
-
- // Parse query parameters.
- const entries = Object.entries(form).map(([k, v]) => {
- // Take only defined form fields.
- if (v.value === undefined || v.value.length === 0) {
- return null;
- }
- return [[k, v.value]];
- }).flatMap(kv => {
- // Remove any nulls.
- return kv || [];
- });
-
- const params: SearchAccountParams = Object.fromEntries(entries);
- searchAcct(params);
- }
-
- return (
- <>
- <form onSubmit={submitSearch}>
- <TextInput
- field={form.username}
- label={"(Optional) username (without leading '@' symbol)"}
- placeholder="someone"
- />
- <TextInput
- field={form.by_domain}
- label={"(Optional) domain"}
- placeholder="example.org"
- />
- <Select
- field={form.origin}
- label="Account origin"
- options={
- <>
- <option value="">Local or remote</option>
- <option value="local">Local only</option>
- <option value="remote">Remote only</option>
- </>
- }
- ></Select>
- <TextInput
- field={form.email}
- label={"(Optional) email address (local accounts only)"}
- placeholder={"someone@example.org"}
- />
- <TextInput
- field={form.ip}
- label={"(Optional) IP address (local accounts only)"}
- placeholder={"198.51.100.0"}
- />
- <Select
- field={form.status}
- label="Account status"
- options={
- <>
- <option value="">Any</option>
- <option value="pending">Pending only</option>
- <option value="disabled">Disabled only</option>
- <option value="suspended">Suspended only</option>
- </>
- }
- ></Select>
- <MutationButton
- disabled={false}
- label={"Search"}
- result={searchRes}
- />
- </form>
- <AccountList
- isLoading={searchRes.isLoading}
- isSuccess={searchRes.isSuccess}
- data={searchRes.data}
- isError={searchRes.isError}
- error={searchRes.error}
- emptyMessage="No accounts found that match your query"
- />
- </>
- );
-}
diff --git a/web/source/settings/admin/actions/keys/expireremote.tsx b/web/source/settings/admin/actions/keys/expireremote.tsx
deleted file mode 100644
index 3b5da2836..000000000
--- a/web/source/settings/admin/actions/keys/expireremote.tsx
+++ /dev/null
@@ -1,63 +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/>.
-*/
-
-import React from "react";
-
-import { useInstanceKeysExpireMutation } from "../../../lib/query";
-
-import { useTextInput } from "../../../lib/form";
-import { TextInput } from "../../../components/form/inputs";
-
-import MutationButton from "../../../components/form/mutation-button";
-
-export default function ExpireRemote({}) {
- const domainField = useTextInput("domain");
-
- const [expire, expireResult] = useInstanceKeysExpireMutation();
-
- function submitExpire(e) {
- e.preventDefault();
- expire(domainField.value);
- }
-
- return (
- <form onSubmit={submitExpire}>
- <h2>Expire remote instance keys</h2>
- <p>
- Mark all public keys from the given remote instance as expired.<br/><br/>
- This is useful in cases where the remote domain has had to rotate their keys for whatever
- reason (security issue, data leak, routine safety procedure, etc), and your instance can no
- longer communicate with theirs properly using cached keys. A key marked as expired in this way
- will be lazily refetched next time a request is made to your instance signed by the owner of that
- key.
- </p>
- <TextInput
- field={domainField}
- label="Domain"
- type="string"
- placeholder="example.org"
- />
- <MutationButton
- disabled={false}
- label="Expire keys"
- result={expireResult}
- />
- </form>
- );
-}
diff --git a/web/source/settings/admin/actions/keys/index.tsx b/web/source/settings/admin/actions/keys/index.tsx
deleted file mode 100644
index 74bfd36ee..000000000
--- a/web/source/settings/admin/actions/keys/index.tsx
+++ /dev/null
@@ -1,30 +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/>.
-*/
-
-import React from "react";
-import ExpireRemote from "./expireremote";
-
-export default function Keys() {
- return (
- <>
- <h1>Key Actions</h1>
- <ExpireRemote />
- </>
- );
-}
diff --git a/web/source/settings/admin/actions/media/cleanup.tsx b/web/source/settings/admin/actions/media/cleanup.tsx
deleted file mode 100644
index fd3ca1f41..000000000
--- a/web/source/settings/admin/actions/media/cleanup.tsx
+++ /dev/null
@@ -1,61 +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/>.
-*/
-
-import React from "react";
-
-import { useMediaCleanupMutation } from "../../../lib/query";
-
-import { useTextInput } from "../../../lib/form";
-import { TextInput } from "../../../components/form/inputs";
-
-import MutationButton from "../../../components/form/mutation-button";
-
-export default function Cleanup({}) {
- const daysField = useTextInput("days", { defaultValue: "30" });
-
- const [mediaCleanup, mediaCleanupResult] = useMediaCleanupMutation();
-
- function submitCleanup(e) {
- e.preventDefault();
- mediaCleanup(daysField.value);
- }
-
- return (
- <form onSubmit={submitCleanup}>
- <h2>Cleanup</h2>
- <p>
- Clean up remote media older than the specified number of days.
- If the remote instance is still online they will be refetched when needed.
- Also cleans up unused headers and avatars from the media cache.
- </p>
- <TextInput
- field={daysField}
- label="Days"
- type="number"
- min="0"
- placeholder="30"
- />
- <MutationButton
- disabled={false}
- label="Remove old media"
- result={mediaCleanupResult}
- />
- </form>
- );
-}
diff --git a/web/source/settings/admin/actions/media/index.tsx b/web/source/settings/admin/actions/media/index.tsx
deleted file mode 100644
index b3b805986..000000000
--- a/web/source/settings/admin/actions/media/index.tsx
+++ /dev/null
@@ -1,30 +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/>.
-*/
-
-import React from "react";
-import Cleanup from "./cleanup";
-
-export default function Media() {
- return (
- <>
- <h1>Media Actions</h1>
- <Cleanup />
- </>
- );
-}
diff --git a/web/source/settings/admin/domain-permissions/detail.tsx b/web/source/settings/admin/domain-permissions/detail.tsx
deleted file mode 100644
index f74802666..000000000
--- a/web/source/settings/admin/domain-permissions/detail.tsx
+++ /dev/null
@@ -1,254 +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/>.
-*/
-
-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/domain-permissions/export-format-table.jsx b/web/source/settings/admin/domain-permissions/export-format-table.jsx
deleted file mode 100644
index 7fcffa348..000000000
--- a/web/source/settings/admin/domain-permissions/export-format-table.jsx
+++ /dev/null
@@ -1,65 +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");
-
-module.exports = function ExportFormatTable() {
- return (
- <div className="export-format-table-wrapper without-border">
- <table className="export-format-table">
- <thead>
- <tr>
- <th rowSpan={2} />
- <th colSpan={2}>Includes</th>
- <th colSpan={2}>Importable by</th>
- </tr>
- <tr>
- <th>Domain</th>
- <th>Public comment</th>
- <th>GoToSocial</th>
- <th>Mastodon</th>
- </tr>
- </thead>
- <tbody>
- <Format name="Text" info={[true, false, true, false]} />
- <Format name="JSON" info={[true, true, true, false]} />
- <Format name="CSV" info={[true, true, true, true]} />
- </tbody>
- </table>
- </div>
- );
-};
-
-function Format({ name, info }) {
- return (
- <tr>
- <td><b>{name}</b></td>
- {info.map((b, key) => <td key={key} className="bool">{bool(b)}</td>)}
- </tr>
- );
-}
-
-function bool(val) {
- return (
- <>
- <i className={`fa fa-${val ? "check" : "times"}`} aria-hidden="true"></i>
- <span className="sr-only">{val ? "Yes" : "No"}</span>
- </>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/domain-permissions/form.tsx b/web/source/settings/admin/domain-permissions/form.tsx
deleted file mode 100644
index 57502d6d9..000000000
--- a/web/source/settings/admin/domain-permissions/form.tsx
+++ /dev/null
@@ -1,153 +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/>.
-*/
-
-import React from "react";
-
-import { useEffect } from "react";
-
-import { useExportDomainListMutation } from "../../lib/query/admin/domain-permissions/export";
-import useFormSubmit from "../../lib/form/submit";
-
-import {
- RadioGroup,
- TextArea,
- Select,
-} from "../../components/form/inputs";
-
-import MutationButton from "../../components/form/mutation-button";
-
-import { Error } from "../../components/error";
-import ExportFormatTable from "./export-format-table";
-
-import type {
- FormSubmitFunction,
- FormSubmitResult,
- RadioFormInputHook,
- TextFormInputHook,
-} from "../../lib/form/types";
-
-export interface ImportExportFormProps {
- form: {
- domains: TextFormInputHook;
- exportType: TextFormInputHook;
- permType: RadioFormInputHook;
- };
- submitParse: FormSubmitFunction;
- parseResult: FormSubmitResult;
-}
-
-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) {
- const res = read.target?.result;
- if (typeof res === "string") {
- form.domains.value = res;
- submitParse();
- }
- };
- reader.readAsText(e.target.files[0]);
- }
-
- useEffect(() => {
- if (exportResult.isSuccess) {
- form.domains.setter(exportResult.data);
- }
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, [exportResult]);
-
- return (
- <>
- <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
- field={form.domains}
- label="Domains"
- placeholder={`google.com\nfacebook.com`}
- rows={8}
- />
-
- <RadioGroup
- field={form.permType}
- />
-
- <div className="button-grid">
- <MutationButton
- label="Import"
- type="button"
- onClick={() => submitParse()}
- result={parseResult}
- showError={false}
- disabled={form.permType.value === undefined || form.permType.value.length === 0}
- />
- <label className={`button with-icon${form.permType.value === undefined || form.permType.value.length === 0 ? " disabled" : ""}`}>
- <i className="fa fa-fw " aria-hidden="true" />
- Import file
- <input
- type="file"
- className="hidden"
- onChange={fileChanged}
- accept="application/json,text/plain,text/csv"
- disabled={form.permType.value === undefined || form.permType.value.length === 0}
- />
- </label>
- <b /> {/* grid filler */}
- <MutationButton
- label="Export"
- type="button"
- onClick={() => submitExport("export")}
- result={exportResult} showError={false}
- disabled={form.permType.value === undefined || form.permType.value.length === 0}
- />
- <MutationButton
- label="Export to file"
- wrapperClassName="export-file-button"
- type="button"
- onClick={() => submitExport("export-file")}
- result={exportResult}
- showError={false}
- disabled={form.permType.value === undefined || form.permType.value.length === 0}
- />
- <div className="export-file">
- <span>
- as
- </span>
- <Select
- field={form.exportType}
- options={<>
- <option value="plain">Text</option>
- <option value="json">JSON</option>
- <option value="csv">CSV</option>
- </>}
- />
- </div>
- </div>
-
- {parseResult.error && <Error error={parseResult.error} />}
- {exportResult.error && <Error error={exportResult.error} />}
- </div>
- </>
- );
-}
diff --git a/web/source/settings/admin/domain-permissions/import-export.tsx b/web/source/settings/admin/domain-permissions/import-export.tsx
deleted file mode 100644
index 871bca131..000000000
--- a/web/source/settings/admin/domain-permissions/import-export.tsx
+++ /dev/null
@@ -1,90 +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/>.
-*/
-
-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);
- }}
- >
- &lt; back
- </span>
- &nbsp; 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/domain-permissions/index.tsx b/web/source/settings/admin/domain-permissions/index.tsx
deleted file mode 100644
index 7d790cfc8..000000000
--- a/web/source/settings/admin/domain-permissions/index.tsx
+++ /dev/null
@@ -1,49 +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/>.
-*/
-
-import React from "react";
-import { Switch, Route } from "wouter";
-
-import DomainPermissionsOverview from "./overview";
-import { PermType } from "../../lib/types/domain-permission";
-import DomainPermDetail from "./detail";
-
-export default function DomainPermissions({ baseUrl }: { baseUrl: string }) {
- return (
- <Switch>
- <Route path="/settings/admin/domain-permissions/:permType/:domain">
- {params => (
- <DomainPermDetail
- permType={params.permType as PermType}
- baseUrl={baseUrl}
- domain={params.domain}
- />
- )}
- </Route>
- <Route path="/settings/admin/domain-permissions/:permType">
- {params => (
- <DomainPermissionsOverview
- permType={params.permType as PermType}
- baseUrl={baseUrl}
- />
- )}
- </Route>
- </Switch>
- );
-}
diff --git a/web/source/settings/admin/domain-permissions/overview.tsx b/web/source/settings/admin/domain-permissions/overview.tsx
deleted file mode 100644
index bdfd214bc..000000000
--- a/web/source/settings/admin/domain-permissions/overview.tsx
+++ /dev/null
@@ -1,198 +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/>.
-*/
-
-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="/settings/admin/domain-permissions/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}&nbsp;{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/domain-permissions/process.tsx b/web/source/settings/admin/domain-permissions/process.tsx
deleted file mode 100644
index bb9411b9d..000000000
--- a/web/source/settings/admin/domain-permissions/process.tsx
+++ /dev/null
@@ -1,402 +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/>.
-*/
-
-import React from "react";
-
-import { memo, useMemo, useCallback, useEffect } from "react";
-
-import { isValidDomainPermission, hasBetterScope } from "../../lib/util/domain-permission";
-
-import {
- useTextInput,
- useBoolInput,
- useRadioInput,
- useCheckListInput,
-} from "../../lib/form";
-
-import {
- Select,
- TextArea,
- RadioGroup,
- Checkbox,
- TextInput,
-} from "../../components/form/inputs";
-
-import useFormSubmit from "../../lib/form/submit";
-
-import CheckList from "../../components/check-list";
-import MutationButton from "../../components/form/mutation-button";
-import FormWithData from "../../lib/form/form-with-data";
-
-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={permType.value == "allow"
- ? useDomainAllowsQuery
- : useDomainBlocksQuery
- }
- DataForm={ImportList}
- {...{ list, permType }}
- />
- </div>
- );
- }
-);
-
-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) {
- hasPublic = true;
- }
-
- if (entry.private_comment) {
- hasPrivate = true;
- }
-
- return hasPublic && hasPrivate;
- });
-
- if (hasPublic && hasPrivate) {
- return { both: true };
- } else if (hasPublic) {
- return { type: "public_comment" };
- } else if (hasPrivate) {
- return { type: "private_comment" };
- } else {
- return {};
- }
- }, [list]);
-
- const showComment = useTextInput("showComment", { defaultValue: hasComment.type ?? "public_comment" });
-
- const form = {
- domains: useCheckListInput("domains", { entries: list }), // DomainPerm is actually also a Checkable.
- obfuscate: useBoolInput("obfuscate"),
- privateComment: useTextInput("private_comment", {
- defaultValue: `Imported on ${new Date().toLocaleString()}`
- }),
- privateCommentBehavior: useRadioInput("private_comment_behavior", {
- defaultValue: "append",
- options: {
- append: "Append to",
- replace: "Replace"
- }
- }),
- publicComment: useTextInput("public_comment"),
- publicCommentBehavior: useRadioInput("public_comment_behavior", {
- defaultValue: "append",
- options: {
- append: "Append to",
- replace: "Replace"
- }
- }),
- permType: permType,
- };
-
- const [importDomains, importResult] = useFormSubmit(form, useImportDomainPermsMutation(), { changedOnly: false });
-
- return (
- <>
- <form
- onSubmit={importDomains}
- className="domain-perm-import-list"
- >
- <span>{list.length} domain{list.length != 1 ? "s" : ""} in this list</span>
-
- {hasComment.both &&
- <Select field={showComment} options={
- <>
- <option value="public_comment">Show public comments</option>
- <option value="private_comment">Show private comments</option>
- </>
- } />
- }
-
- <div className="checkbox-list-wrapper">
- <DomainCheckList
- field={form.domains}
- domainPerms={domainPerms}
- commentType={showComment.value as "public_comment" | "private_comment"}
- permType={form.permType}
- />
- </div>
-
- <TextArea
- field={form.privateComment}
- label="Private comment"
- rows={3}
- />
- <RadioGroup
- field={form.privateCommentBehavior}
- label="imported private comment"
- />
-
- <TextArea
- field={form.publicComment}
- label="Public comment"
- rows={3}
- />
- <RadioGroup
- field={form.publicCommentBehavior}
- label="imported public comment"
- />
-
- <Checkbox
- field={form.obfuscate}
- label="Obfuscate domains in public lists"
- />
-
- <MutationButton
- label="Import"
- disabled={false}
- result={importResult}
- />
- </form>
- </>
- );
-}
-
-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: entry.domain in domainPerms,
- permType: permType,
- };
- }, [domainPerms, commentType, permType]);
-
- 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 as ChecklistInputHook}
- header={<>
- <b>Domain</b>
- <b>
- {commentType == "public_comment" && "Public comment"}
- {commentType == "private_comment" && "Private comment"}
- </b>
- </>}
- EntryComponent={DomainEntry}
- getExtraProps={getExtraProps}
- />
- <UpdateHint
- entries={entriesWithSuggestions}
- updateEntry={field.onChange}
- updateMultiple={field.updateMultiple}
- />
- </>
- );
-}
-
-interface UpdateHintProps {
- entries,
- updateEntry,
- updateMultiple,
-}
-
-const UpdateHint = memo(
- function UpdateHint({ entries, updateEntry, updateMultiple }: UpdateHintProps) {
- if (entries.length == 0) {
- return null;
- }
-
- function changeAll() {
- updateMultiple(
- entries.map((entry) => [entry.key, { domain: entry.suggest, suggest: null }])
- );
- }
-
- return (
- <div className="update-hints">
- <p>
- {entries.length} {entries.length == 1 ? "entry uses" : "entries use"} a specific subdomain,
- which you might want to change to the main domain, as that includes all it's (future) subdomains.
- </p>
- <div className="hints">
- {entries.map((entry) => (
- <UpdateableEntry key={entry.key} entry={entry} updateEntry={updateEntry} />
- ))}
- </div>
- {entries.length > 0 && <a onClick={changeAll}>change all</a>}
- </div>
- );
- }
-);
-
-interface UpdateableEntryProps {
- entry,
- updateEntry,
-}
-
-const UpdateableEntry = memo(
- function UpdateableEntry({ entry, updateEntry }: UpdateableEntryProps) {
- return (
- <>
- <span className="text-cutoff">{entry.domain}</span>
- <i className="fa fa-long-arrow-right" aria-hidden="true"></i>
- <span>{entry.suggest}</span>
- <a role="button" onClick={() =>
- updateEntry(entry.key, { domain: entry.suggest, suggest: null })
- }>change</a>
- </>
- );
- }
-);
-
-function domainValidationError(isValid) {
- return isValid ? "" : "Invalid domain";
-}
-
-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(isValidDomainPermission(value))
- });
-
- useEffect(() => {
- if (entry.valid != domainField.valid) {
- onChange({ valid: domainField.valid });
- }
- }, [onChange, entry.valid, domainField.valid]);
-
- useEffect(() => {
- if (entry.domain != domainField.value) {
- domainField.setter(entry.domain);
- }
- // domainField.setter is enough, eslint wants domainField
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [entry.domain, domainField.setter]);
-
- 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]);
-
- function clickIcon(e) {
- if (entry.suggest) {
- e.stopPropagation();
- e.preventDefault();
- domainField.setter(entry.suggest);
- onChange({ domain: entry.suggest, checked: true });
- }
- }
-
- return (
- <>
- <div className="domain-input">
- <TextInput
- field={domainField}
- onChange={(e) => {
- domainField.onChange(e);
- onChange({ domain: e.target.value, checked: true });
- }}
- />
- <span id="icon" onClick={clickIcon}>
- <DomainEntryIcon
- alreadyExists={alreadyExists}
- suggestion={entry.suggest}
- permTypeString={permType.value?? ""}
- />
- </span>
- </div>
- <p>{comment}</p>
- </>
- );
-}
-
-interface DomainEntryIconProps {
- alreadyExists: boolean;
- suggestion: string;
- permTypeString: string;
-}
-
-function DomainEntryIcon({ alreadyExists, suggestion, permTypeString }: DomainEntryIconProps) {
- let icon;
- let text;
-
- if (suggestion) {
- icon = "fa-info-circle suggest-changes";
- text = `Entry targets a specific subdomain, consider changing it to '${suggestion}'.`;
- } else if (alreadyExists) {
- icon = "fa-history permission-already-exists";
- text = `Domain ${permTypeString} already exists.`;
- }
-
- if (!icon) {
- return null;
- }
-
- return (
- <>
- <i className={`fa fa-fw ${icon}`} aria-hidden="true" title={text}></i>
- <span className="sr-only">{text}</span>
- </>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/category-select.jsx b/web/source/settings/admin/emoji/category-select.jsx
deleted file mode 100644
index e5cf29939..000000000
--- a/web/source/settings/admin/emoji/category-select.jsx
+++ /dev/null
@@ -1,96 +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 splitFilterN = require("split-filter-n");
-const syncpipe = require('syncpipe');
-const { matchSorter } = require("match-sorter");
-
-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)
- return React.useMemo(() => splitFilterN(
- emoji,
- [],
- (entry) => entry.category ?? "Unsorted"
- ), [emoji]);
-}
-
-function CategorySelect({ field, children }) {
- const { value, setIsNew } = field;
-
- const {
- data: emoji = [],
- isLoading,
- isSuccess,
- error
- } = useListEmojiQuery({ filter: "domain:local" });
-
- const emojiByCategory = useEmojiByCategory(emoji);
-
- const categories = React.useMemo(() => new Set(Object.keys(emojiByCategory)), [emojiByCategory]);
-
- // data used by the ComboBox element to select an emoji category
- const categoryItems = React.useMemo(() => {
- return syncpipe(emojiByCategory, [
- (_) => Object.keys(_), // just emoji category names
- (_) => matchSorter(_, value, { threshold: matchSorter.rankings.NO_MATCH }), // sorted by complex algorithm
- (_) => _.map((categoryName) => [ // map to input value, and selectable element with icon
- categoryName,
- <>
- <img src={emojiByCategory[categoryName][0].static_url} aria-hidden="true"></img>
- {categoryName}
- </>
- ])
- ]);
- }, [emojiByCategory, value]);
-
- React.useEffect(() => {
- if (value != undefined && isSuccess && value.trim().length > 0) {
- setIsNew(!categories.has(value.trim()));
- }
- }, [categories, value, isSuccess, setIsNew]);
-
- if (error) { // fall back to plain text input, but this would almost certainly have caused a bigger error message elsewhere
- return (
- <>
- <input type="text" placeholder="e.g., reactions" onChange={(e) => { field.value = e.target.value; }} />;
- </>
- );
- } else if (isLoading) {
- return <input type="text" value="Loading categories..." disabled={true} />;
- }
-
- return (
- <ComboBox
- field={field}
- items={categoryItems}
- label="Category"
- placeholder="e.g., reactions"
- children={children}
- />
- );
-}
-
-module.exports = {
- useEmojiByCategory,
- CategorySelect
-}; \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/local/detail.js b/web/source/settings/admin/emoji/local/detail.js
deleted file mode 100644
index a78e3e499..000000000
--- a/web/source/settings/admin/emoji/local/detail.js
+++ /dev/null
@@ -1,146 +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/>.
-*/
-
-import React, { useEffect } from "react";
-import { useRoute, Link, Redirect } from "wouter";
-
-import { useComboBoxInput, useFileInput, useValue } from "../../../lib/form";
-import { CategorySelect } from "../category-select";
-
-import useFormSubmit from "../../../lib/form/submit";
-import { useBaseUrl } from "../../../lib/navigation/util";
-
-import FakeToot from "../../../components/fake-toot";
-import FormWithData from "../../../lib/form/form-with-data";
-import Loading from "../../../components/loading";
-import { FileInput } from "../../../components/form/inputs";
-import MutationButton from "../../../components/form/mutation-button";
-import { Error } from "../../../components/error";
-
-import { useGetEmojiQuery, useEditEmojiMutation, useDeleteEmojiMutation } from "../../../lib/query/admin/custom-emoji";
-
-export default function EmojiDetailRoute({ }) {
- const baseUrl = useBaseUrl();
- let [_match, params] = useRoute(`${baseUrl}/:emojiId`);
- if (params?.emojiId == undefined) {
- return <Redirect to={baseUrl} />;
- } else {
- return (
- <div className="emoji-detail">
- <Link to={baseUrl}><a>&lt; go back</a></Link>
- <FormWithData dataQuery={useGetEmojiQuery} queryArg={params.emojiId} DataForm={EmojiDetailForm} />
- </div>
- );
- }
-}
-
-function EmojiDetailForm({ data: emoji }) {
- const baseUrl = useBaseUrl();
- const form = {
- id: useValue("id", emoji.id),
- category: useComboBoxInput("category", { source: emoji }),
- image: useFileInput("image", {
- withPreview: true,
- maxSize: 50 * 1024 // TODO: get from instance api
- })
- };
-
- const [modifyEmoji, result] = useFormSubmit(form, useEditEmojiMutation());
-
- // Automatic submitting of category change
- useEffect(() => {
- if (
- form.category.hasChanged() &&
- !form.category.state.open &&
- !form.category.isNew) {
- modifyEmoji();
- }
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, [form.category.hasChanged(), form.category.isNew, form.category.state.open]);
-
- const [deleteEmoji, deleteResult] = useDeleteEmojiMutation();
-
- if (deleteResult.isSuccess) {
- return <Redirect to={baseUrl} />;
- }
-
- return (
- <>
- <div className="emoji-header">
- <img src={emoji.url} alt={emoji.shortcode} title={emoji.shortcode} />
- <div>
- <h2>{emoji.shortcode}</h2>
- <MutationButton
- label="Delete"
- type="button"
- onClick={() => deleteEmoji(emoji.id)}
- className="danger"
- showError={false}
- result={deleteResult}
- />
- </div>
- </div>
-
- <form onSubmit={modifyEmoji} className="left-border">
- <h2>Modify this emoji {result.isLoading && <Loading />}</h2>
-
- <div className="update-category">
- <CategorySelect
- field={form.category}
- >
- <MutationButton
- name="create-category"
- label="Create"
- result={result}
- showError={false}
- style={{ visibility: (form.category.isNew ? "initial" : "hidden") }}
- />
- </CategorySelect>
- </div>
-
- <div className="update-image">
- <FileInput
- field={form.image}
- label="Image"
- accept="image/png,image/gif"
- />
-
- <MutationButton
- name="image"
- label="Replace image"
- showError={false}
- result={result}
- />
-
- <FakeToot>
- Look at this new custom emoji <img
- className="emoji"
- src={form.image.previewURL ?? emoji.url}
- title={`:${emoji.shortcode}:`}
- alt={emoji.shortcode}
- /> isn&apos;t it cool?
- </FakeToot>
-
- {result.error && <Error error={result.error} />}
- {deleteResult.error && <Error error={deleteResult.error} />}
- </div>
- </form>
- </>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/local/index.tsx b/web/source/settings/admin/emoji/local/index.tsx
deleted file mode 100644
index 74a891f3e..000000000
--- a/web/source/settings/admin/emoji/local/index.tsx
+++ /dev/null
@@ -1,35 +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/>.
-*/
-
-import React from "react";
-import { Switch, Route } from "wouter";
-
-import EmojiOverview from "./overview";
-import EmojiDetail from "./detail";
-
-export default function CustomEmoji({ baseUrl }) {
- return (
- <Switch>
- <Route path={`${baseUrl}/:emojiId`}>
- <EmojiDetail />
- </Route>
- <EmojiOverview />
- </Switch>
- );
-}
diff --git a/web/source/settings/admin/emoji/local/new-emoji.tsx b/web/source/settings/admin/emoji/local/new-emoji.tsx
deleted file mode 100644
index c6a203765..000000000
--- a/web/source/settings/admin/emoji/local/new-emoji.tsx
+++ /dev/null
@@ -1,116 +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/>.
-*/
-
-import React, { useMemo, useEffect } from "react";
-
-import { useFileInput, useComboBoxInput } from "../../../lib/form";
-import useShortcode from "./use-shortcode";
-
-import useFormSubmit from "../../../lib/form/submit";
-
-import { TextInput, FileInput } from "../../../components/form/inputs";
-
-import { CategorySelect } from '../category-select';
-import FakeToot from "../../../components/fake-toot";
-import MutationButton from "../../../components/form/mutation-button";
-import { useAddEmojiMutation } from "../../../lib/query/admin/custom-emoji";
-import { useInstanceV1Query } from "../../../lib/query";
-
-export default function NewEmojiForm() {
- const shortcode = useShortcode();
-
- const { data: instance } = useInstanceV1Query();
- const emojiMaxSize = useMemo(() => {
- return instance?.configuration?.emojis?.emoji_size_limit ?? 50 * 1024;
- }, [instance]);
-
- const image = useFileInput("image", {
- withPreview: true,
- maxSize: emojiMaxSize
- });
-
- const category = useComboBoxInput("category");
-
- const [submitForm, result] = useFormSubmit({
- shortcode, image, category
- }, useAddEmojiMutation());
-
- useEffect(() => {
- if (shortcode.value === undefined || shortcode.value.length == 0) {
- if (image.value != undefined) {
- let [name, _ext] = image.value.name.split(".");
- shortcode.setter(name);
- }
- }
-
- /* We explicitly don't want to have 'shortcode' as a dependency here
- because we only want to change the shortcode to the filename if the field is empty
- at the moment the file is selected, not some time after when the field is emptied
- */
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, [image.value]);
-
- let emojiOrShortcode;
-
- if (image.previewValue != undefined) {
- emojiOrShortcode = <img
- className="emoji"
- src={image.previewValue}
- title={`:${shortcode.value}:`}
- alt={shortcode.value}
- />;
- } else if (shortcode.value !== undefined && shortcode.value.length > 0) {
- emojiOrShortcode = `:${shortcode.value}:`;
- } else {
- emojiOrShortcode = `:your_emoji_here:`;
- }
-
- return (
- <div>
- <h2>Add new custom emoji</h2>
-
- <FakeToot>
- Look at this new custom emoji {emojiOrShortcode} isn&apos;t it cool?
- </FakeToot>
-
- <form onSubmit={submitForm} className="form-flex">
- <FileInput
- field={image}
- accept="image/png,image/gif,image/webp"
- />
-
- <TextInput
- field={shortcode}
- label="Shortcode, must be unique among the instance's local emoji"
- />
-
- <CategorySelect
- field={category}
- children={[]}
- />
-
- <MutationButton
- disabled={image.previewValue === undefined}
- label="Upload emoji"
- result={result}
- />
- </form>
- </div>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/local/overview.js b/web/source/settings/admin/emoji/local/overview.js
deleted file mode 100644
index 45bfd614d..000000000
--- a/web/source/settings/admin/emoji/local/overview.js
+++ /dev/null
@@ -1,153 +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 } = require("wouter");
-const syncpipe = require("syncpipe");
-const { matchSorter } = require("match-sorter");
-
-const NewEmojiForm = require("./new-emoji").default;
-const { useTextInput } = require("../../../lib/form");
-
-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 {
- data: emoji = [],
- isLoading,
- isError,
- error
- } = useListEmojiQuery({ filter: "domain:local" });
-
- let content = null;
-
- if (isLoading) {
- content = <Loading />;
- } else if (isError) {
- content = <Error error={error} />;
- } else {
- content = (
- <>
- <EmojiList emoji={emoji} />
- <NewEmojiForm emoji={emoji} />
- </>
- );
- }
-
- return (
- <>
- <h1>Local Custom Emoji</h1>
- <p>
- To use custom emoji in your toots they have to be 'local' to the instance.
- You can either upload them here directly, or copy from those already
- present on other (known) instances through the <Link to={`./remote`}>Remote Emoji</Link> page.
- </p>
- <p>
- <strong>Be warned!</strong> If you upload more than about 300-400 custom emojis in
- total on your instance, this may lead to rate-limiting issues for users and clients
- if they try to load all the emoji images at once (which is what many clients do).
- </p>
- {content}
- </>
- );
-};
-
-function EmojiList({ emoji }) {
- const filterField = useTextInput("filter");
- const filter = filterField.value;
-
- const emojiByCategory = useEmojiByCategory(emoji);
-
- /* Filter emoji based on shortcode match with user input, hiding empty categories */
- const { filteredEmoji, hidden } = React.useMemo(() => {
- let hidden = emoji.length;
- const filteredEmoji = syncpipe(emojiByCategory, [
- (_) => Object.entries(emojiByCategory),
- (_) => _.map(([category, entries]) => {
- let filteredEntries = matchSorter(entries, filter, { keys: ["shortcode"] });
- if (filteredEntries.length == 0) {
- return null;
- } else {
- hidden -= filteredEntries.length;
- return [category, filteredEntries];
- }
- }),
- (_) => _.filter((value) => value !== null)
- ]);
-
- return { filteredEmoji, hidden };
- }, [filter, emojiByCategory, emoji.length]);
-
- return (
- <div>
- <h2>Overview</h2>
- {emoji.length > 0
- ? <span>{emoji.length} custom emoji {hidden > 0 && `(${hidden} filtered)`}</span>
- : <span>No custom emoji yet, you can add one below.</span>
- }
- <div className="list emoji-list">
- <div className="header">
- <TextInput
- field={filterField}
- name="emoji-shortcode"
- placeholder="Search"
- />
- </div>
- <div className="entries scrolling">
- {filteredEmoji.length > 0
- ? (
- <div className="entries scrolling">
- {filteredEmoji.map(([category, entries]) => {
- return <EmojiCategory key={category} category={category} entries={entries} />;
- })}
- </div>
- )
- : <div className="entry">No local emoji matched your filter.</div>
- }
- </div>
- </div>
- </div>
- );
-}
-
-function EmojiCategory({ category, entries }) {
- const baseUrl = useBaseUrl();
- return (
- <div className="entry">
- <b>{category}</b>
- <div className="emoji-group">
- {entries.map((e) => {
- return (
- <Link key={e.id} to={`${baseUrl}/${e.id}`}>
- <a>
- <img src={e.url} alt={e.shortcode} title={`:${e.shortcode}:`} />
- </a>
- </Link>
- );
- })}
- </div>
- </div>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/local/use-shortcode.js b/web/source/settings/admin/emoji/local/use-shortcode.js
deleted file mode 100644
index 67255860f..000000000
--- a/web/source/settings/admin/emoji/local/use-shortcode.js
+++ /dev/null
@@ -1,56 +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 { useTextInput } = require("../../../lib/form");
-const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji");
-
-const shortcodeRegex = /^\w{2,30}$/;
-
-module.exports = function useShortcode() {
- const { data: emoji = [] } = useListEmojiQuery({
- filter: "domain:local"
- });
-
- const emojiCodes = React.useMemo(() => {
- return new Set(emoji.map((e) => e.shortcode));
- }, [emoji]);
-
- return useTextInput("shortcode", {
- validator: function validateShortcode(code) {
- // technically invalid, but hacky fix to prevent validation error on page load
- if (code == "") { return ""; }
-
- if (emojiCodes.has(code)) {
- return "Shortcode already in use";
- }
-
- if (code.length < 2 || code.length > 30) {
- return "Shortcode must be between 2 and 30 characters";
- }
-
- if (!shortcodeRegex.test(code)) {
- return "Shortcode must only contain letters, numbers, and underscores";
- }
-
- return "";
- }
- });
-}; \ No newline at end of file
diff --git a/web/source/settings/admin/emoji/remote/index.tsx b/web/source/settings/admin/emoji/remote/index.tsx
deleted file mode 100644
index d9c786be2..000000000
--- a/web/source/settings/admin/emoji/remote/index.tsx
+++ /dev/null
@@ -1,54 +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/>.
-*/
-
-import React, { useMemo } from "react";
-
-import ParseFromToot from "./parse-from-toot";
-
-import Loading from "../../../components/loading";
-import { Error } from "../../../components/error";
-import { useListEmojiQuery } from "../../../lib/query/admin/custom-emoji";
-
-export default function RemoteEmoji() {
- // local emoji are queried for shortcode collision detection
- const {
- data: emoji = [],
- isLoading,
- error
- } = useListEmojiQuery({ filter: "domain:local" });
-
- const emojiCodes = useMemo(() => {
- return new Set(emoji.map((e) => e.shortcode));
- }, [emoji]);
-
- return (
- <>
- <h1>Custom Emoji (remote)</h1>
- {error &&
- <Error error={error} />
- }
- {isLoading
- ? <Loading />
- : <>
- <ParseFromToot emojiCodes={emojiCodes} />
- </>
- }
- </>
- );
-}
diff --git a/web/source/settings/admin/emoji/remote/parse-from-toot.tsx b/web/source/settings/admin/emoji/remote/parse-from-toot.tsx
deleted file mode 100644
index df1c221ba..000000000
--- a/web/source/settings/admin/emoji/remote/parse-from-toot.tsx
+++ /dev/null
@@ -1,235 +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/>.
-*/
-
-import React, { useCallback, useEffect } from "react";
-
-import { useTextInput, useComboBoxInput, useCheckListInput } from "../../../lib/form";
-
-import useFormSubmit from "../../../lib/form/submit";
-
-import CheckList from "../../../components/check-list";
-import { CategorySelect } from '../category-select';
-
-import { TextInput } from "../../../components/form/inputs";
-import MutationButton from "../../../components/form/mutation-button";
-import { Error } from "../../../components/error";
-import { useSearchItemForEmojiMutation, usePatchRemoteEmojisMutation } from "../../../lib/query/admin/custom-emoji";
-
-export default function ParseFromToot({ emojiCodes }) {
- const [searchStatus, result] = useSearchItemForEmojiMutation();
- const urlField = useTextInput("url");
-
- function submitSearch(e) {
- e.preventDefault();
- if (urlField.value !== undefined && urlField.value.trim().length != 0) {
- searchStatus(urlField.value);
- }
- }
-
- return (
- <div className="parse-emoji">
- <h2>Steal this look</h2>
- <form onSubmit={submitSearch}>
- <div className="form-field text">
- <label htmlFor="url">
- Link to a toot:
- </label>
- <div className="row">
- <input
- type="text"
- id="url"
- name="url"
- onChange={urlField.onChange}
- value={urlField.value}
- />
- <button disabled={result.isLoading}>
- <i className={[
- "fa fa-fw",
- (result.isLoading
- ? "fa-refresh fa-spin"
- : "fa-search")
- ].join(" ")} aria-hidden="true" title="Search" />
- <span className="sr-only">Search</span>
- </button>
- </div>
- </div>
- </form>
- <SearchResult result={result} localEmojiCodes={emojiCodes} />
- </div>
- );
-}
-
-function SearchResult({ result, localEmojiCodes }) {
- const { error, data, isSuccess, isError } = result;
-
- if (!(isSuccess || isError)) {
- return null;
- }
-
- if (error == "NONE_FOUND") {
- return "No results found";
- } else if (error == "LOCAL_INSTANCE") {
- return <b>This is a local user/toot, all referenced emoji are already on your instance</b>;
- } else if (error != undefined) {
- return <Error error={result.error} />;
- }
-
- if (data.list.length == 0) {
- return <b>This {data.type == "statuses" ? "toot" : "account"} doesn't use any custom emoji</b>;
- }
-
- return (
- <CopyEmojiForm
- localEmojiCodes={localEmojiCodes}
- type={data.type}
- emojiList={data.list}
- />
- );
-}
-
-function CopyEmojiForm({ localEmojiCodes, type, emojiList }) {
- const form = {
- selectedEmoji: useCheckListInput("selectedEmoji", {
- entries: emojiList,
- uniqueKey: "id"
- }),
- category: useComboBoxInput("category")
- };
-
- const [formSubmit, result] = useFormSubmit(
- form,
- usePatchRemoteEmojisMutation(),
- {
- changedOnly: false,
- onFinish: ({ data }) => {
- if (data) {
- // uncheck all successfully processed emoji
- const processed = data.map((emoji) => {
- return [emoji.id, { checked: false }];
- });
- form.selectedEmoji.updateMultiple(processed);
- }
- }
- }
- );
-
- const buttonsInactive = form.selectedEmoji.someSelected
- ? {
- disabled: false,
- title: ""
- }
- : {
- disabled: true,
- title: "No emoji selected, cannot perform any actions"
- };
-
- const checkListExtraProps = useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]);
-
- return (
- <div className="parsed">
- <span>This {type == "statuses" ? "toot" : "account"} uses the following custom emoji, select the ones you want to copy/disable:</span>
- <form onSubmit={formSubmit}>
- <CheckList
- field={form.selectedEmoji}
- header={<></>}
- EntryComponent={EmojiEntry}
- getExtraProps={checkListExtraProps}
- />
-
- <CategorySelect
- field={form.category}
- children={[]}
- />
-
- <div className="action-buttons row">
- <MutationButton
- name="copy"
- label="Copy to local emoji"
- result={result}
- showError={false}
- {...buttonsInactive}
- />
- <MutationButton
- name="disable"
- label="Disable"
- result={result}
- className="button danger"
- showError={false}
- {...buttonsInactive}
- />
- </div>
- {result.error && (
- Array.isArray(result.error)
- ? <ErrorList errors={result.error} />
- : <Error error={result.error} />
- )}
- </form>
- </div>
- );
-}
-
-function ErrorList({ errors }) {
- return (
- <div className="error">
- One or multiple emoji failed to process:
- {errors.map(([shortcode, err]) => (
- <div key={shortcode}>
- <b>{shortcode}:</b> {err}
- </div>
- ))}
- </div>
- );
-}
-
-function EmojiEntry({ entry: emoji, onChange, extraProps: { localEmojiCodes } }) {
- const shortcodeField = useTextInput("shortcode", {
- defaultValue: emoji.shortcode,
- validator: function validateShortcode(code) {
- return (emoji.checked && localEmojiCodes.has(code))
- ? "Shortcode already in use"
- : "";
- }
- });
-
- useEffect(() => {
- if (emoji.valid != shortcodeField.valid) {
- onChange({ valid: shortcodeField.valid });
- }
- }, [onChange, emoji.valid, shortcodeField.valid]);
-
- useEffect(() => {
- shortcodeField.validate();
- // only need this update if it's the emoji.checked that updated, not shortcodeField
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [emoji.checked]);
-
- return (
- <>
- <img className="emoji" src={emoji.url} title={emoji.shortcode} />
-
- <TextInput
- field={shortcodeField}
- onChange={(e) => {
- shortcodeField.onChange(e);
- onChange({ shortcode: e.target.value, checked: true });
- }}
- />
- </>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/reports/detail.tsx b/web/source/settings/admin/reports/detail.tsx
deleted file mode 100644
index 94268dc1f..000000000
--- a/web/source/settings/admin/reports/detail.tsx
+++ /dev/null
@@ -1,252 +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/>.
-*/
-
-import React, { useState } from "react";
-import { useRoute, Redirect } from "wouter";
-
-import FormWithData from "../../lib/form/form-with-data";
-import BackButton from "../../components/back-button";
-
-import { useValue, useTextInput } from "../../lib/form";
-import useFormSubmit from "../../lib/form/submit";
-
-import { TextArea } from "../../components/form/inputs";
-
-import MutationButton from "../../components/form/mutation-button";
-import Username from "./username";
-import { useBaseUrl } from "../../lib/navigation/util";
-import { useGetReportQuery, useResolveReportMutation } from "../../lib/query/admin/reports";
-
-export default function ReportDetail({ }) {
- const baseUrl = useBaseUrl();
- let [_match, params] = useRoute(`${baseUrl}/:reportId`);
- if (params?.reportId == undefined) {
- return <Redirect to={baseUrl} />;
- } else {
- return (
- <div className="report-detail">
- <h1>
- <BackButton to={baseUrl} /> Report Details
- </h1>
- <FormWithData
- dataQuery={useGetReportQuery}
- queryArg={params.reportId}
- DataForm={ReportDetailForm}
- />
- </div>
- );
- }
-}
-
-function ReportDetailForm({ data: report }) {
- const from = report.account;
- const target = report.target_account;
-
- return (
- <div className="report detail">
- <div className="usernames">
- <Username user={from} /> reported <Username user={target} />
- </div>
-
- {report.action_taken &&
- <div className="info">
- <h3>Resolved by @{report.action_taken_by_account.account.acct}</h3>
- <span className="timestamp">at {new Date(report.action_taken_at).toLocaleString()}</span>
- <br />
- <b>Comment: </b><span>{report.action_taken_comment}</span>
- </div>
- }
-
- <div className="info-block">
- <h3>Report info:</h3>
- <div className="details">
- <b>Created: </b>
- <span>{new Date(report.created_at).toLocaleString()}</span>
-
- <b>Forwarded: </b> <span>{report.forwarded ? "Yes" : "No"}</span>
- <b>Category: </b> <span>{report.category}</span>
-
- <b>Reason: </b>
- {report.comment.length > 0
- ? <p>{report.comment}</p>
- : <i className="no-comment">none provided</i>
- }
-
- </div>
- </div>
-
- {!report.action_taken && <ReportActionForm report={report} />}
-
- {
- report.statuses.length > 0 &&
- <div className="info-block">
- <h3>Reported toots ({report.statuses.length}):</h3>
- <div className="reported-toots">
- {report.statuses.map((status) => (
- <ReportedToot key={status.id} toot={status} />
- ))}
- </div>
- </div>
- }
- </div>
- );
-}
-
-function ReportActionForm({ report }) {
- const form = {
- id: useValue("id", report.id),
- comment: useTextInput("action_taken_comment")
- };
-
- const [submit, result] = useFormSubmit(form, useResolveReportMutation(), { changedOnly: false });
-
- return (
- <form onSubmit={submit} className="info-block">
- <h3>Resolving this report</h3>
- <p>
- An optional comment can be included while resolving this report.
- Useful for providing an explanation about what action was taken (if any) before the report was marked as resolved.<br />
- <b>This will be visible to the user that created the report!</b>
- </p>
- <TextArea
- field={form.comment}
- label="Comment"
- />
- <MutationButton
- disabled={false}
- label="Resolve"
- result={result}
- />
- </form>
- );
-}
-
-function ReportedToot({ toot }) {
- const account = toot.account;
-
- return (
- <article className="status expanded">
- <header className="status-header">
- <address>
- <a style={{margin: 0}}>
- <img className="avatar" src={account.avatar} alt="" />
- <dl className="author-strap">
- <dt className="sr-only">Display name</dt>
- <dd className="displayname text-cutoff">
- {account.display_name.trim().length > 0 ? account.display_name : account.username}
- </dd>
- <dt className="sr-only">Username</dt>
- <dd className="username text-cutoff">@{account.username}</dd>
- </dl>
- </a>
- </address>
- </header>
- <section className="status-body">
- <div className="text">
- <div className="content">
- {toot.spoiler_text?.length > 0
- ? <TootCW content={toot.content} note={toot.spoiler_text} />
- : toot.content
- }
- </div>
- </div>
- {toot.media_attachments?.length > 0 &&
- <TootMedia media={toot.media_attachments} sensitive={toot.sensitive} />
- }
- </section>
- <aside className="status-info">
- <dl className="status-stats">
- <div className="stats-grouping">
- <div className="stats-item published-at text-cutoff">
- <dt className="sr-only">Published</dt>
- <dd>
- <time dateTime={toot.created_at}>{new Date(toot.created_at).toLocaleString()}</time>
- </dd>
- </div>
- </div>
- </dl>
- </aside>
- </article>
- );
-}
-
-function TootCW({ note, content }) {
- const [visible, setVisible] = useState(false);
-
- function toggleVisible() {
- setVisible(!visible);
- }
-
- return (
- <>
- <div className="spoiler">
- <span>{note}</span>
- <label className="button spoiler-label" onClick={toggleVisible}>Show {visible ? "less" : "more"}</label>
- </div>
- {visible && content}
- </>
- );
-}
-
-function TootMedia({ media, sensitive }) {
- let classes = (media.length % 2 == 0) ? "even" : "odd";
- if (media.length == 1) {
- classes += " single";
- }
-
- return (
- <div className={`media photoswipe-gallery ${classes}`}>
- {media.map((m) => (
- <div key={m.id} className="media-wrapper">
- {sensitive && <>
- <input id={`sensitiveMedia-${m.id}`} type="checkbox" className="sensitive-checkbox hidden" />
- <div className="sensitive">
- <div className="open">
- <label htmlFor={`sensitiveMedia-${m.id}`} className="button" role="button" tabIndex={0}>
- <i className="fa fa-eye-slash" title="Hide sensitive media"></i>
- </label>
- </div>
- <div className="closed" title={m.description}>
- <label htmlFor={`sensitiveMedia-${m.id}`} className="button" role="button" tabIndex={0}>
- Show sensitive media
- </label>
- </div>
- </div>
- </>}
- <a
- href={m.url}
- title={m.description}
- target="_blank"
- rel="noreferrer"
- data-cropped="true"
- data-pswp-width={`${m.meta?.original.width}px`}
- data-pswp-height={`${m.meta?.original.height}px`}
- >
- <img
- alt={m.description}
- src={m.url}
- // thumb={m.preview_url}
- sizes={m.meta?.original}
- />
- </a>
- </div>
- ))}
- </div>
- );
-}
diff --git a/web/source/settings/admin/reports/index.tsx b/web/source/settings/admin/reports/index.tsx
deleted file mode 100644
index 052d72761..000000000
--- a/web/source/settings/admin/reports/index.tsx
+++ /dev/null
@@ -1,103 +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/>.
-*/
-
-import React from "react";
-import { Link, Switch, Route } from "wouter";
-
-import FormWithData from "../../lib/form/form-with-data";
-
-import ReportDetail from "./detail";
-import Username from "./username";
-import { useBaseUrl } from "../../lib/navigation/util";
-import { useListReportsQuery } from "../../lib/query/admin/reports";
-
-export default function Reports({ baseUrl }) {
- return (
- <div className="reports">
- <Switch>
- <Route path={`${baseUrl}/:reportId`}>
- <ReportDetail />
- </Route>
- <ReportOverview />
- </Switch>
- </div>
- );
-}
-
-function ReportOverview({ }) {
- return (
- <>
- <h1>Reports</h1>
- <div>
- <p>
- Here you can view and resolve reports made to your instance, originating from local and remote users.
- </p>
- </div>
- <FormWithData
- dataQuery={useListReportsQuery}
- DataForm={ReportsList}
- />
- </>
- );
-}
-
-function ReportsList({ data: reports }) {
- return (
- <div className="list">
- {reports.map((report) => (
- <ReportEntry key={report.id} report={report} />
- ))}
- </div>
- );
-}
-
-function ReportEntry({ report }) {
- const baseUrl = useBaseUrl();
- const from = report.account;
- const target = report.target_account;
-
- let comment = report.comment.length > 200
- ? report.comment.slice(0, 200) + "..."
- : report.comment;
-
- return (
- <Link to={`${baseUrl}/${report.id}`}>
- <a className={`report entry${report.action_taken ? " resolved" : ""}`}>
- <div className="byline">
- <div className="usernames">
- <Username user={from} link={false} /> reported <Username user={target} link={false} />
- </div>
- <h3 className="report-status">
- {report.action_taken ? "Resolved" : "Open"}
- </h3>
- </div>
- <div className="details">
- <b>Created: </b>
- <span>{new Date(report.created_at).toLocaleString()}</span>
-
- <b>Reason: </b>
- {comment.length > 0
- ? <p>{comment}</p>
- : <i className="no-comment">none provided</i>
- }
- </div>
- </a>
- </Link>
- );
-}
diff --git a/web/source/settings/admin/reports/username.tsx b/web/source/settings/admin/reports/username.tsx
deleted file mode 100644
index 6fba0b804..000000000
--- a/web/source/settings/admin/reports/username.tsx
+++ /dev/null
@@ -1,54 +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/>.
-*/
-
-import React from "react";
-import { Link } from "wouter";
-
-export default function Username({ user, link = true }) {
- let className = "user";
- let isLocal = user.domain == null;
-
- if (user.suspended) {
- className += " suspended";
- }
-
- if (isLocal) {
- className += " local";
- }
-
- let icon = isLocal
- ? { fa: "fa-home", info: "Local user" }
- : { fa: "fa-external-link-square", info: "Remote user" };
-
- let Element: any = "div";
- let href: any = null;
-
- if (link) {
- Element = Link;
- href = `/settings/admin/accounts/${user.id}`;
- }
-
- return (
- <Element className={className} to={href}>
- <span className="acct">@{user.account.acct}</span>
- <i className={`fa fa-fw ${icon.fa}`} aria-hidden="true" title={icon.info} />
- <span className="sr-only">{icon.info}</span>
- </Element>
- );
-}
diff --git a/web/source/settings/admin/settings/index.tsx b/web/source/settings/admin/settings/index.tsx
deleted file mode 100644
index 69fbfd4ca..000000000
--- a/web/source/settings/admin/settings/index.tsx
+++ /dev/null
@@ -1,190 +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/>.
-*/
-
-import React from "react";
-
-import { useTextInput, useFileInput } from "../../lib/form";
-
-const useFormSubmit = require("../../lib/form/submit").default;
-
-import { TextInput, TextArea, FileInput } from "../../components/form/inputs";
-
-const FormWithData = require("../../lib/form/form-with-data").default;
-import MutationButton from "../../components/form/mutation-button";
-
-import { useInstanceV1Query } from "../../lib/query";
-import { useUpdateInstanceMutation } from "../../lib/query/admin";
-import { InstanceV1 } from "../../lib/types/instance";
-
-export default function AdminSettings() {
- return (
- <FormWithData
- dataQuery={useInstanceV1Query}
- DataForm={AdminSettingsForm}
- />
- );
-}
-
-interface AdminSettingsFormProps{
- data: InstanceV1;
-}
-
-function AdminSettingsForm({ data: instance }: AdminSettingsFormProps) {
- const titleLimit = 40;
- const shortDescLimit = 500;
- const descLimit = 5000;
- const termsLimit = 5000;
-
- const form = {
- title: useTextInput("title", {
- source: instance,
- validator: (val: string) => val.length <= titleLimit ? "" : `Instance title is ${val.length} characters; must be ${titleLimit} characters or less`
- }),
- thumbnail: useFileInput("thumbnail", { withPreview: true }),
- thumbnailDesc: useTextInput("thumbnail_description", { source: instance }),
- shortDesc: useTextInput("short_description", {
- source: instance,
- // Select "raw" text version of parsed field for editing.
- valueSelector: (s: InstanceV1) => s.short_description_text,
- validator: (val: string) => val.length <= shortDescLimit ? "" : `Instance short description is ${val.length} characters; must be ${shortDescLimit} characters or less`
- }),
- description: useTextInput("description", {
- source: instance,
- // Select "raw" text version of parsed field for editing.
- valueSelector: (s: InstanceV1) => s.description_text,
- validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less`
- }),
- terms: useTextInput("terms", {
- source: instance,
- // Select "raw" text version of parsed field for editing.
- valueSelector: (s: InstanceV1) => s.terms_text,
- validator: (val: string) => val.length <= termsLimit ? "" : `Instance terms and conditions is ${val.length} characters; must be ${termsLimit} characters or less`
- }),
- contactUser: useTextInput("contact_username", { source: instance, valueSelector: (s) => s.contact_account?.username }),
- contactEmail: useTextInput("contact_email", { source: instance, valueSelector: (s) => s.email })
- };
-
- const [submitForm, result] = useFormSubmit(form, useUpdateInstanceMutation());
-
- return (
- <form onSubmit={submitForm}>
- <h1>Instance Settings</h1>
-
- <div className="form-section-docs">
- <h3>Appearance</h3>
- <a
- href="https://docs.gotosocial.org/en/latest/admin/settings/#instance-appearance"
- target="_blank"
- className="docslink"
- rel="noreferrer"
- >
- Learn more about these settings (opens in a new tab)
- </a>
- </div>
-
- <TextInput
- field={form.title}
- label={`Instance title (max ${titleLimit} characters)`}
- placeholder="My GoToSocial instance"
- />
-
- <div className="file-upload" aria-labelledby="avatar">
- <strong id="avatar">Instance avatar (1:1 images look best)</strong>
- <div className="file-upload-with-preview">
- <img
- className="preview avatar"
- src={form.thumbnail.previewValue ?? instance?.thumbnail}
- alt={form.thumbnailDesc.value ?? (instance?.thumbnail ? `Thumbnail image for the instance` : "No instance thumbnail image set")}
- />
- <div className="file-input-with-image-description">
- <FileInput
- field={form.thumbnail}
- accept="image/png, image/jpeg, image/webp, image/gif"
- />
- <TextInput
- field={form.thumbnailDesc}
- label="Avatar image description"
- placeholder="A cute drawing of a smiling sloth."
- />
- </div>
- </div>
-
- </div>
-
- <div className="form-section-docs">
- <h3>Descriptors</h3>
- <a
- href="https://docs.gotosocial.org/en/latest/admin/settings/#instance-descriptors"
- target="_blank"
- className="docslink"
- rel="noreferrer"
- >
- Learn more about these settings (opens in a new tab)
- </a>
- </div>
-
- <TextArea
- field={form.shortDesc}
- label={`Short description (markdown accepted, max ${shortDescLimit} characters)`}
- placeholder="A small testing instance for the GoToSocial alpha software."
- rows={6}
- />
-
- <TextArea
- field={form.description}
- label={`Full description (markdown accepted, max ${descLimit} characters)`}
- placeholder="A small testing instance for the GoToSocial alpha software. Just trying it out, my main instance is https://example.com"
- rows={6}
- />
-
- <TextArea
- field={form.terms}
- label={`Terms & Conditions (markdown accepted, max ${termsLimit} characters)`}
- placeholder="Terms and conditions of using this instance, data policy, imprint, GDPR stuff, yadda yadda."
- rows={6}
- />
-
- <div className="form-section-docs">
- <h3>Contact info</h3>
- <a
- href="https://docs.gotosocial.org/en/latest/admin/settings/#instance-contact-info"
- target="_blank"
- className="docslink"
- rel="noreferrer"
- >
- Learn more about these settings (opens in a new tab)
- </a>
- </div>
-
- <TextInput
- field={form.contactUser}
- label="Contact user (local account username)"
- placeholder="admin"
- />
-
- <TextInput
- field={form.contactEmail}
- label="Contact email"
- placeholder="admin@example.com"
- />
-
- <MutationButton label="Save" result={result} disabled={false} />
- </form>
- );
-} \ No newline at end of file
diff --git a/web/source/settings/admin/settings/rules.tsx b/web/source/settings/admin/settings/rules.tsx
deleted file mode 100644
index e5e4d17c5..000000000
--- a/web/source/settings/admin/settings/rules.tsx
+++ /dev/null
@@ -1,174 +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/>.
-*/
-
-import React from "react";
-import { Switch, Route, Link, Redirect, useRoute } from "wouter";
-
-import { useInstanceRulesQuery, useAddInstanceRuleMutation, useUpdateInstanceRuleMutation, useDeleteInstanceRuleMutation } from "../../lib/query";
-import FormWithData from "../../lib/form/form-with-data";
-import { useBaseUrl } from "../../lib/navigation/util";
-
-import { useValue, useTextInput } from "../../lib/form";
-import useFormSubmit from "../../lib/form/submit";
-
-import { TextArea } from "../../components/form/inputs";
-import MutationButton from "../../components/form/mutation-button";
-import { Error } from "../../components/error";
-
-export default function InstanceRulesData({ baseUrl }) {
- return (
- <FormWithData
- dataQuery={useInstanceRulesQuery}
- DataForm={InstanceRules}
- {...{baseUrl}}
- />
- );
-}
-
-function InstanceRules({ baseUrl, data: rules }) {
- return (
- <Switch>
- <Route path={`${baseUrl}/:ruleId`}>
- <InstanceRuleDetail rules={rules} />
- </Route>
- <Route>
- <div>
- <h1>Instance Rules</h1>
- <div>
- <p>
- The rules for your instance are listed on the about page, and can be selected when submitting reports.
- </p>
- </div>
- <InstanceRuleList rules={rules} />
- </div>
- </Route>
- </Switch>
- );
-}
-
-function InstanceRuleList({ rules }) {
- const newRule = useTextInput("text", {});
-
- const [submitForm, result] = useFormSubmit({ newRule }, useAddInstanceRuleMutation(), {
- changedOnly: true,
- onFinish: () => newRule.reset()
- });
-
- return (
- <>
- <form onSubmit={submitForm} className="new-rule">
- <ol className="instance-rules">
- {Object.values(rules).map((rule: any) => (
- <InstanceRule key={rule.id} rule={rule} />
- ))}
- </ol>
- <TextArea
- field={newRule}
- label="New instance rule"
- />
- <MutationButton
- disabled={newRule.value === undefined || newRule.value.length === 0}
- label="Add rule"
- result={result}
- />
- </form>
- </>
- );
-}
-
-function InstanceRule({ rule }) {
- const baseUrl = useBaseUrl();
-
- return (
- <Link to={`${baseUrl}/${rule.id}`}>
- <a className="rule">
- <li>
- <h2>{rule.text} <i className="fa fa-pencil edit-icon" /></h2>
- </li>
- <span>{new Date(rule.created_at).toLocaleString()}</span>
- </a>
- </Link>
- );
-}
-
-function InstanceRuleDetail({ rules }) {
- const baseUrl = useBaseUrl();
- let [_match, params] = useRoute(`${baseUrl}/:ruleId`);
-
- if (params?.ruleId == undefined || rules[params.ruleId] == undefined) {
- return <Redirect to={baseUrl} />;
- } else {
- return (
- <>
- <Link to={baseUrl}><a>&lt; go back</a></Link>
- <InstanceRuleForm rule={rules[params.ruleId]} />
- </>
- );
- }
-}
-
-function InstanceRuleForm({ rule }) {
- const baseUrl = useBaseUrl();
- const form = {
- id: useValue("id", rule.id),
- rule: useTextInput("text", { defaultValue: rule.text })
- };
-
- const [submitForm, result] = useFormSubmit(form, useUpdateInstanceRuleMutation());
-
- const [deleteRule, deleteResult] = useDeleteInstanceRuleMutation({ fixedCacheKey: rule.id });
-
- if (result.isSuccess || deleteResult.isSuccess) {
- return (
- <Redirect to={baseUrl} />
- );
- }
-
- return (
- <div className="rule-detail">
- <form onSubmit={submitForm}>
- <TextArea
- field={form.rule}
- />
-
- <div className="action-buttons row">
- <MutationButton
- label="Save"
- showError={false}
- result={result}
- disabled={!form.rule.hasChanged()}
- />
-
- <MutationButton
- disabled={false}
- type="button"
- onClick={() => deleteRule(rule.id)}
- label="Delete"
- className="button danger"
- showError={false}
- result={deleteResult}
- />
- </div>
-
- {result.error && <Error error={result.error} />}
- {deleteResult.error && <Error error={deleteResult.error} />}
- </form>
- </div>
- );
-}