mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-27 02:42:25 +08:00
feat(core): allow importing affine file within import dialog (#12850)
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
import { Button, IconButton, Modal } from '@affine/component';
|
import { Button, IconButton, Modal } from '@affine/component';
|
||||||
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import type {
|
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||||
DialogComponentProps,
|
import {
|
||||||
WORKSPACE_DIALOG_SCHEMA,
|
type DialogComponentProps,
|
||||||
|
GlobalDialogService,
|
||||||
|
type WORKSPACE_DIALOG_SCHEMA,
|
||||||
} from '@affine/core/modules/dialogs';
|
} from '@affine/core/modules/dialogs';
|
||||||
import { UrlService } from '@affine/core/modules/url';
|
import { UrlService } from '@affine/core/modules/url';
|
||||||
import {
|
import {
|
||||||
getAFFiNEWorkspaceSchema,
|
getAFFiNEWorkspaceSchema,
|
||||||
|
type WorkspaceMetadata,
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
} from '@affine/core/modules/workspace';
|
} from '@affine/core/modules/workspace';
|
||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
@@ -27,6 +30,7 @@ import {
|
|||||||
HelpIcon,
|
HelpIcon,
|
||||||
NotionIcon,
|
NotionIcon,
|
||||||
PageIcon,
|
PageIcon,
|
||||||
|
SaveIcon,
|
||||||
ZipIcon,
|
ZipIcon,
|
||||||
} from '@blocksuite/icons/rc';
|
} from '@blocksuite/icons/rc';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
@@ -36,6 +40,7 @@ import {
|
|||||||
type ReactElement,
|
type ReactElement,
|
||||||
type SVGAttributes,
|
type SVGAttributes,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
@@ -43,8 +48,14 @@ import * as style from './styles.css';
|
|||||||
|
|
||||||
const logger = new DebugLogger('import');
|
const logger = new DebugLogger('import');
|
||||||
|
|
||||||
type ImportType = 'markdown' | 'markdownZip' | 'notion' | 'snapshot' | 'html';
|
type ImportType =
|
||||||
type AcceptType = 'Markdown' | 'Zip' | 'Html';
|
| 'markdown'
|
||||||
|
| 'markdownZip'
|
||||||
|
| 'notion'
|
||||||
|
| 'snapshot'
|
||||||
|
| 'html'
|
||||||
|
| 'dotaffinefile';
|
||||||
|
type AcceptType = 'Markdown' | 'Zip' | 'Html' | 'Skip'; // Skip is used for dotaffinefile
|
||||||
type Status = 'idle' | 'importing' | 'success' | 'error';
|
type Status = 'idle' | 'importing' | 'success' | 'error';
|
||||||
type ImportResult = {
|
type ImportResult = {
|
||||||
docIds: string[];
|
docIds: string[];
|
||||||
@@ -56,7 +67,8 @@ type ImportConfig = {
|
|||||||
fileOptions: { acceptType: AcceptType; multiple: boolean };
|
fileOptions: { acceptType: AcceptType; multiple: boolean };
|
||||||
importFunction: (
|
importFunction: (
|
||||||
docCollection: Workspace,
|
docCollection: Workspace,
|
||||||
files: File[]
|
files: File[],
|
||||||
|
handleImportAffineFile: () => Promise<WorkspaceMetadata | undefined>
|
||||||
) => Promise<ImportResult>;
|
) => Promise<ImportResult>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,7 +140,22 @@ const importOptions = [
|
|||||||
testId: 'editor-option-menu-import-snapshot',
|
testId: 'editor-option-menu-import-snapshot',
|
||||||
type: 'snapshot' as ImportType,
|
type: 'snapshot' as ImportType,
|
||||||
},
|
},
|
||||||
];
|
BUILD_CONFIG.isElectron
|
||||||
|
? {
|
||||||
|
key: 'dotaffinefile',
|
||||||
|
label: 'com.affine.import.dotaffinefile',
|
||||||
|
prefixIcon: (
|
||||||
|
<SaveIcon color={cssVarV2('icon/primary')} width={20} height={20} />
|
||||||
|
),
|
||||||
|
suffixIcon: (
|
||||||
|
<HelpIcon color={cssVarV2('icon/primary')} width={20} height={20} />
|
||||||
|
),
|
||||||
|
suffixTooltip: 'com.affine.import.dotaffinefile.tooltip',
|
||||||
|
testId: 'editor-option-menu-import-dotaffinefile',
|
||||||
|
type: 'dotaffinefile' as ImportType,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
].filter(v => v !== null);
|
||||||
|
|
||||||
const importConfigs: Record<ImportType, ImportConfig> = {
|
const importConfigs: Record<ImportType, ImportConfig> = {
|
||||||
markdown: {
|
markdown: {
|
||||||
@@ -234,6 +261,17 @@ const importConfigs: Record<ImportType, ImportConfig> = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
dotaffinefile: {
|
||||||
|
fileOptions: { acceptType: 'Skip', multiple: false },
|
||||||
|
importFunction: async (_, __, handleImportAffineFile) => {
|
||||||
|
await handleImportAffineFile();
|
||||||
|
return {
|
||||||
|
docIds: [],
|
||||||
|
entryId: undefined,
|
||||||
|
isWorkspaceFile: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImportOptionItem = ({
|
const ImportOptionItem = ({
|
||||||
@@ -404,28 +442,84 @@ export const ImportDialog = ({
|
|||||||
const workspace = useService(WorkspaceService).workspace;
|
const workspace = useService(WorkspaceService).workspace;
|
||||||
const docCollection = workspace.docCollection;
|
const docCollection = workspace.docCollection;
|
||||||
|
|
||||||
|
const globalDialogService = useService(GlobalDialogService);
|
||||||
|
|
||||||
|
const { jumpToPage } = useNavigateHelper();
|
||||||
|
const handleCreatedWorkspace = useCallback(
|
||||||
|
(payload: { metadata: WorkspaceMetadata; defaultDocId?: string }) => {
|
||||||
|
if (document.startViewTransition) {
|
||||||
|
document.startViewTransition(() => {
|
||||||
|
if (payload.defaultDocId) {
|
||||||
|
jumpToPage(payload.metadata.id, payload.defaultDocId);
|
||||||
|
} else {
|
||||||
|
jumpToPage(payload.metadata.id, 'all');
|
||||||
|
}
|
||||||
|
return new Promise(resolve =>
|
||||||
|
setTimeout(resolve, 150)
|
||||||
|
); /* start transition after 150ms */
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (payload.defaultDocId) {
|
||||||
|
jumpToPage(payload.metadata.id, payload.defaultDocId);
|
||||||
|
} else {
|
||||||
|
jumpToPage(payload.metadata.id, 'all');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[jumpToPage]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleImportAffineFile = useMemo(() => {
|
||||||
|
return async () => {
|
||||||
|
track.$.navigationPanel.workspaceList.createWorkspace({
|
||||||
|
control: 'import',
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise<WorkspaceMetadata | undefined>((resolve, reject) => {
|
||||||
|
globalDialogService.open('import-workspace', undefined, payload => {
|
||||||
|
if (payload) {
|
||||||
|
handleCreatedWorkspace({ metadata: payload.workspace });
|
||||||
|
resolve(payload.workspace);
|
||||||
|
} else {
|
||||||
|
reject(new Error('No workspace imported'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, [globalDialogService, handleCreatedWorkspace]);
|
||||||
|
|
||||||
const handleImport = useAsyncCallback(
|
const handleImport = useAsyncCallback(
|
||||||
async (type: ImportType) => {
|
async (type: ImportType) => {
|
||||||
setImportError(null);
|
setImportError(null);
|
||||||
try {
|
try {
|
||||||
const importConfig = importConfigs[type];
|
const importConfig = importConfigs[type];
|
||||||
const { acceptType, multiple } = importConfig.fileOptions;
|
const { acceptType, multiple } = importConfig.fileOptions;
|
||||||
const files = await openFilesWith(acceptType, multiple);
|
|
||||||
|
|
||||||
if (!files || files.length === 0) {
|
const files =
|
||||||
|
acceptType === 'Skip'
|
||||||
|
? []
|
||||||
|
: await openFilesWith(acceptType, multiple);
|
||||||
|
|
||||||
|
if (!files || (files.length === 0 && acceptType !== 'Skip')) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
t['com.affine.import.status.failed.message.no-file-selected']()
|
t['com.affine.import.status.failed.message.no-file-selected']()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus('importing');
|
if (acceptType !== 'Skip') {
|
||||||
track.$.importModal.$.import({
|
setStatus('importing');
|
||||||
type,
|
track.$.importModal.$.import({
|
||||||
status: 'importing',
|
type,
|
||||||
});
|
status: 'importing',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { docIds, entryId, isWorkspaceFile } =
|
const { docIds, entryId, isWorkspaceFile } =
|
||||||
await importConfig.importFunction(docCollection, files);
|
await importConfig.importFunction(
|
||||||
|
docCollection,
|
||||||
|
files,
|
||||||
|
handleImportAffineFile
|
||||||
|
);
|
||||||
|
|
||||||
setImportResult({ docIds, entryId, isWorkspaceFile });
|
setImportResult({ docIds, entryId, isWorkspaceFile });
|
||||||
setStatus('success');
|
setStatus('success');
|
||||||
@@ -452,7 +546,7 @@ export const ImportDialog = ({
|
|||||||
logger.error('Failed to import', error);
|
logger.error('Failed to import', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[docCollection, t]
|
[docCollection, handleImportAffineFile, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleComplete = useCallback(() => {
|
const handleComplete = useCallback(() => {
|
||||||
|
|||||||
@@ -2411,6 +2411,14 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Import your AFFiNE workspace and page snapshot file.`
|
* `Import your AFFiNE workspace and page snapshot file.`
|
||||||
*/
|
*/
|
||||||
["com.affine.import.snapshot.tooltip"](): string;
|
["com.affine.import.snapshot.tooltip"](): string;
|
||||||
|
/**
|
||||||
|
* `.affine file`
|
||||||
|
*/
|
||||||
|
["com.affine.import.dotaffinefile"](): string;
|
||||||
|
/**
|
||||||
|
* `Import your AFFiNE db file (.affine)`
|
||||||
|
*/
|
||||||
|
["com.affine.import.dotaffinefile.tooltip"](): string;
|
||||||
/**
|
/**
|
||||||
* `Import failed, please try again.`
|
* `Import failed, please try again.`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -602,6 +602,8 @@
|
|||||||
"com.affine.import.notion.tooltip": "Import your Notion data. Supported import formats: HTML with subpages.",
|
"com.affine.import.notion.tooltip": "Import your Notion data. Supported import formats: HTML with subpages.",
|
||||||
"com.affine.import.snapshot": "Snapshot",
|
"com.affine.import.snapshot": "Snapshot",
|
||||||
"com.affine.import.snapshot.tooltip": "Import your AFFiNE workspace and page snapshot file.",
|
"com.affine.import.snapshot.tooltip": "Import your AFFiNE workspace and page snapshot file.",
|
||||||
|
"com.affine.import.dotaffinefile": ".affine file",
|
||||||
|
"com.affine.import.dotaffinefile.tooltip": "Import your AFFiNE db file (.affine)",
|
||||||
"com.affine.import.status.failed.message": "Import failed, please try again.",
|
"com.affine.import.status.failed.message": "Import failed, please try again.",
|
||||||
"com.affine.import.status.failed.message.no-file-selected": "No file selected",
|
"com.affine.import.status.failed.message.no-file-selected": "No file selected",
|
||||||
"com.affine.import.status.failed.title": "Import failure",
|
"com.affine.import.status.failed.title": "Import failure",
|
||||||
|
|||||||
Reference in New Issue
Block a user