mirror of https://github.com/laurent22/joplin.git
All: Fixes #597: Also import sub-notebooks when importing JEX data
parent
0d9f703c75
commit
ab9675544c
|
@ -13,6 +13,8 @@ const ArrayUtils = require('lib/ArrayUtils');
|
|||
const ObjectUtils = require('lib/ObjectUtils');
|
||||
const { shim } = require('lib/shim.js');
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
@ -249,6 +251,41 @@ describe('services_InteropService', function() {
|
|||
expect(folder2.title).toBe('folder1');
|
||||
}));
|
||||
|
||||
it('should export and import folder and its sub-folders', asyncTest(async () => {
|
||||
|
||||
const service = new InteropService();
|
||||
const filePath = exportDir() + '/test.jex';
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let folder2 = await Folder.save({ title: "folder2", parent_id: folder1.id });
|
||||
let folder3 = await Folder.save({ title: "folder3", parent_id: folder2.id });
|
||||
let folder4 = await Folder.save({ title: "folder4", parent_id: folder2.id });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder4.id });
|
||||
|
||||
await service.export({ path: filePath, sourceFolderIds: [folder1.id] });
|
||||
|
||||
await Note.delete(note1.id);
|
||||
await Folder.delete(folder1.id);
|
||||
await Folder.delete(folder2.id);
|
||||
await Folder.delete(folder3.id);
|
||||
await Folder.delete(folder4.id);
|
||||
|
||||
await service.import({ path: filePath });
|
||||
|
||||
expect(await Note.count()).toBe(1);
|
||||
expect(await Folder.count()).toBe(4);
|
||||
|
||||
let folder1_2 = await Folder.loadByTitle('folder1');
|
||||
let folder2_2 = await Folder.loadByTitle('folder2');
|
||||
let folder3_2 = await Folder.loadByTitle('folder3');
|
||||
let folder4_2 = await Folder.loadByTitle('folder4');
|
||||
let note1_2 = await Note.loadByTitle('ma note');
|
||||
|
||||
expect(folder2_2.parent_id).toBe(folder1_2.id);
|
||||
expect(folder3_2.parent_id).toBe(folder2_2.id);
|
||||
expect(folder4_2.parent_id).toBe(folder2_2.id);
|
||||
expect(note1_2.parent_id).toBe(folder4_2.id);
|
||||
}));
|
||||
|
||||
it('should export and import links to notes', asyncTest(async () => {
|
||||
const service = new InteropService();
|
||||
const filePath = exportDir() + '/test.jex';
|
||||
|
|
|
@ -127,6 +127,23 @@ class Folder extends BaseItem {
|
|||
return output;
|
||||
}
|
||||
|
||||
static async childrenIds(folderId, recursive) {
|
||||
if (recursive === false) throw new Error('Not implemented');
|
||||
|
||||
const folders = await this.db().selectAll('SELECT id FROM folders WHERE parent_id = ?', [folderId]);
|
||||
|
||||
let output = [];
|
||||
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
const f = folders[i];
|
||||
output.push(f.id);
|
||||
const subChildrenIds = await this.childrenIds(f.id, true);
|
||||
output = output.concat(subChildrenIds);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static async allAsTree(options = null) {
|
||||
const all = await this.all(options);
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ class InteropService {
|
|||
|
||||
async export(options) {
|
||||
const exportPath = options.path ? options.path : null;
|
||||
const sourceFolderIds = options.sourceFolderIds ? options.sourceFolderIds : [];
|
||||
let sourceFolderIds = options.sourceFolderIds ? options.sourceFolderIds : [];
|
||||
const sourceNoteIds = options.sourceNoteIds ? options.sourceNoteIds : [];
|
||||
const exportFormat = options.format ? options.format : 'jex';
|
||||
const result = { warnings: [] }
|
||||
|
@ -174,6 +174,14 @@ class InteropService {
|
|||
let resourceIds = [];
|
||||
const folderIds = await Folder.allIds();
|
||||
|
||||
let fullSourceFolderIds = sourceFolderIds.slice();
|
||||
for (let i = 0; i < sourceFolderIds.length; i++) {
|
||||
const id = sourceFolderIds[i];
|
||||
const childrenIds = await Folder.childrenIds(id);
|
||||
fullSourceFolderIds = fullSourceFolderIds.concat(childrenIds);
|
||||
}
|
||||
sourceFolderIds = fullSourceFolderIds;
|
||||
|
||||
for (let folderIndex = 0; folderIndex < folderIds.length; folderIndex++) {
|
||||
const folderId = folderIds[folderIndex];
|
||||
if (sourceFolderIds.length && sourceFolderIds.indexOf(folderId) < 0) continue;
|
||||
|
|
|
@ -56,6 +56,25 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
|
|||
return defaultFolder_;
|
||||
}
|
||||
|
||||
const setFolderToImportTo = async (itemParentId) => {
|
||||
// Logic is a bit complex here:
|
||||
// - If a destination folder was specified, move the note to it.
|
||||
// - Otherwise, if the associated folder exists, use this.
|
||||
// - If it doesn't exist, use the default folder. This is the case for example when importing JEX archives that contain only one or more notes, but no folder.
|
||||
const itemParentExists = folderExists(stats, itemParentId);
|
||||
|
||||
if (!itemIdMap[itemParentId]) {
|
||||
if (destinationFolderId) {
|
||||
itemIdMap[itemParentId] = destinationFolderId;
|
||||
} else if (!itemParentExists) {
|
||||
const parentFolder = await defaultFolder();
|
||||
itemIdMap[itemParentId] = parentFolder.id;
|
||||
} else {
|
||||
itemIdMap[itemParentId] = uuid.create();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < stats.length; i++) {
|
||||
const stat = stats[i];
|
||||
if (stat.isDirectory()) continue;
|
||||
|
@ -70,23 +89,10 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
|
|||
|
||||
if (itemType === BaseModel.TYPE_NOTE) {
|
||||
|
||||
// Logic is a bit complex here:
|
||||
// - If a destination folder was specified, move the note to it.
|
||||
// - Otherwise, if the associated folder exists, use this.
|
||||
// - If it doesn't exist, use the default folder. This is the case for example when importing JEX archives that contain only one or more notes, but no folder.
|
||||
if (!itemIdMap[item.parent_id]) {
|
||||
if (destinationFolderId) {
|
||||
itemIdMap[item.parent_id] = destinationFolderId;
|
||||
} else if (!folderExists(stats, item.parent_id)) {
|
||||
const parentFolder = await defaultFolder();
|
||||
itemIdMap[item.parent_id] = parentFolder.id;
|
||||
} else {
|
||||
itemIdMap[item.parent_id] = uuid.create();
|
||||
}
|
||||
}
|
||||
await setFolderToImportTo(item.parent_id);
|
||||
|
||||
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
|
||||
item.id = itemIdMap[item.id]; //noteId;
|
||||
item.id = itemIdMap[item.id];
|
||||
item.parent_id = itemIdMap[item.parent_id];
|
||||
item.body = await replaceLinkedItemIds(item.body);
|
||||
} else if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
|
@ -95,6 +101,11 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
|
|||
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
|
||||
item.id = itemIdMap[item.id];
|
||||
item.title = await Folder.findUniqueFolderTitle(item.title);
|
||||
|
||||
if (item.parent_id) {
|
||||
await setFolderToImportTo(item.parent_id);
|
||||
item.parent_id = itemIdMap[item.parent_id];
|
||||
}
|
||||
} else if (itemType === BaseModel.TYPE_RESOURCE) {
|
||||
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
|
||||
item.id = itemIdMap[item.id];
|
||||
|
|
Loading…
Reference in New Issue