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 @@ + + + + + + +
Name | + + +Type | + + + + + +Description | +
---|---|---|
keyboardShortcut |
+
+
+ + + +string + + + + | + + + + + +e.g. "ctrl+shift+a" | +
handler |
+
+
+ + + +function + + + + | + + + + + ++ |
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