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 into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}, | ||||||
|             {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, |             {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, | ||||||
|             {title: "----"}, |             {title: "----"}, | ||||||
|             {title: "Collapse sub-tree <kbd>Alt+-</kbd>", cmd: "collapse-sub-tree", uiIcon: "ui-icon-minus"}, |             {title: "Export sub-tree", cmd: "exportSubTree", uiIcon: " ui-icon-arrowthick-1-ne"}, | ||||||
|             {title: "Force note sync", cmd: "force-note-sync", uiIcon: "ui-icon-refresh"}, |             {title: "Import sub-tree into", cmd: "importSubTree", uiIcon: "ui-icon-arrowthick-1-sw"}, | ||||||
|             {title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sort-alphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"} |             {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) => { |         beforeOpen: (event, ui) => { | ||||||
| @@ -139,13 +142,19 @@ const contextMenu = (function() { | |||||||
|             else if (ui.cmd === "delete") { |             else if (ui.cmd === "delete") { | ||||||
|                 treeChanges.deleteNodes(noteTree.getSelectedNodes(true)); |                 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); |                 noteTree.collapseTree(node); | ||||||
|             } |             } | ||||||
|             else if (ui.cmd === "force-note-sync") { |             else if (ui.cmd === "forceNoteSync") { | ||||||
|                 forceNoteSync(node.data.noteId); |                 forceNoteSync(node.data.noteId); | ||||||
|             } |             } | ||||||
|             else if (ui.cmd === "sort-alphabetically") { |             else if (ui.cmd === "sortAlphabetically") { | ||||||
|                 noteTree.sortAlphabetically(node.data.noteId); |                 noteTree.sortAlphabetically(node.data.noteId); | ||||||
|             } |             } | ||||||
|             else { |             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(() => { |     $attachmentDownload.click(() => download(getAttachmentUrl())); | ||||||
|         if (isElectron()) { |  | ||||||
|             const remote = require('electron').remote; |  | ||||||
|  |  | ||||||
|             remote.getCurrentWebContents().downloadURL(getAttachmentUrl()); |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             window.location.href = getAttachmentUrl(); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     $attachmentOpen.click(() => { |     $attachmentOpen.click(() => { | ||||||
|         if (isElectron()) { |         if (isElectron()) { | ||||||
| @@ -328,13 +319,8 @@ const noteEditor = (function() { | |||||||
|  |  | ||||||
|     function getAttachmentUrl() { |     function getAttachmentUrl() { | ||||||
|         // electron needs absolute URL so we extract current host, port, protocol |         // electron needs absolute URL so we extract current host, port, protocol | ||||||
|         const url = new URL(window.location.href); |         return getHost() + "/api/attachments/download/" + getCurrentNoteId() | ||||||
|         const host = url.protocol + "//" + url.hostname + ":" + url.port; |  | ||||||
|  |  | ||||||
|         const downloadUrl = "/api/attachments/download/" + getCurrentNoteId() |  | ||||||
|             + "?protectedSessionId=" + encodeURIComponent(protected_session.getProtectedSessionId()); |             + "?protectedSessionId=" + encodeURIComponent(protected_session.getProtectedSessionId()); | ||||||
|  |  | ||||||
|         return host + downloadUrl; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $(document).ready(() => { |     $(document).ready(() => { | ||||||
|   | |||||||
| @@ -189,4 +189,20 @@ async function requireCss(url) { | |||||||
|     if (!css.includes(url)) { |     if (!css.includes(url)) { | ||||||
|         $('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', 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 express = require('express'); | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| const rimraf = require('rimraf'); |  | ||||||
| const fs = require('fs'); |  | ||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const data_dir = require('../../services/data_dir'); | const attributes = require('../../services/attributes'); | ||||||
| const html = require('html'); | const html = require('html'); | ||||||
| const auth = require('../../services/auth'); | const auth = require('../../services/auth'); | ||||||
| const wrap = require('express-promise-wrap').wrap; | 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 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]); |     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 noteTree = await sql.getRow("SELECT * FROM note_tree WHERE noteTreeId = ?", [noteTreeId]); | ||||||
|     const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteTree.noteId]); |     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]); |     const children = await sql.getRows("SELECT * FROM note_tree WHERE parentNoteId = ? AND isDeleted = 0", [note.noteId]); | ||||||
|  |  | ||||||
|     if (children.length > 0) { |     if (children.length > 0) { | ||||||
|         const childrenDir = dir + '/' + pos + '-' + note.title; |  | ||||||
|  |  | ||||||
|         fs.mkdirSync(childrenDir); |  | ||||||
|  |  | ||||||
|         for (const child of children) { |         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; | module.exports = router; | ||||||
| @@ -20,6 +20,5 @@ module.exports = { | |||||||
|     DOCUMENT_PATH, |     DOCUMENT_PATH, | ||||||
|     BACKUP_DIR, |     BACKUP_DIR, | ||||||
|     LOG_DIR, |     LOG_DIR, | ||||||
|     EXPORT_DIR, |  | ||||||
|     ANONYMIZED_DB_DIR |     ANONYMIZED_DB_DIR | ||||||
| }; | }; | ||||||
| @@ -497,6 +497,7 @@ | |||||||
|     <script src="javascripts/drag_and_drop.js"></script> |     <script src="javascripts/drag_and_drop.js"></script> | ||||||
|     <script src="javascripts/context_menu.js"></script> |     <script src="javascripts/context_menu.js"></script> | ||||||
|     <script src="javascripts/search_tree.js"></script> |     <script src="javascripts/search_tree.js"></script> | ||||||
|  |     <script src="javascripts/export.js"></script> | ||||||
|  |  | ||||||
|     <!-- Note detail --> |     <!-- Note detail --> | ||||||
|     <script src="javascripts/note_editor.js"></script> |     <script src="javascripts/note_editor.js"></script> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user