From 5aa5ec3af1a6aac684e2981df4cfb35d34609855 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 9 Nov 2019 08:53:13 +0100 Subject: [PATCH] downloading note revisions --- .../javascripts/dialogs/note_revisions.js | 11 ++++ .../javascripts/widgets/note_revisions.js | 2 +- src/routes/api/file_upload.js | 7 +-- src/routes/api/note_revisions.js | 61 ++++++++++++++++++- src/routes/routes.js | 1 + src/views/details/image.ejs | 6 +- src/views/dialogs/note_revisions.ejs | 4 +- 7 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/public/javascripts/dialogs/note_revisions.js b/src/public/javascripts/dialogs/note_revisions.js index 4a220e982..fcb646521 100644 --- a/src/public/javascripts/dialogs/note_revisions.js +++ b/src/public/javascripts/dialogs/note_revisions.js @@ -6,6 +6,7 @@ const $dialog = $("#note-revisions-dialog"); const $list = $("#note-revision-list"); const $content = $("#note-revision-content"); const $title = $("#note-revision-title"); +const $titleButtons = $("#note-revision-title-buttons"); let revisionItems = []; let note; @@ -53,6 +54,14 @@ $list.on('change', async () => { $title.html(revisionItem.title); + const $downloadButton = $(''); + + $downloadButton.on('click', () => { + utils.download(utils.getHost() + `/api/notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}/download`); + }); + + $titleButtons.html($downloadButton); + const fullNoteRevision = await server.get(`notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}`); if (note.type === 'text') { @@ -63,6 +72,8 @@ $list.on('change', async () => { } else if (note.type === 'image') { $content.html($("") + // reason why we put this inline as base64 is that we do not want to let user to copy this + // as a URL to be used in a note. Instead if they copy and paste it into a note, it will be a uploaded as a new note .attr("src", `data:${note.mime};base64,` + fullNoteRevision.content) .css("width", "100%")); } diff --git a/src/public/javascripts/widgets/note_revisions.js b/src/public/javascripts/widgets/note_revisions.js index 0af7d3ac6..da3798598 100644 --- a/src/public/javascripts/widgets/note_revisions.js +++ b/src/public/javascripts/widgets/note_revisions.js @@ -47,7 +47,7 @@ class NoteRevisionsWidget extends StandardWidget { }).text(item.dateLastEdited.substr(0, 16))); if (item.contentLength !== null) { - $listItem.append($("").text(` (${item.contentLength} characters)`)) + $listItem.append($("").text(` (${item.contentLength} bytes)`)) } $list.append($listItem); diff --git a/src/routes/api/file_upload.js b/src/routes/api/file_upload.js index 6d2053973..8c68232c1 100644 --- a/src/routes/api/file_upload.js +++ b/src/routes/api/file_upload.js @@ -45,10 +45,9 @@ async function downloadNoteFile(noteId, res) { return res.status(401).send("Protected session not available"); } - const originalFileName = await note.getLabel('originalFileName'); - const fileName = originalFileName ? originalFileName.value : note.title; - - res.setHeader('Content-Disposition', utils.getContentDisposition(fileName)); + // (one) reason we're not using the originFileName (available as label) is that it's not + // available for older note revisions and thus would be inconsistent + res.setHeader('Content-Disposition', utils.getContentDisposition(note.title || "untitled")); res.setHeader('Content-Type', note.mime); res.send(await note.getContent()); diff --git a/src/routes/api/note_revisions.js b/src/routes/api/note_revisions.js index 4126c6cbf..4177586f8 100644 --- a/src/routes/api/note_revisions.js +++ b/src/routes/api/note_revisions.js @@ -2,6 +2,9 @@ const repository = require('../../services/repository'); const noteCacheService = require('../../services/note_cache'); +const protectedSessionService = require('../../services/protected_session'); +const utils = require('../../services/utils'); +const path = require('path'); async function getNoteRevisions(req) { const {noteId} = req.params; @@ -16,15 +19,66 @@ async function getNoteRevisions(req) { async function getNoteRevision(req) { const noteRevision = await repository.getNoteRevision(req.params.noteRevisionId); - await noteRevision.getContent(); + if (noteRevision.type !== 'file') { + await noteRevision.getContent(); - if (noteRevision.content && ['file', 'image'].includes(noteRevision.type)) { - noteRevision.content = noteRevision.content.toString('base64'); + if (noteRevision.content && noteRevision.type === 'image') { + noteRevision.content = noteRevision.content.toString('base64'); + } } return noteRevision; } +/** + * @param {NoteRevision} noteRevision + * @return {string} + */ +function getRevisionFilename(noteRevision) { + let filename = noteRevision.title || "untitled"; + + if (noteRevision.type === 'text') { + filename += '.html'; + } else if (['relation-map', 'search'].includes(noteRevision.type)) { + filename += '.json'; + } + + const extension = path.extname(filename); + const date = noteRevision.dateCreated + .substr(0, 19) + .replace(' ', '_') + .replace(/[^0-9_]/g, ''); + + if (extension) { + filename = filename.substr(0, filename.length - extension.length) + + '-' + date + extension; + } + else { + filename += '-' + date; + } + + return filename; +} + +async function downloadNoteRevision(req, res) { + const noteRevision = await repository.getNoteRevision(req.params.noteRevisionId); + + if (noteRevision.noteId !== req.params.noteId) { + return res.status(400).send(`Note revision ${req.params.noteRevisionId} does not belong to note ${req.params.noteId}`); + } + + if (noteRevision.isProtected && !protectedSessionService.isProtectedSessionAvailable()) { + return res.status(401).send("Protected session not available"); + } + + const filename = getRevisionFilename(noteRevision); + + res.setHeader('Content-Disposition', utils.getContentDisposition(filename)); + res.setHeader('Content-Type', noteRevision.mime); + + res.send(await noteRevision.getContent()); +} + async function getEditedNotesOnDate(req) { const date = req.params.date; @@ -48,5 +102,6 @@ async function getEditedNotesOnDate(req) { module.exports = { getNoteRevisions, getNoteRevision, + downloadNoteRevision, getEditedNotesOnDate }; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index 4b4d4e43a..3726481c1 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -133,6 +133,7 @@ function register(app) { apiRoute(PUT, /\/api\/notes\/(.*)\/type\/(.*)\/mime\/(.*)/, notesApiRoute.setNoteTypeMime); apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions); apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision); + route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision); apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap); apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle); apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateNote); diff --git a/src/views/details/image.ejs b/src/views/details/image.ejs index e8c1aa05f..b0cd93373 100644 --- a/src/views/details/image.ejs +++ b/src/views/details/image.ejs @@ -1,10 +1,10 @@
- + - + - +
diff --git a/src/views/dialogs/note_revisions.ejs b/src/views/dialogs/note_revisions.ejs index 5d136a479..ee6c059f4 100644 --- a/src/views/dialogs/note_revisions.ejs +++ b/src/views/dialogs/note_revisions.ejs @@ -15,8 +15,10 @@
-
+

+ +