added links to the import/export + fixing internal links inside note content

This commit is contained in:
azivner 2018-11-16 11:42:06 +01:00
parent 46c7901e1f
commit e0f9a7fc6a
4 changed files with 78 additions and 18 deletions

View file

@ -115,8 +115,10 @@ async function exportToTar(branch, res) {
noteId: note.noteId, noteId: note.noteId,
title: note.title, title: note.title,
prefix: branch.prefix, prefix: branch.prefix,
isExpanded: branch.isExpanded,
type: note.type, type: note.type,
mime: note.mime, 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 => { attributes: (await note.getOwnedAttributes()).map(attribute => {
return { return {
type: attribute.type, type: attribute.type,
@ -125,6 +127,12 @@ async function exportToTar(branch, res) {
isInheritable: attribute.isInheritable, isInheritable: attribute.isInheritable,
position: attribute.position position: attribute.position
}; };
}),
links: (await note.getLinks()).map(link => {
return {
type: link.type,
targetNoteId: link.targetNoteId
}
}) })
}; };

View file

@ -1,9 +1,11 @@
"use strict"; "use strict";
const Attribute = require('../../entities/attribute');
const Link = require('../../entities/link');
const repository = require('../../services/repository'); const repository = require('../../services/repository');
const log = require('../../services/log'); const log = require('../../services/log');
const enex = require('../../services/enex'); const utils = require('../../services/utils');
const attributeService = require('../../services/attributes'); const enex = require('../../services/import/enex');
const noteService = require('../../services/notes'); const noteService = require('../../services/notes');
const Branch = require('../../entities/branch'); const Branch = require('../../entities/branch');
const tar = require('tar-stream'); const tar = require('tar-stream');
@ -93,33 +95,64 @@ async function importOpml(file, parentNote) {
return returnNote; return returnNote;
} }
/**
* Complication of this export is the need to balance two needs:
* -
*/
async function importTar(file, parentNote) { async function importTar(file, parentNote) {
const files = await parseImportFile(file); const files = await parseImportFile(file);
const ctx = { const ctx = {
// maps from original noteId (in tar file) to newly generated noteId // maps from original noteId (in tar file) to newly generated noteId
noteIdMap: {}, noteIdMap: {},
// new noteIds of notes which were actually created (not just referenced)
createdNoteIds: [],
attributes: [], attributes: [],
links: [],
reader: new commonmark.Parser(), reader: new commonmark.Parser(),
writer: new commonmark.HtmlRenderer() 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); const note = await importNotes(ctx, files, parentNote.noteId);
// we save attributes after importing notes because we need to have all the relation // we save attributes and links after importing notes because we need to check that target noteIds
// targets already existing // have been really created (relation/links with targets outside of the export are not created)
for (const attr of ctx.attributes) { for (const attr of ctx.attributes) {
if (attr.type === 'relation') { if (attr.type === 'relation') {
// map to local noteId attr.value = ctx.getNewNoteId(attr.value);
attr.value = ctx.noteIdMap[attr.value];
if (!attr.value) { if (!ctx.createdNoteIds.includes(attr.value)) {
// relation is targeting note not present in the import // relation targets note outside of the export
continue; 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; return note;
@ -252,26 +285,35 @@ async function importNotes(ctx, files, parentNoteId) {
if (file.meta.clone) { if (file.meta.clone) {
await new Branch({ await new Branch({
parentNoteId: parentNoteId, parentNoteId: parentNoteId,
noteId: ctx.noteIdMap[file.meta.noteId], noteId: ctx.getNewNoteId(file.meta.noteId),
prefix: file.meta.prefix prefix: file.meta.prefix,
isExpanded: !!file.meta.isExpanded
}).save(); }).save();
return; return;
} }
if (file.meta.type !== 'file') { if (file.meta.type !== 'file' && file.meta.type !== 'image') {
file.data = file.data.toString("UTF-8"); file.data = file.data.toString("UTF-8");
// this will replace all internal links (<a> and <img>) 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, { note = (await noteService.createNote(parentNoteId, file.meta.title, file.data, {
noteId: ctx.getNewNoteId(file.meta.noteId),
type: file.meta.type, type: file.meta.type,
mime: file.meta.mime, mime: file.meta.mime,
prefix: file.meta.prefix prefix: file.meta.prefix
})).note; })).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({ ctx.attributes.push({
noteId: note.noteId, noteId: note.noteId,
type: attribute.type, type: attribute.type,
@ -281,6 +323,14 @@ async function importNotes(ctx, files, parentNoteId) {
position: attribute.position 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 // first created note will be activated after import

View file

@ -1,10 +1,10 @@
const sax = require("sax"); const sax = require("sax");
const stream = require('stream'); const stream = require('stream');
const xml2js = require('xml2js'); const xml2js = require('xml2js');
const log = require("./log"); const log = require("../log");
const utils = require("./utils"); const utils = require("../utils");
const noteService = require("./notes"); const noteService = require("../notes");
const imageService = require("./image"); const imageService = require("../image");
// date format is e.g. 20181121T193703Z // date format is e.g. 20181121T193703Z
function parseDate(text) { function parseDate(text) {

View file

@ -69,6 +69,7 @@ async function createNewNote(parentNoteId, noteData) {
noteData.mime = noteData.mime || parentNote.mime; noteData.mime = noteData.mime || parentNote.mime;
const note = await new Note({ const note = await new Note({
noteId: noteData.noteId, // optionally can force specific noteId
title: noteData.title, title: noteData.title,
content: noteData.content, content: noteData.content,
isProtected: noteData.isProtected, isProtected: noteData.isProtected,
@ -116,6 +117,7 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {})
title: title, title: title,
content: extraOptions.json ? JSON.stringify(content, null, '\t') : content, content: extraOptions.json ? JSON.stringify(content, null, '\t') : content,
target: 'into', target: 'into',
noteId: extraOptions.noteId,
isProtected: !!extraOptions.isProtected, isProtected: !!extraOptions.isProtected,
type: extraOptions.type, type: extraOptions.type,
mime: extraOptions.mime, mime: extraOptions.mime,