mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +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