diff --git a/docs/backend_api/entities_branch.js.html b/docs/backend_api/entities_branch.js.html index 7a4d69a8f..9ae543ca5 100644 --- a/docs/backend_api/entities_branch.js.html +++ b/docs/backend_api/entities_branch.js.html @@ -73,6 +73,10 @@ class Branch extends Entity { this.notePosition = maxNotePos === null ? 0 : maxNotePos + 10; } + if (!this.isExpanded) { + this.isExpanded = false; + } + if (!this.isDeleted) { this.isDeleted = false; } diff --git a/docs/frontend_api/FrontendScriptApi.html b/docs/frontend_api/FrontendScriptApi.html index d4415c6ca..3fd3adc2b 100644 --- a/docs/frontend_api/FrontendScriptApi.html +++ b/docs/frontend_api/FrontendScriptApi.html @@ -1240,6 +1240,162 @@ + + + + + + +

bindGlobalShortcut(keyboardShortcut, handler)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
keyboardShortcut + + +string + + + + e.g. "ctrl+shift+a"
handler + + +function + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -1390,7 +1546,7 @@
Source:
@@ -1523,7 +1679,7 @@
Source:
@@ -1629,7 +1785,7 @@
Source:
@@ -1735,7 +1891,7 @@
Source:
@@ -1894,7 +2050,7 @@
Source:
@@ -2001,7 +2157,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -2156,7 +2312,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -2619,7 +2775,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -2774,7 +2930,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -2883,7 +3039,7 @@ note.
Source:
@@ -3038,7 +3194,7 @@ note.
Source:
@@ -3171,7 +3327,7 @@ note.
Source:
@@ -3277,7 +3433,7 @@ note.
Source:
@@ -3365,7 +3521,7 @@ note.
Source:
@@ -3426,6 +3582,10 @@ note. +
+ Update frontend tree (note) cache from the backend. +
+ @@ -3516,7 +3676,7 @@ note.
Source:
@@ -4148,7 +4308,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4299,7 +4459,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4436,7 +4596,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4573,7 +4733,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
diff --git a/docs/frontend_api/NoteShort.html b/docs/frontend_api/NoteShort.html index 1192154d2..d120e54a5 100644 --- a/docs/frontend_api/NoteShort.html +++ b/docs/frontend_api/NoteShort.html @@ -281,7 +281,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -349,7 +349,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -417,7 +417,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -475,7 +475,65 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
+ + + + + + + + + + + + + + + + +

isDeleted

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -717,7 +775,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -785,7 +843,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1049,7 +1107,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1216,7 +1274,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1390,7 +1448,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1496,7 +1554,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1598,7 +1656,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1700,7 +1758,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1802,7 +1860,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1953,7 +2011,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2120,7 +2178,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2287,7 +2345,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2442,7 +2500,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2548,7 +2606,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2650,7 +2708,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2801,7 +2859,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2968,7 +3026,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3135,7 +3193,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3290,7 +3348,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3460,7 +3518,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3611,7 +3669,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3721,7 +3779,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3895,7 +3953,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4001,7 +4059,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4152,7 +4210,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4307,7 +4365,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4418,7 +4476,7 @@ Cache is note instance scoped.
Source:
@@ -4502,7 +4560,7 @@ Cache is note instance scoped.
Source:
diff --git a/docs/frontend_api/entities_note_short.js.html b/docs/frontend_api/entities_note_short.js.html index debf67855..feac110e3 100644 --- a/docs/frontend_api/entities_note_short.js.html +++ b/docs/frontend_api/entities_note_short.js.html @@ -60,6 +60,8 @@ class NoteShort { /** @param {string} content-type, e.g. "application/json" */ this.mime = row.mime; /** @param {boolean} */ + this.isDeleted = row.isDeleted; + /** @param {boolean} */ this.archived = row.archived; /** @param {string} */ this.cssClass = row.cssClass; @@ -108,7 +110,7 @@ class NoteShort { const branchIdPos = {}; for (const branchId of Object.values(this.childToBranch)) { - branchIdPos[branchId] = this.treeCache.branches[branchId].notePosition; + branchIdPos[branchId] = this.treeCache.getBranch(branchId).notePosition; } this.children.sort((a, b) => branchIdPos[this.childToBranch[a]] < branchIdPos[this.childToBranch[b]] ? -1 : 1); @@ -153,7 +155,8 @@ class NoteShort { /** @returns {Promise<Branch[]>} */ async getChildBranches() { - const branchIds = Object.values(this.childToBranch); + // don't use Object.values() to guarantee order + const branchIds = this.children.map(childNoteId => this.childToBranch[childNoteId]); return this.treeCache.getBranches(branchIds); } diff --git a/docs/frontend_api/services_frontend_script_api.js.html b/docs/frontend_api/services_frontend_script_api.js.html index 8f42be765..60cc942ba 100644 --- a/docs/frontend_api/services_frontend_script_api.js.html +++ b/docs/frontend_api/services_frontend_script_api.js.html @@ -240,6 +240,8 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte this.getNotes = async (noteIds, silentNotFoundError = false) => await treeCache.getNotes(noteIds, silentNotFoundError); /** + * Update frontend tree (note) cache from the backend. + * * @param {string[]} noteIds * @method */ @@ -385,6 +387,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte * @return {Promise} */ this.setHoistedNoteId = hoistedNoteService.setHoistedNoteId; + + /** + * @method + * @param {string} keyboardShortcut - e.g. "ctrl+shift+a" + * @param {function} handler + */ + this.bindGlobalShortcut = utils.bindGlobalShortcut; } export default FrontendScriptApi; diff --git a/libraries/springy.js b/libraries/springy.js index 65ad9fecf..5b0bca1a3 100644 --- a/libraries/springy.js +++ b/libraries/springy.js @@ -191,7 +191,7 @@ this.addNodes.apply(this, json['nodes']); this.addEdges.apply(this, json['edges']); } - } + }; // find the edges from node1 to node2 @@ -479,51 +479,35 @@ return energy; }; - var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; // stolen from coffeescript, thanks jashkenas! ;-) - - Springy.requestAnimationFrame = __bind(this.requestAnimationFrame || - this.webkitRequestAnimationFrame || - this.mozRequestAnimationFrame || - this.oRequestAnimationFrame || - this.msRequestAnimationFrame || - (function(callback, element) { - this.setTimeout(callback, 10); - }), this); - - /** * Start simulation if it's not running already. * In case it's running then the call is ignored, and none of the callbacks passed is ever executed. */ - Layout.ForceDirected.prototype.start = function(render, onRenderStop, onRenderStart) { + Layout.ForceDirected.prototype.start = function(onRenderStop) { var t = this; if (this._started) return; this._started = true; this._stop = false; - if (onRenderStart !== undefined) { onRenderStart(); } - - Springy.requestAnimationFrame(function step() { + function step() { t.tick(0.03); - if (!t._stop && render !== undefined) { - render(); - } - // stop simulation when energy of the system goes below a threshold if (t._stop || t.totalEnergy() < t.minEnergyThreshold) { t._started = false; - if (onRenderStop !== undefined) { onRenderStop(); } + onRenderStop(); } else { - Springy.requestAnimationFrame(step); + setImmediate(step); } - }); + } + + step(); }; Layout.ForceDirected.prototype.stop = function() { this._stop = true; - } + }; Layout.ForceDirected.prototype.tick = function(timestep) { this.applyCoulombsLaw(); @@ -644,85 +628,35 @@ /** * Renderer handles the layout rendering loop - * @param onRenderStop optional callback function that gets executed whenever rendering stops. - * @param onRenderStart optional callback function that gets executed whenever rendering starts. - * @param onRenderFrame optional callback function that gets executed after each frame is rendered. */ - var Renderer = Springy.Renderer = function(layout, clear, drawEdge, drawNode, onRenderStop, onRenderStart, onRenderFrame) { + var Renderer = Springy.Renderer = function(layout, onRenderStop) { this.layout = layout; - this.clear = clear; - this.drawEdge = drawEdge; - this.drawNode = drawNode; this.onRenderStop = onRenderStop; - this.onRenderStart = onRenderStart; - this.onRenderFrame = onRenderFrame; this.layout.graph.addGraphListener(this); - } + }; - Renderer.prototype.graphChanged = function(e) { + Renderer.prototype.graphChanged = function() { this.start(); }; /** * Starts the simulation of the layout in use. - * - * Note that in case the algorithm is still or already running then the layout that's in use - * might silently ignore the call, and your optional done callback is never executed. - * At least the built-in ForceDirected layout behaves in this way. - * - * @param done An optional callback function that gets executed when the springy algorithm stops, - * either because it ended or because stop() was called. */ - Renderer.prototype.start = function(done) { - var t = this; - this.layout.start(function render() { - t.clear(); + Renderer.prototype.start = function(maxTime) { + if (maxTime) { + setTimeout(() => this.stop(), maxTime); + } - t.layout.eachEdge(function(edge, spring) { - t.drawEdge(edge, spring.point1.p, spring.point2.p); - }); - - t.layout.eachNode(function(node, point) { - t.drawNode(node, point.p); - }); - - if (t.onRenderFrame !== undefined) { t.onRenderFrame(); } - }, this.onRenderStop, this.onRenderStart); + return new Promise((res, rej) => { + this.layout.start(res); + }); }; Renderer.prototype.stop = function() { this.layout.stop(); }; - // Array.forEach implementation for IE support.. - //https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach - if ( !Array.prototype.forEach ) { - Array.prototype.forEach = function( callback, thisArg ) { - var T, k; - if ( this == null ) { - throw new TypeError( " this is null or not defined" ); - } - var O = Object(this); - var len = O.length >>> 0; // Hack to convert O.length to a UInt32 - if ( {}.toString.call(callback) != "[object Function]" ) { - throw new TypeError( callback + " is not a function" ); - } - if ( thisArg ) { - T = thisArg; - } - k = 0; - while( k < len ) { - var kValue; - if ( k in O ) { - kValue = O[ k ]; - callback.call( T, kValue, k, O ); - } - k++; - } - }; - } - var isEmpty = function(obj) { for (var k in obj) { if (obj.hasOwnProperty(k)) { diff --git a/package.json b/package.json index cebd72f1a..57caefc2f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.36.2", + "version": "0.36.3", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { diff --git a/src/public/javascripts/dialogs/link_map.js b/src/public/javascripts/dialogs/link_map.js index c62001fae..4a3937a53 100644 --- a/src/public/javascripts/dialogs/link_map.js +++ b/src/public/javascripts/dialogs/link_map.js @@ -23,19 +23,18 @@ export async function showDialog() { // set default settings $maxNotesInput.val(20); - const note = noteDetailService.getActiveTabNote(); - - if (!note) { - return; - } - $linkMapContainer.css("height", $("body").height() - 150); - linkMapService = new LinkMapService(note, $linkMapContainer, getOptions()); - - linkMapService.render(); + $linkMapContainer.empty(); $dialog.modal(); } +$dialog.on('shown.bs.modal', () => { + const note = noteDetailService.getActiveTabNote(); + + linkMapService = new LinkMapService(note, $linkMapContainer, getOptions()); + linkMapService.render(); +}); + $maxNotesInput.on("input", () => linkMapService.loadNotesAndRelations(getOptions())); diff --git a/src/public/javascripts/services/branches.js b/src/public/javascripts/services/branches.js index 519a04e24..adad473bd 100644 --- a/src/public/javascripts/services/branches.js +++ b/src/public/javascripts/services/branches.js @@ -26,9 +26,7 @@ async function moveBeforeNode(nodesToMove, beforeNode) { await changeNode( node => node.moveTo(beforeNode, 'before'), - nodeToMove, - beforeNode.data.noteId, - null); + nodeToMove); } } @@ -52,9 +50,7 @@ async function moveAfterNode(nodesToMove, afterNode) { await changeNode( node => node.moveTo(afterNode, 'after'), - nodeToMove, - null, - afterNode.data.noteId); + nodeToMove); } } @@ -62,6 +58,11 @@ async function moveToNode(nodesToMove, toNode) { nodesToMove = await filterRootNote(nodesToMove); for (const nodeToMove of nodesToMove) { + if (nodeToMove.data.noteId === await hoistedNoteService.getHoistedNoteId() + || nodeToMove.getParent().data.noteType === 'search') { + continue; + } + const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-to/' + toNode.data.noteId); if (!resp.success) { @@ -156,7 +157,9 @@ async function deleteNodes(nodes) { } async function moveNodeUpInHierarchy(node) { - if (await hoistedNoteService.isRootNode(node) || await hoistedNoteService.isTopLevelNode(node)) { + if (await hoistedNoteService.isRootNode(node) + || await hoistedNoteService.isTopLevelNode(node) + || node.getParent().data.noteType === 'search') { return; } @@ -177,7 +180,7 @@ async function moveNodeUpInHierarchy(node) { node); } -async function changeNode(func, node, beforeNoteId = null, afterNoteId = null) { +async function changeNode(func, node) { utils.assertArguments(func, node); const childNoteId = node.data.noteId; diff --git a/src/public/javascripts/services/clipboard.js b/src/public/javascripts/services/clipboard.js index a13c335ed..ec198bf1c 100644 --- a/src/public/javascripts/services/clipboard.js +++ b/src/public/javascripts/services/clipboard.js @@ -2,6 +2,7 @@ import treeUtils from "./tree_utils.js"; import treeChangesService from "./branches.js"; import cloningService from "./cloning.js"; import toastService from "./toast.js"; +import hoistedNoteService from "./hoisted_note.js"; let clipboardIds = []; let clipboardMode = null; @@ -66,10 +67,16 @@ function copy(nodes) { } function cut(nodes) { - clipboardIds = nodes.map(node => node.key); - clipboardMode = 'cut'; + clipboardIds = nodes + .filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise()) + .filter(node => node.getParent().data.noteType !== 'search') + .map(node => node.data.noteId); - toastService.showMessage("Note(s) have been cut into clipboard."); + if (clipboardIds.length > 0) { + clipboardMode = 'cut'; + + toastService.showMessage("Note(s) have been cut into clipboard."); + } } function isEmpty() { diff --git a/src/public/javascripts/services/drag_and_drop.js b/src/public/javascripts/services/drag_and_drop.js index 2fa1f9087..0b2025e8f 100644 --- a/src/public/javascripts/services/drag_and_drop.js +++ b/src/public/javascripts/services/drag_and_drop.js @@ -1,11 +1,13 @@ import treeService from './tree.js'; import treeChangesService from './branches.js'; +import hoistedNoteService from './hoisted_note.js'; const dragAndDropSetup = { autoExpandMS: 600, dragStart: (node, data) => { // don't allow dragging root node - if (node.data.noteId === 'root') { + if (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise() + || node.getParent().data.noteType === 'search') { return false; } @@ -25,6 +27,17 @@ const dragAndDropSetup = { dragEnter: (node, data) => true, // allow drop on any node dragOver: (node, data) => true, dragDrop: async (node, data) => { + if ((data.hitMode === 'over' && node.data.noteType === 'search') || + (['after', 'before'].includes(data.hitMode) + && (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise() || node.getParent().data.noteType === 'search'))) { + + const infoDialog = await import('../dialogs/info.js'); + + await infoDialog.info("Dropping notes into this location is not allowed."); + + return; + } + const dataTransfer = data.dataTransfer; if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) { diff --git a/src/public/javascripts/services/frontend_script_api.js b/src/public/javascripts/services/frontend_script_api.js index d92f8d254..387ec993d 100644 --- a/src/public/javascripts/services/frontend_script_api.js +++ b/src/public/javascripts/services/frontend_script_api.js @@ -359,6 +359,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte * @return {Promise} */ this.setHoistedNoteId = hoistedNoteService.setHoistedNoteId; + + /** + * @method + * @param {string} keyboardShortcut - e.g. "ctrl+shift+a" + * @param {function} handler + */ + this.bindGlobalShortcut = utils.bindGlobalShortcut; } export default FrontendScriptApi; \ No newline at end of file diff --git a/src/public/javascripts/services/hoisted_note.js b/src/public/javascripts/services/hoisted_note.js index e7741baf4..ca5b218b1 100644 --- a/src/public/javascripts/services/hoisted_note.js +++ b/src/public/javascripts/services/hoisted_note.js @@ -3,12 +3,16 @@ import server from "./server.js"; import tree from "./tree.js"; import noteDetailService from "./note_detail.js"; -let hoistedNoteId; +let hoistedNoteId = 'root'; optionsService.waitForOptions().then(options => { hoistedNoteId = options.get('hoistedNoteId'); }); +function getHoistedNoteNoPromise() { + return hoistedNoteId; +} + async function getHoistedNoteId() { await optionsService.waitForOptions(); @@ -49,6 +53,7 @@ async function isRootNode(node) { export default { getHoistedNoteId, + getHoistedNoteNoPromise, setHoistedNoteId, unhoist, isTopLevelNode, diff --git a/src/public/javascripts/services/link_map.js b/src/public/javascripts/services/link_map.js index f5041b90f..5ecfcb58b 100644 --- a/src/public/javascripts/services/link_map.js +++ b/src/public/javascripts/services/link_map.js @@ -119,60 +119,54 @@ export default class LinkMap { stop: params => {} }); - return $noteBox; }; - this.renderer = new Springy.Renderer( - layout, - () => {}, - (edge, p1, p2) => { - const connectionId = this.linkMapContainerId + '-' + edge.source.id + '-' + edge.target.id; + this.renderer = new Springy.Renderer(layout); + await this.renderer.start(500); - if ($("#" + connectionId).length > 0) { - return; - } + layout.eachNode((node, point) => { + const $noteBox = getNoteBox(node.id); + const middleW = this.$linkMapContainer.width() / 2; + const middleH = this.$linkMapContainer.height() / 2; - getNoteBox(edge.source.id); - getNoteBox(edge.target.id); + $noteBox + .css("left", (middleW + point.p.x * 100) + "px") + .css("top", (middleH + point.p.y * 100) + "px"); - const connection = this.jsPlumbInstance.connect({ - source: this.noteIdToId(edge.source.id), - target: this.noteIdToId(edge.target.id), - type: 'link' - }); - - if (connection) { - $(connection.canvas) - .prop("id", connectionId) - .addClass('link-' + edge.source.id) - .addClass('link-' + edge.target.id); - } - else { - console.log(`connection not created for`, edge); - } - }, - (node, p) => { - const $noteBox = getNoteBox(node.id); - const middleW = this.$linkMapContainer.width() / 2; - const middleH = this.$linkMapContainer.height() / 2; - - $noteBox - .css("left", (middleW + p.x * 100) + "px") - .css("top", (middleH + p.y * 100) + "px"); - - if ($noteBox.hasClass("link-map-active-note")) { - this.moveToCenterOfElement($noteBox[0]); - } - }, - () => {}, - () => {}, - () => { - this.jsPlumbInstance.repaintEverything(); + if ($noteBox.hasClass("link-map-active-note")) { + this.moveToCenterOfElement($noteBox[0]); } - ); + }); - this.renderer.start(); + layout.eachEdge(edge => { + const connectionId = this.linkMapContainerId + '-' + edge.source.id + '-' + edge.target.id; + + if ($("#" + connectionId).length > 0) { + return; + } + + getNoteBox(edge.source.id); + getNoteBox(edge.target.id); + + const connection = this.jsPlumbInstance.connect({ + source: this.noteIdToId(edge.source.id), + target: this.noteIdToId(edge.target.id), + type: 'link' + }); + + if (connection) { + $(connection.canvas) + .prop("id", connectionId) + .addClass('link-' + edge.source.id) + .addClass('link-' + edge.target.id); + } + else { + console.log(`connection not created for`, edge); + } + }); + + this.jsPlumbInstance.repaintEverything(); } moveToCenterOfElement(element) { diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index 0f278b841..62bca258a 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -676,6 +676,7 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) { refKey: branchEntity.noteId, branchId: branchEntity.branchId, isProtected: extraOptions.isProtected, + type: noteEntity.type, extraClasses: await treeBuilder.getExtraClasses(noteEntity), icon: await treeBuilder.getIcon(noteEntity), folder: extraOptions.type === 'search', diff --git a/src/public/javascripts/services/tree_builder.js b/src/public/javascripts/services/tree_builder.js index edc235a6f..74f12b851 100644 --- a/src/public/javascripts/services/tree_builder.js +++ b/src/public/javascripts/services/tree_builder.js @@ -1,6 +1,4 @@ import utils from "./utils.js"; -import Branch from "../entities/branch.js"; -import server from "./server.js"; import treeCache from "./tree_cache.js"; import ws from "./ws.js"; import hoistedNoteService from "./hoisted_note.js"; @@ -72,6 +70,7 @@ async function prepareNode(branch) { parentNoteId: branch.parentNoteId, branchId: branch.branchId, isProtected: note.isProtected, + noteType: note.type, title: utils.escapeHtml(title), extraClasses: await getExtraClasses(note), icon: await getIcon(note), @@ -110,24 +109,8 @@ async function prepareRealBranch(parentNote) { } async function prepareSearchBranch(note) { - const results = await server.get('search-note/' + note.noteId); + await treeCache.reloadNotes([note.noteId]); - // force to load all the notes at once instead of one by one - await treeCache.getNotes(results.map(res => res.noteId)); - - const {notes, branches} = await server.post('tree/load', { noteIds: [note.noteId] }); - - results.forEach((result, index) => branches.push({ - branchId: "virt" + utils.randomString(10), - noteId: result.noteId, - parentNoteId: note.noteId, - prefix: treeCache.getBranch(result.branchId).prefix, - notePosition: (index + 1) * 10 - })); - - treeCache.addResp(notes, branches); - - // note in cache changed const newNote = await treeCache.getNote(note.noteId); return await prepareRealBranch(newNote); diff --git a/src/public/javascripts/services/tree_cache.js b/src/public/javascripts/services/tree_cache.js index 256ad4784..02bfcbfe4 100644 --- a/src/public/javascripts/services/tree_cache.js +++ b/src/public/javascripts/services/tree_cache.js @@ -102,6 +102,29 @@ class TreeCache { const resp = await server.post('tree/load', { noteIds }); this.addResp(resp.notes, resp.branches); + + for (const note of resp.notes) { + if (note.type === 'search') { + const searchResults = await server.get('search-note/' + note.noteId); + + // force to load all the notes at once instead of one by one + await treeCache.getNotes(searchResults.map(res => res.noteId)); + + const branches = resp.branches.filter(b => b.noteId === note.noteId || b.parentNoteId === note.noteId); + + searchResults.forEach((result, index) => branches.push({ + // branchId should be repeatable since sometimes we reload some notes without rerendering the tree + branchId: "virt" + result.noteId + '-' + note.noteId, + noteId: result.noteId, + parentNoteId: note.noteId, + prefix: treeCache.getBranch(result.branchId).prefix, + notePosition: (index + 1) * 10 + })); + + // update this note with standard (parent) branches + virtual (children) branches + treeCache.addResp([note], branches); + } + } } /** @return {Promise} */ @@ -109,9 +132,7 @@ class TreeCache { const missingNoteIds = noteIds.filter(noteId => this.notes[noteId] === undefined); if (missingNoteIds.length > 0) { - const resp = await server.post('tree/load', { noteIds: missingNoteIds }); - - this.addResp(resp.notes, resp.branches); + await this.reloadNotes(missingNoteIds); } return noteIds.map(noteId => { diff --git a/src/public/javascripts/services/tree_context_menu.js b/src/public/javascripts/services/tree_context_menu.js index 61075d3bc..dda3f31d4 100644 --- a/src/public/javascripts/services/tree_context_menu.js +++ b/src/public/javascripts/services/tree_context_menu.js @@ -26,8 +26,8 @@ class TreeContextMenu { } async getContextMenuItems() { - const branch = treeCache.getBranch(this.node.data.branchId); const note = await treeCache.getNote(this.node.data.noteId); + const branch = treeCache.getBranch(this.node.data.branchId); const parentNote = await treeCache.getNote(branch.parentNoteId); const isNotRoot = note.noteId !== 'root'; const isHoisted = note.noteId === await hoistedNoteService.getHoistedNoteId(); @@ -39,8 +39,9 @@ class TreeContextMenu { const noSelectedNotes = selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node); - const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNote.type !== 'search'; - const insertChildNoteEnabled = note.type !== 'search'; + const notSearch = note.type !== 'search'; + const parentNotSearch = parentNote.type !== 'search'; + const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; return [ { title: "Open in new tab", cmd: "openInTab", uiIcon: "empty", enabled: noSelectedNotes }, @@ -48,15 +49,15 @@ class TreeContextMenu { items: insertNoteAfterEnabled ? this.getNoteTypeItems("insertNoteAfter") : null, enabled: insertNoteAfterEnabled && noSelectedNotes }, { title: "Insert child note Ctrl+P", cmd: "insertChildNote", uiIcon: "plus", - items: insertChildNoteEnabled ? this.getNoteTypeItems("insertChildNote") : null, - enabled: insertChildNoteEnabled && noSelectedNotes }, + items: notSearch ? this.getNoteTypeItems("insertChildNote") : null, + enabled: notSearch && noSelectedNotes }, { title: "Delete Delete", cmd: "delete", uiIcon: "trash", - enabled: isNotRoot && !isHoisted && parentNote.type !== 'search' }, + enabled: isNotRoot && !isHoisted && parentNotSearch }, { title: "----" }, - isHoisted ? null : { title: "Hoist note Ctrl-H", cmd: "hoist", uiIcon: "empty", enabled: noSelectedNotes }, + isHoisted ? null : { title: "Hoist note Ctrl-H", cmd: "hoist", uiIcon: "empty", enabled: noSelectedNotes && notSearch }, !isHoisted || !isNotRoot ? null : { title: "Unhoist note Ctrl-H", cmd: "unhoist", uiIcon: "arrow-up" }, { title: "Edit branch prefix F2", cmd: "editBranchPrefix", uiIcon: "empty", - enabled: isNotRoot && parentNote.type !== 'search' && noSelectedNotes}, + enabled: isNotRoot && parentNotSearch && noSelectedNotes}, { title: "----" }, { title: "Protect subtree", cmd: "protectSubtree", uiIcon: "shield-check", enabled: noSelectedNotes }, { title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "shield-close", enabled: noSelectedNotes }, @@ -64,22 +65,22 @@ class TreeContextMenu { { title: "Copy / clone Ctrl+C", cmd: "copy", uiIcon: "files", enabled: isNotRoot }, { title: "Cut Ctrl+X", cmd: "cut", uiIcon: "scissors", - enabled: isNotRoot }, + enabled: isNotRoot && !isHoisted && parentNotSearch }, { title: "Paste into Ctrl+V", cmd: "pasteInto", uiIcon: "clipboard", - enabled: !clipboard.isEmpty() && note.type !== 'search' && noSelectedNotes }, + enabled: !clipboard.isEmpty() && notSearch && noSelectedNotes }, { title: "Paste after", cmd: "pasteAfter", uiIcon: "clipboard", - enabled: !clipboard.isEmpty() && isNotRoot && parentNote.type !== 'search' && noSelectedNotes }, + enabled: !clipboard.isEmpty() && isNotRoot && parentNotSearch && noSelectedNotes }, { title: "Duplicate note here", cmd: "duplicateNote", uiIcon: "empty", - enabled: noSelectedNotes && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) }, + enabled: noSelectedNotes && parentNotSearch && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) }, { title: "----" }, { title: "Export", cmd: "export", uiIcon: "empty", - enabled: note.type !== 'search' && noSelectedNotes }, + enabled: notSearch && noSelectedNotes }, { title: "Import into note", cmd: "importIntoNote", uiIcon: "empty", - enabled: note.type !== 'search' && noSelectedNotes }, + enabled: notSearch && noSelectedNotes }, { title: "----" }, { title: "Collapse subtree Alt+-", cmd: "collapseSubtree", uiIcon: "align-justify", enabled: noSelectedNotes }, { title: "Force note sync", cmd: "forceNoteSync", uiIcon: "refresh", enabled: noSelectedNotes }, - { title: "Sort alphabetically Alt+S", cmd: "sortAlphabetically", uiIcon: "empty", enabled: noSelectedNotes } + { title: "Sort alphabetically Alt+S", cmd: "sortAlphabetically", uiIcon: "empty", enabled: noSelectedNotes && notSearch } ].filter(row => row !== null); } diff --git a/src/public/javascripts/services/tree_keybindings.js b/src/public/javascripts/services/tree_keybindings.js index 1ad6af2e2..eb5d4b4f7 100644 --- a/src/public/javascripts/services/tree_keybindings.js +++ b/src/public/javascripts/services/tree_keybindings.js @@ -3,6 +3,7 @@ import treeChangesService from "./branches.js"; import treeService from "./tree.js"; import hoistedNoteService from "./hoisted_note.js"; import clipboard from "./clipboard.js"; +import treeCache from "./tree_cache.js"; const keyBindings = { "del": node => { @@ -130,12 +131,16 @@ const keyBindings = { } }, "ctrl+h": node => { - hoistedNoteService.getHoistedNoteId().then(hoistedNoteId => { + hoistedNoteService.getHoistedNoteId().then(async hoistedNoteId => { if (node.data.noteId === hoistedNoteId) { hoistedNoteService.unhoist(); } else { - hoistedNoteService.setHoistedNoteId(node.data.noteId); + const note = await treeCache.getNote(node.data.noteId); + + if (note.type !== 'search') { + hoistedNoteService.setHoistedNoteId(node.data.noteId); + } } }); diff --git a/src/public/javascripts/widgets/link_map.js b/src/public/javascripts/widgets/link_map.js index 449f9c7e9..89d764042 100644 --- a/src/public/javascripts/widgets/link_map.js +++ b/src/public/javascripts/widgets/link_map.js @@ -29,6 +29,7 @@ class LinkMapWidget extends StandardWidget { } async doRenderBody() { + this.$body.css('opacity', 0); this.$body.html(TPL); const $linkMapContainer = this.$body.find('.link-map-container'); @@ -43,6 +44,8 @@ class LinkMapWidget extends StandardWidget { }); await this.linkMapService.render(); + + this.$body.animate({opacity: 1}, 300); } cleanup() { diff --git a/src/routes/api/options.js b/src/routes/api/options.js index db1c4becd..7ee49f476 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -66,7 +66,9 @@ async function update(name, value) { return false; } - log.info(`Updating option ${name} to ${value}`); + if (name !== 'openTabs') { + log.info(`Updating option ${name} to ${value}`); + } await optionService.setOption(name, value); diff --git a/src/services/build.js b/src/services/build.js index 4c98aae97..f4e42aeb4 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -1 +1 @@ -module.exports = { buildDate:"2019-10-31T22:04:44+01:00", buildRevision: "f1a7fce277d963977b73e3d183d700021781e913" }; +module.exports = { buildDate:"2019-11-05T21:49:16+01:00", buildRevision: "72fda89360d924aedf7a26216fd387346254dfd3" };