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 => { 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 ( ); }; export default NoteImportButton;