文件
mynode/packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

138 行
4.5 KiB
TypeScript

import * as React from 'react';
import { _ } from '@joplin/lib/locale';
import Logger from '@joplin/utils/Logger';
import { FunctionComponent, useContext } from 'react';
import { join, basename } from 'path';
import { ConfigScreenStyles } from '../configScreenStyles';
import InteropService from '@joplin/lib/services/interop/InteropService';
import pickDocument from '../../../../utils/pickDocument';
import makeImportExportCacheDirectory from './utils/makeImportExportCacheDirectory';
import shim from '@joplin/lib/shim';
import TaskButton, { OnProgressCallback, SetAfterCompleteListenerCallback, TaskStatus } from './TaskButton';
import { Platform } from 'react-native';
import { FolderEntity } from '@joplin/lib/services/database/types';
import Folder from '@joplin/lib/models/Folder';
import { fileExtension } from '@joplin/lib/path-utils';
import { NoteLockPrompts, promptForImportedNoteLockKey } from '@joplin/lib/services/noteLock/noteLockPrompts';
import { ErrorCode } from '@joplin/lib/errors';
import { DialogContext } from '../../../DialogManager';
const logger = Logger.create('NoteImportButton');
interface Props {
styles: ConfigScreenStyles;
defaultTitle: string;
description: string;
format: string;
activeFolder?: FolderEntity;
// Navigating to the Config screen does nothing while it is already open, so the screen switches its own section.
onOpenNoteLockSetup: ()=> void;
}
export const importedFolderTitle = () => {
return _('Imported Notes');
};
export const textImportExtensions = ['md', 'markdown', 'txt', 'html'];
const importedFolder = async () => {
let folder = await Folder.loadByFields({
title: importedFolderTitle(),
deleted_time: 0,
});
if (!folder) {
folder = await Folder.save({ title: importedFolderTitle() });
}
return folder;
};
const NoteImportButton: FunctionComponent<Props> = props => {
const dialogs = useContext(DialogContext);
const noteLockPrompts: NoteLockPrompts = {
promptPassword: label => dialogs.promptForText(label, '', true),
goToNoteLockSetup: props.onOpenNoteLockSetup,
};
const getTitle = (taskStatus: TaskStatus) => {
if (taskStatus === TaskStatus.InProgress) {
return _('Importing...');
} else {
return props.defaultTitle;
}
};
const runImportTask = async (
_onProgress: OnProgressCallback,
setAfterCompleteListener: SetAfterCompleteListenerCallback,
) => {
logger.info(`Importing ${props.format}...`);
const importFiles = await pickDocument({ multiple: false });
if (importFiles.length === 0) {
logger.info('Canceled.');
return { success: false, warnings: [] };
}
const sourceFileUri = importFiles[0].uri;
const sourceFilePath = Platform.select({
default: sourceFileUri,
ios: decodeURIComponent(sourceFileUri),
});
let validExtensions = ['jex'];
if (props.format === 'txt') validExtensions = textImportExtensions;
// importFiles[0].fileName can be null on iOS
const sourceFileName = importFiles[0].fileName ?? basename(sourceFilePath);
const extension = fileExtension(sourceFileName).toLowerCase();
if (!validExtensions.includes(extension)) {
throw new Error(_('Unsupported file extension. Expected: %s', validExtensions.join(', ')));
}
const importTargetPath = join(await makeImportExportCacheDirectory(), sourceFileName);
setAfterCompleteListener(async (_success: boolean) => {
await shim.fsDriver().remove(importTargetPath);
});
await shim.fsDriver().copy(sourceFilePath, importTargetPath);
let activeFolderId = props.activeFolder ? props.activeFolder.id : null;
if (props.format === 'txt' && !activeFolderId) {
activeFolderId = (await importedFolder()).id;
}
try {
const status = await InteropService.instance().import({
path: importTargetPath,
format: props.format,
destinationFolderId: activeFolderId,
onNoteLockKey: backupKey => promptForImportedNoteLockKey(backupKey, noteLockPrompts),
});
logger.info('Imported successfully');
return { success: true, warnings: status.warnings };
} catch (error) {
if (error.code === ErrorCode.Cancelled) {
logger.info('Canceled.');
return { success: false, warnings: [] };
}
logger.error('Import failed with error', error);
throw new Error(_('Import failed. Make sure a %s file was selected.\nDetails: %s', props.format, error.toString()));
}
};
return (
<TaskButton
taskName={props.defaultTitle}
description={props.description}
buttonLabel={getTitle}
finishedLabel={_('Imported successfully!')}
styles={props.styles}
onRunTask={runImportTask}
/>
);
};
export default NoteImportButton;