eslint --fix

This commit is contained in:
f0x 2024-11-06 15:55:00 +01:00
parent 8464f20370
commit 251286f2c7
94 changed files with 625 additions and 579 deletions

View file

@ -46,7 +46,7 @@ new PhotoswipeCaptionPlugin(lightbox, {
type: 'auto', type: 'auto',
captionContent(slide) { captionContent(slide) {
return slide.data.alt; return slide.data.alt;
} },
}); });
lightbox.addFilter('itemData', (item) => { lightbox.addFilter('itemData', (item) => {
@ -68,10 +68,10 @@ lightbox.addFilter('itemData', (item) => {
}, },
pause() { pause() {
el._player.pause(); el._player.pause();
} },
}, },
width: parseInt(el.dataset.pswpWidth), width: parseInt(el.dataset.pswpWidth),
height: parseInt(el.dataset.pswpHeight) height: parseInt(el.dataset.pswpHeight),
}; };
} }
return item; return item;
@ -169,12 +169,12 @@ Array.from(document.getElementsByClassName("plyr-video")).forEach((video) => {
}, 1); }, 1);
} }
lightbox.loadAndOpen(parseInt(video.dataset.pswpIndex), { lightbox.loadAndOpen(parseInt(video.dataset.pswpIndex), {
gallery: video.closest(".photoswipe-gallery") gallery: video.closest(".photoswipe-gallery"),
}); });
return false; return false;
} },
} },
}); });
player.elements.container.title = video.title; player.elements.container.title = video.title;

View file

@ -29,10 +29,10 @@ const prodCfg = {
transform: [ transform: [
["@browserify/uglifyify", { ["@browserify/uglifyify", {
global: true, global: true,
exts: ".js" exts: ".js",
}], }],
["@browserify/envify", { global: true }] ["@browserify/envify", { global: true }],
] ],
}; };
skulk({ skulk({
@ -42,8 +42,8 @@ skulk({
prodCfg: { prodCfg: {
servers: { servers: {
express: false, express: false,
livereload: false livereload: false,
} },
}, },
servers: { servers: {
express: { express: {
@ -54,6 +54,8 @@ skulk({
delete proxyRes.headers['content-security-policy']; delete proxyRes.headers['content-security-policy'];
}, },
}, },
assets: "/assets",
},
}, },
bundles: { bundles: {
frontend: { frontend: {
@ -64,8 +66,8 @@ skulk({
transform: [ transform: [
["babelify", { ["babelify", {
global: true, global: true,
ignore: [/node_modules\/(?!(photoswipe.*))/] ignore: [/node_modules\/(?!(photoswipe.*))/],
}] }],
], ],
}, },
settings: { settings: {
@ -75,7 +77,7 @@ skulk({
plugin: [ plugin: [
// Additional settings for TS are passed from tsconfig.json. // Additional settings for TS are passed from tsconfig.json.
// See: https://github.com/TypeStrong/tsify#tsconfigjson // See: https://github.com/TypeStrong/tsify#tsconfigjson
["tsify"] ["tsify"],
], ],
transform: [ transform: [
// tsify is called before babelify, so we're just babelifying // tsify is called before babelify, so we're just babelifying
@ -83,28 +85,28 @@ skulk({
["babelify", { ["babelify", {
global: true, global: true,
ignore: [/node_modules\/(?!(nanoid)|(wouter))/], ignore: [/node_modules\/(?!(nanoid)|(wouter))/],
}] }],
], ],
presets: [ presets: [
"react", "react",
["postcss", { ["postcss", {
output: "settings-style.css" output: "settings-style.css",
}] }],
] ],
}, },
cssThemes: { cssThemes: {
entryFiles: cssThemes, entryFiles: cssThemes,
outputFile: "_discard", outputFile: "_discard",
presets: [["postcss", { presets: [["postcss", {
output: "_split" output: "_split",
}]] }]],
}, },
css: { css: {
entryFiles: cssFiles, entryFiles: cssFiles,
outputFile: "_discard", outputFile: "_discard",
presets: [["postcss", { presets: [["postcss", {
output: "style.css" output: "style.css",
}]] }]],
} },
} },
}); });

View file

@ -19,7 +19,8 @@
import { useLogoutMutation, useVerifyCredentialsQuery } from "../../lib/query/oauth"; import { useLogoutMutation, useVerifyCredentialsQuery } from "../../lib/query/oauth";
import { store } from "../../redux/store"; import { store } from "../../redux/store";
import React, { ReactNode } from "react"; import type { ReactNode } from "react";
import React from "react";
import Login from "./login"; import Login from "./login";
import Loading from "../loading"; import Loading from "../loading";

View file

@ -29,7 +29,7 @@ import { TextInput } from "../form/inputs";
export default function Login({ }) { export default function Login({ }) {
const form = { const form = {
instance: useTextInput("instance", { instance: useTextInput("instance", {
defaultValue: window.location.origin defaultValue: window.location.origin,
}), }),
scopes: useValue("scopes", "user admin"), scopes: useValue("scopes", "user admin"),
}; };

View file

@ -17,10 +17,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from "react"; import React, { memo, useDeferredValue, useCallback, useMemo } from "react";
import { memo, useDeferredValue, useCallback, useMemo } from "react"; import type { Checkable, ChecklistInputHook } from "../lib/form/types";
import { Checkable, ChecklistInputHook } from "../lib/form/types";
interface CheckListProps { interface CheckListProps {
field: ChecklistInputHook; field: ChecklistInputHook;
@ -75,7 +74,7 @@ const CheckListEntries = memo(
getExtraProps={getExtraProps} getExtraProps={getExtraProps}
/> />
)); ));
} },
); );
interface CheckListEntryProps { interface CheckListEntryProps {
@ -94,7 +93,7 @@ const CheckListEntry = memo(
function CheckListEntry({ entry, updateValue, getExtraProps, EntryComponent }: CheckListEntryProps) { function CheckListEntry({ entry, updateValue, getExtraProps, EntryComponent }: CheckListEntryProps) {
const onChange = useCallback( const onChange = useCallback(
(value) => updateValue(entry.key, value), (value) => updateValue(entry.key, value),
[updateValue, entry.key] [updateValue, entry.key],
); );
const extraProps = useMemo(() => getExtraProps?.(entry), [getExtraProps, entry]); const extraProps = useMemo(() => getExtraProps?.(entry), [getExtraProps, entry]);
@ -109,5 +108,5 @@ const CheckListEntry = memo(
<EntryComponent entry={entry} onChange={onChange} extraProps={extraProps} /> <EntryComponent entry={entry} onChange={onChange} extraProps={extraProps} />
</label> </label>
); );
} },
); );

View file

@ -17,9 +17,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { SerializedError } from "@reduxjs/toolkit"; import type { SerializedError } from "@reduxjs/toolkit";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; import type { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import React, { ReactNode } from "react"; import type { ReactNode } from "react";
import React from "react";
function ErrorFallback({ error, resetErrorBoundary }) { function ErrorFallback({ error, resetErrorBoundary }) {
return ( return (
@ -74,7 +75,7 @@ function Error({ error, reset }: ErrorProps) {
return null; return null;
} }
/* eslint-disable-next-line no-console */
console.error("caught error: ", error); console.error("caught error: ", error);
let message: ReactNode; let message: ReactNode;

View file

@ -21,7 +21,7 @@ import React from "react";
import { all } from "langs"; import { all } from "langs";
const asElements = all().map((l) => { const asElements = all().map((l) => {
let code = l["1"].toUpperCase(); const code = l["1"].toUpperCase();
let name = l.name; let name = l.name;
if (l.name != l.local) { if (l.name != l.local) {
name = `${name} - ${l.local}`; name = `${name} - ${l.local}`;

View file

@ -17,12 +17,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { ReactNode } from "react"; import type { ReactNode } from "react";
import React from "react";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { Error } from "./error"; import { Error } from "./error";
import { SerializedError } from "@reduxjs/toolkit"; import type { SerializedError } from "@reduxjs/toolkit";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; import type { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { Links } from "parse-link-header"; import type { Links } from "parse-link-header";
import Loading from "./loading"; import Loading from "./loading";
export interface PageableListProps<T> { export interface PageableListProps<T> {

View file

@ -19,14 +19,14 @@
import React from "react"; import React from "react";
import { useVerifyCredentialsQuery } from "../lib/query/oauth"; import { useVerifyCredentialsQuery } from "../lib/query/oauth";
import { MediaAttachment, Status as StatusType } from "../lib/types/status"; import type { MediaAttachment, Status as StatusType } from "../lib/types/status";
import sanitize from "sanitize-html"; import sanitize from "sanitize-html";
export function FakeStatus({ children }) { export function FakeStatus({ children }) {
const { data: account = { const { data: account = {
avatar: "/assets/default_avatars/GoToSocial_icon1.webp", avatar: "/assets/default_avatars/GoToSocial_icon1.webp",
display_name: "", display_name: "",
username: "" username: "",
} } = useVerifyCredentialsQuery(); } } = useVerifyCredentialsQuery();
return ( return (

View file

@ -19,7 +19,7 @@
import React from "react"; import React from "react";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { AdminAccount } from "../lib/types/account"; import type { AdminAccount } from "../lib/types/account";
interface UsernameProps { interface UsernameProps {
account: AdminAccount; account: AdminAccount;
@ -32,7 +32,7 @@ export default function Username({ account, linkTo, backLocation, classNames }:
const [ _location, setLocation ] = useLocation(); const [ _location, setLocation ] = useLocation();
let className = "username-lozenge"; let className = "username-lozenge";
let isLocal = account.domain == null; const isLocal = account.domain == null;
if (account.suspended) { if (account.suspended) {
className += " suspended"; className += " suspended";
@ -46,7 +46,7 @@ export default function Username({ account, linkTo, backLocation, classNames }:
className = [ className, classNames ].flat().join(" "); className = [ className, classNames ].flat().join(" ");
} }
let icon = isLocal const icon = isLocal
? { fa: "fa-home", info: "Local user" } ? { fa: "fa-home", info: "Local user" }
: { fa: "fa-external-link-square", info: "Remote user" }; : { fa: "fa-external-link-square", info: "Remote user" };
@ -71,7 +71,7 @@ export default function Username({ account, linkTo, backLocation, classNames }:
// Store the back location in history so // Store the back location in history so
// the detail view can use it to return to // the detail view can use it to return to
// this page (including query parameters). // this page (including query parameters).
state: { backLocation: backLocation } state: { backLocation: backLocation },
}); });
}} }}
role="link" role="link"

View file

@ -26,7 +26,7 @@ import { PersistGate } from "redux-persist/integration/react";
import { store, persistor } from "./redux/store"; import { store, persistor } from "./redux/store";
import { Authorization } from "./components/authorization"; import { Authorization } from "./components/authorization";
import Loading from "./components/loading"; import Loading from "./components/loading";
import { Account } from "./lib/types/account"; import type { Account } from "./lib/types/account";
import { BaseUrlContext, RoleContext, InstanceDebugContext } from "./lib/navigation/util"; import { BaseUrlContext, RoleContext, InstanceDebugContext } from "./lib/navigation/util";
import { SidebarMenu } from "./lib/navigation/menu"; import { SidebarMenu } from "./lib/navigation/menu";
import { Redirect, Route, Router } from "wouter"; import { Redirect, Route, Router } from "wouter";

View file

@ -87,6 +87,6 @@ export default function useArrayInput(
} else { } else {
return []; return [];
} }
} },
}; };
} }

View file

@ -27,7 +27,7 @@ import type {
const _default = false; const _default = false;
export default function useBoolInput( export default function useBoolInput(
{ name, Name }: CreateHookNames, { name, Name }: CreateHookNames,
{ initialValue = _default }: HookOpts<boolean> { initialValue = _default }: HookOpts<boolean>,
): BoolFormInputHook { ): BoolFormInputHook {
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
@ -45,8 +45,8 @@ export default function useBoolInput(
reset, reset,
{ {
[name]: value, [name]: value,
[`set${Name}`]: setValue [`set${Name}`]: setValue,
} },
], { ], {
name, name,
Name: "", Name: "",
@ -55,6 +55,6 @@ export default function useBoolInput(
value, value,
setter: setValue, setter: setValue,
hasChanged: () => value != initialValue, hasChanged: () => value != initialValue,
_default _default,
}); });
} }

View file

@ -38,20 +38,20 @@ import {
const _default: { [k: string]: Checkable } = {}; const _default: { [k: string]: Checkable } = {};
export default function useCheckListInput( export default function useCheckListInput(
/* eslint-disable no-unused-vars */
{ name, Name }: CreateHookNames, { name, Name }: CreateHookNames,
{ {
entries = [], entries = [],
uniqueKey = "key", uniqueKey = "key",
initialValue = false, initialValue = false,
}: HookOpts<boolean> }: HookOpts<boolean>,
): ChecklistInputHook { ): ChecklistInputHook {
const [state, dispatch] = useChecklistReducer(entries, uniqueKey, initialValue); const [state, dispatch] = useChecklistReducer(entries, uniqueKey, initialValue);
const toggleAllRef = useRef<any>(null); const toggleAllRef = useRef<any>(null);
useEffect(() => { useEffect(() => {
if (toggleAllRef.current != null) { if (toggleAllRef.current != null) {
let some = state.selectedEntries.size > 0; const some = state.selectedEntries.size > 0;
let all = false; let all = false;
if (some) { if (some) {
all = state.selectedEntries.size == Object.values(state.entries).length; all = state.selectedEntries.size == Object.values(state.entries).length;
@ -64,18 +64,24 @@ export default function useCheckListInput(
}, [state.selectedEntries]); }, [state.selectedEntries]);
const reset = useCallback( const reset = useCallback(
() => dispatch(actions.updateAll(initialValue)), () => {
[initialValue, dispatch] dispatch(actions.updateAll(initialValue));
},
[initialValue, dispatch],
); );
const onChange = useCallback( const onChange = useCallback(
(key: string, value: Checkable) => dispatch(actions.update({ key, value })), (key: string, value: Checkable) => {
[dispatch] dispatch(actions.update({ key, value }));
},
[dispatch],
); );
const updateMultiple = useCallback( const updateMultiple = useCallback(
(entries: [key: string, value: Partial<Checkable>][]) => dispatch(actions.updateMultiple(entries)), (entries: [key: string, value: Partial<Checkable>][]) => {
[dispatch] dispatch(actions.updateMultiple(entries));
},
[dispatch],
); );
return useMemo(() => { return useMemo(() => {
@ -89,14 +95,14 @@ export default function useCheckListInput(
function selectedValues() { function selectedValues() {
return Array.from((state.selectedEntries)).map((key) => ({ return Array.from((state.selectedEntries)).map((key) => ({
...state.entries[key] // returned as new object, because reducer state is immutable ...state.entries[key], // returned as new object, because reducer state is immutable
})); }));
} }
return Object.assign([ return Object.assign([
state, state,
reset, reset,
{ name } { name },
], { ], {
_default, _default,
hasChanged: () => true, hasChanged: () => true,
@ -110,8 +116,8 @@ export default function useCheckListInput(
updateMultiple, updateMultiple,
toggleAll: { toggleAll: {
ref: toggleAllRef, ref: toggleAllRef,
onChange: toggleAll onChange: toggleAll,
} },
}); });
}, [state, reset, name, onChange, updateMultiple, dispatch]); }, [state, reset, name, onChange, updateMultiple, dispatch]);
} }

View file

@ -20,7 +20,7 @@
import { useState } from "react"; import { useState } from "react";
import { useComboboxState } from "ariakit/combobox"; import { useComboboxState } from "ariakit/combobox";
import { import type {
ComboboxFormInputHook, ComboboxFormInputHook,
CreateHookNames, CreateHookNames,
HookOpts, HookOpts,
@ -29,14 +29,14 @@ import {
const _default = ""; const _default = "";
export default function useComboBoxInput( export default function useComboBoxInput(
{ name, Name }: CreateHookNames, { name, Name }: CreateHookNames,
{ initialValue = _default }: HookOpts<string> { initialValue = _default }: HookOpts<string>,
): ComboboxFormInputHook { ): ComboboxFormInputHook {
const [isNew, setIsNew] = useState(false); const [isNew, setIsNew] = useState(false);
const state = useComboboxState({ const state = useComboboxState({
defaultValue: initialValue, defaultValue: initialValue,
gutter: 0, gutter: 0,
sameWidth: true sameWidth: true,
}); });
function reset() { function reset() {
@ -50,18 +50,20 @@ export default function useComboBoxInput(
[name]: state.value, [name]: state.value,
name, name,
[`${name}IsNew`]: isNew, [`${name}IsNew`]: isNew,
[`set${Name}IsNew`]: setIsNew [`set${Name}IsNew`]: setIsNew,
} },
], { ], {
reset, reset,
name, name,
Name: "", // Will be set by inputHook function. Name: "", // Will be set by inputHook function.
state, state,
value: state.value, value: state.value,
setter: (val: string) => state.setValue(val), setter: (val: string) => {
state.setValue(val);
},
hasChanged: () => state.value != initialValue, hasChanged: () => state.value != initialValue,
isNew, isNew,
setIsNew, setIsNew,
_default _default,
}); });
} }

View file

@ -80,6 +80,6 @@ export default function useFieldArrayInput(
} else { } else {
return []; return [];
} }
} },
}; };
} }

View file

@ -17,9 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from "react"; import React, { useState } from "react";
import { useState } from "react";
import prettierBytes from "prettier-bytes"; import prettierBytes from "prettier-bytes";
import type { import type {
@ -35,8 +34,8 @@ export default function useFileInput(
{ {
withPreview, withPreview,
maxSize, maxSize,
initialInfo = "no file selected" initialInfo = "no file selected",
}: HookOpts<File> }: HookOpts<File>,
): FileFormInputHook { ): FileFormInputHook {
const [file, setFile] = useState<File>(); const [file, setFile] = useState<File>();
const [imageURL, setImageURL] = useState<string>(); const [imageURL, setImageURL] = useState<string>();
@ -58,7 +57,7 @@ export default function useFileInput(
return; return;
} }
let file = files[0]; const file = files[0];
setFile(file); setFile(file);
if (imageURL) { if (imageURL) {
@ -76,7 +75,7 @@ export default function useFileInput(
<ErrorC <ErrorC
error={new Error(`file size ${sizePrettier} is larger than max size ${maxSizePrettier}`)} error={new Error(`file size ${sizePrettier} is larger than max size ${maxSizePrettier}`)}
reset={(reset)} reset={(reset)}
/> />,
); );
} else { } else {
setInfo(<>{file.name} ({sizePrettier})</>); setInfo(<>{file.name} ({sizePrettier})</>);
@ -100,7 +99,7 @@ export default function useFileInput(
[name]: file, [name]: file,
[`${name}URL`]: imageURL, [`${name}URL`]: imageURL,
[`${name}Info`]: infoComponent, [`${name}Info`]: infoComponent,
} },
], { ], {
onChange, onChange,
reset, reset,

View file

@ -17,14 +17,12 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* eslint-disable no-unused-vars */
import React from "react"; import React from "react";
import { Error } from "../../components/error"; import { Error } from "../../components/error";
import Loading from "../../components/loading"; import Loading from "../../components/loading";
import { NoArg } from "../types/query"; import { NoArg } from "../types/query";
import { FormWithDataQuery } from "./types"; import type { FormWithDataQuery } from "./types";
export interface FormWithDataProps { export interface FormWithDataProps {
dataQuery: FormWithDataQuery, dataQuery: FormWithDataQuery,

View file

@ -17,17 +17,17 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { FormInputHook, HookedForm } from "./types"; import type { FormInputHook, HookedForm } from "./types";
export default function getFormMutations( export default function getFormMutations(
form: HookedForm, form: HookedForm,
{ changedOnly }: { changedOnly: boolean }, { changedOnly }: { changedOnly: boolean },
): { ): {
updatedFields: FormInputHook<any>[]; updatedFields: FormInputHook[];
mutationData: { mutationData: {
[k: string]: any; [k: string]: any;
}; };
} { } {
const updatedFields: FormInputHook[] = []; const updatedFields: FormInputHook[] = [];
const mutationData: Array<[string, any]> = []; const mutationData: Array<[string, any]> = [];

View file

@ -18,7 +18,7 @@
*/ */
import { useState } from "react"; import { useState } from "react";
import { CreateHookNames, HookOpts, RadioFormInputHook } from "./types"; import type { CreateHookNames, HookOpts, RadioFormInputHook } from "./types";
const _default = ""; const _default = "";
export default function useRadioInput( export default function useRadioInput(
@ -26,7 +26,7 @@ export default function useRadioInput(
{ {
initialValue = _default, initialValue = _default,
options = {}, options = {},
}: HookOpts<string> }: HookOpts<string>,
): RadioFormInputHook { ): RadioFormInputHook {
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
@ -44,8 +44,8 @@ export default function useRadioInput(
reset, reset,
{ {
[name]: value, [name]: value,
[`set${Name}`]: setValue [`set${Name}`]: setValue,
} },
], { ], {
onChange, onChange,
reset, reset,
@ -55,6 +55,6 @@ export default function useRadioInput(
setter: setValue, setter: setValue,
options, options,
hasChanged: () => value != initialValue, hasChanged: () => value != initialValue,
_default _default,
}); });
} }

View file

@ -60,7 +60,7 @@ interface UseFormSubmitOptions {
export default function useFormSubmit( export default function useFormSubmit(
form: HookedForm, form: HookedForm,
mutationQuery: readonly [MutationTrigger<any>, UseMutationStateResult<any, any>], mutationQuery: readonly [MutationTrigger<any>, UseMutationStateResult<any, any>],
opts: UseFormSubmitOptions = { changedOnly: true } opts: UseFormSubmitOptions = { changedOnly: true },
): [ FormSubmitFunction, FormSubmitResult ] { ): [ FormSubmitFunction, FormSubmitResult ] {
if (!Array.isArray(mutationQuery)) { if (!Array.isArray(mutationQuery)) {
throw "useFormSubmit: mutationQuery was not an Array. Is a valid useMutation RTK Query provided?"; throw "useFormSubmit: mutationQuery was not an Array. Is a valid useMutation RTK Query provided?";
@ -90,7 +90,7 @@ export default function useFormSubmit(
// what at this point. If it's an empty string, fall back to undefined. // what at this point. If it's an empty string, fall back to undefined.
// //
// See: https://developer.mozilla.org/en-US/docs/Web/API/SubmitEvent/submitter // See: https://developer.mozilla.org/en-US/docs/Web/API/SubmitEvent/submitter
action = (e.nativeEvent.submitter as Object as { name: string }).name || undefined; action = (e.nativeEvent.submitter as object as { name: string }).name || undefined;
} else { } else {
// No submitter defined. Fall back // No submitter defined. Fall back
// to just use the FormSubmitEvent. // to just use the FormSubmitEvent.
@ -125,7 +125,7 @@ export default function useFormSubmit(
onFinish(res); onFinish(res);
} }
} catch (e) { } catch (e) {
// eslint-disable-next-line no-console
console.error(`caught error running mutation: ${e}`); console.error(`caught error running mutation: ${e}`);
} }
}; };
@ -134,7 +134,7 @@ export default function useFormSubmit(
submitForm, submitForm,
{ {
...mutationResult, ...mutationResult,
action: usedAction.current action: usedAction.current,
} },
]; ];
} }

View file

@ -17,7 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { import type React from "react";
import {
useState, useState,
useRef, useRef,
useTransition, useTransition,
@ -41,7 +42,7 @@ export default function useTextInput(
showValidation = true, showValidation = true,
initValidation, initValidation,
nosubmit = false, nosubmit = false,
}: HookOpts<string> }: HookOpts<string>,
): TextFormInputHook { ): TextFormInputHook {
const [text, setText] = useState(initialValue); const [text, setText] = useState(initialValue);
const textRef = useRef<HTMLInputElement>(null); const textRef = useRef<HTMLInputElement>(null);
@ -86,7 +87,7 @@ export default function useTextInput(
[`${name}Ref`]: textRef, [`${name}Ref`]: textRef,
[`set${Name}`]: setText, [`set${Name}`]: setText,
[`${name}Valid`]: valid, [`${name}Valid`]: valid,
} },
], { ], {
onChange, onChange,
reset, reset,
@ -97,8 +98,10 @@ export default function useTextInput(
ref: textRef, ref: textRef,
setter: setText, setter: setText,
valid, valid,
validate: () => setValidation(validator ? validator(text): ""), validate: () => {
setValidation(validator ? validator(text): "");
},
hasChanged: () => text != initialValue, hasChanged: () => text != initialValue,
_default _default,
}); });
} }

View file

@ -17,12 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* eslint-disable no-unused-vars */ import type { ComboboxState } from "ariakit";
import type React, {
import { ComboboxState } from "ariakit";
import React from "react";
import {
ChangeEventHandler, ChangeEventHandler,
Dispatch, Dispatch,
RefObject, RefObject,
@ -30,6 +26,7 @@ import {
SyntheticEvent, SyntheticEvent,
} from "react"; } from "react";
export interface CreateHookNames { export interface CreateHookNames {
name: string; name: string;
Name: string; Name: string;
@ -225,9 +222,9 @@ export interface ChecklistInputHook<T = Checkable> extends FormInputHook<{[k: st
_withSelectedFieldValues, _withSelectedFieldValues,
_withSomeSelected, _withSomeSelected,
_withUpdateMultiple { _withUpdateMultiple {
// Uses its own funky onChange handler. // Uses its own funky onChange handler.
onChange: (key: any, value: any) => void onChange: (key: any, value: any) => void
} }
export type AnyFormInputHook = export type AnyFormInputHook =
FormInputHook | FormInputHook |

View file

@ -17,7 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { Component, ReactNode } from "react"; import type { ReactNode } from "react";
import React, { Component } from "react";
interface ErrorBoundaryProps { interface ErrorBoundaryProps {
@ -48,7 +49,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
componentDidCatch(_e, info) { componentDidCatch(_e, info) {
this.setState({ this.setState({
...this.state, ...this.state,
componentStack: info.componentStack componentStack: info.componentStack,
}); });
} }
@ -83,7 +84,7 @@ function ErrorFallback({ error, componentStack, resetErrorBoundary }) {
{componentStack && [ {componentStack && [
"\n\nComponent trace:", "\n\nComponent trace:",
componentStack componentStack,
]} ]}
{["\n\nError trace: ", error.stack]} {["\n\nError trace: ", error.stack]}
</pre> </pre>

View file

@ -17,7 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { PropsWithChildren } from "react"; import type { PropsWithChildren } from "react";
import React from "react";
import { Link, useRoute } from "wouter"; import { Link, useRoute } from "wouter";
import { import {
BaseUrlContext, BaseUrlContext,

View file

@ -26,9 +26,9 @@ const extended = gtsApi.injectEndpoints({
method: "POST", method: "POST",
url: `/api/v1/admin/media_cleanup`, url: `/api/v1/admin/media_cleanup`,
params: { params: {
remote_cache_days: days remote_cache_days: days,
} },
}) }),
}), }),
instanceKeysExpire: build.mutation({ instanceKeysExpire: build.mutation({
@ -36,9 +36,9 @@ const extended = gtsApi.injectEndpoints({
method: "POST", method: "POST",
url: `/api/v1/admin/domain_keys_expire`, url: `/api/v1/admin/domain_keys_expire`,
params: { params: {
domain: domain domain: domain,
} },
}) }),
}), }),
sendTestEmail: build.mutation<any, { email: string, message?: string }>({ sendTestEmail: build.mutation<any, { email: string, message?: string }>({
@ -46,7 +46,7 @@ const extended = gtsApi.injectEndpoints({
method: "POST", method: "POST",
url: `/api/v1/admin/email/test`, url: `/api/v1/admin/email/test`,
params: params, params: params,
}) }),
}), }),
}), }),
}); });

View file

@ -18,8 +18,8 @@
*/ */
import { gtsApi } from "../../gts-api"; import { gtsApi } from "../../gts-api";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; import type { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { RootState } from "../../../../redux/store"; import type { RootState } from "../../../../redux/store";
import type { CustomEmoji, EmojisFromItem, ListEmojiParams } from "../../../types/custom-emoji"; import type { CustomEmoji, EmojisFromItem, ListEmojiParams } from "../../../types/custom-emoji";
@ -77,39 +77,39 @@ const extended = gtsApi.injectEndpoints({
url: "/api/v1/admin/custom_emojis", url: "/api/v1/admin/custom_emojis",
params: { params: {
limit: 0, limit: 0,
...params ...params,
} },
}), }),
providesTags: (res, _error, _arg) => providesTags: (res, _error, _arg) =>
res res
? [ ? [
...res.map((emoji) => ({ type: "Emoji" as const, id: emoji.id })), ...res.map((emoji) => ({ type: "Emoji" as const, id: emoji.id })),
{ type: "Emoji", id: "LIST" } { type: "Emoji", id: "LIST" },
] ]
: [{ type: "Emoji", id: "LIST" }] : [{ type: "Emoji", id: "LIST" }],
}), }),
getEmoji: build.query<CustomEmoji, string>({ getEmoji: build.query<CustomEmoji, string>({
query: (id) => ({ query: (id) => ({
url: `/api/v1/admin/custom_emojis/${id}` url: `/api/v1/admin/custom_emojis/${id}`,
}), }),
providesTags: (_res, _error, id) => [{ type: "Emoji", id }] providesTags: (_res, _error, id) => [{ type: "Emoji", id }],
}), }),
addEmoji: build.mutation<CustomEmoji, Object>({ addEmoji: build.mutation<CustomEmoji, object>({
query: (form) => { query: (form) => {
return { return {
method: "POST", method: "POST",
url: `/api/v1/admin/custom_emojis`, url: `/api/v1/admin/custom_emojis`,
asForm: true, asForm: true,
body: form, body: form,
discardEmpty: true discardEmpty: true,
}; };
}, },
invalidatesTags: (res) => invalidatesTags: (res) =>
res res
? [{ type: "Emoji", id: "LIST" }, { type: "Emoji", id: res.id }] ? [{ type: "Emoji", id: "LIST" }, { type: "Emoji", id: res.id }]
: [{ type: "Emoji", id: "LIST" }] : [{ type: "Emoji", id: "LIST" }],
}), }),
editEmoji: build.mutation<CustomEmoji, any>({ editEmoji: build.mutation<CustomEmoji, any>({
@ -120,22 +120,22 @@ const extended = gtsApi.injectEndpoints({
asForm: true, asForm: true,
body: { body: {
type: "modify", type: "modify",
...patch ...patch,
} },
}; };
}, },
invalidatesTags: (res) => invalidatesTags: (res) =>
res res
? [{ type: "Emoji", id: "LIST" }, { type: "Emoji", id: res.id }] ? [{ type: "Emoji", id: "LIST" }, { type: "Emoji", id: res.id }]
: [{ type: "Emoji", id: "LIST" }] : [{ type: "Emoji", id: "LIST" }],
}), }),
deleteEmoji: build.mutation<any, string>({ deleteEmoji: build.mutation<any, string>({
query: (id) => ({ query: (id) => ({
method: "DELETE", method: "DELETE",
url: `/api/v1/admin/custom_emojis/${id}` url: `/api/v1/admin/custom_emojis/${id}`,
}), }),
invalidatesTags: (_res, _error, id) => [{ type: "Emoji", id }] invalidatesTags: (_res, _error, id) => [{ type: "Emoji", id }],
}), }),
searchItemForEmoji: build.mutation<EmojisFromItem, string>({ searchItemForEmoji: build.mutation<EmojisFromItem, string>({
@ -145,10 +145,10 @@ const extended = gtsApi.injectEndpoints({
// First search for given url. // First search for given url.
const searchRes = await fetchWithBQ({ const searchRes = await fetchWithBQ({
url: `/api/v2/search?q=${encodeURIComponent(url)}&resolve=true&limit=1` url: `/api/v2/search?q=${encodeURIComponent(url)}&resolve=true&limit=1`,
}); });
if (searchRes.error) { if (searchRes.error) {
return { error: searchRes.error as FetchBaseQueryError }; return { error: searchRes.error };
} }
// Parse initial results of search. // Parse initial results of search.
@ -178,8 +178,8 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/admin/custom_emojis`, url: `/api/v1/admin/custom_emojis`,
params: { params: {
filter: `domain:${domain},shortcode:${emoji.shortcode}`, filter: `domain:${domain},shortcode:${emoji.shortcode}`,
limit: 1 limit: 1,
} },
}); });
if (emojiRes.error) { if (emojiRes.error) {
@ -191,7 +191,7 @@ const extended = gtsApi.injectEndpoints({
// Got it! // Got it!
return emojiRes.data as CustomEmoji; return emojiRes.data as CustomEmoji;
}) }),
) )
).flatMap((emoji) => { ).flatMap((emoji) => {
// Remove any nulls. // Remove any nulls.
@ -205,7 +205,7 @@ const extended = gtsApi.injectEndpoints({
status: 400, status: 400,
statusText: 'Bad Request', statusText: 'Bad Request',
data: { data: {
error: `One or more errors fetching custom emojis: [${errData}]` error: `One or more errors fetching custom emojis: [${errData}]`,
}, },
}, },
}; };
@ -218,9 +218,9 @@ const extended = gtsApi.injectEndpoints({
type, type,
domain, domain,
list: withIDs, list: withIDs,
} },
}; };
} },
}), }),
patchRemoteEmojis: build.mutation({ patchRemoteEmojis: build.mutation({
@ -231,7 +231,7 @@ const extended = gtsApi.injectEndpoints({
// Map function to get a promise // Map function to get a promise
// of an emoji (or null). // of an emoji (or null).
const copyEmoji = async(emoji: CustomEmoji) => { const copyEmoji = async(emoji: CustomEmoji) => {
let body: { const body: {
type: string; type: string;
shortcode?: string; shortcode?: string;
category?: string; category?: string;
@ -285,7 +285,7 @@ const extended = gtsApi.injectEndpoints({
status: 400, status: 400,
statusText: 'Bad Request', statusText: 'Bad Request',
data: { data: {
error: `One or more errors patching custom emojis: [${errData}]` error: `One or more errors patching custom emojis: [${errData}]`,
}, },
}, },
}; };
@ -293,9 +293,9 @@ const extended = gtsApi.injectEndpoints({
return { data }; return { data };
}, },
invalidatesTags: () => [{ type: "Emoji", id: "LIST" }] invalidatesTags: () => [{ type: "Emoji", id: "LIST" }],
}) }),
}) }),
}); });
/** /**

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ApURLResponse } from "../../../types/debug"; import type { ApURLResponse } from "../../../types/debug";
import { gtsApi } from "../../gts-api"; import { gtsApi } from "../../gts-api";
const extended = gtsApi.injectEndpoints({ const extended = gtsApi.injectEndpoints({
@ -32,13 +32,13 @@ const extended = gtsApi.injectEndpoints({
return { return {
url: `/api/v1/admin/debug/apurl?${urlParam.toString()}`, url: `/api/v1/admin/debug/apurl?${urlParam.toString()}`,
}; };
} },
}), }),
ClearCaches: build.mutation<{}, void>({ ClearCaches: build.mutation<{}, void>({
query: () => ({ query: () => ({
method: "POST", method: "POST",
url: `/api/v1/admin/debug/caches/clear` url: `/api/v1/admin/debug/caches/clear`,
}) }),
}), }),
}), }),
}); });

View file

@ -21,9 +21,9 @@ import fileDownload from "js-file-download";
import { unparse as csvUnparse } from "papaparse"; import { unparse as csvUnparse } from "papaparse";
import { gtsApi } from "../../gts-api"; import { gtsApi } from "../../gts-api";
import { RootState } from "../../../../redux/store"; import type { RootState } from "../../../../redux/store";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; import type { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { DomainPerm, ExportDomainPermsParams } from "../../../types/domain-permission"; import type { DomainPerm, ExportDomainPermsParams } from "../../../types/domain-permission";
interface _exportProcess { interface _exportProcess {
transformEntry: (_entry: DomainPerm) => any; transformEntry: (_entry: DomainPerm) => any;
@ -45,11 +45,11 @@ function exportProcess(formData: ExportDomainPermsParams): _exportProcess {
transformEntry: (entry) => ({ transformEntry: (entry) => ({
domain: entry.domain, domain: entry.domain,
public_comment: entry.public_comment, public_comment: entry.public_comment,
obfuscate: entry.obfuscate obfuscate: entry.obfuscate,
}), }),
stringify: (list) => JSON.stringify(list), stringify: (list) => JSON.stringify(list),
extension: ".json", extension: ".json",
mime: "application/json" mime: "application/json",
}; };
} }
@ -61,7 +61,7 @@ function exportProcess(formData: ExportDomainPermsParams): _exportProcess {
false, // reject_media false, // reject_media
false, // reject_reports false, // reject_reports
entry.public_comment ?? "", // public_comment entry.public_comment ?? "", // public_comment
entry.obfuscate ?? false // obfuscate entry.obfuscate ?? false, // obfuscate
], ],
stringify: (list) => csvUnparse({ stringify: (list) => csvUnparse({
fields: [ fields: [
@ -72,10 +72,10 @@ function exportProcess(formData: ExportDomainPermsParams): _exportProcess {
"#public_comment", "#public_comment",
"#obfuscate", "#obfuscate",
], ],
data: list data: list,
}), }),
extension: ".csv", extension: ".csv",
mime: "text/csv" mime: "text/csv",
}; };
} }
@ -84,7 +84,7 @@ function exportProcess(formData: ExportDomainPermsParams): _exportProcess {
transformEntry: (entry) => entry.domain, transformEntry: (entry) => entry.domain,
stringify: (list) => list.join("\n"), stringify: (list) => list.join("\n"),
extension: ".txt", extension: ".txt",
mime: "text/plain" mime: "text/plain",
}; };
} }
@ -98,7 +98,7 @@ const extended = gtsApi.injectEndpoints({
// we want the untransformed array version. // we want the untransformed array version.
const permsRes = await fetchWithBQ({ url: `/api/v1/admin/domain_${formData.permType}s` }); const permsRes = await fetchWithBQ({ url: `/api/v1/admin/domain_${formData.permType}s` });
if (permsRes.error) { if (permsRes.error) {
return { error: permsRes.error as FetchBaseQueryError }; return { error: permsRes.error };
} }
// Process perms into desired export format. // Process perms into desired export format.
@ -130,16 +130,16 @@ const extended = gtsApi.injectEndpoints({
fileDownload( fileDownload(
exportAsString, exportAsString,
filename + process.extension, filename + process.extension,
process.mime process.mime,
); );
// js-file-download handles the // js-file-download handles the
// nitty gritty for us, so we can // nitty gritty for us, so we can
// just return null data. // just return null data.
return { data: null }; return { data: null };
} },
}), }),
}) }),
}); });
/** /**

View file

@ -26,14 +26,14 @@ const extended = gtsApi.injectEndpoints({
endpoints: (build) => ({ endpoints: (build) => ({
domainBlocks: build.query<MappedDomainPerms, void>({ domainBlocks: build.query<MappedDomainPerms, void>({
query: () => ({ query: () => ({
url: `/api/v1/admin/domain_blocks` url: `/api/v1/admin/domain_blocks`,
}), }),
transformResponse: listToKeyedObject<DomainPerm>("domain"), transformResponse: listToKeyedObject<DomainPerm>("domain"),
}), }),
domainAllows: build.query<MappedDomainPerms, void>({ domainAllows: build.query<MappedDomainPerms, void>({
query: () => ({ query: () => ({
url: `/api/v1/admin/domain_allows` url: `/api/v1/admin/domain_allows`,
}), }),
transformResponse: listToKeyedObject<DomainPerm>("domain"), transformResponse: listToKeyedObject<DomainPerm>("domain"),
}), }),

View file

@ -36,7 +36,7 @@ import { listToKeyedObject } from "../../transforms";
* @returns * @returns
*/ */
function importEntriesProcessor(formData: ImportDomainPermsParams): (_entry: DomainPerm) => DomainPerm { function importEntriesProcessor(formData: ImportDomainPermsParams): (_entry: DomainPerm) => DomainPerm {
let processingFuncs: { (_entry: DomainPerm): void; }[] = []; const processingFuncs: { (_entry: DomainPerm): void; }[] = [];
// Override each obfuscate entry if necessary. // Override each obfuscate entry if necessary.
if (formData.obfuscate !== undefined) { if (formData.obfuscate !== undefined) {
@ -49,7 +49,7 @@ function importEntriesProcessor(formData: ImportDomainPermsParams): (_entry: Dom
// Check whether we need to append or replace // Check whether we need to append or replace
// private_comment and public_comment. // private_comment and public_comment.
["private_comment","public_comment"].forEach((commentType) => { ["private_comment","public_comment"].forEach((commentType) => {
let text = formData.commentType?.trim(); const text = formData.commentType?.trim();
if (!text) { if (!text) {
return; return;
} }
@ -78,7 +78,9 @@ function importEntriesProcessor(formData: ImportDomainPermsParams): (_entry: Dom
return function process(entry) { return function process(entry) {
// Call all the assembled processing functions. // Call all the assembled processing functions.
processingFuncs.forEach((f) => f(entry)); processingFuncs.forEach((f) => {
f(entry);
});
// Unset all internal processing keys // Unset all internal processing keys
// and any undefined keys on this entry. // and any undefined keys on this entry.
@ -111,7 +113,7 @@ const extended = gtsApi.injectEndpoints({
[JSON.stringify(domains)], [JSON.stringify(domains)],
{ type: "application/json" }, { type: "application/json" },
), ),
} },
}; };
}, },
transformResponse: listToKeyedObject<DomainPerm>("domain"), transformResponse: listToKeyedObject<DomainPerm>("domain"),
@ -125,8 +127,8 @@ const extended = gtsApi.injectEndpoints({
formData.permType.slice(1); formData.permType.slice(1);
return `domain${permType}s`; return `domain${permType}s`;
}), }),
}) }),
}) }),
}); });
/** /**

View file

@ -17,9 +17,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import type {
ParseConfig as CSVParseConfig} from "papaparse";
import { import {
ParseConfig as CSVParseConfig, parse as csvParse,
parse as csvParse
} from "papaparse"; } from "papaparse";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
@ -75,7 +76,7 @@ function parseDomainList(list: string): DomainPerm[] {
"reject_reports": true, "reject_reports": true,
"public_comment": false, "public_comment": false,
"obfuscate": true, "obfuscate": true,
} },
}; };
const { data, errors } = csvParse(list, csvParseCfg); const { data, errors } = csvParse(list, csvParseCfg);
@ -119,7 +120,7 @@ function parseDomainList(list: string): DomainPerm[] {
} }
function deduplicateDomainList(list: DomainPerm[]): DomainPerm[] { function deduplicateDomainList(list: DomainPerm[]): DomainPerm[] {
let domains = new Set(); const domains = new Set();
return list.filter((entry) => { return list.filter((entry) => {
if (domains.has(entry.domain)) { if (domains.has(entry.domain)) {
return false; return false;
@ -168,9 +169,9 @@ const extended = gtsApi.injectEndpoints({
}); });
return { data: validated }; return { data: validated };
} },
}) }),
}) }),
}); });
/** /**

View file

@ -26,7 +26,7 @@ import {
import { listToKeyedObject } from "../../transforms"; import { listToKeyedObject } from "../../transforms";
import type { import type {
DomainPerm, DomainPerm,
MappedDomainPerms MappedDomainPerms,
} from "../../../types/domain-permission"; } from "../../../types/domain-permission";
const extended = gtsApi.injectEndpoints({ const extended = gtsApi.injectEndpoints({
@ -37,7 +37,7 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/admin/domain_blocks`, url: `/api/v1/admin/domain_blocks`,
asForm: true, asForm: true,
body: formData, body: formData,
discardEmpty: true discardEmpty: true,
}), }),
transformResponse: listToKeyedObject<DomainPerm>("domain"), transformResponse: listToKeyedObject<DomainPerm>("domain"),
...replaceCacheOnMutation("domainBlocks"), ...replaceCacheOnMutation("domainBlocks"),
@ -49,10 +49,10 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/admin/domain_allows`, url: `/api/v1/admin/domain_allows`,
asForm: true, asForm: true,
body: formData, body: formData,
discardEmpty: true discardEmpty: true,
}), }),
transformResponse: listToKeyedObject<DomainPerm>("domain"), transformResponse: listToKeyedObject<DomainPerm>("domain"),
...replaceCacheOnMutation("domainAllows") ...replaceCacheOnMutation("domainAllows"),
}), }),
removeDomainBlock: build.mutation<DomainPerm, string>({ removeDomainBlock: build.mutation<DomainPerm, string>({
@ -63,8 +63,8 @@ const extended = gtsApi.injectEndpoints({
...removeFromCacheOnMutation("domainBlocks", { ...removeFromCacheOnMutation("domainBlocks", {
key: (_draft, newData) => { key: (_draft, newData) => {
return newData.domain; return newData.domain;
} },
}) }),
}), }),
removeDomainAllow: build.mutation<DomainPerm, string>({ removeDomainAllow: build.mutation<DomainPerm, string>({
@ -75,8 +75,8 @@ const extended = gtsApi.injectEndpoints({
...removeFromCacheOnMutation("domainAllows", { ...removeFromCacheOnMutation("domainAllows", {
key: (_draft, newData) => { key: (_draft, newData) => {
return newData.domain; return newData.domain;
} },
}) }),
}), }),
}), }),
}); });
@ -105,5 +105,5 @@ export {
useAddDomainBlockMutation, useAddDomainBlockMutation,
useAddDomainAllowMutation, useAddDomainAllowMutation,
useRemoveDomainBlockMutation, useRemoveDomainBlockMutation,
useRemoveDomainAllowMutation useRemoveDomainAllowMutation,
}; };

View file

@ -18,7 +18,7 @@
*/ */
import { gtsApi } from "../../gts-api"; import { gtsApi } from "../../gts-api";
import { HeaderPermission } from "../../../types/http-header-permissions"; import type { HeaderPermission } from "../../../types/http-header-permissions";
const extended = gtsApi.injectEndpoints({ const extended = gtsApi.injectEndpoints({
endpoints: (build) => ({ endpoints: (build) => ({
@ -27,7 +27,7 @@ const extended = gtsApi.injectEndpoints({
getHeaderAllows: build.query<HeaderPermission[], void>({ getHeaderAllows: build.query<HeaderPermission[], void>({
query: () => ({ query: () => ({
url: `/api/v1/admin/header_allows` url: `/api/v1/admin/header_allows`,
}), }),
providesTags: (res) => providesTags: (res) =>
res res
@ -40,7 +40,7 @@ const extended = gtsApi.injectEndpoints({
getHeaderAllow: build.query<HeaderPermission, string>({ getHeaderAllow: build.query<HeaderPermission, string>({
query: (id) => ({ query: (id) => ({
url: `/api/v1/admin/header_allows/${id}` url: `/api/v1/admin/header_allows/${id}`,
}), }),
providesTags: (_res, _error, id) => [{ type: "HTTPHeaderAllows", id }], providesTags: (_res, _error, id) => [{ type: "HTTPHeaderAllows", id }],
}), }),
@ -51,7 +51,7 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/admin/header_allows`, url: `/api/v1/admin/header_allows`,
asForm: true, asForm: true,
body: formData, body: formData,
discardEmpty: true discardEmpty: true,
}), }),
invalidatesTags: [{ type: "HTTPHeaderAllows", id: "LIST" }], invalidatesTags: [{ type: "HTTPHeaderAllows", id: "LIST" }],
}), }),
@ -59,7 +59,7 @@ const extended = gtsApi.injectEndpoints({
deleteHeaderAllow: build.mutation<HeaderPermission, string>({ deleteHeaderAllow: build.mutation<HeaderPermission, string>({
query: (id) => ({ query: (id) => ({
method: "DELETE", method: "DELETE",
url: `/api/v1/admin/header_allows/${id}` url: `/api/v1/admin/header_allows/${id}`,
}), }),
invalidatesTags: (_res, _error, id) => [{ type: "HTTPHeaderAllows", id }], invalidatesTags: (_res, _error, id) => [{ type: "HTTPHeaderAllows", id }],
}), }),
@ -68,7 +68,7 @@ const extended = gtsApi.injectEndpoints({
getHeaderBlocks: build.query<HeaderPermission[], void>({ getHeaderBlocks: build.query<HeaderPermission[], void>({
query: () => ({ query: () => ({
url: `/api/v1/admin/header_blocks` url: `/api/v1/admin/header_blocks`,
}), }),
providesTags: (res) => providesTags: (res) =>
res res
@ -85,14 +85,14 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/admin/header_blocks`, url: `/api/v1/admin/header_blocks`,
asForm: true, asForm: true,
body: formData, body: formData,
discardEmpty: true discardEmpty: true,
}), }),
invalidatesTags: [{ type: "HTTPHeaderBlocks", id: "LIST" }], invalidatesTags: [{ type: "HTTPHeaderBlocks", id: "LIST" }],
}), }),
getHeaderBlock: build.query<HeaderPermission, string>({ getHeaderBlock: build.query<HeaderPermission, string>({
query: (id) => ({ query: (id) => ({
url: `/api/v1/admin/header_blocks/${id}` url: `/api/v1/admin/header_blocks/${id}`,
}), }),
providesTags: (_res, _error, id) => [{ type: "HTTPHeaderBlocks", id }], providesTags: (_res, _error, id) => [{ type: "HTTPHeaderBlocks", id }],
}), }),
@ -100,7 +100,7 @@ const extended = gtsApi.injectEndpoints({
deleteHeaderBlock: build.mutation<HeaderPermission, string>({ deleteHeaderBlock: build.mutation<HeaderPermission, string>({
query: (id) => ({ query: (id) => ({
method: "DELETE", method: "DELETE",
url: `/api/v1/admin/header_blocks/${id}` url: `/api/v1/admin/header_blocks/${id}`,
}), }),
invalidatesTags: (_res, _error, id) => [{ type: "HTTPHeaderBlocks", id }], invalidatesTags: (_res, _error, id) => [{ type: "HTTPHeaderBlocks", id }],
}), }),

View file

@ -20,8 +20,8 @@
import { replaceCacheOnMutation, removeFromCacheOnMutation } from "../query-modifiers"; import { replaceCacheOnMutation, removeFromCacheOnMutation } from "../query-modifiers";
import { gtsApi } from "../gts-api"; import { gtsApi } from "../gts-api";
import { listToKeyedObject } from "../transforms"; import { listToKeyedObject } from "../transforms";
import { ActionAccountParams, AdminAccount, HandleSignupParams, SearchAccountParams, SearchAccountResp } from "../../types/account"; import type { ActionAccountParams, AdminAccount, HandleSignupParams, SearchAccountParams, SearchAccountResp } from "../../types/account";
import { InstanceRule, MappedRules } from "../../types/rules"; import type { InstanceRule, MappedRules } from "../../types/rules";
import parse from "parse-link-header"; import parse from "parse-link-header";
const extended = gtsApi.injectEndpoints({ const extended = gtsApi.injectEndpoints({
@ -32,17 +32,17 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/instance`, url: `/api/v1/instance`,
asForm: true, asForm: true,
body: formData, body: formData,
discardEmpty: true discardEmpty: true,
}), }),
...replaceCacheOnMutation("instanceV1"), ...replaceCacheOnMutation("instanceV1"),
}), }),
getAccount: build.query<AdminAccount, string>({ getAccount: build.query<AdminAccount, string>({
query: (id) => ({ query: (id) => ({
url: `/api/v1/admin/accounts/${id}` url: `/api/v1/admin/accounts/${id}`,
}), }),
providesTags: (_result, _error, id) => [ providesTags: (_result, _error, id) => [
{ type: 'Account', id } { type: 'Account', id },
], ],
}), }),
@ -61,7 +61,7 @@ const extended = gtsApi.injectEndpoints({
} }
return { return {
url: `/api/v2/admin/accounts${query}` url: `/api/v2/admin/accounts${query}`,
}; };
}, },
// Headers required for paging. // Headers required for paging.
@ -73,7 +73,7 @@ const extended = gtsApi.injectEndpoints({
}, },
// Only provide LIST tag id since this model is not the // Only provide LIST tag id since this model is not the
// same as getAccount model (due to transformResponse). // same as getAccount model (due to transformResponse).
providesTags: [{ type: "Account", id: "TRANSFORMED" }] providesTags: [{ type: "Account", id: "TRANSFORMED" }],
}), }),
actionAccount: build.mutation<string, ActionAccountParams>({ actionAccount: build.mutation<string, ActionAccountParams>({
@ -83,8 +83,8 @@ const extended = gtsApi.injectEndpoints({
asForm: true, asForm: true,
body: { body: {
type: action, type: action,
text: reason text: reason,
} },
}), }),
// Do an optimistic update on this account to mark // Do an optimistic update on this account to mark
// it according to whatever action was submitted. // it according to whatever action was submitted.
@ -95,7 +95,7 @@ const extended = gtsApi.injectEndpoints({
draft.suspended = true; draft.suspended = true;
draft.account.suspended = true; draft.account.suspended = true;
} }
}) }),
); );
// Revert optimistic // Revert optimistic
@ -105,7 +105,7 @@ const extended = gtsApi.injectEndpoints({
} catch { } catch {
patchResult.undo(); patchResult.undo();
} }
} },
}), }),
handleSignup: build.mutation<AdminAccount, HandleSignupParams>({ handleSignup: build.mutation<AdminAccount, HandleSignupParams>({
@ -124,7 +124,7 @@ const extended = gtsApi.injectEndpoints({
// Just invalidate this ID and getAccounts. // Just invalidate this ID and getAccounts.
dispatch(extended.util.invalidateTags([ dispatch(extended.util.invalidateTags([
{ type: "Account", id: id }, { type: "Account", id: id },
{ type: "Account", id: "TRANSFORMED" } { type: "Account", id: "TRANSFORMED" },
])); ]));
return; return;
} }
@ -132,7 +132,7 @@ const extended = gtsApi.injectEndpoints({
const patchResult = dispatch( const patchResult = dispatch(
extended.util.updateQueryData("getAccount", id, (draft) => { extended.util.updateQueryData("getAccount", id, (draft) => {
draft.approved = true; draft.approved = true;
}) }),
); );
// Revert optimistic // Revert optimistic
@ -142,14 +142,14 @@ const extended = gtsApi.injectEndpoints({
} catch { } catch {
patchResult.undo(); patchResult.undo();
} }
} },
}), }),
instanceRules: build.query<MappedRules, void>({ instanceRules: build.query<MappedRules, void>({
query: () => ({ query: () => ({
url: `/api/v1/admin/instance/rules` url: `/api/v1/admin/instance/rules`,
}), }),
transformResponse: listToKeyedObject<InstanceRule>("id") transformResponse: listToKeyedObject<InstanceRule>("id"),
}), }),
addInstanceRule: build.mutation<MappedRules, any>({ addInstanceRule: build.mutation<MappedRules, any>({
@ -158,7 +158,7 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/admin/instance/rules`, url: `/api/v1/admin/instance/rules`,
asForm: true, asForm: true,
body: formData, body: formData,
discardEmpty: true discardEmpty: true,
}), }),
transformResponse: listToKeyedObject<InstanceRule>("id"), transformResponse: listToKeyedObject<InstanceRule>("id"),
...replaceCacheOnMutation("instanceRules"), ...replaceCacheOnMutation("instanceRules"),
@ -170,11 +170,11 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/admin/instance/rules/${id}`, url: `/api/v1/admin/instance/rules/${id}`,
asForm: true, asForm: true,
body: edit, body: edit,
discardEmpty: true discardEmpty: true,
}), }),
transformResponse: (data) => { transformResponse: (data) => {
return { return {
[data.id]: data [data.id]: data,
}; };
}, },
...replaceCacheOnMutation("instanceRules"), ...replaceCacheOnMutation("instanceRules"),
@ -183,13 +183,13 @@ const extended = gtsApi.injectEndpoints({
deleteInstanceRule: build.mutation({ deleteInstanceRule: build.mutation({
query: (id) => ({ query: (id) => ({
method: "DELETE", method: "DELETE",
url: `/api/v1/admin/instance/rules/${id}` url: `/api/v1/admin/instance/rules/${id}`,
}), }),
...removeFromCacheOnMutation("instanceRules", { ...removeFromCacheOnMutation("instanceRules", {
key: (_draft, rule) => rule.id, key: (_draft, rule) => rule.id,
}) }),
}) }),
}) }),
}); });
export const { export const {

View file

@ -44,7 +44,7 @@ const extended = gtsApi.injectEndpoints({
} }
return { return {
url: `/api/v1/admin/reports${query}` url: `/api/v1/admin/reports${query}`,
}; };
}, },
// Headers required for paging. // Headers required for paging.
@ -56,15 +56,15 @@ const extended = gtsApi.injectEndpoints({
}, },
// Only provide LIST tag id since this model is not the // Only provide LIST tag id since this model is not the
// same as getReport model (due to transformResponse). // same as getReport model (due to transformResponse).
providesTags: [{ type: "Report", id: "TRANSFORMED" }] providesTags: [{ type: "Report", id: "TRANSFORMED" }],
}), }),
getReport: build.query<AdminReport, string>({ getReport: build.query<AdminReport, string>({
query: (id) => ({ query: (id) => ({
url: `/api/v1/admin/reports/${id}` url: `/api/v1/admin/reports/${id}`,
}), }),
providesTags: (_result, _error, id) => [ providesTags: (_result, _error, id) => [
{ type: 'Report', id } { type: 'Report', id },
], ],
}), }),
@ -73,14 +73,14 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/admin/reports/${formData.id}/resolve`, url: `/api/v1/admin/reports/${formData.id}/resolve`,
method: "POST", method: "POST",
asForm: true, asForm: true,
body: formData body: formData,
}), }),
invalidatesTags: (res) => invalidatesTags: (res) =>
res res
? [{ type: "Report", id: "TRANSFORMED" }, { type: "Report", id: res.id }] ? [{ type: "Report", id: "TRANSFORMED" }, { type: "Report", id: res.id }]
: [{ type: "Report", id: "TRANSFORMED" }] : [{ type: "Report", id: "TRANSFORMED" }],
}) }),
}) }),
}); });
/** /**

View file

@ -26,7 +26,7 @@ import type {
import { serialize as serializeForm } from "object-to-formdata"; import { serialize as serializeForm } from "object-to-formdata";
import type { FetchBaseQueryMeta } from "@reduxjs/toolkit/dist/query/fetchBaseQuery"; import type { FetchBaseQueryMeta } from "@reduxjs/toolkit/dist/query/fetchBaseQuery";
import type { RootState } from '../../redux/store'; import type { RootState } from '../../redux/store';
import { InstanceV1 } from '../types/instance'; import type { InstanceV1 } from '../types/instance';
/** /**
* GTSFetchArgs extends standard FetchArgs used by * GTSFetchArgs extends standard FetchArgs used by
@ -173,10 +173,10 @@ export const gtsApi = createApi({
endpoints: (build) => ({ endpoints: (build) => ({
instanceV1: build.query<InstanceV1, void>({ instanceV1: build.query<InstanceV1, void>({
query: () => ({ query: () => ({
url: `/api/v1/instance` url: `/api/v1/instance`,
}) }),
}) }),
}) }),
}); });
/** /**

View file

@ -25,8 +25,8 @@ import {
remove as oauthRemove, remove as oauthRemove,
authorize as oauthAuthorize, authorize as oauthAuthorize,
} from "../../../redux/oauth"; } from "../../../redux/oauth";
import { RootState } from '../../../redux/store'; import type { RootState } from '../../../redux/store';
import { Account } from '../../types/account'; import type { Account } from '../../types/account';
export interface OauthTokenRequestBody { export interface OauthTokenRequestBody {
client_id: string; client_id: string;
@ -45,7 +45,7 @@ function getSettingsURL() {
Also drops anything past /settings/, because authorization urls that are too long Also drops anything past /settings/, because authorization urls that are too long
get rejected by GTS. get rejected by GTS.
*/ */
let [pre, _past] = window.location.pathname.split("/settings"); const [pre, _past] = window.location.pathname.split("/settings");
return `${window.location.origin}${pre}/settings`; return `${window.location.origin}${pre}/settings`;
} }
@ -71,14 +71,14 @@ const extended = gtsApi.injectEndpoints({
// return a standard verify_credentials query. // return a standard verify_credentials query.
if (oauthState.loginState != 'callback') { if (oauthState.loginState != 'callback') {
return fetchWithBQ({ return fetchWithBQ({
url: `/api/v1/accounts/verify_credentials` url: `/api/v1/accounts/verify_credentials`,
}); });
} }
// We're in the middle of an auth/callback flow. // We're in the middle of an auth/callback flow.
// Try to retrieve callback code from URL query. // Try to retrieve callback code from URL query.
let urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
let code = urlParams.get("code"); const code = urlParams.get("code");
if (code == undefined) { if (code == undefined) {
return { return {
error: { error: {
@ -91,7 +91,7 @@ const extended = gtsApi.injectEndpoints({
// Retrieve app with which the // Retrieve app with which the
// callback code was generated. // callback code was generated.
let app = oauthState.app; const app = oauthState.app;
if (app == undefined || app.client_id == undefined) { if (app == undefined || app.client_id == undefined) {
return { return {
error: { error: {
@ -109,7 +109,7 @@ const extended = gtsApi.injectEndpoints({
client_secret: app.client_secret, client_secret: app.client_secret,
redirect_uri: SETTINGS_URL, redirect_uri: SETTINGS_URL,
grant_type: "authorization_code", grant_type: "authorization_code",
code: code code: code,
}; };
const tokenResult = await fetchWithBQ({ const tokenResult = await fetchWithBQ({
@ -118,7 +118,7 @@ const extended = gtsApi.injectEndpoints({
body: tokenReqBody, body: tokenReqBody,
}); });
if (tokenResult.error) { if (tokenResult.error) {
return { error: tokenResult.error as FetchBaseQueryError }; return { error: tokenResult.error };
} }
// Remove ?code= query param from // Remove ?code= query param from
@ -131,9 +131,9 @@ const extended = gtsApi.injectEndpoints({
// We're now authed! So return // We're now authed! So return
// standard verify_credentials query. // standard verify_credentials query.
return fetchWithBQ({ return fetchWithBQ({
url: `/api/v1/accounts/verify_credentials` url: `/api/v1/accounts/verify_credentials`,
}); });
} },
}), }),
authorizeFlow: build.mutation({ authorizeFlow: build.mutation({
@ -147,7 +147,7 @@ const extended = gtsApi.injectEndpoints({
} }
instanceUrl = new URL(formData.instance).origin; instanceUrl = new URL(formData.instance).origin;
if (oauthState?.instanceUrl == instanceUrl && oauthState.app) { if (oauthState.instanceUrl == instanceUrl && oauthState.app) {
return { data: oauthState.app }; return { data: oauthState.app };
} }
@ -159,31 +159,31 @@ const extended = gtsApi.injectEndpoints({
client_name: "GoToSocial Settings", client_name: "GoToSocial Settings",
scopes: formData.scopes, scopes: formData.scopes,
redirect_uris: SETTINGS_URL, redirect_uris: SETTINGS_URL,
website: SETTINGS_URL website: SETTINGS_URL,
} },
}); });
if (appResult.error) { if (appResult.error) {
return { error: appResult.error as FetchBaseQueryError }; return { error: appResult.error };
} }
let app = appResult.data as any; const app = appResult.data;
app.scopes = formData.scopes; app.scopes = formData.scopes;
api.dispatch(oauthAuthorize({ api.dispatch(oauthAuthorize({
instanceUrl: instanceUrl, instanceUrl: instanceUrl,
app: app, app: app,
loginState: "callback", loginState: "callback",
expectingRedirect: true expectingRedirect: true,
})); }));
let url = new URL(instanceUrl); const url = new URL(instanceUrl);
url.pathname = "/oauth/authorize"; url.pathname = "/oauth/authorize";
url.searchParams.set("client_id", app.client_id); url.searchParams.set("client_id", app.client_id);
url.searchParams.set("redirect_uri", SETTINGS_URL); url.searchParams.set("redirect_uri", SETTINGS_URL);
url.searchParams.set("response_type", "code"); url.searchParams.set("response_type", "code");
url.searchParams.set("scope", app.scopes); url.searchParams.set("scope", app.scopes);
let redirectURL = url.toString(); const redirectURL = url.toString();
window.location.assign(redirectURL); window.location.assign(redirectURL);
return { data: null }; return { data: null };
}, },
@ -193,9 +193,9 @@ const extended = gtsApi.injectEndpoints({
api.dispatch(oauthRemove()); api.dispatch(oauthRemove());
return { data: null }; return { data: null };
}, },
invalidatesTags: ["Auth"] invalidatesTags: ["Auth"],
}) }),
}) }),
}); });
export const { export const {

View file

@ -86,13 +86,13 @@ function makeCacheMutation(action: Action): CacheMutation {
key = key(draft, newData); key = key(draft, newData);
} }
action(draft, newData, { key }); action(draft, newData, { key });
}) }),
); );
} catch (e) { } catch (e) {
// eslint-disable-next-line no-console
console.error(`rolling back pessimistic update of ${queryName}: ${JSON.stringify(e)}`); console.error(`rolling back pessimistic update of ${queryName}: ${JSON.stringify(e)}`);
} }
} },
}; };
}; };
} }

View file

@ -20,15 +20,15 @@
import fileDownload from "js-file-download"; import fileDownload from "js-file-download";
import { gtsApi } from "../gts-api"; import { gtsApi } from "../gts-api";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; import type { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { AccountExportStats } from "../../types/account"; import type { AccountExportStats } from "../../types/account";
const extended = gtsApi.injectEndpoints({ const extended = gtsApi.injectEndpoints({
endpoints: (build) => ({ endpoints: (build) => ({
exportStats: build.query<AccountExportStats, void>({ exportStats: build.query<AccountExportStats, void>({
query: () => ({ query: () => ({
url: `/api/v1/exports/stats` url: `/api/v1/exports/stats`,
}) }),
}), }),
exportFollowing: build.mutation<string | null, void>({ exportFollowing: build.mutation<string | null, void>({
@ -38,7 +38,7 @@ const extended = gtsApi.injectEndpoints({
acceptContentType: "text/csv", acceptContentType: "text/csv",
}); });
if (csvRes.error) { if (csvRes.error) {
return { error: csvRes.error as FetchBaseQueryError }; return { error: csvRes.error };
} }
if (csvRes.meta?.response?.status !== 200) { if (csvRes.meta?.response?.status !== 200) {
@ -47,7 +47,7 @@ const extended = gtsApi.injectEndpoints({
fileDownload(csvRes.data, "following.csv", "text/csv"); fileDownload(csvRes.data, "following.csv", "text/csv");
return { data: null }; return { data: null };
} },
}), }),
exportFollowers: build.mutation<string | null, void>({ exportFollowers: build.mutation<string | null, void>({
@ -57,7 +57,7 @@ const extended = gtsApi.injectEndpoints({
acceptContentType: "text/csv", acceptContentType: "text/csv",
}); });
if (csvRes.error) { if (csvRes.error) {
return { error: csvRes.error as FetchBaseQueryError }; return { error: csvRes.error };
} }
if (csvRes.meta?.response?.status !== 200) { if (csvRes.meta?.response?.status !== 200) {
@ -66,7 +66,7 @@ const extended = gtsApi.injectEndpoints({
fileDownload(csvRes.data, "followers.csv", "text/csv"); fileDownload(csvRes.data, "followers.csv", "text/csv");
return { data: null }; return { data: null };
} },
}), }),
exportLists: build.mutation<string | null, void>({ exportLists: build.mutation<string | null, void>({
@ -76,7 +76,7 @@ const extended = gtsApi.injectEndpoints({
acceptContentType: "text/csv", acceptContentType: "text/csv",
}); });
if (csvRes.error) { if (csvRes.error) {
return { error: csvRes.error as FetchBaseQueryError }; return { error: csvRes.error };
} }
if (csvRes.meta?.response?.status !== 200) { if (csvRes.meta?.response?.status !== 200) {
@ -85,7 +85,7 @@ const extended = gtsApi.injectEndpoints({
fileDownload(csvRes.data, "lists.csv", "text/csv"); fileDownload(csvRes.data, "lists.csv", "text/csv");
return { data: null }; return { data: null };
} },
}), }),
exportBlocks: build.mutation<string | null, void>({ exportBlocks: build.mutation<string | null, void>({
@ -95,7 +95,7 @@ const extended = gtsApi.injectEndpoints({
acceptContentType: "text/csv", acceptContentType: "text/csv",
}); });
if (csvRes.error) { if (csvRes.error) {
return { error: csvRes.error as FetchBaseQueryError }; return { error: csvRes.error };
} }
if (csvRes.meta?.response?.status !== 200) { if (csvRes.meta?.response?.status !== 200) {
@ -104,7 +104,7 @@ const extended = gtsApi.injectEndpoints({
fileDownload(csvRes.data, "blocks.csv", "text/csv"); fileDownload(csvRes.data, "blocks.csv", "text/csv");
return { data: null }; return { data: null };
} },
}), }),
exportMutes: build.mutation<string | null, void>({ exportMutes: build.mutation<string | null, void>({
@ -114,7 +114,7 @@ const extended = gtsApi.injectEndpoints({
acceptContentType: "text/csv", acceptContentType: "text/csv",
}); });
if (csvRes.error) { if (csvRes.error) {
return { error: csvRes.error as FetchBaseQueryError }; return { error: csvRes.error };
} }
if (csvRes.meta?.response?.status !== 200) { if (csvRes.meta?.response?.status !== 200) {
@ -123,7 +123,7 @@ const extended = gtsApi.injectEndpoints({
fileDownload(csvRes.data, "mutes.csv", "text/csv"); fileDownload(csvRes.data, "mutes.csv", "text/csv");
return { data: null }; return { data: null };
} },
}), }),
importData: build.mutation({ importData: build.mutation({
@ -132,10 +132,10 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/import`, url: `/api/v1/import`,
asForm: true, asForm: true,
body: formData, body: formData,
discardEmpty: true discardEmpty: true,
}), }),
}), }),
}) }),
}); });
export const { export const {

View file

@ -21,11 +21,11 @@ import { replaceCacheOnMutation } from "../query-modifiers";
import { gtsApi } from "../gts-api"; import { gtsApi } from "../gts-api";
import type { import type {
MoveAccountFormData, MoveAccountFormData,
UpdateAliasesFormData UpdateAliasesFormData,
} from "../../types/migration"; } from "../../types/migration";
import type { Theme } from "../../types/theme"; import type { Theme } from "../../types/theme";
import { User } from "../../types/user"; import type { User } from "../../types/user";
import { DefaultInteractionPolicies, UpdateDefaultInteractionPolicies } from "../../types/interaction"; import type { DefaultInteractionPolicies, UpdateDefaultInteractionPolicies } from "../../types/interaction";
const extended = gtsApi.injectEndpoints({ const extended = gtsApi.injectEndpoints({
endpoints: (build) => ({ endpoints: (build) => ({
@ -35,36 +35,36 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/accounts/update_credentials`, url: `/api/v1/accounts/update_credentials`,
asForm: true, asForm: true,
body: formData, body: formData,
discardEmpty: true discardEmpty: true,
}), }),
...replaceCacheOnMutation("verifyCredentials") ...replaceCacheOnMutation("verifyCredentials"),
}), }),
user: build.query<User, void>({ user: build.query<User, void>({
query: () => ({url: `/api/v1/user`}) query: () => ({url: `/api/v1/user`}),
}), }),
passwordChange: build.mutation({ passwordChange: build.mutation({
query: (data) => ({ query: (data) => ({
method: "POST", method: "POST",
url: `/api/v1/user/password_change`, url: `/api/v1/user/password_change`,
body: data body: data,
}) }),
}), }),
emailChange: build.mutation<User, { password: string, new_email: string }>({ emailChange: build.mutation<User, { password: string, new_email: string }>({
query: (data) => ({ query: (data) => ({
method: "POST", method: "POST",
url: `/api/v1/user/email_change`, url: `/api/v1/user/email_change`,
body: data body: data,
}), }),
...replaceCacheOnMutation("user") ...replaceCacheOnMutation("user"),
}), }),
aliasAccount: build.mutation<any, UpdateAliasesFormData>({ aliasAccount: build.mutation<any, UpdateAliasesFormData>({
async queryFn(formData, _api, _extraOpts, fetchWithBQ) { async queryFn(formData, _api, _extraOpts, fetchWithBQ) {
// Pull entries out from the hooked form. // Pull entries out from the hooked form.
const entries: String[] = []; const entries: string[] = [];
formData.also_known_as_uris.forEach(entry => { formData.also_known_as_uris.forEach(entry => {
if (entry) { if (entry) {
entries.push(entry); entries.push(entry);
@ -76,28 +76,28 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/accounts/alias`, url: `/api/v1/accounts/alias`,
body: { also_known_as_uris: entries }, body: { also_known_as_uris: entries },
}); });
} },
}), }),
moveAccount: build.mutation<any, MoveAccountFormData>({ moveAccount: build.mutation<any, MoveAccountFormData>({
query: (data) => ({ query: (data) => ({
method: "POST", method: "POST",
url: `/api/v1/accounts/move`, url: `/api/v1/accounts/move`,
body: data body: data,
}) }),
}), }),
accountThemes: build.query<Theme[], void>({ accountThemes: build.query<Theme[], void>({
query: () => ({ query: () => ({
url: `/api/v1/accounts/themes` url: `/api/v1/accounts/themes`,
}) }),
}), }),
defaultInteractionPolicies: build.query<DefaultInteractionPolicies, void>({ defaultInteractionPolicies: build.query<DefaultInteractionPolicies, void>({
query: () => ({ query: () => ({
url: `/api/v1/interaction_policies/defaults` url: `/api/v1/interaction_policies/defaults`,
}), }),
providesTags: ["DefaultInteractionPolicies"] providesTags: ["DefaultInteractionPolicies"],
}), }),
updateDefaultInteractionPolicies: build.mutation<DefaultInteractionPolicies, UpdateDefaultInteractionPolicies>({ updateDefaultInteractionPolicies: build.mutation<DefaultInteractionPolicies, UpdateDefaultInteractionPolicies>({
@ -106,7 +106,7 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/interaction_policies/defaults`, url: `/api/v1/interaction_policies/defaults`,
body: data, body: data,
}), }),
...replaceCacheOnMutation("defaultInteractionPolicies") ...replaceCacheOnMutation("defaultInteractionPolicies"),
}), }),
resetDefaultInteractionPolicies: build.mutation<DefaultInteractionPolicies, void>({ resetDefaultInteractionPolicies: build.mutation<DefaultInteractionPolicies, void>({
@ -115,9 +115,9 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/interaction_policies/defaults`, url: `/api/v1/interaction_policies/defaults`,
body: {}, body: {},
}), }),
invalidatesTags: ["DefaultInteractionPolicies"] invalidatesTags: ["DefaultInteractionPolicies"],
}), }),
}) }),
}); });
export const { export const {

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { import type {
InteractionRequest, InteractionRequest,
SearchInteractionRequestsParams, SearchInteractionRequestsParams,
SearchInteractionRequestsResp, SearchInteractionRequestsResp,
@ -33,7 +33,7 @@ const extended = gtsApi.injectEndpoints({
url: `/api/v1/interaction_requests/${id}`, url: `/api/v1/interaction_requests/${id}`,
}), }),
providesTags: (_result, _error, id) => [ providesTags: (_result, _error, id) => [
{ type: 'InteractionRequest', id } { type: 'InteractionRequest', id },
], ],
}), }),
@ -52,7 +52,7 @@ const extended = gtsApi.injectEndpoints({
} }
return { return {
url: `/api/v1/interaction_requests${query}` url: `/api/v1/interaction_requests${query}`,
}; };
}, },
// Headers required for paging. // Headers required for paging.
@ -62,7 +62,7 @@ const extended = gtsApi.injectEndpoints({
const links = parse(linksStr); const links = parse(linksStr);
return { requests, links }; return { requests, links };
}, },
providesTags: [{ type: "InteractionRequest", id: "TRANSFORMED" }] providesTags: [{ type: "InteractionRequest", id: "TRANSFORMED" }],
}), }),
approveInteractionRequest: build.mutation<InteractionRequest, string>({ approveInteractionRequest: build.mutation<InteractionRequest, string>({
@ -73,7 +73,7 @@ const extended = gtsApi.injectEndpoints({
invalidatesTags: (res) => invalidatesTags: (res) =>
res res
? [{ type: "InteractionRequest", id: "TRANSFORMED" }, { type: "InteractionRequest", id: res.id }] ? [{ type: "InteractionRequest", id: "TRANSFORMED" }, { type: "InteractionRequest", id: res.id }]
: [{ type: "InteractionRequest", id: "TRANSFORMED" }] : [{ type: "InteractionRequest", id: "TRANSFORMED" }],
}), }),
rejectInteractionRequest: build.mutation<any, string>({ rejectInteractionRequest: build.mutation<any, string>({
@ -84,9 +84,9 @@ const extended = gtsApi.injectEndpoints({
invalidatesTags: (res) => invalidatesTags: (res) =>
res res
? [{ type: "InteractionRequest", id: "TRANSFORMED" }, { type: "InteractionRequest", id: res.id }] ? [{ type: "InteractionRequest", id: "TRANSFORMED" }, { type: "InteractionRequest", id: res.id }]
: [{ type: "InteractionRequest", id: "TRANSFORMED" }] : [{ type: "InteractionRequest", id: "TRANSFORMED" }],
}), }),
}) }),
}); });
export const { export const {

View file

@ -17,8 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Links } from "parse-link-header"; import type { Links } from "parse-link-header";
import { CustomEmoji } from "./custom-emoji"; import type { CustomEmoji } from "./custom-emoji";
export interface AdminAccount { export interface AdminAccount {
id: string, id: string,

View file

@ -18,7 +18,7 @@
*/ */
import typia from "typia"; import typia from "typia";
import { PermType } from "./perm"; import type { PermType } from "./perm";
export const validateDomainPerms = typia.createValidate<DomainPerm[]>(); export const validateDomainPerms = typia.createValidate<DomainPerm[]>();

View file

@ -18,80 +18,80 @@
*/ */
export interface InstanceV1 { export interface InstanceV1 {
uri: string; uri: string;
account_domain: string; account_domain: string;
title: string; title: string;
description: string; description: string;
description_text?: string; description_text?: string;
short_description: string; short_description: string;
short_description_text?: string; short_description_text?: string;
email: string; email: string;
version: string; version: string;
debug?: boolean; debug?: boolean;
languages: any[]; // TODO: define this languages: any[]; // TODO: define this
registrations: boolean; registrations: boolean;
approval_required: boolean; approval_required: boolean;
invites_enabled: boolean; invites_enabled: boolean;
configuration: InstanceConfiguration; configuration: InstanceConfiguration;
urls: InstanceUrls; urls: InstanceUrls;
stats: InstanceStats; stats: InstanceStats;
thumbnail: string; thumbnail: string;
contact_account: Object; // TODO: define this. contact_account: object; // TODO: define this.
max_toot_chars: number; max_toot_chars: number;
rules: any[]; // TODO: define this rules: any[]; // TODO: define this
terms?: string; terms?: string;
terms_text?: string; terms_text?: string;
} }
export interface InstanceConfiguration { export interface InstanceConfiguration {
statuses: InstanceStatuses; statuses: InstanceStatuses;
media_attachments: InstanceMediaAttachments; media_attachments: InstanceMediaAttachments;
polls: InstancePolls; polls: InstancePolls;
accounts: InstanceAccounts; accounts: InstanceAccounts;
emojis: InstanceEmojis; emojis: InstanceEmojis;
oidc_enabled?: boolean; oidc_enabled?: boolean;
} }
export interface InstanceAccounts { export interface InstanceAccounts {
allow_custom_css: boolean; allow_custom_css: boolean;
max_featured_tags: number; max_featured_tags: number;
max_profile_fields: number; max_profile_fields: number;
} }
export interface InstanceEmojis { export interface InstanceEmojis {
emoji_size_limit: number; emoji_size_limit: number;
} }
export interface InstanceMediaAttachments { export interface InstanceMediaAttachments {
supported_mime_types: string[]; supported_mime_types: string[];
image_size_limit: number; image_size_limit: number;
image_matrix_limit: number; image_matrix_limit: number;
video_size_limit: number; video_size_limit: number;
video_frame_rate_limit: number; video_frame_rate_limit: number;
video_matrix_limit: number; video_matrix_limit: number;
} }
export interface InstancePolls { export interface InstancePolls {
max_options: number; max_options: number;
max_characters_per_option: number; max_characters_per_option: number;
min_expiration: number; min_expiration: number;
max_expiration: number; max_expiration: number;
} }
export interface InstanceStatuses { export interface InstanceStatuses {
max_characters: number; max_characters: number;
max_media_attachments: number; max_media_attachments: number;
characters_reserved_per_url: number; characters_reserved_per_url: number;
supported_mime_types: string[]; supported_mime_types: string[];
} }
export interface InstanceStats { export interface InstanceStats {
domain_count: number; domain_count: number;
status_count: number; status_count: number;
user_count: number; user_count: number;
} }
export interface InstanceUrls { export interface InstanceUrls {
streaming_api: string; streaming_api: string;
} }

View file

@ -17,9 +17,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Links } from "parse-link-header"; import type { Links } from "parse-link-header";
import { Account } from "./account"; import type { Account } from "./account";
import { Status } from "./status"; import type { Status } from "./status";
export interface DefaultInteractionPolicies { export interface DefaultInteractionPolicies {
direct: InteractionPolicy; direct: InteractionPolicy;
@ -71,7 +71,7 @@ export {
* Interaction request targeting a status by an account. * Interaction request targeting a status by an account.
*/ */
export interface InteractionRequest { export interface InteractionRequest {
/** /**
* ID of the request. * ID of the request.
*/ */
id: string; id: string;

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Draft } from "@reduxjs/toolkit"; import type { Draft } from "@reduxjs/toolkit";
/** /**
* Pass into a query when you don't * Pass into a query when you don't
@ -46,25 +46,25 @@ interface MutationStartedParams {
/** /**
* A method to get the current state for the store. * A method to get the current state for the store.
*/ */
getState, getState,
/** /**
* extra as provided as thunk.extraArgument to the configureStore getDefaultMiddleware option. * extra as provided as thunk.extraArgument to the configureStore getDefaultMiddleware option.
*/ */
extra, extra,
/** /**
* A unique ID generated for the query/mutation. * A unique ID generated for the query/mutation.
*/ */
requestId, requestId,
/** /**
* A Promise that will resolve with a data property (the transformed query result), and a * A Promise that will resolve with a data property (the transformed query result), and a
* meta property (meta returned by the baseQuery). If the query fails, this Promise will * meta property (meta returned by the baseQuery). If the query fails, this Promise will
* reject with the error. This allows you to await for the query to finish. * reject with the error. This allows you to await for the query to finish.
*/ */
queryFulfilled, queryFulfilled,
/** /**
* A function that gets the current value of the cache entry. * A function that gets the current value of the cache entry.
*/ */
getCacheEntry, getCacheEntry,
} }
export type Action = ( export type Action = (

View file

@ -17,16 +17,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Links } from "parse-link-header"; import type { Links } from "parse-link-header";
import { AdminAccount } from "./account"; import type { AdminAccount } from "./account";
import { Status } from "./status"; import type { Status } from "./status";
/** /**
* Admin model of a report. Differs from the client * Admin model of a report. Differs from the client
* model, which contains less detailed information. * model, which contains less detailed information.
*/ */
export interface AdminReport { export interface AdminReport {
/** /**
* ID of the report. * ID of the report.
*/ */
id: string; id: string;
@ -83,7 +83,7 @@ export interface AdminReport {
* Rules broken according to the reporter, if any. * Rules broken according to the reporter, if any.
* TODO: model this properly. * TODO: model this properly.
*/ */
rules: Object[]; rules: object[];
/** /**
* Comment stored about what action (if any) was taken. * Comment stored about what action (if any) was taken.
*/ */
@ -94,7 +94,7 @@ export interface AdminReport {
* Parameters for POST to /api/v1/admin/reports/{id}/resolve. * Parameters for POST to /api/v1/admin/reports/{id}/resolve.
*/ */
export interface AdminReportResolveParams { export interface AdminReportResolveParams {
/** /**
* The ID of the report to resolve. * The ID of the report to resolve.
*/ */
id: string; id: string;

View file

@ -17,8 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Account } from "./account"; import type { Account } from "./account";
import { CustomEmoji } from "./custom-emoji"; import type { CustomEmoji } from "./custom-emoji";
export interface Status { export interface Status {
id: string; id: string;

View file

@ -29,7 +29,7 @@ import { get } from "psl";
export function isValidDomainPermission(domain: string): boolean { export function isValidDomainPermission(domain: string): boolean {
return isValidDomain(domain, { return isValidDomain(domain, {
wildcard: false, wildcard: false,
allowUnicode: true allowUnicode: true,
}); });
} }

View file

@ -19,7 +19,7 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { AdminAccount } from "../types/account"; import type { AdminAccount } from "../types/account";
import { store } from "../../redux/store"; import { store } from "../../redux/store";
export function yesOrNo(b: boolean): string { export function yesOrNo(b: boolean): string {

View file

@ -17,7 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { PayloadAction, createSlice } from "@reduxjs/toolkit"; import type { PayloadAction} from "@reduxjs/toolkit";
import { createSlice } from "@reduxjs/toolkit";
import type { Checkable } from "../lib/form/types"; import type { Checkable } from "../lib/form/types";
import { useReducer } from "react"; import { useReducer } from "react";
@ -57,12 +58,12 @@ function initialHookState({
} }
return [ key, { ...entry, key, checked } ]; return [ key, { ...entry, key, checked } ];
}) }),
); );
return { return {
entries: mappedEntries, entries: mappedEntries,
selectedEntries selectedEntries,
}; };
} }
@ -81,7 +82,7 @@ const checklistSlice = createSlice({
} }
return [entry.key, { ...entry, checked } ]; return [entry.key, { ...entry, checked } ];
}) }),
); );
return { entries, selectedEntries }; return { entries, selectedEntries };
@ -97,7 +98,7 @@ const checklistSlice = createSlice({
state.entries[key] = { state.entries[key] = {
...state.entries[key], ...state.entries[key],
...value ...value,
}; };
}, },
updateMultiple: (state, { payload }: PayloadAction<Array<[key: string, value: Partial<Checkable>]>>) => { updateMultiple: (state, { payload }: PayloadAction<Array<[key: string, value: Partial<Checkable>]>>) => {
@ -112,11 +113,11 @@ const checklistSlice = createSlice({
state.entries[key] = { state.entries[key] = {
...state.entries[key], ...state.entries[key],
...value ...value,
}; };
}); });
} },
} },
}); });
export const actions = checklistSlice.actions; export const actions = checklistSlice.actions;
@ -153,6 +154,6 @@ export const useChecklistReducer = (entries: Checkable[], uniqueKey: string, ini
return useReducer( return useReducer(
checklistSlice.reducer, checklistSlice.reducer,
initialState, initialState,
(_) => initialHookState({ entries, uniqueKey, initialValue }) (_) => initialHookState({ entries, uniqueKey, initialValue }),
); );
}; };

View file

@ -17,7 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { PayloadAction, createSlice } from "@reduxjs/toolkit"; import type { PayloadAction} from "@reduxjs/toolkit";
import { createSlice } from "@reduxjs/toolkit";
/** /**
* OAuthToken represents a response * OAuthToken represents a response
@ -78,8 +79,8 @@ export const oauthSlice = createSlice({
delete state.token; delete state.token;
delete state.app; delete state.app;
state.loginState = "logout"; state.loginState = "logout";
} },
} },
}); });
export const { export const {

View file

@ -50,13 +50,13 @@ const persistedReducer = persistReducer({
// This is a cheeky workaround for // This is a cheeky workaround for
// redux-persist being a stickler. // redux-persist being a stickler.
let anyState = state as any; const anyState = state as any;
if (anyState?.oauth != undefined) { if (anyState?.oauth != undefined) {
anyState.oauth.expectingRedirect = false; anyState.oauth.expectingRedirect = false;
} }
return anyState; return anyState;
} },
}, combinedReducers); }, combinedReducers);
export const store = configureStore({ export const store = configureStore({
@ -71,10 +71,10 @@ export const store = configureStore({
PERSIST, PERSIST,
PURGE, PURGE,
REGISTER, REGISTER,
] ],
} },
}).concat(gtsApi.middleware); }).concat(gtsApi.middleware);
} },
}); });
export const persistor = persistStore(store); export const persistor = persistStore(store);

View file

@ -30,7 +30,7 @@ export default function Test({}) {
const form = { const form = {
email: useTextInput("email", { defaultValue: instance?.email }), email: useTextInput("email", { defaultValue: instance?.email }),
message: useTextInput("message") message: useTextInput("message"),
}; };
const [submit, result] = useFormSubmit(form, useSendTestEmailMutation(), { changedOnly: false }); const [submit, result] = useFormSubmit(form, useSendTestEmailMutation(), { changedOnly: false });

View file

@ -47,7 +47,7 @@ export default function ExpireRemote({}) {
} }
return "invalid domain"; return "invalid domain";
} },
}); });
const [expire, expireResult] = useInstanceKeysExpireMutation(); const [expire, expireResult] = useInstanceKeysExpireMutation();

View file

@ -22,7 +22,7 @@ import { useTextInput } from "../../../../lib/form";
import { useLazyApURLQuery } from "../../../../lib/query/admin/debug"; import { useLazyApURLQuery } from "../../../../lib/query/admin/debug";
import { TextInput } from "../../../../components/form/inputs"; import { TextInput } from "../../../../components/form/inputs";
import MutationButton from "../../../../components/form/mutation-button"; import MutationButton from "../../../../components/form/mutation-button";
import { ApURLResponse } from "../../../../lib/types/debug"; import type { ApURLResponse } from "../../../../lib/types/debug";
import Loading from "../../../../components/loading"; import Loading from "../../../../components/loading";
// Used for syntax highlighting of json result. // Used for syntax highlighting of json result.

View file

@ -17,12 +17,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { useMemo, useEffect, PropsWithChildren, ReactElement } from "react"; import type { PropsWithChildren, ReactElement } from "react";
import React, { useMemo, useEffect } from "react";
import { matchSorter } from "match-sorter"; import { matchSorter } from "match-sorter";
import ComboBox from "../../../components/combo-box"; import ComboBox from "../../../components/combo-box";
import { useListEmojiQuery } from "../../../lib/query/admin/custom-emoji"; import { useListEmojiQuery } from "../../../lib/query/admin/custom-emoji";
import { CustomEmoji } from "../../../lib/types/custom-emoji"; import type { CustomEmoji } from "../../../lib/types/custom-emoji";
import { ComboboxFormInputHook } from "../../../lib/form/types"; import type { ComboboxFormInputHook } from "../../../lib/form/types";
import Loading from "../../../components/loading"; import Loading from "../../../components/loading";
import { Error } from "../../../components/error"; import { Error } from "../../../components/error";
@ -97,7 +98,7 @@ export function CategorySelect({ field, children }: PropsWithChildren<CategorySe
aria-hidden="true" aria-hidden="true"
/> />
{categoryName} {categoryName}
</> </>,
]); ]);
}); });

View file

@ -47,7 +47,7 @@ export default function EmojiDetail() {
function EmojiDetailForm({ data: emoji }) { function EmojiDetailForm({ data: emoji }) {
const { data: instance } = useInstanceV1Query(); const { data: instance } = useInstanceV1Query();
const emojiMaxSize = useMemo(() => { const emojiMaxSize = useMemo(() => {
return instance?.configuration?.emojis?.emoji_size_limit ?? 50 * 1024; return instance?.configuration.emojis.emoji_size_limit ?? 50 * 1024;
}, [instance]); }, [instance]);
const baseUrl = useBaseUrl(); const baseUrl = useBaseUrl();
@ -56,8 +56,8 @@ function EmojiDetailForm({ data: emoji }) {
category: useComboBoxInput("category", { source: emoji }), category: useComboBoxInput("category", { source: emoji }),
image: useFileInput("image", { image: useFileInput("image", {
withPreview: true, withPreview: true,
maxSize: emojiMaxSize maxSize: emojiMaxSize,
}) }),
}; };
const [modifyEmoji, result] = useFormSubmit(form, useEditEmojiMutation()); const [modifyEmoji, result] = useFormSubmit(form, useEditEmojiMutation());

View file

@ -17,7 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { useMemo, useEffect, ReactNode } from "react"; import type { ReactNode } from "react";
import React, { useMemo, useEffect } from "react";
import { useFileInput, useComboBoxInput } from "../../../../lib/form"; import { useFileInput, useComboBoxInput } from "../../../../lib/form";
import useShortcode from "./use-shortcode"; import useShortcode from "./use-shortcode";
import useFormSubmit from "../../../../lib/form/submit"; import useFormSubmit from "../../../../lib/form/submit";
@ -32,7 +33,7 @@ import prettierBytes from "prettier-bytes";
export default function NewEmojiForm() { export default function NewEmojiForm() {
const { data: instance } = useInstanceV1Query(); const { data: instance } = useInstanceV1Query();
const emojiMaxSize = useMemo(() => { const emojiMaxSize = useMemo(() => {
return instance?.configuration?.emojis?.emoji_size_limit ?? 50 * 1024; return instance?.configuration.emojis.emoji_size_limit ?? 50 * 1024;
}, [instance]); }, [instance]);
const prettierMaxSize = useMemo(() => { const prettierMaxSize = useMemo(() => {
@ -43,7 +44,7 @@ export default function NewEmojiForm() {
shortcode: useShortcode(), shortcode: useShortcode(),
image: useFileInput("image", { image: useFileInput("image", {
withPreview: true, withPreview: true,
maxSize: emojiMaxSize maxSize: emojiMaxSize,
}), }),
category: useComboBoxInput("category"), category: useComboBoxInput("category"),
}; };
@ -59,7 +60,7 @@ export default function NewEmojiForm() {
form.shortcode.reset(); form.shortcode.reset();
form.image.reset(); form.image.reset();
form.category.reset(); form.category.reset();
} },
}, },
); );
@ -70,7 +71,7 @@ export default function NewEmojiForm() {
(form.shortcode.value === undefined || form.shortcode.value.length === 0) && (form.shortcode.value === undefined || form.shortcode.value.length === 0) &&
form.image.value !== undefined form.image.value !== undefined
) { ) {
let [name, _ext] = form.image.value.name.split("."); const [name, _ext] = form.image.value.name.split(".");
form.shortcode.setter(name); form.shortcode.setter(name);
} }

View file

@ -27,7 +27,7 @@ import Loading from "../../../../components/loading";
import { Error } from "../../../../components/error"; import { Error } from "../../../../components/error";
import { TextInput } from "../../../../components/form/inputs"; import { TextInput } from "../../../../components/form/inputs";
import { useListEmojiQuery } from "../../../../lib/query/admin/custom-emoji"; import { useListEmojiQuery } from "../../../../lib/query/admin/custom-emoji";
import { CustomEmoji } from "../../../../lib/types/custom-emoji"; import type { CustomEmoji } from "../../../../lib/types/custom-emoji";
export default function EmojiOverview() { export default function EmojiOverview() {
const { data: emoji = [], isLoading, isError, error } = useListEmojiQuery({ filter: "domain:local" }); const { data: emoji = [], isLoading, isError, error } = useListEmojiQuery({ filter: "domain:local" });
@ -87,7 +87,7 @@ function EmojiList({ emoji }: EmojiListParams) {
// Filter from emojis in this category. // Filter from emojis in this category.
emojiByCategory.forEach((entries, category) => { emojiByCategory.forEach((entries, category) => {
const filteredEntries = matchSorter(entries, filter, { const filteredEntries = matchSorter(entries, filter, {
keys: ["shortcode"] keys: ["shortcode"],
}); });
if (filteredEntries.length == 0) { if (filteredEntries.length == 0) {
@ -164,8 +164,12 @@ function EmojiPreview({ emoji }) {
return ( return (
<img <img
onMouseEnter={() => { setAnimate(true); }} onMouseEnter={() => {
onMouseLeave={() => { setAnimate(false); }} setAnimate(true);
}}
onMouseLeave={() => {
setAnimate(false);
}}
src={animate ? emoji.url : emoji.static_url} src={animate ? emoji.url : emoji.static_url}
alt={emoji.shortcode} alt={emoji.shortcode}
title={emoji.shortcode} title={emoji.shortcode}

View file

@ -26,7 +26,7 @@ const shortcodeRegex = /^\w{2,30}$/;
export default function useShortcode() { export default function useShortcode() {
const { data: emoji = [] } = useListEmojiQuery({ const { data: emoji = [] } = useListEmojiQuery({
filter: "domain:local" filter: "domain:local",
}); });
const emojiCodes = useMemo(() => { const emojiCodes = useMemo(() => {
@ -36,7 +36,9 @@ export default function useShortcode() {
return useTextInput("shortcode", { return useTextInput("shortcode", {
validator: function validateShortcode(code) { validator: function validateShortcode(code) {
// technically invalid, but hacky fix to prevent validation error on page load // technically invalid, but hacky fix to prevent validation error on page load
if (code == "") { return ""; } if (code == "") {
return "";
}
if (emojiCodes.has(code)) { if (emojiCodes.has(code)) {
return "Shortcode already in use"; return "Shortcode already in use";
@ -51,6 +53,6 @@ export default function useShortcode() {
} }
return ""; return "";
} },
}); });
} }

View file

@ -31,7 +31,7 @@ export default function RemoteEmoji() {
const { const {
data: emoji = [], data: emoji = [],
isLoading, isLoading,
error error,
} = useListEmojiQuery({ filter: "domain:local" }); } = useListEmojiQuery({ filter: "domain:local" });
const emojiCodes = useMemo(() => new Set(emoji.map((e) => e.shortcode)), [emoji]); const emojiCodes = useMemo(() => new Set(emoji.map((e) => e.shortcode)), [emoji]);

View file

@ -64,7 +64,7 @@ export default function StealThisLook({ emojiCodes }) {
"fa fa-fw", "fa fa-fw",
(result.isLoading (result.isLoading
? "fa-refresh fa-spin" ? "fa-refresh fa-spin"
: "fa-search") : "fa-search"),
].join(" ")} aria-hidden="true" title="Search" /> ].join(" ")} aria-hidden="true" title="Search" />
<span className="sr-only">Search</span> <span className="sr-only">Search</span>
</button> </button>
@ -108,9 +108,9 @@ function CopyEmojiForm({ localEmojiCodes, type, emojiList }) {
const form = { const form = {
selectedEmoji: useCheckListInput("selectedEmoji", { selectedEmoji: useCheckListInput("selectedEmoji", {
entries: emojiList, entries: emojiList,
uniqueKey: "id" uniqueKey: "id",
}), }),
category: useComboBoxInput("category") category: useComboBoxInput("category"),
}; };
const [formSubmit, result] = useFormSubmit( const [formSubmit, result] = useFormSubmit(
@ -126,18 +126,18 @@ function CopyEmojiForm({ localEmojiCodes, type, emojiList }) {
}); });
form.selectedEmoji.updateMultiple(processed); form.selectedEmoji.updateMultiple(processed);
} }
} },
} },
); );
const buttonsInactive = form.selectedEmoji.someSelected const buttonsInactive = form.selectedEmoji.someSelected
? { ? {
disabled: false, disabled: false,
title: "" title: "",
} }
: { : {
disabled: true, disabled: true,
title: "No emoji selected, cannot perform any actions" title: "No emoji selected, cannot perform any actions",
}; };
const checkListExtraProps = useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]); const checkListExtraProps = useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]);
@ -205,7 +205,7 @@ function EmojiEntry({ entry: emoji, onChange, extraProps: { localEmojiCodes } })
return (emoji.checked && localEmojiCodes.has(code)) return (emoji.checked && localEmojiCodes.has(code))
? "Shortcode already in use" ? "Shortcode already in use"
: ""; : "";
} },
}); });
useEffect(() => { useEffect(() => {

View file

@ -23,7 +23,7 @@ import { useTextInput } from "../../../lib/form";
import useFormSubmit from "../../../lib/form/submit"; import useFormSubmit from "../../../lib/form/submit";
import { TextInput } from "../../../components/form/inputs"; import { TextInput } from "../../../components/form/inputs";
import MutationButton from "../../../components/form/mutation-button"; import MutationButton from "../../../components/form/mutation-button";
import { PermType } from "../../../lib/types/perm"; import type { PermType } from "../../../lib/types/perm";
import { RE2JS } from "re2js"; import { RE2JS } from "re2js";
export default function HeaderPermCreateForm({ permType }: { permType: PermType }) { export default function HeaderPermCreateForm({ permType }: { permType: PermType }) {
@ -44,7 +44,7 @@ export default function HeaderPermCreateForm({ permType }: { permType: PermType
} }
return ""; return "";
} },
}), }),
regex: useTextInput("regex", { regex: useTextInput("regex", {
validator: (val: string) => { validator: (val: string) => {
@ -63,7 +63,7 @@ export default function HeaderPermCreateForm({ permType }: { permType: PermType
} }
return ""; return "";
} },
}), }),
}; };
@ -101,7 +101,7 @@ export default function HeaderPermCreateForm({ permType }: { permType: PermType
label={ label={
<> <>
Header Name&nbsp; Header Name&nbsp;
<a <a
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers"
target="_blank" target="_blank"
className="docslink" className="docslink"

View file

@ -19,11 +19,11 @@
import React, { useEffect, useMemo } from "react"; import React, { useEffect, useMemo } from "react";
import { useLocation, useParams } from "wouter"; import { useLocation, useParams } from "wouter";
import { PermType } from "../../../lib/types/perm"; import type { PermType } from "../../../lib/types/perm";
import { useDeleteHeaderAllowMutation, useDeleteHeaderBlockMutation, useGetHeaderAllowQuery, useGetHeaderBlockQuery } from "../../../lib/query/admin/http-header-permissions"; import { useDeleteHeaderAllowMutation, useDeleteHeaderBlockMutation, useGetHeaderAllowQuery, useGetHeaderBlockQuery } from "../../../lib/query/admin/http-header-permissions";
import { HeaderPermission } from "../../../lib/types/http-header-permissions"; import type { HeaderPermission } from "../../../lib/types/http-header-permissions";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; import type { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { SerializedError } from "@reduxjs/toolkit"; import type { SerializedError } from "@reduxjs/toolkit";
import Loading from "../../../components/loading"; import Loading from "../../../components/loading";
import { Error } from "../../../components/error"; import { Error } from "../../../components/error";
import { useLazyGetAccountQuery } from "../../../lib/query/admin"; import { useLazyGetAccountQuery } from "../../../lib/query/admin";
@ -42,7 +42,7 @@ Some Test Value
Another Test Value`; Another Test Value`;
export default function HeaderPermDetail() { export default function HeaderPermDetail() {
let params = useParams(); const params = useParams();
if (params.permType !== "blocks" && params.permType !== "allows") { if (params.permType !== "blocks" && params.permType !== "allows") {
throw "unrecognized perm type " + params.permType; throw "unrecognized perm type " + params.permType;
} }
@ -50,7 +50,7 @@ export default function HeaderPermDetail() {
return params.permType?.slice(0, -1) as PermType; return params.permType?.slice(0, -1) as PermType;
}, [params]); }, [params]);
let permID = params.permId as string | undefined; const permID = params.permId;
if (!permID) { if (!permID) {
throw "no perm ID"; throw "no perm ID";
} }
@ -127,7 +127,7 @@ function PermDeets({
return <Loading />; return <Loading />;
} else if (isErrorAccount || account === undefined) { } else if (isErrorAccount || account === undefined) {
// Fall back to account ID. // Fall back to account ID.
return perm?.created_by; return perm.created_by;
} }
return ( return (

View file

@ -21,18 +21,18 @@ import React, { useMemo } from "react";
import { useGetHeaderAllowsQuery, useGetHeaderBlocksQuery } from "../../../lib/query/admin/http-header-permissions"; import { useGetHeaderAllowsQuery, useGetHeaderBlocksQuery } from "../../../lib/query/admin/http-header-permissions";
import { NoArg } from "../../../lib/types/query"; import { NoArg } from "../../../lib/types/query";
import { PageableList } from "../../../components/pageable-list"; import { PageableList } from "../../../components/pageable-list";
import { HeaderPermission } from "../../../lib/types/http-header-permissions"; import type { HeaderPermission } from "../../../lib/types/http-header-permissions";
import { useLocation, useParams } from "wouter"; import { useLocation, useParams } from "wouter";
import { PermType } from "../../../lib/types/perm"; import type { PermType } from "../../../lib/types/perm";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; import type { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { SerializedError } from "@reduxjs/toolkit"; import type { SerializedError } from "@reduxjs/toolkit";
import HeaderPermCreateForm from "./create"; import HeaderPermCreateForm from "./create";
export default function HeaderPermsOverview() { export default function HeaderPermsOverview() {
const [ location, setLocation ] = useLocation(); const [ location, setLocation ] = useLocation();
// Parse perm type from routing params. // Parse perm type from routing params.
let params = useParams(); const params = useParams();
if (params.permType !== "blocks" && params.permType !== "allows") { if (params.permType !== "blocks" && params.permType !== "allows") {
throw "unrecognized perm type " + params.permType; throw "unrecognized perm type " + params.permType;
} }
@ -53,7 +53,7 @@ export default function HeaderPermsOverview() {
isFetching: isFetchingBlocks, isFetching: isFetchingBlocks,
isSuccess: isSuccessBlocks, isSuccess: isSuccessBlocks,
isError: isErrorBlocks, isError: isErrorBlocks,
error: errorBlocks error: errorBlocks,
} = useGetHeaderBlocksQuery(NoArg, { skip: permType !== "block" }); } = useGetHeaderBlocksQuery(NoArg, { skip: permType !== "block" });
const { const {
@ -62,7 +62,7 @@ export default function HeaderPermsOverview() {
isFetching: isFetchingAllows, isFetching: isFetchingAllows,
isSuccess: isSuccessAllows, isSuccess: isSuccessAllows,
isError: isErrorAllows, isError: isErrorAllows,
error: errorAllows error: errorAllows,
} = useGetHeaderAllowsQuery(NoArg, { skip: permType !== "allow" }); } = useGetHeaderAllowsQuery(NoArg, { skip: permType !== "allow" });
const itemToEntry = (perm: HeaderPermission) => { const itemToEntry = (perm: HeaderPermission) => {
@ -77,7 +77,7 @@ export default function HeaderPermsOverview() {
// Store the back location in // Store the back location in
// history so the detail view // history so the detail view
// can use it to return here. // can use it to return here.
state: { backLocation: location } state: { backLocation: location },
}); });
}} }}
role="link" role="link"

View file

@ -56,7 +56,7 @@ function EditInstanceRuleForm({ rule }) {
const baseUrl = useBaseUrl(); const baseUrl = useBaseUrl();
const form = { const form = {
id: useValue("id", rule.id), id: useValue("id", rule.id),
rule: useTextInput("text", { defaultValue: rule.text }) rule: useTextInput("text", { defaultValue: rule.text }),
}; };
const [submitForm, result] = useFormSubmit(form, useUpdateInstanceRuleMutation()); const [submitForm, result] = useFormSubmit(form, useUpdateInstanceRuleMutation());

View file

@ -25,7 +25,7 @@ import { useTextInput } from "../../../lib/form";
import useFormSubmit from "../../../lib/form/submit"; import useFormSubmit from "../../../lib/form/submit";
import { TextArea } from "../../../components/form/inputs"; import { TextArea } from "../../../components/form/inputs";
import MutationButton from "../../../components/form/mutation-button"; import MutationButton from "../../../components/form/mutation-button";
import { InstanceRule, MappedRules } from "../../../lib/types/rules"; import type { InstanceRule, MappedRules } from "../../../lib/types/rules";
import FormWithData from "../../../lib/form/form-with-data"; import FormWithData from "../../../lib/form/form-with-data";
export default function InstanceRules() { export default function InstanceRules() {
@ -46,7 +46,9 @@ function InstanceRulesForm({ data: rules }: { data: MappedRules }) {
const [submitForm, result] = useFormSubmit({ newRule }, useAddInstanceRuleMutation(), { const [submitForm, result] = useFormSubmit({ newRule }, useAddInstanceRuleMutation(), {
changedOnly: true, changedOnly: true,
onFinish: () => newRule.reset() onFinish: () => {
newRule.reset();
},
}); });
return ( return (

View file

@ -24,7 +24,7 @@ import { TextInput, TextArea, FileInput } from "../../../components/form/inputs"
import MutationButton from "../../../components/form/mutation-button"; import MutationButton from "../../../components/form/mutation-button";
import { useInstanceV1Query } from "../../../lib/query/gts-api"; import { useInstanceV1Query } from "../../../lib/query/gts-api";
import { useUpdateInstanceMutation } from "../../../lib/query/admin"; import { useUpdateInstanceMutation } from "../../../lib/query/admin";
import { InstanceV1 } from "../../../lib/types/instance"; import type { InstanceV1 } from "../../../lib/types/instance";
import FormWithData from "../../../lib/form/form-with-data"; import FormWithData from "../../../lib/form/form-with-data";
import useFormSubmit from "../../../lib/form/submit"; import useFormSubmit from "../../../lib/form/submit";
@ -50,7 +50,7 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
const form = { const form = {
title: useTextInput("title", { title: useTextInput("title", {
source: instance, source: instance,
validator: (val: string) => val.length <= titleLimit ? "" : `Instance title is ${val.length} characters; must be ${titleLimit} characters or less` validator: (val: string) => val.length <= titleLimit ? "" : `Instance title is ${val.length} characters; must be ${titleLimit} characters or less`,
}), }),
thumbnail: useFileInput("thumbnail", { withPreview: true }), thumbnail: useFileInput("thumbnail", { withPreview: true }),
thumbnailDesc: useTextInput("thumbnail_description", { source: instance }), thumbnailDesc: useTextInput("thumbnail_description", { source: instance }),
@ -58,22 +58,22 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
source: instance, source: instance,
// Select "raw" text version of parsed field for editing. // Select "raw" text version of parsed field for editing.
valueSelector: (s: InstanceV1) => s.short_description_text, 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` validator: (val: string) => val.length <= shortDescLimit ? "" : `Instance short description is ${val.length} characters; must be ${shortDescLimit} characters or less`,
}), }),
description: useTextInput("description", { description: useTextInput("description", {
source: instance, source: instance,
// Select "raw" text version of parsed field for editing. // Select "raw" text version of parsed field for editing.
valueSelector: (s: InstanceV1) => s.description_text, valueSelector: (s: InstanceV1) => s.description_text,
validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less` validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less`,
}), }),
terms: useTextInput("terms", { terms: useTextInput("terms", {
source: instance, source: instance,
// Select "raw" text version of parsed field for editing. // Select "raw" text version of parsed field for editing.
valueSelector: (s: InstanceV1) => s.terms_text, 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` 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 }), contactUser: useTextInput("contact_username", { source: instance, valueSelector: (s) => s.contact_account?.username }),
contactEmail: useTextInput("contact_email", { source: instance, valueSelector: (s) => s.email }) contactEmail: useTextInput("contact_email", { source: instance, valueSelector: (s) => s.email }),
}; };
const [submitForm, result] = useFormSubmit(form, useUpdateInstanceMutation()); const [submitForm, result] = useFormSubmit(form, useUpdateInstanceMutation());
@ -109,8 +109,8 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
<div className="file-upload-with-preview"> <div className="file-upload-with-preview">
<img <img
className="preview avatar" className="preview avatar"
src={form.thumbnail.previewValue ?? instance?.thumbnail} src={form.thumbnail.previewValue ?? instance.thumbnail}
alt={form.thumbnailDesc.value ?? (instance?.thumbnail ? `Thumbnail image for the instance` : "No instance thumbnail image set")} alt={form.thumbnailDesc.value ?? (instance.thumbnail ? `Thumbnail image for the instance` : "No instance thumbnail image set")}
/> />
<div className="file-input-with-image-description"> <div className="file-input-with-image-description">
<FileInput <FileInput

View file

@ -28,7 +28,7 @@ import {
useBoolInput, useBoolInput,
} from "../../../../lib/form"; } from "../../../../lib/form";
import { Checkbox, Select, TextInput } from "../../../../components/form/inputs"; import { Checkbox, Select, TextInput } from "../../../../components/form/inputs";
import { AdminAccount } from "../../../../lib/types/account"; import type { AdminAccount } from "../../../../lib/types/account";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
export interface AccountActionsProps { export interface AccountActionsProps {
@ -65,7 +65,7 @@ export function AccountActions({ account, backLocation }: AccountActionsProps) {
function ModerateAccount({ account }: { account: AdminAccount }) { function ModerateAccount({ account }: { account: AdminAccount }) {
const form = { const form = {
id: useValue("id", account.id), id: useValue("id", account.id),
reason: useTextInput("text") reason: useTextInput("text"),
}; };
const reallySuspend = useBoolInput("reallySuspend"); const reallySuspend = useBoolInput("reallySuspend");
@ -96,7 +96,7 @@ function ModerateAccount({ account }: { account: AdminAccount }) {
/> />
<div className="action-buttons"> <div className="action-buttons">
<MutationButton <MutationButton
disabled={account.suspended || reallySuspend.value === undefined || reallySuspend.value === false} disabled={account.suspended || reallySuspend.value === undefined || !reallySuspend.value}
label="Suspend" label="Suspend"
name="suspend" name="suspend"
result={result} result={result}
@ -140,7 +140,7 @@ function HandleSignup({ account, backLocation }: { account: AdminAccount, backLo
// redirect to accounts page. // redirect to accounts page.
setLocation(backLocation); setLocation(backLocation);
} }
} },
}); });
return ( return (

View file

@ -22,7 +22,7 @@ import React from "react";
import { useGetAccountQuery } from "../../../../lib/query/admin"; import { useGetAccountQuery } from "../../../../lib/query/admin";
import FormWithData from "../../../../lib/form/form-with-data"; import FormWithData from "../../../../lib/form/form-with-data";
import FakeProfile from "../../../../components/profile"; import FakeProfile from "../../../../components/profile";
import { AdminAccount } from "../../../../lib/types/account"; import type { AdminAccount } from "../../../../lib/types/account";
import { AccountActions } from "./actions"; import { AccountActions } from "./actions";
import { useParams } from "wouter"; import { useParams } from "wouter";
import { useBaseUrl } from "../../../../lib/navigation/util"; import { useBaseUrl } from "../../../../lib/navigation/util";
@ -32,7 +32,7 @@ import { UseOurInstanceAccount, yesOrNo } from "../../../../lib/util";
export default function AccountDetail() { export default function AccountDetail() {
const params: { accountID: string } = useParams(); const params: { accountID: string } = useParams();
const baseUrl = useBaseUrl(); const baseUrl = useBaseUrl();
const backLocation: String = history.state?.backLocation ?? `~${baseUrl}`; const backLocation: string = history.state?.backLocation ?? `~${baseUrl}`;
return ( return (
<div className="account-detail"> <div className="account-detail">

View file

@ -17,12 +17,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { ReactNode } from "react"; import type { ReactNode } from "react";
import React from "react";
import { useSearchAccountsQuery } from "../../../../lib/query/admin"; import { useSearchAccountsQuery } from "../../../../lib/query/admin";
import { PageableList } from "../../../../components/pageable-list"; import { PageableList } from "../../../../components/pageable-list";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import Username from "../../../../components/username"; import Username from "../../../../components/username";
import { AdminAccount } from "../../../../lib/types/account"; import type { AdminAccount } from "../../../../lib/types/account";
export default function AccountsPending() { export default function AccountsPending() {
const [ location, _setLocation ] = useLocation(); const [ location, _setLocation ] = useLocation();

View file

@ -17,7 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { ReactNode, useEffect, useMemo } from "react"; import type { ReactNode} from "react";
import React, { useEffect, useMemo } from "react";
import { useLazySearchAccountsQuery } from "../../../../lib/query/admin"; import { useLazySearchAccountsQuery } from "../../../../lib/query/admin";
import { useTextInput } from "../../../../lib/form"; import { useTextInput } from "../../../../lib/form";
@ -25,7 +26,7 @@ import { PageableList } from "../../../../components/pageable-list";
import { Select, TextInput } from "../../../../components/form/inputs"; import { Select, TextInput } from "../../../../components/form/inputs";
import MutationButton from "../../../../components/form/mutation-button"; import MutationButton from "../../../../components/form/mutation-button";
import { useLocation, useSearch } from "wouter"; import { useLocation, useSearch } from "wouter";
import { AdminAccount } from "../../../../lib/types/account"; import type { AdminAccount } from "../../../../lib/types/account";
import Username from "../../../../components/username"; import Username from "../../../../components/username";
import isValidDomain from "is-valid-domain"; import isValidDomain from "is-valid-domain";
@ -66,11 +67,11 @@ export function AccountSearchForm() {
} }
return "invalid domain"; return "invalid domain";
} },
}), }),
email: useTextInput("email", { defaultValue: urlQueryParams.get("email") ?? ""}), email: useTextInput("email", { defaultValue: urlQueryParams.get("email") ?? ""}),
ip: useTextInput("ip", { defaultValue: urlQueryParams.get("ip") ?? ""}), ip: useTextInput("ip", { defaultValue: urlQueryParams.get("ip") ?? ""}),
limit: useTextInput("limit", { defaultValue: urlQueryParams.get("limit") ?? "50"}) limit: useTextInput("limit", { defaultValue: urlQueryParams.get("limit") ?? "50"}),
}; };
// On mount, if urlQueryParams were provided, // On mount, if urlQueryParams were provided,

View file

@ -17,9 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from "react"; import React, { useMemo } from "react";
import { useMemo } from "react";
import { useLocation, useParams, useSearch } from "wouter"; import { useLocation, useParams, useSearch } from "wouter";
import { useTextInput, useBoolInput } from "../../../lib/form"; import { useTextInput, useBoolInput } from "../../../lib/form";
@ -34,18 +33,18 @@ import MutationButton from "../../../components/form/mutation-button";
import { useDomainAllowsQuery, useDomainBlocksQuery } from "../../../lib/query/admin/domain-permissions/get"; import { useDomainAllowsQuery, useDomainBlocksQuery } from "../../../lib/query/admin/domain-permissions/get";
import { useAddDomainAllowMutation, useAddDomainBlockMutation, useRemoveDomainAllowMutation, useRemoveDomainBlockMutation } from "../../../lib/query/admin/domain-permissions/update"; import { useAddDomainAllowMutation, useAddDomainBlockMutation, useRemoveDomainAllowMutation, useRemoveDomainBlockMutation } from "../../../lib/query/admin/domain-permissions/update";
import { DomainPerm } from "../../../lib/types/domain-permission"; import type { DomainPerm } from "../../../lib/types/domain-permission";
import { NoArg } from "../../../lib/types/query"; import { NoArg } from "../../../lib/types/query";
import { Error } from "../../../components/error"; import { Error } from "../../../components/error";
import { useBaseUrl } from "../../../lib/navigation/util"; import { useBaseUrl } from "../../../lib/navigation/util";
import { PermType } from "../../../lib/types/perm"; import type { PermType } from "../../../lib/types/perm";
import isValidDomain from "is-valid-domain"; import isValidDomain from "is-valid-domain";
export default function DomainPermDetail() { export default function DomainPermDetail() {
const baseUrl = useBaseUrl(); const baseUrl = useBaseUrl();
// Parse perm type from routing params. // Parse perm type from routing params.
let params = useParams(); const params = useParams();
if (params.permType !== "blocks" && params.permType !== "allows") { if (params.permType !== "blocks" && params.permType !== "allows") {
throw "unrecognized perm type " + params.permType; throw "unrecognized perm type " + params.permType;
} }
@ -132,7 +131,7 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
const disabledForm = isExistingPerm const disabledForm = isExistingPerm
? { ? {
disabled: true, disabled: true,
title: "Domain permissions currently cannot be edited." title: "Domain permissions currently cannot be edited.",
} }
: { : {
disabled: false, disabled: false,
@ -164,11 +163,11 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
} }
return "invalid domain"; return "invalid domain";
} },
}), }),
obfuscate: useBoolInput("obfuscate", { source: perm }), obfuscate: useBoolInput("obfuscate", { source: perm }),
commentPrivate: useTextInput("private_comment", { source: perm }), commentPrivate: useTextInput("private_comment", { source: perm }),
commentPublic: useTextInput("public_comment", { source: perm }) commentPublic: useTextInput("public_comment", { source: perm }),
}; };
// Check which perm type we're meant to be handling // Check which perm type we're meant to be handling
@ -221,11 +220,11 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
// but if domain input changes, that doesn't match anymore // but if domain input changes, that doesn't match anymore
// and causes issues later on so, before submitting the form, // and causes issues later on so, before submitting the form,
// silently change url, and THEN submit. // silently change url, and THEN submit.
let correctUrl = `/${permType}s/${form.domain.value}`; const correctUrl = `/${permType}s/${form.domain.value}`;
if (location != correctUrl) { if (location != correctUrl) {
setLocation(correctUrl); setLocation(correctUrl);
} }
return submitForm(e); submitForm(e);
} }
return ( return (

View file

@ -17,9 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from "react"; import React, { useEffect } from "react";
import { useEffect } from "react";
import { useExportDomainListMutation } from "../../../lib/query/admin/domain-permissions/export"; import { useExportDomainListMutation } from "../../../lib/query/admin/domain-permissions/export";
import useFormSubmit from "../../../lib/form/submit"; import useFormSubmit from "../../../lib/form/submit";
import { import {
@ -96,7 +95,9 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp
<MutationButton <MutationButton
label="Import" label="Import"
type="button" type="button"
onClick={() => submitParse()} onClick={() => {
submitParse();
}}
result={parseResult} result={parseResult}
showError={false} showError={false}
disabled={form.permType.value === undefined || form.permType.value.length === 0} disabled={form.permType.value === undefined || form.permType.value.length === 0}
@ -116,7 +117,9 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp
<MutationButton <MutationButton
label="Export" label="Export"
type="button" type="button"
onClick={() => submitExport("export")} onClick={() => {
submitExport("export");
}}
result={exportResult} showError={false} result={exportResult} showError={false}
disabled={form.permType.value === undefined || form.permType.value.length === 0} disabled={form.permType.value === undefined || form.permType.value.length === 0}
/> />
@ -124,7 +127,9 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp
label="Export to file" label="Export to file"
wrapperClassName="export-file-button" wrapperClassName="export-file-button"
type="button" type="button"
onClick={() => submitExport("export-file")} onClick={() => {
submitExport("export-file");
}}
result={exportResult} result={exportResult}
showError={false} showError={false}
disabled={form.permType.value === undefined || form.permType.value.length === 0} disabled={form.permType.value === undefined || form.permType.value.length === 0}

View file

@ -37,8 +37,8 @@ export default function ImportExport() {
options: { options: {
block: "Domain blocks", block: "Domain blocks",
allow: "Domain allows", allow: "Domain allows",
} },
}) }),
}; };
const [submitParse, parseResult] = useFormSubmit(form, useProcessDomainPermissionsMutation(), { changedOnly: false }); const [submitParse, parseResult] = useFormSubmit(form, useProcessDomainPermissionsMutation(), { changedOnly: false });

View file

@ -17,9 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from "react"; import React, { useMemo } from "react";
import { useMemo } from "react";
import { Link, useLocation, useParams } from "wouter"; import { Link, useLocation, useParams } from "wouter";
import { matchSorter } from "match-sorter"; import { matchSorter } from "match-sorter";
import { useTextInput } from "../../../lib/form"; import { useTextInput } from "../../../lib/form";
@ -28,14 +27,14 @@ import Loading from "../../../components/loading";
import { useDomainAllowsQuery, useDomainBlocksQuery } from "../../../lib/query/admin/domain-permissions/get"; import { useDomainAllowsQuery, useDomainBlocksQuery } from "../../../lib/query/admin/domain-permissions/get";
import type { MappedDomainPerms } from "../../../lib/types/domain-permission"; import type { MappedDomainPerms } from "../../../lib/types/domain-permission";
import { NoArg } from "../../../lib/types/query"; import { NoArg } from "../../../lib/types/query";
import { PermType } from "../../../lib/types/perm"; import type { PermType } from "../../../lib/types/perm";
import { useBaseUrl } from "../../../lib/navigation/util"; import { useBaseUrl } from "../../../lib/navigation/util";
export default function DomainPermissionsOverview() { export default function DomainPermissionsOverview() {
const baseUrl = useBaseUrl(); const baseUrl = useBaseUrl();
// Parse perm type from routing params. // Parse perm type from routing params.
let params = useParams(); const params = useParams();
if (params.permType !== "blocks" && params.permType !== "allows") { if (params.permType !== "blocks" && params.permType !== "allows") {
throw "unrecognized perm type " + params.permType; throw "unrecognized perm type " + params.permType;
} }

View file

@ -17,8 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from "react"; import React, { memo, useMemo, useCallback, useEffect } from "react";
import { memo, useMemo, useCallback, useEffect } from "react";
import { isValidDomainPermission, hasBetterScope } from "../../../lib/util/domain-permission"; import { isValidDomainPermission, hasBetterScope } from "../../../lib/util/domain-permission";
import { import {
@ -45,7 +44,7 @@ import FormWithData from "../../../lib/form/form-with-data";
import { useImportDomainPermsMutation } from "../../../lib/query/admin/domain-permissions/import"; import { useImportDomainPermsMutation } from "../../../lib/query/admin/domain-permissions/import";
import { import {
useDomainAllowsQuery, useDomainAllowsQuery,
useDomainBlocksQuery useDomainBlocksQuery,
} from "../../../lib/query/admin/domain-permissions/get"; } from "../../../lib/query/admin/domain-permissions/get";
import type { DomainPerm, MappedDomainPerms } from "../../../lib/types/domain-permission"; import type { DomainPerm, MappedDomainPerms } from "../../../lib/types/domain-permission";
@ -68,7 +67,7 @@ export const ProcessImport = memo(
{...{ list, permType }} {...{ list, permType }}
/> />
); );
} },
); );
export interface ImportListProps { export interface ImportListProps {
@ -111,22 +110,22 @@ function ImportList({ list, data: domainPerms, permType }: ImportListProps) {
domains: useCheckListInput("domains", { entries: list }), // DomainPerm is actually also a Checkable. domains: useCheckListInput("domains", { entries: list }), // DomainPerm is actually also a Checkable.
obfuscate: useBoolInput("obfuscate"), obfuscate: useBoolInput("obfuscate"),
privateComment: useTextInput("private_comment", { privateComment: useTextInput("private_comment", {
defaultValue: `Imported on ${new Date().toLocaleString()}` defaultValue: `Imported on ${new Date().toLocaleString()}`,
}), }),
privateCommentBehavior: useRadioInput("private_comment_behavior", { privateCommentBehavior: useRadioInput("private_comment_behavior", {
defaultValue: "append", defaultValue: "append",
options: { options: {
append: "Append to", append: "Append to",
replace: "Replace" replace: "Replace",
} },
}), }),
publicComment: useTextInput("public_comment"), publicComment: useTextInput("public_comment"),
publicCommentBehavior: useRadioInput("public_comment_behavior", { publicCommentBehavior: useRadioInput("public_comment_behavior", {
defaultValue: "append", defaultValue: "append",
options: { options: {
append: "Append to", append: "Append to",
replace: "Replace" replace: "Replace",
} },
}), }),
permType: permType, permType: permType,
}; };
@ -218,7 +217,7 @@ function DomainCheckList({ field, domainPerms, commentType, permType }: DomainCh
return ( return (
<> <>
<CheckList <CheckList
field={field as ChecklistInputHook} field={field}
header={<> header={<>
<b>Domain</b> <b>Domain</b>
<b> <b>
@ -252,7 +251,7 @@ const UpdateHint = memo(
function changeAll() { function changeAll() {
updateMultiple( updateMultiple(
entries.map((entry) => [entry.key, { domain: entry.suggest, suggest: null }]) entries.map((entry) => [entry.key, { domain: entry.suggest, suggest: null }]),
); );
} }
@ -270,7 +269,7 @@ const UpdateHint = memo(
{entries.length > 0 && <a onClick={changeAll}>change all</a>} {entries.length > 0 && <a onClick={changeAll}>change all</a>}
</div> </div>
); );
} },
); );
interface UpdateableEntryProps { interface UpdateableEntryProps {
@ -290,7 +289,7 @@ const UpdateableEntry = memo(
}>change</a> }>change</a>
</> </>
); );
} },
); );
function domainValidationError(isValid) { function domainValidationError(isValid) {
@ -312,7 +311,7 @@ function DomainEntry({ entry, onChange, extraProps: { alreadyExists, comment, pe
defaultValue: entry.domain, defaultValue: entry.domain,
showValidation: entry.checked, showValidation: entry.checked,
initValidation: domainValidationError(entry.valid), initValidation: domainValidationError(entry.valid),
validator: (value) => domainValidationError(isValidDomainPermission(value)) validator: (value) => domainValidationError(isValidDomainPermission(value)),
}); });
useEffect(() => { useEffect(() => {

View file

@ -28,14 +28,14 @@ import MutationButton from "../../../components/form/mutation-button";
import Username from "../../../components/username"; import Username from "../../../components/username";
import { useGetReportQuery, useResolveReportMutation } from "../../../lib/query/admin/reports"; import { useGetReportQuery, useResolveReportMutation } from "../../../lib/query/admin/reports";
import { useBaseUrl } from "../../../lib/navigation/util"; import { useBaseUrl } from "../../../lib/navigation/util";
import { AdminReport } from "../../../lib/types/report"; import type { AdminReport } from "../../../lib/types/report";
import { yesOrNo } from "../../../lib/util"; import { yesOrNo } from "../../../lib/util";
import { Status } from "../../../components/status"; import { Status } from "../../../components/status";
export default function ReportDetail({ }) { export default function ReportDetail({ }) {
const params: { reportId: string } = useParams(); const params: { reportId: string } = useParams();
const baseUrl = useBaseUrl(); const baseUrl = useBaseUrl();
const backLocation: String = history.state?.backLocation ?? `~${baseUrl}`; const backLocation: string = history.state?.backLocation ?? `~${baseUrl}`;
return ( return (
<div className="report-detail"> <div className="report-detail">
@ -200,7 +200,7 @@ function ReportHistory({ report, baseUrl, location }: ReportSectionProps) {
function ReportActionForm({ report }) { function ReportActionForm({ report }) {
const form = { const form = {
id: useValue("id", report.id), id: useValue("id", report.id),
comment: useTextInput("action_taken_comment") comment: useTextInput("action_taken_comment"),
}; };
const [submit, result] = useFormSubmit(form, useResolveReportMutation(), { changedOnly: false }); const [submit, result] = useFormSubmit(form, useResolveReportMutation(), { changedOnly: false });
@ -249,7 +249,7 @@ function ReportStatuses({ report }: { report: AdminReport }) {
<Status <Status
key={status.id} key={status.id}
status={status} status={status}
/> />
); );
})} })}
</ul> </ul>

View file

@ -17,7 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { ReactNode, useEffect, useMemo } from "react"; import type { ReactNode} from "react";
import React, { useEffect, useMemo } from "react";
import { useLazySearchReportsQuery } from "../../../lib/query/admin/reports"; import { useLazySearchReportsQuery } from "../../../lib/query/admin/reports";
import { useTextInput } from "../../../lib/form"; import { useTextInput } from "../../../lib/form";
@ -26,7 +27,7 @@ import { Select } from "../../../components/form/inputs";
import MutationButton from "../../../components/form/mutation-button"; import MutationButton from "../../../components/form/mutation-button";
import { useLocation, useSearch } from "wouter"; import { useLocation, useSearch } from "wouter";
import Username from "../../../components/username"; import Username from "../../../components/username";
import { AdminReport } from "../../../lib/types/report"; import type { AdminReport } from "../../../lib/types/report";
export default function ReportsSearch() { export default function ReportsSearch() {
return ( return (
@ -61,7 +62,7 @@ function ReportSearchForm() {
resolved: useTextInput("resolved", { defaultValue: resolved }), resolved: useTextInput("resolved", { defaultValue: resolved }),
account_id: useTextInput("account_id", { defaultValue: urlQueryParams.get("account_id") ?? "" }), account_id: useTextInput("account_id", { defaultValue: urlQueryParams.get("account_id") ?? "" }),
target_account_id: useTextInput("target_account_id", { defaultValue: urlQueryParams.get("target_account_id") ?? "" }), target_account_id: useTextInput("target_account_id", { defaultValue: urlQueryParams.get("target_account_id") ?? "" }),
limit: useTextInput("limit", { defaultValue: urlQueryParams.get("limit") ?? "20" }) limit: useTextInput("limit", { defaultValue: urlQueryParams.get("limit") ?? "20" }),
}; };
const setResolved = form.resolved.setter; const setResolved = form.resolved.setter;
@ -196,7 +197,7 @@ function ReportListEntry({ report, linkTo, backLocation }: ReportEntryProps) {
// Store the back location in history so // Store the back location in history so
// the detail view can use it to return to // the detail view can use it to return to
// this page (including query parameters). // this page (including query parameters).
state: { backLocation: backLocation } state: { backLocation: backLocation },
}); });
}} }}
role="link" role="link"

View file

@ -24,7 +24,7 @@ import { TextInput } from "../../components/form/inputs";
import MutationButton from "../../components/form/mutation-button"; import MutationButton from "../../components/form/mutation-button";
import { useEmailChangeMutation, usePasswordChangeMutation, useUserQuery } from "../../lib/query/user"; import { useEmailChangeMutation, usePasswordChangeMutation, useUserQuery } from "../../lib/query/user";
import Loading from "../../components/loading"; import Loading from "../../components/loading";
import { User } from "../../lib/types/user"; import type { User } from "../../lib/types/user";
import { useInstanceV1Query } from "../../lib/query/gts-api"; import { useInstanceV1Query } from "../../lib/query/gts-api";
export default function EmailPassword() { export default function EmailPassword() {
@ -42,7 +42,7 @@ function PasswordChange() {
const { const {
data: instance, data: instance,
isFetching: isFetchingInstance, isFetching: isFetchingInstance,
isLoading: isLoadingInstance isLoading: isLoadingInstance,
} = useInstanceV1Query(); } = useInstanceV1Query();
if (isFetchingInstance || isLoadingInstance) { if (isFetchingInstance || isLoadingInstance) {
return <Loading />; return <Loading />;
@ -64,8 +64,8 @@ function PasswordChangeForm({ oidcEnabled }: { oidcEnabled?: boolean }) {
return "New password same as old password"; return "New password same as old password";
} }
return ""; return "";
} },
}) }),
}; };
const verifyNewPassword = useTextInput("verifyNewPassword", { const verifyNewPassword = useTextInput("verifyNewPassword", {
@ -74,7 +74,7 @@ function PasswordChangeForm({ oidcEnabled }: { oidcEnabled?: boolean }) {
return "Passwords do not match"; return "Passwords do not match";
} }
return ""; return "";
} },
}); });
const [submitForm, result] = useFormSubmit(form, usePasswordChangeMutation()); const [submitForm, result] = useFormSubmit(form, usePasswordChangeMutation());
@ -138,14 +138,14 @@ function EmailChange() {
const { const {
data: instance, data: instance,
isFetching: isFetchingInstance, isFetching: isFetchingInstance,
isLoading: isLoadingInstance isLoading: isLoadingInstance,
} = useInstanceV1Query(); } = useInstanceV1Query();
// Load user data. // Load user data.
const { const {
data: user, data: user,
isFetching: isFetchingUser, isFetching: isFetchingUser,
isLoading: isLoadingUser isLoading: isLoadingUser,
} = useUserQuery(); } = useUserQuery();
if ( if (
@ -170,7 +170,7 @@ function EmailChangeForm({user, oidcEnabled}: { user: User, oidcEnabled?: boolea
const form = { const form = {
currentEmail: useTextInput("current_email", { currentEmail: useTextInput("current_email", {
defaultValue: user.email, defaultValue: user.email,
nosubmit: true nosubmit: true,
}), }),
newEmail: useTextInput("new_email", { newEmail: useTextInput("new_email", {
validator: (value: string | undefined) => { validator: (value: string | undefined) => {

View file

@ -28,7 +28,7 @@ import {
import MutationButton from "../../../components/form/mutation-button"; import MutationButton from "../../../components/form/mutation-button";
import useFormSubmit from "../../../lib/form/submit"; import useFormSubmit from "../../../lib/form/submit";
import { useValue } from "../../../lib/form"; import { useValue } from "../../../lib/form";
import { AccountExportStats } from "../../../lib/types/account"; import type { AccountExportStats } from "../../../lib/types/account";
export default function Export({ exportStats }: { exportStats: AccountExportStats }) { export default function Export({ exportStats }: { exportStats: AccountExportStats }) {
const [exportFollowing, exportFollowingResult] = useFormSubmit( const [exportFollowing, exportFollowingResult] = useFormSubmit(
@ -92,7 +92,7 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat
className="docslink" className="docslink"
rel="noreferrer" rel="noreferrer"
> >
Learn more about this section (opens in a new tab) Learn more about this section (opens in a new tab)
</a> </a>
</div> </div>
@ -105,7 +105,9 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat
className="text-cutoff" className="text-cutoff"
label="Download following.csv" label="Download following.csv"
type="button" type="button"
onClick={() => exportFollowing()} onClick={() => {
exportFollowing();
}}
result={exportFollowingResult} result={exportFollowingResult}
showError={true} showError={true}
disabled={exportStats.following_count === 0} disabled={exportStats.following_count === 0}
@ -119,7 +121,9 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat
className="text-cutoff" className="text-cutoff"
label="Download followers.csv" label="Download followers.csv"
type="button" type="button"
onClick={() => exportFollowers()} onClick={() => {
exportFollowers();
}}
result={exportFollowersResult} result={exportFollowersResult}
showError={true} showError={true}
disabled={exportStats.followers_count === 0} disabled={exportStats.followers_count === 0}
@ -133,7 +137,9 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat
className="text-cutoff" className="text-cutoff"
label="Download lists.csv" label="Download lists.csv"
type="button" type="button"
onClick={() => exportLists()} onClick={() => {
exportLists();
}}
result={exportListsResult} result={exportListsResult}
showError={true} showError={true}
disabled={exportStats.lists_count === 0} disabled={exportStats.lists_count === 0}
@ -147,7 +153,9 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat
className="text-cutoff" className="text-cutoff"
label="Download blocks.csv" label="Download blocks.csv"
type="button" type="button"
onClick={() => exportBlocks()} onClick={() => {
exportBlocks();
}}
result={exportBlocksResult} result={exportBlocksResult}
showError={true} showError={true}
disabled={exportStats.blocks_count === 0} disabled={exportStats.blocks_count === 0}
@ -161,7 +169,9 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat
className="text-cutoff" className="text-cutoff"
label="Download mutes.csv" label="Download mutes.csv"
type="button" type="button"
onClick={() => exportMutes()} onClick={() => {
exportMutes();
}}
result={exportMutesResult} result={exportMutesResult}
showError={true} showError={true}
disabled={exportStats.mutes_count === 0} disabled={exportStats.mutes_count === 0}

View file

@ -28,7 +28,7 @@ export default function Import() {
const form = { const form = {
data: useFileInput("data"), data: useFileInput("data"),
type: useTextInput("type", { defaultValue: "" }), type: useTextInput("type", { defaultValue: "" }),
mode: useTextInput("mode", { defaultValue: "" }) mode: useTextInput("mode", { defaultValue: "" }),
}; };
const [submitForm, result] = useFormSubmit(form, useImportDataMutation(), { const [submitForm, result] = useFormSubmit(form, useImportDataMutation(), {
@ -37,7 +37,7 @@ export default function Import() {
form.data.reset(); form.data.reset();
form.type.reset(); form.type.reset();
form.mode.reset(); form.mode.reset();
} },
}); });
return ( return (

View file

@ -23,7 +23,7 @@ import FormWithData from "../../../lib/form/form-with-data";
import BackButton from "../../../components/back-button"; import BackButton from "../../../components/back-button";
import { useBaseUrl } from "../../../lib/navigation/util"; import { useBaseUrl } from "../../../lib/navigation/util";
import { useApproveInteractionRequestMutation, useGetInteractionRequestQuery, useRejectInteractionRequestMutation } from "../../../lib/query/user/interactions"; import { useApproveInteractionRequestMutation, useGetInteractionRequestQuery, useRejectInteractionRequestMutation } from "../../../lib/query/user/interactions";
import { InteractionRequest } from "../../../lib/types/interaction"; import type { InteractionRequest } from "../../../lib/types/interaction";
import { useIcon, useNoun, useVerbed } from "./util"; import { useIcon, useNoun, useVerbed } from "./util";
import MutationButton from "../../../components/form/mutation-button"; import MutationButton from "../../../components/form/mutation-button";
import { Status } from "../../../components/status"; import { Status } from "../../../components/status";
@ -31,7 +31,7 @@ import { Status } from "../../../components/status";
export default function InteractionRequestDetail({ }) { export default function InteractionRequestDetail({ }) {
const params: { reqId: string } = useParams(); const params: { reqId: string } = useParams();
const baseUrl = useBaseUrl(); const baseUrl = useBaseUrl();
const backLocation: String = history.state?.backLocation ?? `~${baseUrl}`; const backLocation: string = history.state?.backLocation ?? `~${baseUrl}`;
return ( return (
<div className="interaction-request-detail"> <div className="interaction-request-detail">

View file

@ -17,14 +17,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { ReactNode, useEffect, useMemo } from "react"; import type { ReactNode} from "react";
import React, { useEffect, useMemo } from "react";
import { useBoolInput, useTextInput } from "../../../lib/form"; import { useBoolInput, useTextInput } from "../../../lib/form";
import { PageableList } from "../../../components/pageable-list"; import { PageableList } from "../../../components/pageable-list";
import MutationButton from "../../../components/form/mutation-button"; import MutationButton from "../../../components/form/mutation-button";
import { useLocation, useSearch } from "wouter"; import { useLocation, useSearch } from "wouter";
import { useApproveInteractionRequestMutation, useLazySearchInteractionRequestsQuery, useRejectInteractionRequestMutation } from "../../../lib/query/user/interactions"; import { useApproveInteractionRequestMutation, useLazySearchInteractionRequestsQuery, useRejectInteractionRequestMutation } from "../../../lib/query/user/interactions";
import { InteractionRequest } from "../../../lib/types/interaction"; import type { InteractionRequest } from "../../../lib/types/interaction";
import { Checkbox } from "../../../components/form/inputs"; import { Checkbox } from "../../../components/form/inputs";
import { useContent, useIcon, useNoun, useVerbed } from "./util"; import { useContent, useIcon, useNoun, useVerbed } from "./util";
@ -46,16 +47,16 @@ export default function InteractionRequestsSearchForm() {
// urlQueryParams, to allow paging. // urlQueryParams, to allow paging.
const form = { const form = {
statusID: useTextInput("status_id", { statusID: useTextInput("status_id", {
defaultValue: urlQueryParams.get("status_id") ?? "" defaultValue: urlQueryParams.get("status_id") ?? "",
}), }),
likes: useBoolInput("favourites", { likes: useBoolInput("favourites", {
defaultValue: defaultTrue(urlQueryParams.get("favourites")) defaultValue: defaultTrue(urlQueryParams.get("favourites")),
}), }),
replies: useBoolInput("replies", { replies: useBoolInput("replies", {
defaultValue: defaultTrue(urlQueryParams.get("replies")) defaultValue: defaultTrue(urlQueryParams.get("replies")),
}), }),
boosts: useBoolInput("reblogs", { boosts: useBoolInput("reblogs", {
defaultValue: defaultTrue(urlQueryParams.get("reblogs")) defaultValue: defaultTrue(urlQueryParams.get("reblogs")),
}), }),
}; };
@ -186,7 +187,7 @@ function ReqsListEntry({ req, linkTo, backLocation }: ReqsListEntryProps) {
// Store the back location in history so // Store the back location in history so
// the detail view can use it to return to // the detail view can use it to return to
// this page (including query parameters). // this page (including query parameters).
state: { backLocation: backLocation } state: { backLocation: backLocation },
}); });
}} }}
role="link" role="link"

View file

@ -20,8 +20,9 @@
import { useMemo } from "react"; import { useMemo } from "react";
import sanitize from "sanitize-html"; import sanitize from "sanitize-html";
import { compile, HtmlToTextOptions } from "html-to-text"; import type { HtmlToTextOptions } from "html-to-text";
import { Status } from "../../../lib/types/status"; import { compile } from "html-to-text";
import type { Status } from "../../../lib/types/status";
// Options for converting HTML statuses // Options for converting HTML statuses
// to plaintext representations. // to plaintext representations.
@ -29,7 +30,7 @@ const convertOptions: HtmlToTextOptions = {
selectors: [ selectors: [
// Don't fancy format links, just use their text value. // Don't fancy format links, just use their text value.
{ selector: 'a', options: { ignoreHref: true } }, { selector: 'a', options: { ignoreHref: true } },
] ],
}; };
const convertHTML = compile(convertOptions); const convertHTML = compile(convertOptions);

View file

@ -142,8 +142,8 @@ function AlsoKnownAsURI({ index, data }) {
} }
function MoveForm({ data: profile }) { function MoveForm({ data: profile }) {
let urlStr = store.getState().oauth.instanceUrl ?? ""; const urlStr = store.getState().oauth.instanceUrl ?? "";
let url = new URL(urlStr); const url = new URL(urlStr);
const form = { const form = {
movedToURI: useTextInput("moved_to_uri", { movedToURI: useTextInput("moved_to_uri", {
@ -162,10 +162,10 @@ function MoveForm({ data: profile }) {
<div className="form-section-docs"> <div className="form-section-docs">
<h3>Move Account</h3> <h3>Move Account</h3>
<p> <p>
For a move to be successful, you must have already set an alias from the For a move to be successful, you must have already set an alias from the
target account back to the account you're moving from (ie., this account), target account back to the account you're moving from (ie., this account),
using the settings panel of the instance on which the target account resides. using the settings panel of the instance on which the target account resides.
To do this, provide the following details to the other instance: To do this, provide the following details to the other instance:
</p> </p>
<dl className="migration-details"> <dl className="migration-details">
<div> <div>

View file

@ -24,7 +24,7 @@ import { Select, Checkbox } from "../../../../components/form/inputs";
import Languages from "../../../../components/languages"; import Languages from "../../../../components/languages";
import MutationButton from "../../../../components/form/mutation-button"; import MutationButton from "../../../../components/form/mutation-button";
import { useUpdateCredentialsMutation } from "../../../../lib/query/user"; import { useUpdateCredentialsMutation } from "../../../../lib/query/user";
import { Account } from "../../../../lib/types/account"; import type { Account } from "../../../../lib/types/account";
export default function BasicSettings({ account }: { account: Account }) { export default function BasicSettings({ account }: { account: Account }) {
/* form keys /* form keys
@ -36,7 +36,7 @@ export default function BasicSettings({ account }: { account: Account }) {
const form = { const form = {
defaultPrivacy: useTextInput("source[privacy]", { source: account, defaultValue: "unlisted" }), defaultPrivacy: useTextInput("source[privacy]", { source: account, defaultValue: "unlisted" }),
isSensitive: useBoolInput("source[sensitive]", { source: account }), isSensitive: useBoolInput("source[sensitive]", { source: account }),
language: useTextInput("source[language]", { source: account, valueSelector: (s: Account) => s.source?.language?.toUpperCase() ?? "EN" }), language: useTextInput("source[language]", { source: account, valueSelector: (s: Account) => s.source?.language.toUpperCase() ?? "EN" }),
statusContentType: useTextInput("source[status_content_type]", { source: account, defaultValue: "text/plain" }), statusContentType: useTextInput("source[status_content_type]", { source: account, defaultValue: "text/plain" }),
}; };
@ -52,7 +52,7 @@ export default function BasicSettings({ account }: { account: Account }) {
className="docslink" className="docslink"
rel="noreferrer" rel="noreferrer"
> >
Learn more about these settings (opens in a new tab) Learn more about these settings (opens in a new tab)
</a> </a>
</div> </div>
<Select field={form.language} label="Default post language" options={ <Select field={form.language} label="Default post language" options={

View file

@ -18,15 +18,16 @@
*/ */
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import type {
InteractionPolicyValue} from "../../../../lib/types/interaction";
import { import {
InteractionPolicyValue,
PolicyValueAuthor, PolicyValueAuthor,
PolicyValueFollowers, PolicyValueFollowers,
PolicyValueMentioned, PolicyValueMentioned,
PolicyValuePublic, PolicyValuePublic,
} from "../../../../lib/types/interaction"; } from "../../../../lib/types/interaction";
import { useTextInput } from "../../../../lib/form"; import { useTextInput } from "../../../../lib/form";
import { Action, BasicValue, PolicyFormSub, Visibility } from "./types"; import type { Action, BasicValue, PolicyFormSub, Visibility } from "./types";
// Based on the given visibility, action, and states, // Based on the given visibility, action, and states,
// derives what the initial basic Select value should be. // derives what the initial basic Select value should be.

View file

@ -26,11 +26,12 @@ import {
import Loading from "../../../../components/loading"; import Loading from "../../../../components/loading";
import { Error } from "../../../../components/error"; import { Error } from "../../../../components/error";
import MutationButton from "../../../../components/form/mutation-button"; import MutationButton from "../../../../components/form/mutation-button";
import { import type {
DefaultInteractionPolicies, DefaultInteractionPolicies,
InteractionPolicy, InteractionPolicy,
InteractionPolicyEntry, InteractionPolicyEntry,
InteractionPolicyValue, InteractionPolicyValue} from "../../../../lib/types/interaction";
import {
PolicyValueAuthor, PolicyValueAuthor,
PolicyValueFollowers, PolicyValueFollowers,
PolicyValueFollowing, PolicyValueFollowing,
@ -39,10 +40,11 @@ import {
} from "../../../../lib/types/interaction"; } from "../../../../lib/types/interaction";
import { useTextInput } from "../../../../lib/form"; import { useTextInput } from "../../../../lib/form";
import { Select } from "../../../../components/form/inputs"; import { Select } from "../../../../components/form/inputs";
import { TextFormInputHook } from "../../../../lib/form/types"; import type { TextFormInputHook } from "../../../../lib/form/types";
import { useBasicFor } from "./basic"; import { useBasicFor } from "./basic";
import { PolicyFormSomethingElse, useSomethingElseFor } from "./something-else"; import type { PolicyFormSomethingElse} from "./something-else";
import { Action, PolicyFormSub, SomethingElseValue, Visibility } from "./types"; import { useSomethingElseFor } from "./something-else";
import type { Action, PolicyFormSub, SomethingElseValue, Visibility } from "./types";
export default function InteractionPolicySettings() { export default function InteractionPolicySettings() {
const { const {
@ -134,7 +136,7 @@ function InteractionPoliciesForm({ defaultPolicies }: InteractionPoliciesFormPro
these settings; they do not apply retroactively. these settings; they do not apply retroactively.
<br/> <br/>
The word "anyone" in the below options means <em>anyone with The word "anyone" in the below options means <em>anyone with
permission to see the post</em>, taking account of blocks. permission to see the post</em>, taking account of blocks.
<br/> <br/>
Bear in mind that no matter what you set below, you will always Bear in mind that no matter what you set below, you will always
be able to like, reply-to, and boost your own posts. be able to like, reply-to, and boost your own posts.

View file

@ -18,9 +18,10 @@
*/ */
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import { InteractionPolicyValue, PolicyValueFollowers, PolicyValueFollowing, PolicyValuePublic } from "../../../../lib/types/interaction"; import type { InteractionPolicyValue} from "../../../../lib/types/interaction";
import { PolicyValueFollowers, PolicyValueFollowing, PolicyValuePublic } from "../../../../lib/types/interaction";
import { useTextInput } from "../../../../lib/form"; import { useTextInput } from "../../../../lib/form";
import { Action, Audience, PolicyFormSub, SomethingElseValue, Visibility } from "./types"; import type { Action, Audience, PolicyFormSub, SomethingElseValue, Visibility } from "./types";
export interface PolicyFormSomethingElse { export interface PolicyFormSomethingElse {
followers: PolicyFormSub, followers: PolicyFormSub,

View file

@ -17,8 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { TextFormInputHook } from "../../../../lib/form/types"; import type { TextFormInputHook } from "../../../../lib/form/types";
import React from "react"; import type React from "react";
export interface PolicyFormSub { export interface PolicyFormSub {
field: TextFormInputHook; field: TextFormInputHook;

View file

@ -34,18 +34,17 @@ import {
TextArea, TextArea,
FileInput, FileInput,
Checkbox, Checkbox,
Select Select,
} from "../../components/form/inputs"; } from "../../components/form/inputs";
import FormWithData from "../../lib/form/form-with-data"; import FormWithData from "../../lib/form/form-with-data";
import FakeProfile from "../../components/profile"; import FakeProfile from "../../components/profile";
import MutationButton from "../../components/form/mutation-button"; import MutationButton from "../../components/form/mutation-button";
import { useAccountThemesQuery } from "../../lib/query/user"; import { useAccountThemesQuery , useUpdateCredentialsMutation } from "../../lib/query/user";
import { useUpdateCredentialsMutation } from "../../lib/query/user";
import { useVerifyCredentialsQuery } from "../../lib/query/oauth"; import { useVerifyCredentialsQuery } from "../../lib/query/oauth";
import { useInstanceV1Query } from "../../lib/query/gts-api"; import { useInstanceV1Query } from "../../lib/query/gts-api";
import { Account } from "../../lib/types/account"; import type { Account } from "../../lib/types/account";
export default function UserProfile() { export default function UserProfile() {
return ( return (
@ -78,18 +77,18 @@ function UserProfileForm({ data: profile }: UserProfileFormProps) {
const { data: instance } = useInstanceV1Query(); const { data: instance } = useInstanceV1Query();
const instanceConfig = React.useMemo(() => { const instanceConfig = React.useMemo(() => {
return { return {
allowCustomCSS: instance?.configuration?.accounts?.allow_custom_css === true, allowCustomCSS: instance?.configuration.accounts.allow_custom_css === true,
maxPinnedFields: instance?.configuration?.accounts?.max_profile_fields ?? 6 maxPinnedFields: instance?.configuration.accounts.max_profile_fields ?? 6,
}; };
}, [instance]); }, [instance]);
// Parse out available theme options into nice format. // Parse out available theme options into nice format.
const { data: themes } = useAccountThemesQuery(); const { data: themes } = useAccountThemesQuery();
const themeOptions = useMemo(() => { const themeOptions = useMemo(() => {
let themeOptions = [ const themeOptions = [
<option key="" value=""> <option key="" value="">
Default Default
</option> </option>,
]; ];
themes?.forEach((theme) => { themes?.forEach((theme) => {
@ -101,7 +100,7 @@ function UserProfileForm({ data: profile }: UserProfileFormProps) {
themeOptions.push( themeOptions.push(
<option key={value} value={value}> <option key={value} value={value}>
{text} {text}
</option> </option>,
); );
}); });
@ -122,8 +121,8 @@ function UserProfileForm({ data: profile }: UserProfileFormProps) {
hideCollections: useBoolInput("hide_collections", { source: profile }), hideCollections: useBoolInput("hide_collections", { source: profile }),
webVisibility: useTextInput("web_visibility", { source: profile, valueSelector: (p) => p.source?.web_visibility }), webVisibility: useTextInput("web_visibility", { source: profile, valueSelector: (p) => p.source?.web_visibility }),
fields: useFieldArrayInput("fields_attributes", { fields: useFieldArrayInput("fields_attributes", {
defaultValue: profile?.source?.fields, defaultValue: profile.source?.fields,
length: instanceConfig.maxPinnedFields length: instanceConfig.maxPinnedFields,
}), }),
customCSS: useTextInput("custom_css", { source: profile, nosubmit: !instanceConfig.allowCustomCSS }), customCSS: useTextInput("custom_css", { source: profile, nosubmit: !instanceConfig.allowCustomCSS }),
theme: useTextInput("theme", { source: profile }), theme: useTextInput("theme", { source: profile }),
@ -134,7 +133,7 @@ function UserProfileForm({ data: profile }: UserProfileFormProps) {
onFinish: () => { onFinish: () => {
form.avatar.reset(); form.avatar.reset();
form.header.reset(); form.header.reset();
} },
}); });
const noAvatarSet = !profile.avatar_media_id; const noAvatarSet = !profile.avatar_media_id;
@ -322,7 +321,7 @@ function ProfileFields({ field: formField }) {
function Field({ index, data }) { function Field({ index, data }) {
const form = useWithFormContext(index, { const form = useWithFormContext(index, {
name: useTextInput("name", { defaultValue: data.name }), name: useTextInput("name", { defaultValue: data.name }),
value: useTextInput("value", { defaultValue: data.value }) value: useTextInput("value", { defaultValue: data.value }),
}); });
return ( return (