refactor(editor): split openFileOrFiles into openSingleFileWith and openFilesWith (#12523)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Improved file selection dialogs for attachments, imports, and uploads, allowing for more consistent and streamlined file picking across the app.

- **Bug Fixes**
	- Resolved inconsistencies when selecting single or multiple files, ensuring a smoother user experience during file import and upload.

- **Refactor**
	- Unified and simplified file selection logic throughout the app for better reliability and maintainability.
	- Standardized import functions to uniformly handle arrays of files, enhancing consistency in file processing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
fundon
2025-05-28 03:06:32 +00:00
parent cf456c888f
commit c43e1bcc4e
12 changed files with 72 additions and 118 deletions

View File

@@ -1,4 +1,4 @@
import { openFileOrFiles } from '@blocksuite/affine-shared/utils'; import { openSingleFileWith } from '@blocksuite/affine-shared/utils';
import { type SlashMenuConfig } from '@blocksuite/affine-widget-slash-menu'; import { type SlashMenuConfig } from '@blocksuite/affine-widget-slash-menu';
import { ExportToPdfIcon, FileIcon } from '@blocksuite/icons/lit'; import { ExportToPdfIcon, FileIcon } from '@blocksuite/icons/lit';
@@ -21,7 +21,7 @@ export const attachmentSlashMenuConfig: SlashMenuConfig = {
model.store.schema.flavourSchemaMap.has('affine:attachment'), model.store.schema.flavourSchemaMap.has('affine:attachment'),
action: ({ std, model }) => { action: ({ std, model }) => {
(async () => { (async () => {
const file = await openFileOrFiles(); const file = await openSingleFileWith();
if (!file) return; if (!file) return;
await addSiblingAttachmentBlocks(std, [file], model); await addSiblingAttachmentBlocks(std, [file], model);
@@ -44,7 +44,7 @@ export const attachmentSlashMenuConfig: SlashMenuConfig = {
model.store.schema.flavourSchemaMap.has('affine:attachment'), model.store.schema.flavourSchemaMap.has('affine:attachment'),
action: ({ std, model }) => { action: ({ std, model }) => {
(async () => { (async () => {
const file = await openFileOrFiles(); const file = await openSingleFileWith();
if (!file) return; if (!file) return;
await addSiblingAttachmentBlocks(std, [file], model); await addSiblingAttachmentBlocks(std, [file], model);

View File

@@ -17,7 +17,7 @@ import {
FeatureFlagService, FeatureFlagService,
TelemetryProvider, TelemetryProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { openFileOrFiles } from '@blocksuite/affine-shared/utils'; import { openSingleFileWith } from '@blocksuite/affine-shared/utils';
import { Bound, type IVec } from '@blocksuite/global/gfx'; import { Bound, type IVec } from '@blocksuite/global/gfx';
import type { BlockComponent } from '@blocksuite/std'; import type { BlockComponent } from '@blocksuite/std';
import type { TemplateResult } from 'lit'; import type { TemplateResult } from 'lit';
@@ -158,7 +158,7 @@ export const textRender: DraggableTool['render'] = async (bound, edgeless) => {
export const mediaRender: DraggableTool['render'] = async (bound, edgeless) => { export const mediaRender: DraggableTool['render'] = async (bound, edgeless) => {
let file: File | null = null; let file: File | null = null;
try { try {
file = await openFileOrFiles(); file = await openSingleFileWith();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return null; return null;

View File

@@ -1,4 +1,4 @@
import { openFileOrFiles } from '@blocksuite/affine-shared/utils'; import { openSingleFileWith } from '@blocksuite/affine-shared/utils';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { Bound } from '@blocksuite/global/gfx'; import type { Bound } from '@blocksuite/global/gfx';
import c from 'simple-xml-to-json'; import c from 'simple-xml-to-json';
@@ -12,9 +12,7 @@ type MindMapNode = {
}; };
export async function importMindmap(bound: Bound): Promise<MindMapNode> { export async function importMindmap(bound: Bound): Promise<MindMapNode> {
const file = await openFileOrFiles({ const file = await openSingleFileWith('MindMap');
acceptType: 'MindMap',
});
if (!file) { if (!file) {
throw new BlockSuiteError(ErrorCode.UserAbortError, 'Aborted by user'); throw new BlockSuiteError(ErrorCode.UserAbortError, 'Aborted by user');

View File

@@ -7,7 +7,7 @@ import { TelemetryProvider } from '@blocksuite/affine-shared/services';
import type { NoteChildrenFlavour } from '@blocksuite/affine-shared/types'; import type { NoteChildrenFlavour } from '@blocksuite/affine-shared/types';
import { import {
getImageFilesFromLocal, getImageFilesFromLocal,
openFileOrFiles, openSingleFileWith,
} from '@blocksuite/affine-shared/utils'; } from '@blocksuite/affine-shared/utils';
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar'; import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
import { AttachmentIcon, ImageIcon, LinkIcon } from '@blocksuite/icons/lit'; import { AttachmentIcon, ImageIcon, LinkIcon } from '@blocksuite/icons/lit';
@@ -139,7 +139,7 @@ export class EdgelessNoteMenu extends EdgelessToolbarToolMixin(LitElement) {
.activeMode=${'background'} .activeMode=${'background'}
.tooltip=${'File'} .tooltip=${'File'}
@click=${async () => { @click=${async () => {
const file = await openFileOrFiles(); const file = await openSingleFileWith();
if (!file) return; if (!file) return;
await addAttachments(this.edgeless.std, [file]); await addAttachments(this.edgeless.std, [file]);
this.gfx.tool.setTool(DefaultTool); this.gfx.tool.setTool(DefaultTool);

View File

@@ -112,21 +112,11 @@ type AcceptTypes =
| 'Html' | 'Html'
| 'Zip' | 'Zip'
| 'MindMap'; | 'MindMap';
export function openFileOrFiles(options?: {
acceptType?: AcceptTypes; export async function openFilesWith(
}): Promise<File | null>; acceptType: AcceptTypes = 'Any',
export function openFileOrFiles(options: { multiple: boolean = true
acceptType?: AcceptTypes; ): Promise<File[] | null> {
multiple: false;
}): Promise<File | null>;
export function openFileOrFiles(options: {
acceptType?: AcceptTypes;
multiple: true;
}): Promise<File[] | null>;
export async function openFileOrFiles({
acceptType = 'Any',
multiple = false,
} = {}) {
// Feature detection. The API needs to be supported // Feature detection. The API needs to be supported
// and the app not run in an iframe. // and the app not run in an iframe.
const supportsFileSystemAccess = const supportsFileSystemAccess =
@@ -138,6 +128,7 @@ export async function openFileOrFiles({
return false; return false;
} }
})(); })();
// If the File System Access API is supported… // If the File System Access API is supported…
if (supportsFileSystemAccess && window.showOpenFilePicker) { if (supportsFileSystemAccess && window.showOpenFilePicker) {
try { try {
@@ -153,30 +144,14 @@ export async function openFileOrFiles({
} satisfies OpenFilePickerOptions; } satisfies OpenFilePickerOptions;
// Show the file picker, optionally allowing multiple files. // Show the file picker, optionally allowing multiple files.
const handles = await window.showOpenFilePicker(pickerOpts); const handles = await window.showOpenFilePicker(pickerOpts);
// Only one file is requested.
if (!multiple) { return await Promise.all(handles.map(handle => handle.getFile()));
// Add the `FileSystemFileHandle` as `.handle`.
const file = await handles[0].getFile();
// Add the `FileSystemFileHandle` as `.handle`.
// file.handle = handles[0];
return file;
} else {
const files = await Promise.all(
handles.map(async handle => {
const file = await handle.getFile();
// Add the `FileSystemFileHandle` as `.handle`.
// file.handle = handles[0];
return file;
})
);
return files;
}
} catch (err) { } catch (err) {
console.error('Error opening file');
console.error(err); console.error(err);
return null; return null;
} }
} }
// Fallback if the File System Access API is not supported. // Fallback if the File System Access API is not supported.
return new Promise(resolve => { return new Promise(resolve => {
// Append a new `<input type="file" multiple? />` and hide it. // Append a new `<input type="file" multiple? />` and hide it.
@@ -184,9 +159,8 @@ export async function openFileOrFiles({
input.classList.add('affine-upload-input'); input.classList.add('affine-upload-input');
input.style.display = 'none'; input.style.display = 'none';
input.type = 'file'; input.type = 'file';
if (multiple) { input.multiple = multiple;
input.multiple = true;
}
if (acceptType !== 'Any') { if (acceptType !== 'Any') {
// For example, `accept="image/*"` or `accept="video/*,audio/*"`. // For example, `accept="image/*"` or `accept="video/*,audio/*"`.
input.accept = Object.keys( input.accept = Object.keys(
@@ -198,17 +172,8 @@ export async function openFileOrFiles({
input.addEventListener('change', () => { input.addEventListener('change', () => {
// Remove the `<input type="file" multiple? />` again from the DOM. // Remove the `<input type="file" multiple? />` again from the DOM.
input.remove(); input.remove();
// If no files were selected, return.
if (!input.files) { resolve(input.files ? Array.from(input.files) : null);
resolve(null);
return;
}
// Return all files or just one file.
if (multiple) {
resolve(Array.from(input.files));
return;
}
resolve(input.files[0]);
}); });
// The `cancel` event fires when the user cancels the dialog. // The `cancel` event fires when the user cancels the dialog.
input.addEventListener('cancel', () => { input.addEventListener('cancel', () => {
@@ -223,11 +188,14 @@ export async function openFileOrFiles({
}); });
} }
export function openSingleFileWith(
acceptType?: AcceptTypes
): Promise<File | null> {
return openFilesWith(acceptType, false).then(files => files?.at(0) ?? null);
}
export async function getImageFilesFromLocal() { export async function getImageFilesFromLocal() {
const imageFiles = await openFileOrFiles({ const imageFiles = await openFilesWith('Images');
acceptType: 'Images',
multiple: true,
});
if (!imageFiles) return []; if (!imageFiles) return [];
return imageFiles; return imageFiles;
} }

View File

@@ -48,7 +48,7 @@ import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { import {
createDefaultDoc, createDefaultDoc,
openFileOrFiles, openSingleFileWith,
type Signal, type Signal,
} from '@blocksuite/affine-shared/utils'; } from '@blocksuite/affine-shared/utils';
import type { AffineLinkedDocWidget } from '@blocksuite/affine-widget-linked-doc'; import type { AffineLinkedDocWidget } from '@blocksuite/affine-widget-linked-doc';
@@ -418,7 +418,7 @@ const contentMediaToolGroup: KeyboardToolPanelGroup = {
const model = selectedModels?.[0]; const model = selectedModels?.[0];
if (!model) return; if (!model) return;
const file = await openFileOrFiles(); const file = await openSingleFileWith();
if (!file) return; if (!file) return;
await addSiblingAttachmentBlocks(std, [file], model); await addSiblingAttachmentBlocks(std, [file], model);
@@ -1040,7 +1040,7 @@ export const defaultKeyboardToolbarConfig: KeyboardToolbarConfig = {
const model = selectedModels?.[0]; const model = selectedModels?.[0];
if (!model) return; if (!model) return;
const file = await openFileOrFiles(); const file = await openSingleFileWith();
if (!file) return; if (!file) return;
await addSiblingAttachmentBlocks(std, [file], model); await addSiblingAttachmentBlocks(std, [file], model);

View File

@@ -6,7 +6,10 @@ import {
NewIcon, NewIcon,
NotionIcon, NotionIcon,
} from '@blocksuite/affine-components/icons'; } from '@blocksuite/affine-components/icons';
import { openFileOrFiles } from '@blocksuite/affine-shared/utils'; import {
openFilesWith,
openSingleFileWith,
} from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/lit'; import { WithDisposable } from '@blocksuite/global/lit';
import type { ExtensionType, Schema, Workspace } from '@blocksuite/store'; import type { ExtensionType, Schema, Workspace } from '@blocksuite/store';
import { html, LitElement, type PropertyValues } from 'lit'; import { html, LitElement, type PropertyValues } from 'lit';
@@ -50,7 +53,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
} }
private async _importHtml() { private async _importHtml() {
const files = await openFileOrFiles({ acceptType: 'Html', multiple: true }); const files = await openFilesWith('Html');
if (!files) return; if (!files) return;
const pageIds: string[] = []; const pageIds: string[] = [];
for (const file of files) { for (const file of files) {
@@ -79,10 +82,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
} }
private async _importMarkDown() { private async _importMarkDown() {
const files = await openFileOrFiles({ const files = await openFilesWith('Markdown');
acceptType: 'Markdown',
multiple: true,
});
if (!files) return; if (!files) return;
const pageIds: string[] = []; const pageIds: string[] = [];
for (const file of files) { for (const file of files) {
@@ -111,7 +111,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
} }
private async _importNotion() { private async _importNotion() {
const file = await openFileOrFiles({ acceptType: 'Zip' }); const file = await openSingleFileWith('Zip');
if (!file) return; if (!file) return;
const needLoading = file.size > SHOW_LOADING_SIZE; const needLoading = file.size > SHOW_LOADING_SIZE;
if (needLoading) { if (needLoading) {

View File

@@ -41,7 +41,11 @@ import {
SizeVariables, SizeVariables,
StyleVariables, StyleVariables,
} from '@blocksuite/affine/shared/theme'; } from '@blocksuite/affine/shared/theme';
import { openFileOrFiles, printToPdf } from '@blocksuite/affine/shared/utils'; import {
openFilesWith,
openSingleFileWith,
printToPdf,
} from '@blocksuite/affine/shared/utils';
import { ShadowlessElement } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std';
import { GfxControllerIdentifier } from '@blocksuite/affine/std/gfx'; import { GfxControllerIdentifier } from '@blocksuite/affine/std/gfx';
import { import {
@@ -339,10 +343,7 @@ export class StarterDebugMenu extends ShadowlessElement {
private async _importHTML() { private async _importHTML() {
try { try {
const files = await openFileOrFiles({ const files = await openFilesWith('Html');
acceptType: 'Html',
multiple: true,
});
if (!files) return; if (!files) return;
@@ -373,7 +374,7 @@ export class StarterDebugMenu extends ShadowlessElement {
private async _importHTMLZip() { private async _importHTMLZip() {
try { try {
const file = await openFileOrFiles({ acceptType: 'Zip' }); const file = await openSingleFileWith('Zip');
if (!file) return; if (!file) return;
const result = await HtmlTransformer.importHTMLZip({ const result = await HtmlTransformer.importHTMLZip({
collection: this.collection, collection: this.collection,
@@ -393,10 +394,7 @@ export class StarterDebugMenu extends ShadowlessElement {
private async _importMarkdown() { private async _importMarkdown() {
try { try {
const files = await openFileOrFiles({ const files = await openFilesWith('Markdown');
acceptType: 'Markdown',
multiple: true,
});
if (!files) return; if (!files) return;
@@ -427,7 +425,7 @@ export class StarterDebugMenu extends ShadowlessElement {
private async _importMarkdownZip() { private async _importMarkdownZip() {
try { try {
const file = await openFileOrFiles({ acceptType: 'Zip' }); const file = await openSingleFileWith('Zip');
if (!file) return; if (!file) return;
const result = await MarkdownTransformer.importMarkdownZip({ const result = await MarkdownTransformer.importMarkdownZip({
collection: this.collection, collection: this.collection,
@@ -447,10 +445,7 @@ export class StarterDebugMenu extends ShadowlessElement {
private async _importNotionHTML() { private async _importNotionHTML() {
try { try {
const file = await openFileOrFiles({ const file = await openSingleFileWith('Html');
acceptType: 'Html',
multiple: false,
});
if (!file) return; if (!file) return;
const doc = this.editor.doc; const doc = this.editor.doc;
const job = doc.getTransformer([defaultImageProxyMiddleware]); const job = doc.getTransformer([defaultImageProxyMiddleware]);
@@ -467,7 +462,7 @@ export class StarterDebugMenu extends ShadowlessElement {
private async _importNotionHTMLZip() { private async _importNotionHTMLZip() {
try { try {
const file = await openFileOrFiles({ acceptType: 'Zip' }); const file = await openSingleFileWith('Zip');
if (!file) return; if (!file) return;
const result = await NotionHtmlTransformer.importNotionZip({ const result = await NotionHtmlTransformer.importNotionZip({
collection: this.collection, collection: this.collection,

View File

@@ -5,7 +5,7 @@ import track from '@affine/track';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import { scrollbarStyle } from '@blocksuite/affine/shared/styles'; import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { openFileOrFiles } from '@blocksuite/affine/shared/utils'; import { openFilesWith } from '@blocksuite/affine/shared/utils';
import { ShadowlessElement } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std';
import type { DocMeta } from '@blocksuite/affine/store'; import type { DocMeta } from '@blocksuite/affine/store';
import { import {
@@ -159,9 +159,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
}; };
private readonly _addFileChip = async () => { private readonly _addFileChip = async () => {
const files = await openFileOrFiles({ const files = await openFilesWith();
multiple: true,
});
if (!files || files.length === 0) return; if (!files || files.length === 0) return;
const images = files.filter(file => file.type.startsWith('image/')); const images = files.filter(file => file.type.startsWith('image/'));

View File

@@ -3,7 +3,7 @@ import { stopPropagation } from '@affine/core/utils';
import type { CopilotSessionType } from '@affine/graphql'; import type { CopilotSessionType } from '@affine/graphql';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { openFileOrFiles } from '@blocksuite/affine/shared/utils'; import { openFilesWith } from '@blocksuite/affine/shared/utils';
import type { EditorHost } from '@blocksuite/affine/std'; import type { EditorHost } from '@blocksuite/affine/std';
import { import {
CloseIcon, CloseIcon,
@@ -533,10 +533,7 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
private readonly _uploadImageFiles = async (_e: MouseEvent) => { private readonly _uploadImageFiles = async (_e: MouseEvent) => {
if (this._isImageUploadDisabled) return; if (this._isImageUploadDisabled) return;
const images = await openFileOrFiles({ const images = await openFilesWith('Images');
acceptType: 'Images',
multiple: true,
});
if (!images) return; if (!images) return;
if (this.chatContextValue.images.length + images.length > MAX_IMAGE_COUNT) { if (this.chatContextValue.images.length + images.length > MAX_IMAGE_COUNT) {
toast(`You can only upload up to ${MAX_IMAGE_COUNT} images`); toast(`You can only upload up to ${MAX_IMAGE_COUNT} images`);

View File

@@ -9,7 +9,7 @@ import {
type DataViewCellLifeCycle, type DataViewCellLifeCycle,
EditorHostKey, EditorHostKey,
} from '@blocksuite/affine/blocks/database'; } from '@blocksuite/affine/blocks/database';
import { openFileOrFiles } from '@blocksuite/affine/shared/utils'; import { openFilesWith } from '@blocksuite/affine/shared/utils';
import type { BlobEngine } from '@blocksuite/affine/sync'; import type { BlobEngine } from '@blocksuite/affine/sync';
import { import {
DeleteIcon, DeleteIcon,
@@ -402,7 +402,7 @@ const FileCellComponent: ForwardRefRenderFunction<
> >
<Button <Button
onClick={() => { onClick={() => {
openFileOrFiles({ multiple: true }) openFilesWith()
.then(files => { .then(files => {
files?.forEach(file => { files?.forEach(file => {
manager.uploadFile(file); manager.uploadFile(file);
@@ -447,7 +447,7 @@ const FileCellComponent: ForwardRefRenderFunction<
<div className={styles.uploadContainer}> <div className={styles.uploadContainer}>
<div <div
onClick={() => { onClick={() => {
openFileOrFiles({ multiple: true }) openFilesWith()
.then(files => { .then(files => {
files?.forEach(file => { files?.forEach(file => {
manager.uploadFile(file); manager.uploadFile(file);

View File

@@ -13,7 +13,7 @@ import {
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import track from '@affine/track'; import track from '@affine/track';
import { openFileOrFiles } from '@blocksuite/affine/shared/utils'; import { openFilesWith } from '@blocksuite/affine/shared/utils';
import type { Workspace } from '@blocksuite/affine/store'; import type { Workspace } from '@blocksuite/affine/store';
import { import {
HtmlTransformer, HtmlTransformer,
@@ -56,7 +56,7 @@ type ImportConfig = {
fileOptions: { acceptType: AcceptType; multiple: boolean }; fileOptions: { acceptType: AcceptType; multiple: boolean };
importFunction: ( importFunction: (
docCollection: Workspace, docCollection: Workspace,
file: File | File[] files: File[]
) => Promise<ImportResult>; ) => Promise<ImportResult>;
}; };
@@ -134,9 +134,6 @@ const importConfigs: Record<ImportType, ImportConfig> = {
markdown: { markdown: {
fileOptions: { acceptType: 'Markdown', multiple: true }, fileOptions: { acceptType: 'Markdown', multiple: true },
importFunction: async (docCollection, files) => { importFunction: async (docCollection, files) => {
if (!Array.isArray(files)) {
throw new Error('Expected an array of files for markdown files import');
}
const docIds: string[] = []; const docIds: string[] = [];
for (const file of files) { for (const file of files) {
const text = await file.text(); const text = await file.text();
@@ -157,8 +154,9 @@ const importConfigs: Record<ImportType, ImportConfig> = {
}, },
markdownZip: { markdownZip: {
fileOptions: { acceptType: 'Zip', multiple: false }, fileOptions: { acceptType: 'Zip', multiple: false },
importFunction: async (docCollection, file) => { importFunction: async (docCollection, files) => {
if (Array.isArray(file)) { const file = files.length === 1 ? files[0] : null;
if (!file) {
throw new Error('Expected a single zip file for markdownZip import'); throw new Error('Expected a single zip file for markdownZip import');
} }
const docIds = await MarkdownTransformer.importMarkdownZip({ const docIds = await MarkdownTransformer.importMarkdownZip({
@@ -175,9 +173,6 @@ const importConfigs: Record<ImportType, ImportConfig> = {
html: { html: {
fileOptions: { acceptType: 'Html', multiple: true }, fileOptions: { acceptType: 'Html', multiple: true },
importFunction: async (docCollection, files) => { importFunction: async (docCollection, files) => {
if (!Array.isArray(files)) {
throw new Error('Expected an array of files for html files import');
}
const docIds: string[] = []; const docIds: string[] = [];
for (const file of files) { for (const file of files) {
const text = await file.text(); const text = await file.text();
@@ -198,8 +193,9 @@ const importConfigs: Record<ImportType, ImportConfig> = {
}, },
notion: { notion: {
fileOptions: { acceptType: 'Zip', multiple: false }, fileOptions: { acceptType: 'Zip', multiple: false },
importFunction: async (docCollection, file) => { importFunction: async (docCollection, files) => {
if (Array.isArray(file)) { const file = files.length === 1 ? files[0] : null;
if (!file) {
throw new Error('Expected a single zip file for notion import'); throw new Error('Expected a single zip file for notion import');
} }
const { entryId, pageIds, isWorkspaceFile } = const { entryId, pageIds, isWorkspaceFile } =
@@ -218,8 +214,9 @@ const importConfigs: Record<ImportType, ImportConfig> = {
}, },
snapshot: { snapshot: {
fileOptions: { acceptType: 'Zip', multiple: false }, fileOptions: { acceptType: 'Zip', multiple: false },
importFunction: async (docCollection, file) => { importFunction: async (docCollection, files) => {
if (Array.isArray(file)) { const file = files.length === 1 ? files[0] : null;
if (!file) {
throw new Error('Expected a single zip file for snapshot import'); throw new Error('Expected a single zip file for snapshot import');
} }
const docIds = ( const docIds = (
@@ -412,9 +409,10 @@ export const ImportDialog = ({
setImportError(null); setImportError(null);
try { try {
const importConfig = importConfigs[type]; const importConfig = importConfigs[type];
const file = await openFileOrFiles(importConfig.fileOptions); const { acceptType, multiple } = importConfig.fileOptions;
const files = await openFilesWith(acceptType, multiple);
if (!file || (Array.isArray(file) && file.length === 0)) { if (!files || files.length === 0) {
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']()
); );
@@ -427,7 +425,7 @@ export const ImportDialog = ({
}); });
const { docIds, entryId, isWorkspaceFile } = const { docIds, entryId, isWorkspaceFile } =
await importConfig.importFunction(docCollection, file); await importConfig.importFunction(docCollection, files);
setImportResult({ docIds, entryId, isWorkspaceFile }); setImportResult({ docIds, entryId, isWorkspaceFile });
setStatus('success'); setStatus('success');