mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-27 16:26:31 +01:00 
			
		
		
		
	Compare commits
	
		
			25 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 361d8a4216 | ||
|  | ae6e222c50 | ||
|  | 37995f1ce5 | ||
|  | ad7fa5e096 | ||
|  | 3585982758 | ||
|  | c776f298f2 | ||
|  | f07c427da1 | ||
|  | e560072f8b | ||
|  | 3f976a3821 | ||
|  | 274bb32696 | ||
|  | 99b163a042 | ||
|  | fdcc833f6d | ||
|  | a8e45019e4 | ||
|  | 7f7028873c | ||
|  | 2d2d76a715 | ||
|  | 69cbfaae17 | ||
|  | aebce8f12b | ||
|  | 045ca1f0bf | ||
|  | bf2db6eac7 | ||
|  | cf84114f91 | ||
|  | 6426157bb3 | ||
|  | 332fc16852 | ||
|  | da2cd57428 | ||
|  | de9bab1181 | ||
|  | 136375cf11 | 
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.1.1", | ||||
|   "version": "0.2.0", | ||||
|   "scripts": { | ||||
|     "start": "node ./bin/www", | ||||
|     "test-electron": "xo", | ||||
|   | ||||
| @@ -3,54 +3,72 @@ | ||||
| const contextMenu = (function() { | ||||
|     const treeEl = $("#tree"); | ||||
|  | ||||
|     let clipboardId = null; | ||||
|     let clipboardIds = []; | ||||
|     let clipboardMode = null; | ||||
|  | ||||
|     function pasteAfter(node) { | ||||
|         if (clipboardMode === 'cut') { | ||||
|             const subjectNode = treeUtils.getNodeByKey(clipboardId); | ||||
|             for (const nodeKey of clipboardIds) { | ||||
|                 const subjectNode = treeUtils.getNodeByKey(nodeKey); | ||||
|  | ||||
|             treeChanges.moveAfterNode(subjectNode, node); | ||||
|                 treeChanges.moveAfterNode([subjectNode], node); | ||||
|             } | ||||
|  | ||||
|             clipboardIds = []; | ||||
|             clipboardMode = null; | ||||
|         } | ||||
|         else if (clipboardMode === 'copy') { | ||||
|             treeChanges.cloneNoteAfter(clipboardId, node.data.note_tree_id); | ||||
|             for (const noteId of clipboardIds) { | ||||
|                 treeChanges.cloneNoteAfter(noteId, node.data.note_tree_id); | ||||
|             } | ||||
|  | ||||
|             // copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places | ||||
|         } | ||||
|         else if (clipboardId === null) { | ||||
|         else if (clipboardIds.length === 0) { | ||||
|             // just do nothing | ||||
|         } | ||||
|         else { | ||||
|             throwError("Unrecognized clipboard mode=" + clipboardMode); | ||||
|         } | ||||
|  | ||||
|         clipboardId = null; | ||||
|         clipboardMode = null; | ||||
|     } | ||||
|  | ||||
|     function pasteInto(node) { | ||||
|         if (clipboardMode === 'cut') { | ||||
|             const subjectNode = treeUtils.getNodeByKey(clipboardId); | ||||
|             for (const nodeKey of clipboardIds) { | ||||
|                 const subjectNode = treeUtils.getNodeByKey(nodeKey); | ||||
|  | ||||
|             treeChanges.moveToNode(subjectNode, node); | ||||
|                 treeChanges.moveToNode([subjectNode], node); | ||||
|             } | ||||
|  | ||||
|             clipboardIds = []; | ||||
|             clipboardMode = null; | ||||
|         } | ||||
|         else if (clipboardMode === 'copy') { | ||||
|             treeChanges.cloneNoteTo(clipboardId, node.data.note_id); | ||||
|             for (const noteId of clipboardIds) { | ||||
|                 treeChanges.cloneNoteTo(noteId, node.data.note_id); | ||||
|             } | ||||
|             // copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places | ||||
|         } | ||||
|         else if (clipboardIds.length === 0) { | ||||
|             // just do nothing | ||||
|         } | ||||
|         else { | ||||
|             throwError("Unrecognized clipboard mode=" + mode); | ||||
|         } | ||||
|  | ||||
|         clipboardId = null; | ||||
|         clipboardMode = null; | ||||
|     } | ||||
|  | ||||
|     function copy(node) { | ||||
|         clipboardId = node.data.note_id; | ||||
|     function copy(nodes) { | ||||
|         clipboardIds = nodes.map(node => node.data.note_id); | ||||
|         clipboardMode = 'copy'; | ||||
|  | ||||
|         showMessage("Note(s) have been copied into clipboard."); | ||||
|     } | ||||
|  | ||||
|     function cut(node) { | ||||
|         clipboardId = node.key; | ||||
|     function cut(nodes) { | ||||
|         clipboardIds = nodes.map(node => node.key); | ||||
|         clipboardMode = 'cut'; | ||||
|  | ||||
|         showMessage("Note(s) have been cut into clipboard."); | ||||
|     } | ||||
|  | ||||
|     const contextMenuSettings = { | ||||
| @@ -71,13 +89,14 @@ const contextMenu = (function() { | ||||
|             {title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}, | ||||
|             {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, | ||||
|             {title: "----"}, | ||||
|             {title: "Collapse sub-tree <kbd>Alt+-</kbd>", cmd: "collapse-sub-tree", uiIcon: "ui-icon-minus"} | ||||
|             {title: "Collapse sub-tree <kbd>Alt+-</kbd>", cmd: "collapse-sub-tree", uiIcon: "ui-icon-minus"}, | ||||
|             {title: "Force note sync", cmd: "force-note-sync", uiIcon: "ui-icon-refresh"} | ||||
|         ], | ||||
|         beforeOpen: (event, ui) => { | ||||
|             const node = $.ui.fancytree.getNode(ui.target); | ||||
|             // Modify menu entries depending on node status | ||||
|             treeEl.contextmenu("enableEntry", "pasteAfter", clipboardId !== null); | ||||
|             treeEl.contextmenu("enableEntry", "pasteInto", clipboardId !== null); | ||||
|             treeEl.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0); | ||||
|             treeEl.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0); | ||||
|  | ||||
|             // Activate node on right-click | ||||
|             node.setActive(); | ||||
| @@ -108,10 +127,10 @@ const contextMenu = (function() { | ||||
|                 protected_session.protectSubTree(node.data.note_id, false); | ||||
|             } | ||||
|             else if (ui.cmd === "copy") { | ||||
|                 copy(node); | ||||
|                 copy(noteTree.getSelectedNodes()); | ||||
|             } | ||||
|             else if (ui.cmd === "cut") { | ||||
|                 cut(node); | ||||
|                 cut(noteTree.getSelectedNodes()); | ||||
|             } | ||||
|             else if (ui.cmd === "pasteAfter") { | ||||
|                 pasteAfter(node); | ||||
| @@ -125,6 +144,9 @@ const contextMenu = (function() { | ||||
|             else if (ui.cmd === "collapse-sub-tree") { | ||||
|                 noteTree.collapseTree(node); | ||||
|             } | ||||
|             else if (ui.cmd === "force-note-sync") { | ||||
|                 forceNoteSync(node.data.note_id); | ||||
|             } | ||||
|             else { | ||||
|                 messaging.logError("Unknown command: " + ui.cmd); | ||||
|             } | ||||
|   | ||||
| @@ -46,14 +46,19 @@ const dragAndDropSetup = { | ||||
|         // This function MUST be defined to enable dropping of items on the tree. | ||||
|         // data.hitMode is 'before', 'after', or 'over'. | ||||
|  | ||||
|         const nodeToMove = data.otherNode; | ||||
|         nodeToMove.setSelected(true); | ||||
|  | ||||
|         const selectedNodes = noteTree.getSelectedNodes(); | ||||
|  | ||||
|         if (data.hitMode === "before") { | ||||
|             treeChanges.moveBeforeNode(data.otherNode, node); | ||||
|             treeChanges.moveBeforeNode(selectedNodes, node); | ||||
|         } | ||||
|         else if (data.hitMode === "after") { | ||||
|             treeChanges.moveAfterNode(data.otherNode, node); | ||||
|             treeChanges.moveAfterNode(selectedNodes, node); | ||||
|         } | ||||
|         else if (data.hitMode === "over") { | ||||
|             treeChanges.moveToNode(data.otherNode, node); | ||||
|             treeChanges.moveToNode(selectedNodes, node); | ||||
|         } | ||||
|         else { | ||||
|             throw new Exception("Unknown hitMode=" + data.hitMode); | ||||
|   | ||||
| @@ -57,6 +57,42 @@ $(document).bind('keydown', 'ctrl+f', () => { | ||||
|     } | ||||
| }); | ||||
|  | ||||
| $(document).bind('keydown', "ctrl+shift+left", () => { | ||||
|     const node = noteTree.getCurrentNode(); | ||||
|     node.navigate($.ui.keyCode.LEFT, true); | ||||
|  | ||||
|     $("#note-detail").focus(); | ||||
|  | ||||
|     return false; | ||||
| }); | ||||
|  | ||||
| $(document).bind('keydown', "ctrl+shift+right", () => { | ||||
|     const node = noteTree.getCurrentNode(); | ||||
|     node.navigate($.ui.keyCode.RIGHT, true); | ||||
|  | ||||
|     $("#note-detail").focus(); | ||||
|  | ||||
|     return false; | ||||
| }); | ||||
|  | ||||
| $(document).bind('keydown', "ctrl+shift+up", () => { | ||||
|     const node = noteTree.getCurrentNode(); | ||||
|     node.navigate($.ui.keyCode.UP, true); | ||||
|  | ||||
|     $("#note-detail").focus(); | ||||
|  | ||||
|     return false; | ||||
| }); | ||||
|  | ||||
| $(document).bind('keydown', "ctrl+shift+down", () => { | ||||
|     const node = noteTree.getCurrentNode(); | ||||
|     node.navigate($.ui.keyCode.DOWN, true); | ||||
|  | ||||
|     $("#note-detail").focus(); | ||||
|  | ||||
|     return false; | ||||
| }); | ||||
|  | ||||
| $(window).on('beforeunload', () => { | ||||
|     // this makes sure that when user e.g. reloads the page or navigates away from the page, the note's content is saved | ||||
|     // this sends the request asynchronously and doesn't wait for result | ||||
|   | ||||
| @@ -128,6 +128,9 @@ const noteEditor = (function() { | ||||
|         setNoteBackgroundIfProtected(currentNote); | ||||
|         noteTree.setNoteTreeBackgroundBasedOnProtectedStatus(noteId); | ||||
|  | ||||
|         // after loading new note make sure editor is scrolled to the top | ||||
|         noteDetailWrapperEl.scrollTop(0); | ||||
|  | ||||
|         showAppIfHidden(); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ const noteTree = (function() { | ||||
|     const treeEl = $("#tree"); | ||||
|     const parentListEl = $("#parent-list"); | ||||
|     const parentListListEl = $("#parent-list-list"); | ||||
|     const noteDetailEl = $("#note-detail"); | ||||
|  | ||||
|     let startNotePath = null; | ||||
|     let notesTreeMap = {}; | ||||
| @@ -59,23 +60,6 @@ const noteTree = (function() { | ||||
|         return treeUtils.getNotePath(node); | ||||
|     } | ||||
|  | ||||
|     function getCurrentNoteId() { | ||||
|         const node = getCurrentNode(); | ||||
|  | ||||
|         return node ? node.data.note_id : null; | ||||
|     } | ||||
|  | ||||
|     function getCurrentClones() { | ||||
|         const noteId = getCurrentNoteId(); | ||||
|  | ||||
|         if (noteId) { | ||||
|             return getNodesByNoteId(noteId); | ||||
|         } | ||||
|         else { | ||||
|             return []; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function getNodesByNoteTreeId(noteTreeId) { | ||||
|         assertArguments(noteTreeId); | ||||
|  | ||||
| @@ -185,13 +169,15 @@ const noteTree = (function() { | ||||
|             const noteTreeId = getNoteTreeId(parentNoteId, noteId); | ||||
|             const noteTree = notesTreeMap[noteTreeId]; | ||||
|  | ||||
|             const title = (noteTree.prefix ? (noteTree.prefix + " - ") : "") + noteIdToTitle[noteTree.note_id]; | ||||
|  | ||||
|             const node = { | ||||
|                 note_id: noteTree.note_id, | ||||
|                 parent_note_id: noteTree.parent_note_id, | ||||
|                 note_tree_id: noteTree.note_tree_id, | ||||
|                 is_protected: noteTree.is_protected, | ||||
|                 prefix: noteTree.prefix, | ||||
|                 title: (noteTree.prefix ? (noteTree.prefix + " - ") : "") + noteIdToTitle[noteTree.note_id], | ||||
|                 title: escapeHtml(title), | ||||
|                 extraClasses: getExtraClasses(noteTree), | ||||
|                 refKey: noteTree.note_id, | ||||
|                 expanded: noteTree.is_expanded | ||||
| @@ -223,8 +209,6 @@ const noteTree = (function() { | ||||
|  | ||||
|         let parentNoteId = 'root'; | ||||
|  | ||||
|         //console.log(now(), "Run path: ", runPath); | ||||
|  | ||||
|         for (const childNoteId of runPath) { | ||||
|             const node = getNodesByNoteId(childNoteId).find(node => node.data.parent_note_id === parentNoteId); | ||||
|  | ||||
| @@ -237,6 +221,8 @@ const noteTree = (function() { | ||||
|  | ||||
|             parentNoteId = childNoteId; | ||||
|         } | ||||
|  | ||||
|         clearSelectedNodes(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -396,6 +382,22 @@ const noteTree = (function() { | ||||
|         recentNotes.addRecentNote(currentNoteTreeId, currentNotePath); | ||||
|     } | ||||
|  | ||||
|     function getSelectedNodes() { | ||||
|         return getTree().getSelectedNodes(); | ||||
|     } | ||||
|  | ||||
|     function clearSelectedNodes() { | ||||
|         for (const selectedNode of getSelectedNodes()) { | ||||
|             selectedNode.setSelected(false); | ||||
|         } | ||||
|  | ||||
|         const currentNode = getCurrentNode(); | ||||
|  | ||||
|         if (currentNode) { | ||||
|             currentNode.setSelected(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function initFancyTree(noteTree) { | ||||
|         assertArguments(noteTree); | ||||
|  | ||||
| @@ -403,28 +405,62 @@ const noteTree = (function() { | ||||
|             "del": node => { | ||||
|                 treeChanges.deleteNode(node); | ||||
|             }, | ||||
|             "shift+up": node => { | ||||
|             "ctrl+up": node => { | ||||
|                 const beforeNode = node.getPrevSibling(); | ||||
|  | ||||
|                 if (beforeNode !== null) { | ||||
|                     treeChanges.moveBeforeNode(node, beforeNode); | ||||
|                     treeChanges.moveBeforeNode([node], beforeNode); | ||||
|                 } | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "shift+down": node => { | ||||
|             "ctrl+down": node => { | ||||
|                 let afterNode = node.getNextSibling(); | ||||
|                 if (afterNode !== null) { | ||||
|                     treeChanges.moveAfterNode(node, afterNode); | ||||
|                     treeChanges.moveAfterNode([node], afterNode); | ||||
|                 } | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "shift+left": node => { | ||||
|             "ctrl+left": node => { | ||||
|                 treeChanges.moveNodeUpInHierarchy(node); | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "shift+right": node => { | ||||
|             "ctrl+right": node => { | ||||
|                 let toNode = node.getPrevSibling(); | ||||
|  | ||||
|                 if (toNode !== null) { | ||||
|                     treeChanges.moveToNode(node, toNode); | ||||
|                     treeChanges.moveToNode([node], toNode); | ||||
|                 } | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "shift+up": node => { | ||||
|                 node.navigate($.ui.keyCode.UP, true).then(() => { | ||||
|                     const currentNode = getCurrentNode(); | ||||
|  | ||||
|                     if (currentNode.isSelected()) { | ||||
|                         node.setSelected(false); | ||||
|                     } | ||||
|  | ||||
|                     currentNode.setSelected(true); | ||||
|                 }); | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "shift+down": node => { | ||||
|                 node.navigate($.ui.keyCode.DOWN, true).then(() => { | ||||
|                     const currentNode = getCurrentNode(); | ||||
|  | ||||
|                     if (currentNode.isSelected()) { | ||||
|                         node.setSelected(false); | ||||
|                     } | ||||
|  | ||||
|                     currentNode.setSelected(true); | ||||
|                 }); | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "f2": node => { | ||||
|                 editTreePrefix.showDialog(node); | ||||
| @@ -432,26 +468,54 @@ const noteTree = (function() { | ||||
|             "alt+-": node => { | ||||
|                 collapseTree(node); | ||||
|             }, | ||||
|             "ctrl+a": node => { | ||||
|                 for (const child of node.getParent().getChildren()) { | ||||
|                     child.setSelected(true); | ||||
|                 } | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "ctrl+c": () => { | ||||
|                 contextMenu.copy(getSelectedNodes()); | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "ctrl+x": () => { | ||||
|                 contextMenu.cut(getSelectedNodes()); | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "ctrl+v": node => { | ||||
|                 contextMenu.pasteInto(node); | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "ctrl+return": node => { | ||||
|                 noteDetailEl.focus(); | ||||
|             }, | ||||
|             "return": node => { | ||||
|                 noteDetailEl.focus(); | ||||
|             }, | ||||
|             // code below shouldn't be necessary normally, however there's some problem with interaction with context menu plugin | ||||
|             // after opening context menu, standard shortcuts don't work, but they are detected here | ||||
|             // so we essentially takeover the standard handling with our implementation. | ||||
|             "left": node => { | ||||
|                 node.navigate($.ui.keyCode.LEFT, true); | ||||
|                 node.navigate($.ui.keyCode.LEFT, true).then(() => clearSelectedNodes()); | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "right": node => { | ||||
|                 node.navigate($.ui.keyCode.RIGHT, true); | ||||
|                 node.navigate($.ui.keyCode.RIGHT, true).then(() => clearSelectedNodes()); | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "up": node => { | ||||
|                 node.navigate($.ui.keyCode.UP, true); | ||||
|                 node.navigate($.ui.keyCode.UP, true).then(() => clearSelectedNodes()); | ||||
|  | ||||
|                 return false; | ||||
|             }, | ||||
|             "down": node => { | ||||
|                 node.navigate($.ui.keyCode.DOWN, true); | ||||
|                 node.navigate($.ui.keyCode.DOWN, true).then(() => clearSelectedNodes()); | ||||
|  | ||||
|                 return false; | ||||
|             } | ||||
| @@ -463,6 +527,22 @@ const noteTree = (function() { | ||||
|             extensions: ["hotkeys", "filter", "dnd", "clones"], | ||||
|             source: noteTree, | ||||
|             scrollParent: $("#tree"), | ||||
|             click: (event, data) => { | ||||
|                 const targetType = data.targetType; | ||||
|                 const node = data.node; | ||||
|  | ||||
|                 if (targetType === 'title' || targetType === 'icon') { | ||||
|                     node.setActive(); | ||||
|  | ||||
|                     if (!event.ctrlKey) { | ||||
|                         clearSelectedNodes(); | ||||
|                     } | ||||
|  | ||||
|                     node.setSelected(true); | ||||
|  | ||||
|                     return false; | ||||
|                 } | ||||
|             }, | ||||
|             activate: (event, data) => { | ||||
|                 const node = data.node.data; | ||||
|  | ||||
| @@ -513,57 +593,6 @@ const noteTree = (function() { | ||||
|                 mode: "hide"       // Grayout unmatched nodes (pass "hide" to remove unmatched node instead) | ||||
|             }, | ||||
|             dnd: dragAndDropSetup, | ||||
|             keydown: (event, data) => { | ||||
|                 const node = data.node; | ||||
|                 // Eat keyboard events, when a menu is open | ||||
|                 if ($(".contextMenu:visible").length > 0) | ||||
|                     return false; | ||||
|  | ||||
|                 switch (event.which) { | ||||
|                     // Open context menu on [Space] key (simulate right click) | ||||
|                     case 32: // [Space] | ||||
|                         $(node.span).trigger("mousedown", { | ||||
|                             preventDefault: true, | ||||
|                             button: 2 | ||||
|                         }) | ||||
|                             .trigger("mouseup", { | ||||
|                                 preventDefault: true, | ||||
|                                 pageX: node.span.offsetLeft, | ||||
|                                 pageY: node.span.offsetTop, | ||||
|                                 button: 2 | ||||
|                             }); | ||||
|                         return false; | ||||
|  | ||||
|                     // Handle Ctrl+C, +X and +V | ||||
|                     case 67: | ||||
|                         if (event.ctrlKey) { // Ctrl+C | ||||
|                             contextMenu.copy(node); | ||||
|  | ||||
|                             showMessage("Note copied into clipboard."); | ||||
|  | ||||
|                             return false; | ||||
|                         } | ||||
|                         break; | ||||
|                     case 88: | ||||
|                         if (event.ctrlKey) { // Ctrl+X | ||||
|                             contextMenu.cut(node); | ||||
|  | ||||
|                             showMessage("Note cut into clipboard."); | ||||
|  | ||||
|                             return false; | ||||
|                         } | ||||
|                         break; | ||||
|                     case 86: | ||||
|                         if (event.ctrlKey) { // Ctrl+V | ||||
|                             contextMenu.pasteInto(node); | ||||
|  | ||||
|                             showMessage("Note pasted from clipboard into current note."); | ||||
|  | ||||
|                             return false; | ||||
|                         } | ||||
|                         break; | ||||
|                 } | ||||
|             }, | ||||
|             lazyLoad: function(event, data){ | ||||
|                 const node = data.node.data; | ||||
|  | ||||
| @@ -772,7 +801,11 @@ const noteTree = (function() { | ||||
|     $(window).bind('hashchange', function() { | ||||
|         const notePath = getNotePathFromAddress(); | ||||
|  | ||||
|         activateNode(notePath); | ||||
|         if (getCurrentNotePath() !== notePath) { | ||||
|             console.log("Switching to " + notePath + " because of hash change"); | ||||
|  | ||||
|             activateNode(notePath); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     if (isElectron()) { | ||||
| @@ -807,6 +840,7 @@ const noteTree = (function() { | ||||
|         setPrefix, | ||||
|         getNotePathTitle, | ||||
|         removeParentChildRelation, | ||||
|         setParentChildRelation | ||||
|         setParentChildRelation, | ||||
|         getSelectedNodes | ||||
|     }; | ||||
| })(); | ||||
| @@ -13,4 +13,10 @@ async function syncNow() { | ||||
|  | ||||
|         showError("Sync failed: " + result.message); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function forceNoteSync(noteId) { | ||||
|     const result = await server.post('sync/force-note-sync/' + noteId); | ||||
|  | ||||
|     showMessage("Note added to sync queue."); | ||||
| } | ||||
| @@ -1,16 +1,30 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const treeChanges = (function() { | ||||
|     async function moveBeforeNode(node, beforeNode) { | ||||
|         await server.put('notes/' + node.data.note_tree_id + '/move-before/' + beforeNode.data.note_tree_id); | ||||
|     async function moveBeforeNode(nodesToMove, beforeNode) { | ||||
|         for (const nodeToMove of nodesToMove) { | ||||
|             const resp = await server.put('notes/' + nodeToMove.data.note_tree_id + '/move-before/' + beforeNode.data.note_tree_id); | ||||
|  | ||||
|         changeNode(node, node => node.moveTo(beforeNode, 'before')); | ||||
|             if (!resp.success) { | ||||
|                 alert(resp.message); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             changeNode(nodeToMove, node => node.moveTo(beforeNode, 'before')); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async function moveAfterNode(node, afterNode) { | ||||
|         await server.put('notes/' + node.data.note_tree_id + '/move-after/' + afterNode.data.note_tree_id); | ||||
|     async function moveAfterNode(nodesToMove, afterNode) { | ||||
|         for (const nodeToMove of nodesToMove) { | ||||
|             const resp = await server.put('notes/' + nodeToMove.data.note_tree_id + '/move-after/' + afterNode.data.note_tree_id); | ||||
|  | ||||
|         changeNode(node, node => node.moveTo(afterNode, 'after')); | ||||
|             if (!resp.success) { | ||||
|                 alert(resp.message); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             changeNode(nodeToMove, node => node.moveTo(afterNode, 'after')); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // beware that first arg is noteId and second is noteTreeId! | ||||
| @@ -25,17 +39,30 @@ const treeChanges = (function() { | ||||
|         await noteTree.reload(); | ||||
|     } | ||||
|  | ||||
|     async function moveToNode(node, toNode) { | ||||
|         await server.put('notes/' + node.data.note_tree_id + '/move-to/' + toNode.data.note_id); | ||||
|     async function moveToNode(nodesToMove, toNode) { | ||||
|         for (const nodeToMove of nodesToMove) { | ||||
|             const resp = await server.put('notes/' + nodeToMove.data.note_tree_id + '/move-to/' + toNode.data.note_id); | ||||
|  | ||||
|         changeNode(node, node => { | ||||
|             node.moveTo(toNode); | ||||
|             if (!resp.success) { | ||||
|                 alert(resp.message); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             toNode.setExpanded(true); | ||||
|             changeNode(nodeToMove, node => { | ||||
|                 // first expand which will force lazy load and only then move the node | ||||
|                 // if this is not expanded before moving, then lazy load won't happen because it already contains node | ||||
|                 // this doesn't work if this isn't a folder yet, that's why we expand second time below | ||||
|                 toNode.setExpanded(true); | ||||
|  | ||||
|             toNode.folder = true; | ||||
|             toNode.renderTitle(); | ||||
|         }); | ||||
|                 node.moveTo(toNode); | ||||
|  | ||||
|                 toNode.folder = true; | ||||
|                 toNode.renderTitle(); | ||||
|  | ||||
|                 // this expands the note in case it become the folder only after the move | ||||
|                 toNode.setExpanded(true); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async function cloneNoteTo(childNoteId, parentNoteId, prefix) { | ||||
| @@ -92,7 +119,12 @@ const treeChanges = (function() { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         await server.put('notes/' + node.data.note_tree_id + '/move-after/' + node.getParent().data.note_tree_id); | ||||
|         const resp = await server.put('notes/' + node.data.note_tree_id + '/move-after/' + node.getParent().data.note_tree_id); | ||||
|  | ||||
|         if (!resp.success) { | ||||
|             alert(resp.message); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (!isTopLevelNode(node) && node.getParent().getChildren().length <= 1) { | ||||
|             node.getParent().folder = false; | ||||
|   | ||||
| @@ -37,7 +37,7 @@ const treeUtils = (function() { | ||||
|  | ||||
|         const title = (prefix ? (prefix + " - ") : "") + noteTitle; | ||||
|  | ||||
|         node.setTitle(title); | ||||
|         node.setTitle(escapeHtml(title)); | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|   | ||||
| @@ -93,4 +93,8 @@ function isTopLevelNode(node) { | ||||
|  | ||||
| function isRootNode(node) { | ||||
|     return node.key === "root_1"; | ||||
| } | ||||
|  | ||||
| function escapeHtml(str) { | ||||
|     return $('<div/>').text(str).html(); | ||||
| } | ||||
| @@ -74,6 +74,12 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| /* By default not focused active tree item is not easily visible, this makes it more visible */ | ||||
| span.fancytree-active:not(.fancytree-focused) .fancytree-title { | ||||
|     background-color: #ddd !important; | ||||
|     border-color: #555 !important; | ||||
| } | ||||
|  | ||||
| .ui-autocomplete { | ||||
|     max-height: 300px; | ||||
|     overflow-y: auto; | ||||
| @@ -169,7 +175,6 @@ div.ui-tooltip { | ||||
|  | ||||
| /* Allow to use <kbd> elements inside the title to define shortcut hints. */ | ||||
| .ui-menu kbd, button kbd { | ||||
|     float: right; | ||||
|     color: black; | ||||
|     border: none; | ||||
|     background-color: transparent; | ||||
| @@ -178,6 +183,7 @@ div.ui-tooltip { | ||||
|  | ||||
| .ui-menu kbd { | ||||
|     margin-left: 30px; | ||||
|     float: right; | ||||
| } | ||||
|  | ||||
| #note-id-display { | ||||
|   | ||||
| @@ -12,6 +12,15 @@ router.put('/:noteTreeId/move-to/:parentNoteId', auth.checkApiAuth, async (req, | ||||
|     const parentNoteId = req.params.parentNoteId; | ||||
|     const sourceId = req.headers.source_id; | ||||
|  | ||||
|     const noteToMove = await sql.getFirst("SELECT * FROM notes_tree WHERE note_tree_id = ?", [noteTreeId]); | ||||
|  | ||||
|     if (!await checkTreeCycle(parentNoteId, noteToMove.note_id)) { | ||||
|         return res.send({ | ||||
|             success: false, | ||||
|             message: 'Moving note here would create cycle.' | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     const maxNotePos = await sql.getFirstValue('SELECT MAX(note_position) FROM notes_tree WHERE parent_note_id = ? AND is_deleted = 0', [parentNoteId]); | ||||
|     const newNotePos = maxNotePos === null ? 0 : maxNotePos + 1; | ||||
|  | ||||
| @@ -24,7 +33,7 @@ router.put('/:noteTreeId/move-to/:parentNoteId', auth.checkApiAuth, async (req, | ||||
|         await sync_table.addNoteTreeSync(noteTreeId, sourceId); | ||||
|     }); | ||||
|  | ||||
|     res.send({}); | ||||
|     res.send({ success: true }); | ||||
| }); | ||||
|  | ||||
| router.put('/:noteTreeId/move-before/:beforeNoteTreeId', auth.checkApiAuth, async (req, res, next) => { | ||||
| @@ -32,8 +41,16 @@ router.put('/:noteTreeId/move-before/:beforeNoteTreeId', auth.checkApiAuth, asyn | ||||
|     const beforeNoteTreeId = req.params.beforeNoteTreeId; | ||||
|     const sourceId = req.headers.source_id; | ||||
|  | ||||
|     const noteToMove = await sql.getFirst("SELECT * FROM notes_tree WHERE note_tree_id = ?", [noteTreeId]); | ||||
|     const beforeNote = await sql.getFirst("SELECT * FROM notes_tree WHERE note_tree_id = ?", [beforeNoteTreeId]); | ||||
|  | ||||
|     if (!await checkTreeCycle(beforeNote.parent_note_id, noteToMove.note_id)) { | ||||
|         return res.send({ | ||||
|             success: false, | ||||
|             message: 'Moving note here would create cycle.' | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     if (beforeNote) { | ||||
|         await sql.doInTransaction(async () => { | ||||
|             // we don't change date_modified so other changes are prioritized in case of conflict | ||||
| @@ -51,7 +68,7 @@ router.put('/:noteTreeId/move-before/:beforeNoteTreeId', auth.checkApiAuth, asyn | ||||
|             await sync_table.addNoteTreeSync(noteTreeId, sourceId); | ||||
|         }); | ||||
|  | ||||
|         res.send({}); | ||||
|         res.send({ success: true }); | ||||
|     } | ||||
|     else { | ||||
|         res.status(500).send("Before note " + beforeNoteTreeId + " doesn't exist."); | ||||
| @@ -63,8 +80,16 @@ router.put('/:noteTreeId/move-after/:afterNoteTreeId', auth.checkApiAuth, async | ||||
|     const afterNoteTreeId = req.params.afterNoteTreeId; | ||||
|     const sourceId = req.headers.source_id; | ||||
|  | ||||
|     const noteToMove = await sql.getFirst("SELECT * FROM notes_tree WHERE note_tree_id = ?", [noteTreeId]); | ||||
|     const afterNote = await sql.getFirst("SELECT * FROM notes_tree WHERE note_tree_id = ?", [afterNoteTreeId]); | ||||
|  | ||||
|     if (!await checkTreeCycle(afterNote.parent_note_id, noteToMove.note_id)) { | ||||
|         return res.send({ | ||||
|             success: false, | ||||
|             message: 'Moving note here would create cycle.' | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     if (afterNote) { | ||||
|         await sql.doInTransaction(async () => { | ||||
|             // we don't change date_modified so other changes are prioritized in case of conflict | ||||
| @@ -80,7 +105,7 @@ router.put('/:noteTreeId/move-after/:afterNoteTreeId', auth.checkApiAuth, async | ||||
|             await sync_table.addNoteTreeSync(noteTreeId, sourceId); | ||||
|         }); | ||||
|  | ||||
|         res.send({}); | ||||
|         res.send({ success: true }); | ||||
|     } | ||||
|     else { | ||||
|         res.status(500).send("After note " + afterNoteTreeId + " doesn't exist."); | ||||
| @@ -102,7 +127,7 @@ router.put('/:childNoteId/clone-to/:parentNoteId', auth.checkApiAuth, async (req | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     if (!await checkCycle(parentNoteId, childNoteId)) { | ||||
|     if (!await checkTreeCycle(parentNoteId, childNoteId)) { | ||||
|         return res.send({ | ||||
|             success: false, | ||||
|             message: 'Cloning note here would create cycle.' | ||||
| @@ -131,9 +156,7 @@ router.put('/:childNoteId/clone-to/:parentNoteId', auth.checkApiAuth, async (req | ||||
|         await sql.execute("UPDATE notes_tree SET is_expanded = 1 WHERE note_id = ?", [parentNoteId]); | ||||
|     }); | ||||
|  | ||||
|     res.send({ | ||||
|         success: true | ||||
|     }); | ||||
|     res.send({ success: true }); | ||||
| }); | ||||
|  | ||||
| router.put('/:noteId/clone-after/:afterNoteTreeId', auth.checkApiAuth, async (req, res, next) => { | ||||
| @@ -147,7 +170,7 @@ router.put('/:noteId/clone-after/:afterNoteTreeId', auth.checkApiAuth, async (re | ||||
|         return res.status(500).send("After note " + afterNoteTreeId + " doesn't exist."); | ||||
|     } | ||||
|  | ||||
|     if (!await checkCycle(afterNote.parent_note_id, noteId)) { | ||||
|     if (!await checkTreeCycle(afterNote.parent_note_id, noteId)) { | ||||
|         return res.send({ | ||||
|             success: false, | ||||
|             message: 'Cloning note here would create cycle.' | ||||
| @@ -186,29 +209,51 @@ router.put('/:noteId/clone-after/:afterNoteTreeId', auth.checkApiAuth, async (re | ||||
|         await sync_table.addNoteTreeSync(noteTree.note_tree_id, sourceId); | ||||
|     }); | ||||
|  | ||||
|     res.send({ | ||||
|         success: true | ||||
|     }); | ||||
|     res.send({ success: true }); | ||||
| }); | ||||
|  | ||||
| async function checkCycle(parentNoteId, childNoteId) { | ||||
|     if (parentNoteId === 'root') { | ||||
| async function loadSubTreeNoteIds(parentNoteId, subTreeNoteIds) { | ||||
|     subTreeNoteIds.push(parentNoteId); | ||||
|  | ||||
|     const children = await sql.getFirstColumn("SELECT note_id FROM notes_tree WHERE parent_note_id = ?", [parentNoteId]); | ||||
|  | ||||
|     for (const childNoteId of children) { | ||||
|         await loadSubTreeNoteIds(childNoteId, subTreeNoteIds); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Tree cycle can be created when cloning or when moving existing clone. This method should detect both cases. | ||||
|  */ | ||||
| async function checkTreeCycle(parentNoteId, childNoteId) { | ||||
|     const subTreeNoteIds = []; | ||||
|  | ||||
|     // we'll load the whole sub tree - because the cycle can start in one of the notes in the sub tree | ||||
|     await loadSubTreeNoteIds(childNoteId, subTreeNoteIds); | ||||
|  | ||||
|     async function checkTreeCycleInner(parentNoteId) { | ||||
|         if (parentNoteId === 'root') { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (subTreeNoteIds.includes(parentNoteId)) { | ||||
|             // while towards the root of the tree we encountered noteId which is already present in the subtree | ||||
|             // joining parentNoteId with childNoteId would then clearly create a cycle | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         const parentNoteIds = await sql.getFirstColumn("SELECT DISTINCT parent_note_id FROM notes_tree WHERE note_id = ?", [parentNoteId]); | ||||
|  | ||||
|         for (const pid of parentNoteIds) { | ||||
|             if (!await checkTreeCycleInner(pid)) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     if (parentNoteId === childNoteId) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     const parentNoteIds = await sql.getFirstColumn("SELECT DISTINCT parent_note_id FROM notes_tree WHERE note_id = ?", [parentNoteId]); | ||||
|  | ||||
|     for (const pid of parentNoteIds) { | ||||
|         if (!await checkCycle(pid, childNoteId)) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|     return await checkTreeCycleInner(parentNoteId); | ||||
| } | ||||
|  | ||||
| router.put('/:noteTreeId/expanded/:expanded', auth.checkApiAuth, async (req, res, next) => { | ||||
|   | ||||
| @@ -46,6 +46,30 @@ router.post('/force-full-sync', auth.checkApiAuth, async (req, res, next) => { | ||||
|     res.send({}); | ||||
| }); | ||||
|  | ||||
| router.post('/force-note-sync/:noteId', auth.checkApiAuth, async (req, res, next) => { | ||||
|     const noteId = req.params.noteId; | ||||
|  | ||||
|     await sql.doInTransaction(async () => { | ||||
|         await sync_table.addNoteSync(noteId); | ||||
|  | ||||
|         for (const noteTreeId of await sql.getFirstColumn("SELECT note_tree_id FROM notes_tree WHERE is_deleted = 0 AND note_id = ?", [noteId])) { | ||||
|             await sync_table.addNoteTreeSync(noteTreeId); | ||||
|             await sync_table.addRecentNoteSync(noteTreeId); | ||||
|         } | ||||
|  | ||||
|         for (const noteHistoryId of await sql.getFirstColumn("SELECT note_history_id FROM notes_history WHERE note_id = ?", [noteId])) { | ||||
|             await sync_table.addNoteHistorySync(noteHistoryId); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     log.info("Forcing note sync for " + noteId); | ||||
|  | ||||
|     // not awaiting for the job to finish (will probably take a long time) | ||||
|     sync.sync(); | ||||
|  | ||||
|     res.send({}); | ||||
| }); | ||||
|  | ||||
| router.get('/changed', auth.checkApiAuth, async (req, res, next) => { | ||||
|     const lastSyncId = parseInt(req.query.lastSyncId); | ||||
|  | ||||
|   | ||||
| @@ -3,12 +3,9 @@ | ||||
| const migration = require('./migration'); | ||||
| const sql = require('./sql'); | ||||
| const utils = require('./utils'); | ||||
| const options = require('./options'); | ||||
|  | ||||
| async function checkAuth(req, res, next) { | ||||
|     const username = await options.getOption('username'); | ||||
|  | ||||
|     if (!username) { | ||||
|     if (!await sql.isUserInitialized()) { | ||||
|         res.redirect("setup"); | ||||
|     } | ||||
|     else if (!req.session.loggedIn && !utils.isElectron()) { | ||||
| @@ -53,9 +50,7 @@ async function checkApiAuthForMigrationPage(req, res, next) { | ||||
| } | ||||
|  | ||||
| async function checkAppNotInitialized(req, res, next) { | ||||
|     const username = await options.getOption('username'); | ||||
|  | ||||
|     if (username) { | ||||
|     if (await sql.isUserInitialized()) { | ||||
|         res.status(400).send("App already initialized."); | ||||
|     } | ||||
|     else { | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { build_date:"2017-12-27T17:41:07-05:00", build_revision: "6405d6e06658188f14f29b0a2e1891e5287000f5" }; | ||||
| module.exports = { build_date:"2018-01-01T23:29:34-05:00", build_revision: "ae6e222c506c170ecd24d758328e0678f158bb47" }; | ||||
|   | ||||
| @@ -17,6 +17,46 @@ async function runCheck(query, errorText, errorList) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function checkTreeCycles(errorList) { | ||||
|     const childToParents = {}; | ||||
|     const rows = await sql.getAll("SELECT note_id, parent_note_id FROM notes_tree"); | ||||
|  | ||||
|     for (const row of rows) { | ||||
|         const childNoteId = row.note_id; | ||||
|         const parentNoteId = row.parent_note_id; | ||||
|  | ||||
|         if (!childToParents[childNoteId]) { | ||||
|             childToParents[childNoteId] = []; | ||||
|         } | ||||
|  | ||||
|         childToParents[childNoteId].push(parentNoteId); | ||||
|     } | ||||
|  | ||||
|     function checkTreeCycle(noteId, path, errorList) { | ||||
|         if (noteId === 'root') { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         for (const parentNoteId of childToParents[noteId]) { | ||||
|             if (path.includes(parentNoteId)) { | ||||
|                 errorList.push(`Tree cycle detected at parent-child relationship: ${parentNoteId} - ${noteId}, whole path: ${path}`); | ||||
|             } | ||||
|             else { | ||||
|                 const newPath = path.slice(); | ||||
|                 newPath.push(noteId); | ||||
|  | ||||
|                 checkTreeCycle(parentNoteId, newPath, errorList); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const noteIds = Object.keys(childToParents); | ||||
|  | ||||
|     for (const noteId of noteIds) { | ||||
|         checkTreeCycle(noteId, [], errorList); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function runSyncRowChecks(table, key, errorList) { | ||||
|     await runCheck(` | ||||
|         SELECT  | ||||
| @@ -43,6 +83,8 @@ async function runSyncRowChecks(table, key, errorList) { | ||||
| async function runChecks() { | ||||
|     const errorList = []; | ||||
|  | ||||
|     const startTime = new Date(); | ||||
|  | ||||
|     await runCheck(` | ||||
|           SELECT  | ||||
|             note_id  | ||||
| @@ -124,11 +166,21 @@ async function runChecks() { | ||||
|     await runSyncRowChecks("notes_tree", "note_tree_id", errorList); | ||||
|     await runSyncRowChecks("recent_notes", "note_tree_id", errorList); | ||||
|  | ||||
|     if (errorList.length === 0) { | ||||
|         // we run this only if basic checks passed since this assumes basic data consistency | ||||
|  | ||||
|         await checkTreeCycles(errorList); | ||||
|     } | ||||
|  | ||||
|     const elapsedTimeMs = new Date().getTime() - startTime.getTime(); | ||||
|  | ||||
|     if (errorList.length > 0) { | ||||
|         log.info(`Consistency checks failed (took ${elapsedTimeMs}ms) with these errors: ` + JSON.stringify(errorList)); | ||||
|  | ||||
|         messaging.sendMessageToAllClients({type: 'consistency-checks-failed'}); | ||||
|     } | ||||
|     else { | ||||
|         log.info("All consistency checks passed."); | ||||
|         log.info(`All consistency checks passed (took ${elapsedTimeMs}ms)`); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| const sql = require('./sql'); | ||||
| const utils = require('./utils'); | ||||
| const options = require('./options'); | ||||
| const log = require('./log'); | ||||
|  | ||||
| function getHash(rows) { | ||||
|     let hash = ''; | ||||
| @@ -13,9 +14,11 @@ function getHash(rows) { | ||||
| } | ||||
|  | ||||
| async function getHashes() { | ||||
|     const startTime = new Date(); | ||||
|  | ||||
|     const optionsQuestionMarks = Array(options.SYNCED_OPTIONS.length).fill('?').join(','); | ||||
|  | ||||
|     return { | ||||
|     const hashes = { | ||||
|         notes: getHash(await sql.getAll(`SELECT | ||||
|                                                   note_id, | ||||
|                                                   note_title, | ||||
| @@ -62,6 +65,12 @@ async function getHashes() { | ||||
|                                                   WHERE opt_name IN (${optionsQuestionMarks})  | ||||
|                                                   ORDER BY opt_name`, options.SYNCED_OPTIONS)) | ||||
|     }; | ||||
|  | ||||
|     const elapseTimeMs = new Date().getTime() - startTime.getTime(); | ||||
|  | ||||
|     log.info(`Content hash computation took ${elapseTimeMs}ms`); | ||||
|  | ||||
|     return hashes; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const crypto = require('crypto'); | ||||
| const log = require('./log'); | ||||
|  | ||||
| function arraysIdentical(a, b) { | ||||
|     let i = a.length; | ||||
| @@ -72,7 +73,15 @@ function decrypt(key, iv, cipherText) { | ||||
| function decryptString(dataKey, iv, cipherText) { | ||||
|     const buffer = decrypt(dataKey, iv, cipherText); | ||||
|  | ||||
|     return buffer.toString('utf-8'); | ||||
|     const str = buffer.toString('utf-8'); | ||||
|  | ||||
|     if (str === 'false') { | ||||
|         log.error("Could not decrypt string. Buffer: " + buffer); | ||||
|  | ||||
|         throw new Error("Could not decrypt string."); | ||||
|     } | ||||
|  | ||||
|     return str; | ||||
| } | ||||
|  | ||||
| function noteTitleIv(iv) { | ||||
|   | ||||
| @@ -54,6 +54,8 @@ async function sendMessage(client, message) { | ||||
| async function sendMessageToAllClients(message) { | ||||
|     const jsonStr = JSON.stringify(message); | ||||
|  | ||||
|     log.info("Sending message to all clients: " + jsonStr); | ||||
|  | ||||
|     webSocketServer.clients.forEach(function each(client) { | ||||
|         if (client.readyState === WebSocket.OPEN) { | ||||
|             client.send(jsonStr); | ||||
|   | ||||
| @@ -49,9 +49,7 @@ const dbReady = new Promise((resolve, reject) => { | ||||
|             // the database | ||||
|         } | ||||
|         else { | ||||
|             const username = await getFirstValue("SELECT opt_value FROM options WHERE opt_name = 'username'"); | ||||
|  | ||||
|             if (!username) { | ||||
|             if (!await isUserInitialized()) { | ||||
|                 log.info("Login/password not initialized. DB not ready."); | ||||
|  | ||||
|                 return; | ||||
| @@ -235,8 +233,15 @@ async function isDbUpToDate() { | ||||
|     return upToDate; | ||||
| } | ||||
|  | ||||
| async function isUserInitialized() { | ||||
|     const username = await getFirstValue("SELECT opt_value FROM options WHERE opt_name = 'username'"); | ||||
|  | ||||
|     return !!username; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     dbReady, | ||||
|     isUserInitialized, | ||||
|     insert, | ||||
|     replace, | ||||
|     getFirstValue, | ||||
|   | ||||
| @@ -24,8 +24,8 @@ async function addOptionsSync(optName, sourceId) { | ||||
|     await addEntitySync("options", optName, sourceId); | ||||
| } | ||||
|  | ||||
| async function addRecentNoteSync(notePath, sourceId) { | ||||
|     await addEntitySync("recent_notes", notePath, sourceId); | ||||
| async function addRecentNoteSync(noteTreeId, sourceId) { | ||||
|     await addEntitySync("recent_notes", noteTreeId, sourceId); | ||||
| } | ||||
|  | ||||
| async function addEntitySync(entityName, entityId, sourceId) { | ||||
|   | ||||
| @@ -40,7 +40,9 @@ async function updateNoteHistory(entity, sourceId) { | ||||
|     const orig = await sql.getFirstOrNull("SELECT * FROM notes_history WHERE note_history_id = ?", [entity.note_history_id]); | ||||
|  | ||||
|     await sql.doInTransaction(async () => { | ||||
|         if (orig === null || orig.date_modified_to < entity.date_modified_to) { | ||||
|         // we update note history even if date modified to is the same because the only thing which might have changed | ||||
|         // is the protected status (and correnspondingly note_title and note_text) which doesn't affect the date_modified_to | ||||
|         if (orig === null || orig.date_modified_to <= entity.date_modified_to) { | ||||
|             await sql.replace('notes_history', entity); | ||||
|  | ||||
|             await sync_table.addNoteHistorySync(entity.note_history_id, sourceId); | ||||
|   | ||||
| @@ -48,6 +48,5 @@ | ||||
|     <script>if (typeof module === 'object') {window.module = module; module = undefined;}</script> | ||||
|  | ||||
|     <link href="libraries/bootstrap/css/bootstrap.css" rel="stylesheet"> | ||||
|     <script src="libraries/bootstrap/js/bootstrap.js"></script> | ||||
|   </body> | ||||
| </html> | ||||
		Reference in New Issue
	
	Block a user