diff --git a/src/public/javascripts/note_editor.js b/src/public/javascripts/note_editor.js index 766b957b5..c130ac583 100644 --- a/src/public/javascripts/note_editor.js +++ b/src/public/javascripts/note_editor.js @@ -14,6 +14,10 @@ const noteEditor = (function() { const $noteIdDisplay = $("#note-id-display"); const $attributeList = $("#attribute-list"); const $attributeListInner = $("#attribute-list-inner"); + const $attachmentFileName = $("#attachment-filename"); + const $attachmentFileType = $("#attachment-filetype"); + const $attachmentFileSize = $("#attachment-filesize"); + const $attachmentDownload = $("#attachment-download"); let editor = null; let codeEditor = null; @@ -83,7 +87,7 @@ const noteEditor = (function() { else if (note.detail.type === 'code') { note.detail.content = codeEditor.getValue(); } - else if (note.detail.type === 'render') { + else if (note.detail.type === 'render' || note.detail.type === 'file') { // nothing } else { @@ -185,6 +189,10 @@ const noteEditor = (function() { } else if (currentNote.detail.type === 'file') { $noteDetailAttachment.show(); + + $attachmentFileName.text(currentNote.attributes.original_file_name); + $attachmentFileSize.text(currentNote.attributes.file_size + " bytes"); + $attachmentFileType.text(currentNote.detail.mime); } else { setContent(currentNote.detail.content); @@ -237,7 +245,7 @@ const noteEditor = (function() { else if (note.detail.type === 'code') { codeEditor.focus(); } - else if (note.detail.type === 'render') { + else if (note.detail.type === 'render' || note.detail.type === 'file') { // do nothing } else { @@ -262,6 +270,10 @@ const noteEditor = (function() { } } + $attachmentDownload.click(() => { + window.location.href = "/api/attachments/download/" + getCurrentNoteId(); + }); + $(document).ready(() => { $noteTitle.on('input', () => { noteChanged(); diff --git a/src/public/javascripts/note_type.js b/src/public/javascripts/note_type.js index 3e78ab6de..916bb9427 100644 --- a/src/public/javascripts/note_type.js +++ b/src/public/javascripts/note_type.js @@ -65,11 +65,18 @@ const noteType = (function() { else if (type === 'render') { return 'Render HTML note'; } + else if (type === 'file') { + return 'Attachment'; + } else { throwError('Unrecognized type: ' + type); } }; + this.isDisabled = function() { + return self.type() === "file"; + }; + async function save() { const note = noteEditor.getCurrentNote(); diff --git a/src/public/javascripts/server.js b/src/public/javascripts/server.js index 8d3002d06..bbdab12f4 100644 --- a/src/public/javascripts/server.js +++ b/src/public/javascripts/server.js @@ -116,6 +116,7 @@ const server = (function() { put, remove, exec, + ajax, // don't remove, used from CKEditor image upload! getHeaders } diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index 66be78699..de8f6075d 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -268,4 +268,9 @@ div.ui-tooltip { #attribute-list button { padding: 2px; margin-right: 5px; +} + +#attachment-table th, #attachment-table td { + padding: 10px; + font-size: large; } \ No newline at end of file diff --git a/src/routes/api/attachments.js b/src/routes/api/attachments.js index 185d51c88..0da5b5e14 100644 --- a/src/routes/api/attachments.js +++ b/src/routes/api/attachments.js @@ -5,6 +5,7 @@ const router = express.Router(); const sql = require('../../services/sql'); const auth = require('../../services/auth'); const notes = require('../../services/notes'); +const attributes = require('../../services/attributes'); const multer = require('multer')(); const wrap = require('express-promise-wrap').wrap; @@ -12,6 +13,8 @@ router.post('/upload/:parentNoteId', auth.checkApiAuthOrElectron, multer.single( const sourceId = req.headers.source_id; const parentNoteId = req.params.parentNoteId; const file = req.file; + const originalName = file.originalname; + const size = file.size; const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [parentNoteId]); @@ -19,18 +22,40 @@ router.post('/upload/:parentNoteId', auth.checkApiAuthOrElectron, multer.single( return res.status(404).send(`Note ${parentNoteId} doesn't exist.`); } - const noteId = (await notes.createNewNote(parentNoteId, { - title: "attachment", - content: file.buffer, - target: 'into', - isProtected: false, - type: 'file', - mime: '' - })).noteId; + await sql.doInTransaction(async () => { + const noteId = (await notes.createNewNote(parentNoteId, { + title: originalName, + content: file.buffer, + target: 'into', + isProtected: false, + type: 'file', + mime: file.mimetype + }, req, sourceId)).noteId; - res.send({ - noteId: noteId + await attributes.createAttribute(noteId, "original_file_name", originalName); + await attributes.createAttribute(noteId, "file_size", size); + + res.send({ + noteId: noteId + }); }); })); +router.get('/download/:noteId', auth.checkApiAuthOrElectron, wrap(async (req, res, next) => { + const noteId = req.params.noteId; + const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); + + if (!note) { + return res.status(404).send(`Note ${parentNoteId} doesn't exist.`); + } + + const attributeMap = await attributes.getNoteAttributeMap(noteId); + const fileName = attributeMap.original_file_name ? attributeMap.original_file_name : note.title; + + res.setHeader('Content-Disposition', 'attachment; filename=' + fileName); + res.setHeader('Content-Type', note.mime); + + res.send(note.content); +})); + module.exports = router; \ No newline at end of file diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index bb69c817b..6f058ee59 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -5,6 +5,7 @@ const router = express.Router(); const auth = require('../../services/auth'); const sql = require('../../services/sql'); const notes = require('../../services/notes'); +const attributes = require('../../services/attributes'); const log = require('../../services/log'); const utils = require('../../services/utils'); const protected_session = require('../../services/protected_session'); @@ -25,8 +26,19 @@ router.get('/:noteId', auth.checkApiAuth, wrap(async (req, res, next) => { protected_session.decryptNote(req, detail); + let attributeMap = null; + + if (detail.type === 'file') { + // no need to transfer attachment payload for this request + detail.content = null; + + // attributes contain important attachment metadata - filename and size + attributeMap = await attributes.getNoteAttributeMap(noteId); + } + res.send({ - detail: detail + detail: detail, + attributes: attributeMap }); })); diff --git a/src/scripts/weight.html b/src/scripts/weight.html index b3ae26702..c976058d7 100644 --- a/src/scripts/weight.html +++ b/src/scripts/weight.html @@ -32,7 +32,7 @@ $date.datepicker('setDate', new Date()); async function saveWeight() { - await server.exec([$date.val(), $weight.val(), $comment.val()], async (date, weight, comment) => { + await server.exec([$date.val(), parseFloat($weight.val()), $comment.val()], async (date, weight, comment) => { const dataNote = await this.getNoteWithAttribute('date_data', date); if (dataNote) { diff --git a/src/services/attributes.js b/src/services/attributes.js index a1c26b26e..f1f752c0b 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -13,7 +13,7 @@ const BUILTIN_ATTRIBUTES = [ ]; async function getNoteAttributeMap(noteId) { - return await sql.getMap(`SELECT name, value FROM attributes WHERE noteId = ?`, [noteId]); + return await sql.getMap(`SELECT name, value FROM attributes WHERE noteId = ? AND isDeleted = 0`, [noteId]); } async function getNoteIdWithAttribute(name, value) { diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js index f1a976c22..fdb3dd122 100644 --- a/src/services/consistency_checks.js +++ b/src/services/consistency_checks.js @@ -214,7 +214,7 @@ async function runAllChecks() { FROM notes WHERE - type != 'text' AND type != 'code' AND type != 'render'`, + type != 'text' AND type != 'code' AND type != 'render' AND type != 'file'`, "Note has invalid type", errorList); await runSyncRowChecks("notes", "noteId", errorList); diff --git a/src/views/index.ejs b/src/views/index.ejs index 4df7943da..ac2d9648e 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -105,7 +105,7 @@ onclick="noteEditor.executeCurrentNote()">Execute Ctrl+Enter