From cdde6a4d8efff67d14d24cc178426ea836d221e0 Mon Sep 17 00:00:00 2001 From: azivner Date: Wed, 14 Feb 2018 23:31:20 -0500 Subject: [PATCH] file/attachment upload, wiP --- src/public/javascripts/api.js | 4 +- src/public/javascripts/context_menu.js | 6 +- src/public/javascripts/init.js | 24 ++++++- src/public/javascripts/link.js | 6 +- src/public/javascripts/messaging.js | 4 +- src/public/javascripts/note_editor.js | 79 +++++++++++---------- src/public/javascripts/note_tree.js | 26 +++---- src/public/javascripts/note_type.js | 4 +- src/public/javascripts/protected_session.js | 28 ++++---- src/public/javascripts/search_tree.js | 28 ++++---- src/public/javascripts/tree_utils.js | 4 +- src/routes/api/attachments.js | 36 ++++++++++ src/routes/routes.js | 2 + src/views/index.ejs | 8 +++ 14 files changed, 166 insertions(+), 93 deletions(-) create mode 100644 src/routes/api/attachments.js diff --git a/src/public/javascripts/api.js b/src/public/javascripts/api.js index 941187d2b..20f25c36b 100644 --- a/src/public/javascripts/api.js +++ b/src/public/javascripts/api.js @@ -1,5 +1,5 @@ const api = (function() { - const pluginButtonsEl = $("#plugin-buttons"); + const $pluginButtons = $("#plugin-buttons"); async function activateNote(notePath) { await noteTree.activateNode(notePath); @@ -10,7 +10,7 @@ const api = (function() { button.attr('id', buttonId); - pluginButtonsEl.append(button); + $pluginButtons.append(button); } diff --git a/src/public/javascripts/context_menu.js b/src/public/javascripts/context_menu.js index 775c80db0..36c8ebc33 100644 --- a/src/public/javascripts/context_menu.js +++ b/src/public/javascripts/context_menu.js @@ -1,7 +1,7 @@ "use strict"; const contextMenu = (function() { - const treeEl = $("#tree"); + const $tree = $("#tree"); let clipboardIds = []; let clipboardMode = null; @@ -93,8 +93,8 @@ const contextMenu = (function() { beforeOpen: (event, ui) => { const node = $.ui.fancytree.getNode(ui.target); // Modify menu entries depending on node status - treeEl.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0); - treeEl.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0); + $tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0); + $tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0); // Activate node on right-click node.setActive(); diff --git a/src/public/javascripts/init.js b/src/public/javascripts/init.js index 0eb294fdc..ac32d3d73 100644 --- a/src/public/javascripts/init.js +++ b/src/public/javascripts/init.js @@ -211,4 +211,26 @@ if (isElectron()) { await noteTree.createNote(node, node.data.noteId, 'into', node.data.isProtected); }); -} \ No newline at end of file +} + +function uploadAttachment() { + $("#file-upload").trigger('click'); +} + +$("#file-upload").change(async function() { + const formData = new FormData(); + formData.append('upload', this.files[0]); + + const resp = await $.ajax({ + url: baseApiUrl + 'attachments/upload/' + noteEditor.getCurrentNoteId(), + headers: server.getHeaders(), + data: formData, + type: 'POST', + contentType: false, // NEEDED, DON'T OMIT THIS + processData: false, // NEEDED, DON'T OMIT THIS + }); + + await noteTree.reload(); + + await noteTree.activateNode(resp.noteId); +}); \ No newline at end of file diff --git a/src/public/javascripts/link.js b/src/public/javascripts/link.js index 29041a436..a5b2bd79f 100644 --- a/src/public/javascripts/link.js +++ b/src/public/javascripts/link.js @@ -41,11 +41,11 @@ const link = (function() { function goToLink(e) { e.preventDefault(); - const linkEl = $(e.target); - let notePath = linkEl.attr("note-path"); + const $link = $(e.target); + let notePath = $link.attr("note-path"); if (!notePath) { - const address = linkEl.attr("note-path") ? linkEl.attr("note-path") : linkEl.attr('href'); + const address = $link.attr("note-path") ? $link.attr("note-path") : $link.attr('href'); if (!address) { return; diff --git a/src/public/javascripts/messaging.js b/src/public/javascripts/messaging.js index 704d1bf1d..2ad1c0127 100644 --- a/src/public/javascripts/messaging.js +++ b/src/public/javascripts/messaging.js @@ -1,7 +1,7 @@ "use strict"; const messaging = (function() { - const changesToPushCountEl = $("#changes-to-push-count"); + const $changesToPushCount = $("#changes-to-push-count"); function logError(message) { console.log(now(), message); // needs to be separate from .trace() @@ -52,7 +52,7 @@ const messaging = (function() { // we don't detect image changes here since images themselves are immutable and references should be // updated in note detail as well - changesToPushCountEl.html(message.changesToPushCount); + $changesToPushCount.html(message.changesToPushCount); } else if (message.type === 'sync-hash-check-failed') { showError("Sync check failed!", 60000); diff --git a/src/public/javascripts/note_editor.js b/src/public/javascripts/note_editor.js index 048566aee..766b957b5 100644 --- a/src/public/javascripts/note_editor.js +++ b/src/public/javascripts/note_editor.js @@ -1,16 +1,19 @@ "use strict"; const noteEditor = (function() { - const noteTitleEl = $("#note-title"); - const noteDetailEl = $('#note-detail'); - const noteDetailCodeEl = $('#note-detail-code'); - const noteDetailRenderEl = $('#note-detail-render'); - const protectButton = $("#protect-button"); - const unprotectButton = $("#unprotect-button"); - const noteDetailWrapperEl = $("#note-detail-wrapper"); - const noteIdDisplayEl = $("#note-id-display"); - const attributeListEl = $("#attribute-list"); - const attributeListInnerEl = $("#attribute-list-inner"); + const $noteTitle = $("#note-title"); + + const $noteDetail = $('#note-detail'); + const $noteDetailCode = $('#note-detail-code'); + const $noteDetailRender = $('#note-detail-render'); + const $noteDetailAttachment = $('#note-detail-attachment'); + + const $protectButton = $("#protect-button"); + const $unprotectButton = $("#unprotect-button"); + const $noteDetailWrapper = $("#note-detail-wrapper"); + const $noteIdDisplay = $("#note-id-display"); + const $attributeList = $("#attribute-list"); + const $attributeListInner = $("#attribute-list-inner"); let editor = null; let codeEditor = null; @@ -87,7 +90,7 @@ const noteEditor = (function() { throwError("Unrecognized type: " + note.detail.type); } - const title = noteTitleEl.val(); + const title = $noteTitle.val(); note.detail.title = title; @@ -105,9 +108,9 @@ const noteEditor = (function() { function setNoteBackgroundIfProtected(note) { const isProtected = !!note.detail.isProtected; - noteDetailWrapperEl.toggleClass("protected", isProtected); - protectButton.toggle(!isProtected); - unprotectButton.toggle(isProtected); + $noteDetailWrapper.toggleClass("protected", isProtected); + $protectButton.toggle(!isProtected); + $unprotectButton.toggle(isProtected); } let isNewNoteCreated = false; @@ -121,14 +124,10 @@ const noteEditor = (function() { // temporary workaround for https://github.com/ckeditor/ckeditor5-enter/issues/49 editor.setData(content ? content : "

"); - noteDetailEl.show(); - noteDetailCodeEl.hide(); - noteDetailRenderEl.html('').hide(); + $noteDetail.show(); } else if (currentNote.detail.type === 'code') { - noteDetailEl.hide(); - noteDetailCodeEl.show(); - noteDetailRenderEl.html('').hide(); + $noteDetailCode.show(); // this needs to happen after the element is shown, otherwise the editor won't be refresheds codeEditor.setValue(content); @@ -148,10 +147,10 @@ const noteEditor = (function() { if (isNewNoteCreated) { isNewNoteCreated = false; - noteTitleEl.focus().select(); + $noteTitle.focus().select(); } - noteIdDisplayEl.html(noteId); + $noteIdDisplay.html(noteId); await protected_session.ensureProtectedSession(currentNote.detail.isProtected, false); @@ -163,23 +162,29 @@ const noteEditor = (function() { // to login, but we chose instead to come to another node - at that point the dialog is still visible and this will close it. protected_session.ensureDialogIsClosed(); - noteDetailWrapperEl.show(); + $noteDetailWrapper.show(); noteChangeDisabled = true; - noteTitleEl.val(currentNote.detail.title); + $noteTitle.val(currentNote.detail.title); noteType.setNoteType(currentNote.detail.type); noteType.setNoteMime(currentNote.detail.mime); + $noteDetail.hide(); + $noteDetailCode.hide(); + $noteDetailRender.html('').hide(); + $noteDetailAttachment.hide(); + if (currentNote.detail.type === 'render') { - noteDetailEl.hide(); - noteDetailCodeEl.hide(); - noteDetailRenderEl.html('').show(); + $noteDetailRender.show(); const subTree = await server.get('script/subtree/' + getCurrentNoteId()); - noteDetailRenderEl.html(subTree); + $noteDetailRender.html(subTree); + } + else if (currentNote.detail.type === 'file') { + $noteDetailAttachment.show(); } else { setContent(currentNote.detail.content); @@ -191,7 +196,7 @@ const noteEditor = (function() { noteTree.setNoteTreeBackgroundBasedOnProtectedStatus(noteId); // after loading new note make sure editor is scrolled to the top - noteDetailWrapperEl.scrollTop(0); + $noteDetailWrapper.scrollTop(0); loadAttributeList(); } @@ -201,17 +206,17 @@ const noteEditor = (function() { const attributes = await server.get('notes/' + noteId + '/attributes'); - attributeListInnerEl.html(''); + $attributeListInner.html(''); if (attributes.length > 0) { for (const attr of attributes) { - attributeListInnerEl.append(formatAttribute(attr) + " "); + $attributeListInner.append(formatAttribute(attr) + " "); } - attributeListEl.show(); + $attributeList.show(); } else { - attributeListEl.hide(); + $attributeList.hide(); } } @@ -227,7 +232,7 @@ const noteEditor = (function() { const note = getCurrentNote(); if (note.detail.type === 'text') { - noteDetailEl.focus(); + $noteDetail.focus(); } else if (note.detail.type === 'code') { codeEditor.focus(); @@ -258,10 +263,10 @@ const noteEditor = (function() { } $(document).ready(() => { - noteTitleEl.on('input', () => { + $noteTitle.on('input', () => { noteChanged(); - const title = noteTitleEl.val(); + const title = $noteTitle.val(); noteTree.setNoteTitle(getCurrentNoteId(), title); }); @@ -295,7 +300,7 @@ const noteEditor = (function() { codeEditor.on('change', noteChanged); // so that tab jumps from note title (which has tabindex 1) - noteDetailEl.attr("tabindex", 2); + $noteDetail.attr("tabindex", 2); }); $(document).bind('keydown', "ctrl+return", executeCurrentNote); diff --git a/src/public/javascripts/note_tree.js b/src/public/javascripts/note_tree.js index 4345c97e1..8e82153dd 100644 --- a/src/public/javascripts/note_tree.js +++ b/src/public/javascripts/note_tree.js @@ -1,9 +1,9 @@ "use strict"; const noteTree = (function() { - const treeEl = $("#tree"); - const parentListEl = $("#parent-list"); - const parentListListEl = $("#parent-list-inner"); + const $tree = $("#tree"); + const $parentList = $("#parent-list"); + const $parentListList = $("#parent-list-inner"); let startNotePath = null; let notesTreeMap = {}; @@ -52,7 +52,7 @@ const noteTree = (function() { // note that if you want to access data like noteId or isProtected, you need to go into "data" property function getCurrentNode() { - return treeEl.fancytree("getActiveNode"); + return $tree.fancytree("getActiveNode"); } function getCurrentNotePath() { @@ -314,11 +314,11 @@ const noteTree = (function() { } if (parents.length <= 1) { - parentListEl.hide(); + $parentList.hide(); } else { - parentListEl.show(); - parentListListEl.empty(); + $parentList.show(); + $parentListList.empty(); for (const parentNoteId of parents) { const parentNotePath = getSomeNotePath(parentNoteId); @@ -335,7 +335,7 @@ const noteTree = (function() { item = link.createNoteLink(notePath, title); } - parentListListEl.append($("
  • ").append(item)); + $parentListList.append($("
  • ").append(item)); } } } @@ -543,7 +543,7 @@ const noteTree = (function() { } }; - treeEl.fancytree({ + $tree.fancytree({ autoScroll: true, keyboard: false, // we takover keyboard handling in the hotkeys plugin extensions: ["hotkeys", "filter", "dnd", "clones"], @@ -624,11 +624,11 @@ const noteTree = (function() { } }); - treeEl.contextmenu(contextMenu.contextMenuSettings); + $tree.contextmenu(contextMenu.contextMenuSettings); } function getTree() { - return treeEl.fancytree('getTree'); + return $tree.fancytree('getTree'); } async function reload() { @@ -663,7 +663,7 @@ const noteTree = (function() { function collapseTree(node = null) { if (!node) { - node = treeEl.fancytree("getRootNode"); + node = $tree.fancytree("getRootNode"); } node.setExpanded(false); @@ -744,7 +744,7 @@ const noteTree = (function() { } async function createNewTopLevelNote() { - const rootNode = treeEl.fancytree("getRootNode"); + const rootNode = $tree.fancytree("getRootNode"); await createNote(rootNode, "root", "into"); } diff --git a/src/public/javascripts/note_type.js b/src/public/javascripts/note_type.js index 715ba1ab6..3e78ab6de 100644 --- a/src/public/javascripts/note_type.js +++ b/src/public/javascripts/note_type.js @@ -1,7 +1,7 @@ "use strict"; const noteType = (function() { - const executeScriptButton = $("#execute-script-button"); + const $executeScriptButton = $("#execute-script-button"); const noteTypeModel = new NoteTypeModel(); function NoteTypeModel() { @@ -114,7 +114,7 @@ const noteType = (function() { }; this.updateExecuteScriptButtonVisibility = function() { - executeScriptButton.toggle(self.mime() === 'application/javascript'); + $executeScriptButton.toggle(self.mime() === 'application/javascript'); } } diff --git a/src/public/javascripts/protected_session.js b/src/public/javascripts/protected_session.js index f4f321fdc..77269388a 100644 --- a/src/public/javascripts/protected_session.js +++ b/src/public/javascripts/protected_session.js @@ -1,10 +1,10 @@ "use strict"; const protected_session = (function() { - const dialogEl = $("#protected-session-password-dialog"); - const passwordFormEl = $("#protected-session-password-form"); - const passwordEl = $("#protected-session-password"); - const noteDetailWrapperEl = $("#note-detail-wrapper"); + const $dialog = $("#protected-session-password-dialog"); + const $passwordForm = $("#protected-session-password-form"); + const $password = $("#protected-session-password"); + const $noteDetailWrapper = $("#note-detail-wrapper"); let protectedSessionDeferred = null; let lastProtectedSessionOperationDate = null; @@ -25,9 +25,9 @@ const protected_session = (function() { if (requireProtectedSession && !isProtectedSessionAvailable()) { protectedSessionDeferred = dfd; - noteDetailWrapperEl.hide(); + $noteDetailWrapper.hide(); - dialogEl.dialog({ + $dialog.dialog({ modal: modal, width: 400, open: () => { @@ -46,8 +46,8 @@ const protected_session = (function() { } async function setupProtectedSession() { - const password = passwordEl.val(); - passwordEl.val(""); + const password = $password.val(); + $password.val(""); const response = await enterProtectedSession(password); @@ -58,15 +58,15 @@ const protected_session = (function() { protectedSessionId = response.protectedSessionId; - dialogEl.dialog("close"); + $dialog.dialog("close"); noteEditor.reload(); noteTree.reload(); if (protectedSessionDeferred !== null) { - ensureDialogIsClosed(dialogEl, passwordEl); + ensureDialogIsClosed($dialog, $password); - noteDetailWrapperEl.show(); + $noteDetailWrapper.show(); protectedSessionDeferred.resolve(); @@ -77,11 +77,11 @@ const protected_session = (function() { function ensureDialogIsClosed() { // this may fal if the dialog has not been previously opened try { - dialogEl.dialog('close'); + $dialog.dialog('close'); } catch (e) {} - passwordEl.val(''); + $password.val(''); } async function enterProtectedSession(password) { @@ -155,7 +155,7 @@ const protected_session = (function() { noteEditor.reload(); } - passwordFormEl.submit(() => { + $passwordForm.submit(() => { setupProtectedSession(); return false; diff --git a/src/public/javascripts/search_tree.js b/src/public/javascripts/search_tree.js index e2e8b598c..59386099a 100644 --- a/src/public/javascripts/search_tree.js +++ b/src/public/javascripts/search_tree.js @@ -1,40 +1,40 @@ "use strict"; const searchTree = (function() { - const treeEl = $("#tree"); - const searchInputEl = $("input[name='search-text']"); - const resetSearchButton = $("button#reset-search-button"); - const searchBoxEl = $("#search-box"); + const $tree = $("#tree"); + const $searchInput = $("input[name='search-text']"); + const $resetSearchButton = $("button#reset-search-button"); + const $searchBox = $("#search-box"); - resetSearchButton.click(resetSearch); + $resetSearchButton.click(resetSearch); function toggleSearch() { - if (searchBoxEl.is(":hidden")) { - searchBoxEl.show(); - searchInputEl.focus(); + if ($searchBox.is(":hidden")) { + $searchBox.show(); + $searchInput.focus(); } else { resetSearch(); - searchBoxEl.hide(); + $searchBox.hide(); } } function resetSearch() { - searchInputEl.val(""); + $searchInput.val(""); getTree().clearFilter(); } function getTree() { - return treeEl.fancytree('getTree'); + return $tree.fancytree('getTree'); } - searchInputEl.keyup(async e => { - const searchText = searchInputEl.val(); + $searchInput.keyup(async e => { + const searchText = $searchInput.val(); if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(searchText) === "") { - resetSearchButton.click(); + $resetSearchButton.click(); return; } diff --git a/src/public/javascripts/tree_utils.js b/src/public/javascripts/tree_utils.js index 0ca2b96e4..7a183d4a9 100644 --- a/src/public/javascripts/tree_utils.js +++ b/src/public/javascripts/tree_utils.js @@ -1,14 +1,14 @@ "use strict"; const treeUtils = (function() { - const treeEl = $("#tree"); + const $tree = $("#tree"); function getParentProtectedStatus(node) { return isTopLevelNode(node) ? 0 : node.getParent().data.isProtected; } function getNodeByKey(key) { - return treeEl.fancytree('getNodeByKey', key); + return $tree.fancytree('getNodeByKey', key); } function getNoteIdFromNotePath(notePath) { diff --git a/src/routes/api/attachments.js b/src/routes/api/attachments.js new file mode 100644 index 000000000..185d51c88 --- /dev/null +++ b/src/routes/api/attachments.js @@ -0,0 +1,36 @@ +"use strict"; + +const express = require('express'); +const router = express.Router(); +const sql = require('../../services/sql'); +const auth = require('../../services/auth'); +const notes = require('../../services/notes'); +const multer = require('multer')(); +const wrap = require('express-promise-wrap').wrap; + +router.post('/upload/:parentNoteId', auth.checkApiAuthOrElectron, multer.single('upload'), wrap(async (req, res, next) => { + const sourceId = req.headers.source_id; + const parentNoteId = req.params.parentNoteId; + const file = req.file; + + const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [parentNoteId]); + + if (!note) { + 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; + + res.send({ + noteId: noteId + }); +})); + +module.exports = router; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index d8050f9f6..0d3704b5d 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -29,6 +29,7 @@ const imageRoute = require('./api/image'); const attributesRoute = require('./api/attributes'); const scriptRoute = require('./api/script'); const senderRoute = require('./api/sender'); +const attachmentsRoute = require('./api/attachments'); function register(app) { app.use('/', indexRoute); @@ -61,6 +62,7 @@ function register(app) { app.use('/api/images', imageRoute); app.use('/api/script', scriptRoute); app.use('/api/sender', senderRoute); + app.use('/api/attachments', attachmentsRoute); } module.exports = { diff --git a/src/views/index.ejs b/src/views/index.ejs index 1d5de24ef..4df7943da 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -130,6 +130,7 @@
  • Alt+H History
  • Alt+A Attributes
  • Ctrl+U HTML source
  • +
  • Upload attachment
  • @@ -141,6 +142,13 @@
    + +
    + Attachment!!! + +
    + +