refactor(core): adjust core struct (#8218)

packages/frontend/core/src

hooks -> components/hooks
atoms -> components/atoms
layouts -> components/layouts
providers -> components/providers
mixpanel -> @affine/track
~~shared~~
~~unexpected-application-state~~
This commit is contained in:
EYHN
2024-09-13 11:31:21 +00:00
parent fc7e7a37ee
commit 5e56ec65e3
274 changed files with 552 additions and 902 deletions
@@ -10,17 +10,12 @@ import { DumpInfo } from './error-basic/info-logger';
import { AnyErrorFallback } from './error-fallbacks/any-error-fallback';
import { NoPageRootFallback } from './error-fallbacks/no-page-root-fallback';
import { PageNotFoundDetail } from './error-fallbacks/page-not-found-fallback';
import { RecoverableErrorFallback } from './error-fallbacks/recoverable-error-fallback';
/**
* Register all fallback components here.
* If have new one just add it to the set.
*/
const fallbacks = new Set([
PageNotFoundDetail,
RecoverableErrorFallback,
NoPageRootFallback,
]);
const fallbacks = new Set([PageNotFoundDetail, NoPageRootFallback]);
function getErrorFallbackComponent(error: any): FC<FallbackProps> {
for (const Component of fallbacks) {
@@ -1,5 +1,5 @@
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { Trans, useI18n } from '@affine/i18n';
import { useTheme } from 'next-themes';
import type { FC, PropsWithChildren, ReactNode } from 'react';
@@ -5,7 +5,7 @@ import { useCallback } from 'react';
import {
RouteLogic,
useNavigateHelper,
} from '../../../../hooks/use-navigate-helper';
} from '../../../../components/hooks/use-navigate-helper';
import { ErrorDetail, ErrorStatus } from '../error-basic/error-detail';
import { createErrorFallback } from '../error-basic/fallback-creator';
@@ -1,41 +0,0 @@
import { useI18n } from '@affine/i18n';
import { useCallback, useMemo, useState } from 'react';
import { RecoverableError } from '../../../../unexpected-application-state/errors';
import { ContactUS, ErrorDetail } from '../error-basic/error-detail';
import { createErrorFallback } from '../error-basic/fallback-creator';
export const RecoverableErrorFallback = createErrorFallback(
RecoverableError,
props => {
const { error, resetError } = props;
const t = useI18n();
const [count, rerender] = useState(0);
const canRetry = error.canRetry();
const buttonDesc = useMemo(() => {
if (canRetry) {
return t['com.affine.error.refetch']();
}
return t['com.affine.error.reload']();
}, [canRetry, t]);
const onRetry = useCallback(async () => {
if (canRetry) {
rerender(count + 1);
await error.retry();
} else {
document.location.reload();
}
}, [error, count, canRetry]);
return (
<ErrorDetail
title={t['com.affine.error.unexpected-error.title']()}
resetError={resetError}
buttonText={buttonDesc}
onButtonClick={onRetry}
description={[error.message, <ContactUS key="contact-us" />]}
/>
);
}
);
@@ -1,9 +1,9 @@
import { Button, FlexWrapper, notify } from '@affine/component';
import { openSettingModalAtom } from '@affine/core/atoms';
import { track } from '@affine/core/mixpanel';
import { openSettingModalAtom } from '@affine/core/components/atoms';
import { SubscriptionService } from '@affine/core/modules/cloud';
import { EditorService } from '@affine/core/modules/editor';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { AiIcon } from '@blocksuite/icons/rc';
import { useLiveData, useServices } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
@@ -1,9 +1,9 @@
import { Button, IconButton, Modal } from '@affine/component';
import { openSettingModalAtom } from '@affine/core/atoms';
import { useBlurRoot } from '@affine/core/hooks/use-blur-root';
import { track } from '@affine/core/mixpanel';
import { openSettingModalAtom } from '@affine/core/components/atoms';
import { useBlurRoot } from '@affine/core/components/hooks/use-blur-root';
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { Trans, useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
import { useLiveData, useServices } from '@toeverything/infra';
import { useAtom } from 'jotai';
@@ -2,7 +2,7 @@ import { Button, notify } from '@affine/component';
import {
RouteLogic,
useNavigateHelper,
} from '@affine/core/hooks/use-navigate-helper';
} from '@affine/core/components/hooks/use-navigate-helper';
import { AuthService } from '@affine/core/modules/cloud';
import { useI18n } from '@affine/i18n';
import { AiIcon } from '@blocksuite/icons/rc';
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import type { PropsWithChildren, ReactElement } from 'react';
import { useAppSettingHelper } from '../../hooks/affine/use-app-setting-helper';
import { useAppSettingHelper } from '../../components/hooks/affine/use-app-setting-helper';
import { AppSidebarFallback, ShellAppSidebarFallback } from '../app-sidebar';
import type { WorkspaceRootProps } from '../workspace';
import {
@@ -6,7 +6,7 @@ import {
ModalHeader,
} from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { AuthService } from '@affine/core/modules/cloud';
import { Trans, useI18n } from '@affine/i18n';
import { useService } from '@toeverything/infra';
@@ -6,7 +6,7 @@ import {
ModalHeader,
} from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { Trans, useI18n } from '@affine/i18n';
import { useService } from '@toeverything/infra';
import type { FC } from 'react';
@@ -1,5 +1,5 @@
import { useConfirmModal } from '@affine/component';
import { authAtom } from '@affine/core/atoms';
import { authAtom } from '@affine/core/components/atoms';
import { useI18n } from '@affine/i18n';
import { atom, useAtom, useSetAtom } from 'jotai';
import { useCallback, useEffect } from 'react';
@@ -1,7 +1,7 @@
import { notify } from '@affine/component';
import { AuthModal as AuthModalBase } from '@affine/component/auth-components';
import { authAtom, type AuthAtomData } from '@affine/core/atoms';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { authAtom, type AuthAtomData } from '@affine/core/components/atoms';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { AuthService } from '@affine/core/modules/cloud';
import { apis, events } from '@affine/electron-api';
import { useI18n } from '@affine/i18n';
@@ -6,7 +6,7 @@ import {
ModalHeader,
} from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import {
sendChangeEmailMutation,
sendChangePasswordEmailMutation,
@@ -17,7 +17,7 @@ import { useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useState } from 'react';
import { useMutation } from '../../../hooks/use-mutation';
import { useMutation } from '../../../components/hooks/use-mutation';
import { ServerConfigService } from '../../../modules/cloud';
import type { AuthPanelProps } from './index';
@@ -5,7 +5,7 @@ import {
ModalHeader,
} from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { AuthService } from '@affine/core/modules/cloud';
import { useI18n } from '@affine/i18n';
import { useService } from '@toeverything/infra';
@@ -1,7 +1,7 @@
import { notify } from '@affine/component';
import { AuthInput, ModalHeader } from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { Trans, useI18n } from '@affine/i18n';
import { ArrowRightBigIcon } from '@blocksuite/icons/rc';
import { useService } from '@toeverything/infra';
@@ -1,16 +1,16 @@
import { Tooltip } from '@affine/component/ui/tooltip';
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { useLiveData, useServices } from '@toeverything/infra';
import { useSetAtom } from 'jotai';
import { useEffect } from 'react';
import { openSettingModalAtom } from '../../../atoms';
import {
ServerConfigService,
SubscriptionService,
} from '../../../modules/cloud';
import { openSettingModalAtom } from '../../atoms';
import * as styles from './style.css';
export const UserPlanButton = () => {
@@ -1,13 +1,13 @@
import { Avatar, Input, Switch, toast } from '@affine/component';
import type { ConfirmModalProps } from '@affine/component/ui/modal';
import { ConfirmModal, Modal } from '@affine/component/ui/modal';
import { authAtom } from '@affine/core/atoms';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
import { authAtom } from '@affine/core/components/atoms';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { DebugLogger } from '@affine/debug';
import { apis } from '@affine/electron-api';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import {
DocsService,
useLiveData,
@@ -1,4 +1,4 @@
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { CollectionService } from '@affine/core/modules/collection';
import type { Collection } from '@affine/env/filter';
import { useI18n } from '@affine/i18n';
@@ -1,4 +1,4 @@
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
import { CollectionService } from '@affine/core/modules/collection';
import { useI18n } from '@affine/i18n';
import { ViewLayersIcon } from '@blocksuite/icons/rc';
@@ -1,6 +1,6 @@
import { OverlayModal } from '@affine/component';
import { openHistoryTipsModalAtom } from '@affine/core/atoms';
import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { openHistoryTipsModalAtom } from '@affine/core/components/atoms';
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { useI18n } from '@affine/i18n';
import { useService, WorkspaceService } from '@toeverything/infra';
import { useAtom } from 'jotai';
@@ -1,5 +1,5 @@
import { OverlayModal } from '@affine/component';
import { openIssueFeedbackModalAtom } from '@affine/core/atoms';
import { openIssueFeedbackModalAtom } from '@affine/core/components/atoms';
import { useI18n } from '@affine/i18n';
import { useAtom } from 'jotai';
@@ -3,7 +3,7 @@ import { DoneIcon } from '@blocksuite/icons/rc';
import type { ReactElement } from 'react';
import { memo } from 'react';
import { useLanguageHelper } from '../../../hooks/affine/use-language-helper';
import { useLanguageHelper } from '../../../components/hooks/affine/use-language-helper';
import * as styles from './style.css';
// Fixme: keyboard focus should be supported by Menu component
@@ -1,5 +1,5 @@
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useDocCollectionPage } from '@affine/core/hooks/use-block-suite-workspace-page';
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
import { useDocCollectionPage } from '@affine/core/components/hooks/use-block-suite-workspace-page';
import { DebugLogger } from '@affine/debug';
import type { ListHistoryQuery } from '@affine/graphql';
import { listHistoryQuery, recoverDocMutation } from '@affine/graphql';
@@ -20,8 +20,8 @@ import {
import {
useMutateQueryResource,
useMutation,
} from '../../../hooks/use-mutation';
import { useQueryInfinite } from '../../../hooks/use-query';
} from '../../../components/hooks/use-mutation';
import { useQueryInfinite } from '../../../components/hooks/use-query';
import { CloudBlobStorage } from '../../../modules/workspace-engine/impls/engine/blob-cloud';
const logger = new DebugLogger('page-history');
@@ -2,13 +2,13 @@ import { Loading, Scrollable } from '@affine/component';
import { EditorLoading } from '@affine/component/page-detail-skeleton';
import { Button, IconButton } from '@affine/component/ui/button';
import { Modal, useConfirmModal } from '@affine/component/ui/modal';
import { openSettingModalAtom } from '@affine/core/atoms';
import { useDocCollectionPageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title';
import { track } from '@affine/core/mixpanel';
import { openSettingModalAtom } from '@affine/core/components/atoms';
import { useDocCollectionPageTitle } from '@affine/core/components/hooks/use-block-suite-workspace-page-title';
import { EditorService } from '@affine/core/modules/editor';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { i18nTime, Trans, useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { DocMode } from '@blocksuite/blocks';
import { CloseIcon, ToggleCollapseIcon } from '@blocksuite/icons/rc';
import type { Doc as BlockSuiteDoc, DocCollection } from '@blocksuite/store';
@@ -28,7 +28,7 @@ import {
} from 'react';
import { encodeStateAsUpdate } from 'yjs';
import { pageHistoryModalAtom } from '../../../atoms/page-history';
import { pageHistoryModalAtom } from '../../atoms/page-history';
import { BlockSuiteEditor } from '../../blocksuite/block-suite-editor';
import { PureEditorModeSwitch } from '../../blocksuite/block-suite-mode-switch';
import { AffineErrorBoundary } from '../affine-error-boundary';
@@ -1,7 +1,6 @@
import type { MenuProps } from '@affine/component';
import { Button, IconButton, Menu, MenuItem, Tooltip } from '@affine/component';
import { useCurrentWorkspacePropertiesAdapter } from '@affine/core/hooks/use-affine-adapter';
import { track } from '@affine/core/mixpanel';
import { useCurrentWorkspacePropertiesAdapter } from '@affine/core/components/hooks/use-affine-adapter';
import { DocLinksService } from '@affine/core/modules/doc-link';
import { EditorSettingService } from '@affine/core/modules/editor-settting';
import type {
@@ -10,6 +9,7 @@ import type {
PagePropertyType,
} from '@affine/core/modules/properties/services/schema';
import { i18nTime, useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { assertExists } from '@blocksuite/global/utils';
import {
ArrowDownSmallIcon,
@@ -1,6 +1,6 @@
import type { MenuProps } from '@affine/component';
import { IconButton, Input, Menu, Scrollable } from '@affine/component';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
import { WorkspaceLegacyProperties } from '@affine/core/modules/properties';
import type { Tag } from '@affine/core/modules/tag';
import { DeleteTagConfirmModal, TagService } from '@affine/core/modules/tag';
@@ -1,10 +1,13 @@
import { ConfirmModal } from '@affine/component/ui/modal';
import { openQuotaModalAtom, openSettingModalAtom } from '@affine/core/atoms';
import { track } from '@affine/core/mixpanel';
import {
openQuotaModalAtom,
openSettingModalAtom,
} from '@affine/core/components/atoms';
import { UserQuotaService } from '@affine/core/modules/cloud';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
import bytes from 'bytes';
import { useAtom, useSetAtom } from 'jotai';
@@ -1,5 +1,5 @@
import { ConfirmModal } from '@affine/component/ui/modal';
import { openQuotaModalAtom } from '@affine/core/atoms';
import { openQuotaModalAtom } from '@affine/core/components/atoms';
import { useI18n } from '@affine/i18n';
import { useService, WorkspaceService } from '@toeverything/infra';
import { useAtom } from 'jotai';
@@ -1,12 +1,12 @@
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useJournalHelper } from '@affine/core/hooks/use-journal';
import { track } from '@affine/core/mixpanel';
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
import { useJournalHelper } from '@affine/core/components/hooks/use-journal';
import {
PeekViewService,
useInsidePeekView,
} from '@affine/core/modules/peek-view';
import { WorkbenchLink } from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { DocMode } from '@blocksuite/blocks';
import {
BlockLinkIcon,
@@ -1,7 +1,6 @@
import { Button, ErrorMessage, Skeleton } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { openSettingModalAtom } from '@affine/core/atoms';
import { track } from '@affine/core/mixpanel';
import { openSettingModalAtom } from '@affine/core/components/atoms';
import {
ServerConfigService,
SubscriptionService,
@@ -9,6 +8,7 @@ import {
} from '@affine/core/modules/cloud';
import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import { useSetAtom } from 'jotai';
@@ -5,11 +5,11 @@ import {
} from '@affine/component/setting-components';
import { Avatar } from '@affine/component/ui/avatar';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
import { track } from '@affine/core/mixpanel';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons/rc';
import {
useEnsureLiveData,
@@ -21,12 +21,12 @@ import { useSetAtom } from 'jotai';
import type { FC } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { AuthService, ServerConfigService } from '../../../../modules/cloud';
import {
authAtom,
openSettingModalAtom,
openSignOutModalAtom,
} from '../../../../atoms';
import { AuthService, ServerConfigService } from '../../../../modules/cloud';
} from '../../../atoms';
import { Upload } from '../../../pure/file-upload';
import { AIUsagePanel } from './ai-usage-panel';
import { StorageProgress } from './storage-progress';
@@ -4,13 +4,13 @@ import {
SettingRow,
SettingWrapper,
} from '@affine/component/setting-components';
import { useAppUpdater } from '@affine/core/hooks/use-app-updater';
import { mixpanel } from '@affine/core/mixpanel';
import { useAppUpdater } from '@affine/core/components/hooks/use-app-updater';
import { useI18n } from '@affine/i18n';
import { mixpanel } from '@affine/track';
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons/rc';
import { useCallback } from 'react';
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
import { useAppSettingHelper } from '../../../../../components/hooks/affine/use-app-setting-helper';
import { appIconMap, appNames } from '../../../../../pages/open-app';
import { popupWindow } from '../../../../../utils';
import { relatedLinks } from './config';
@@ -1,8 +1,8 @@
import { Loading } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAppUpdater } from '@affine/core/hooks/use-app-updater';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useAppUpdater } from '@affine/core/components/hooks/use-app-updater';
import { useI18n } from '@affine/i18n';
import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react';
@@ -4,7 +4,7 @@ import { dateFormatOptions } from '@toeverything/infra';
import dayjs from 'dayjs';
import { useCallback } from 'react';
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
import { useAppSettingHelper } from '../../../../../components/hooks/affine/use-app-setting-helper';
interface DateFormatMenuContentProps {
currentOption: DateFormats;
@@ -11,7 +11,7 @@ import { windowFrameStyleOptions } from '@toeverything/infra';
import { useTheme } from 'next-themes';
import { useCallback, useMemo } from 'react';
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
import { useAppSettingHelper } from '../../../../../components/hooks/affine/use-app-setting-helper';
import { LanguageMenu } from '../../../language-menu';
import { Page } from '../editor/page';
import { DateFormatSetting } from './date-format-setting';
@@ -7,9 +7,8 @@ import {
} from '@affine/component/setting-components';
import { Button, IconButton } from '@affine/component/ui/button';
import { Loading } from '@affine/component/ui/loading';
import { getUpgradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
import { getUpgradeQuestionnaireLink } from '@affine/core/components/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import {
AuthService,
InvoicesService,
@@ -25,18 +24,19 @@ import {
UserFriendlyError,
} from '@affine/graphql';
import { i18nTime, Trans, useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import { useSetAtom } from 'jotai';
import { useCallback, useEffect, useState } from 'react';
import { useMutation } from '../../../../../components/hooks/use-mutation';
import { popupWindow } from '../../../../../utils';
import {
openSettingModalAtom,
type PlansScrollAnchor,
} from '../../../../../atoms';
import { useMutation } from '../../../../../hooks/use-mutation';
import { popupWindow } from '../../../../../utils';
} from '../../../../atoms';
import { CancelAction, ResumeAction } from '../plans/actions';
import { AICancel, AIResume, AISubscribe } from '../plans/ai/actions';
import { BelieverCard } from '../plans/lifetime/believer-card';
@@ -1,6 +1,6 @@
import { Button, Checkbox, Loading, Switch, Tooltip } from '@affine/component';
import { SettingHeader } from '@affine/component/setting-components';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useI18n } from '@affine/i18n';
import {
ArrowRightSmallIcon,
@@ -1,7 +1,7 @@
import { getDowngradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
import { getDowngradeQuestionnaireLink } from '@affine/core/components/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { SubscriptionPlan } from '@affine/graphql';
import { track } from '@affine/track';
import { useLiveData, useService } from '@toeverything/infra';
import { nanoid } from 'nanoid';
import type { PropsWithChildren } from 'react';
@@ -1,11 +1,11 @@
import { Button, type ButtonProps, useConfirmModal } from '@affine/component';
import { useDowngradeNotify } from '@affine/core/components/affine/subscription-landing/notify';
import { getDowngradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
import { getDowngradeQuestionnaireLink } from '@affine/core/components/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { useService } from '@toeverything/infra';
import { nanoid } from 'nanoid';
import { useState } from 'react';
@@ -1,5 +1,5 @@
import { Button, type ButtonProps } from '@affine/component';
import { authAtom } from '@affine/core/atoms';
import { authAtom } from '@affine/core/components/atoms';
import { useI18n } from '@affine/i18n';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
@@ -4,11 +4,11 @@ import {
notify,
useConfirmModal,
} from '@affine/component';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { SubscriptionService } from '@affine/core/modules/cloud';
import { SubscriptionPlan } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { SingleSelectSelectSolidIcon } from '@blocksuite/icons/rc';
import { useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
@@ -1,11 +1,11 @@
import { Button, type ButtonProps, Skeleton } from '@affine/component';
import { generateSubscriptionCallbackLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
import { generateSubscriptionCallbackLink } from '@affine/core/components/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { popupWindow } from '@affine/core/utils';
import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { useLiveData, useService } from '@toeverything/infra';
import { nanoid } from 'nanoid';
import { useEffect, useState } from 'react';
@@ -3,7 +3,7 @@ import { SettingHeader } from '@affine/component/setting-components';
import {
openSettingModalAtom,
type PlansScrollAnchor,
} from '@affine/core/atoms';
} from '@affine/core/components/atoms';
import { useI18n } from '@affine/i18n';
import { ArrowRightBigIcon, ArrowUpSmallIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
@@ -1,13 +1,13 @@
import { Button, type ButtonProps } from '@affine/component/ui/button';
import { Tooltip } from '@affine/component/ui/tooltip';
import { generateSubscriptionCallbackLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
import { generateSubscriptionCallbackLink } from '@affine/core/components/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { popupWindow } from '@affine/core/utils';
import type { SubscriptionRecurring } from '@affine/graphql';
import { SubscriptionPlan, SubscriptionStatus } from '@affine/graphql';
import { Trans, useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { DoneIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx';
@@ -16,7 +16,7 @@ import { nanoid } from 'nanoid';
import type { PropsWithChildren } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { authAtom } from '../../../../../atoms/index';
import { authAtom } from '../../../../atoms/index';
import { CancelAction, ResumeAction } from './actions';
import type { DynamicPrice, FixedPrice } from './cloud-plans';
import { ConfirmLoadingModal } from './modals';
@@ -4,13 +4,13 @@ import {
} from '@affine/component/setting-components';
import { useI18n } from '@affine/i18n';
import type { ShortcutsInfo } from '../../../../../hooks/affine/use-shortcuts';
import type { ShortcutsInfo } from '../../../../../components/hooks/affine/use-shortcuts';
import {
useEdgelessShortcuts,
useGeneralShortcuts,
useMarkdownShortcuts,
usePageShortcuts,
} from '../../../../../hooks/affine/use-shortcuts';
} from '../../../../../components/hooks/affine/use-shortcuts';
import { shortcutKey, shortcutKeyContainer, shortcutRow } from './style.css';
const ShortcutsPanel = ({
@@ -5,7 +5,7 @@ import { Modal } from '@affine/component/ui/modal';
import {
openIssueFeedbackModalAtom,
openStarAFFiNEModalAtom,
} from '@affine/core/atoms';
} from '@affine/core/components/atoms';
import { AuthService } from '@affine/core/modules/cloud';
import { Trans } from '@affine/i18n';
import { ContactWithUsIcon } from '@blocksuite/icons/rc';
@@ -5,12 +5,12 @@ import {
import { Avatar } from '@affine/component/ui/avatar';
import { Tooltip } from '@affine/component/ui/tooltip';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { track } from '@affine/core/mixpanel';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
import { AuthService } from '@affine/core/modules/cloud';
import { UserFeatureService } from '@affine/core/modules/cloud/services/user-feature';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { Logo1Icon } from '@blocksuite/icons/rc';
import type { WorkspaceMetadata } from '@toeverything/infra';
import {
@@ -30,7 +30,7 @@ import {
useMemo,
} from 'react';
import { authAtom } from '../../../../atoms';
import { authAtom } from '../../../atoms';
import { UserPlanButton } from '../../auth/user-plan-button';
import { useGeneralSettingList } from '../general-setting';
import type { ActiveTab, WorkspaceSubTab } from '../types';
@@ -1,7 +1,7 @@
import { Input } from '@affine/component';
import type { ConfirmModalProps } from '@affine/component/ui/modal';
import { ConfirmModal } from '@affine/component/ui/modal';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { Trans, useI18n } from '@affine/i18n';
@@ -1,7 +1,7 @@
import { notify } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { ConfirmModal } from '@affine/component/ui/modal';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { useI18n } from '@affine/i18n';
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
@@ -15,12 +15,11 @@ import {
import { useSetAtom } from 'jotai';
import { useCallback, useEffect, useState } from 'react';
import { openSettingModalAtom } from '../../../../../../atoms';
import {
RouteLogic,
useNavigateHelper,
} from '../../../../../../hooks/use-navigate-helper';
import { WorkspaceSubPath } from '../../../../../../shared';
} from '../../../../../../components/hooks/use-navigate-helper';
import { openSettingModalAtom } from '../../../../../atoms';
import { WorkspaceDeleteModal } from './delete';
export const DeleteLeaveWorkspace = () => {
@@ -37,7 +36,7 @@ export const DeleteLeaveWorkspace = () => {
});
const t = useI18n();
const workspace = workspaceService.workspace;
const { jumpToSubPath, jumpToIndex } = useNavigateHelper();
const { jumpToPage, jumpToIndex } = useNavigateHelper();
// fixme: cloud regression
const [showDelete, setShowDelete] = useState(false);
const [showLeave, setShowLeave] = useState(false);
@@ -72,11 +71,7 @@ export const DeleteLeaveWorkspace = () => {
);
// TODO(@eyhn): if there is no workspace, jump to a new page(wait for design)
if (backWorkspace) {
jumpToSubPath(
backWorkspace?.id || '',
WorkspaceSubPath.ALL,
RouteLogic.REPLACE
);
jumpToPage(backWorkspace?.id || '', 'all', RouteLogic.REPLACE);
} else {
jumpToIndex(RouteLogic.REPLACE);
}
@@ -96,7 +91,7 @@ export const DeleteLeaveWorkspace = () => {
isOwner,
t,
workspaceList,
jumpToSubPath,
jumpToPage,
jumpToIndex,
workspacesService,
workspacePermissionService,
@@ -1,6 +1,6 @@
import { SettingRow } from '@affine/component/setting-components';
import { Button } from '@affine/component/ui/button';
import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useI18n } from '@affine/i18n';
@@ -13,7 +13,7 @@ import {
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { openSettingModalAtom } from '../../../../../atoms';
import { openSettingModalAtom } from '../../../../atoms';
export interface PublishPanelProps {
workspace: Workspace | null;
@@ -1,8 +1,8 @@
import { notify } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useSystemOnline } from '@affine/core/hooks/use-system-online';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useSystemOnline } from '@affine/core/components/hooks/use-system-online';
import { apis } from '@affine/electron-api';
import { useI18n } from '@affine/i18n';
import type { Workspace, WorkspaceMetadata } from '@toeverything/infra';
@@ -3,8 +3,8 @@ import {
SettingRow,
SettingWrapper,
} from '@affine/component/setting-components';
import { useWorkspace } from '@affine/core/hooks/use-workspace';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { useWorkspace } from '@affine/core/components/hooks/use-workspace';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { useI18n } from '@affine/i18n';
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
@@ -11,11 +11,10 @@ import { Button, IconButton } from '@affine/component/ui/button';
import { Loading } from '@affine/component/ui/loading';
import { Menu, MenuItem } from '@affine/component/ui/menu';
import { Tooltip } from '@affine/component/ui/tooltip';
import { openSettingModalAtom } from '@affine/core/atoms';
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
import { useInviteMember } from '@affine/core/hooks/affine/use-invite-member';
import { useRevokeMemberPermission } from '@affine/core/hooks/affine/use-revoke-member-permission';
import { track } from '@affine/core/mixpanel';
import { openSettingModalAtom } from '@affine/core/components/atoms';
import { useInviteMember } from '@affine/core/components/hooks/affine/use-invite-member';
import { useRevokeMemberPermission } from '@affine/core/components/hooks/affine/use-revoke-member-permission';
import {
type Member,
WorkspaceMembersService,
@@ -25,6 +24,7 @@ import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { Permission, UserFriendlyError } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { MoreVerticalIcon } from '@blocksuite/icons/rc';
import {
useEnsureLiveData,
@@ -1,8 +1,8 @@
import { FlexWrapper, Input, notify, Wrapper } from '@affine/component';
import { Button } from '@affine/component/ui/button';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
import { Upload } from '@affine/core/components/pure/file-upload';
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { validateAndReduceImage } from '@affine/core/utils/reduce-image';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
@@ -3,7 +3,7 @@ import {
SettingRow,
SettingWrapper,
} from '@affine/component/setting-components';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { WorkspaceShareSettingService } from '@affine/core/modules/share-setting';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useI18n } from '@affine/i18n';
@@ -1,6 +1,6 @@
import { Button, IconButton, Menu } from '@affine/component';
import { SettingHeader } from '@affine/component/setting-components';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
import type { PageInfoCustomPropertyMeta } from '@affine/core/modules/properties/services/schema';
import { Trans, useI18n } from '@affine/i18n';
import {
@@ -20,8 +20,8 @@ import {
useState,
} from 'react';
import { useCurrentWorkspacePropertiesAdapter } from '../../../../../hooks/use-affine-adapter';
import { useWorkspace } from '../../../../../hooks/use-workspace';
import { useCurrentWorkspacePropertiesAdapter } from '../../../../../components/hooks/use-affine-adapter';
import { useWorkspace } from '../../../../../components/hooks/use-workspace';
import type { PagePropertyIcon } from '../../../page-properties';
import {
nameToIcon,
@@ -1,5 +1,5 @@
import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { track } from '@affine/core/mixpanel';
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { track } from '@affine/track';
import type { Doc } from '@blocksuite/store';
import { type Workspace } from '@toeverything/infra';
import { useCallback } from 'react';
@@ -1,8 +1,8 @@
import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page';
import {
ExportMenuItems,
PrintMenuItems,
} from '@affine/core/components/page-list';
import { useExportPage } from '@affine/core/hooks/affine/use-export-page';
import { EditorService } from '@affine/core/modules/editor';
import { useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra';
@@ -1,13 +1,12 @@
import { notify, Skeleton } from '@affine/component';
import { Button } from '@affine/component/ui/button';
import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu';
import { openSettingModalAtom } from '@affine/core/atoms';
import { openSettingModalAtom } from '@affine/core/components/atoms';
import {
getSelectedNodes,
useSharingUrl,
} from '@affine/core/hooks/affine/use-share-url';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
} from '@affine/core/components/hooks/affine/use-share-url';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { ServerConfigService } from '@affine/core/modules/cloud';
import { EditorService } from '@affine/core/modules/editor';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
@@ -15,6 +14,7 @@ import { ShareInfoService } from '@affine/core/modules/share-doc';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { PublicPageMode } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { DocMode } from '@blocksuite/blocks';
import {
BlockIcon,
@@ -1,5 +1,5 @@
import { OverlayModal } from '@affine/component';
import { openStarAFFiNEModalAtom } from '@affine/core/atoms';
import { openStarAFFiNEModalAtom } from '@affine/core/components/atoms';
import { useI18n } from '@affine/i18n';
import { useAtom } from 'jotai';
@@ -1,7 +1,7 @@
import { AuthPageContainer } from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useSubscriptionNotifyWriter } from '@affine/core/hooks/affine/use-subscription-notify';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { useSubscriptionNotifyWriter } from '@affine/core/components/hooks/affine/use-subscription-notify';
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
import { Trans, useI18n } from '@affine/i18n';
import { type ReactNode, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
@@ -1,5 +1,5 @@
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
import { track } from '@affine/core/mixpanel';
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
import { track } from '@affine/track';
import { CloseIcon, DownloadIcon } from '@blocksuite/icons/rc';
import clsx from 'clsx';
import { useCallback, useState } from 'react';
@@ -1,5 +1,5 @@
import { Tooltip } from '@affine/component';
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
import { popupWindow } from '@affine/core/utils';
import { Unreachable } from '@affine/env/constant';
import { useI18n } from '@affine/i18n';
@@ -1,7 +1,7 @@
import { Skeleton } from '@affine/component';
import { ResizePanel } from '@affine/component/resize-panel';
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { NavigateContext } from '@affine/core/hooks/use-navigate-helper';
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
import { NavigateContext } from '@affine/core/components/hooks/use-navigate-helper';
import { useServiceOptional, WorkspaceService } from '@toeverything/infra';
import { useAtom, useAtomValue } from 'jotai';
import { debounce } from 'lodash-es';
@@ -0,0 +1,75 @@
import { atom } from 'jotai';
import type { SettingProps } from '../affine/setting-modal';
import type { ActiveTab } from '../affine/setting-modal/types';
// modal atoms
export const openWorkspacesModalAtom = atom(false);
/**
* @deprecated use `useSignOut` hook instated
*/
export const openSignOutModalAtom = atom(false);
export const openQuotaModalAtom = atom(false);
export const openStarAFFiNEModalAtom = atom(false);
export const openIssueFeedbackModalAtom = atom(false);
export const openHistoryTipsModalAtom = atom(false);
export const openInfoModalAtom = atom(false);
export const rightSidebarWidthAtom = atom(320);
export type PlansScrollAnchor =
| 'aiPricingPlan'
| 'cloudPricingPlan'
| 'lifetimePricingPlan';
export type SettingAtom = {
open: boolean;
workspaceMetadata?: SettingProps['workspaceMetadata'];
} & (
| {
activeTab: 'plans';
scrollAnchor?: PlansScrollAnchor;
}
| { activeTab: Exclude<ActiveTab, 'plans'> }
);
export const openSettingModalAtom = atom<SettingAtom>({
activeTab: 'appearance',
open: false,
});
export type AuthAtomData =
| { state: 'signIn' }
| {
state: 'afterSignUpSendEmail';
email: string;
}
| {
state: 'afterSignInSendEmail';
email: string;
}
| {
state: 'signInWithPassword';
email: string;
}
| {
state: 'sendEmail';
email: string;
emailType:
| 'setPassword'
| 'changePassword'
| 'changeEmail'
| 'verifyEmail';
};
export const authAtom = atom<
AuthAtomData & {
openModal: boolean;
}
>({
openModal: false,
state: 'signIn',
});
export type AllPageFilterOption = 'docs' | 'collections' | 'tags';
export const allPageFilterSelectAtom = atom<AllPageFilterOption>('docs');
export const openWorkspaceListModalAtom = atom(false);
@@ -0,0 +1,7 @@
import { atom } from 'jotai';
// make page history controllable by atom to make it easier to use in CMDK
export const pageHistoryModalAtom = atom({
open: false,
pageId: '',
});
@@ -0,0 +1,13 @@
import { atom } from 'jotai';
export type TrashModal = {
open: boolean;
pageIds: string[];
pageTitles: string[];
};
export const trashModalAtom = atom<TrashModal>({
open: false,
pageIds: [],
pageTitles: [],
});
@@ -1,14 +1,14 @@
import { notify } from '@affine/component';
import { authAtom, openSettingModalAtom } from '@affine/core/atoms';
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
import { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onboarding/apis';
import { track } from '@affine/core/mixpanel';
import { authAtom, openSettingModalAtom } from '@affine/core/components/atoms';
import {
getBaseUrl,
type getCopilotHistoriesQuery,
type RequestOptions,
} from '@affine/graphql';
import { Trans } from '@affine/i18n';
import { track } from '@affine/track';
import { UnauthorizedError } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils';
import { getCurrentStore } from '@toeverything/infra';
@@ -1,5 +1,5 @@
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
import { mixpanel, track } from '@affine/core/mixpanel';
import { mixpanel, track } from '@affine/track';
import type { EditorHost } from '@blocksuite/block-std';
import type { BlockModel } from '@blocksuite/store';
import { lowerCase, omit } from 'lodash-es';
@@ -1,4 +1,4 @@
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
import { useJournalInfoHelper } from '@affine/core/components/hooks/use-journal';
import { useI18n } from '@affine/i18n';
import type { Doc } from '@blocksuite/store';
@@ -3,7 +3,7 @@ import {
useConfirmModal,
useLitPortalFactory,
} from '@affine/component';
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
import { useJournalInfoHelper } from '@affine/core/components/hooks/use-journal';
import { EditorService } from '@affine/core/modules/editor';
import { EditorSettingService } from '@affine/core/modules/editor-settting';
import { PeekViewService } from '@affine/core/modules/peek-view';
@@ -2,11 +2,11 @@ import { notify } from '@affine/component';
import {
generateUrl,
type UseSharingUrl,
} from '@affine/core/hooks/affine/use-share-url';
import { track } from '@affine/core/mixpanel';
} from '@affine/core/components/hooks/affine/use-share-url';
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
import { EditorService } from '@affine/core/modules/editor';
import { I18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { DatabaseBlockModel, MenuOptions } from '@blocksuite/blocks';
import { LinkIcon } from '@blocksuite/icons/lit';
import type { FrameworkProvider } from '@toeverything/infra';
@@ -2,8 +2,8 @@ import {
AIEdgelessRootBlockSpec,
AIPageRootBlockSpec,
} from '@affine/core/blocksuite/presets/ai';
import { mixpanel } from '@affine/core/mixpanel';
import { EditorSettingService } from '@affine/core/modules/editor-settting';
import { mixpanel } from '@affine/track';
import { ConfigExtension, type ExtensionType } from '@blocksuite/block-std';
import {
EdgelessRootBlockSpec,
@@ -7,7 +7,6 @@ import {
toReactNode,
type useConfirmModal,
} from '@affine/component';
import { track } from '@affine/core/mixpanel';
import type { EditorService } from '@affine/core/modules/editor';
import { resolveLinkToDoc } from '@affine/core/modules/navigation';
import type { PeekViewService } from '@affine/core/modules/peek-view';
@@ -21,6 +20,7 @@ import {
} from '@affine/core/modules/quicksearch';
import { ExternalLinksQuickSearchSession } from '@affine/core/modules/quicksearch/impls/external-links';
import { DebugLogger } from '@affine/debug';
import { track } from '@affine/track';
import {
type BlockService,
BlockViewIdentifier,
@@ -1,6 +1,6 @@
import { track } from '@affine/core/mixpanel';
import { WorkspacePropertiesAdapter } from '@affine/core/modules/properties';
import { I18n, i18nTime } from '@affine/i18n';
import { track } from '@affine/track';
import type { EditorHost } from '@blocksuite/block-std';
import type { AffineInlineEditor } from '@blocksuite/blocks';
import { LinkedWidgetUtils } from '@blocksuite/blocks';
@@ -2,11 +2,11 @@ import { notify } from '@affine/component';
import {
generateUrl,
type UseSharingUrl,
} from '@affine/core/hooks/affine/use-share-url';
import { track } from '@affine/core/mixpanel';
} from '@affine/core/components/hooks/affine/use-share-url';
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
import { EditorService } from '@affine/core/modules/editor';
import { I18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
import type {
GfxBlockElementModel,
@@ -1,8 +1,8 @@
import { FavoriteTag } from '@affine/core/components/page-list';
import { track } from '@affine/core/mixpanel';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { toast } from '@affine/core/utils';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback } from 'react';
@@ -1,7 +1,7 @@
import { IconButton } from '@affine/component';
import { openInfoModalAtom } from '@affine/core/atoms';
import { track } from '@affine/core/mixpanel';
import { openInfoModalAtom } from '@affine/core/components/atoms';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { InformationIcon } from '@blocksuite/icons/rc';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
@@ -3,7 +3,7 @@ import { WeekDatePicker } from '@affine/component';
import {
useJournalInfoHelper,
useJournalRouteHelper,
} from '@affine/core/hooks/use-journal';
} from '@affine/core/components/hooks/use-journal';
import type { Doc, DocCollection } from '@blocksuite/store';
import dayjs from 'dayjs';
import { useEffect, useRef, useState } from 'react';
@@ -1,5 +1,5 @@
import { Button } from '@affine/component';
import { useJournalRouteHelper } from '@affine/core/hooks/use-journal';
import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal';
import { useI18n } from '@affine/i18n';
import type { DocCollection } from '@blocksuite/store';
import { useCallback } from 'react';
@@ -5,27 +5,27 @@ import {
MenuSeparator,
MenuSub,
} from '@affine/component/ui/menu';
import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal';
import { ShareMenuContent } from '@affine/core/components/affine/share-page-modal/share-menu';
import {
openHistoryTipsModalAtom,
openInfoModalAtom,
} from '@affine/core/atoms';
import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal';
import { ShareMenuContent } from '@affine/core/components/affine/share-page-modal/share-menu';
} from '@affine/core/components/atoms';
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page';
import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
import { Export, MoveToTrash } from '@affine/core/components/page-list';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { useExportPage } from '@affine/core/hooks/affine/use-export-page';
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { track } from '@affine/core/mixpanel';
import { EditorService } from '@affine/core/modules/editor';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { ViewService } from '@affine/core/modules/workbench/services/view';
import { useDetailPageHeaderResponsive } from '@affine/core/pages/workspace/detail-page/use-header-responsive';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import {
DuplicateIcon,
EdgelessIcon,
@@ -1,7 +1,7 @@
import type { InlineEditProps } from '@affine/component';
import { InlineEdit } from '@affine/component';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { track } from '@affine/track';
import {
DocsService,
useLiveData,
@@ -1,8 +1,8 @@
import { RadioGroup, type RadioItem, toast, Tooltip } from '@affine/component';
import { registerAffineCommand } from '@affine/core/commands';
import { track } from '@affine/core/mixpanel';
import { EditorService } from '@affine/core/modules/editor';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { DocMode } from '@blocksuite/blocks';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
@@ -1,5 +1,5 @@
import { Button } from '@affine/component';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
import { useI18n } from '@affine/i18n';
export const ImportTemplateButton = ({
@@ -1,5 +1,5 @@
import { Button } from '@affine/component/ui/button';
import { authAtom } from '@affine/core/atoms';
import { authAtom } from '@affine/core/components/atoms';
import { useI18n } from '@affine/i18n';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
@@ -1,6 +1,6 @@
import { Avatar } from '@affine/component/ui/avatar';
import { Menu, MenuItem, MenuSeparator } from '@affine/component/ui/menu';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useI18n } from '@affine/i18n';
import { SignOutIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
@@ -0,0 +1,23 @@
/**
* @vitest-environment happy-dom
*/
import { renderHook } from '@testing-library/react';
import { describe, expect, test, vi } from 'vitest';
import { useSystemOnline } from '../use-system-online';
describe('useSystemOnline', () => {
test('should be online', () => {
const systemOnlineHook = renderHook(() => useSystemOnline());
expect(systemOnlineHook.result.current).toBe(true);
});
test('should be offline', () => {
const systemOnlineHook = renderHook(() => useSystemOnline());
vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(false);
expect(systemOnlineHook.result.current).toBe(true);
window.dispatchEvent(new Event('offline'));
systemOnlineHook.rerender();
expect(systemOnlineHook.result.current).toBe(false);
});
});
@@ -0,0 +1,29 @@
import React, { type DependencyList } 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: DependencyList
): (...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
);
}
@@ -0,0 +1,6 @@
# AFFiNE Hooks
> This directory will be moved to `@affine/worksapce/affine/hooks` in the future.
Only put hooks in this directory if they are specific to AFFiNE, for example
if they are using the AFFiNE API, or if the `AffineWorkspace` is required.
@@ -0,0 +1,88 @@
import { toast } from '@affine/component';
import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta';
import type { AllPageListConfig } from '@affine/core/components/page-list';
import { FavoriteTag } from '@affine/core/components/page-list';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { ShareDocsListService } from '@affine/core/modules/share-doc';
import { PublicPageMode } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import type { DocMeta } from '@blocksuite/store';
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo } from 'react';
/**
* @deprecated very poor performance
*/
export const useAllPageListConfig = () => {
const currentWorkspace = useService(WorkspaceService).workspace;
const shareDocsListService = useService(ShareDocsListService);
const shareDocs = useLiveData(shareDocsListService.shareDocs?.list$);
useEffect(() => {
// TODO(@eyhn): loading & error UI
shareDocsListService.shareDocs?.revalidate();
}, [shareDocsListService]);
const workspace = currentWorkspace.docCollection;
const pageMetas = useBlockSuiteDocMeta(workspace);
const pageMap = useMemo(
() => Object.fromEntries(pageMetas.map(page => [page.id, page])),
[pageMetas]
);
const favAdapter = useService(CompatibleFavoriteItemsAdapter);
const t = useI18n();
const favoriteItems = useLiveData(favAdapter.favorites$);
const isActive = useCallback(
(page: DocMeta) => {
return favoriteItems.some(fav => fav.id === page.id);
},
[favoriteItems]
);
const onToggleFavoritePage = useCallback(
(page: DocMeta) => {
const status = isActive(page);
favAdapter.toggle(page.id, 'doc');
toast(
status
? t['com.affine.toastMessage.removedFavorites']()
: t['com.affine.toastMessage.addedFavorites']()
);
},
[favAdapter, isActive, t]
);
return useMemo<AllPageListConfig>(() => {
return {
allPages: pageMetas,
getPublicMode(id) {
const mode = shareDocs?.find(shareDoc => shareDoc.id === id)?.mode;
if (mode === PublicPageMode.Edgeless) {
return 'edgeless';
} else if (mode === PublicPageMode.Page) {
return 'page';
} else {
return undefined;
}
},
docCollection: currentWorkspace.docCollection,
getPage: id => pageMap[id],
favoriteRender: page => {
return (
<FavoriteTag
style={{ marginRight: 8 }}
onClick={() => onToggleFavoritePage(page)}
active={isActive(page)}
/>
);
},
};
}, [
pageMetas,
currentWorkspace.docCollection,
shareDocs,
pageMap,
isActive,
onToggleFavoritePage,
]);
};
@@ -0,0 +1,23 @@
import type { AppSetting } from '@toeverything/infra';
import { appSettingAtom } from '@toeverything/infra';
import { useAtom } from 'jotai';
import { useCallback, useMemo } from 'react';
export function useAppSettingHelper() {
const [appSettings, setAppSettings] = useAtom(appSettingAtom);
const updateSettings = useCallback(
<K extends keyof AppSetting>(key: K, value: AppSetting[K]) => {
setAppSettings(prevSettings => ({ ...prevSettings, [key]: value }));
},
[setAppSettings]
);
return useMemo(
() => ({
appSettings,
updateSettings,
}),
[appSettings, updateSettings]
);
}
@@ -0,0 +1,102 @@
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
import { useDocCollectionHelper } from '@affine/core/components/hooks/use-block-suite-workspace-helper';
import type { DocMode } from '@blocksuite/blocks';
import { DocsService, useService, WorkspaceService } from '@toeverything/infra';
import { useCallback } from 'react';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { useNavigateHelper } from '../use-navigate-helper';
export function useBlockSuiteMetaHelper() {
const workspace = useService(WorkspaceService).workspace;
const { setDocMeta, getDocMeta, setDocTitle, setDocReadonly } =
useDocMetaHelper();
const { createDoc } = useDocCollectionHelper(workspace.docCollection);
const { openPage } = useNavigateHelper();
const docRecordList = useService(DocsService).list;
// TODO-Doma
// "Remove" may cause ambiguity here. Consider renaming as "moveToTrash".
const removeToTrash = useCallback(
(docId: string) => {
const docRecord = docRecordList.doc$(docId).value;
if (docRecord) {
docRecord.moveToTrash();
setDocReadonly(docId, true);
}
},
[docRecordList, setDocReadonly]
);
const restoreFromTrash = useCallback(
(docId: string) => {
const docRecord = docRecordList.doc$(docId).value;
if (docRecord) {
docRecord.restoreFromTrash();
setDocReadonly(docId, false);
}
},
[docRecordList, setDocReadonly]
);
const permanentlyDeletePage = useCallback(
(pageId: string) => {
workspace.docCollection.removeDoc(pageId);
},
[workspace]
);
const duplicate = useAsyncCallback(
async (pageId: string, openPageAfterDuplication: boolean = true) => {
const currentPagePrimaryMode =
docRecordList.doc$(pageId).value?.primaryMode$.value;
const currentPageMeta = getDocMeta(pageId);
const newPage = createDoc();
const currentPage = workspace.docCollection.getDoc(pageId);
newPage.load();
if (!currentPageMeta || !currentPage) {
return;
}
const update = encodeStateAsUpdate(currentPage.spaceDoc);
applyUpdate(newPage.spaceDoc, update);
setDocMeta(newPage.id, {
tags: currentPageMeta.tags,
});
const lastDigitRegex = /\((\d+)\)$/;
const match = currentPageMeta?.title?.match(lastDigitRegex);
const newNumber = match ? parseInt(match[1], 10) + 1 : 1;
const newPageTitle =
currentPageMeta?.title?.replace(lastDigitRegex, '') + `(${newNumber})`;
docRecordList
.doc$(newPage.id)
.value?.setPrimaryMode(currentPagePrimaryMode || ('page' as DocMode));
setDocTitle(newPage.id, newPageTitle);
openPageAfterDuplication &&
openPage(workspace.docCollection.id, newPage.id);
},
[
docRecordList,
getDocMeta,
createDoc,
workspace.docCollection,
setDocMeta,
setDocTitle,
openPage,
]
);
return {
removeToTrash,
restoreFromTrash,
permanentlyDeletePage,
duplicate,
};
}
@@ -0,0 +1,15 @@
import { AuthService } from '@affine/core/modules/cloud';
import type { DeleteCollectionInfo } from '@affine/env/filter';
import { useLiveData, useService } from '@toeverything/infra';
import { useMemo } from 'react';
export const useDeleteCollectionInfo = () => {
const authService = useService(AuthService);
const user = useLiveData(authService.session.account$);
return useMemo<DeleteCollectionInfo | null>(
() => (user ? { userName: user.label, userId: user.id } : null),
[user]
);
};
@@ -0,0 +1,118 @@
import { notify, useConfirmModal } from '@affine/component';
import { authAtom } from '@affine/core/components/atoms';
import { AuthService } from '@affine/core/modules/cloud';
import { useI18n } from '@affine/i18n';
import type { Workspace } from '@toeverything/infra';
import {
useLiveData,
useService,
WorkspacesService,
} from '@toeverything/infra';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { useNavigateHelper } from '../use-navigate-helper';
interface ConfirmEnableCloudOptions {
/**
* Fired when the workspace is successfully enabled
*/
onSuccess?: () => void;
/**
* Fired when workspace is successfully enabled or user cancels the operation
*/
onFinished?: () => void;
openPageId?: string;
}
type ConfirmEnableArgs = [Workspace, ConfirmEnableCloudOptions | undefined];
export const useEnableCloud = () => {
const t = useI18n();
const authService = useService(AuthService);
const account = useLiveData(authService.session.account$);
const loginStatus = useLiveData(useService(AuthService).session.status$);
const setAuthAtom = useSetAtom(authAtom);
const { openConfirmModal, closeConfirmModal } = useConfirmModal();
const workspacesService = useService(WorkspacesService);
const { jumpToPage } = useNavigateHelper();
const enableCloud = useCallback(
async (ws: Workspace | null, options?: ConfirmEnableCloudOptions) => {
try {
if (!ws) return;
if (!account) return;
const { id: newId } = await workspacesService.transformLocalToCloud(
ws,
account.id
);
jumpToPage(newId, options?.openPageId || 'all');
options?.onSuccess?.();
} catch (e) {
console.error(e);
notify.error({
title: t['com.affine.workspace.enable-cloud.failed'](),
});
}
},
[account, jumpToPage, t, workspacesService]
);
const openSignIn = useCallback(() => {
setAuthAtom(prev => ({ ...prev, openModal: true }));
}, [setAuthAtom]);
const signInOrEnableCloud = useCallback(
async (...args: ConfirmEnableArgs) => {
// not logged in, open login modal
if (loginStatus === 'unauthenticated') {
openSignIn();
}
if (loginStatus === 'authenticated') {
await enableCloud(...args);
}
},
[enableCloud, loginStatus, openSignIn]
);
const confirmEnableCloud = useCallback(
(ws: Workspace, options?: ConfirmEnableCloudOptions) => {
const { onSuccess, onFinished } = options ?? {};
const closeOnSuccess = () => {
closeConfirmModal();
onSuccess?.();
};
openConfirmModal(
{
title: t['Enable AFFiNE Cloud'](),
description: t['Enable AFFiNE Cloud Description'](),
cancelText: t['com.affine.enableAffineCloudModal.button.cancel'](),
confirmText:
loginStatus === 'authenticated'
? t['Enable']()
: t['Sign in and Enable'](),
confirmButtonOptions: {
variant: 'primary',
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
},
onConfirm: async () =>
await signInOrEnableCloud(ws, {
...options,
onSuccess: closeOnSuccess,
}),
onOpenChange: open => {
if (!open) onFinished?.();
},
},
{
autoClose: false,
}
);
},
[closeConfirmModal, loginStatus, openConfirmModal, signInOrEnableCloud, t]
);
return confirmEnableCloud;
};
@@ -0,0 +1,107 @@
import { notify } from '@affine/component';
import {
pushGlobalLoadingEventAtom,
resolveGlobalLoadingEventAtom,
} from '@affine/component/global-loading';
import { EditorService } from '@affine/core/modules/editor';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import {
ExportManager,
HtmlTransformer,
MarkdownTransformer,
printToPdf,
} from '@blocksuite/blocks';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { Doc } from '@blocksuite/store';
import { useLiveData, useService } from '@toeverything/infra';
import { useSetAtom } from 'jotai';
import { nanoid } from 'nanoid';
import { useAsyncCallback } from '../affine-async-hooks';
type ExportType = 'pdf' | 'html' | 'png' | 'markdown';
interface ExportHandlerOptions {
page: Doc;
editorContainer: AffineEditorContainer;
type: ExportType;
}
async function exportHandler({
page,
type,
editorContainer,
}: ExportHandlerOptions) {
const editorRoot = document.querySelector('editor-host');
track.$.sharePanel.$.export({
type,
});
switch (type) {
case 'html':
await HtmlTransformer.exportDoc(page);
return;
case 'markdown':
await MarkdownTransformer.exportDoc(page);
return;
case 'pdf':
await printToPdf(editorContainer);
return;
case 'png': {
await editorRoot?.std.get(ExportManager).exportPng();
return;
}
}
}
export const useExportPage = () => {
const editor = useService(EditorService).editor;
const editorContainer = useLiveData(editor.editorContainer$);
const blocksuiteDoc = editor.doc.blockSuiteDoc;
const pushGlobalLoadingEvent = useSetAtom(pushGlobalLoadingEventAtom);
const resolveGlobalLoadingEvent = useSetAtom(resolveGlobalLoadingEventAtom);
const t = useI18n();
const onClickHandler = useAsyncCallback(
async (type: ExportType) => {
if (editorContainer === null) return;
// editor container is wrapped by a proxy, we need to get the origin
const originEditorContainer = (editorContainer as any)
.origin as AffineEditorContainer;
const globalLoadingID = nanoid();
pushGlobalLoadingEvent({
key: globalLoadingID,
});
try {
await exportHandler({
page: blocksuiteDoc,
type,
editorContainer: originEditorContainer,
});
notify.success({
title: t['com.affine.export.success.title'](),
message: t['com.affine.export.success.message'](),
});
} catch (err) {
console.error(err);
notify.error({
title: t['com.affine.export.error.title'](),
message: t['com.affine.export.error.message'](),
});
} finally {
resolveGlobalLoadingEvent(globalLoadingID);
}
},
[
blocksuiteDoc,
editorContainer,
pushGlobalLoadingEvent,
resolveGlobalLoadingEvent,
t,
]
);
return onClickHandler;
};
@@ -0,0 +1,30 @@
import type { Permission } from '@affine/graphql';
import { inviteByEmailMutation } from '@affine/graphql';
import { useCallback } from 'react';
import { useMutation } from '../use-mutation';
import { useMutateCloud } from './use-mutate-cloud';
export function useInviteMember(workspaceId: string) {
const { trigger, isMutating } = useMutation({
mutation: inviteByEmailMutation,
});
const mutate = useMutateCloud();
return {
invite: useCallback(
async (email: string, permission: Permission, sendInviteMail = false) => {
const res = await trigger({
workspaceId,
email,
permission,
sendInviteMail,
});
await mutate();
// return is successful
return res?.invite;
},
[mutate, trigger, workspaceId]
),
isMutating,
};
}
@@ -0,0 +1,42 @@
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { LOCALES, useI18n } from '@affine/i18n';
import { useEffect, useMemo } from 'react';
export function useLanguageHelper() {
const i18n = useI18n();
const currentLanguage = useMemo(
() => LOCALES.find(item => item.tag === i18n.language),
[i18n.language]
);
const languagesList = useMemo(
() =>
LOCALES.map(item => ({
tag: item.tag,
originalName: item.originalName,
name: item.name,
Completeness: item.completeRate,
})),
[]
);
const onLanguageChange = useAsyncCallback(
async (event: string) => {
await i18n.changeLanguage(event);
},
[i18n]
);
useEffect(() => {
if (currentLanguage) {
document.documentElement.lang = currentLanguage.tag;
}
}, [currentLanguage]);
return useMemo(
() => ({
currentLanguage,
languagesList,
onLanguageChange,
}),
[currentLanguage, languagesList, onLanguageChange]
);
}
@@ -0,0 +1,15 @@
import { useCallback } from 'react';
import { useSWRConfig } from 'swr';
export function useMutateCloud() {
const { mutate } = useSWRConfig();
return useCallback(async () => {
// TODO(@eyhn): should not mutate all graphql cache
return mutate(key => {
if (Array.isArray(key)) {
return key[0] === 'cloud';
}
return false;
});
}, [mutate]);
}
@@ -0,0 +1,32 @@
import type { DocCollection } from '@blocksuite/store';
import { useCallback } from 'react';
export function useReferenceLinkHelper(docCollection: DocCollection) {
const addReferenceLink = useCallback(
(pageId: string, referenceId: string) => {
const page = docCollection?.getDoc(pageId);
if (!page) {
return;
}
const text = page.Text.fromDelta([
{
insert: ' ',
attributes: {
reference: {
type: 'Subpage',
pageId: referenceId,
},
},
},
]);
const [frame] = page.getBlockByFlavour('affine:note');
frame && page.addBlock('affine:paragraph', { text }, frame.id);
},
[docCollection]
);
return {
addReferenceLink,
};
}
@@ -0,0 +1,327 @@
import { toast } from '@affine/component';
import {
PreconditionStrategy,
registerAffineCommand,
} from '@affine/core/commands';
import { openInfoModalAtom } from '@affine/core/components/atoms';
import type { Editor } from '@affine/core/modules/editor';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { EdgelessIcon, HistoryIcon, PageIcon } from '@blocksuite/icons/rc';
import {
DocService,
useLiveData,
useService,
WorkspaceService,
} from '@toeverything/infra';
import { useSetAtom } from 'jotai';
import { useCallback, useEffect } from 'react';
import { pageHistoryModalAtom } from '../../../components/atoms/page-history';
import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper';
import { useExportPage } from './use-export-page';
import { useTrashModalHelper } from './use-trash-modal-helper';
export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
const doc = useService(DocService).doc;
const docId = doc.id;
const mode = useLiveData(editor.mode$);
const t = useI18n();
const workspace = useService(WorkspaceService).workspace;
const favAdapter = useService(CompatibleFavoriteItemsAdapter);
const favorite = useLiveData(favAdapter.isFavorite$(docId, 'doc'));
const trash = useLiveData(doc.trash$);
const setPageHistoryModalState = useSetAtom(pageHistoryModalAtom);
const setInfoModalState = useSetAtom(openInfoModalAtom);
const openHistoryModal = useCallback(() => {
setPageHistoryModalState(() => ({
pageId: docId,
open: true,
}));
}, [docId, setPageHistoryModalState]);
const openInfoModal = useCallback(() => {
setInfoModalState(true);
}, [setInfoModalState]);
const { duplicate } = useBlockSuiteMetaHelper();
const exportHandler = useExportPage();
const { setTrashModal } = useTrashModalHelper();
const onClickDelete = useCallback(
(title: string) => {
setTrashModal({
open: true,
pageIds: [docId],
pageTitles: [title],
});
},
[docId, setTrashModal]
);
const isCloudWorkspace = workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD;
useEffect(() => {
const unsubs: Array<() => void> = [];
const preconditionStrategy = () =>
PreconditionStrategy.InPaperOrEdgeless && !trash;
// TODO(@Peng): add back when edgeless presentation is ready
// this is pretty hack and easy to break. need a better way to communicate with blocksuite editor
// unsubs.push(
// registerAffineCommand({
// id: 'editor:edgeless-presentation-start',
// preconditionStrategy: () => PreconditionStrategy.InEdgeless && !trash,
// category: 'editor:edgeless',
// icon: <EdgelessIcon />,
// label: t['com.affine.cmdk.affine.editor.edgeless.presentation-start'](),
// run() {
// document
// .querySelector<HTMLElement>('edgeless-toolbar')
// ?.shadowRoot?.querySelector<HTMLElement>(
// '.edgeless-toolbar-left-part > edgeless-tool-icon-button:last-child'
// )
// ?.click();
// },
// })
// );
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-view-info`,
preconditionStrategy: () =>
PreconditionStrategy.InPaperOrEdgeless && !trash,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['com.affine.page-properties.page-info.view'](),
run() {
track.$.cmdk.docInfo.open();
openInfoModal();
},
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-${favorite ? 'remove-from' : 'add-to'}-favourites`,
preconditionStrategy,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: favorite
? t['com.affine.favoritePageOperation.remove']()
: t['com.affine.favoritePageOperation.add'](),
run() {
favAdapter.toggle(docId, 'doc');
track.$.cmdk.editor.toggleFavorite();
toast(
favorite
? t['com.affine.cmdk.affine.editor.remove-from-favourites']()
: t['com.affine.cmdk.affine.editor.add-to-favourites']()
);
},
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-convert-to-${
mode === 'page' ? 'edgeless' : 'page'
}`,
preconditionStrategy,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: `${t['Convert to ']()}${
mode === 'page'
? t['com.affine.pageMode.edgeless']()
: t['com.affine.pageMode.page']()
}`,
run() {
track.$.cmdk.editor.switchPageMode({
mode: mode === 'page' ? 'edgeless' : 'page',
});
editor.toggleMode();
toast(
mode === 'page'
? t['com.affine.toastMessage.edgelessMode']()
: t['com.affine.toastMessage.pageMode']()
);
},
})
);
// TODO(@Peng): should not show duplicate for journal
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-duplicate`,
preconditionStrategy,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['com.affine.header.option.duplicate'](),
run() {
duplicate(docId);
track.$.cmdk.editor.createDoc({
control: 'duplicate',
});
},
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-export-to-pdf`,
preconditionStrategy: () => mode === 'page' && !trash,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['Export to PDF'](),
async run() {
track.$.cmdk.editor.export({
type: 'pdf',
});
exportHandler('pdf');
},
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-export-to-html`,
preconditionStrategy,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['Export to HTML'](),
async run() {
track.$.cmdk.editor.export({
type: 'html',
});
exportHandler('html');
},
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-export-to-png`,
preconditionStrategy: () => mode === 'page' && !trash,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['Export to PNG'](),
async run() {
track.$.cmdk.editor.export({
type: 'png',
});
exportHandler('png');
},
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-export-to-markdown`,
preconditionStrategy,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['Export to Markdown'](),
async run() {
track.$.cmdk.editor.export({
type: 'markdown',
});
exportHandler('markdown');
},
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-move-to-trash`,
preconditionStrategy,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['com.affine.moveToTrash.title'](),
run() {
track.$.cmdk.editor.deleteDoc();
onClickDelete(doc.title$.value);
},
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-restore-from-trash`,
preconditionStrategy: () =>
PreconditionStrategy.InPaperOrEdgeless && trash,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['com.affine.cmdk.affine.editor.restore-from-trash'](),
run() {
track.$.cmdk.editor.restoreDoc();
doc.restoreFromTrash();
},
})
);
if (isCloudWorkspace) {
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-page-history`,
category: `editor:${mode}`,
icon: <HistoryIcon />,
label: t['com.affine.cmdk.affine.editor.reveal-page-history-modal'](),
run() {
track.$.cmdk.docHistory.open();
openHistoryModal();
},
})
);
}
unsubs.push(
registerAffineCommand({
id: 'alert-ctrl-s',
category: 'affine:general',
preconditionStrategy: PreconditionStrategy.Never,
keyBinding: {
binding: '$mod+s',
},
label: '',
icon: null,
run() {
toast(t['Save']());
},
})
);
return () => {
unsubs.forEach(unsub => unsub());
};
}, [
editor,
favorite,
mode,
onClickDelete,
exportHandler,
t,
trash,
isCloudWorkspace,
openHistoryModal,
duplicate,
favAdapter,
docId,
doc,
openInfoModal,
]);
}

Some files were not shown because too many files have changed in this diff Show More