Upgrade react-admin to version 5 (#40)

* wip on ra upgrade

* Finish upgrade

* fix package.json

* pr fixes

* more pr fixes

* update readme
This commit is contained in:
Borislav Pantaleev 2024-09-24 10:02:47 +03:00 committed by GitHub
parent 24cf0a60bf
commit 48d933e028
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 5926 additions and 8556 deletions

View file

@ -5,6 +5,6 @@
}, },
"eslint.nodePath": ".yarn/sdks", "eslint.nodePath": ".yarn/sdks",
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs", "prettier.prettierPath": ".yarn/sdks/prettier/index.cjs",
"typescript.tsdk": ".yarn/sdks/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true "typescript.enablePromptUseWorkspaceTsdk": true
} }

View file

@ -1 +0,0 @@
yarnPath: .yarn/releases/yarn-4.1.1.cjs

View file

@ -32,6 +32,7 @@ The following changes are already implemented:
* [Fix required fields check on Bulk registration CSV upload](https://github.com/etkecc/synapse-admin/pull/32) * [Fix required fields check on Bulk registration CSV upload](https://github.com/etkecc/synapse-admin/pull/32)
* [Fix requests with invalid MXIDs on Bulk registration](https://github.com/etkecc/synapse-admin/pull/33) * [Fix requests with invalid MXIDs on Bulk registration](https://github.com/etkecc/synapse-admin/pull/33)
* [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27) * [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27)
* [Upgrade react-admin to v5](https://github.com/etkecc/synapse-admin/pull/40)
_the list will be updated as new changes are added_ _the list will be updated as new changes are added_

View file

@ -10,21 +10,20 @@
"type": "git", "type": "git",
"url": "https://github.com/etkecc/synapse-admin" "url": "https://github.com/etkecc/synapse-admin"
}, },
"packageManager": "yarn@4.1.1",
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.7.0", "@eslint/js": "^9.7.0",
"@testing-library/dom": "^10.0.0", "@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.0.0", "@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^16.0.0", "@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.13",
"@types/lodash": "^4.17.7", "@types/lodash": "^4.17.7",
"@types/node": "^20.14.12", "@types/node": "^20.14.12",
"@types/papaparse": "^5.3.14", "@types/papaparse": "^5.3.14",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/eslint-plugin": "^7.16.1",
"@typescript-eslint/parser": "^7.16.1", "@typescript-eslint/parser": "^7.16.1",
"@vitejs/plugin-react": "^4.0.0", "@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
@ -37,11 +36,11 @@
"jest-fetch-mock": "^3.0.3", "jest-fetch-mock": "^3.0.3",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"react-test-renderer": "^18.3.1", "react-test-renderer": "^18.3.1",
"ts-jest": "^29.2.3", "ts-jest": "^29.2.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"typescript-eslint": "^7.16.1", "typescript-eslint": "^7.16.1",
"vite": "^5.3.4", "vite": "^5.4.6",
"vite-plugin-version-mark": "^0.1.0" "vite-plugin-version-mark": "^0.1.0"
}, },
"dependencies": { "dependencies": {
@ -49,27 +48,26 @@
"@emotion/styled": "^11.13.0", "@emotion/styled": "^11.13.0",
"@haleos/ra-language-german": "^1.0.0", "@haleos/ra-language-german": "^1.0.0",
"@haxqer/ra-language-chinese": "^4.16.2", "@haxqer/ra-language-chinese": "^4.16.2",
"@mui/icons-material": "^5.16.4", "@mui/icons-material": "^6.1.1",
"@mui/material": "^5.16.4", "@mui/material": "^6.1.1",
"@tanstack/react-query": "^5.56.2",
"history": "^5.3.0", "history": "^5.3.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"query-string": "^7.1.3", "ra-core": "^5.2.0",
"ra-core": "^4.16.20", "ra-i18n-polyglot": "^5.2.0",
"ra-i18n-polyglot": "^4.16.20", "ra-language-english": "^5.2.0",
"ra-language-english": "^4.16.20", "ra-language-farsi": "^5.0.0",
"ra-language-farsi": "^4.2.0", "ra-language-french": "^5.2.0",
"ra-language-french": "^4.16.20",
"ra-language-italian": "^3.13.1", "ra-language-italian": "^3.13.1",
"ra-language-russian": "^4.14.2", "ra-language-russian": "^4.14.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-admin": "^4.16.20", "react-admin": "^5.2.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.52.1", "react-hook-form": "^7.53.0",
"react-is": "^18.3.1", "react-is": "^18.3.1",
"react-query": "^3.39.3", "react-router": "^6.26.2",
"react-router": "^6.25.1", "react-router-dom": "^6.26.2"
"react-router-dom": "^6.25.1"
}, },
"scripts": { "scripts": {
"start": "vite serve", "start": "vite serve",

View file

@ -1,4 +1,6 @@
import { render, screen } from "@testing-library/react"; import { render, screen, waitFor } from "@testing-library/react";
import fetchMock from "jest-fetch-mock";
fetchMock.enableMocks();
import App from "./App"; import App from "./App";
@ -7,4 +9,4 @@ describe("App", () => {
render(<App />); render(<App />);
await screen.findAllByText("Welcome to Synapse-admin"); await screen.findAllByText("Welcome to Synapse-admin");
}); });
}); });

View file

@ -53,7 +53,6 @@ const App = () => (
authProvider={authProvider} authProvider={authProvider}
dataProvider={dataProvider} dataProvider={dataProvider}
i18nProvider={i18nProvider} i18nProvider={i18nProvider}
darkTheme={{ palette: { mode: "dark" } }}
> >
<CustomRoutes> <CustomRoutes>
<Route path="/import_users" element={<ImportFeature />} /> <Route path="/import_users" element={<ImportFeature />} />

View file

@ -74,7 +74,6 @@ const DeleteRoomButton: React.FC<DeleteRoomButtonProps> = (props) => {
<DialogContentText>{translate(props.confirmContent)}</DialogContentText> <DialogContentText>{translate(props.confirmContent)}</DialogContentText>
<SimpleForm toolbar={false}> <SimpleForm toolbar={false}>
<BooleanInput <BooleanInput
fullWidth
source="block" source="block"
value={block} value={block}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setBlock(event.target.checked)} onChange={(event: React.ChangeEvent<HTMLInputElement>) => setBlock(event.target.checked)}

View file

@ -20,7 +20,7 @@ import {
useTranslate, useTranslate,
useUnselectAll, useUnselectAll,
} from "react-admin"; } from "react-admin";
import { useMutation } from "react-query"; import { useMutation } from "@tanstack/react-query";
const ServerNoticeDialog = ({ open, onClose, onSubmit }) => { const ServerNoticeDialog = ({ open, onClose, onSubmit }) => {
const translate = useTranslate(); const translate = useTranslate();
@ -43,7 +43,6 @@ const ServerNoticeDialog = ({ open, onClose, onSubmit }) => {
<TextInput <TextInput
source="body" source="body"
label="resources.servernotices.fields.body" label="resources.servernotices.fields.body"
fullWidth
multiline multiline
rows="4" rows="4"
resettable resettable
@ -64,6 +63,10 @@ export const ServerNoticeButton = () => {
const handleDialogOpen = () => setOpen(true); const handleDialogOpen = () => setOpen(true);
const handleDialogClose = () => setOpen(false); const handleDialogClose = () => setOpen(false);
if (!record) {
return null;
}
const handleSend = (values: Partial<RaRecord>) => { const handleSend = (values: Partial<RaRecord>) => {
create( create(
"servernotices", "servernotices",
@ -100,28 +103,26 @@ export const ServerNoticeBulkButton = () => {
const unselectAllUsers = useUnselectAll("users"); const unselectAllUsers = useUnselectAll("users");
const dataProvider = useDataProvider(); const dataProvider = useDataProvider();
const { mutate: sendNotices, isLoading } = useMutation( const { mutate: sendNotices, isPending } = useMutation({
data => mutationFn: (data) =>
dataProvider.createMany("servernotices", { dataProvider.createMany("servernotices", {
ids: selectedIds, ids: selectedIds,
data: data, data: data,
}), }),
{ onSuccess: () => {
onSuccess: () => { notify("resources.servernotices.action.send_success");
notify("resources.servernotices.action.send_success"); unselectAllUsers();
unselectAllUsers(); closeDialog();
closeDialog(); },
}, onError: () =>
onError: () => notify("resources.servernotices.action.send_failure", {
notify("resources.servernotices.action.send_failure", { type: "error",
type: "error", }),
}), });
}
);
return ( return (
<> <>
<Button label="resources.servernotices.send" onClick={openDialog} disabled={isLoading}> <Button label="resources.servernotices.send" onClick={openDialog} disabled={isPending}>
<MessageIcon /> <MessageIcon />
</Button> </Button>
<ServerNoticeDialog open={open} onClose={closeDialog} onSubmit={sendNotices} /> <ServerNoticeDialog open={open} onClose={closeDialog} onSubmit={sendNotices} />

View file

@ -28,7 +28,7 @@ import {
useRefresh, useRefresh,
useTranslate, useTranslate,
} from "react-admin"; } from "react-admin";
import { useMutation } from "react-query"; import { useMutation } from "@tanstack/react-query";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { dateParser } from "./date"; import { dateParser } from "./date";
@ -55,14 +55,12 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
<DialogContentText>{translate("delete_media.helper.send")}</DialogContentText> <DialogContentText>{translate("delete_media.helper.send")}</DialogContentText>
<SimpleForm toolbar={<DeleteMediaToolbar />} onSubmit={onSubmit}> <SimpleForm toolbar={<DeleteMediaToolbar />} onSubmit={onSubmit}>
<DateTimeInput <DateTimeInput
fullWidth
source="before_ts" source="before_ts"
label="delete_media.fields.before_ts" label="delete_media.fields.before_ts"
defaultValue={0} defaultValue={0}
parse={dateParser} parse={dateParser}
/> />
<NumberInput <NumberInput
fullWidth
source="size_gt" source="size_gt"
label="delete_media.fields.size_gt" label="delete_media.fields.size_gt"
defaultValue={0} defaultValue={0}
@ -70,7 +68,6 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
step={1024} step={1024}
/> />
<BooleanInput <BooleanInput
fullWidth
source="keep_profiles" source="keep_profiles"
label="delete_media.fields.keep_profiles" label="delete_media.fields.keep_profiles"
defaultValue={true} defaultValue={true}
@ -86,20 +83,18 @@ export const DeleteMediaButton = (props: ButtonProps) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const notify = useNotify(); const notify = useNotify();
const dataProvider = useDataProvider<SynapseDataProvider>(); const dataProvider = useDataProvider<SynapseDataProvider>();
const { mutate: deleteMedia, isLoading } = useMutation( const { mutate: deleteMedia, isPending } = useMutation({
(values: DeleteMediaParams) => dataProvider.deleteMedia(values), mutationFn: (values: DeleteMediaParams) => dataProvider.deleteMedia(values),
{ onSuccess: () => {
onSuccess: () => { notify("delete_media.action.send_success");
notify("delete_media.action.send_success"); closeDialog();
closeDialog(); },
}, onError: () => {
onError: () => { notify("delete_media.action.send_failure", {
notify("delete_media.action.send_failure", { type: "error",
type: "error", });
}); },
}, });
}
);
const openDialog = () => setOpen(true); const openDialog = () => setOpen(true);
const closeDialog = () => setOpen(false); const closeDialog = () => setOpen(false);
@ -110,7 +105,7 @@ export const DeleteMediaButton = (props: ButtonProps) => {
{...props} {...props}
label="delete_media.action.send" label="delete_media.action.send"
onClick={openDialog} onClick={openDialog}
disabled={isLoading} disabled={isPending}
sx={{ sx={{
color: theme.palette.error.main, color: theme.palette.error.main,
"&:hover": { "&:hover": {

View file

@ -4,6 +4,14 @@ import { SynapseTranslationMessages } from ".";
const de: SynapseTranslationMessages = { const de: SynapseTranslationMessages = {
...formalGermanMessages, ...formalGermanMessages,
ra: {
...formalGermanMessages.ra,
navigation: {
...formalGermanMessages.ra.navigation,
no_filtered_results: "Keine Ergebnisse",
clear_filters: "Alle Filter entfernen",
},
},
synapseadmin: { synapseadmin: {
auth: { auth: {
base_url: "Heimserver URL", base_url: "Heimserver URL",

View file

@ -4,6 +4,14 @@ import { SynapseTranslationMessages } from ".";
const ru: SynapseTranslationMessages = { const ru: SynapseTranslationMessages = {
...russianMessages, ...russianMessages,
ra: {
...russianMessages.ra,
navigation: {
...russianMessages.ra.navigation,
no_filtered_results: "Нет результатов",
clear_filters: "Все фильтры сбросить",
},
},
synapseadmin: { synapseadmin: {
auth: { auth: {
base_url: "Адрес домашнего сервера", base_url: "Адрес домашнего сервера",

View file

@ -4,6 +4,14 @@ import { SynapseTranslationMessages } from ".";
const zh: SynapseTranslationMessages = { const zh: SynapseTranslationMessages = {
...chineseMessages, ...chineseMessages,
ra: {
...chineseMessages.ra,
navigation: {
...chineseMessages.ra.navigation,
no_filtered_results: "没有结果",
clear_filters: "清除所有过滤器",
},
},
synapseadmin: { synapseadmin: {
auth: { auth: {
base_url: "服务器 URL", base_url: "服务器 URL",

View file

@ -8,14 +8,17 @@ import { AppContext } from "../AppContext";
import englishMessages from "../i18n/en"; import englishMessages from "../i18n/en";
const i18nProvider = polyglotI18nProvider(() => englishMessages, "en", [{ locale: "en", name: "English" }]); const i18nProvider = polyglotI18nProvider(() => englishMessages, "en", [{ locale: "en", name: "English" }]);
import { act } from "@testing-library/react";
describe("LoginForm", () => { describe("LoginForm", () => {
it("renders with no restriction to homeserver", () => { it("renders with no restriction to homeserver", async () => {
render( await act(async () => {
<AdminContext i18nProvider={i18nProvider}> render(
<LoginPage /> <AdminContext i18nProvider={i18nProvider}>
</AdminContext> <LoginPage />
); </AdminContext>
);
});
screen.getByText(englishMessages.synapseadmin.auth.welcome); screen.getByText(englishMessages.synapseadmin.auth.welcome);
screen.getByRole("combobox", { name: "" }); screen.getByRole("combobox", { name: "" });

View file

@ -222,7 +222,6 @@ const LoginPage = () => {
disabled={loading || !supportPassAuth} disabled={loading || !supportPassAuth}
onBlur={handleUsernameChange} onBlur={handleUsernameChange}
resettable resettable
fullWidth
validate={required()} validate={required()}
/> />
</Box> </Box>
@ -234,7 +233,6 @@ const LoginPage = () => {
autoComplete="current-password" autoComplete="current-password"
disabled={loading || !supportPassAuth} disabled={loading || !supportPassAuth}
resettable resettable
fullWidth
validate={required()} validate={required()}
/> />
</Box> </Box>
@ -247,7 +245,6 @@ const LoginPage = () => {
disabled={loading} disabled={loading}
readOnly={allowSingleBaseUrl} readOnly={allowSingleBaseUrl}
resettable={allowAnyBaseUrl} resettable={allowAnyBaseUrl}
fullWidth
validate={[required(), validateBaseUrl]} validate={[required(), validateBaseUrl]}
> >
{allowMultipleBaseUrls && {allowMultipleBaseUrls &&
@ -280,9 +277,9 @@ const LoginPage = () => {
<Box className="hint">{translate("synapseadmin.auth.welcome")}</Box> <Box className="hint">{translate("synapseadmin.auth.welcome")}</Box>
<Box className="form"> <Box className="form">
<Select <Select
fullWidth
value={locale} value={locale}
onChange={e => setLocale(e.target.value)} onChange={e => setLocale(e.target.value)}
fullWidth
disabled={loading} disabled={loading}
className="select" className="select"
> >

View file

@ -25,7 +25,7 @@ import {
useRefresh, useRefresh,
useUnselectAll, useUnselectAll,
} from "react-admin"; } from "react-admin";
import { useMutation } from "react-query"; import { useMutation } from "@tanstack/react-query";
import AvatarField from "../components/AvatarField"; import AvatarField from "../components/AvatarField";
@ -70,27 +70,25 @@ export const RoomDirectoryBulkPublishButton = (props: ButtonProps) => {
const refresh = useRefresh(); const refresh = useRefresh();
const unselectAllRooms = useUnselectAll("rooms"); const unselectAllRooms = useUnselectAll("rooms");
const dataProvider = useDataProvider(); const dataProvider = useDataProvider();
const { mutate, isLoading } = useMutation( const { mutate, isPending } = useMutation({
() => mutationFn: () =>
dataProvider.createMany("room_directory", { dataProvider.createMany("room_directory", {
ids: selectedIds, ids: selectedIds,
data: {}, data: {},
}), }),
{ onSuccess: () => {
onSuccess: () => { notify("resources.room_directory.action.send_success");
notify("resources.room_directory.action.send_success"); unselectAllRooms();
unselectAllRooms(); refresh();
refresh(); },
}, onError: () =>
onError: () => notify("resources.room_directory.action.send_failure", {
notify("resources.room_directory.action.send_failure", { type: "error",
type: "error", }),
}), });
}
);
return ( return (
<Button {...props} label="resources.room_directory.action.create" onClick={mutate} disabled={isLoading}> <Button {...props} label="resources.room_directory.action.create" onClick={mutate} disabled={isPending}>
<RoomDirectoryIcon /> <RoomDirectoryIcon />
</Button> </Button>
); );
@ -102,6 +100,10 @@ export const RoomDirectoryPublishButton = (props: ButtonProps) => {
const refresh = useRefresh(); const refresh = useRefresh();
const [create, { isLoading }] = useCreate(); const [create, { isLoading }] = useCreate();
if (!record) {
return null;
}
const handleSend = () => { const handleSend = () => {
create( create(
"room_directory", "room_directory",

View file

@ -67,6 +67,9 @@ const RoomTitle = () => {
const RoomShowActions = () => { const RoomShowActions = () => {
const record = useRecordContext(); const record = useRecordContext();
if (!record) {
return null;
}
const publishButton = record?.public ? <RoomDirectoryUnpublishButton /> : <RoomDirectoryPublishButton />; const publishButton = record?.public ? <RoomDirectoryUnpublishButton /> : <RoomDirectoryPublishButton />;
// FIXME: refresh after (un)publish // FIXME: refresh after (un)publish
return ( return (

View file

@ -287,15 +287,19 @@ export const UserEdit = (props: EditProps) => {
const translate = useTranslate(); const translate = useTranslate();
return ( return (
<Edit {...props} title={<UserTitle />} actions={<UserEditActions />}> <Edit {...props} title={<UserTitle />} actions={<UserEditActions />} mutationMode="pessimistic">
<TabbedForm toolbar={<UserEditToolbar />}> <TabbedForm toolbar={<UserEditToolbar />}>
<FormTab label={translate("resources.users.name", { smart_count: 1 })} icon={<PersonPinIcon />}> <FormTab label={translate("resources.users.name", { smart_count: 1 })} icon={<PersonPinIcon />}>
<AvatarField source="avatar_src" sortable={false} sx={{ height: "120px", width: "120px" }} /> <AvatarField source="avatar_src" sortable={false} sx={{ height: "120px", width: "120px" }} />
<BooleanInput source="avatar_erase" label="resources.users.action.erase_avatar" /> <BooleanInput source="avatar_erase" label="resources.users.action.erase_avatar" />
<ImageInput source="avatar_file" label="resources.users.fields.avatar" accept="image/*"> <ImageInput
<ImageField source="src" title="Avatar" /> source="avatar_file"
label="resources.users.fields.avatar"
accept={{ "image/*": [".png", ".jpg"] }}
>
<ImageField source="src" title="Avatar" />
</ImageInput> </ImageInput>
<TextInput source="id" disabled /> <TextInput source="id" readOnly />
<TextInput source="displayname" /> <TextInput source="displayname" />
<PasswordInput source="password" autoComplete="new-password" helperText="resources.users.helper.password" /> <PasswordInput source="password" autoComplete="new-password" helperText="resources.users.helper.password" />
<SelectInput source="user_type" choices={choices_type} translateChoice={false} resettable /> <SelectInput source="user_type" choices={choices_type} translateChoice={false} resettable />

View file

@ -1,12 +1,12 @@
import { stringify } from "query-string";
import { import {
DataProvider, DataProvider,
DeleteParams, DeleteParams,
HttpError, HttpError,
Identifier, Identifier,
Options, Options,
PaginationPayload,
RaRecord, RaRecord,
SortPayload,
UpdateParams, UpdateParams,
fetchUtils, fetchUtils,
withLifecycleCallbacks, withLifecycleCallbacks,
@ -52,6 +52,10 @@ const mxcUrlToHttp = (mxcUrl: string) => {
return `${homeserver}/_matrix/media/r0/thumbnail/${serverName}/${mediaId}?width=24&height=24&method=scale`; return `${homeserver}/_matrix/media/r0/thumbnail/${serverName}/${mediaId}?width=24&height=24&method=scale`;
}; };
const filterUndefined = (obj: Record<string, any>) => {
return Object.fromEntries(Object.entries(obj).filter(([key, value]) => value !== undefined));
};
interface Room { interface Room {
room_id: string; room_id: string;
name?: string; name?: string;
@ -527,8 +531,8 @@ const baseDataProvider: SynapseDataProvider = {
getList: async (resource, params) => { getList: async (resource, params) => {
console.log("getList " + resource); console.log("getList " + resource);
const { user_id, name, guests, deactivated, locked, search_term, destination, valid } = params.filter; const { user_id, name, guests, deactivated, locked, search_term, destination, valid } = params.filter;
const { page, perPage } = params.pagination; const { page, perPage } = params.pagination as PaginationPayload;
const { field, order } = params.sort; const { field, order } = params.sort as SortPayload;
const from = (page - 1) * perPage; const from = (page - 1) * perPage;
const query = { const query = {
from: from, from: from,
@ -550,7 +554,7 @@ const baseDataProvider: SynapseDataProvider = {
const res = resourceMap[resource]; const res = resourceMap[resource];
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
const url = `${endpoint_url}?${stringify(query)}`; const url = `${endpoint_url}?${new URLSearchParams(filterUndefined(query)).toString()}`;
const { json } = await jsonClient(url); const { json } = await jsonClient(url);
return { return {
@ -604,7 +608,7 @@ const baseDataProvider: SynapseDataProvider = {
const res = resourceMap[resource]; const res = resourceMap[resource];
const ref = res.reference(params.id); const ref = res.reference(params.id);
const endpoint_url = `${homeserver}${ref.endpoint}?${stringify(query)}`; const endpoint_url = `${homeserver}${ref.endpoint}?${new URLSearchParams(filterUndefined(query)).toString()}`;
const { json } = await jsonClient(endpoint_url); const { json } = await jsonClient(endpoint_url);
return { return {

View file

@ -24,7 +24,7 @@
/* Strict Type-Checking Options */ /* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */, "strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */,
// "strictNullChecks": true, /* Enable strict null checks. */ "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */

14257
yarn.lock

File diff suppressed because it is too large Load diff