mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	OPML import support (issue #78)
This commit is contained in:
		
							parent
							
								
									f47ae12019
								
							
						
					
					
						commit
						be51e533fc
					
				
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "trilium",
 | 
					  "name": "trilium",
 | 
				
			||||||
  "version": "0.12.0",
 | 
					  "version": "0.13.0-beta",
 | 
				
			||||||
  "lockfileVersion": 1,
 | 
					  "lockfileVersion": 1,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
@ -13618,9 +13618,9 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "xmlbuilder": {
 | 
					        "xmlbuilder": {
 | 
				
			||||||
          "version": "9.0.4",
 | 
					          "version": "9.0.7",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz",
 | 
					          "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
 | 
				
			||||||
          "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8="
 | 
					          "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -59,7 +59,8 @@
 | 
				
			|||||||
    "sqlite": "^2.9.2",
 | 
					    "sqlite": "^2.9.2",
 | 
				
			||||||
    "tar-stream": "^1.6.1",
 | 
					    "tar-stream": "^1.6.1",
 | 
				
			||||||
    "unescape": "^1.0.1",
 | 
					    "unescape": "^1.0.1",
 | 
				
			||||||
    "ws": "^5.2.0"
 | 
					    "ws": "^5.2.0",
 | 
				
			||||||
 | 
					    "xml2js": "^0.4.19"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "electron": "^2.0.1",
 | 
					    "electron": "^2.0.1",
 | 
				
			||||||
 | 
				
			|||||||
@ -98,7 +98,7 @@ const contextMenuOptions = {
 | 
				
			|||||||
            {title: "Native Tar", cmd: "exportBranchToTar"},
 | 
					            {title: "Native Tar", cmd: "exportBranchToTar"},
 | 
				
			||||||
            {title: "OPML", cmd: "exportBranchToOpml"}
 | 
					            {title: "OPML", cmd: "exportBranchToOpml"}
 | 
				
			||||||
        ]},
 | 
					        ]},
 | 
				
			||||||
        {title: "Import into branch", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"},
 | 
					        {title: "Import into branch (tar, opml)", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"},
 | 
				
			||||||
        {title: "----"},
 | 
					        {title: "----"},
 | 
				
			||||||
        {title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"},
 | 
					        {title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"},
 | 
				
			||||||
        {title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
 | 
					        {title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,7 @@ $("#import-upload").change(async function() {
 | 
				
			|||||||
        type: 'POST',
 | 
					        type: 'POST',
 | 
				
			||||||
        contentType: false, // NEEDED, DON'T OMIT THIS
 | 
					        contentType: false, // NEEDED, DON'T OMIT THIS
 | 
				
			||||||
        processData: false, // NEEDED, DON'T OMIT THIS
 | 
					        processData: false, // NEEDED, DON'T OMIT THIS
 | 
				
			||||||
    });
 | 
					    }).fail((xhr, status, error) => alert('Import error: ' + xhr.responseText));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await treeService.reload();
 | 
					    await treeService.reload();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,75 @@ 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 parseString = require('xml2js').parseString;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function importToBranch(req) {
 | 
				
			||||||
 | 
					    const parentNoteId = req.params.parentNoteId;
 | 
				
			||||||
 | 
					    const file = req.file;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const parentNote = await repository.getNote(parentNoteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!parentNote) {
 | 
				
			||||||
 | 
					        return [404, `Note ${parentNoteId} doesn't exist.`];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const extension = path.extname(file.originalname).toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (extension === '.tar') {
 | 
				
			||||||
 | 
					        await importTar(file, parentNoteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else if (extension === '.opml') {
 | 
				
			||||||
 | 
					        return await importOpml(file, parentNoteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        return [400, `Unrecognized extension ${extension}, must be .tar or .opml`];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function toHtml(text) {
 | 
				
			||||||
 | 
					    return '<p>' + text.replace(/(?:\r\n|\r|\n)/g, '</p><p>') + '</p>';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function importOutline(outline, parentNoteId) {
 | 
				
			||||||
 | 
					    const {note} = await noteService.createNote(parentNoteId, outline.$.title, toHtml(outline.$.text));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const childOutline of (outline.outline || [])) {
 | 
				
			||||||
 | 
					        await importOutline(childOutline, note.noteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function importOpml(file, parentNoteId) {
 | 
				
			||||||
 | 
					    const xml = await new Promise(function(resolve, reject)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        parseString(file.buffer, function (err, result) {
 | 
				
			||||||
 | 
					            if (err) {
 | 
				
			||||||
 | 
					                reject(err);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                resolve(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (xml.opml.$.version !== '1.0' && xml.opml.$.version !== '1.1') {
 | 
				
			||||||
 | 
					        return [400, 'Unsupported OPML version ' + xml.opml.$.version + ', 1.0 or 1.1 expected instead.'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const outlines = xml.opml.body[0].outline || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const outline of outlines) {
 | 
				
			||||||
 | 
					        await importOutline(outline, parentNoteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function importTar(file, parentNoteId) {
 | 
				
			||||||
 | 
					    const files = await parseImportFile(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // maps from original noteId (in tar file) to newly generated noteId
 | 
				
			||||||
 | 
					    const noteIdMap = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await importNotes(files, parentNoteId, noteIdMap);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getFileName(name) {
 | 
					function getFileName(name) {
 | 
				
			||||||
    let key;
 | 
					    let key;
 | 
				
			||||||
@ -86,24 +155,6 @@ async function parseImportFile(file) {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function importTar(req) {
 | 
					 | 
				
			||||||
    const parentNoteId = req.params.parentNoteId;
 | 
					 | 
				
			||||||
    const file = req.file;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const parentNote = await repository.getNote(parentNoteId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!parentNote) {
 | 
					 | 
				
			||||||
        return [404, `Note ${parentNoteId} doesn't exist.`];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const files = await parseImportFile(file);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // maps from original noteId (in tar file) to newly generated noteId
 | 
					 | 
				
			||||||
    const noteIdMap = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await importNotes(files, parentNoteId, noteIdMap);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function importNotes(files, parentNoteId, noteIdMap) {
 | 
					async function importNotes(files, parentNoteId, noteIdMap) {
 | 
				
			||||||
    for (const file of files) {
 | 
					    for (const file of files) {
 | 
				
			||||||
        if (file.meta.version !== 1) {
 | 
					        if (file.meta.version !== 1) {
 | 
				
			||||||
@ -143,5 +194,5 @@ async function importNotes(files, parentNoteId, noteIdMap) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    importTar
 | 
					    importToBranch
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -123,7 +123,7 @@ function register(app) {
 | 
				
			|||||||
    apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
 | 
					    apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    route(GET, '/api/notes/:noteId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote);
 | 
					    route(GET, '/api/notes/:noteId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote);
 | 
				
			||||||
    route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importTar, apiResultHandler);
 | 
					    route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware],
 | 
					    route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware],
 | 
				
			||||||
        filesRoute.uploadFile, apiResultHandler);
 | 
					        filesRoute.uploadFile, apiResultHandler);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user