From e09b61d1ac887bd54f0aa65c4bbc2634aef565e1 Mon Sep 17 00:00:00 2001 From: azivner Date: Sat, 24 Nov 2018 20:58:38 +0100 Subject: [PATCH] single file export working, tar WIP --- package-lock.json | 17 +++- package.json | 1 + src/public/javascripts/dialogs/export.js | 12 ++- src/public/javascripts/services/export.js | 12 +-- src/routes/api/export.js | 29 +++--- src/routes/routes.js | 2 +- src/services/export/markdown_single.js | 31 ------- src/services/export/markdown_tar.js | 91 ------------------- src/services/export/single.js | 57 ++++++++++++ src/services/export/{native_tar.js => tar.js} | 6 +- src/views/dialogs/export.ejs | 4 +- 11 files changed, 103 insertions(+), 159 deletions(-) delete mode 100644 src/services/export/markdown_single.js delete mode 100644 src/services/export/markdown_tar.js create mode 100644 src/services/export/single.js rename src/services/export/{native_tar.js => tar.js} (96%) diff --git a/package-lock.json b/package-lock.json index 6842a978a..22987c9dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "trilium", - "version": "0.24.3-beta", + "version": "0.24.4-beta", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -6417,11 +6417,18 @@ "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" }, "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "~1.36.0" + "mime-db": "~1.37.0" + }, + "dependencies": { + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + } } }, "mimic-fn": { diff --git a/package.json b/package.json index d56b683b3..2499112f0 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "imagemin-pngquant": "6.0.0", "ini": "1.3.5", "jimp": "0.5.6", + "mime-types": "^2.1.21", "moment": "2.22.2", "multer": "1.4.1", "open": "0.0.5", diff --git a/src/public/javascripts/dialogs/export.js b/src/public/javascripts/dialogs/export.js index 820e9963c..7b6008196 100644 --- a/src/public/javascripts/dialogs/export.js +++ b/src/public/javascripts/dialogs/export.js @@ -34,9 +34,19 @@ async function showDialog(defaultType) { $form.submit(() => { const exportType = $dialog.find("input[name='export-type']:checked").val(); + if (!exportType) { + // this shouldn't happen as we always choose default export type + alert("Choose export type first please"); + return; + } + + const exportFormat = exportType === 'subtree' + ? $("input[name=export-subtree-format]:checked").val() + : $("input[name=export-single-format]:checked").val(); + const currentNode = treeService.getCurrentNode(); - exportService.exportNote(currentNode.data.branchId, exportType); + exportService.exportBranch(currentNode.data.branchId, exportType, exportFormat); $dialog.modal('hide'); diff --git a/src/public/javascripts/services/export.js b/src/public/javascripts/services/export.js index c82532909..e81ddb6e6 100644 --- a/src/public/javascripts/services/export.js +++ b/src/public/javascripts/services/export.js @@ -1,16 +1,14 @@ import treeService from './tree.js'; -import infoService from './info.js'; import protectedSessionHolder from './protected_session_holder.js'; import utils from './utils.js'; import server from './server.js'; -function exportNote(noteId, format) { - const url = utils.getHost() + "/api/notes/" + noteId + "/export/" + format + - "?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); +function exportBranch(branchId, type, format) { + const url = utils.getHost() + `/api/notes/${branchId}/export/${type}/${format}?protectedSessionId=` + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); + + console.log(url); utils.download(url); - - infoService.showMessage("Export to file has been finished."); } let importNoteId; @@ -47,6 +45,6 @@ $("#import-upload").change(async function() { }); export default { - exportNote, + exportBranch, importIntoNote }; \ No newline at end of file diff --git a/src/routes/api/export.js b/src/routes/api/export.js index b64834e62..cd590cbd7 100644 --- a/src/routes/api/export.js +++ b/src/routes/api/export.js @@ -1,29 +1,22 @@ "use strict"; -const nativeTarExportService = require('../../services/export/native_tar'); -const markdownTarExportService = require('../../services/export/markdown_tar'); -const markdownSingleExportService = require('../../services/export/markdown_single'); +const tarExportService = require('../../services/export/tar'); +const singleExportService = require('../../services/export/single'); const opmlExportService = require('../../services/export/opml'); const repository = require("../../services/repository"); -async function exportNote(req, res) { - // entityId maybe either noteId or branchId depending on format - const entityId = req.params.entityId; - const type = req.params.type; - const format = req.params.format; +async function exportBranch(req, res) { + const {branchId, type, format} = req.params; + const branch = await repository.getBranch(branchId); - if (type === 'tar') { - await nativeTarExportService.exportToTar(await repository.getBranch(entityId), format, res); + if (type === 'subtree' && (format === 'html' || format === 'markdown')) { + await tarExportService.exportToTar(branch, format, res); } - // else if (format === 'tar') { - // await markdownTarExportService.exportToMarkdown(await repository.getBranch(entityId), res); - // } - // export single note without subtree - else if (format === 'markdown-single') { - await markdownSingleExportService.exportSingleMarkdown(await repository.getNote(entityId), res); + else if (type === 'single') { + await singleExportService.exportSingleNote(branch, format, res); } else if (format === 'opml') { - await opmlExportService.exportToOpml(await repository.getBranch(entityId), res); + await opmlExportService.exportToOpml(branch, res); } else { return [404, "Unrecognized export format " + format]; @@ -31,5 +24,5 @@ async function exportNote(req, res) { } module.exports = { - exportNote + exportBranch }; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index 6b08e584f..fce2c078e 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -128,7 +128,7 @@ function register(app) { apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent); apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); - route(GET, '/api/notes/:entityId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote); + route(GET, '/api/notes/:branchId/export/:type/:format', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler); route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware], diff --git a/src/services/export/markdown_single.js b/src/services/export/markdown_single.js deleted file mode 100644 index 7e878094c..000000000 --- a/src/services/export/markdown_single.js +++ /dev/null @@ -1,31 +0,0 @@ -"use strict"; - -const sanitize = require("sanitize-filename"); -const TurndownService = require('turndown'); - -async function exportSingleMarkdown(note, res) { - if (note.type !== 'text' && note.type !== 'code') { - return [400, `Note type ${note.type} cannot be exported as single markdown file.`]; - } - - let markdown; - - if (note.type === 'code') { - markdown = '```\n' + note.content + "\n```"; - } - else if (note.type === 'text') { - const turndownService = new TurndownService(); - markdown = turndownService.turndown(note.content); - } - - const name = sanitize(note.title); - - res.setHeader('Content-Disposition', 'file; filename="' + name + '.md"'); - res.setHeader('Content-Type', 'text/markdown; charset=UTF-8'); - - res.send(markdown); -} - -module.exports = { - exportSingleMarkdown -}; \ No newline at end of file diff --git a/src/services/export/markdown_tar.js b/src/services/export/markdown_tar.js deleted file mode 100644 index 59ae6cc5b..000000000 --- a/src/services/export/markdown_tar.js +++ /dev/null @@ -1,91 +0,0 @@ -"use strict"; - -const tar = require('tar-stream'); -const TurndownService = require('turndown'); -const sanitize = require("sanitize-filename"); -const markdownSingleExportService = require('../../services/export/markdown_single'); - -async function exportToMarkdown(branch, res) { - const note = await branch.getNote(); - - if (!await note.hasChildren()) { - await markdownSingleExportService.exportSingleMarkdown(note, res); - - return; - } - - const turndownService = new TurndownService(); - const pack = tar.pack(); - const name = await exportNoteInner(note, ''); - - async function exportNoteInner(note, directory) { - const childFileName = directory + sanitize(note.title); - - if (await note.hasLabel('excludeFromExport')) { - return; - } - - saveNote(childFileName, note); - - const childNotes = await note.getChildNotes(); - - if (childNotes.length > 0) { - saveDirectory(childFileName); - } - - for (const childNote of childNotes) { - await exportNoteInner(childNote, childFileName + "/"); - } - - return childFileName; - } - - function saveTextNote(childFileName, note) { - if (note.content.trim().length === 0) { - return; - } - - let markdown; - - if (note.type === 'code') { - markdown = '```\n' + note.content + "\n```"; - } - else if (note.type === 'text') { - markdown = turndownService.turndown(note.content); - } - else { - // other note types are not supported - return; - } - - pack.entry({name: childFileName + ".md", size: markdown.length}, markdown); - } - - function saveFileNote(childFileName, note) { - pack.entry({name: childFileName, size: note.content.length}, note.content); - } - - function saveNote(childFileName, note) { - if (note.type === 'text' || note.type === 'code') { - saveTextNote(childFileName, note); - } - else if (note.type === 'image' || note.type === 'file') { - saveFileNote(childFileName, note); - } - } - - function saveDirectory(childFileName) { - pack.entry({name: childFileName, type: 'directory'}); - } - - pack.finalize(); - - res.setHeader('Content-Disposition', 'file; filename="' + name + '.tar"'); - res.setHeader('Content-Type', 'application/tar'); - - pack.pipe(res); -} - -module.exports = { - exportToMarkdown -}; \ No newline at end of file diff --git a/src/services/export/single.js b/src/services/export/single.js new file mode 100644 index 000000000..b0e0ac612 --- /dev/null +++ b/src/services/export/single.js @@ -0,0 +1,57 @@ +"use strict"; + +const sanitize = require("sanitize-filename"); +const TurndownService = require('turndown'); +const mimeTypes = require('mime-types'); +const html = require('html'); + +async function exportSingleNote(branch, format, res) { + const note = await branch.getNote(); + + if (note.type === 'image' || note.type === 'file') { + return [400, `Note type ${note.type} cannot be exported as single file.`]; + } + + if (format !== 'html' && format !== 'markdown') { + return [400, 'Unrecognized format ' + format]; + } + + let payload, extension, mime; + + if (note.type === 'text') { + if (format === 'html') { + payload = html.prettyPrint(note.content, {indent_size: 2}); + extension = 'html'; + mime = 'text/html'; + } + else if (format === 'markdown') { + const turndownService = new TurndownService(); + payload = turndownService.turndown(note.content); + extension = 'md'; + mime = 'text/markdown' + } + } + else if (note.type === 'code') { + payload = note.content; + extension = mimeTypes.extension(note.mime) || 'code'; + mime = note.mime; + } + else if (note.type === 'relation-map' || note.type === 'search') { + payload = note.content; + extension = 'json'; + mime = 'application/json'; + } + + const name = sanitize(note.title); + + console.log(name, extension, mime); + + res.setHeader('Content-Disposition', `file; filename="${name}.${extension}"`); + res.setHeader('Content-Type', mime + '; charset=UTF-8'); + + res.send(payload); +} + +module.exports = { + exportSingleNote +}; \ No newline at end of file diff --git a/src/services/export/native_tar.js b/src/services/export/tar.js similarity index 96% rename from src/services/export/native_tar.js rename to src/services/export/tar.js index 3ea4bd544..04ecae729 100644 --- a/src/services/export/native_tar.js +++ b/src/services/export/tar.js @@ -1,7 +1,7 @@ "use strict"; const html = require('html'); -const native_tar = require('tar-stream'); +const tar = require('tar-stream'); const sanitize = require("sanitize-filename"); const mimeTypes = require('mime-types'); const TurndownService = require('turndown'); @@ -12,7 +12,7 @@ const TurndownService = require('turndown'); async function exportToTar(branch, format, res) { const turndownService = new TurndownService(); - const pack = native_tar.pack(); + const pack = tar.pack(); const exportedNoteIds = []; const name = await exportNoteInner(branch, ''); @@ -79,7 +79,7 @@ async function exportToTar(branch, format, res) { } for (const childBranch of childBranches) { - await exportNoteInner(childBranch, childFileName + "/"); + await exportNoteInner(await childBranch.getNote(), childBranch, childFileName + "/"); } return childFileName; diff --git a/src/views/dialogs/export.ejs b/src/views/dialogs/export.ejs index 0ae2f2f8c..c26139918 100644 --- a/src/views/dialogs/export.ejs +++ b/src/views/dialogs/export.ejs @@ -23,7 +23,7 @@
+ value="markdown"> @@ -51,7 +51,7 @@
+ value="markdown">