feat: use custom @ import dialog (#8723)

[BS-1747](https://linear.app/affine-design/issue/BS-1747/[ui]-通过导入的ui还是旧的,需要更新)
This commit is contained in:
donteatfriedrice
2024-11-08 01:53:29 +00:00
parent d8eda5e42d
commit c323e5ae93
11 changed files with 257 additions and 531 deletions

View File

@@ -1,376 +0,0 @@
import { Button, IconButton, Modal } from '@affine/component';
import { UrlService } from '@affine/core/modules/url';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import {
MarkdownTransformer,
NotionHtmlTransformer,
openFileOrFiles,
} from '@blocksuite/affine/blocks';
import type { DocCollection } from '@blocksuite/affine/store';
import {
ExportToMarkdownIcon,
HelpIcon,
NotionIcon,
} from '@blocksuite/icons/rc';
import { useService, WorkspaceService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { useSetAtom } from 'jotai';
import { type ReactElement, useCallback, useState } from 'react';
import { openImportModalAtom } from '../../atoms';
import { useAsyncCallback } from '../../hooks/affine-async-hooks';
import * as style from './style.css';
type ImportType = 'markdown' | 'markdownZip' | 'notion';
type AcceptType = 'Markdown' | 'Zip';
type Status = 'idle' | 'importing' | 'success' | 'error';
type ImportConfig = {
fileOptions: { acceptType: AcceptType; multiple: boolean };
importFunction: (
docCollection: DocCollection,
file: File | File[]
) => Promise<string[]>;
};
const DISCORD_URL = 'https://discord.gg/whd5mjYqVw';
const importOptions = [
{
label: 'com.affine.import.markdown-files',
prefixIcon: (
<ExportToMarkdownIcon
color={cssVarV2('icon/primary')}
width={20}
height={20}
/>
),
testId: 'editor-option-menu-import-markdown-files',
type: 'markdown' as ImportType,
},
{
label: 'com.affine.import.markdown-with-media-files',
prefixIcon: (
<ExportToMarkdownIcon
color={cssVarV2('icon/primary')}
width={20}
height={20}
/>
),
testId: 'editor-option-menu-import-markdown-with-media',
type: 'markdownZip' as ImportType,
},
{
label: 'com.affine.import.notion',
prefixIcon: <NotionIcon color={cssVar('black')} width={20} height={20} />,
suffixIcon: (
<HelpIcon color={cssVarV2('icon/primary')} width={20} height={20} />
),
suffixTooltip: 'com.affine.import.notion.tooltip',
testId: 'editor-option-menu-import-notion',
type: 'notion' as ImportType,
},
];
const importConfigs: Record<ImportType, ImportConfig> = {
markdown: {
fileOptions: { acceptType: 'Markdown', multiple: true },
importFunction: async (docCollection, files) => {
if (!Array.isArray(files)) {
throw new Error('Expected an array of files for markdown files import');
}
const pageIds: string[] = [];
for (const file of files) {
const text = await file.text();
const fileName = file.name.split('.').slice(0, -1).join('.');
const pageId = await MarkdownTransformer.importMarkdownToDoc({
collection: docCollection,
markdown: text,
fileName,
});
if (pageId) pageIds.push(pageId);
}
return pageIds;
},
},
markdownZip: {
fileOptions: { acceptType: 'Zip', multiple: false },
importFunction: async (docCollection, file) => {
if (Array.isArray(file)) {
throw new Error('Expected a single zip file for markdownZip import');
}
return MarkdownTransformer.importMarkdownZip({
collection: docCollection,
imported: file,
});
},
},
notion: {
fileOptions: { acceptType: 'Zip', multiple: false },
importFunction: async (docCollection, file) => {
if (Array.isArray(file)) {
throw new Error('Expected a single zip file for notion import');
}
const { pageIds } = await NotionHtmlTransformer.importNotionZip({
collection: docCollection,
imported: file,
});
return pageIds;
},
},
};
const ImportOptionItem = ({
label,
prefixIcon,
suffixIcon,
suffixTooltip,
type,
onImport,
}: {
label: string;
prefixIcon: ReactElement;
suffixIcon?: ReactElement;
suffixTooltip?: string;
type: ImportType;
onImport: (type: ImportType) => void;
}) => {
const t = useI18n();
return (
<div className={style.importItem} onClick={() => onImport(type)}>
{prefixIcon}
<div className={style.importItemLabel}>{t[label]()}</div>
{suffixIcon && (
<IconButton
className={style.importItemSuffix}
icon={suffixIcon}
tooltip={suffixTooltip ? t[suffixTooltip]() : undefined}
/>
)}
</div>
);
};
const ImportOptions = ({
onImport,
}: {
onImport: (type: ImportType) => void;
}) => {
const t = useI18n();
return (
<>
<div className={style.importModalTitle}>{t['Import']()}</div>
<div className={style.importModalContent}>
{importOptions.map(
({ label, prefixIcon, suffixIcon, suffixTooltip, testId, type }) => (
<ImportOptionItem
key={testId}
prefixIcon={prefixIcon}
suffixIcon={suffixIcon}
suffixTooltip={suffixTooltip}
label={label}
data-testid={testId}
type={type}
onImport={onImport}
/>
)
)}
</div>
<div className={style.importModalTip}>
{t['com.affine.import.modal.tip']()}{' '}
<a
className={style.link}
href="https://discord.gg/whd5mjYqVw"
target="_blank"
rel="noreferrer"
>
Discord
</a>{' '}
.
</div>
</>
);
};
const ImportingStatus = () => {
const t = useI18n();
return (
<>
<div className={style.importModalTitle}>
{t['com.affine.import.status.importing.title']()}
</div>
<p className={style.importStatusContent}>
{t['com.affine.import.status.importing.message']()}
</p>
</>
);
};
const SuccessStatus = ({ onComplete }: { onComplete: () => void }) => {
const t = useI18n();
return (
<>
<div className={style.importModalTitle}>
{t['com.affine.import.status.success.title']()}
</div>
<p className={style.importStatusContent}>
{t['com.affine.import.status.success.message']()}{' '}
<a
className={style.link}
href={DISCORD_URL}
target="_blank"
rel="noreferrer"
>
Discord
</a>
.
</p>
<div className={style.importModalButtonContainer}>
<Button onClick={onComplete} variant="primary">
{t['Complete']()}
</Button>
</div>
</>
);
};
const ErrorStatus = ({
error,
onRetry,
}: {
error: string | null;
onRetry: () => void;
}) => {
const t = useI18n();
const urlService = useService(UrlService);
return (
<>
<div className={style.importModalTitle}>
{t['com.affine.import.status.failed.title']()}
</div>
<p className={style.importStatusContent}>
{error || 'Unknown error occurred'}
</p>
<div className={style.importModalButtonContainer}>
<Button
onClick={() => {
urlService.openPopupWindow(DISCORD_URL);
}}
variant="secondary"
>
{t['Feedback']()}
</Button>
<Button onClick={onRetry} variant="primary">
{t['Retry']()}
</Button>
</div>
</>
);
};
export const ImportModal = ({ ...modalProps }) => {
const t = useI18n();
const [status, setStatus] = useState<Status>('idle');
const [importError, setImportError] = useState<string | null>(null);
const [pageIds, setPageIds] = useState<string[]>([]);
const setOpenImportModalAtom = useSetAtom(openImportModalAtom);
const workspace = useService(WorkspaceService).workspace;
const workbench = useService(WorkbenchService).workbench;
const docCollection = workspace.docCollection;
const handleImport = useAsyncCallback(
async (type: ImportType) => {
setImportError(null);
try {
const importConfig = importConfigs[type];
const file = await openFileOrFiles(importConfig.fileOptions);
if (!file || (Array.isArray(file) && file.length === 0)) {
throw new Error(
t['com.affine.import.status.failed.message.no-file-selected']()
);
}
setStatus('importing');
track.$.importModal.$.import({
type,
status: 'importing',
});
const pageIds = await importConfig.importFunction(docCollection, file);
setPageIds(pageIds);
setStatus('success');
track.$.importModal.$.import({
type,
status: 'success',
result: {
docCount: pageIds.length,
},
});
} catch (error) {
setImportError(
error instanceof Error ? error.message : 'Unknown error occurred'
);
setStatus('error');
track.$.importModal.$.import({
type,
status: 'failed',
error: importError || undefined,
});
}
},
[docCollection, t, importError]
);
const handleComplete = useCallback(() => {
if (pageIds.length > 1) {
workbench.openAll();
} else if (pageIds.length === 1) {
workbench.openDoc(pageIds[0]);
}
setOpenImportModalAtom(false);
}, [pageIds, workbench, setOpenImportModalAtom]);
const handleRetry = () => {
setStatus('idle');
};
const statusComponents = {
idle: <ImportOptions onImport={handleImport} />,
importing: <ImportingStatus />,
success: <SuccessStatus onComplete={handleComplete} />,
error: <ErrorStatus error={importError} onRetry={handleRetry} />,
};
return (
<Modal
width={480}
contentOptions={{
['data-testid' as string]: 'import-modal',
style: {
maxHeight: '85vh',
maxWidth: '70vw',
minHeight: '126px',
padding: 0,
overflow: 'hidden',
display: 'flex',
background: cssVarV2('layer/background/primary'),
},
}}
closeButtonOptions={{
className: style.closeButton,
}}
withoutCloseButton={status === 'importing'}
persistent={status === 'importing'}
{...modalProps}
>
<div className={style.importModalContainer}>
{statusComponents[status]}
</div>
</Modal>
);
};

View File

@@ -1,110 +0,0 @@
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
export const importModalContainer = style({
width: '100%',
height: '100%',
display: 'flex',
boxSizing: 'border-box',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '20px 24px',
gap: '12px',
});
export const importModalTitle = style({
width: '100%',
height: 'auto',
fontSize: cssVar('fontH6'),
fontWeight: '600',
lineHeight: cssVar('lineHeight'),
});
export const importModalContent = style({
width: '100%',
flex: 1,
display: 'flex',
flexDirection: 'column',
gap: '12px',
});
export const closeButton = style({
top: '24px',
right: '24px',
});
export const importModalTip = style({
width: '100%',
height: 'auto',
fontSize: cssVar('fontSm'),
lineHeight: cssVar('lineHeight'),
fontWeight: '400',
color: cssVar('textSecondaryColor'),
});
export const link = style({
color: cssVar('linkColor'),
cursor: 'pointer',
});
export const importStatusContent = style({
width: '100%',
fontSize: cssVar('fontBase'),
lineHeight: cssVar('lineHeight'),
fontWeight: '400',
color: cssVar('textPrimaryColor'),
});
export const importModalButtonContainer = style({
width: '100%',
display: 'flex',
flexDirection: 'row',
gap: '20px',
justifyContent: 'end',
marginTop: '20px',
});
export const importItem = style({
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
height: 'auto',
gap: '4px',
padding: '8px 12px',
borderRadius: '8px',
border: `1px solid ${cssVarV2('layer/insideBorder/border')}`,
background: cssVarV2('button/secondary'),
selectors: {
'&:hover': {
background: cssVarV2('layer/background/hoverOverlay'),
cursor: 'pointer',
transition: 'background .30s',
},
},
});
export const importItemLabel = style({
display: 'flex',
alignItems: 'center',
padding: '0 4px',
textAlign: 'left',
flex: 1,
color: cssVar('textPrimaryColor'),
fontSize: cssVar('fontBase'),
lineHeight: cssVar('lineHeight'),
fontWeight: '500',
whiteSpace: 'nowrap',
overflow: 'hidden',
});
export const importItemPrefix = style({
marginRight: 'auto',
});
export const importItemSuffix = style({
marginLeft: 'auto',
});

View File

@@ -1,3 +1,4 @@
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
import { JournalService } from '@affine/core/modules/journal';
import { I18n } from '@affine/i18n';
@@ -8,6 +9,68 @@ import { LinkedWidgetUtils } from '@blocksuite/affine/blocks';
import type { DocMeta } from '@blocksuite/affine/store';
import { type FrameworkProvider, WorkspaceService } from '@toeverything/infra';
function createNewDocMenuGroup(
framework: FrameworkProvider,
query: string,
abort: () => void,
editorHost: EditorHost,
inlineEditor: AffineInlineEditor
) {
const originalNewDocMenuGroup = LinkedWidgetUtils.createNewDocMenuGroup(
query,
abort,
editorHost,
inlineEditor
);
// Patch the import item, to use the custom import dialog.
const importItemIndex = originalNewDocMenuGroup.items.findIndex(
item => item.key === 'import'
);
if (importItemIndex === -1) {
return originalNewDocMenuGroup;
}
const originalItems = originalNewDocMenuGroup.items;
const originalImportItem = originalItems[importItemIndex];
const customImportItem = {
...originalImportItem,
action: () => {
abort();
track.doc.editor.atMenu.import();
framework
.get(WorkspaceDialogService)
.open('import', undefined, payload => {
if (!payload) {
return;
}
// If the imported file is a workspace file, insert the entry page node.
const { docIds, entryId, isWorkspaceFile } = payload;
if (isWorkspaceFile && entryId) {
LinkedWidgetUtils.insertLinkedNode({
inlineEditor,
docId: entryId,
});
return;
}
// Otherwise, insert all the doc nodes.
for (const docId of docIds) {
LinkedWidgetUtils.insertLinkedNode({
inlineEditor,
docId,
});
}
});
},
};
// only replace the original import item
originalItems.splice(importItemIndex, 1, customImportItem);
return originalNewDocMenuGroup;
}
// TODO: fix the type
export function createLinkedWidgetConfig(
framework: FrameworkProvider
@@ -70,7 +133,8 @@ export function createLinkedWidgetConfig(
maxDisplay: MAX_DOCS,
overflowText: `${docMetas.length - MAX_DOCS} more docs`,
},
LinkedWidgetUtils.createNewDocMenuGroup(
createNewDocMenuGroup(
framework,
query,
abort,
editorHost,

View File

@@ -18,10 +18,7 @@ import {
} from '@affine/core/components/page-list';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
import { useDetailPageHeaderResponsive } from '@affine/core/desktop/pages/workspace/detail-page/use-header-responsive';
import {
GlobalDialogService,
WorkspaceDialogService,
} from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { EditorService } from '@affine/core/modules/editor';
import { OpenInAppService } from '@affine/core/modules/open-in-app/services';
import { WorkbenchService } from '@affine/core/modules/workbench';
@@ -79,7 +76,6 @@ export const PageHeaderMenuButton = ({
const workspace = useService(WorkspaceService).workspace;
const globalDialogService = useService(GlobalDialogService);
const editorService = useService(EditorService);
const isInTrash = useLiveData(
editorService.editor.doc.meta$.map(meta => meta.trash)
@@ -204,10 +200,39 @@ export const PageHeaderMenuButton = ({
});
}, [duplicate, pageId]);
const handleOpenDocs = useCallback(
(result: {
docIds: string[];
entryId?: string;
isWorkspaceFile?: boolean;
}) => {
const { docIds, entryId, isWorkspaceFile } = result;
// If the imported file is a workspace file, open the entry page.
if (isWorkspaceFile && entryId) {
workbench.openDoc(entryId);
} else if (!docIds.length) {
return;
}
// Open all the docs when there are multiple docs imported.
if (docIds.length > 1) {
workbench.openAll();
} else {
// Otherwise, open the only doc.
workbench.openDoc(docIds[0]);
}
},
[workbench]
);
const handleOpenImportModal = useCallback(() => {
track.$.header.importModal.open();
globalDialogService.open('import', undefined);
}, [globalDialogService]);
workspaceDialogService.open('import', undefined, payload => {
if (!payload) {
return;
}
handleOpenDocs(payload);
});
}, [workspaceDialogService, handleOpenDocs]);
const handleShareMenuOpenChange = useCallback((open: boolean) => {
if (open) {

View File

@@ -11,7 +11,10 @@ import {
SidebarScrollableContainer,
} from '@affine/core/modules/app-sidebar/views';
import { ExternalMenuLinkItem } from '@affine/core/modules/app-sidebar/views/menu-item/external-menu-link-item';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import {
GlobalDialogService,
WorkspaceDialogService,
} from '@affine/core/modules/dialogs';
import {
ExplorerCollections,
ExplorerFavorites,
@@ -84,6 +87,7 @@ export const RootAppSidebar = (): ReactElement => {
const currentWorkspace = workspaceService.workspace;
const t = useI18n();
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const workbench = workbenchService.workbench;
const currentPath = useLiveData(
workbench.location$.map(location => location.pathname)
@@ -111,10 +115,39 @@ export const RootAppSidebar = (): ReactElement => {
track.$.navigationPanel.$.openSettings();
}, [globalDialogService]);
const handleOpenDocs = useCallback(
(result: {
docIds: string[];
entryId?: string;
isWorkspaceFile?: boolean;
}) => {
const { docIds, entryId, isWorkspaceFile } = result;
// If the imported file is a workspace file, open the entry page.
if (isWorkspaceFile && entryId) {
workbench.openDoc(entryId);
} else if (!docIds.length) {
return;
}
// Open all the docs when there are multiple docs imported.
if (docIds.length > 1) {
workbench.openAll();
} else {
// Otherwise, open the only doc.
workbench.openDoc(docIds[0]);
}
},
[workbench]
);
const onOpenImportModal = useCallback(() => {
track.$.navigationPanel.importModal.open();
globalDialogService.open('import', undefined);
}, [globalDialogService]);
workspaceDialogService.open('import', undefined, payload => {
if (!payload) {
return;
}
handleOpenDocs(payload);
});
}, [workspaceDialogService, handleOpenDocs]);
return (
<AppSidebar>

View File

@@ -2,11 +2,11 @@ import { Button, IconButton, Modal } from '@affine/component';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import type {
DialogComponentProps,
GLOBAL_DIALOG_SCHEMA,
WORKSPACE_DIALOG_SCHEMA,
} from '@affine/core/modules/dialogs';
import { UrlService } from '@affine/core/modules/url';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import {
MarkdownTransformer,
NotionHtmlTransformer,
@@ -28,13 +28,18 @@ import * as style from './styles.css';
type ImportType = 'markdown' | 'markdownZip' | 'notion';
type AcceptType = 'Markdown' | 'Zip';
type Status = 'idle' | 'importing' | 'success' | 'error';
type ImportResult = {
docIds: string[];
entryId?: string;
isWorkspaceFile?: boolean;
};
type ImportConfig = {
fileOptions: { acceptType: AcceptType; multiple: boolean };
importFunction: (
docCollection: DocCollection,
file: File | File[]
) => Promise<string[]>;
) => Promise<ImportResult>;
};
const DISCORD_URL = 'https://discord.gg/whd5mjYqVw';
@@ -83,18 +88,20 @@ const importConfigs: Record<ImportType, ImportConfig> = {
if (!Array.isArray(files)) {
throw new Error('Expected an array of files for markdown files import');
}
const pageIds: string[] = [];
const docIds: string[] = [];
for (const file of files) {
const text = await file.text();
const fileName = file.name.split('.').slice(0, -1).join('.');
const pageId = await MarkdownTransformer.importMarkdownToDoc({
const docId = await MarkdownTransformer.importMarkdownToDoc({
collection: docCollection,
markdown: text,
fileName,
});
if (pageId) pageIds.push(pageId);
if (docId) docIds.push(docId);
}
return pageIds;
return {
docIds,
};
},
},
markdownZip: {
@@ -103,10 +110,13 @@ const importConfigs: Record<ImportType, ImportConfig> = {
if (Array.isArray(file)) {
throw new Error('Expected a single zip file for markdownZip import');
}
return MarkdownTransformer.importMarkdownZip({
const docIds = await MarkdownTransformer.importMarkdownZip({
collection: docCollection,
imported: file,
});
return {
docIds,
};
},
},
notion: {
@@ -115,11 +125,16 @@ const importConfigs: Record<ImportType, ImportConfig> = {
if (Array.isArray(file)) {
throw new Error('Expected a single zip file for notion import');
}
const { pageIds } = await NotionHtmlTransformer.importNotionZip({
collection: docCollection,
imported: file,
});
return pageIds;
const { entryId, pageIds, isWorkspaceFile } =
await NotionHtmlTransformer.importNotionZip({
collection: docCollection,
imported: file,
});
return {
docIds: pageIds,
entryId,
isWorkspaceFile,
};
},
},
};
@@ -274,13 +289,12 @@ const ErrorStatus = ({
export const ImportDialog = ({
close,
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['import']>) => {
}: DialogComponentProps<WORKSPACE_DIALOG_SCHEMA['import']>) => {
const t = useI18n();
const [status, setStatus] = useState<Status>('idle');
const [importError, setImportError] = useState<string | null>(null);
const [pageIds, setPageIds] = useState<string[]>([]);
const [importResult, setImportResult] = useState<ImportResult | null>(null);
const workspace = useService(WorkspaceService).workspace;
const workbench = useService(WorkbenchService).workbench;
const docCollection = workspace.docCollection;
const handleImport = useAsyncCallback(
@@ -297,29 +311,41 @@ export const ImportDialog = ({
}
setStatus('importing');
track.$.importModal.$.import({
type,
status: 'importing',
});
const pageIds = await importConfig.importFunction(docCollection, file);
const { docIds, entryId, isWorkspaceFile } =
await importConfig.importFunction(docCollection, file);
setPageIds(pageIds);
setImportResult({ docIds, entryId, isWorkspaceFile });
setStatus('success');
track.$.importModal.$.import({
type,
status: 'success',
result: {
docCount: docIds.length,
},
});
} catch (error) {
setImportError(
error instanceof Error ? error.message : 'Unknown error occurred'
);
const errorMessage =
error instanceof Error ? error.message : 'Unknown error occurred';
setImportError(errorMessage);
setStatus('error');
track.$.importModal.$.import({
type,
status: 'failed',
error: errorMessage || undefined,
});
}
},
[docCollection, t]
);
const handleComplete = useCallback(() => {
if (pageIds.length > 1) {
workbench.openAll();
} else if (pageIds.length === 1) {
workbench.openDoc(pageIds[0]);
}
close();
}, [pageIds, close, workbench]);
close(importResult || undefined);
}, [importResult, close]);
const handleRetry = () => {
setStatus('idle');
@@ -335,8 +361,10 @@ export const ImportDialog = ({
return (
<Modal
open
onOpenChange={() => {
close();
onOpenChange={(open: boolean) => {
if (!open) {
close(importResult || undefined);
}
}}
width={480}
contentOptions={{
@@ -357,7 +385,7 @@ export const ImportDialog = ({
withoutCloseButton={status === 'importing'}
persistent={status === 'importing'}
>
<div className={style.importModalContainer}>
<div className={style.importModalContainer} data-testid="import-dialog">
{statusComponents[status]}
</div>
</Modal>

View File

@@ -24,7 +24,6 @@ const GLOBAL_DIALOGS = {
'import-workspace': ImportWorkspaceDialog,
'import-template': ImportTemplateDialog,
setting: SettingDialog,
import: ImportDialog,
} satisfies {
[key in keyof GLOBAL_DIALOG_SCHEMA]?: React.FC<
DialogComponentProps<GLOBAL_DIALOG_SCHEMA[key]>
@@ -37,6 +36,7 @@ const WORKSPACE_DIALOGS = {
'tag-selector': TagSelectorDialog,
'doc-selector': DocSelectorDialog,
'collection-selector': CollectionSelectorDialog,
import: ImportDialog,
} satisfies {
[key in keyof WORKSPACE_DIALOG_SCHEMA]?: React.FC<
DialogComponentProps<WORKSPACE_DIALOG_SCHEMA[key]>

View File

@@ -26,7 +26,6 @@ export type GLOBAL_DIALOG_SCHEMA = {
templateMode: DocMode;
snapshotUrl: string;
}) => void;
import: () => void;
setting: (props: {
activeTab?: SettingTab;
workspaceMetadata?: WorkspaceMetadata | null;
@@ -52,4 +51,9 @@ export type WORKSPACE_DIALOG_SCHEMA = {
init: string[];
onBeforeConfirm?: (ids: string[], cb: () => void) => void;
}) => string[];
import: () => {
docIds: string[];
entryId?: string;
isWorkspaceFile?: boolean;
};
};