mirror of
https://github.com/zadam/trilium.git
synced 2024-09-22 00:26:01 +08:00
file/attachment upload, wiP
This commit is contained in:
parent
8028b09351
commit
cdde6a4d8e
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -211,4 +211,26 @@ if (isElectron()) {
|
|||
|
||||
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);
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 : "<p></p>");
|
||||
|
||||
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);
|
||||
|
|
|
@ -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($("<li/>").append(item));
|
||||
$parentListList.append($("<li/>").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");
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
36
src/routes/api/attachments.js
Normal file
36
src/routes/api/attachments.js
Normal 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;
|
|
@ -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 = {
|
||||
|
|
|
@ -130,6 +130,7 @@
|
|||
<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="noteSource.showDialog();"><kbd>Ctrl+U</kbd> HTML source</a></li>
|
||||
<li><a onclick="uploadAttachment();">Upload attachment</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -141,6 +142,13 @@
|
|||
<div id="note-detail-code"></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 id="attribute-list">
|
||||
|
|
Loading…
Reference in a new issue