From e0f9a7fc6aa7c385f449018baf8f29eb983d1420 Mon Sep 17 00:00:00 2001 From: azivner Date: Fri, 16 Nov 2018 11:42:06 +0100 Subject: [PATCH] added links to the import/export + fixing internal links inside note content --- src/routes/api/export.js | 8 ++++ src/routes/api/import.js | 78 +++++++++++++++++++++++++------ src/services/{ => import}/enex.js | 8 ++-- src/services/notes.js | 2 + 4 files changed, 78 insertions(+), 18 deletions(-) rename src/services/{ => import}/enex.js (98%) diff --git a/src/routes/api/export.js b/src/routes/api/export.js index 362d896a4..460c9a003 100644 --- a/src/routes/api/export.js +++ b/src/routes/api/export.js @@ -115,8 +115,10 @@ async function exportToTar(branch, res) { noteId: note.noteId, title: note.title, prefix: branch.prefix, + isExpanded: branch.isExpanded, type: note.type, mime: note.mime, + // we don't export dateCreated and dateModified of any entity since that would be a bit misleading attributes: (await note.getOwnedAttributes()).map(attribute => { return { type: attribute.type, @@ -125,6 +127,12 @@ async function exportToTar(branch, res) { isInheritable: attribute.isInheritable, position: attribute.position }; + }), + links: (await note.getLinks()).map(link => { + return { + type: link.type, + targetNoteId: link.targetNoteId + } }) }; diff --git a/src/routes/api/import.js b/src/routes/api/import.js index c0da827c6..60475925d 100644 --- a/src/routes/api/import.js +++ b/src/routes/api/import.js @@ -1,9 +1,11 @@ "use strict"; +const Attribute = require('../../entities/attribute'); +const Link = require('../../entities/link'); const repository = require('../../services/repository'); const log = require('../../services/log'); -const enex = require('../../services/enex'); -const attributeService = require('../../services/attributes'); +const utils = require('../../services/utils'); +const enex = require('../../services/import/enex'); const noteService = require('../../services/notes'); const Branch = require('../../entities/branch'); const tar = require('tar-stream'); @@ -93,33 +95,64 @@ async function importOpml(file, parentNote) { return returnNote; } +/** + * Complication of this export is the need to balance two needs: + * - + */ async function importTar(file, parentNote) { const files = await parseImportFile(file); const ctx = { // maps from original noteId (in tar file) to newly generated noteId noteIdMap: {}, + // new noteIds of notes which were actually created (not just referenced) + createdNoteIds: [], attributes: [], + links: [], reader: new commonmark.Parser(), writer: new commonmark.HtmlRenderer() }; + ctx.getNewNoteId = function(origNoteId) { + // in case the original noteId is empty. This probably shouldn't happen, but still good to have this precaution + if (!origNoteId.trim()) { + return ""; + } + + if (!ctx.noteIdMap[origNoteId]) { + ctx.noteIdMap[origNoteId] = utils.newEntityId(); + } + + return ctx.noteIdMap[origNoteId]; + }; + const note = await importNotes(ctx, files, parentNote.noteId); - // we save attributes after importing notes because we need to have all the relation - // targets already existing + // we save attributes and links after importing notes because we need to check that target noteIds + // have been really created (relation/links with targets outside of the export are not created) + for (const attr of ctx.attributes) { if (attr.type === 'relation') { - // map to local noteId - attr.value = ctx.noteIdMap[attr.value]; + attr.value = ctx.getNewNoteId(attr.value); - if (!attr.value) { - // relation is targeting note not present in the import + if (!ctx.createdNoteIds.includes(attr.value)) { + // relation targets note outside of the export continue; } } - await attributeService.createAttribute(attr); + await new Attribute(attr).save(); + } + + for (const link of ctx.links) { + link.targetNoteId = ctx.getNewNoteId(link.targetNoteId); + + if (!ctx.createdNoteIds.includes(link.targetNoteId)) { + // link targets note outside of the export + continue; + } + + await new Link(link).save(); } return note; @@ -252,26 +285,35 @@ async function importNotes(ctx, files, parentNoteId) { if (file.meta.clone) { await new Branch({ parentNoteId: parentNoteId, - noteId: ctx.noteIdMap[file.meta.noteId], - prefix: file.meta.prefix + noteId: ctx.getNewNoteId(file.meta.noteId), + prefix: file.meta.prefix, + isExpanded: !!file.meta.isExpanded }).save(); return; } - if (file.meta.type !== 'file') { + if (file.meta.type !== 'file' && file.meta.type !== 'image') { file.data = file.data.toString("UTF-8"); + + // this will replace all internal links ( and ) inside the body + // links pointing outside the export will be broken and changed (ctx.getNewNoteId() will still assign new noteId) + for (const link of file.meta.links || []) { + // no need to escape the regexp find string since it's a noteId which doesn't contain any special characters + file.data = file.data.replace(new RegExp(link.targetNoteId, "g"), ctx.getNewNoteId(link.targetNoteId)); + } } note = (await noteService.createNote(parentNoteId, file.meta.title, file.data, { + noteId: ctx.getNewNoteId(file.meta.noteId), type: file.meta.type, mime: file.meta.mime, prefix: file.meta.prefix })).note; - ctx.noteIdMap[file.meta.noteId] = note.noteId; + ctx.createdNoteIds.push(note.noteId); - for (const attribute of file.meta.attributes) { + for (const attribute of file.meta.attributes || []) { ctx.attributes.push({ noteId: note.noteId, type: attribute.type, @@ -281,6 +323,14 @@ async function importNotes(ctx, files, parentNoteId) { position: attribute.position }); } + + for (const link of file.meta.links || []) { + ctx.links.push({ + noteId: note.noteId, + type: link.type, + targetNoteId: link.targetNoteId + }); + } } // first created note will be activated after import diff --git a/src/services/enex.js b/src/services/import/enex.js similarity index 98% rename from src/services/enex.js rename to src/services/import/enex.js index 0c8767bc2..aeb4f67d0 100644 --- a/src/services/enex.js +++ b/src/services/import/enex.js @@ -1,10 +1,10 @@ const sax = require("sax"); const stream = require('stream'); const xml2js = require('xml2js'); -const log = require("./log"); -const utils = require("./utils"); -const noteService = require("./notes"); -const imageService = require("./image"); +const log = require("../log"); +const utils = require("../utils"); +const noteService = require("../notes"); +const imageService = require("../image"); // date format is e.g. 20181121T193703Z function parseDate(text) { diff --git a/src/services/notes.js b/src/services/notes.js index 60859d551..0748a988f 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -69,6 +69,7 @@ async function createNewNote(parentNoteId, noteData) { noteData.mime = noteData.mime || parentNote.mime; const note = await new Note({ + noteId: noteData.noteId, // optionally can force specific noteId title: noteData.title, content: noteData.content, isProtected: noteData.isProtected, @@ -116,6 +117,7 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {}) title: title, content: extraOptions.json ? JSON.stringify(content, null, '\t') : content, target: 'into', + noteId: extraOptions.noteId, isProtected: !!extraOptions.isProtected, type: extraOptions.type, mime: extraOptions.mime,