file/attachment upload, wiP

This commit is contained in:
azivner 2018-02-14 23:31:20 -05:00
parent 8028b09351
commit cdde6a4d8e
14 changed files with 166 additions and 93 deletions

View File

@ -1,5 +1,5 @@
const api = (function() { const api = (function() {
const pluginButtonsEl = $("#plugin-buttons"); const $pluginButtons = $("#plugin-buttons");
async function activateNote(notePath) { async function activateNote(notePath) {
await noteTree.activateNode(notePath); await noteTree.activateNode(notePath);
@ -10,7 +10,7 @@ const api = (function() {
button.attr('id', buttonId); button.attr('id', buttonId);
pluginButtonsEl.append(button); $pluginButtons.append(button);
} }

View File

@ -1,7 +1,7 @@
"use strict"; "use strict";
const contextMenu = (function() { const contextMenu = (function() {
const treeEl = $("#tree"); const $tree = $("#tree");
let clipboardIds = []; let clipboardIds = [];
let clipboardMode = null; let clipboardMode = null;
@ -93,8 +93,8 @@ const contextMenu = (function() {
beforeOpen: (event, ui) => { beforeOpen: (event, ui) => {
const node = $.ui.fancytree.getNode(ui.target); const node = $.ui.fancytree.getNode(ui.target);
// Modify menu entries depending on node status // Modify menu entries depending on node status
treeEl.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0); $tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0);
treeEl.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0); $tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0);
// Activate node on right-click // Activate node on right-click
node.setActive(); node.setActive();

View File

@ -211,4 +211,26 @@ if (isElectron()) {
await noteTree.createNote(node, node.data.noteId, 'into', node.data.isProtected); await noteTree.createNote(node, node.data.noteId, 'into', node.data.isProtected);
}); });
} }
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);
});

View File

@ -41,11 +41,11 @@ const link = (function() {
function goToLink(e) { function goToLink(e) {
e.preventDefault(); e.preventDefault();
const linkEl = $(e.target); const $link = $(e.target);
let notePath = linkEl.attr("note-path"); let notePath = $link.attr("note-path");
if (!notePath) { 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) { if (!address) {
return; return;

View File

@ -1,7 +1,7 @@
"use strict"; "use strict";
const messaging = (function() { const messaging = (function() {
const changesToPushCountEl = $("#changes-to-push-count"); const $changesToPushCount = $("#changes-to-push-count");
function logError(message) { function logError(message) {
console.log(now(), message); // needs to be separate from .trace() 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 // we don't detect image changes here since images themselves are immutable and references should be
// updated in note detail as well // updated in note detail as well
changesToPushCountEl.html(message.changesToPushCount); $changesToPushCount.html(message.changesToPushCount);
} }
else if (message.type === 'sync-hash-check-failed') { else if (message.type === 'sync-hash-check-failed') {
showError("Sync check failed!", 60000); showError("Sync check failed!", 60000);

View File

@ -1,16 +1,19 @@
"use strict"; "use strict";
const noteEditor = (function() { const noteEditor = (function() {
const noteTitleEl = $("#note-title"); const $noteTitle = $("#note-title");
const noteDetailEl = $('#note-detail');
const noteDetailCodeEl = $('#note-detail-code'); const $noteDetail = $('#note-detail');
const noteDetailRenderEl = $('#note-detail-render'); const $noteDetailCode = $('#note-detail-code');
const protectButton = $("#protect-button"); const $noteDetailRender = $('#note-detail-render');
const unprotectButton = $("#unprotect-button"); const $noteDetailAttachment = $('#note-detail-attachment');
const noteDetailWrapperEl = $("#note-detail-wrapper");
const noteIdDisplayEl = $("#note-id-display"); const $protectButton = $("#protect-button");
const attributeListEl = $("#attribute-list"); const $unprotectButton = $("#unprotect-button");
const attributeListInnerEl = $("#attribute-list-inner"); const $noteDetailWrapper = $("#note-detail-wrapper");
const $noteIdDisplay = $("#note-id-display");
const $attributeList = $("#attribute-list");
const $attributeListInner = $("#attribute-list-inner");
let editor = null; let editor = null;
let codeEditor = null; let codeEditor = null;
@ -87,7 +90,7 @@ const noteEditor = (function() {
throwError("Unrecognized type: " + note.detail.type); throwError("Unrecognized type: " + note.detail.type);
} }
const title = noteTitleEl.val(); const title = $noteTitle.val();
note.detail.title = title; note.detail.title = title;
@ -105,9 +108,9 @@ const noteEditor = (function() {
function setNoteBackgroundIfProtected(note) { function setNoteBackgroundIfProtected(note) {
const isProtected = !!note.detail.isProtected; const isProtected = !!note.detail.isProtected;
noteDetailWrapperEl.toggleClass("protected", isProtected); $noteDetailWrapper.toggleClass("protected", isProtected);
protectButton.toggle(!isProtected); $protectButton.toggle(!isProtected);
unprotectButton.toggle(isProtected); $unprotectButton.toggle(isProtected);
} }
let isNewNoteCreated = false; let isNewNoteCreated = false;
@ -121,14 +124,10 @@ const noteEditor = (function() {
// temporary workaround for https://github.com/ckeditor/ckeditor5-enter/issues/49 // temporary workaround for https://github.com/ckeditor/ckeditor5-enter/issues/49
editor.setData(content ? content : "<p></p>"); editor.setData(content ? content : "<p></p>");
noteDetailEl.show(); $noteDetail.show();
noteDetailCodeEl.hide();
noteDetailRenderEl.html('').hide();
} }
else if (currentNote.detail.type === 'code') { else if (currentNote.detail.type === 'code') {
noteDetailEl.hide(); $noteDetailCode.show();
noteDetailCodeEl.show();
noteDetailRenderEl.html('').hide();
// this needs to happen after the element is shown, otherwise the editor won't be refresheds // this needs to happen after the element is shown, otherwise the editor won't be refresheds
codeEditor.setValue(content); codeEditor.setValue(content);
@ -148,10 +147,10 @@ const noteEditor = (function() {
if (isNewNoteCreated) { if (isNewNoteCreated) {
isNewNoteCreated = false; isNewNoteCreated = false;
noteTitleEl.focus().select(); $noteTitle.focus().select();
} }
noteIdDisplayEl.html(noteId); $noteIdDisplay.html(noteId);
await protected_session.ensureProtectedSession(currentNote.detail.isProtected, false); 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. // 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(); protected_session.ensureDialogIsClosed();
noteDetailWrapperEl.show(); $noteDetailWrapper.show();
noteChangeDisabled = true; noteChangeDisabled = true;
noteTitleEl.val(currentNote.detail.title); $noteTitle.val(currentNote.detail.title);
noteType.setNoteType(currentNote.detail.type); noteType.setNoteType(currentNote.detail.type);
noteType.setNoteMime(currentNote.detail.mime); noteType.setNoteMime(currentNote.detail.mime);
$noteDetail.hide();
$noteDetailCode.hide();
$noteDetailRender.html('').hide();
$noteDetailAttachment.hide();
if (currentNote.detail.type === 'render') { if (currentNote.detail.type === 'render') {
noteDetailEl.hide(); $noteDetailRender.show();
noteDetailCodeEl.hide();
noteDetailRenderEl.html('').show();
const subTree = await server.get('script/subtree/' + getCurrentNoteId()); const subTree = await server.get('script/subtree/' + getCurrentNoteId());
noteDetailRenderEl.html(subTree); $noteDetailRender.html(subTree);
}
else if (currentNote.detail.type === 'file') {
$noteDetailAttachment.show();
} }
else { else {
setContent(currentNote.detail.content); setContent(currentNote.detail.content);
@ -191,7 +196,7 @@ const noteEditor = (function() {
noteTree.setNoteTreeBackgroundBasedOnProtectedStatus(noteId); noteTree.setNoteTreeBackgroundBasedOnProtectedStatus(noteId);
// after loading new note make sure editor is scrolled to the top // after loading new note make sure editor is scrolled to the top
noteDetailWrapperEl.scrollTop(0); $noteDetailWrapper.scrollTop(0);
loadAttributeList(); loadAttributeList();
} }
@ -201,17 +206,17 @@ const noteEditor = (function() {
const attributes = await server.get('notes/' + noteId + '/attributes'); const attributes = await server.get('notes/' + noteId + '/attributes');
attributeListInnerEl.html(''); $attributeListInner.html('');
if (attributes.length > 0) { if (attributes.length > 0) {
for (const attr of attributes) { for (const attr of attributes) {
attributeListInnerEl.append(formatAttribute(attr) + " "); $attributeListInner.append(formatAttribute(attr) + " ");
} }
attributeListEl.show(); $attributeList.show();
} }
else { else {
attributeListEl.hide(); $attributeList.hide();
} }
} }
@ -227,7 +232,7 @@ const noteEditor = (function() {
const note = getCurrentNote(); const note = getCurrentNote();
if (note.detail.type === 'text') { if (note.detail.type === 'text') {
noteDetailEl.focus(); $noteDetail.focus();
} }
else if (note.detail.type === 'code') { else if (note.detail.type === 'code') {
codeEditor.focus(); codeEditor.focus();
@ -258,10 +263,10 @@ const noteEditor = (function() {
} }
$(document).ready(() => { $(document).ready(() => {
noteTitleEl.on('input', () => { $noteTitle.on('input', () => {
noteChanged(); noteChanged();
const title = noteTitleEl.val(); const title = $noteTitle.val();
noteTree.setNoteTitle(getCurrentNoteId(), title); noteTree.setNoteTitle(getCurrentNoteId(), title);
}); });
@ -295,7 +300,7 @@ const noteEditor = (function() {
codeEditor.on('change', noteChanged); codeEditor.on('change', noteChanged);
// so that tab jumps from note title (which has tabindex 1) // 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); $(document).bind('keydown', "ctrl+return", executeCurrentNote);

View File

@ -1,9 +1,9 @@
"use strict"; "use strict";
const noteTree = (function() { const noteTree = (function() {
const treeEl = $("#tree"); const $tree = $("#tree");
const parentListEl = $("#parent-list"); const $parentList = $("#parent-list");
const parentListListEl = $("#parent-list-inner"); const $parentListList = $("#parent-list-inner");
let startNotePath = null; let startNotePath = null;
let notesTreeMap = {}; 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 // note that if you want to access data like noteId or isProtected, you need to go into "data" property
function getCurrentNode() { function getCurrentNode() {
return treeEl.fancytree("getActiveNode"); return $tree.fancytree("getActiveNode");
} }
function getCurrentNotePath() { function getCurrentNotePath() {
@ -314,11 +314,11 @@ const noteTree = (function() {
} }
if (parents.length <= 1) { if (parents.length <= 1) {
parentListEl.hide(); $parentList.hide();
} }
else { else {
parentListEl.show(); $parentList.show();
parentListListEl.empty(); $parentListList.empty();
for (const parentNoteId of parents) { for (const parentNoteId of parents) {
const parentNotePath = getSomeNotePath(parentNoteId); const parentNotePath = getSomeNotePath(parentNoteId);
@ -335,7 +335,7 @@ const noteTree = (function() {
item = link.createNoteLink(notePath, title); item = link.createNoteLink(notePath, title);
} }
parentListListEl.append($("<li/>").append(item)); $parentListList.append($("<li/>").append(item));
} }
} }
} }
@ -543,7 +543,7 @@ const noteTree = (function() {
} }
}; };
treeEl.fancytree({ $tree.fancytree({
autoScroll: true, autoScroll: true,
keyboard: false, // we takover keyboard handling in the hotkeys plugin keyboard: false, // we takover keyboard handling in the hotkeys plugin
extensions: ["hotkeys", "filter", "dnd", "clones"], extensions: ["hotkeys", "filter", "dnd", "clones"],
@ -624,11 +624,11 @@ const noteTree = (function() {
} }
}); });
treeEl.contextmenu(contextMenu.contextMenuSettings); $tree.contextmenu(contextMenu.contextMenuSettings);
} }
function getTree() { function getTree() {
return treeEl.fancytree('getTree'); return $tree.fancytree('getTree');
} }
async function reload() { async function reload() {
@ -663,7 +663,7 @@ const noteTree = (function() {
function collapseTree(node = null) { function collapseTree(node = null) {
if (!node) { if (!node) {
node = treeEl.fancytree("getRootNode"); node = $tree.fancytree("getRootNode");
} }
node.setExpanded(false); node.setExpanded(false);
@ -744,7 +744,7 @@ const noteTree = (function() {
} }
async function createNewTopLevelNote() { async function createNewTopLevelNote() {
const rootNode = treeEl.fancytree("getRootNode"); const rootNode = $tree.fancytree("getRootNode");
await createNote(rootNode, "root", "into"); await createNote(rootNode, "root", "into");
} }

View File

@ -1,7 +1,7 @@
"use strict"; "use strict";
const noteType = (function() { const noteType = (function() {
const executeScriptButton = $("#execute-script-button"); const $executeScriptButton = $("#execute-script-button");
const noteTypeModel = new NoteTypeModel(); const noteTypeModel = new NoteTypeModel();
function NoteTypeModel() { function NoteTypeModel() {
@ -114,7 +114,7 @@ const noteType = (function() {
}; };
this.updateExecuteScriptButtonVisibility = function() { this.updateExecuteScriptButtonVisibility = function() {
executeScriptButton.toggle(self.mime() === 'application/javascript'); $executeScriptButton.toggle(self.mime() === 'application/javascript');
} }
} }

View File

@ -1,10 +1,10 @@
"use strict"; "use strict";
const protected_session = (function() { const protected_session = (function() {
const dialogEl = $("#protected-session-password-dialog"); const $dialog = $("#protected-session-password-dialog");
const passwordFormEl = $("#protected-session-password-form"); const $passwordForm = $("#protected-session-password-form");
const passwordEl = $("#protected-session-password"); const $password = $("#protected-session-password");
const noteDetailWrapperEl = $("#note-detail-wrapper"); const $noteDetailWrapper = $("#note-detail-wrapper");
let protectedSessionDeferred = null; let protectedSessionDeferred = null;
let lastProtectedSessionOperationDate = null; let lastProtectedSessionOperationDate = null;
@ -25,9 +25,9 @@ const protected_session = (function() {
if (requireProtectedSession && !isProtectedSessionAvailable()) { if (requireProtectedSession && !isProtectedSessionAvailable()) {
protectedSessionDeferred = dfd; protectedSessionDeferred = dfd;
noteDetailWrapperEl.hide(); $noteDetailWrapper.hide();
dialogEl.dialog({ $dialog.dialog({
modal: modal, modal: modal,
width: 400, width: 400,
open: () => { open: () => {
@ -46,8 +46,8 @@ const protected_session = (function() {
} }
async function setupProtectedSession() { async function setupProtectedSession() {
const password = passwordEl.val(); const password = $password.val();
passwordEl.val(""); $password.val("");
const response = await enterProtectedSession(password); const response = await enterProtectedSession(password);
@ -58,15 +58,15 @@ const protected_session = (function() {
protectedSessionId = response.protectedSessionId; protectedSessionId = response.protectedSessionId;
dialogEl.dialog("close"); $dialog.dialog("close");
noteEditor.reload(); noteEditor.reload();
noteTree.reload(); noteTree.reload();
if (protectedSessionDeferred !== null) { if (protectedSessionDeferred !== null) {
ensureDialogIsClosed(dialogEl, passwordEl); ensureDialogIsClosed($dialog, $password);
noteDetailWrapperEl.show(); $noteDetailWrapper.show();
protectedSessionDeferred.resolve(); protectedSessionDeferred.resolve();
@ -77,11 +77,11 @@ const protected_session = (function() {
function ensureDialogIsClosed() { function ensureDialogIsClosed() {
// this may fal if the dialog has not been previously opened // this may fal if the dialog has not been previously opened
try { try {
dialogEl.dialog('close'); $dialog.dialog('close');
} }
catch (e) {} catch (e) {}
passwordEl.val(''); $password.val('');
} }
async function enterProtectedSession(password) { async function enterProtectedSession(password) {
@ -155,7 +155,7 @@ const protected_session = (function() {
noteEditor.reload(); noteEditor.reload();
} }
passwordFormEl.submit(() => { $passwordForm.submit(() => {
setupProtectedSession(); setupProtectedSession();
return false; return false;

View File

@ -1,40 +1,40 @@
"use strict"; "use strict";
const searchTree = (function() { const searchTree = (function() {
const treeEl = $("#tree"); const $tree = $("#tree");
const searchInputEl = $("input[name='search-text']"); const $searchInput = $("input[name='search-text']");
const resetSearchButton = $("button#reset-search-button"); const $resetSearchButton = $("button#reset-search-button");
const searchBoxEl = $("#search-box"); const $searchBox = $("#search-box");
resetSearchButton.click(resetSearch); $resetSearchButton.click(resetSearch);
function toggleSearch() { function toggleSearch() {
if (searchBoxEl.is(":hidden")) { if ($searchBox.is(":hidden")) {
searchBoxEl.show(); $searchBox.show();
searchInputEl.focus(); $searchInput.focus();
} }
else { else {
resetSearch(); resetSearch();
searchBoxEl.hide(); $searchBox.hide();
} }
} }
function resetSearch() { function resetSearch() {
searchInputEl.val(""); $searchInput.val("");
getTree().clearFilter(); getTree().clearFilter();
} }
function getTree() { function getTree() {
return treeEl.fancytree('getTree'); return $tree.fancytree('getTree');
} }
searchInputEl.keyup(async e => { $searchInput.keyup(async e => {
const searchText = searchInputEl.val(); const searchText = $searchInput.val();
if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(searchText) === "") { if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(searchText) === "") {
resetSearchButton.click(); $resetSearchButton.click();
return; return;
} }

View File

@ -1,14 +1,14 @@
"use strict"; "use strict";
const treeUtils = (function() { const treeUtils = (function() {
const treeEl = $("#tree"); const $tree = $("#tree");
function getParentProtectedStatus(node) { function getParentProtectedStatus(node) {
return isTopLevelNode(node) ? 0 : node.getParent().data.isProtected; return isTopLevelNode(node) ? 0 : node.getParent().data.isProtected;
} }
function getNodeByKey(key) { function getNodeByKey(key) {
return treeEl.fancytree('getNodeByKey', key); return $tree.fancytree('getNodeByKey', key);
} }
function getNoteIdFromNotePath(notePath) { function getNoteIdFromNotePath(notePath) {

View File

@ -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;

View File

@ -29,6 +29,7 @@ const imageRoute = require('./api/image');
const attributesRoute = require('./api/attributes'); const attributesRoute = require('./api/attributes');
const scriptRoute = require('./api/script'); const scriptRoute = require('./api/script');
const senderRoute = require('./api/sender'); const senderRoute = require('./api/sender');
const attachmentsRoute = require('./api/attachments');
function register(app) { function register(app) {
app.use('/', indexRoute); app.use('/', indexRoute);
@ -61,6 +62,7 @@ function register(app) {
app.use('/api/images', imageRoute); app.use('/api/images', imageRoute);
app.use('/api/script', scriptRoute); app.use('/api/script', scriptRoute);
app.use('/api/sender', senderRoute); app.use('/api/sender', senderRoute);
app.use('/api/attachments', attachmentsRoute);
} }
module.exports = { module.exports = {

View File

@ -130,6 +130,7 @@
<li><a onclick="noteHistory.showCurrentNoteHistory();"><kbd>Alt+H</kbd> History</a></li> <li><a onclick="noteHistory.showCurrentNoteHistory();"><kbd>Alt+H</kbd> History</a></li>
<li><a onclick="attributesDialog.showDialog();"><kbd>Alt+A</kbd> Attributes</a></li> <li><a onclick="attributesDialog.showDialog();"><kbd>Alt+A</kbd> Attributes</a></li>
<li><a onclick="noteSource.showDialog();"><kbd>Ctrl+U</kbd> HTML source</a></li> <li><a onclick="noteSource.showDialog();"><kbd>Ctrl+U</kbd> HTML source</a></li>
<li><a onclick="uploadAttachment();">Upload attachment</a></li>
</ul> </ul>
</div> </div>
</div> </div>
@ -141,6 +142,13 @@
<div id="note-detail-code"></div> <div id="note-detail-code"></div>
<div id="note-detail-render"></div> <div id="note-detail-render"></div>
<div id="note-detail-attachment">
Attachment!!!
</div>
<input type="file" id="file-upload" style="display: none" />
</div> </div>
<div id="attribute-list"> <div id="attribute-list">