feat: Initial clean release of CHM
一些检查失败了
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
一些检查失败了
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
这个提交包含在:
@@ -0,0 +1,137 @@
|
||||
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;
|
||||
在新工单中引用
屏蔽一个用户