mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	single file export working, tar WIP
This commit is contained in:
		
							
								
								
									
										17
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -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": { | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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'); | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| }; | ||||
| @@ -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 | ||||
| }; | ||||
| @@ -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], | ||||
|   | ||||
| @@ -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 | ||||
| }; | ||||
| @@ -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 | ||||
| }; | ||||
							
								
								
									
										57
									
								
								src/services/export/single.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/services/export/single.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| }; | ||||
| @@ -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; | ||||
| @@ -23,7 +23,7 @@ | ||||
|  | ||||
|                         <div class="form-check"> | ||||
|                             <input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-markdown" | ||||
|                                    value="markdown-tar"> | ||||
|                                    value="markdown"> | ||||
|                             <label class="form-check-label" for="export-subtree-format-markdown"> | ||||
|                                 Markdown - this preserves most of the formatting. | ||||
|                             </label> | ||||
| @@ -51,7 +51,7 @@ | ||||
|  | ||||
|                         <div class="form-check"> | ||||
|                             <input class="form-check-input" type="radio" name="export-single-format" id="export-single-format-markdown" | ||||
|                                    value="markdown-tar"> | ||||
|                                    value="markdown"> | ||||
|                             <label class="form-check-label" for="export-single-format-markdown"> | ||||
|                                 Markdown - this preserves most of the formatting. | ||||
|                             </label> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user