mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	export subtree to tar file
This commit is contained in:
		| @@ -85,9 +85,12 @@ const contextMenu = (function() { | ||||
|             {title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}, | ||||
|             {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, | ||||
|             {title: "----"}, | ||||
|             {title: "Collapse sub-tree <kbd>Alt+-</kbd>", cmd: "collapse-sub-tree", uiIcon: "ui-icon-minus"}, | ||||
|             {title: "Force note sync", cmd: "force-note-sync", uiIcon: "ui-icon-refresh"}, | ||||
|             {title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sort-alphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"} | ||||
|             {title: "Export sub-tree", cmd: "exportSubTree", uiIcon: " ui-icon-arrowthick-1-ne"}, | ||||
|             {title: "Import sub-tree into", cmd: "importSubTree", uiIcon: "ui-icon-arrowthick-1-sw"}, | ||||
|             {title: "----"}, | ||||
|             {title: "Collapse sub-tree <kbd>Alt+-</kbd>", cmd: "collapseSubTree", uiIcon: "ui-icon-minus"}, | ||||
|             {title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"}, | ||||
|             {title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"} | ||||
|  | ||||
|         ], | ||||
|         beforeOpen: (event, ui) => { | ||||
| @@ -139,13 +142,19 @@ const contextMenu = (function() { | ||||
|             else if (ui.cmd === "delete") { | ||||
|                 treeChanges.deleteNodes(noteTree.getSelectedNodes(true)); | ||||
|             } | ||||
|             else if (ui.cmd === "collapse-sub-tree") { | ||||
|             else if (ui.cmd === "exportSubTree") { | ||||
|                 exportSubTree(node.data.noteId); | ||||
|             } | ||||
|             else if (ui.cmd === "importSubTree") { | ||||
|                 importSubTree(node.data.noteId); | ||||
|             } | ||||
|             else if (ui.cmd === "collapseSubTree") { | ||||
|                 noteTree.collapseTree(node); | ||||
|             } | ||||
|             else if (ui.cmd === "force-note-sync") { | ||||
|             else if (ui.cmd === "forceNoteSync") { | ||||
|                 forceNoteSync(node.data.noteId); | ||||
|             } | ||||
|             else if (ui.cmd === "sort-alphabetically") { | ||||
|             else if (ui.cmd === "sortAlphabetically") { | ||||
|                 noteTree.sortAlphabetically(node.data.noteId); | ||||
|             } | ||||
|             else { | ||||
|   | ||||
							
								
								
									
										11
									
								
								src/public/javascripts/export.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/public/javascripts/export.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| "use strict"; | ||||
|  | ||||
| function exportSubTree(noteId) { | ||||
|     const url = getHost() + "/api/export/" + noteId; | ||||
|  | ||||
|     download(url); | ||||
| } | ||||
|  | ||||
| function importSubTree(noteId) { | ||||
|  | ||||
| } | ||||
| @@ -304,16 +304,7 @@ const noteEditor = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     $attachmentDownload.click(() => { | ||||
|         if (isElectron()) { | ||||
|             const remote = require('electron').remote; | ||||
|  | ||||
|             remote.getCurrentWebContents().downloadURL(getAttachmentUrl()); | ||||
|         } | ||||
|         else { | ||||
|             window.location.href = getAttachmentUrl(); | ||||
|         } | ||||
|     }); | ||||
|     $attachmentDownload.click(() => download(getAttachmentUrl())); | ||||
|  | ||||
|     $attachmentOpen.click(() => { | ||||
|         if (isElectron()) { | ||||
| @@ -328,13 +319,8 @@ const noteEditor = (function() { | ||||
|  | ||||
|     function getAttachmentUrl() { | ||||
|         // electron needs absolute URL so we extract current host, port, protocol | ||||
|         const url = new URL(window.location.href); | ||||
|         const host = url.protocol + "//" + url.hostname + ":" + url.port; | ||||
|  | ||||
|         const downloadUrl = "/api/attachments/download/" + getCurrentNoteId() | ||||
|         return getHost() + "/api/attachments/download/" + getCurrentNoteId() | ||||
|             + "?protectedSessionId=" + encodeURIComponent(protected_session.getProtectedSessionId()); | ||||
|  | ||||
|         return host + downloadUrl; | ||||
|     } | ||||
|  | ||||
|     $(document).ready(() => { | ||||
|   | ||||
| @@ -189,4 +189,20 @@ async function requireCss(url) { | ||||
|     if (!css.includes(url)) { | ||||
|         $('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', url)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function getHost() { | ||||
|     const url = new URL(window.location.href); | ||||
|     return url.protocol + "//" + url.hostname + ":" + url.port; | ||||
| } | ||||
|  | ||||
| function download(url) { | ||||
|     if (isElectron()) { | ||||
|         const remote = require('electron').remote; | ||||
|  | ||||
|         remote.getCurrentWebContents().downloadURL(url); | ||||
|     } | ||||
|     else { | ||||
|         window.location.href = url; | ||||
|     } | ||||
| } | ||||
| @@ -2,56 +2,67 @@ | ||||
|  | ||||
| const express = require('express'); | ||||
| const router = express.Router(); | ||||
| const rimraf = require('rimraf'); | ||||
| const fs = require('fs'); | ||||
| const sql = require('../../services/sql'); | ||||
| const data_dir = require('../../services/data_dir'); | ||||
| const attributes = require('../../services/attributes'); | ||||
| const html = require('html'); | ||||
| const auth = require('../../services/auth'); | ||||
| const wrap = require('express-promise-wrap').wrap; | ||||
| const tar = require('tar-stream'); | ||||
| const sanitize = require("sanitize-filename"); | ||||
|  | ||||
| router.get('/:noteId/to/:directory', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||
| router.get('/:noteId/', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||
|     const noteId = req.params.noteId; | ||||
|     const directory = req.params.directory.replace(/[^0-9a-zA-Z_-]/gi, ''); | ||||
|  | ||||
|     if (!fs.existsSync(data_dir.EXPORT_DIR)) { | ||||
|         fs.mkdirSync(data_dir.EXPORT_DIR); | ||||
|     } | ||||
|  | ||||
|     const completeExportDir = data_dir.EXPORT_DIR + '/' + directory; | ||||
|  | ||||
|     if (fs.existsSync(completeExportDir)) { | ||||
|         rimraf.sync(completeExportDir); | ||||
|     } | ||||
|  | ||||
|     fs.mkdirSync(completeExportDir); | ||||
|  | ||||
|     const noteTreeId = await sql.getValue('SELECT noteTreeId FROM note_tree WHERE noteId = ?', [noteId]); | ||||
|  | ||||
|     await exportNote(noteTreeId, completeExportDir); | ||||
|     const pack = tar.pack(); | ||||
|  | ||||
|     res.send({}); | ||||
|     const name = await exportNote(noteTreeId, '', pack); | ||||
|  | ||||
|     pack.finalize(); | ||||
|  | ||||
|     res.setHeader('Content-Disposition', 'attachment; filename="' + name + '.tar"'); | ||||
|     res.setHeader('Content-Type', 'application/tar'); | ||||
|  | ||||
|     pack.pipe(res); | ||||
| })); | ||||
|  | ||||
| async function exportNote(noteTreeId, dir) { | ||||
| async function exportNote(noteTreeId, directory, pack) { | ||||
|     const noteTree = await sql.getRow("SELECT * FROM note_tree WHERE noteTreeId = ?", [noteTreeId]); | ||||
|     const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteTree.noteId]); | ||||
|  | ||||
|     const pos = (noteTree.notePosition + '').padStart(4, '0'); | ||||
|     const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content; | ||||
|  | ||||
|     fs.writeFileSync(dir + '/' + pos + '-' + note.title + '.html', html.prettyPrint(note.content, {indent_size: 2})); | ||||
|     const childFileName = directory + sanitize(note.title); | ||||
|  | ||||
|     console.log(childFileName); | ||||
|  | ||||
|     pack.entry({ name: childFileName + ".dat", size: content.length }, content); | ||||
|  | ||||
|     const metadata = await getMetadata(note); | ||||
|  | ||||
|     pack.entry({ name: childFileName + ".meta", size: metadata.length }, metadata); | ||||
|  | ||||
|     const children = await sql.getRows("SELECT * FROM note_tree WHERE parentNoteId = ? AND isDeleted = 0", [note.noteId]); | ||||
|  | ||||
|     if (children.length > 0) { | ||||
|         const childrenDir = dir + '/' + pos + '-' + note.title; | ||||
|  | ||||
|         fs.mkdirSync(childrenDir); | ||||
|  | ||||
|         for (const child of children) { | ||||
|             await exportNote(child.noteTreeId, childrenDir); | ||||
|             await exportNote(child.noteTreeId, childFileName + "/", pack); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return childFileName; | ||||
| } | ||||
|  | ||||
| async function getMetadata(note) { | ||||
|     const meta = { | ||||
|         title: note.title, | ||||
|         type: note.type, | ||||
|         mime: note.mime, | ||||
|         attributes: await attributes.getNoteAttributeMap(note.noteId) | ||||
|     }; | ||||
|  | ||||
|     return JSON.stringify(meta, null, '\t') | ||||
| } | ||||
|  | ||||
| module.exports = router; | ||||
| @@ -20,6 +20,5 @@ module.exports = { | ||||
|     DOCUMENT_PATH, | ||||
|     BACKUP_DIR, | ||||
|     LOG_DIR, | ||||
|     EXPORT_DIR, | ||||
|     ANONYMIZED_DB_DIR | ||||
| }; | ||||
| @@ -497,6 +497,7 @@ | ||||
|     <script src="javascripts/drag_and_drop.js"></script> | ||||
|     <script src="javascripts/context_menu.js"></script> | ||||
|     <script src="javascripts/search_tree.js"></script> | ||||
|     <script src="javascripts/export.js"></script> | ||||
|  | ||||
|     <!-- Note detail --> | ||||
|     <script src="javascripts/note_editor.js"></script> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user