ci(core): eslint errors for core (#4662)

This commit is contained in:
Joooye_34 2023-11-10 18:25:59 +08:00 committed by GitHub
parent b98a258083
commit 30bac7dce2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 264 additions and 140 deletions

View File

@ -58,7 +58,7 @@ const createPattern = packageName => [
const allPackages = [
'packages/backend/server',
'packages/frontend/component',
'packages/frontend/web',
'packages/frontend/core',
'packages/frontend/electron',
'packages/frontend/graphql',
'packages/frontend/hooks',
@ -255,6 +255,12 @@ const config = {
],
'@typescript-eslint/no-misused-promises': ['error'],
'i/no-extraneous-dependencies': ['error'],
'react-hooks/exhaustive-deps': [
'warn',
{
additionalHooks: 'useAsyncCallback',
},
],
},
})),
{

View File

@ -51,7 +51,6 @@
"jotai-effect": "^0.2.2",
"jotai-scope": "^0.4.0",
"lit": "^3.0.2",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"lottie-react": "^2.4.0",
"lottie-web": "^5.12.2",

View File

@ -2,7 +2,7 @@ import { Skeleton } from '@mui/material';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx';
import { useAtom, useAtomValue } from 'jotai';
import debounce from 'lodash/debounce';
import { debounce } from 'lodash-es';
import type { PropsWithChildren, ReactElement } from 'react';
import { useEffect, useRef, useState } from 'react';

View File

@ -17,6 +17,7 @@
},
"dependencies": {
"@affine-test/fixtures": "workspace:*",
"@affine/cmdk": "workspace:*",
"@affine/component": "workspace:*",
"@affine/debug": "workspace:*",
"@affine/env": "workspace:*",
@ -31,6 +32,7 @@
"@blocksuite/icons": "2.1.35",
"@blocksuite/lit": "0.0.0-20231110042432-4fdac4dc-nightly",
"@blocksuite/store": "0.0.0-20231110042432-4fdac4dc-nightly",
"@blocksuite/virgo": "0.0.0-20231110042432-4fdac4dc-nightly",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@emotion/cache": "^11.11.0",
@ -39,31 +41,44 @@
"@emotion/styled": "^11.11.0",
"@marsidev/react-turnstile": "^0.3.1",
"@mui/material": "^5.14.14",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@react-hookz/web": "^23.1.0",
"@toeverything/components": "^0.0.46",
"@toeverything/theme": "^0.7.20",
"@vanilla-extract/css": "^1.13.0",
"@vanilla-extract/dynamic": "^2.0.3",
"async-call-rpc": "^6.3.1",
"bytes": "^3.1.2",
"clsx": "^2.0.0",
"css-spring": "^4.1.0",
"cssnano": "^6.0.1",
"dayjs": "^1.11.10",
"foxact": "^0.2.20",
"graphql": "^16.8.1",
"idb": "^7.1.1",
"intl-segmenter-polyfill-rs": "^0.1.6",
"jotai": "^2.4.3",
"jotai-devtools": "^0.7.0",
"lit": "^3.0.2",
"lottie-web": "^5.12.2",
"mini-css-extract-plugin": "^2.7.6",
"nanoid": "^5.0.1",
"next-auth": "^4.23.2",
"next-themes": "^0.2.1",
"postcss-loader": "^7.3.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.11",
"react-is": "18.2.0",
"react-resizable-panels": "^0.0.55",
"react-router-dom": "^6.16.0",
"rxjs": "^7.8.1",
"ses": "^0.18.8",
"swr": "2.2.4",
"uuid": "^9.0.1",
"valtio": "^1.11.2",
"y-protocols": "^1.0.6",
"yjs": "^13.6.8",
@ -76,12 +91,15 @@
"@sentry/webpack-plugin": "^2.8.0",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.3.93",
"@testing-library/react": "^14.0.0",
"@types/bytes": "^3.1.3",
"@types/lodash-es": "^4.17.9",
"@types/uuid": "^9.0.5",
"@types/webpack-env": "^1.18.2",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"express": "^4.18.2",
"fake-indexeddb": "^5.0.0",
"html-webpack-plugin": "^5.5.3",
"lodash-es": "^4.17.21",
"mime-types": "^2.1.35",
@ -91,6 +109,7 @@
"swc-loader": "^0.2.3",
"swc-plugin-coverage-instrument": "^0.0.20",
"thread-loader": "^4.0.2",
"vitest": "0.34.6",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",

View File

@ -147,36 +147,49 @@ export const pageCollectionBaseAtom =
return new Observable<BaseCollectionsDataType>(subscriber => {
const group = new DisposableGroup();
currentWorkspacePromise.then(async currentWorkspace => {
const workspaceSetting = getWorkspaceSetting(currentWorkspace);
migrateCollectionsFromIdbData(currentWorkspace).then(collections => {
if (collections.length) {
workspaceSetting.addCollection(...collections);
}
});
migrateCollectionsFromUserData(currentWorkspace).then(collections => {
if (collections.length) {
workspaceSetting.addCollection(...collections);
}
});
subscriber.next({
loading: false,
collections: workspaceSetting.collections,
});
if (group.disposed) {
return;
}
const fn = () => {
currentWorkspacePromise
.then(async currentWorkspace => {
const workspaceSetting = getWorkspaceSetting(currentWorkspace);
migrateCollectionsFromIdbData(currentWorkspace)
.then(collections => {
if (collections.length) {
workspaceSetting.addCollection(...collections);
}
})
.catch(error => {
console.error(error);
});
migrateCollectionsFromUserData(currentWorkspace)
.then(collections => {
if (collections.length) {
workspaceSetting.addCollection(...collections);
}
})
.catch(error => {
console.error(error);
});
subscriber.next({
loading: false,
collections: workspaceSetting.collections,
});
};
workspaceSetting.collectionsYArray.observe(fn);
group.add(() => {
workspaceSetting.collectionsYArray.unobserve(fn);
if (group.disposed) {
return;
}
const fn = () => {
subscriber.next({
loading: false,
collections: workspaceSetting.collections,
});
};
workspaceSetting.collectionsYArray.observe(fn);
group.add(() => {
workspaceSetting.collectionsYArray.unobserve(fn);
});
})
.catch(error => {
subscriber.error(error);
});
});
return () => {
group.dispose();
};

View File

@ -7,6 +7,7 @@ import {
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import React, { useCallback } from 'react';
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
@ -31,7 +32,7 @@ export const AfterSignInSendEmail = ({
onSignedIn?.();
}
const onResendClick = useCallback(async () => {
const onResendClick = useAsyncCallback(async () => {
if (verifyToken) {
await signIn(email, verifyToken, challenge);
}

View File

@ -6,6 +6,7 @@ import {
} from '@affine/component/auth-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { type FC, useCallback } from 'react';
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
@ -29,7 +30,7 @@ export const AfterSignUpSendEmail: FC<AuthPanelProps> = ({
onSignedIn?.();
}
const onResendClick = useCallback(async () => {
const onResendClick = useAsyncCallback(async () => {
if (verifyToken) {
await signUp(email, verifyToken, challenge);
}

View File

@ -14,6 +14,7 @@ import {
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useMutation } from '@affine/workspace/affine/gql';
import { Button } from '@toeverything/components/button';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useSetAtom } from 'jotai/react';
import { useCallback, useState } from 'react';
@ -146,7 +147,7 @@ export const SendEmail = ({
const buttonContent = useButtonContent(emailType);
const { loading, sendEmail } = useSendEmail(emailType);
const onSendEmail = useCallback(async () => {
const onSendEmail = useAsyncCallback(async () => {
// TODO: add error handler
await sendEmail(email);

View File

@ -6,6 +6,7 @@ import {
} from '@affine/component/auth-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useSession } from 'next-auth/react';
import type { FC } from 'react';
@ -26,7 +27,7 @@ export const SignInWithPassword: FC<AuthPanelProps> = ({
const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState(false);
const onSignIn = useCallback(async () => {
const onSignIn = useAsyncCallback(async () => {
const res = await signInCloud('credentials', {
redirect: false,
email,

View File

@ -9,6 +9,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useMutation } from '@affine/workspace/affine/gql';
import { ArrowDownBigIcon, GoogleDuotoneIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { GraphQLError } from 'graphql';
import { type FC, useState } from 'react';
import { useCallback } from 'react';
@ -52,7 +53,7 @@ export const SignIn: FC<AuthPanelProps> = ({
onSignedIn?.();
}
const onContinue = useCallback(async () => {
const onContinue = useAsyncCallback(async () => {
if (!validateEmail(email)) {
setIsValidEmail(false);
return;

View File

@ -10,6 +10,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useMutation, useQuery } from '@affine/workspace/affine/gql';
import { Button } from '@toeverything/components/button';
import { Loading } from '@toeverything/components/loading';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { nanoid } from 'nanoid';
import { Suspense, useCallback, useEffect, useMemo } from 'react';
@ -33,12 +34,12 @@ const usePaymentRedirect = () => {
mutation: checkoutMutation,
});
return useCallback(() => {
checkoutSubscription({ recurring, idempotencyKey })
.then(({ checkout }) => {
window.open(checkout, '_self', 'norefferer');
})
.catch(e => console.error(e));
return useAsyncCallback(async () => {
const { checkout } = await checkoutSubscription({
recurring,
idempotencyKey,
});
window.open(checkout, '_self', 'norefferer');
}, [recurring, idempotencyKey, checkoutSubscription]);
};

View File

@ -9,6 +9,7 @@ import {
Modal,
} from '@toeverything/components/modal';
import { Tooltip } from '@toeverything/components/tooltip';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import type {
LoadDBFileResult,
SelectDBFileLocationResult,
@ -330,14 +331,13 @@ export const CreateWorkspaceModal = ({
]
);
const onConfirmName = useCallback(
(name: string) => {
const onConfirmName = useAsyncCallback(
async (name: string) => {
setWorkspaceName(name);
// this will be the last step for web for now
// fix me later
createLocalWorkspace(name).then(id => {
onCreate(id);
});
const id = await createLocalWorkspace(name);
onCreate(id);
},
[createLocalWorkspace, onCreate]
);

View File

@ -19,7 +19,7 @@ export const EnableAffineCloudModal = ({
const setAuthAtom = useSetAtom(authAtom);
const setOnceSignedInEvent = useSetAtom(setOnceSignedInEventAtom);
const confirm = useCallback(async () => {
const confirm = useCallback(() => {
return propsOnConfirm?.();
}, [propsOnConfirm]);

View File

@ -1,17 +1,17 @@
import { pushNotificationAtom } from '@affine/component/notification-center';
import { SettingRow } from '@affine/component/setting-components';
import { isDesktop } from '@affine/env/constant';
import type { AffineOfficialWorkspace } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import type { SaveDBFileResult } from '@toeverything/infra/type';
import { useSetAtom } from 'jotai';
import { useCallback, useState } from 'react';
import { useState } from 'react';
import type { Doc } from 'yjs';
import { encodeStateAsUpdate } from 'yjs';
async function syncBlobsToSqliteDb(workspace: AffineOfficialWorkspace) {
if (window.apis && isDesktop) {
if (window.apis && environment.isDesktop) {
const bs = workspace.blockSuiteWorkspace.blob;
const blobsInDb = await window.apis.db.getBlobKeys(workspace.id);
const blobsInStorage = await bs.list();
@ -32,7 +32,7 @@ async function syncBlobsToSqliteDb(workspace: AffineOfficialWorkspace) {
}
async function syncDocsToSqliteDb(workspace: AffineOfficialWorkspace) {
if (window.apis && isDesktop) {
if (window.apis && environment.isDesktop) {
const workspaceId = workspace.blockSuiteWorkspace.doc.guid;
const syncDoc = async (doc: Doc) => {
await window.apis.db.applyDocUpdate(
@ -56,7 +56,7 @@ export const ExportPanel = ({ workspace }: ExportPanelProps) => {
const t = useAFFiNEI18N();
const [syncing, setSyncing] = useState(false);
const pushNotification = useSetAtom(pushNotificationAtom);
const onExport = useCallback(async () => {
const onExport = useAsyncCallback(async () => {
if (syncing) {
return;
}

View File

@ -77,8 +77,8 @@ export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => {
);
const handleUploadAvatar = useCallback(
async (file: File) => {
await update(file)
(file: File) => {
update(file)
.then(() => {
pushNotification({
title: 'Update workspace avatar success',

View File

@ -11,9 +11,10 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { Tooltip } from '@toeverything/components/tooltip';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { noop } from 'foxact/noop';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { toast } from '../../../utils';
import { EnableAffineCloudModal } from '../enable-affine-cloud-modal';
@ -52,7 +53,7 @@ const PublishPanelAffine = (props: PublishPanelAffineProps) => {
);
}, []);
const copyUrl = useCallback(async () => {
const copyUrl = useAsyncCallback(async () => {
await navigator.clipboard.writeText(shareUrl);
toast(t['Copied link to clipboard']());
}, [shareUrl, t]);

View File

@ -16,6 +16,7 @@ import { useMutation, useQuery } from '@affine/workspace/affine/gql';
import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons';
import { Avatar } from '@toeverything/components/avatar';
import { Button } from '@toeverything/components/button';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { validateAndReduceImage } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
import bytes from 'bytes';
import { useSetAtom } from 'jotai';
@ -50,7 +51,7 @@ export const UserAvatar = () => {
mutation: removeAvatarMutation,
});
const handleUpdateUserAvatar = useCallback(
const handleUpdateUserAvatar = useAsyncCallback(
async (file: File) => {
try {
const reducedFile = await validateAndReduceImage(file);

View File

@ -22,6 +22,7 @@ import { ArrowRightSmallIcon } from '@blocksuite/icons';
import { Skeleton } from '@mui/material';
import { Button, IconButton } from '@toeverything/components/button';
import { Loading } from '@toeverything/components/loading';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useSetAtom } from 'jotai';
import { Suspense, useCallback, useMemo, useState } from 'react';
@ -255,8 +256,8 @@ const PaymentMethodUpdater = () => {
});
const t = useAFFiNEI18N();
const update = useCallback(() => {
trigger(null, {
const update = useAsyncCallback(async () => {
await trigger(null, {
onSuccess: data => {
window.open(data.createCustomerPortal, '_blank', 'noopener noreferrer');
},

View File

@ -4,9 +4,10 @@ import {
resumeSubscriptionMutation,
} from '@affine/graphql';
import { useMutation } from '@affine/workspace/affine/gql';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { nanoid } from 'nanoid';
import type { PropsWithChildren } from 'react';
import { useCallback, useState } from 'react';
import { useState } from 'react';
import { ConfirmLoadingModal, DowngradeModal } from './modals';
@ -30,8 +31,8 @@ export const CancelAction = ({
mutation: cancelSubscriptionMutation,
});
const downgrade = useCallback(() => {
trigger(
const downgrade = useAsyncCallback(async () => {
await trigger(
{ idempotencyKey },
{
onSuccess: data => {
@ -78,8 +79,8 @@ export const ResumeAction = ({
mutation: resumeSubscriptionMutation,
});
const resume = useCallback(() => {
trigger(
const resume = useAsyncCallback(async () => {
await trigger(
{ idempotencyKey },
{
onSuccess: data => {

View File

@ -15,6 +15,7 @@ import { useMutation } from '@affine/workspace/affine/gql';
import { DoneIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import { Tooltip } from '@toeverything/components/tooltip';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useSetAtom } from 'jotai';
import { useAtom } from 'jotai';
import { nanoid } from 'nanoid';
@ -364,7 +365,7 @@ const Upgrade = ({
}, [onSubscriptionUpdate]);
const [, openPaymentDisableModal] = useAtom(openPaymentDisableAtom);
const upgrade = useCallback(() => {
const upgrade = useAsyncCallback(async () => {
if (!runtimeConfig.enablePayment) {
openPaymentDisableModal(true);
return;
@ -373,7 +374,7 @@ const Upgrade = ({
if (newTabRef.current) {
newTabRef.current.focus();
} else {
trigger(
await trigger(
{ recurring, idempotencyKey },
{
onSuccess: data => {
@ -439,8 +440,8 @@ const ChangeRecurring = ({
mutation: updateSubscriptionMutation,
});
const change = useCallback(() => {
trigger(
const change = useAsyncCallback(async () => {
await trigger(
{ recurring: to, idempotencyKey },
{
onSuccess: data => {
@ -492,7 +493,7 @@ const ChangeRecurring = ({
const SignUpAction = ({ children }: PropsWithChildren) => {
const setOpen = useSetAtom(authAtom);
const onClickSignIn = useCallback(async () => {
const onClickSignIn = useCallback(() => {
setOpen(state => ({
...state,
openModal: true,

View File

@ -2,6 +2,7 @@ import { pushNotificationAtom } from '@affine/component/notification-center';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { useSetAtom } from 'jotai';
import { useAtomValue } from 'jotai';
@ -65,7 +66,7 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
workspaces,
]);
const handleDeleteWorkspace = useCallback(async () => {
const handleDeleteWorkspace = useAsyncCallback(async () => {
closeAndJumpOut();
await deleteWorkspace(workspaceId);
@ -75,7 +76,7 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
});
}, [closeAndJumpOut, deleteWorkspace, pushNotification, t, workspaceId]);
const handleLeaveWorkspace = useCallback(async () => {
const handleLeaveWorkspace = useAsyncCallback(async () => {
closeAndJumpOut();
await leaveWorkspace(workspaceId, workspaceName);

View File

@ -18,6 +18,7 @@ import {
MenuItem,
MenuSeparator,
} from '@toeverything/components/menu';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
@ -99,7 +100,7 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
const exportHandler = useExportPage(currentPage);
const setPageMode = useSetAtom(setPageModeAtom);
const duplicate = useCallback(async () => {
const duplicate = useAsyncCallback(async () => {
const currentPageMeta = currentPage.meta;
const newPage = createPage();
await newPage.waitForLoaded();

View File

@ -1,5 +1,6 @@
import { toast } from '@affine/component';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
import { initEmptyPage } from '@toeverything/infra/blocksuite';
import { useAtomValue, useSetAtom } from 'jotai';
@ -33,7 +34,7 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
const createEdgelessAndOpen = useCallback(() => {
return createPageAndOpen('edgeless');
}, [createPageAndOpen]);
const importFileAndOpen = useCallback(async () => {
const importFileAndOpen = useAsyncCallback(async () => {
const { showImportModal } = await import('@blocksuite/blocks');
const onSuccess = (pageIds: string[], isWorkspaceFile: boolean) => {
toast(

View File

@ -1,6 +1,7 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloudWorkspaceIcon } from '@blocksuite/icons';
import { Avatar } from '@toeverything/components/avatar';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useCurrentLoginStatus } from '../../hooks/affine/use-current-login-status';
import { useCurrentUser } from '../../hooks/affine/use-current-user';
@ -10,16 +11,16 @@ import { StyledSignInButton } from '../pure/footer/styles';
export const LoginCard = () => {
const t = useAFFiNEI18N();
const loginStatus = useCurrentLoginStatus();
const onSignInClick = useAsyncCallback(async () => {
await signInCloud();
}, []);
if (loginStatus === 'authenticated') {
return <UserCard />;
}
return (
<StyledSignInButton
data-testid="sign-in-button"
onClick={async () => {
signInCloud().catch(console.error);
}}
>
<StyledSignInButton data-testid="sign-in-button" onClick={onSignInClick}>
<div className="circle">
<CloudWorkspaceIcon />
</div>{' '}

View File

@ -24,7 +24,7 @@ import {
PreconditionStrategy,
} from '@toeverything/infra/command';
import { atom, useAtomValue } from 'jotai';
import groupBy from 'lodash/groupBy';
import { groupBy } from 'lodash-es';
import { useCallback, useMemo } from 'react';
import {

View File

@ -2,6 +2,7 @@ import { Command } from '@affine/cmdk';
import { formatDate } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { PageMeta } from '@blocksuite/store';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import type { CommandCategory } from '@toeverything/infra/command';
import clsx from 'clsx';
import { useAtom } from 'jotai';
@ -55,6 +56,19 @@ const QuickSearchGroup = ({
const t = useAFFiNEI18N();
const i18nkey = categoryToI18nKey[category];
const [query, setQuery] = useAtom(cmdkQueryAtom);
const onCommendSelect = useAsyncCallback(
async (command: CMDKCommand) => {
try {
await command.run();
} finally {
setQuery('');
onOpenChange?.(false);
}
},
[setQuery, onOpenChange]
);
return (
<Command.Group key={category} heading={t[i18nkey]()}>
{commands.map(command => {
@ -67,11 +81,7 @@ const QuickSearchGroup = ({
return (
<Command.Item
key={command.id}
onSelect={() => {
command.run();
setQuery('');
onOpenChange?.(false);
}}
onSelect={() => onCommendSelect(command)}
value={command.value}
data-is-danger={
command.id === 'editor:page-move-to-trash' ||

View File

@ -18,8 +18,9 @@ import type { DragEndEvent } from '@dnd-kit/core';
import { useDroppable } from '@dnd-kit/core';
import * as Collapsible from '@radix-ui/react-collapsible';
import { IconButton } from '@toeverything/components/button';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useCallback, useMemo, useState } from 'react';
import { useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { collectionsCRUDAtom } from '../../../../atoms/collections';
@ -80,9 +81,9 @@ const CollectionRenderer = ({
() => new Set(collection.allowList),
[collection.allowList]
);
const removeFromAllowList = useCallback(
(id: string) => {
return setting.updateCollection({
const removeFromAllowList = useAsyncCallback(
async (id: string) => {
await setting.updateCollection({
...collection,
allowList: collection.allowList?.filter(v => v != id),
});

View File

@ -1,8 +1,8 @@
import { PlusIcon } from '@blocksuite/icons';
import type { Workspace } from '@blocksuite/store';
import { IconButton } from '@toeverything/components/button';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
import { useCallback } from 'react';
import { usePageHelper } from '../../../blocksuite/block-suite-page-list/utils';
@ -13,7 +13,7 @@ type AddFavouriteButtonProps = {
export const AddFavouriteButton = ({ workspace }: AddFavouriteButtonProps) => {
const { createPage } = usePageHelper(workspace);
const { setPageMeta } = usePageMetaHelper(workspace);
const handleAddFavorite = useCallback(async () => {
const handleAddFavorite = useAsyncCallback(async () => {
const page = createPage();
await page.waitForLoaded();
setPageMeta(page.id, { favorite: true });

View File

@ -25,7 +25,7 @@ const SignInItem = () => {
const t = useAFFiNEI18N();
const onClickSignIn = useCallback(async () => {
const onClickSignIn = useCallback(() => {
if (!runtimeConfig.enableCloud) {
setDisableCloudOpen(true);
} else {
@ -76,7 +76,7 @@ export const UserWithWorkspaceList = ({
onEventEnd?.();
}, [onEventEnd, setOpenCreateWorkspaceModal]);
const onAddWorkspace = useCallback(async () => {
const onAddWorkspace = useCallback(() => {
setOpenCreateWorkspaceModal('add');
onEventEnd?.();
}, [onEventEnd, setOpenCreateWorkspaceModal]);

View File

@ -193,7 +193,7 @@ export const AFFiNEWorkspaceList = ({
onEventEnd?.();
}, [onEventEnd, setOpenCreateWorkspaceModal]);
const onAddWorkspace = useCallback(async () => {
const onAddWorkspace = useCallback(() => {
setOpenCreateWorkspaceModal('add');
onEventEnd?.();
}, [onEventEnd, setOpenCreateWorkspaceModal]);

View File

@ -18,6 +18,7 @@ import { FolderIcon, SettingsIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import { useDroppable } from '@dnd-kit/core';
import { Menu } from '@toeverything/components/menu';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtom, useAtomValue } from 'jotai';
import type { HTMLAttributes, ReactElement } from 'react';
import { forwardRef, useCallback, useEffect, useMemo } from 'react';
@ -107,7 +108,7 @@ export const RootAppSidebar = ({
);
const generalShortcutsInfo = useGeneralShortcuts();
const onClickNewPage = useCallback(async () => {
const onClickNewPage = useAsyncCallback(async () => {
const page = createPage();
await page.waitForLoaded();
openPage(page.id);

View File

@ -5,7 +5,7 @@ import {
useCollectionManager,
} from '@affine/component/page-list';
import { Unreachable } from '@affine/env/constant';
import type { Collection } from '@affine/env/filter';
import type { Collection, Filter } from '@affine/env/filter';
import type {
WorkspaceFlavour,
WorkspaceHeaderProps,
@ -13,6 +13,7 @@ import type {
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { DeleteIcon } from '@blocksuite/icons';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useSetAtom } from 'jotai/react';
import { useCallback } from 'react';
@ -44,6 +45,17 @@ const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
},
[setting, navigateHelper, workspaceId]
);
const onFilterChange = useAsyncCallback(
async (filterList: Filter[]) => {
await setting.updateCollection({
...setting.currentCollection,
filterList,
});
},
[setting]
);
if (!setting.isDefault || !setting.currentCollection.filterList.length) {
return null;
}
@ -54,12 +66,7 @@ const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
<FilterList
propertiesMeta={currentWorkspace.blockSuiteWorkspace.meta.properties}
value={setting.currentCollection.filterList}
onChange={filterList => {
return setting.updateCollection({
...setting.currentCollection,
filterList,
});
}}
onChange={onFilterChange}
/>
</div>
<div>

View File

@ -1,5 +1,6 @@
import { LOCALES, useI18N } from '@affine/i18n';
import { useCallback, useMemo } from 'react';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useMemo } from 'react';
export function useLanguageHelper() {
const i18n = useI18N();
@ -16,9 +17,9 @@ export function useLanguageHelper() {
})),
[]
);
const onLanguageChange = useCallback(
(event: string) => {
i18n.changeLanguage(event);
const onLanguageChange = useAsyncCallback(
async (event: string) => {
await i18n.changeLanguage(event);
},
[i18n]
);

View File

@ -117,8 +117,8 @@ export function useRegisterBlocksuiteEditorCommands(
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['Export to PDF'](),
run() {
exportHandler('pdf');
async run() {
await exportHandler('pdf');
},
})
);
@ -130,8 +130,8 @@ export function useRegisterBlocksuiteEditorCommands(
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['Export to HTML'](),
run() {
exportHandler('html');
async run() {
await exportHandler('html');
},
})
);
@ -143,8 +143,8 @@ export function useRegisterBlocksuiteEditorCommands(
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['Export to PNG'](),
run() {
exportHandler('png');
async run() {
await exportHandler('png');
},
})
);
@ -156,8 +156,8 @@ export function useRegisterBlocksuiteEditorCommands(
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['Export to Markdown'](),
run() {
exportHandler('markdown');
async run() {
await exportHandler('markdown');
},
})
);

View File

@ -7,10 +7,10 @@ import {
rootWorkspacesMetadataAtom,
workspaceAdaptersAtom,
} from '@affine/workspace/atom';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { currentPageIdAtom } from '@toeverything/infra/atom';
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { openSettingModalAtom } from '../../atoms';
import { useNavigateHelper } from '../use-navigate-helper';
@ -24,7 +24,7 @@ export function useOnTransformWorkspace() {
const currentPageId = useAtomValue(currentPageIdAtom);
const pushNotification = useSetAtom(pushNotificationAtom);
return useCallback(
return useAsyncCallback(
async <From extends WorkspaceFlavour, To extends WorkspaceFlavour>(
from: From,
to: To,

View File

@ -1,6 +1,6 @@
import { type SubscriptionQuery, subscriptionQuery } from '@affine/graphql';
import { useQuery } from '@affine/workspace/affine/gql';
import { useCallback } from 'react';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
export type Subscription = NonNullable<
NonNullable<SubscriptionQuery['currentUser']>['subscription']
@ -16,9 +16,9 @@ export const useUserSubscription = () => {
query: subscriptionQuery,
});
const set: SubscriptionMutator = useCallback(
(update?: Partial<Subscription>) => {
mutate(prev => {
const set: SubscriptionMutator = useAsyncCallback(
async (update?: Partial<Subscription>) => {
await mutate(prev => {
if (!update || !prev?.currentUser?.subscription) {
return;
}

View File

@ -121,6 +121,7 @@ type WorkspaceLayoutProps = {
function useLoadWorkspacePages() {
const [currentWorkspace] = useCurrentWorkspace();
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
useEffect(() => {
if (currentWorkspace) {
const timer = setTimeout(() => {
@ -129,7 +130,7 @@ function useLoadWorkspacePages() {
.map(id => currentWorkspace.blockSuiteWorkspace.getPage(id))
.filter((p): p is Page => !!p);
pages.forEach(page => {
loadPage(page, -10);
loadPage(page, -10).catch(e => console.error(e));
});
}, 10 * 1000); // load pages after 10s
return () => {

View File

@ -1,4 +1,5 @@
import { NotFoundPage } from '@affine/component/not-found-page';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useSession } from 'next-auth/react';
import type { ReactElement } from 'react';
@ -22,7 +23,7 @@ export const Component = (): ReactElement => {
setOpen(true);
}, [setOpen]);
const onConfirmSignOut = useCallback(async () => {
const onConfirmSignOut = useAsyncCallback(async () => {
setOpen(false);
await signOutCloud({
callbackUrl: '/signIn',

View File

@ -14,6 +14,7 @@ import {
PageIcon,
ViewLayersIcon,
} from '@blocksuite/icons';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { getCurrentStore } from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai';
import { useSetAtom } from 'jotai';
@ -92,11 +93,13 @@ export const Component = function CollectionPage() {
const Placeholder = ({ collection }: { collection: Collection }) => {
const { updateCollection } = useCollectionManager(collectionsCRUDAtom);
const { node, open } = useEditCollection(useAllPageListConfig());
const openPageEdit = useCallback(() => {
open({ ...collection }, 'page').then(updateCollection);
const openPageEdit = useAsyncCallback(async () => {
const ret = await open({ ...collection }, 'page');
await updateCollection(ret);
}, [open, collection, updateCollection]);
const openRuleEdit = useCallback(() => {
open({ ...collection }, 'rule').then(updateCollection);
const openRuleEdit = useAsyncCallback(async () => {
const ret = await open({ ...collection }, 'rule');
await updateCollection(ret);
}, [collection, open, updateCollection]);
const [showTips, setShowTips] = useState(false);
useEffect(() => {

View File

@ -1,5 +1,6 @@
import { WorkspaceSubPath } from '@affine/env/workspace';
import { assertExists } from '@blocksuite/global/utils';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtom } from 'jotai';
import type { ReactElement } from 'react';
import { lazy, Suspense, useCallback } from 'react';
@ -154,13 +155,10 @@ export const SignOutConfirmModal = () => {
const { jumpToIndex } = useNavigateHelper();
const [open, setOpen] = useAtom(openSignOutModalAtom);
const onConfirm = useCallback(async () => {
const onConfirm = useAsyncCallback(async () => {
setOpen(false);
signOutCloud()
.then(() => {
jumpToIndex();
})
.catch(console.error);
await signOutCloud();
jumpToIndex();
}, [jumpToIndex, setOpen]);
return (

View File

@ -3,6 +3,7 @@ import '@toeverything/hooks/use-affine-ipc-renderer';
import { pushNotificationAtom } from '@affine/component/notification-center';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { refreshRootMetadataAtom } from '@affine/workspace/atom';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtom, useSetAtom } from 'jotai';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { SessionProvider, useSession } from 'next-auth/react';
@ -24,6 +25,12 @@ const SessionDefence = (props: PropsWithChildren) => {
const refreshMetadata = useSetAtom(refreshRootMetadataAtom);
const onceSignedInEvents = useOnceSignedInEvents();
const t = useAFFiNEI18N();
const refreshAfterSignedInEvents = useAsyncCallback(async () => {
await onceSignedInEvents();
refreshMetadata();
}, [onceSignedInEvents, refreshMetadata]);
useEffect(() => {
if (sessionInAtom !== session && session.status === 'authenticated') {
setSession(session);
@ -35,11 +42,7 @@ const SessionDefence = (props: PropsWithChildren) => {
prevSession.current?.status === 'unauthenticated' &&
session.status === 'authenticated'
) {
startTransition(() => {
onceSignedInEvents().then(() => {
refreshMetadata();
});
});
startTransition(() => refreshAfterSignedInEvents());
pushNotification({
title: t['com.affine.auth.has.signed'](),
message: t['com.affine.auth.has.signed.message'](),
@ -57,9 +60,8 @@ const SessionDefence = (props: PropsWithChildren) => {
sessionInAtom,
prevSession,
setSession,
onceSignedInEvents,
pushNotification,
refreshMetadata,
refreshAfterSignedInEvents,
t,
]);
return props.children;

View File

@ -15,6 +15,7 @@
"@graphql-codegen/typescript": "^4.0.1",
"@graphql-codegen/typescript-operations": "^4.0.1",
"@types/lodash-es": "^4.17.9",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"prettier": "^3.0.3",
"vitest": "0.34.6"

View File

@ -0,0 +1,27 @@
import React from 'react';
export type AsyncErrorHandler = (error: Error) => void;
/**
* App should provide a global error handler for async callback in the root.
*/
export const AsyncCallbackContext = React.createContext<AsyncErrorHandler>(e =>
console.error(e)
);
/**
* Translate async function to sync function and handle error automatically.
* Only accept void function, return data here is meaningless.
*/
export function useAsyncCallback<T extends any[]>(
callback: (...args: T) => Promise<void>,
deps: any[]
): (...args: T) => void {
const handleAsyncError = React.useContext(AsyncCallbackContext);
return React.useCallback(
(...args: any) => {
callback(...args).catch(e => handleAsyncError(e));
},
[...deps] // eslint-disable-line react-hooks/exhaustive-deps
);
}

View File

@ -178,7 +178,7 @@ __metadata:
languageName: unknown
linkType: soft
"@affine/cmdk@workspace:packages/common/cmdk":
"@affine/cmdk@workspace:*, @affine/cmdk@workspace:packages/common/cmdk":
version: 0.0.0-use.local
resolution: "@affine/cmdk@workspace:packages/common/cmdk"
dependencies:
@ -242,7 +242,6 @@ __metadata:
jotai-effect: "npm:^0.2.2"
jotai-scope: "npm:^0.4.0"
lit: "npm:^3.0.2"
lodash: "npm:^4.17.21"
lodash-es: "npm:^4.17.21"
lottie-react: "npm:^2.4.0"
lottie-web: "npm:^5.12.2"
@ -304,6 +303,7 @@ __metadata:
resolution: "@affine/core@workspace:packages/frontend/core"
dependencies:
"@affine-test/fixtures": "workspace:*"
"@affine/cmdk": "workspace:*"
"@affine/component": "workspace:*"
"@affine/debug": "workspace:*"
"@affine/env": "workspace:*"
@ -319,6 +319,7 @@ __metadata:
"@blocksuite/icons": "npm:2.1.35"
"@blocksuite/lit": "npm:0.0.0-20231110042432-4fdac4dc-nightly"
"@blocksuite/store": "npm:0.0.0-20231110042432-4fdac4dc-nightly"
"@blocksuite/virgo": "npm:0.0.0-20231110042432-4fdac4dc-nightly"
"@dnd-kit/core": "npm:^6.0.8"
"@dnd-kit/sortable": "npm:^7.0.2"
"@emotion/cache": "npm:^11.11.0"
@ -329,24 +330,37 @@ __metadata:
"@mui/material": "npm:^5.14.14"
"@perfsee/webpack": "npm:^1.8.4"
"@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.11"
"@radix-ui/react-collapsible": "npm:^1.0.3"
"@radix-ui/react-dialog": "npm:^1.0.4"
"@radix-ui/react-scroll-area": "npm:^1.0.5"
"@radix-ui/react-select": "npm:^2.0.0"
"@react-hookz/web": "npm:^23.1.0"
"@sentry/webpack-plugin": "npm:^2.8.0"
"@svgr/webpack": "npm:^8.1.0"
"@swc/core": "npm:^1.3.93"
"@testing-library/react": "npm:^14.0.0"
"@toeverything/components": "npm:^0.0.46"
"@toeverything/theme": "npm:^0.7.20"
"@types/bytes": "npm:^3.1.3"
"@types/lodash-es": "npm:^4.17.9"
"@types/uuid": "npm:^9.0.5"
"@types/webpack-env": "npm:^1.18.2"
"@vanilla-extract/css": "npm:^1.13.0"
"@vanilla-extract/dynamic": "npm:^2.0.3"
async-call-rpc: "npm:^6.3.1"
bytes: "npm:^3.1.2"
clsx: "npm:^2.0.0"
copy-webpack-plugin: "npm:^11.0.0"
css-loader: "npm:^6.8.1"
css-spring: "npm:^4.1.0"
cssnano: "npm:^6.0.1"
dayjs: "npm:^1.11.10"
express: "npm:^4.18.2"
fake-indexeddb: "npm:^5.0.0"
foxact: "npm:^0.2.20"
graphql: "npm:^16.8.1"
html-webpack-plugin: "npm:^5.5.3"
idb: "npm:^7.1.1"
intl-segmenter-polyfill-rs: "npm:^0.1.6"
jotai: "npm:^2.4.3"
jotai-devtools: "npm:^0.7.0"
@ -355,12 +369,14 @@ __metadata:
lottie-web: "npm:^5.12.2"
mime-types: "npm:^2.1.35"
mini-css-extract-plugin: "npm:^2.7.6"
nanoid: "npm:^5.0.1"
next-auth: "npm:^4.23.2"
next-themes: "npm:^0.2.1"
postcss-loader: "npm:^7.3.3"
raw-loader: "npm:^4.0.2"
react: "npm:18.2.0"
react-dom: "npm:18.2.0"
react-error-boundary: "npm:^4.0.11"
react-is: "npm:18.2.0"
react-resizable-panels: "npm:^0.0.55"
react-router-dom: "npm:^6.16.0"
@ -372,7 +388,9 @@ __metadata:
swc-plugin-coverage-instrument: "npm:^0.0.20"
swr: "npm:2.2.4"
thread-loader: "npm:^4.0.2"
uuid: "npm:^9.0.1"
valtio: "npm:^1.11.2"
vitest: "npm:0.34.6"
webpack: "npm:^5.89.0"
webpack-cli: "npm:^5.1.4"
webpack-dev-server: "npm:^4.15.1"
@ -482,6 +500,7 @@ __metadata:
"@graphql-codegen/typescript-operations": "npm:^4.0.1"
"@types/lodash-es": "npm:^4.17.9"
graphql: "npm:^16.8.1"
lodash: "npm:^4.17.21"
lodash-es: "npm:^4.17.21"
nanoid: "npm:^5.0.1"
prettier: "npm:^3.0.3"
@ -12679,7 +12698,7 @@ __metadata:
languageName: unknown
linkType: soft
"@toeverything/theme@npm:^0.7.21, @toeverything/theme@npm:^0.7.24":
"@toeverything/theme@npm:^0.7.20, @toeverything/theme@npm:^0.7.21, @toeverything/theme@npm:^0.7.24":
version: 0.7.24
resolution: "@toeverything/theme@npm:0.7.24"
checksum: faa97dad2a411e895090497ff6cbb83836e9be963e608cbc7f3421c4a933d86393551250fa015d4b9060778f0abb0e122a41d12a70e6f7fb7c9eadc2324a6035