mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	import tar archive WIP
This commit is contained in:
		| @@ -14,7 +14,7 @@ class Branch { | |||||||
|         /** @param {string} */ |         /** @param {string} */ | ||||||
|         this.prefix = row.prefix; |         this.prefix = row.prefix; | ||||||
|         /** @param {boolean} */ |         /** @param {boolean} */ | ||||||
|         this.isExpanded = row.isExpanded; |         this.isExpanded = !!row.isExpanded; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {NoteShort} */ |     /** @returns {NoteShort} */ | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ | |||||||
| const html = require('html'); | const html = require('html'); | ||||||
| const repository = require('../repository'); | const repository = require('../repository'); | ||||||
| const tar = require('tar-stream'); | const tar = require('tar-stream'); | ||||||
| const path = require('path'); |  | ||||||
| const sanitize = require("sanitize-filename"); | const sanitize = require("sanitize-filename"); | ||||||
| const mimeTypes = require('mime-types'); | const mimeTypes = require('mime-types'); | ||||||
| const TurndownService = require('turndown'); | const TurndownService = require('turndown'); | ||||||
| @@ -77,7 +76,7 @@ async function exportToTar(branch, format, res) { | |||||||
|             const fileName = getUniqueFilename(existingFileNames, sanitizedFileName); |             const fileName = getUniqueFilename(existingFileNames, sanitizedFileName); | ||||||
|  |  | ||||||
|             return { |             return { | ||||||
|                 clone: true, |                 isClone: true, | ||||||
|                 noteId: note.noteId, |                 noteId: note.noteId, | ||||||
|                 prefix: branch.prefix, |                 prefix: branch.prefix, | ||||||
|                 dataFileName: fileName |                 dataFileName: fileName | ||||||
| @@ -85,7 +84,7 @@ async function exportToTar(branch, format, res) { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         const meta = { |         const meta = { | ||||||
|             clone: false, |             isClone: false, | ||||||
|             noteId: note.noteId, |             noteId: note.noteId, | ||||||
|             title: note.title, |             title: note.title, | ||||||
|             prefix: branch.prefix, |             prefix: branch.prefix, | ||||||
| @@ -158,7 +157,7 @@ async function exportToTar(branch, format, res) { | |||||||
|     const notePaths = {}; |     const notePaths = {}; | ||||||
|  |  | ||||||
|     async function saveNote(noteMeta, path) { |     async function saveNote(noteMeta, path) { | ||||||
|         if (noteMeta.clone) { |         if (noteMeta.isClone) { | ||||||
|             const content = "Note is present at " + notePaths[noteMeta.noteId]; |             const content = "Note is present at " + notePaths[noteMeta.noteId]; | ||||||
|  |  | ||||||
|             pack.entry({name: path + '/' + noteMeta.dataFileName, size: content.length}, content); |             pack.entry({name: path + '/' + noteMeta.dataFileName, size: content.length}, content); | ||||||
|   | |||||||
| @@ -2,30 +2,29 @@ | |||||||
|  |  | ||||||
| const Attribute = require('../../entities/attribute'); | const Attribute = require('../../entities/attribute'); | ||||||
| const Link = require('../../entities/link'); | const Link = require('../../entities/link'); | ||||||
| const log = require('../../services/log'); |  | ||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
|  | const repository = require('../../services/repository'); | ||||||
| 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'); | ||||||
| const stream = require('stream'); | const stream = require('stream'); | ||||||
| const path = require('path'); | const path = require('path'); | ||||||
| const commonmark = require('commonmark'); | const commonmark = require('commonmark'); | ||||||
|  | const mimeTypes = require('mime-types'); | ||||||
|  |  | ||||||
| async function importTar(fileBuffer, parentNote) { | async function importTar(fileBuffer, importRootNote) { | ||||||
|     const files = await parseImportFile(fileBuffer); |  | ||||||
|  |  | ||||||
|     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: {}, |     const noteIdMap = {}; | ||||||
|         // new noteIds of notes which were actually created (not just referenced) |     // path => noteId | ||||||
|         createdNoteIds: [], |     const createdPaths = { '/': importRootNote.noteId, '\\': importRootNote.noteId }; | ||||||
|         attributes: [], |     const mdReader = new commonmark.Parser(); | ||||||
|         links: [], |     const mdWriter = new commonmark.HtmlRenderer(); | ||||||
|         reader: new commonmark.Parser(), |     let metaFile = null; | ||||||
|         writer: new commonmark.HtmlRenderer() |     let firstNote = null; | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     ctx.getNewNoteId = function(origNoteId) { |     const extract = tar.extract(); | ||||||
|  |  | ||||||
|  |     function getNewNoteId(origNoteId) { | ||||||
|         // in case the original noteId is empty. This probably shouldn't happen, but still good to have this precaution |         // in case the original noteId is empty. This probably shouldn't happen, but still good to have this precaution | ||||||
|         if (!origNoteId.trim()) { |         if (!origNoteId.trim()) { | ||||||
|             return ""; |             return ""; | ||||||
| @@ -36,107 +35,224 @@ async function importTar(fileBuffer, parentNote) { | |||||||
|             return origNoteId; |             return origNoteId; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!ctx.noteIdMap[origNoteId]) { |         if (!noteIdMap[origNoteId]) { | ||||||
|             ctx.noteIdMap[origNoteId] = utils.newEntityId(); |             noteIdMap[origNoteId] = utils.newEntityId(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ctx.noteIdMap[origNoteId]; |         return noteIdMap[origNoteId]; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     function getMeta(filePath) { | ||||||
|  |         if (!metaFile) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const pathSegments = filePath.split(/[\/\\]/g); | ||||||
|  |  | ||||||
|  |         let cursor = { children: metaFile.files }; | ||||||
|  |         let parent; | ||||||
|  |  | ||||||
|  |         for (const segment of pathSegments) { | ||||||
|  |             if (!cursor || !cursor.children || cursor.children.length === 0) { | ||||||
|  |                 return {}; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             parent = cursor; | ||||||
|  |             cursor = cursor.children.find(file => file.dataFileName === segment || file.dirFileName === segment); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return { | ||||||
|  |             parentNoteMeta: parent, | ||||||
|  |             noteMeta: cursor | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|     const note = await importNotes(ctx, files, parentNote.noteId); |  | ||||||
|  |  | ||||||
|     // 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') { |  | ||||||
|             attr.value = ctx.getNewNoteId(attr.value); |  | ||||||
|  |  | ||||||
|             if (!ctx.createdNoteIds.includes(attr.value)) { |  | ||||||
|                 // relation targets note outside of the export |  | ||||||
|                 continue; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     function getParentNoteId(filePath, parentNoteMeta, noteMeta) { | ||||||
|  |         let parentNoteId; | ||||||
|  |  | ||||||
|  |         if (noteMeta) { | ||||||
|  |             if (parentNoteMeta) { | ||||||
|  |                 parentNoteId = getNewNoteId(parentNoteMeta.noteId); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 parentNoteId = importRootNote.noteId; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             const parentPath = path.dirname(filePath); | ||||||
|  |  | ||||||
|  |             if (parentPath in createdPaths) { | ||||||
|  |                 parentNoteId = createdPaths[parentPath]; | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 throw new Error(`Could not find existing path ${parentPath}.`); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return parentNoteId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function getNoteTitle(filePath, noteMeta) { | ||||||
|  |         if (noteMeta) { | ||||||
|  |             return noteMeta.title; | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             const basename = path.basename(filePath); | ||||||
|  |  | ||||||
|  |             return getTextFileWithoutExtension(basename); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function getNoteId(noteMeta, filePath) { | ||||||
|  |         if (noteMeta) { | ||||||
|  |             return getNewNoteId(noteMeta.noteId); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             const filePathNoExt = getTextFileWithoutExtension(filePath); | ||||||
|  |  | ||||||
|  |             if (filePathNoExt in createdPaths) { | ||||||
|  |                 return createdPaths[filePathNoExt]; | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 return utils.newEntityId(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function detectFileTypeAndMime(filePath) { | ||||||
|  |         const mime = mimeTypes.lookup(filePath); | ||||||
|  |         let type = 'file'; | ||||||
|  |  | ||||||
|  |         if (mime) { | ||||||
|  |             if (mime === 'text/html' || mime === 'text/markdown') { | ||||||
|  |                 type = 'text'; | ||||||
|  |             } | ||||||
|  |             else if (mime.startsWith('image/')) { | ||||||
|  |                 type = 'image'; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return { type, mime }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async function saveAttributes(note, noteMeta) { | ||||||
|  |         if (!noteMeta) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for (const attr of noteMeta.attributes) { | ||||||
|  |             if (attr.type === 'relation') { | ||||||
|  |                 attr.value = getNewNoteId(attr.value); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             await new Attribute(attr).save(); |             await new Attribute(attr).save(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     for (const link of ctx.links) { |         for (const link of noteMeta.links) { | ||||||
|         link.targetNoteId = ctx.getNewNoteId(link.targetNoteId); |             link.targetNoteId = getNewNoteId(link.targetNoteId); | ||||||
|  |  | ||||||
|         if (!ctx.createdNoteIds.includes(link.targetNoteId)) { |  | ||||||
|             // link targets note outside of the export |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|             await new Link(link).save(); |             await new Link(link).save(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     return note; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| function getFileName(name) { |     async function saveDirectory(filePath) { | ||||||
|     let key; |  | ||||||
|  |  | ||||||
|     if (name.endsWith(".dat")) { |  | ||||||
|         key = "data"; |  | ||||||
|         name = name.substr(0, name.length - 4); |  | ||||||
|     } |  | ||||||
|     else if (name.endsWith(".md")) { |  | ||||||
|         key = "markdown"; |  | ||||||
|         name = name.substr(0, name.length - 3); |  | ||||||
|     } |  | ||||||
|     else if (name.endsWith((".meta"))) { |  | ||||||
|         key = "meta"; |  | ||||||
|         name = name.substr(0, name.length - 5); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         log.error("Unknown file type in import: " + name); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return {name, key}; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function parseImportFile(fileBuffer) { |  | ||||||
|     const fileMap = {}; |  | ||||||
|     const files = []; |  | ||||||
|  |  | ||||||
|     const extract = tar.extract(); |  | ||||||
|  |  | ||||||
|     extract.on('entry', function(header, stream, next) { |  | ||||||
|         let name, key; |  | ||||||
|  |  | ||||||
|         if (header.type === 'file') { |  | ||||||
|             ({name, key} = getFileName(header.name)); |  | ||||||
|         } |  | ||||||
|         else if (header.type === 'directory') { |  | ||||||
|         // directory entries in tar often end with directory separator |         // directory entries in tar often end with directory separator | ||||||
|             name = (header.name.endsWith("/") || header.name.endsWith("\\")) ? header.name.substr(0, header.name.length - 1) : header.name; |         filePath = (filePath.endsWith("/") || filePath.endsWith("\\")) ? filePath.substr(0, filePath.length - 1) : filePath; | ||||||
|             key = 'directory'; |  | ||||||
|  |         const { parentNoteMeta, noteMeta } = getMeta(filePath); | ||||||
|  |  | ||||||
|  |         const noteId = getNoteId(noteMeta, filePath); | ||||||
|  |         const noteTitle = getNoteTitle(filePath, noteMeta); | ||||||
|  |         const parentNoteId = getParentNoteId(filePath, parentNoteMeta, noteMeta); | ||||||
|  |  | ||||||
|  |         const {note} = await noteService.createNote(parentNoteId, noteTitle, '', { | ||||||
|  |             noteId, | ||||||
|  |             type: noteMeta ? noteMeta.type : 'text', | ||||||
|  |             mime: noteMeta ? noteMeta.mime : 'text/html', | ||||||
|  |             prefix: noteMeta ? noteMeta.prefix : '', | ||||||
|  |             isExpanded: noteMeta ? noteMeta.isExpanded : false | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         await saveAttributes(note, noteMeta); | ||||||
|  |  | ||||||
|  |         if (!firstNote) { | ||||||
|  |             firstNote = note; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         createdPaths[filePath] = noteId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function getTextFileWithoutExtension(filePath) { | ||||||
|  |         const extension = path.extname(filePath).toLowerCase(); | ||||||
|  |  | ||||||
|  |         if (extension === '.md' || extension === '.html') { | ||||||
|  |             return filePath.substr(0, filePath.length - extension.length); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             log.error("Unrecognized tar entry: " + JSON.stringify(header)); |             return filePath; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async function saveNote(filePath, content) { | ||||||
|  |         const {parentNoteMeta, noteMeta} = getMeta(filePath); | ||||||
|  |  | ||||||
|  |         const noteId = getNoteId(noteMeta, filePath); | ||||||
|  |         const parentNoteId = getParentNoteId(filePath, parentNoteMeta, noteMeta); | ||||||
|  |  | ||||||
|  |         if (noteMeta && noteMeta.isClone) { | ||||||
|  |             await new Branch({ | ||||||
|  |                 noteId, | ||||||
|  |                 parentNoteId, | ||||||
|  |                 isExpanded: noteMeta.isExpanded, | ||||||
|  |                 prefix: noteMeta.prefix | ||||||
|  |             }).save(); | ||||||
|  |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let file = fileMap[name]; |         const {type, mime} = noteMeta ? noteMeta : detectFileTypeAndMime(filePath); | ||||||
|  |  | ||||||
|         if (!file) { |         if (type === 'text') { | ||||||
|             file = fileMap[name] = { |             content = content.toString("UTF-8"); | ||||||
|                 name: path.basename(name), |         } | ||||||
|                 children: [] |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             let parentFileName = path.dirname(header.name); |         if ((noteMeta && noteMeta.format === 'markdown') || (!noteMeta && mime === 'text/markdown')) { | ||||||
|  |             const parsed = mdReader.parse(content); | ||||||
|  |             content = mdWriter.render(parsed); | ||||||
|  |         } | ||||||
|  |  | ||||||
|             if (parentFileName && parentFileName !== '.') { |         let note = await repository.getNote(noteId); | ||||||
|                 fileMap[parentFileName].children.push(file); |  | ||||||
|  |         if (!note) { | ||||||
|  |             const noteTitle = getNoteTitle(filePath, noteMeta); | ||||||
|  |  | ||||||
|  |             ({note} = await noteService.createNote(parentNoteId, noteTitle, content, { | ||||||
|  |                 noteId, | ||||||
|  |                 type, | ||||||
|  |                 mime, | ||||||
|  |                 prefix: noteMeta ? noteMeta.prefix : '', | ||||||
|  |                 isExpanded: noteMeta ? noteMeta.isExpanded : false | ||||||
|  |             })); | ||||||
|  |  | ||||||
|  |             await saveAttributes(note, noteMeta); | ||||||
|  |  | ||||||
|  |             if (!firstNote) { | ||||||
|  |                 firstNote = note; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (type === 'text') { | ||||||
|  |                 filePath = getTextFileWithoutExtension(filePath); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             createdPaths[filePath] = noteId; | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|                 files.push(file); |             note.content = content; | ||||||
|  |             await note.save(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     extract.on('entry', function(header, stream, next) { | ||||||
|         const chunks = []; |         const chunks = []; | ||||||
|  |  | ||||||
|         stream.on("data", function (chunk) { |         stream.on("data", function (chunk) { | ||||||
| @@ -147,11 +263,18 @@ async function parseImportFile(fileBuffer) { | |||||||
|         // stream is the content body (might be an empty stream) |         // stream is the content body (might be an empty stream) | ||||||
|         // call next when you are done with this entry |         // call next when you are done with this entry | ||||||
|  |  | ||||||
|         stream.on('end', function() { |         stream.on('end', async function() { | ||||||
|             file[key] = Buffer.concat(chunks); |             const filePath = header.name; | ||||||
|  |             const content = Buffer.concat(chunks); | ||||||
|  |  | ||||||
|             if (key === "meta") { |             if (filePath === '!!!meta.json') { | ||||||
|                 file[key] = JSON.parse(file[key].toString("UTF-8")); |                 metaFile = JSON.parse(content.toString("UTF-8")); | ||||||
|  |             } | ||||||
|  |             else if (header.type === 'directory') { | ||||||
|  |                 await saveDirectory(filePath); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 await saveNote(filePath, content); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             next(); // ready for next entry |             next(); // ready for next entry | ||||||
| @@ -162,7 +285,7 @@ async function parseImportFile(fileBuffer) { | |||||||
|  |  | ||||||
|     return new Promise(resolve => { |     return new Promise(resolve => { | ||||||
|         extract.on('finish', function() { |         extract.on('finish', function() { | ||||||
|             resolve(files); |             resolve(firstNote); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         const bufferStream = new stream.PassThrough(); |         const bufferStream = new stream.PassThrough(); | ||||||
| @@ -172,96 +295,6 @@ async function parseImportFile(fileBuffer) { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function importNotes(ctx, files, parentNoteId) { |  | ||||||
|     let returnNote = null; |  | ||||||
|  |  | ||||||
|     for (const file of files) { |  | ||||||
|         let note; |  | ||||||
|  |  | ||||||
|         if (!file.meta) { |  | ||||||
|             let content = ''; |  | ||||||
|  |  | ||||||
|             if (file.data) { |  | ||||||
|                 content = file.data.toString("UTF-8"); |  | ||||||
|             } |  | ||||||
|             else if (file.markdown) { |  | ||||||
|                 const parsed = ctx.reader.parse(file.markdown.toString("UTF-8")); |  | ||||||
|                 content = ctx.writer.render(parsed); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             note = (await noteService.createNote(parentNoteId, file.name, content, { |  | ||||||
|                 type: 'text', |  | ||||||
|                 mime: 'text/html' |  | ||||||
|             })).note; |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             if (file.meta.version !== 1) { |  | ||||||
|                 throw new Error("Can't read meta data version " + file.meta.version); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (file.meta.clone) { |  | ||||||
|                 await new Branch({ |  | ||||||
|                     parentNoteId: parentNoteId, |  | ||||||
|                     noteId: ctx.getNewNoteId(file.meta.noteId), |  | ||||||
|                     prefix: file.meta.prefix, |  | ||||||
|                     isExpanded: !!file.meta.isExpanded |  | ||||||
|                 }).save(); |  | ||||||
|  |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (file.meta.type !== 'file' && file.meta.type !== 'image') { |  | ||||||
|                 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, { |  | ||||||
|                 noteId: ctx.getNewNoteId(file.meta.noteId), |  | ||||||
|                 type: file.meta.type, |  | ||||||
|                 mime: file.meta.mime, |  | ||||||
|                 prefix: file.meta.prefix, |  | ||||||
|                 isExpanded: !!file.meta.isExpanded |  | ||||||
|             })).note; |  | ||||||
|  |  | ||||||
|             ctx.createdNoteIds.push(note.noteId); |  | ||||||
|  |  | ||||||
|             for (const attribute of file.meta.attributes || []) { |  | ||||||
|                 ctx.attributes.push({ |  | ||||||
|                     noteId: note.noteId, |  | ||||||
|                     type: attribute.type, |  | ||||||
|                     name: attribute.name, |  | ||||||
|                     value: attribute.value, |  | ||||||
|                     isInheritable: attribute.isInheritable, |  | ||||||
|                     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 |  | ||||||
|         returnNote = returnNote || note; |  | ||||||
|  |  | ||||||
|         if (file.children.length > 0) { |  | ||||||
|             await importNotes(ctx, file.children, note.noteId); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return returnNote; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     importTar |     importTar | ||||||
| }; | }; | ||||||
		Reference in New Issue
	
	Block a user