mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-08 13:06:15 +02:00
Chore: Refactor InteropService to not use dynamic imports (#8454)
This commit is contained in:
parent
93e4004033
commit
d95d6733a1
@ -638,6 +638,8 @@ packages/lib/services/interop/InteropService_Importer_Md_frontmatter.js
|
||||
packages/lib/services/interop/InteropService_Importer_Md_frontmatter.test.js
|
||||
packages/lib/services/interop/InteropService_Importer_Raw.js
|
||||
packages/lib/services/interop/InteropService_Importer_Raw.test.js
|
||||
packages/lib/services/interop/Module.js
|
||||
packages/lib/services/interop/Module.test.js
|
||||
packages/lib/services/interop/types.js
|
||||
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js
|
||||
packages/lib/services/keychain/KeychainService.js
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -623,6 +623,8 @@ packages/lib/services/interop/InteropService_Importer_Md_frontmatter.js
|
||||
packages/lib/services/interop/InteropService_Importer_Md_frontmatter.test.js
|
||||
packages/lib/services/interop/InteropService_Importer_Raw.js
|
||||
packages/lib/services/interop/InteropService_Importer_Raw.test.js
|
||||
packages/lib/services/interop/Module.js
|
||||
packages/lib/services/interop/Module.test.js
|
||||
packages/lib/services/interop/types.js
|
||||
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js
|
||||
packages/lib/services/keychain/KeychainService.js
|
||||
|
@ -1,7 +1,8 @@
|
||||
import InteropService from '@joplin/lib/services/interop/InteropService';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import { ExportOptions, FileSystemItem, Module } from '@joplin/lib/services/interop/types';
|
||||
import { ExportOptions, FileSystemItem } from '@joplin/lib/services/interop/types';
|
||||
import { ExportModule } from '@joplin/lib/services/interop/Module';
|
||||
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
@ -152,7 +153,7 @@ export default class InteropServiceHelper {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
public static async export(_dispatch: Function, module: Module, options: ExportNoteOptions = null) {
|
||||
public static async export(_dispatch: Function, module: ExportModule, options: ExportNoteOptions = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
let path = null;
|
||||
|
@ -9,7 +9,7 @@ import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins
|
||||
import shim from '@joplin/lib/shim';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import versionInfo from '@joplin/lib/versionInfo';
|
||||
import { Module } from '@joplin/lib/services/interop/types';
|
||||
import { ImportModule } from '@joplin/lib/services/interop/Module';
|
||||
import InteropServiceHelper from '../InteropServiceHelper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { isContextMenuItemLocation, MenuItem, MenuItemLocation } from '@joplin/lib/services/plugins/api/types';
|
||||
@ -230,7 +230,7 @@ function useMenu(props: Props) {
|
||||
void CommandService.instance().execute(commandName);
|
||||
}, []);
|
||||
|
||||
const onImportModuleClick = useCallback(async (module: Module, moduleSource: string) => {
|
||||
const onImportModuleClick = useCallback(async (module: ImportModule, moduleSource: string) => {
|
||||
let path = null;
|
||||
|
||||
if (moduleSource === 'file') {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import InteropService from '../../services/interop/InteropService';
|
||||
import { CustomExportContext, CustomImportContext, Module, ModuleType } from '../../services/interop/types';
|
||||
import { CustomExportContext, CustomImportContext, ModuleType } from '../../services/interop/types';
|
||||
import shim from '../../shim';
|
||||
import { fileContentEqual, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync, exportDir, supportDir } from '../../testing/test-utils';
|
||||
import Folder from '../../models/Folder';
|
||||
@ -10,6 +10,9 @@ import * as fs from 'fs-extra';
|
||||
import { FolderEntity, NoteEntity, ResourceEntity } from '../database/types';
|
||||
import { ModelType } from '../../BaseModel';
|
||||
import * as ArrayUtils from '../../ArrayUtils';
|
||||
import InteropService_Importer_Custom from './InteropService_Importer_Custom';
|
||||
import InteropService_Exporter_Custom from './InteropService_Exporter_Custom';
|
||||
import Module, { makeExportModule, makeImportModule } from './Module';
|
||||
|
||||
async function recreateExportDir() {
|
||||
const dir = exportDir();
|
||||
@ -47,35 +50,35 @@ function memoryExportModule() {
|
||||
resources: [],
|
||||
};
|
||||
|
||||
const module: Module = {
|
||||
type: ModuleType.Exporter,
|
||||
const module: Module = makeExportModule({
|
||||
description: 'Memory Export Module',
|
||||
format: 'memory',
|
||||
fileExtensions: ['memory'],
|
||||
isCustom: true,
|
||||
}, () => {
|
||||
return new InteropService_Exporter_Custom({
|
||||
onInit: async (context: CustomExportContext) => {
|
||||
result.destPath = context.destPath;
|
||||
},
|
||||
|
||||
onInit: async (context: CustomExportContext) => {
|
||||
result.destPath = context.destPath;
|
||||
},
|
||||
onProcessItem: async (_context: CustomExportContext, itemType: number, item: any) => {
|
||||
result.items.push({
|
||||
type: itemType,
|
||||
object: item,
|
||||
});
|
||||
},
|
||||
|
||||
onProcessItem: async (_context: CustomExportContext, itemType: number, item: any) => {
|
||||
result.items.push({
|
||||
type: itemType,
|
||||
object: item,
|
||||
});
|
||||
},
|
||||
onProcessResource: async (_context: CustomExportContext, resource: any, filePath: string) => {
|
||||
result.resources.push({
|
||||
filePath: filePath,
|
||||
object: resource,
|
||||
});
|
||||
},
|
||||
|
||||
onProcessResource: async (_context: CustomExportContext, resource: any, filePath: string) => {
|
||||
result.resources.push({
|
||||
filePath: filePath,
|
||||
object: resource,
|
||||
});
|
||||
},
|
||||
|
||||
onClose: async (_context: CustomExportContext) => {
|
||||
// nothing
|
||||
},
|
||||
};
|
||||
onClose: async (_context: CustomExportContext) => {
|
||||
// nothing
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return { result, module };
|
||||
}
|
||||
@ -555,18 +558,19 @@ describe('services_InteropService', () => {
|
||||
sourcePath: '',
|
||||
};
|
||||
|
||||
const module: Module = {
|
||||
const module = makeImportModule({
|
||||
type: ModuleType.Importer,
|
||||
description: 'Test Import Module',
|
||||
format: 'testing',
|
||||
fileExtensions: ['test'],
|
||||
isCustom: true,
|
||||
|
||||
onExec: async (context: CustomImportContext) => {
|
||||
result.hasBeenExecuted = true;
|
||||
result.sourcePath = context.sourcePath;
|
||||
},
|
||||
};
|
||||
}, () => {
|
||||
return new InteropService_Importer_Custom({
|
||||
onExec: async (context: CustomImportContext) => {
|
||||
result.hasBeenExecuted = true;
|
||||
result.sourcePath = context.sourcePath;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const service = InteropService.instance();
|
||||
service.registerModule(module);
|
||||
@ -596,31 +600,32 @@ describe('services_InteropService', () => {
|
||||
closeCalled: false,
|
||||
};
|
||||
|
||||
const module: Module = {
|
||||
const module: Module = makeExportModule({
|
||||
type: ModuleType.Exporter,
|
||||
description: 'Test Export Module',
|
||||
format: 'testing',
|
||||
fileExtensions: ['test'],
|
||||
isCustom: true,
|
||||
}, () => {
|
||||
return new InteropService_Exporter_Custom({
|
||||
onInit: async (context: CustomExportContext) => {
|
||||
result.destPath = context.destPath;
|
||||
},
|
||||
|
||||
onInit: async (context: CustomExportContext) => {
|
||||
result.destPath = context.destPath;
|
||||
},
|
||||
onProcessItem: async (_context: CustomExportContext, itemType: number, item: any) => {
|
||||
result.itemTypes.push(itemType);
|
||||
result.items.push(item);
|
||||
},
|
||||
|
||||
onProcessItem: async (_context: CustomExportContext, itemType: number, item: any) => {
|
||||
result.itemTypes.push(itemType);
|
||||
result.items.push(item);
|
||||
},
|
||||
onProcessResource: async (_context: CustomExportContext, resource: any, filePath: string) => {
|
||||
result.resources.push(resource);
|
||||
result.filePaths.push(filePath);
|
||||
},
|
||||
|
||||
onProcessResource: async (_context: CustomExportContext, resource: any, filePath: string) => {
|
||||
result.resources.push(resource);
|
||||
result.filePaths.push(filePath);
|
||||
},
|
||||
|
||||
onClose: async (_context: CustomExportContext) => {
|
||||
result.closeCalled = true;
|
||||
},
|
||||
};
|
||||
onClose: async (_context: CustomExportContext) => {
|
||||
result.closeCalled = true;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const service = InteropService.instance();
|
||||
service.registerModule(module);
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { ModuleType, FileSystemItem, ImportModuleOutputFormat, Module, ImportOptions, ExportOptions, ImportExportResult, defaultImportExportModule } from './types';
|
||||
import InteropService_Importer_Custom from './InteropService_Importer_Custom';
|
||||
import InteropService_Exporter_Custom from './InteropService_Exporter_Custom';
|
||||
import { ModuleType, FileSystemItem, ImportModuleOutputFormat, ImportOptions, ExportOptions, ImportExportResult } from './types';
|
||||
import shim from '../../shim';
|
||||
import { _ } from '../../locale';
|
||||
import BaseItem from '../../models/BaseItem';
|
||||
@ -10,9 +8,22 @@ import Folder from '../../models/Folder';
|
||||
import NoteTag from '../../models/NoteTag';
|
||||
import Note from '../../models/Note';
|
||||
import * as ArrayUtils from '../../ArrayUtils';
|
||||
import InteropService_Importer_Jex from './InteropService_Importer_Jex';
|
||||
import InteropService_Importer_Md from './InteropService_Importer_Md';
|
||||
import InteropService_Importer_Md_frontmatter from './InteropService_Importer_Md_frontmatter';
|
||||
import InteropService_Importer_Raw from './InteropService_Importer_Raw';
|
||||
import InteropService_Importer_EnexToMd from './InteropService_Importer_EnexToMd';
|
||||
import InteropService_Importer_EnexToHtml from './InteropService_Importer_EnexToHtml';
|
||||
import InteropService_Exporter_Jex from './InteropService_Exporter_Jex';
|
||||
import InteropService_Exporter_Raw from './InteropService_Exporter_Raw';
|
||||
import InteropService_Exporter_Md from './InteropService_Exporter_Md';
|
||||
import InteropService_Exporter_Md_frontmatter from './InteropService_Exporter_Md_frontmatter';
|
||||
import InteropService_Exporter_Html from './InteropService_Exporter_Html';
|
||||
import InteropService_Importer_Base from './InteropService_Importer_Base';
|
||||
import InteropService_Exporter_Base from './InteropService_Exporter_Base';
|
||||
import Module, { makeExportModule, makeImportModule } from './Module';
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const { fileExtension } = require('../../path-utils');
|
||||
const { toTitleCase } = require('../../string-utils');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
export default class InteropService {
|
||||
@ -43,47 +54,46 @@ export default class InteropService {
|
||||
|
||||
public modules() {
|
||||
if (!this.defaultModules_) {
|
||||
const importModules: Module[] = [
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Importer),
|
||||
const importModules = [
|
||||
makeImportModule({
|
||||
format: 'jex',
|
||||
fileExtensions: ['jex'],
|
||||
sources: [FileSystemItem.File],
|
||||
description: _('Joplin Export File'),
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Importer),
|
||||
}, () => new InteropService_Importer_Jex()),
|
||||
|
||||
makeImportModule({
|
||||
format: 'md',
|
||||
fileExtensions: ['md', 'markdown', 'txt', 'html'],
|
||||
sources: [FileSystemItem.File, FileSystemItem.Directory],
|
||||
isNoteArchive: false, // Tells whether the file can contain multiple notes (eg. Enex or Jex format)
|
||||
description: _('Markdown'),
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Importer),
|
||||
}, () => new InteropService_Importer_Md()),
|
||||
|
||||
makeImportModule({
|
||||
format: 'md_frontmatter',
|
||||
fileExtensions: ['md', 'markdown', 'txt', 'html'],
|
||||
sources: [FileSystemItem.File, FileSystemItem.Directory],
|
||||
isNoteArchive: false, // Tells whether the file can contain multiple notes (eg. Enex or Jex format)
|
||||
description: _('Markdown + Front Matter'),
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Importer),
|
||||
}, () => new InteropService_Importer_Md_frontmatter()),
|
||||
|
||||
makeImportModule({
|
||||
format: 'raw',
|
||||
sources: [FileSystemItem.Directory],
|
||||
description: _('Joplin Export Directory'),
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Importer),
|
||||
}, () => new InteropService_Importer_Raw()),
|
||||
|
||||
makeImportModule({
|
||||
format: 'enex',
|
||||
fileExtensions: ['enex'],
|
||||
sources: [FileSystemItem.File],
|
||||
description: _('Evernote Export File (as Markdown)'),
|
||||
importerClass: 'InteropService_Importer_EnexToMd',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Importer),
|
||||
}, () => new InteropService_Importer_EnexToMd()),
|
||||
|
||||
makeImportModule({
|
||||
format: 'enex',
|
||||
fileExtensions: ['enex'],
|
||||
sources: [FileSystemItem.File],
|
||||
@ -91,65 +101,58 @@ export default class InteropService {
|
||||
// TODO: Consider doing this the same way as the multiple `md` importers are handled
|
||||
importerClass: 'InteropService_Importer_EnexToHtml',
|
||||
outputFormat: ImportModuleOutputFormat.Html,
|
||||
},
|
||||
}, () => new InteropService_Importer_EnexToHtml()),
|
||||
];
|
||||
|
||||
const exportModules: Module[] = [
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Exporter),
|
||||
const exportModules = [
|
||||
makeExportModule({
|
||||
format: 'jex',
|
||||
fileExtensions: ['jex'],
|
||||
target: FileSystemItem.File,
|
||||
description: _('Joplin Export File'),
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Exporter),
|
||||
}, () => new InteropService_Exporter_Jex()),
|
||||
|
||||
makeExportModule({
|
||||
format: 'raw',
|
||||
target: FileSystemItem.Directory,
|
||||
description: _('Joplin Export Directory'),
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Exporter),
|
||||
}, () => new InteropService_Exporter_Raw()),
|
||||
|
||||
makeExportModule({
|
||||
format: 'md',
|
||||
target: FileSystemItem.Directory,
|
||||
description: _('Markdown'),
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Exporter),
|
||||
}, () => new InteropService_Exporter_Md()),
|
||||
|
||||
makeExportModule({
|
||||
format: 'md_frontmatter',
|
||||
target: FileSystemItem.Directory,
|
||||
description: _('Markdown + Front Matter'),
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Exporter),
|
||||
}, () => new InteropService_Exporter_Md_frontmatter()),
|
||||
|
||||
makeExportModule({
|
||||
format: 'html',
|
||||
fileExtensions: ['html', 'htm'],
|
||||
target: FileSystemItem.File,
|
||||
isNoteArchive: false,
|
||||
description: _('HTML File'),
|
||||
},
|
||||
{
|
||||
...defaultImportExportModule(ModuleType.Exporter),
|
||||
}, () => new InteropService_Exporter_Html()),
|
||||
|
||||
makeExportModule({
|
||||
format: 'html',
|
||||
target: FileSystemItem.Directory,
|
||||
description: _('HTML Directory'),
|
||||
},
|
||||
}, () => new InteropService_Exporter_Html()),
|
||||
];
|
||||
|
||||
this.defaultModules_ = importModules.concat(exportModules);
|
||||
this.defaultModules_ = (importModules as Module[]).concat(exportModules);
|
||||
}
|
||||
|
||||
return this.defaultModules_.concat(this.userModules_);
|
||||
}
|
||||
|
||||
public registerModule(module: Module) {
|
||||
module = {
|
||||
...defaultImportExportModule(module.type),
|
||||
...module,
|
||||
};
|
||||
|
||||
this.userModules_.push(module);
|
||||
|
||||
this.eventEmitter_.emit('modulesChanged');
|
||||
}
|
||||
|
||||
@ -166,9 +169,13 @@ export default class InteropService {
|
||||
if (m.format === format && m.type === type) {
|
||||
if (!target && !outputFormat) {
|
||||
matches.push(m);
|
||||
} else if (target && target === m.target) {
|
||||
} else if (
|
||||
m.type === ModuleType.Exporter && target && target === m.target
|
||||
) {
|
||||
matches.push(m);
|
||||
} else if (outputFormat && outputFormat === m.outputFormat) {
|
||||
} else if (
|
||||
m.type === ModuleType.Importer && outputFormat && outputFormat === m.outputFormat
|
||||
) {
|
||||
matches.push(m);
|
||||
}
|
||||
}
|
||||
@ -180,24 +187,6 @@ export default class InteropService {
|
||||
return matches.length ? matches[0] : null;
|
||||
}
|
||||
|
||||
private modulePath(module: Module) {
|
||||
let className = '';
|
||||
if (module.type === ModuleType.Importer) {
|
||||
className = module.importerClass || `InteropService_Importer_${toTitleCase(module.format)}`;
|
||||
} else {
|
||||
className = `InteropService_Exporter_${toTitleCase(module.format)}`;
|
||||
}
|
||||
return `./${className}`;
|
||||
}
|
||||
|
||||
private newModuleFromCustomFactory(module: Module) {
|
||||
if (module.type === ModuleType.Importer) {
|
||||
return new InteropService_Importer_Custom(module);
|
||||
} else {
|
||||
return new InteropService_Exporter_Custom(module);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE TO FUTURE SELF: It might make sense to simply move all the existing
|
||||
// formatters to the `newModuleFromPath_` approach, so that there's only one way
|
||||
// to do this mapping. This isn't a priority right now (per the convo in:
|
||||
@ -207,18 +196,7 @@ export default class InteropService {
|
||||
const moduleMetadata = this.findModuleByFormat_(type, format, null, outputFormat);
|
||||
if (!moduleMetadata) throw new Error(_('Cannot load "%s" module for format "%s" and output "%s"', type, format, outputFormat));
|
||||
|
||||
let output = null;
|
||||
|
||||
if (moduleMetadata.isCustom) {
|
||||
output = this.newModuleFromCustomFactory(moduleMetadata);
|
||||
} else {
|
||||
const ModuleClass = shim.requireDynamic(this.modulePath(moduleMetadata)).default;
|
||||
output = new ModuleClass();
|
||||
}
|
||||
|
||||
output.setMetadata(moduleMetadata);
|
||||
|
||||
return output;
|
||||
return moduleMetadata.factory();
|
||||
}
|
||||
|
||||
// The existing `newModuleByFormat_` fn would load by the input format. This
|
||||
@ -231,19 +209,7 @@ export default class InteropService {
|
||||
const moduleMetadata = this.findModuleByFormat_(type, options.format, options.target);
|
||||
if (!moduleMetadata) throw new Error(_('Cannot load "%s" module for format "%s" and target "%s"', type, options.format, options.target));
|
||||
|
||||
let output = null;
|
||||
|
||||
if (moduleMetadata.isCustom) {
|
||||
output = this.newModuleFromCustomFactory(moduleMetadata);
|
||||
} else {
|
||||
const modulePath = this.modulePath(moduleMetadata);
|
||||
const ModuleClass = shim.requireDynamic(modulePath).default;
|
||||
output = new ModuleClass();
|
||||
}
|
||||
|
||||
output.setMetadata({ options, ...moduleMetadata });
|
||||
|
||||
return output;
|
||||
return moduleMetadata.factory(options);
|
||||
}
|
||||
|
||||
private moduleByFileExtension_(type: ModuleType, ext: string) {
|
||||
@ -254,7 +220,7 @@ export default class InteropService {
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const m = modules[i];
|
||||
if (type !== m.type) continue;
|
||||
if (m.fileExtensions && m.fileExtensions.indexOf(ext) >= 0) return m;
|
||||
if (m.fileExtensions.includes(ext)) return m;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -273,14 +239,12 @@ export default class InteropService {
|
||||
if (options.format === 'auto') {
|
||||
const module = this.moduleByFileExtension_(ModuleType.Importer, fileExtension(options.path));
|
||||
if (!module) throw new Error(_('Please specify import format for %s', options.path));
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
options.format = module.format;
|
||||
}
|
||||
|
||||
if (options.destinationFolderId) {
|
||||
const folder = await Folder.load(options.destinationFolderId);
|
||||
if (!folder) throw new Error(_('Cannot find "%s".', options.destinationFolderId));
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
options.destinationFolder = folder;
|
||||
}
|
||||
|
||||
@ -288,6 +252,10 @@ export default class InteropService {
|
||||
|
||||
const importer = this.newModuleByFormat_(ModuleType.Importer, options.format, options.outputFormat);
|
||||
|
||||
if (!(importer instanceof InteropService_Importer_Base)) {
|
||||
throw new Error('Resolved importer is not an importer');
|
||||
}
|
||||
|
||||
await importer.init(options.path, options);
|
||||
result = await importer.exec(result);
|
||||
|
||||
@ -386,6 +354,10 @@ export default class InteropService {
|
||||
}
|
||||
|
||||
const exporter = this.newModuleFromPath_(ModuleType.Exporter, options);
|
||||
if (!(exporter instanceof InteropService_Exporter_Base)) {
|
||||
throw new Error('Resolved exporter is not an exporter');
|
||||
}
|
||||
|
||||
await exporter.init(exportPath, options);
|
||||
|
||||
const typeOrder = [BaseModel.TYPE_FOLDER, BaseModel.TYPE_RESOURCE, BaseModel.TYPE_NOTE, BaseModel.TYPE_TAG, BaseModel.TYPE_NOTE_TAG];
|
||||
|
@ -1,13 +1,20 @@
|
||||
import { ExportContext } from '../plugins/api/types';
|
||||
import InteropService_Exporter_Base from './InteropService_Exporter_Base';
|
||||
import { ExportOptions, Module } from './types';
|
||||
import { ExportOptions } from './types';
|
||||
|
||||
interface CustomImporter {
|
||||
onInit(context: any): Promise<void>;
|
||||
onProcessItem(context: any, itemType: number, item: any): Promise<void>;
|
||||
onProcessResource(context: any, resource: any, filePath: string): Promise<void>;
|
||||
onClose(context: any): Promise<void>;
|
||||
}
|
||||
|
||||
export default class InteropService_Exporter_Custom extends InteropService_Exporter_Base {
|
||||
|
||||
private customContext_: ExportContext;
|
||||
private module_: Module = null;
|
||||
private module_: CustomImporter = null;
|
||||
|
||||
public constructor(module: Module) {
|
||||
public constructor(module: CustomImporter) {
|
||||
super();
|
||||
this.module_ = module;
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
import InteropService_Importer_Base from './InteropService_Importer_Base';
|
||||
import { ImportExportResult, Module } from './types';
|
||||
import { ImportExportResult } from './types';
|
||||
|
||||
interface CustomImporter {
|
||||
onExec(
|
||||
context: { sourcePath: string; options: any; warnings: string[] }
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export default class InteropService_Importer_Custom extends InteropService_Importer_Base {
|
||||
|
||||
private module_: Module = null;
|
||||
private module_: CustomImporter = null;
|
||||
|
||||
public constructor(handler: Module) {
|
||||
public constructor(handler: CustomImporter) {
|
||||
super();
|
||||
this.module_ = handler;
|
||||
}
|
||||
@ -23,10 +29,12 @@ export default class InteropService_Importer_Custom extends InteropService_Impor
|
||||
}
|
||||
}
|
||||
|
||||
return this.module_.onExec({
|
||||
await this.module_.onExec({
|
||||
sourcePath: this.sourcePath_,
|
||||
options: processedOptions,
|
||||
warnings: result.warnings,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
53
packages/lib/services/interop/Module.test.ts
Normal file
53
packages/lib/services/interop/Module.test.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import InteropService_Exporter_Base from './InteropService_Exporter_Base';
|
||||
import InteropService_Importer_Base from './InteropService_Importer_Base';
|
||||
import { makeExportModule, makeImportModule } from './Module';
|
||||
import { FileSystemItem } from './types';
|
||||
|
||||
describe('Module', () => {
|
||||
it('should return correct default fullLabel for an ImportModule', () => {
|
||||
const baseMetadata = {
|
||||
format: 'Foo_test',
|
||||
description: 'Some description here',
|
||||
sources: [FileSystemItem.File, FileSystemItem.Directory],
|
||||
};
|
||||
|
||||
const importModuleMultiSource = makeImportModule(
|
||||
baseMetadata,
|
||||
() => new InteropService_Importer_Base()
|
||||
);
|
||||
|
||||
const importModuleSingleSource = makeImportModule({
|
||||
...baseMetadata,
|
||||
sources: [FileSystemItem.File],
|
||||
}, () => new InteropService_Importer_Base());
|
||||
|
||||
// The two modules should have the same data, except for their sources.
|
||||
expect(importModuleMultiSource.format).toBe('Foo_test');
|
||||
expect(importModuleSingleSource.format).toBe(importModuleMultiSource.format);
|
||||
expect(importModuleMultiSource.sources).toHaveLength(2);
|
||||
expect(importModuleSingleSource.sources).toHaveLength(1);
|
||||
|
||||
const baseLabel = 'FOO - Some description here';
|
||||
expect(importModuleMultiSource.fullLabel()).toBe(baseLabel);
|
||||
expect(importModuleSingleSource.fullLabel()).toBe(baseLabel);
|
||||
|
||||
// Should only include (File) if the import module has more than one source
|
||||
expect(importModuleMultiSource.fullLabel(FileSystemItem.File)).toBe(`${baseLabel} (File)`);
|
||||
expect(importModuleSingleSource.fullLabel(FileSystemItem.File)).toBe(baseLabel);
|
||||
});
|
||||
|
||||
it('should return correct default fullLabel for an ExportModule', () => {
|
||||
const testExportModule = makeExportModule({
|
||||
format: 'format_test_______TEST',
|
||||
description: 'Testing...',
|
||||
}, () => new InteropService_Exporter_Base());
|
||||
|
||||
// Should only include the portion of format before the first underscore
|
||||
const label = 'FORMAT - Testing...';
|
||||
expect(testExportModule.fullLabel()).toBe(label);
|
||||
|
||||
// Sources should only be shown for import modules
|
||||
expect(testExportModule.fullLabel(FileSystemItem.File)).toBe(label);
|
||||
expect(testExportModule.fullLabel(FileSystemItem.Directory)).toBe(label);
|
||||
});
|
||||
});
|
123
packages/lib/services/interop/Module.ts
Normal file
123
packages/lib/services/interop/Module.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { _ } from '../../locale';
|
||||
import InteropService_Exporter_Base from './InteropService_Exporter_Base';
|
||||
import InteropService_Importer_Base from './InteropService_Importer_Base';
|
||||
import { ExportOptions, FileSystemItem, ImportModuleOutputFormat, ImportOptions, ModuleType } from './types';
|
||||
|
||||
// Metadata shared between importers and exporters.
|
||||
interface BaseMetadata {
|
||||
format: string;
|
||||
fileExtensions: string[];
|
||||
description: string;
|
||||
isDefault: boolean;
|
||||
|
||||
// Returns the full label to be displayed in the UI.
|
||||
fullLabel(moduleSource?: FileSystemItem): string;
|
||||
|
||||
// Only applies to single file exporters or importers
|
||||
// It tells whether the format can package multiple notes into one file.
|
||||
// For example JEX or ENEX can, but HTML cannot.
|
||||
// Default: true.
|
||||
isNoteArchive: boolean;
|
||||
}
|
||||
|
||||
interface ImportMetadata extends BaseMetadata {
|
||||
type: ModuleType.Importer;
|
||||
|
||||
sources: FileSystemItem[];
|
||||
importerClass: string;
|
||||
outputFormat: ImportModuleOutputFormat;
|
||||
}
|
||||
|
||||
export interface ImportModule extends ImportMetadata {
|
||||
factory(options?: ImportOptions): InteropService_Importer_Base;
|
||||
}
|
||||
|
||||
interface ExportMetadata extends BaseMetadata {
|
||||
type: ModuleType.Exporter;
|
||||
|
||||
target: FileSystemItem;
|
||||
}
|
||||
|
||||
export interface ExportModule extends ExportMetadata {
|
||||
factory(options?: ExportOptions): InteropService_Exporter_Base;
|
||||
}
|
||||
|
||||
const defaultBaseMetadata = {
|
||||
format: '',
|
||||
fileExtensions: [] as string[],
|
||||
description: '',
|
||||
isNoteArchive: true,
|
||||
isDefault: false,
|
||||
};
|
||||
|
||||
const moduleFullLabel = (metadata: ImportMetadata|ExportMetadata, moduleSource: FileSystemItem = null) => {
|
||||
const format = metadata.format.split('_')[0];
|
||||
const label = [`${format.toUpperCase()} - ${metadata.description}`];
|
||||
if (moduleSource && metadata.type === ModuleType.Importer && metadata.sources.length > 1) {
|
||||
label.push(`(${moduleSource === FileSystemItem.File ? _('File') : _('Directory')})`);
|
||||
}
|
||||
return label.join(' ');
|
||||
};
|
||||
|
||||
export const makeImportModule = (
|
||||
metadata: Partial<ImportMetadata>, factory: ()=> InteropService_Importer_Base
|
||||
): ImportModule => {
|
||||
const importerDefaults: ImportMetadata = {
|
||||
...defaultBaseMetadata,
|
||||
type: ModuleType.Importer,
|
||||
sources: [],
|
||||
importerClass: '',
|
||||
outputFormat: ImportModuleOutputFormat.Markdown,
|
||||
|
||||
fullLabel: (moduleSource?: FileSystemItem) => {
|
||||
return moduleFullLabel(fullMetadata, moduleSource);
|
||||
},
|
||||
};
|
||||
|
||||
const fullMetadata = {
|
||||
...importerDefaults,
|
||||
...metadata,
|
||||
};
|
||||
|
||||
return {
|
||||
...fullMetadata,
|
||||
factory: (options: ImportOptions = {}) => {
|
||||
const result = factory();
|
||||
result.setMetadata({ ...fullMetadata, ...(options ?? {}) });
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const makeExportModule = (
|
||||
metadata: Partial<ExportMetadata>, factory: ()=> InteropService_Exporter_Base
|
||||
): ExportModule => {
|
||||
const exporterDefaults: ExportMetadata = {
|
||||
...defaultBaseMetadata,
|
||||
type: ModuleType.Exporter,
|
||||
target: FileSystemItem.File,
|
||||
|
||||
fullLabel: (moduleSource?: FileSystemItem) => {
|
||||
return moduleFullLabel(fullMetadata, moduleSource);
|
||||
},
|
||||
};
|
||||
|
||||
const fullMetadata = {
|
||||
...exporterDefaults,
|
||||
...metadata,
|
||||
};
|
||||
|
||||
return {
|
||||
...fullMetadata,
|
||||
factory: (options: ExportOptions = {}) => {
|
||||
const result = factory();
|
||||
result.setMetadata({ ...fullMetadata, ...(options ?? {}) });
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type Module = ImportModule|ExportModule;
|
||||
export default Module;
|
@ -1,4 +1,3 @@
|
||||
import { _ } from '../../locale';
|
||||
import { PluginStates } from '../plugins/reducer';
|
||||
|
||||
export interface CustomImportContext {
|
||||
@ -27,58 +26,6 @@ export enum ImportModuleOutputFormat {
|
||||
Html = 'html',
|
||||
}
|
||||
|
||||
// For historical reasons the import and export modules share the same
|
||||
// interface, except that some properties are used only for import
|
||||
// and others only for export.
|
||||
export interface Module {
|
||||
// ---------------------------------------
|
||||
// Shared properties
|
||||
// ---------------------------------------
|
||||
|
||||
type: ModuleType;
|
||||
format: string;
|
||||
fileExtensions: string[];
|
||||
description: string;
|
||||
// path?: string;
|
||||
|
||||
// Only applies to single file exporters or importers
|
||||
// It tells whether the format can package multiple notes into one file.
|
||||
// For example JEX or ENEX can, but HTML cannot.
|
||||
// Default: true.
|
||||
isNoteArchive?: boolean;
|
||||
|
||||
// A custom module is one that was not hard-coded, that was created at runtime
|
||||
// by a plugin for example. If `isCustom` is `true` if it is expected that all
|
||||
// the event handlers below are defined (it's enforced by the plugin API).
|
||||
isCustom?: boolean;
|
||||
|
||||
// ---------------------------------------
|
||||
// Import-only properties
|
||||
// ---------------------------------------
|
||||
|
||||
sources?: FileSystemItem[];
|
||||
importerClass?: string;
|
||||
outputFormat?: ImportModuleOutputFormat;
|
||||
isDefault?: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
fullLabel?: Function;
|
||||
|
||||
// Used only if `isCustom` is true
|
||||
onExec?(context: any): Promise<any>;
|
||||
|
||||
// ---------------------------------------
|
||||
// Export-only properties
|
||||
// ---------------------------------------
|
||||
|
||||
target?: FileSystemItem;
|
||||
|
||||
// Used only if `isCustom` is true
|
||||
onInit?(context: any): Promise<void>;
|
||||
onProcessItem?(context: any, itemType: number, item: any): Promise<void>;
|
||||
onProcessResource?(context: any, resource: any, filePath: string): Promise<void>;
|
||||
onClose?(context: any): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ImportOptions {
|
||||
path?: string;
|
||||
format?: string;
|
||||
@ -119,29 +66,3 @@ export interface MdFrontMatterExport {
|
||||
'created'?: string;
|
||||
'tags'?: string[];
|
||||
}
|
||||
|
||||
function moduleFullLabel(moduleSource: FileSystemItem = null): string {
|
||||
const format = this.format.split('_')[0];
|
||||
const label = [`${format.toUpperCase()} - ${this.description}`];
|
||||
if (moduleSource && this.sources.length > 1) {
|
||||
label.push(`(${moduleSource === 'file' ? _('File') : _('Directory')})`);
|
||||
}
|
||||
return label.join(' ');
|
||||
}
|
||||
|
||||
export function defaultImportExportModule(type: ModuleType): Module {
|
||||
return {
|
||||
type: type,
|
||||
format: '',
|
||||
fileExtensions: [],
|
||||
sources: [],
|
||||
description: '',
|
||||
isNoteArchive: true,
|
||||
importerClass: '',
|
||||
outputFormat: ImportModuleOutputFormat.Markdown,
|
||||
isDefault: false,
|
||||
fullLabel: moduleFullLabel,
|
||||
isCustom: false,
|
||||
target: FileSystemItem.File,
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
/* eslint-disable multiline-comment-style */
|
||||
|
||||
import InteropService from '../../interop/InteropService';
|
||||
import { Module, ModuleType } from '../../interop/types';
|
||||
import InteropService_Exporter_Custom from '../../interop/InteropService_Exporter_Custom';
|
||||
import InteropService_Importer_Custom from '../../interop/InteropService_Importer_Custom';
|
||||
import { makeExportModule, makeImportModule } from '../../interop/Module';
|
||||
import { ModuleType } from '../../interop/types';
|
||||
import { ExportModule, ImportModule } from './types';
|
||||
|
||||
/**
|
||||
@ -19,23 +22,23 @@ import { ExportModule, ImportModule } from './types';
|
||||
export default class JoplinInterop {
|
||||
|
||||
public async registerExportModule(module: ExportModule) {
|
||||
const internalModule: Module = {
|
||||
const internalModule = makeExportModule({
|
||||
...module,
|
||||
type: ModuleType.Exporter,
|
||||
isCustom: true,
|
||||
fileExtensions: module.fileExtensions ? module.fileExtensions : [],
|
||||
};
|
||||
}, () => new InteropService_Exporter_Custom(module));
|
||||
|
||||
return InteropService.instance().registerModule(internalModule);
|
||||
}
|
||||
|
||||
public async registerImportModule(module: ImportModule) {
|
||||
const internalModule: Module = {
|
||||
const internalModule = makeImportModule({
|
||||
...module,
|
||||
type: ModuleType.Importer,
|
||||
isCustom: true,
|
||||
fileExtensions: module.fileExtensions ? module.fileExtensions : [],
|
||||
};
|
||||
}, () => {
|
||||
return new InteropService_Importer_Custom(module);
|
||||
});
|
||||
|
||||
return InteropService.instance().registerModule(internalModule);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user