mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
added progress also to export
This commit is contained in:
parent
a097cefba7
commit
6be8a3f343
@ -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">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user