From 18cc9f24753eff9120fe5f928d296a3e70929a72 Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 13 Oct 2020 22:50:45 +0200 Subject: [PATCH] "open" action will save note to temp directory to try it then open with native application, #1304 --- package-lock.json | 77 ++++++++++++--------- package.json | 1 + src/public/app/widgets/type_widgets/file.js | 12 +++- src/routes/api/files.js | 34 +++++++-- src/routes/routes.js | 1 + 5 files changed, 86 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index a24432ad3..054d8d3ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "trilium", - "version": "0.44.5", + "version": "0.44.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1409,6 +1409,15 @@ "path-is-absolute": "^1.0.0" } }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, "tmp-promise": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.1.0.tgz", @@ -1417,6 +1426,17 @@ "requires": { "bluebird": "^3.5.0", "tmp": "0.1.0" + }, + "dependencies": { + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "optional": true, + "requires": { + "rimraf": "^2.6.3" + } + } } } } @@ -4605,7 +4625,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -8456,37 +8475,11 @@ "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" }, "tmp": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", - "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", - "optional": true, + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", "requires": { - "rimraf": "^2.6.3" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "optional": true, - "requires": { - "glob": "^7.1.3" - } - } + "rimraf": "^3.0.0" } }, "tmp-promise": { @@ -8496,6 +8489,26 @@ "optional": true, "requires": { "tmp": "0.1.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "optional": true, + "requires": { + "rimraf": "^2.6.3" + } + } } }, "to-object-path": { diff --git a/package.json b/package.json index dbb2c8b7b..3d9e62081 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "serve-favicon": "2.5.0", "session-file-store": "1.4.0", "striptags": "3.1.1", + "tmp": "^0.2.1", "turndown": "6.0.0", "turndown-plugin-gfm": "1.0.2", "unescape": "1.0.1", diff --git a/src/public/app/widgets/type_widgets/file.js b/src/public/app/widgets/type_widgets/file.js index 2577880ca..554d13564 100644 --- a/src/public/app/widgets/type_widgets/file.js +++ b/src/public/app/widgets/type_widgets/file.js @@ -75,11 +75,17 @@ export default class FileTypeWidget extends TypeWidget { this.$downloadButton.on('click', () => utils.download(this.getFileUrl())); - this.$openButton.on('click', () => { + this.$openButton.on('click', async () => { if (utils.isElectron()) { - const open = utils.dynamicRequire("open"); + const resp = await server.post("notes/" + this.noteId + "/saveToTmpDir"); - open(this.getFileUrl(), {url: true}); + const electron = utils.dynamicRequire('electron'); + const res = await electron.shell.openPath(resp.tmpFilePath); + + if (res) { + // fallback in case there's no default application for this file + open(this.getFileUrl(), {url: true}); + } } else { window.location.href = this.getFileUrl(); diff --git a/src/routes/api/files.js b/src/routes/api/files.js index 84b3dd30c..2afb25466 100644 --- a/src/routes/api/files.js +++ b/src/routes/api/files.js @@ -4,6 +4,8 @@ const protectedSessionService = require('../../services/protected_session'); const repository = require('../../services/repository'); const utils = require('../../services/utils'); const noteRevisionService = require('../../services/note_revisions'); +const tmp = require('tmp'); +const fs = require('fs'); function updateFile(req) { const {noteId} = req.params; @@ -31,6 +33,12 @@ function updateFile(req) { }; } +function getFilename(note) { + // (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 + return utils.formatDownloadTitle(note.title, note.type, note.mime); +} + function downloadNoteFile(noteId, res, contentDisposition = true) { const note = repository.getNote(noteId); @@ -43,9 +51,7 @@ function downloadNoteFile(noteId, res, contentDisposition = true) { } if (contentDisposition) { - // (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 - const filename = utils.formatDownloadTitle(note.title, note.type, note.mime); + const filename = getFilename(note); res.setHeader('Content-Disposition', utils.getContentDisposition(filename)); } @@ -67,9 +73,29 @@ function openFile(req, res) { return downloadNoteFile(noteId, res, false); } +function saveToTmpDir(req) { + const noteId = req.params.noteId; + + const note = repository.getNote(noteId); + + if (!note) { + return [404,`Note ${noteId} doesn't exist.`]; + } + + const tmpObj = tmp.fileSync({postfix: getFilename(note)}); + + fs.writeSync(tmpObj.fd, note.getContent()); + fs.closeSync(tmpObj.fd); + + return { + tmpFilePath: tmpObj.name + }; +} + module.exports = { updateFile, openFile, downloadFile, - downloadNoteFile + downloadNoteFile, + saveToTmpDir }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 9f25a49d4..f52424ee0 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -171,6 +171,7 @@ function register(app) { route(GET, '/api/notes/:noteId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadFile); // this "hacky" path is used for easier referencing of CSS resources route(GET, '/api/notes/download/:noteId', [auth.checkApiAuthOrElectron], filesRoute.downloadFile); + apiRoute(POST, '/api/notes/:noteId/saveToTmpDir', filesRoute.saveToTmpDir); apiRoute(GET, '/api/notes/:noteId/attributes', attributesRoute.getEffectiveNoteAttributes); apiRoute(PUT, '/api/notes/:noteId/attributes', attributesRoute.updateNoteAttributes);