attachment upload and download now works for browser

This commit is contained in:
azivner 2018-02-18 21:28:24 -05:00
parent fda4146150
commit 78e8c15786
10 changed files with 98 additions and 19 deletions

View File

@ -14,6 +14,10 @@ const noteEditor = (function() {
const $noteIdDisplay = $("#note-id-display"); const $noteIdDisplay = $("#note-id-display");
const $attributeList = $("#attribute-list"); const $attributeList = $("#attribute-list");
const $attributeListInner = $("#attribute-list-inner"); 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 editor = null;
let codeEditor = null; let codeEditor = null;
@ -83,7 +87,7 @@ const noteEditor = (function() {
else if (note.detail.type === 'code') { else if (note.detail.type === 'code') {
note.detail.content = codeEditor.getValue(); note.detail.content = codeEditor.getValue();
} }
else if (note.detail.type === 'render') { else if (note.detail.type === 'render' || note.detail.type === 'file') {
// nothing // nothing
} }
else { else {
@ -185,6 +189,10 @@ const noteEditor = (function() {
} }
else if (currentNote.detail.type === 'file') { else if (currentNote.detail.type === 'file') {
$noteDetailAttachment.show(); $noteDetailAttachment.show();
$attachmentFileName.text(currentNote.attributes.original_file_name);
$attachmentFileSize.text(currentNote.attributes.file_size + " bytes");
$attachmentFileType.text(currentNote.detail.mime);
} }
else { else {
setContent(currentNote.detail.content); setContent(currentNote.detail.content);
@ -237,7 +245,7 @@ const noteEditor = (function() {
else if (note.detail.type === 'code') { else if (note.detail.type === 'code') {
codeEditor.focus(); codeEditor.focus();
} }
else if (note.detail.type === 'render') { else if (note.detail.type === 'render' || note.detail.type === 'file') {
// do nothing // do nothing
} }
else { else {
@ -262,6 +270,10 @@ const noteEditor = (function() {
} }
} }
$attachmentDownload.click(() => {
window.location.href = "/api/attachments/download/" + getCurrentNoteId();
});
$(document).ready(() => { $(document).ready(() => {
$noteTitle.on('input', () => { $noteTitle.on('input', () => {
noteChanged(); noteChanged();

View File

@ -65,11 +65,18 @@ const noteType = (function() {
else if (type === 'render') { else if (type === 'render') {
return 'Render HTML note'; return 'Render HTML note';
} }
else if (type === 'file') {
return 'Attachment';
}
else { else {
throwError('Unrecognized type: ' + type); throwError('Unrecognized type: ' + type);
} }
}; };
this.isDisabled = function() {
return self.type() === "file";
};
async function save() { async function save() {
const note = noteEditor.getCurrentNote(); const note = noteEditor.getCurrentNote();

View File

@ -116,6 +116,7 @@ const server = (function() {
put, put,
remove, remove,
exec, exec,
ajax,
// don't remove, used from CKEditor image upload! // don't remove, used from CKEditor image upload!
getHeaders getHeaders
} }

View File

@ -268,4 +268,9 @@ div.ui-tooltip {
#attribute-list button { #attribute-list button {
padding: 2px; padding: 2px;
margin-right: 5px; margin-right: 5px;
}
#attachment-table th, #attachment-table td {
padding: 10px;
font-size: large;
} }

View File

@ -5,6 +5,7 @@ const router = express.Router();
const sql = require('../../services/sql'); const sql = require('../../services/sql');
const auth = require('../../services/auth'); const auth = require('../../services/auth');
const notes = require('../../services/notes'); const notes = require('../../services/notes');
const attributes = require('../../services/attributes');
const multer = require('multer')(); const multer = require('multer')();
const wrap = require('express-promise-wrap').wrap; 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 sourceId = req.headers.source_id;
const parentNoteId = req.params.parentNoteId; const parentNoteId = req.params.parentNoteId;
const file = req.file; const file = req.file;
const originalName = file.originalname;
const size = file.size;
const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [parentNoteId]); 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.`); return res.status(404).send(`Note ${parentNoteId} doesn't exist.`);
} }
const noteId = (await notes.createNewNote(parentNoteId, { await sql.doInTransaction(async () => {
title: "attachment", const noteId = (await notes.createNewNote(parentNoteId, {
content: file.buffer, title: originalName,
target: 'into', content: file.buffer,
isProtected: false, target: 'into',
type: 'file', isProtected: false,
mime: '' type: 'file',
})).noteId; mime: file.mimetype
}, req, sourceId)).noteId;
res.send({ await attributes.createAttribute(noteId, "original_file_name", originalName);
noteId: noteId 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; module.exports = router;

View File

@ -5,6 +5,7 @@ const router = express.Router();
const auth = require('../../services/auth'); const auth = require('../../services/auth');
const sql = require('../../services/sql'); const sql = require('../../services/sql');
const notes = require('../../services/notes'); const notes = require('../../services/notes');
const attributes = require('../../services/attributes');
const log = require('../../services/log'); const log = require('../../services/log');
const utils = require('../../services/utils'); const utils = require('../../services/utils');
const protected_session = require('../../services/protected_session'); 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); 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({ res.send({
detail: detail detail: detail,
attributes: attributeMap
}); });
})); }));

View File

@ -32,7 +32,7 @@
$date.datepicker('setDate', new Date()); $date.datepicker('setDate', new Date());
async function saveWeight() { 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); const dataNote = await this.getNoteWithAttribute('date_data', date);
if (dataNote) { if (dataNote) {

View File

@ -13,7 +13,7 @@ const BUILTIN_ATTRIBUTES = [
]; ];
async function getNoteAttributeMap(noteId) { 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) { async function getNoteIdWithAttribute(name, value) {

View File

@ -214,7 +214,7 @@ async function runAllChecks() {
FROM FROM
notes notes
WHERE 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); "Note has invalid type", errorList);
await runSyncRowChecks("notes", "noteId", errorList); await runSyncRowChecks("notes", "noteId", errorList);

View File

@ -105,7 +105,7 @@
onclick="noteEditor.executeCurrentNote()">Execute <kbd>Ctrl+Enter</kbd></button> onclick="noteEditor.executeCurrentNote()">Execute <kbd>Ctrl+Enter</kbd></button>
<div class="dropdown" id="note-type"> <div class="dropdown" id="note-type">
<button id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm"> <button data-bind="disable: isDisabled()" id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm">
Type: <span data-bind="text: typeString()"></span> Type: <span data-bind="text: typeString()"></span>
<span class="caret"></span> <span class="caret"></span>
</button> </button>
@ -144,8 +144,25 @@
<div id="note-detail-render"></div> <div id="note-detail-render"></div>
<div id="note-detail-attachment"> <div id="note-detail-attachment">
Attachment!!! <table id="attachment-table">
<tr>
<th>File name:</th>
<td id="attachment-filename"></td>
</tr>
<tr>
<th>File type:</th>
<td id="attachment-filetype"></td>
</tr>
<tr>
<th>File size:</th>
<td id="attachment-filesize"></td>
</tr>
<tr>
<td>
<button id="attachment-download" class="btn btn-primary" type="button">Download</button>
</td>
</tr>
</table>
</div> </div>
<input type="file" id="file-upload" style="display: none" /> <input type="file" id="file-upload" style="display: none" />