diff --git a/packages/lib/services/interop/InteropService_Importer_Md.test.ts b/packages/lib/services/interop/InteropService_Importer_Md.test.ts index 1586d8ef4..f5a92bb3e 100644 --- a/packages/lib/services/interop/InteropService_Importer_Md.test.ts +++ b/packages/lib/services/interop/InteropService_Importer_Md.test.ts @@ -1,21 +1,33 @@ import InteropService_Importer_Md from '../../services/interop/InteropService_Importer_Md'; import Note from '../../models/Note'; -import { setupDatabaseAndSynchronizer, supportDir, switchClient } from '../../testing/test-utils'; +import Folder from '../../models/Folder'; +import * as fs from 'fs-extra'; +import { createTempDir, setupDatabaseAndSynchronizer, supportDir, switchClient } from '../../testing/test-utils'; import { MarkupToHtml } from '@joplin/renderer'; +import { FolderEntity } from '../database/types'; -describe('InteropService_Importer_Md: importLocalImages', function() { +describe('InteropService_Importer_Md', function() { + let tempDir: string; async function importNote(path: string) { const importer = new InteropService_Importer_Md(); importer.setMetadata({ fileExtensions: ['md', 'html'] }); return await importer.importFile(path, 'notebook'); } - + async function importNoteDirectory(path: string) { + const importer = new InteropService_Importer_Md(); + importer.setMetadata({ fileExtensions: ['md', 'html'] }); + return await importer.importDirectory(path, 'notebook'); + } beforeEach(async (done) => { await setupDatabaseAndSynchronizer(1); await switchClient(1); + tempDir = await createTempDir(); done(); }); + afterEach(async () => { + await fs.remove(tempDir); + }); it('should import linked files and modify tags appropriately', async function() { const note = await importNote(`${supportDir}/test_notes/md/sample.md`); @@ -117,4 +129,30 @@ describe('InteropService_Importer_Md: importLocalImages', function() { const preservedAlt = note.body.includes('alt="../../photo.jpg"'); expect(preservedAlt).toBe(true); }); + it('should import non-empty directory', async function() { + await fs.mkdirp(`${tempDir}/non-empty/non-empty`); + await fs.writeFile(`${tempDir}/non-empty/non-empty/sample.md`, '# Sample'); + + await importNoteDirectory(`${tempDir}/non-empty`); + const allFolders = await Folder.all(); + expect(allFolders.map((f: FolderEntity) => f.title).indexOf('non-empty')).toBeGreaterThanOrEqual(0); + }); + it('should not import empty directory', async function() { + await fs.mkdirp(`${tempDir}/empty/empty`); + + await importNoteDirectory(`${tempDir}/empty`); + const allFolders = await Folder.all(); + expect(allFolders.map((f: FolderEntity) => f.title).indexOf('empty')).toBe(-1); + }); + it('should import directory with non-empty subdirectory', async function() { + await fs.mkdirp(`${tempDir}/non-empty-subdir/non-empty-subdir/subdir-empty`); + await fs.mkdirp(`${tempDir}/non-empty-subdir/non-empty-subdir/subdir-non-empty`); + await fs.writeFile(`${tempDir}/non-empty-subdir/non-empty-subdir/subdir-non-empty/sample.md`, '# Sample'); + + await importNoteDirectory(`${tempDir}/non-empty-subdir`); + const allFolders = await Folder.all(); + expect(allFolders.map((f: FolderEntity) => f.title).indexOf('non-empty-subdir')).toBeGreaterThanOrEqual(0); + expect(allFolders.map((f: FolderEntity) => f.title).indexOf('subdir-empty')).toBe(-1); + expect(allFolders.map((f: FolderEntity) => f.title).indexOf('subdir-non-empty')).toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/lib/services/interop/InteropService_Importer_Md.ts b/packages/lib/services/interop/InteropService_Importer_Md.ts index 654077dc0..68ae20236 100644 --- a/packages/lib/services/interop/InteropService_Importer_Md.ts +++ b/packages/lib/services/interop/InteropService_Importer_Md.ts @@ -47,13 +47,16 @@ export default class InteropService_Importer_Md extends InteropService_Importer_ async importDirectory(dirPath: string, parentFolderId: string) { console.info(`Import: ${dirPath}`); - const supportedFileExtension = this.metadata().fileExtensions; const stats = await shim.fsDriver().readDirStats(dirPath); for (let i = 0; i < stats.length; i++) { const stat = stats[i]; if (stat.isDirectory()) { + if (await this.isDirectoryEmpty(`${dirPath}/${stat.path}`)) { + console.info(`Ignoring empty directory: ${stat.path}`); + continue; + } const folderTitle = await Folder.findUniqueItemTitle(basename(stat.path)); const folder = await Folder.save({ title: folderTitle, parent_id: parentFolderId }); await this.importDirectory(`${dirPath}/${basename(stat.path)}`, folder.id); @@ -63,6 +66,24 @@ export default class InteropService_Importer_Md extends InteropService_Importer_ } } + private async isDirectoryEmpty(dirPath: string) { + const supportedFileExtension = this.metadata().fileExtensions; + const innerStats = await shim.fsDriver().readDirStats(dirPath); + for (let i = 0; i < innerStats.length; i++) { + const innerStat = innerStats[i]; + + if (innerStat.isDirectory()) { + if (!(await this.isDirectoryEmpty(`${dirPath}/${innerStat.path}`))) { + return false; + } + } else if (supportedFileExtension.indexOf(fileExtension(innerStat.path).toLowerCase()) >= 0) { + return false; + } + } + return true; + + } + private trimAnchorLink(link: string) { if (link.indexOf('#') <= 0) return link;