diff --git a/bin/release.sh b/bin/release.sh index 5759da04d..0967443ea 100755 --- a/bin/release.sh +++ b/bin/release.sh @@ -24,9 +24,9 @@ jq '.version = "'$VERSION'"' package.json|sponge package.json git add package.json -echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > services/build.js +echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.js -git add services/build.js +git add src/services/build.js TAG=v$VERSION diff --git a/db/migrations/0087__add_type_mime_to_note_revision.sql b/db/migrations/0087__add_type_mime_to_note_revision.sql new file mode 100644 index 000000000..f4b6629e3 --- /dev/null +++ b/db/migrations/0087__add_type_mime_to_note_revision.sql @@ -0,0 +1,5 @@ +ALTER TABLE note_revisions ADD type TEXT DEFAULT '' NOT NULL; +ALTER TABLE note_revisions ADD mime TEXT DEFAULT '' NOT NULL; + +UPDATE note_revisions SET type = (SELECT type FROM notes WHERE notes.noteId = note_revisions.noteId); +UPDATE note_revisions SET mime = (SELECT mime FROM notes WHERE notes.noteId = note_revisions.noteId); \ No newline at end of file diff --git a/src/entities/note.js b/src/entities/note.js index c11833667..9f599c5f1 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -12,7 +12,8 @@ class Note extends Entity { constructor(row) { super(row); - if (this.isProtected) { + // check if there's noteId, otherwise this is a new entity which wasn't encrypted yet + if (this.isProtected && this.noteId) { protected_session.decryptNote(this); } @@ -21,6 +22,14 @@ class Note extends Entity { } } + setContent(content) { + this.content = content; + + if (this.isJson()) { + this.jsonContent = JSON.parse(this.content); + } + } + isJson() { return this.mime === "application/json"; } diff --git a/src/public/javascripts/dialogs/note_revisions.js b/src/public/javascripts/dialogs/note_revisions.js index 331a0dff3..42543565f 100644 --- a/src/public/javascripts/dialogs/note_revisions.js +++ b/src/public/javascripts/dialogs/note_revisions.js @@ -54,7 +54,13 @@ $list.on('change', () => { const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal); $title.html(revisionItem.title); - $content.html(revisionItem.content); + + if (revisionItem.type === 'text') { + $content.html(revisionItem.content); + } + else if (revisionItem.type === 'code') { + $content.html($("
").text(revisionItem.content)); + } }); $(document).on('click', "a[action='note-revision']", event => { diff --git a/src/public/javascripts/entities/branch.js b/src/public/javascripts/entities/branch.js index e6f2d66a2..2a2268e5d 100644 --- a/src/public/javascripts/entities/branch.js +++ b/src/public/javascripts/entities/branch.js @@ -14,6 +14,10 @@ class Branch { return await this.treeCache.getNote(this.noteId); } + isTopLevel() { + return this.parentNoteId === 'root'; + } + get toString() { return `Branch(branchId=${this.branchId})`; } diff --git a/src/public/javascripts/entities/note_short.js b/src/public/javascripts/entities/note_short.js index 8d10a7c9b..6a831111b 100644 --- a/src/public/javascripts/entities/note_short.js +++ b/src/public/javascripts/entities/note_short.js @@ -44,6 +44,14 @@ class NoteShort { get toString() { return `Note(noteId=${this.noteId}, title=${this.title})`; } + + get dto() { + const dto = Object.assign({}, this); + delete dto.treeCache; + delete dto.hideInAutocomplete; + + return dto; + } } export default NoteShort; \ No newline at end of file diff --git a/src/public/javascripts/services/autocomplete.js b/src/public/javascripts/services/autocomplete.js index 7054c14d7..7243a53f9 100644 --- a/src/public/javascripts/services/autocomplete.js +++ b/src/public/javascripts/services/autocomplete.js @@ -1,5 +1,6 @@ import treeCache from "./tree_cache.js"; import treeUtils from "./tree_utils.js"; +import protectedSessionHolder from './protected_session_holder.js'; async function getAutocompleteItems(parentNoteId, notePath, titlePath) { if (!parentNoteId) { @@ -21,9 +22,6 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) { titlePath = ''; } - // https://github.com/zadam/trilium/issues/46 - // unfortunately not easy to implement because we don't have an easy access to note's isProtected property - const autocompleteItems = []; for (const childNote of childNotes) { @@ -34,10 +32,12 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) { const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId; const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId); - autocompleteItems.push({ - value: childTitlePath + ' (' + childNotePath + ')', - label: childTitlePath - }); + if (!childNote.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) { + autocompleteItems.push({ + value: childTitlePath + ' (' + childNotePath + ')', + label: childTitlePath + }); + } const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath); diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 33d45cf4b..cbf648f74 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -1,4 +1,5 @@ import treeService from './tree.js'; +import treeUtils from './tree_utils.js'; import noteTypeService from './note_type.js'; import protectedSessionService from './protected_session.js'; import protectedSessionHolder from './protected_session_holder.js'; @@ -24,6 +25,7 @@ const $noteDetailWrapper = $("#note-detail-wrapper"); const $noteIdDisplay = $("#note-id-display"); const $labelList = $("#label-list"); const $labelListInner = $("#label-list-inner"); +const $childrenOverview = $("#children-overview"); let currentNote = null; @@ -73,50 +75,42 @@ function noteChanged() { async function reload() { // no saving here - await loadNoteToEditor(getCurrentNoteId()); + await loadNoteDetail(getCurrentNoteId()); } async function switchToNote(noteId) { if (getCurrentNoteId() !== noteId) { await saveNoteIfChanged(); - await loadNoteToEditor(noteId); + await loadNoteDetail(noteId); } } +async function saveNote() { + const note = getCurrentNote(); + + note.title = $noteTitle.val(); + note.content = getComponent(note.type).getContent(); + + treeService.setNoteTitle(note.noteId, note.title); + + await server.put('notes/' + note.noteId, note.dto); + + isNoteChanged = false; + + if (note.isProtected) { + protectedSessionHolder.touchProtectedSession(); + } + + infoService.showMessage("Saved!"); +} + async function saveNoteIfChanged() { if (!isNoteChanged) { return; } - const note = getCurrentNote(); - - updateNoteFromInputs(note); - - await saveNoteToServer(note); - - if (note.isProtected) { - protectedSessionHolder.touchProtectedSession(); - } -} - -function updateNoteFromInputs(note) { - note.title = $noteTitle.val(); - note.content = getComponent(note.type).getContent(); - - treeService.setNoteTitle(note.noteId, note.title); -} - -async function saveNoteToServer(note) { - const dto = Object.assign({}, note); - delete dto.treeCache; - delete dto.hideInAutocomplete; - - await server.put('notes/' + dto.noteId, dto); - - isNoteChanged = false; - - infoService.showMessage("Saved!"); + await saveNote(); } function setNoteBackgroundIfProtected(note) { @@ -145,7 +139,7 @@ async function handleProtectedSession() { protectedSessionService.ensureDialogIsClosed(); } -async function loadNoteToEditor(noteId) { +async function loadNoteDetail(noteId) { currentNote = await loadNote(noteId); if (isNewNoteCreated) { @@ -183,6 +177,26 @@ async function loadNoteToEditor(noteId) { $noteDetailWrapper.scrollTop(0); await loadLabelList(); + + await showChildrenOverview(); +} + +async function showChildrenOverview() { + const note = getCurrentNote(); + + $childrenOverview.empty(); + + const notePath = treeService.getCurrentNotePath(); + + for (const childBranch of await note.getChildBranches()) { + const link = $('', { + href: 'javascript:', + text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId) + }).attr('action', 'note').attr('note-path', notePath + '/' + childBranch.noteId); + + const childEl = $('').html(link); + $childrenOverview.append(childEl); + } } async function loadLabelList() { @@ -245,8 +259,6 @@ setInterval(saveNoteIfChanged, 5000); export default { reload, switchToNote, - updateNoteFromInputs, - saveNoteToServer, setNoteBackgroundIfProtected, loadNote, getCurrentNote, @@ -255,6 +267,7 @@ export default { newNoteCreated, focus, loadLabelList, + saveNote, saveNoteIfChanged, noteChanged }; \ No newline at end of file diff --git a/src/public/javascripts/services/note_detail_code.js b/src/public/javascripts/services/note_detail_code.js index 00b52194d..5a9baf2b3 100644 --- a/src/public/javascripts/services/note_detail_code.js +++ b/src/public/javascripts/services/note_detail_code.js @@ -16,6 +16,10 @@ async function show() { CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; CodeMirror.keyMap.default["Tab"] = "indentMore"; + // these conflict with backward/forward navigation shortcuts + delete CodeMirror.keyMap.default["Alt-Left"]; + delete CodeMirror.keyMap.default["Alt-Right"]; + CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js'; codeEditor = CodeMirror($noteDetailCode[0], { diff --git a/src/public/javascripts/services/note_type.js b/src/public/javascripts/services/note_type.js index 83c5dfe92..34922c2c9 100644 --- a/src/public/javascripts/services/note_type.js +++ b/src/public/javascripts/services/note_type.js @@ -1,5 +1,5 @@ import treeService from './tree.js'; -import noteDetail from './note_detail.js'; +import noteDetailService from './note_detail.js'; import server from './server.js'; import infoService from "./info.js"; @@ -84,13 +84,13 @@ function NoteTypeModel() { }; async function save() { - const note = noteDetail.getCurrentNote(); + const note = noteDetailService.getCurrentNote(); await server.put('notes/' + note.noteId + '/type/' + encodeURIComponent(self.type()) + '/mime/' + encodeURIComponent(self.mime())); - await noteDetail.reload(); + await noteDetailService.reload(); // for the note icon to be updated in the tree await treeService.reload(); diff --git a/src/public/javascripts/services/protected_session.js b/src/public/javascripts/services/protected_session.js index 36306bc2c..399742250 100644 --- a/src/public/javascripts/services/protected_session.js +++ b/src/public/javascripts/services/protected_session.js @@ -1,5 +1,5 @@ import treeService from './tree.js'; -import noteDetail from './note_detail.js'; +import noteDetailService from './note_detail.js'; import utils from './utils.js'; import server from './server.js'; import protectedSessionHolder from './protected_session_holder.js'; @@ -57,7 +57,7 @@ async function setupProtectedSession() { $dialog.dialog("close"); - noteDetail.reload(); + noteDetailService.reload(); treeService.reload(); if (protectedSessionDeferred !== null) { @@ -90,33 +90,27 @@ async function enterProtectedSession(password) { async function protectNoteAndSendToServer() { await ensureProtectedSession(true, true); - const note = noteDetail.getCurrentNote(); - - noteDetail.updateNoteFromInputs(note); - + const note = noteDetailService.getCurrentNote(); note.isProtected = true; - await noteDetail.saveNoteToServer(note); + await noteDetailService.saveNote(note); treeService.setProtected(note.noteId, note.isProtected); - noteDetail.setNoteBackgroundIfProtected(note); + noteDetailService.setNoteBackgroundIfProtected(note); } async function unprotectNoteAndSendToServer() { await ensureProtectedSession(true, true); - const note = noteDetail.getCurrentNote(); - - noteDetail.updateNoteFromInputs(note); - + const note = noteDetailService.getCurrentNote(); note.isProtected = false; - await noteDetail.saveNoteToServer(note); + await noteDetailService.saveNote(note); treeService.setProtected(note.noteId, note.isProtected); - noteDetail.setNoteBackgroundIfProtected(note); + noteDetailService.setNoteBackgroundIfProtected(note); } async function protectBranch(noteId, protect) { @@ -127,7 +121,7 @@ async function protectBranch(noteId, protect) { infoService.showMessage("Request to un/protect sub tree has finished successfully"); treeService.reload(); - noteDetail.reload(); + noteDetailService.reload(); } $passwordForm.submit(() => { diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index 751a8d3fb..9a47262ee 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -293,7 +293,7 @@ function initFancyTree(branch) { keyboard: false, // we takover keyboard handling in the hotkeys plugin extensions: ["hotkeys", "filter", "dnd", "clones"], source: branch, - scrollParent: $("#tree"), + scrollParent: $tree, click: (event, data) => { const targetType = data.targetType; const node = data.node; diff --git a/src/public/libraries/jquery.js b/src/public/libraries/jquery.js index d2d8ca479..9b5206bcc 100644 --- a/src/public/libraries/jquery.js +++ b/src/public/libraries/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v3.2.1 + * jQuery JavaScript Library v3.3.1 * https://jquery.com/ * * Includes Sizzle.js @@ -9,7 +9,7 @@ * Released under the MIT license * https://jquery.org/license * - * Date: 2017-03-20T18:59Z + * Date: 2018-01-20T17:24Z */ ( function( global, factory ) { @@ -71,16 +71,57 @@ var ObjectFunctionString = fnToString.call( Object ); var support = {}; +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML