mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	added progress also to export
This commit is contained in:
		| @@ -2,6 +2,8 @@ import treeService from '../services/tree.js'; | |||||||
| import treeUtils from "../services/tree_utils.js"; | import treeUtils from "../services/tree_utils.js"; | ||||||
| import utils from "../services/utils.js"; | import utils from "../services/utils.js"; | ||||||
| import protectedSessionHolder from "../services/protected_session_holder.js"; | import protectedSessionHolder from "../services/protected_session_holder.js"; | ||||||
|  | import messagingService from "../services/messaging.js"; | ||||||
|  | import infoService from "../services/info.js"; | ||||||
|  |  | ||||||
| const $dialog = $("#export-dialog"); | const $dialog = $("#export-dialog"); | ||||||
| const $form = $("#export-form"); | const $form = $("#export-form"); | ||||||
| @@ -10,8 +12,18 @@ const $subtreeFormats = $("#export-subtree-formats"); | |||||||
| const $singleFormats = $("#export-single-formats"); | const $singleFormats = $("#export-single-formats"); | ||||||
| const $subtreeType = $("#export-type-subtree"); | const $subtreeType = $("#export-type-subtree"); | ||||||
| const $singleType = $("#export-type-single"); | const $singleType = $("#export-type-single"); | ||||||
|  | const $exportNoteCountWrapper = $("#export-progress-count-wrapper"); | ||||||
|  | const $exportNoteCount = $("#export-progress-count"); | ||||||
|  | const $exportButton = $("#export-button"); | ||||||
|  |  | ||||||
|  | let exportId = ''; | ||||||
|  |  | ||||||
| async function showDialog(defaultType) { | async function showDialog(defaultType) { | ||||||
|  |     // each opening of the dialog resets the exportId so we don't associate it with previous exports anymore | ||||||
|  |     exportId = ''; | ||||||
|  |     $exportNoteCountWrapper.hide(); | ||||||
|  |     $exportNoteCount.text('0'); | ||||||
|  |  | ||||||
|     if (defaultType === 'subtree') { |     if (defaultType === 'subtree') { | ||||||
|         $subtreeType.prop("checked", true).change(); |         $subtreeType.prop("checked", true).change(); | ||||||
|     } |     } | ||||||
| @@ -33,6 +45,8 @@ async function showDialog(defaultType) { | |||||||
| } | } | ||||||
|  |  | ||||||
| $form.submit(() => { | $form.submit(() => { | ||||||
|  |     $exportButton.attr("disabled", "disabled"); | ||||||
|  |  | ||||||
|     const exportType = $dialog.find("input[name='export-type']:checked").val(); |     const exportType = $dialog.find("input[name='export-type']:checked").val(); | ||||||
|  |  | ||||||
|     if (!exportType) { |     if (!exportType) { | ||||||
| @@ -49,13 +63,13 @@ $form.submit(() => { | |||||||
|  |  | ||||||
|     exportBranch(currentNode.data.branchId, exportType, exportFormat); |     exportBranch(currentNode.data.branchId, exportType, exportFormat); | ||||||
|  |  | ||||||
|     $dialog.modal('hide'); |  | ||||||
|  |  | ||||||
|     return false; |     return false; | ||||||
| }); | }); | ||||||
|  |  | ||||||
| function exportBranch(branchId, type, format) { | function exportBranch(branchId, type, format) { | ||||||
|     const url = utils.getHost() + `/api/notes/${branchId}/export/${type}/${format}?protectedSessionId=` + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); |     exportId = utils.randomString(10); | ||||||
|  |  | ||||||
|  |     const url = utils.getHost() + `/api/notes/${branchId}/export/${type}/${format}/${exportId}?protectedSessionId=` + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); | ||||||
|  |  | ||||||
|     utils.download(url); |     utils.download(url); | ||||||
| } | } | ||||||
| @@ -79,6 +93,30 @@ $('input[name=export-type]').change(function () { | |||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | messagingService.subscribeToMessages(async message => { | ||||||
|  |     if (message.type === 'export-error') { | ||||||
|  |         infoService.showError(message.message); | ||||||
|  |         $dialog.modal('hide'); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!message.exportId || message.exportId !== exportId) { | ||||||
|  |         // incoming messages must correspond to this export instance | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (message.type === 'export-progress-count') { | ||||||
|  |         $exportNoteCountWrapper.show(); | ||||||
|  |  | ||||||
|  |         $exportNoteCount.text(message.progressCount); | ||||||
|  |     } | ||||||
|  |     else if (message.type === 'export-finished') { | ||||||
|  |         $dialog.modal('hide'); | ||||||
|  |  | ||||||
|  |         infoService.showMessage("Export finished successfully."); | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     showDialog |     showDialog | ||||||
| }; | }; | ||||||
| @@ -9,8 +9,8 @@ const $dialog = $("#import-dialog"); | |||||||
| const $form = $("#import-form"); | const $form = $("#import-form"); | ||||||
| const $noteTitle = $dialog.find(".note-title"); | const $noteTitle = $dialog.find(".note-title"); | ||||||
| const $fileUploadInput = $("#import-file-upload-input"); | const $fileUploadInput = $("#import-file-upload-input"); | ||||||
| const $importNoteCountWrapper = $("#import-note-count-wrapper"); | const $importNoteCountWrapper = $("#import-progress-count-wrapper"); | ||||||
| const $importNoteCount = $("#import-note-count"); | const $importNoteCount = $("#import-progress-count"); | ||||||
| const $importButton = $("#import-button"); | const $importButton = $("#import-button"); | ||||||
|  |  | ||||||
| let importId; | let importId; | ||||||
| @@ -74,10 +74,10 @@ messagingService.subscribeToMessages(async message => { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (message.type === 'import-note-count') { |     if (message.type === 'import-progress-count') { | ||||||
|         $importNoteCountWrapper.show(); |         $importNoteCountWrapper.show(); | ||||||
|  |  | ||||||
|         $importNoteCount.text(message.count); |         $importNoteCount.text(message.progressCount); | ||||||
|     } |     } | ||||||
|     else if (message.type === 'import-finished') { |     else if (message.type === 'import-finished') { | ||||||
|         $dialog.modal('hide'); |         $dialog.modal('hide'); | ||||||
|   | |||||||
| @@ -4,24 +4,78 @@ const tarExportService = require('../../services/export/tar'); | |||||||
| const singleExportService = require('../../services/export/single'); | const singleExportService = require('../../services/export/single'); | ||||||
| const opmlExportService = require('../../services/export/opml'); | const opmlExportService = require('../../services/export/opml'); | ||||||
| const repository = require("../../services/repository"); | const repository = require("../../services/repository"); | ||||||
|  | const messagingService = require("../../services/messaging"); | ||||||
|  | const log = require("../../services/log"); | ||||||
|  |  | ||||||
|  | class ExportContext { | ||||||
|  |     constructor(exportId) { | ||||||
|  |         // exportId is to distinguish between different export events - it is possible (though not recommended) | ||||||
|  |         // to have multiple exports going at the same time | ||||||
|  |         this.exportId = exportId; | ||||||
|  |         // count is mean to represent count of exported notes where practical, otherwise it's just some measure of progress | ||||||
|  |         this.progressCount = 0; | ||||||
|  |         this.lastSentCountTs = Date.now(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async increaseProgressCount() { | ||||||
|  |         this.progressCount++; | ||||||
|  |  | ||||||
|  |         if (Date.now() - this.lastSentCountTs >= 200) { | ||||||
|  |             this.lastSentCountTs = Date.now(); | ||||||
|  |  | ||||||
|  |             await messagingService.sendMessageToAllClients({ | ||||||
|  |                 exportId: this.exportId, | ||||||
|  |                 type: 'export-progress-count', | ||||||
|  |                 progressCount: this.progressCount | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async exportFinished() { | ||||||
|  |         await messagingService.sendMessageToAllClients({ | ||||||
|  |             exportId: this.exportId, | ||||||
|  |             type: 'export-finished' | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // must remaing static | ||||||
|  |     async reportError(message) { | ||||||
|  |         await messagingService.sendMessageToAllClients({ | ||||||
|  |             type: 'export-error', | ||||||
|  |             message: message | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| async function exportBranch(req, res) { | async function exportBranch(req, res) { | ||||||
|     const {branchId, type, format} = req.params; |     const {branchId, type, format, exportId} = req.params; | ||||||
|     const branch = await repository.getBranch(branchId); |     const branch = await repository.getBranch(branchId); | ||||||
|  |  | ||||||
|  |     const exportContext = new ExportContext(exportId); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|         if (type === 'subtree' && (format === 'html' || format === 'markdown')) { |         if (type === 'subtree' && (format === 'html' || format === 'markdown')) { | ||||||
|         await tarExportService.exportToTar(branch, format, res); |             await tarExportService.exportToTar(exportContext, branch, format, res); | ||||||
|         } |         } | ||||||
|         else if (type === 'single') { |         else if (type === 'single') { | ||||||
|         await singleExportService.exportSingleNote(branch, format, res); |             await singleExportService.exportSingleNote(exportContext, branch, format, res); | ||||||
|         } |         } | ||||||
|         else if (format === 'opml') { |         else if (format === 'opml') { | ||||||
|         await opmlExportService.exportToOpml(branch, res); |             await opmlExportService.exportToOpml(exportContext, branch, res); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             return [404, "Unrecognized export format " + format]; |             return [404, "Unrecognized export format " + format]; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     catch (e) { | ||||||
|  |         const message = "Export failed with following error: '" + e.message + "'. More details might be in the logs."; | ||||||
|  |         exportContext.reportError(message); | ||||||
|  |  | ||||||
|  |         log.error(message + e.stack); | ||||||
|  |  | ||||||
|  |         res.status(500).send(message); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     exportBranch |     exportBranch | ||||||
|   | |||||||
| @@ -16,19 +16,20 @@ class ImportContext { | |||||||
|         // importId is to distinguish between different import events - it is possible (though not recommended) |         // importId is to distinguish between different import events - it is possible (though not recommended) | ||||||
|         // to have multiple imports going at the same time |         // to have multiple imports going at the same time | ||||||
|         this.importId = importId; |         this.importId = importId; | ||||||
|  |         // // count is mean to represent count of exported notes where practical, otherwise it's just some measure of progress | ||||||
|         this.count = 0; |         this.count = 0; | ||||||
|         this.lastSentCountTs = Date.now(); |         this.lastSentCountTs = Date.now(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async increaseCount() { |     async increaseProgressCount() { | ||||||
|         this.count++; |         this.count++; | ||||||
|  |  | ||||||
|         if (Date.now() - this.lastSentCountTs >= 1000) { |         if (Date.now() - this.lastSentCountTs >= 200) { | ||||||
|             this.lastSentCountTs = Date.now(); |             this.lastSentCountTs = Date.now(); | ||||||
|  |  | ||||||
|             await messagingService.sendMessageToAllClients({ |             await messagingService.sendMessageToAllClients({ | ||||||
|                 importId: this.importId, |                 importId: this.importId, | ||||||
|                 type: 'import-note-count', |                 type: 'import-progress-count', | ||||||
|                 count: this.count |                 count: this.count | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -128,7 +128,7 @@ function register(app) { | |||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent); |     apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); |     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); | ||||||
|  |  | ||||||
|     route(GET, '/api/notes/:branchId/export/:type/:format', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); |     route(GET, '/api/notes/:branchId/export/:type/:format/:exportId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); | ||||||
|     route(POST, '/api/notes/:parentNoteId/import/:importId', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler); |     route(POST, '/api/notes/:parentNoteId/import/:importId', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler); | ||||||
|  |  | ||||||
|     route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware], |     route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware], | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| const repository = require("../repository"); | const repository = require("../repository"); | ||||||
| const utils = require('../utils'); | const utils = require('../utils'); | ||||||
|  |  | ||||||
| async function exportToOpml(branch, res) { | async function exportToOpml(exportContext, branch, res) { | ||||||
|     const note = await branch.getNote(); |     const note = await branch.getNote(); | ||||||
|  |  | ||||||
|     async function exportNoteInner(branchId) { |     async function exportNoteInner(branchId) { | ||||||
| @@ -21,6 +21,8 @@ async function exportToOpml(branch, res) { | |||||||
|  |  | ||||||
|         res.write(`<outline title="${preparedTitle}" text="${preparedContent}">\n`); |         res.write(`<outline title="${preparedTitle}" text="${preparedContent}">\n`); | ||||||
|  |  | ||||||
|  |         exportContext.increaseProgressCount(); | ||||||
|  |  | ||||||
|         for (const child of await note.getChildBranches()) { |         for (const child of await note.getChildBranches()) { | ||||||
|             await exportNoteInner(child.branchId); |             await exportNoteInner(child.branchId); | ||||||
|         } |         } | ||||||
| @@ -45,6 +47,8 @@ async function exportToOpml(branch, res) { | |||||||
|     res.write(`</body> |     res.write(`</body> | ||||||
| </opml>`); | </opml>`); | ||||||
|     res.end(); |     res.end(); | ||||||
|  |  | ||||||
|  |     exportContext.exportFinished(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function prepareText(text) { | function prepareText(text) { | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ const mimeTypes = require('mime-types'); | |||||||
| const html = require('html'); | const html = require('html'); | ||||||
| const utils = require('../utils'); | const utils = require('../utils'); | ||||||
|  |  | ||||||
| async function exportSingleNote(branch, format, res) { | async function exportSingleNote(exportContext, branch, format, res) { | ||||||
|     const note = await branch.getNote(); |     const note = await branch.getNote(); | ||||||
|  |  | ||||||
|     if (note.type === 'image' || note.type === 'file') { |     if (note.type === 'image' || note.type === 'file') { | ||||||
| @@ -54,6 +54,9 @@ async function exportSingleNote(branch, format, res) { | |||||||
|     res.setHeader('Content-Type', mime + '; charset=UTF-8'); |     res.setHeader('Content-Type', mime + '; charset=UTF-8'); | ||||||
|  |  | ||||||
|     res.send(payload); |     res.send(payload); | ||||||
|  |  | ||||||
|  |     exportContext.increaseProgressCount(); | ||||||
|  |     exportContext.exportFinished(); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -11,9 +11,11 @@ const utils = require('../utils'); | |||||||
| const sanitize = require("sanitize-filename"); | const sanitize = require("sanitize-filename"); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @param format - 'html' or 'markdown' |  * @param {ExportContext} exportContext | ||||||
|  |  * @param {Branch} branch | ||||||
|  |  * @param {string} format - 'html' or 'markdown' | ||||||
|  */ |  */ | ||||||
| async function exportToTar(branch, format, res) { | async function exportToTar(exportContext, branch, format, res) { | ||||||
|     let turndownService = format === 'markdown' ? new TurndownService() : null; |     let turndownService = format === 'markdown' ? new TurndownService() : null; | ||||||
|  |  | ||||||
|     const pack = tar.pack(); |     const pack = tar.pack(); | ||||||
| @@ -114,6 +116,8 @@ async function exportToTar(branch, format, res) { | |||||||
|             }) |             }) | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |         exportContext.increaseProgressCount(); | ||||||
|  |  | ||||||
|         if (note.type === 'text') { |         if (note.type === 'text') { | ||||||
|             meta.format = format; |             meta.format = format; | ||||||
|         } |         } | ||||||
| @@ -186,6 +190,8 @@ async function exportToTar(branch, format, res) { | |||||||
|             pack.entry({name: path + noteMeta.dataFileName, size: content.length}, content); |             pack.entry({name: path + noteMeta.dataFileName, size: content.length}, content); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         exportContext.increaseProgressCount(); | ||||||
|  |  | ||||||
|         if (noteMeta.children && noteMeta.children.length > 0) { |         if (noteMeta.children && noteMeta.children.length > 0) { | ||||||
|             const directoryPath = path + noteMeta.dirFileName; |             const directoryPath = path + noteMeta.dirFileName; | ||||||
|  |  | ||||||
| @@ -231,6 +237,8 @@ async function exportToTar(branch, format, res) { | |||||||
|     res.setHeader('Content-Type', 'application/tar'); |     res.setHeader('Content-Type', 'application/tar'); | ||||||
|  |  | ||||||
|     pack.pipe(res); |     pack.pipe(res); | ||||||
|  |  | ||||||
|  |     exportContext.exportFinished(); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -218,7 +218,7 @@ async function importEnex(importContext, file, parentNote) { | |||||||
|             mime: 'text/html' |             mime: 'text/html' | ||||||
|         })).note; |         })).note; | ||||||
|  |  | ||||||
|         importContext.increaseCount(); |         importContext.increaseProgressCount(); | ||||||
|  |  | ||||||
|         const noteContent = await noteEntity.getNoteContent(); |         const noteContent = await noteEntity.getNoteContent(); | ||||||
|  |  | ||||||
| @@ -240,7 +240,7 @@ async function importEnex(importContext, file, parentNote) { | |||||||
|                     mime: resource.mime |                     mime: resource.mime | ||||||
|                 })).note; |                 })).note; | ||||||
|  |  | ||||||
|                 importContext.increaseCount(); |                 importContext.increaseProgressCount(); | ||||||
|  |  | ||||||
|                 const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`; |                 const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ function toHtml(text) { | |||||||
| async function importOutline(importContext, outline, parentNoteId) { | async function importOutline(importContext, outline, parentNoteId) { | ||||||
|     const {note} = await noteService.createNote(parentNoteId, outline.$.title, toHtml(outline.$.text)); |     const {note} = await noteService.createNote(parentNoteId, outline.$.title, toHtml(outline.$.text)); | ||||||
|  |  | ||||||
|     importContext.increaseCount(); |     importContext.increaseProgressCount(); | ||||||
|  |  | ||||||
|     for (const childOutline of (outline.outline || [])) { |     for (const childOutline of (outline.outline || [])) { | ||||||
|         await importOutline(childOutline, note.noteId); |         await importOutline(childOutline, note.noteId); | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ async function importMarkdown(importContext, file, parentNote) { | |||||||
|         mime: 'text/html' |         mime: 'text/html' | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     importContext.increaseCount(); |     importContext.increaseProgressCount(); | ||||||
|     importContext.importFinished(note.noteId); |     importContext.importFinished(note.noteId); | ||||||
|  |  | ||||||
|     return note; |     return note; | ||||||
| @@ -35,7 +35,7 @@ async function importHtml(importContext, file, parentNote) { | |||||||
|         mime: 'text/html' |         mime: 'text/html' | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     importContext.increaseCount(); |     importContext.increaseProgressCount(); | ||||||
|     importContext.importFinished(note.noteId); |     importContext.importFinished(note.noteId); | ||||||
|  |  | ||||||
|     return note; |     return note; | ||||||
|   | |||||||
| @@ -342,7 +342,7 @@ async function importTar(importContext, fileBuffer, importRootNote) { | |||||||
|                 log.info("Ignoring tar import entry with type " + header.type); |                 log.info("Ignoring tar import entry with type " + header.type); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             importContext.increaseCount(); |             importContext.increaseProgressCount(); | ||||||
|  |  | ||||||
|             next(); // ready for next entry |             next(); // ready for next entry | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -57,9 +57,13 @@ | |||||||
|                             </label> |                             </label> | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|  |  | ||||||
|  |                     <div id="export-progress-count-wrapper"> | ||||||
|  |                         <strong>Note export progress count:</strong> <span id="export-progress-count"></span> | ||||||
|  |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="modal-footer"> |                 <div class="modal-footer"> | ||||||
|                     <button class="btn btn-primary">Export</button> |                     <button class="btn btn-primary" id="export-button">Export</button> | ||||||
|                 </div> |                 </div> | ||||||
|             </form> |             </form> | ||||||
|         </div> |         </div> | ||||||
|   | |||||||
| @@ -28,8 +28,8 @@ | |||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|  |  | ||||||
|                     <div id="import-note-count-wrapper"> |                     <div id="import-progress-count-wrapper"> | ||||||
|                         <strong>Imported notes:</strong> <span id="import-note-count"></span> |                         <strong>Note import progress count:</strong> <span id="import-progress-count"></span> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="modal-footer"> |                 <div class="modal-footer"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user