mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-30 01:52:26 -05:00
[feature] Admin accounts endpoints; approve/reject sign-ups (#2826)
* update settings panels, add pending overview + approve/deny functions * add admin accounts get, approve, reject * send approved/rejected emails * use signup URL * docs! * email * swagger * web linting * fix email tests * wee lil fixerinos * use new paging logic for GetAccounts() series of admin endpoints, small changes to query building * shuffle useAccountIDIn check *before* adding to query * fix parse from toot react error * use `netip.Addr` * put valid slices in globals * optimistic updates for account state --------- Co-authored-by: kim <grufwub@gmail.com>
This commit is contained in:
parent
1439042104
commit
89e0cfd874
74 changed files with 4102 additions and 545 deletions
|
|
@ -17,29 +17,25 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
const { useRoute, Link, Redirect } = require("wouter");
|
||||
import React, { useEffect } from "react";
|
||||
import { useRoute, Link, Redirect } from "wouter";
|
||||
|
||||
const { useComboBoxInput, useFileInput, useValue } = require("../../../lib/form");
|
||||
const { CategorySelect } = require("../category-select");
|
||||
import { useComboBoxInput, useFileInput, useValue } from "../../../lib/form";
|
||||
import { CategorySelect } from "../category-select";
|
||||
|
||||
const useFormSubmit = require("../../../lib/form/submit").default;
|
||||
const { useBaseUrl } = require("../../../lib/navigation/util");
|
||||
import useFormSubmit from "../../../lib/form/submit";
|
||||
import { useBaseUrl } from "../../../lib/navigation/util";
|
||||
|
||||
const FakeToot = require("../../../components/fake-toot");
|
||||
const FormWithData = require("../../../lib/form/form-with-data").default;
|
||||
const Loading = require("../../../components/loading");
|
||||
const { FileInput } = require("../../../components/form/inputs");
|
||||
const MutationButton = require("../../../components/form/mutation-button");
|
||||
const { Error } = require("../../../components/error");
|
||||
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";
|
||||
|
||||
const {
|
||||
useGetEmojiQuery,
|
||||
useEditEmojiMutation,
|
||||
useDeleteEmojiMutation,
|
||||
} = require("../../../lib/query/admin/custom-emoji");
|
||||
import { useGetEmojiQuery, useEditEmojiMutation, useDeleteEmojiMutation } from "../../../lib/query/admin/custom-emoji";
|
||||
|
||||
module.exports = function EmojiDetailRoute({ }) {
|
||||
export default function EmojiDetailRoute({ }) {
|
||||
const baseUrl = useBaseUrl();
|
||||
let [_match, params] = useRoute(`${baseUrl}/:emojiId`);
|
||||
if (params?.emojiId == undefined) {
|
||||
|
|
@ -52,7 +48,7 @@ module.exports = function EmojiDetailRoute({ }) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function EmojiDetailForm({ data: emoji }) {
|
||||
const baseUrl = useBaseUrl();
|
||||
|
|
@ -68,7 +64,7 @@ function EmojiDetailForm({ data: emoji }) {
|
|||
const [modifyEmoji, result] = useFormSubmit(form, useEditEmojiMutation());
|
||||
|
||||
// Automatic submitting of category change
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (
|
||||
form.category.hasChanged() &&
|
||||
!form.category.state.open &&
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
const { Switch, Route } = require("wouter");
|
||||
import React from "react";
|
||||
import { Switch, Route } from "wouter";
|
||||
|
||||
const EmojiOverview = require("./overview");
|
||||
const EmojiDetail = require("./detail");
|
||||
import EmojiOverview from "./overview";
|
||||
import EmojiDetail from "./detail";
|
||||
|
||||
module.exports = function CustomEmoji({ baseUrl }) {
|
||||
export default function CustomEmoji({ baseUrl }) {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${baseUrl}/:emojiId`}>
|
||||
|
|
@ -32,4 +32,4 @@ module.exports = function CustomEmoji({ baseUrl }) {
|
|||
<EmojiOverview />
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
@ -17,31 +17,26 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
import React, { useMemo, useEffect } from "react";
|
||||
|
||||
const {
|
||||
useFileInput,
|
||||
useComboBoxInput
|
||||
} = require("../../../lib/form");
|
||||
const useShortcode = require("./use-shortcode");
|
||||
import { useFileInput, useComboBoxInput } from "../../../lib/form";
|
||||
import useShortcode from "./use-shortcode";
|
||||
|
||||
const useFormSubmit = require("../../../lib/form/submit").default;
|
||||
import useFormSubmit from "../../../lib/form/submit";
|
||||
|
||||
const {
|
||||
TextInput, FileInput
|
||||
} = require("../../../components/form/inputs");
|
||||
import { TextInput, FileInput } from "../../../components/form/inputs";
|
||||
|
||||
const { CategorySelect } = require('../category-select');
|
||||
const FakeToot = require("../../../components/fake-toot");
|
||||
const MutationButton = require("../../../components/form/mutation-button");
|
||||
const { useAddEmojiMutation } = require("../../../lib/query/admin/custom-emoji");
|
||||
const { useInstanceV1Query } = require("../../../lib/query");
|
||||
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";
|
||||
|
||||
module.exports = function NewEmojiForm() {
|
||||
export default function NewEmojiForm() {
|
||||
const shortcode = useShortcode();
|
||||
|
||||
const { data: instance } = useInstanceV1Query();
|
||||
const emojiMaxSize = React.useMemo(() => {
|
||||
const emojiMaxSize = useMemo(() => {
|
||||
return instance?.configuration?.emojis?.emoji_size_limit ?? 50 * 1024;
|
||||
}, [instance]);
|
||||
|
||||
|
|
@ -56,8 +51,8 @@ module.exports = function NewEmojiForm() {
|
|||
shortcode, image, category
|
||||
}, useAddEmojiMutation());
|
||||
|
||||
React.useEffect(() => {
|
||||
if (shortcode.value.length == 0) {
|
||||
useEffect(() => {
|
||||
if (shortcode.value === undefined || shortcode.value.length == 0) {
|
||||
if (image.value != undefined) {
|
||||
let [name, _ext] = image.value.name.split(".");
|
||||
shortcode.setter(name);
|
||||
|
|
@ -71,7 +66,7 @@ module.exports = function NewEmojiForm() {
|
|||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
||||
}, [image.value]);
|
||||
|
||||
let emojiOrShortcode = `:${shortcode.value}:`;
|
||||
let emojiOrShortcode;
|
||||
|
||||
if (image.previewValue != undefined) {
|
||||
emojiOrShortcode = <img
|
||||
|
|
@ -80,6 +75,10 @@ module.exports = function NewEmojiForm() {
|
|||
title={`:${shortcode.value}:`}
|
||||
alt={shortcode.value}
|
||||
/>;
|
||||
} else if (shortcode.value !== undefined && shortcode.value.length > 0) {
|
||||
emojiOrShortcode = `:${shortcode.value}:`;
|
||||
} else {
|
||||
emojiOrShortcode = `:your_emoji_here:`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -103,10 +102,15 @@ module.exports = function NewEmojiForm() {
|
|||
|
||||
<CategorySelect
|
||||
field={category}
|
||||
children={[]}
|
||||
/>
|
||||
|
||||
<MutationButton label="Upload emoji" result={result} />
|
||||
<MutationButton
|
||||
disabled={image.previewValue === undefined}
|
||||
label="Upload emoji"
|
||||
result={result}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ const { Link } = require("wouter");
|
|||
const syncpipe = require("syncpipe");
|
||||
const { matchSorter } = require("match-sorter");
|
||||
|
||||
const NewEmojiForm = require("./new-emoji");
|
||||
const NewEmojiForm = require("./new-emoji").default;
|
||||
const { useTextInput } = require("../../../lib/form");
|
||||
|
||||
const { useEmojiByCategory } = require("../category-select");
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
import React, { useMemo } from "react";
|
||||
|
||||
const ParseFromToot = require("./parse-from-toot");
|
||||
import ParseFromToot from "./parse-from-toot";
|
||||
|
||||
const Loading = require("../../../components/loading");
|
||||
const { Error } = require("../../../components/error");
|
||||
const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji");
|
||||
import Loading from "../../../components/loading";
|
||||
import { Error } from "../../../components/error";
|
||||
import { useListEmojiQuery } from "../../../lib/query/admin/custom-emoji";
|
||||
|
||||
module.exports = function RemoteEmoji() {
|
||||
export default function RemoteEmoji() {
|
||||
// local emoji are queried for shortcode collision detection
|
||||
const {
|
||||
data: emoji = [],
|
||||
|
|
@ -33,7 +33,7 @@ module.exports = function RemoteEmoji() {
|
|||
error
|
||||
} = useListEmojiQuery({ filter: "domain:local" });
|
||||
|
||||
const emojiCodes = React.useMemo(() => {
|
||||
const emojiCodes = useMemo(() => {
|
||||
return new Set(emoji.map((e) => e.shortcode));
|
||||
}, [emoji]);
|
||||
|
||||
|
|
@ -46,9 +46,9 @@ module.exports = function RemoteEmoji() {
|
|||
{isLoading
|
||||
? <Loading />
|
||||
: <>
|
||||
<ParseFromToot emoji={emoji} emojiCodes={emojiCodes} />
|
||||
<ParseFromToot emojiCodes={emojiCodes} />
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
@ -17,36 +17,28 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
|
||||
const {
|
||||
useTextInput,
|
||||
useComboBoxInput,
|
||||
useCheckListInput
|
||||
} = require("../../../lib/form");
|
||||
import { useTextInput, useComboBoxInput, useCheckListInput } from "../../../lib/form";
|
||||
|
||||
const useFormSubmit = require("../../../lib/form/submit").default;
|
||||
import useFormSubmit from "../../../lib/form/submit";
|
||||
|
||||
const CheckList = require("../../../components/check-list").default;
|
||||
const { CategorySelect } = require('../category-select');
|
||||
import CheckList from "../../../components/check-list";
|
||||
import { CategorySelect } from '../category-select';
|
||||
|
||||
const { TextInput } = require("../../../components/form/inputs");
|
||||
const MutationButton = require("../../../components/form/mutation-button");
|
||||
const { Error } = require("../../../components/error");
|
||||
const {
|
||||
useSearchItemForEmojiMutation,
|
||||
usePatchRemoteEmojisMutation
|
||||
} = require("../../../lib/query/admin/custom-emoji");
|
||||
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";
|
||||
|
||||
module.exports = function ParseFromToot({ emojiCodes }) {
|
||||
export default function ParseFromToot({ emojiCodes }) {
|
||||
const [searchStatus, result] = useSearchItemForEmojiMutation();
|
||||
|
||||
const [onURLChange, _resetURL, { url }] = useTextInput("url");
|
||||
const urlField = useTextInput("url");
|
||||
|
||||
function submitSearch(e) {
|
||||
e.preventDefault();
|
||||
if (url.trim().length != 0) {
|
||||
searchStatus(url);
|
||||
if (urlField.value !== undefined && urlField.value.trim().length != 0) {
|
||||
searchStatus(urlField.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,8 +55,8 @@ module.exports = function ParseFromToot({ emojiCodes }) {
|
|||
type="text"
|
||||
id="url"
|
||||
name="url"
|
||||
onChange={onURLChange}
|
||||
value={url}
|
||||
onChange={urlField.onChange}
|
||||
value={urlField.value}
|
||||
/>
|
||||
<button disabled={result.isLoading}>
|
||||
<i className={[
|
||||
|
|
@ -81,7 +73,7 @@ module.exports = function ParseFromToot({ emojiCodes }) {
|
|||
<SearchResult result={result} localEmojiCodes={emojiCodes} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function SearchResult({ result, localEmojiCodes }) {
|
||||
const { error, data, isSuccess, isError } = result;
|
||||
|
|
@ -106,7 +98,6 @@ function SearchResult({ result, localEmojiCodes }) {
|
|||
<CopyEmojiForm
|
||||
localEmojiCodes={localEmojiCodes}
|
||||
type={data.type}
|
||||
domain={data.domain}
|
||||
emojiList={data.list}
|
||||
/>
|
||||
);
|
||||
|
|
@ -139,13 +130,16 @@ function CopyEmojiForm({ localEmojiCodes, type, emojiList }) {
|
|||
);
|
||||
|
||||
const buttonsInactive = form.selectedEmoji.someSelected
|
||||
? {}
|
||||
? {
|
||||
disabled: false,
|
||||
title: ""
|
||||
}
|
||||
: {
|
||||
disabled: true,
|
||||
title: "No emoji selected, cannot perform any actions"
|
||||
};
|
||||
|
||||
const checkListExtraProps = React.useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]);
|
||||
const checkListExtraProps = useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]);
|
||||
|
||||
return (
|
||||
<div className="parsed">
|
||||
|
|
@ -153,17 +147,32 @@ function CopyEmojiForm({ localEmojiCodes, type, emojiList }) {
|
|||
<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} />
|
||||
<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)
|
||||
|
|
@ -198,13 +207,13 @@ function EmojiEntry({ entry: emoji, onChange, extraProps: { localEmojiCodes } })
|
|||
}
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (emoji.valid != shortcodeField.valid) {
|
||||
onChange({ valid: shortcodeField.valid });
|
||||
}
|
||||
}, [onChange, emoji.valid, shortcodeField.valid]);
|
||||
|
||||
React.useEffect(() => {
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue