mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	notes_tree now has note_tree_id so we stricly distinguish between working on notes or note trees
This commit is contained in:
		
							
								
								
									
										21
									
								
								migrations/0037__add_note_tree_id.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								migrations/0037__add_note_tree_id.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | CREATE TABLE [notes_tree_mig] ( | ||||||
|  |     [note_tree_id] VARCHAR(30) PRIMARY KEY NOT NULL, | ||||||
|  |     [note_id] VARCHAR(30) UNIQUE NOT NULL, | ||||||
|  |     [note_pid] VARCHAR(30) NOT NULL, | ||||||
|  |     [note_pos] INTEGER NOT NULL, | ||||||
|  |     [is_expanded] BOOLEAN NULL , | ||||||
|  |     date_modified INTEGER NOT NULL DEFAULT 0, | ||||||
|  |     is_deleted INTEGER NOT NULL DEFAULT 0 | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | INSERT INTO notes_tree_mig (note_tree_id, note_id, note_pid, note_pos, is_expanded, date_modified, is_deleted) | ||||||
|  |     SELECT 'TT' || SUBSTR(note_id, 3), note_id, note_pid, note_pos, is_expanded, date_modified, is_deleted FROM notes_tree; | ||||||
|  |  | ||||||
|  | UPDATE notes_tree_mig SET note_pid = 'TT' || SUBSTR(note_pid, 3) WHERE note_pid != 'root'; | ||||||
|  |  | ||||||
|  | DROP TABLE notes_tree; | ||||||
|  | ALTER TABLE notes_tree_mig RENAME TO notes_tree; | ||||||
|  |  | ||||||
|  | CREATE INDEX `IDX_notes_tree_note_id` ON `notes_tree` ( | ||||||
|  | 	`note_tree_id` | ||||||
|  | ); | ||||||
							
								
								
									
										1
									
								
								migrations/0038__rename_start_node.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								migrations/0038__rename_start_node.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | UPDATE options SET opt_name = 'start_note_tree_id' WHERE opt_name = 'start_node'; | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | DROP TABLE recent_notes; | ||||||
|  |  | ||||||
|  | CREATE TABLE `recent_notes` ( | ||||||
|  |     `note_tree_id` TEXT NOT NULL PRIMARY KEY, | ||||||
|  |     `date_accessed` INTEGER NOT NULL , | ||||||
|  |     is_deleted INT | ||||||
|  | ); | ||||||
| @@ -4,23 +4,23 @@ const contextMenu = (function() { | |||||||
|     const treeEl = $("#tree"); |     const treeEl = $("#tree"); | ||||||
|  |  | ||||||
|     function pasteAfter(node) { |     function pasteAfter(node) { | ||||||
|         const subjectNode = treeUtils.getNodeByKey(noteTree.getClipboardNoteId()); |         const subjectNode = treeUtils.getNodeByNoteTreeId(noteTree.getClipboardNoteTreeId()); | ||||||
|  |  | ||||||
|         treeChanges.moveAfterNode(subjectNode, node); |         treeChanges.moveAfterNode(subjectNode, node); | ||||||
|  |  | ||||||
|         noteTree.setClipboardNoteId(null); |         noteTree.setClipboardNoteTreeId(null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function pasteInto(node) { |     function pasteInto(node) { | ||||||
|         const subjectNode = treeUtils.getNodeByKey(noteTree.getClipboardNoteId()); |         const subjectNode = treeUtils.getNodeByNoteTreeId(noteTree.getClipboardNoteTreeId()); | ||||||
|  |  | ||||||
|         treeChanges.moveToNode(subjectNode, node); |         treeChanges.moveToNode(subjectNode, node); | ||||||
|  |  | ||||||
|         noteTree.setClipboardNoteId(null); |         noteTree.setClipboardNoteTreeId(null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function cut(node) { |     function cut(node) { | ||||||
|         noteTree.setClipboardNoteId(node.key); |         noteTree.setClipboardNoteTreeId(node.note_tree_id); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const contextMenuSettings = { |     const contextMenuSettings = { | ||||||
| @@ -42,8 +42,8 @@ const contextMenu = (function() { | |||||||
|         beforeOpen: (event, ui) => { |         beforeOpen: (event, ui) => { | ||||||
|             const node = $.ui.fancytree.getNode(ui.target); |             const node = $.ui.fancytree.getNode(ui.target); | ||||||
|             // Modify menu entries depending on node status |             // Modify menu entries depending on node status | ||||||
|             treeEl.contextmenu("enableEntry", "pasteAfter", noteTree.getClipboardNoteId() !== null); |             treeEl.contextmenu("enableEntry", "pasteAfter", noteTree.getClipboardNoteTreeId() !== null); | ||||||
|             treeEl.contextmenu("enableEntry", "pasteInto", noteTree.getClipboardNoteId() !== null); |             treeEl.contextmenu("enableEntry", "pasteInto", noteTree.getClipboardNoteTreeId() !== null); | ||||||
|  |  | ||||||
|             treeEl.contextmenu("enableEntry", "protectSubTree", protected_session.isProtectedSessionAvailable()); |             treeEl.contextmenu("enableEntry", "protectSubTree", protected_session.isProtectedSessionAvailable()); | ||||||
|             treeEl.contextmenu("enableEntry", "unprotectSubTree", protected_session.isProtectedSessionAvailable()); |             treeEl.contextmenu("enableEntry", "unprotectSubTree", protected_session.isProtectedSessionAvailable()); | ||||||
| @@ -59,10 +59,10 @@ const contextMenu = (function() { | |||||||
|             const node = $.ui.fancytree.getNode(ui.target); |             const node = $.ui.fancytree.getNode(ui.target); | ||||||
|  |  | ||||||
|             if (ui.cmd === "insertNoteHere") { |             if (ui.cmd === "insertNoteHere") { | ||||||
|                 const parentKey = treeUtils.getParentKey(node); |                 const parentNoteTreeId = treeUtils.getParentNoteTreeId(node); | ||||||
|                 const isProtected = treeUtils.getParentProtectedStatus(node); |                 const isProtected = treeUtils.getParentProtectedStatus(node); | ||||||
|  |  | ||||||
|                 noteEditor.createNote(node, parentKey, 'after', isProtected); |                 noteEditor.createNote(node, parentNoteTreeId, 'after', isProtected); | ||||||
|             } |             } | ||||||
|             else if (ui.cmd === "insertChildNote") { |             else if (ui.cmd === "insertChildNote") { | ||||||
|                 noteEditor.createNote(node, node.key, 'into'); |                 noteEditor.createNote(node, node.key, 'into'); | ||||||
|   | |||||||
| @@ -13,31 +13,31 @@ const recentNotes = (function() { | |||||||
|         type: 'GET', |         type: 'GET', | ||||||
|         error: () => showError("Error getting recent notes.") |         error: () => showError("Error getting recent notes.") | ||||||
|     }).then(result => { |     }).then(result => { | ||||||
|         list = result.map(r => r.note_id); |         list = result.map(r => r.note_tree_id); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     function addRecentNote(noteTreeId, noteContentId) { |     function addRecentNote(noteTreeId) { | ||||||
|         setTimeout(() => { |         setTimeout(() => { | ||||||
|             // we include the note into recent list only if the user stayed on the note at least 5 seconds |             // we include the note into recent list only if the user stayed on the note at least 5 seconds | ||||||
|             if (noteTreeId === noteEditor.getCurrentNoteId() || noteContentId === noteEditor.getCurrentNoteId()) { |             if (noteTreeId === noteEditor.getCurrentNoteId()) { | ||||||
|                 $.ajax({ |                 $.ajax({ | ||||||
|                     url: baseApiUrl + 'recent-notes/' + noteTreeId, |                     url: baseApiUrl + 'recent-notes/' + noteTreeId, | ||||||
|                     type: 'PUT', |                     type: 'PUT', | ||||||
|                     error: () => showError("Error setting recent notes.") |                     error: () => showError("Error setting recent notes.") | ||||||
|                 }).then(result => { |                 }).then(result => { | ||||||
|                     list = result.map(r => r.note_id); |                     list = result.map(r => r.note_tree_id); | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|         }, 1500); |         }, 1500); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function removeRecentNote(noteIdToRemove) { |     function removeRecentNote(noteTreeIdToRemove) { | ||||||
|         $.ajax({ |         $.ajax({ | ||||||
|             url: baseApiUrl + 'recent-notes/' + noteIdToRemove, |             url: baseApiUrl + 'recent-notes/' + noteTreeIdToRemove, | ||||||
|             type: 'DELETE', |             type: 'DELETE', | ||||||
|             error: () => showError("Error removing note from recent notes.") |             error: () => showError("Error removing note from recent notes.") | ||||||
|         }).then(result => { |         }).then(result => { | ||||||
|             list = result.map(r => r.note_id); |             list = result.map(r => r.note_tree_id); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -56,15 +56,15 @@ const recentNotes = (function() { | |||||||
|         // remove the current note |         // remove the current note | ||||||
|         const recNotes = list.filter(note => note !== noteEditor.getCurrentNoteId()); |         const recNotes = list.filter(note => note !== noteEditor.getCurrentNoteId()); | ||||||
|  |  | ||||||
|         $.each(recNotes, (key, valueNoteId) => { |         $.each(recNotes, (key, valueNoteTreeId) => { | ||||||
|             const noteTitle = treeUtils.getFullName(valueNoteId); |             const noteTitle = treeUtils.getFullName(valueNoteTreeId); | ||||||
|  |  | ||||||
|             if (!noteTitle) { |             if (!noteTitle) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             const option = $("<option></option>") |             const option = $("<option></option>") | ||||||
|                 .attr("value", valueNoteId) |                 .attr("value", valueNoteTreeId) | ||||||
|                 .text(noteTitle); |                 .text(noteTitle); | ||||||
|  |  | ||||||
|             // select the first one (most recent one) by default |             // select the first one (most recent one) by default | ||||||
|   | |||||||
| @@ -92,7 +92,7 @@ const noteEditor = (function() { | |||||||
|  |  | ||||||
|         const title = noteTitleEl.val(); |         const title = noteTitleEl.val(); | ||||||
|  |  | ||||||
|         treeUtils.getNodeByKey(note.detail.note_id).setTitle(title); |         noteTree.getCurrentNode().setTitle(title); | ||||||
|  |  | ||||||
|         note.detail.note_title = title; |         note.detail.note_title = title; | ||||||
|     } |     } | ||||||
| @@ -121,7 +121,7 @@ const noteEditor = (function() { | |||||||
|  |  | ||||||
|     let newNoteCreated = false; |     let newNoteCreated = false; | ||||||
|  |  | ||||||
|     async function createNote(node, parentKey, target, isProtected) { |     async function createNote(node, parentTreeId, target, isProtected) { | ||||||
|         // if isProtected isn't available (user didn't enter password yet), then note is created as unencrypted |         // if isProtected isn't available (user didn't enter password yet), then note is created as unencrypted | ||||||
|         // but this is quite weird since user doesn't see where the note is being created so it shouldn't occur often |         // but this is quite weird since user doesn't see where the note is being created so it shouldn't occur often | ||||||
|         if (!isProtected || !protected_session.isProtectedSessionAvailable()) { |         if (!isProtected || !protected_session.isProtectedSessionAvailable()) { | ||||||
| @@ -131,12 +131,12 @@ const noteEditor = (function() { | |||||||
|         const newNoteName = "new note"; |         const newNoteName = "new note"; | ||||||
|  |  | ||||||
|         const result = await $.ajax({ |         const result = await $.ajax({ | ||||||
|             url: baseApiUrl + 'notes/' + parentKey + '/children' , |             url: baseApiUrl + 'notes/' + parentTreeId + '/children' , | ||||||
|             type: 'POST', |             type: 'POST', | ||||||
|             data: JSON.stringify({ |             data: JSON.stringify({ | ||||||
|                 note_title: newNoteName, |                 note_title: newNoteName, | ||||||
|                 target: target, |                 target: target, | ||||||
|                 target_note_id: node.key, |                 target_note_id: node.note_tree_id, | ||||||
|                 is_protected: isProtected |                 is_protected: isProtected | ||||||
|             }), |             }), | ||||||
|             contentType: "application/json" |             contentType: "application/json" | ||||||
| @@ -144,13 +144,14 @@ const noteEditor = (function() { | |||||||
|  |  | ||||||
|         const newNode = { |         const newNode = { | ||||||
|             title: newNoteName, |             title: newNoteName, | ||||||
|             key: result.note_id, |             key: counter++, | ||||||
|             note_id: result.note_id, |             note_id: result.note_id, | ||||||
|  |             note_tree_id: result.note_tree_id, | ||||||
|             is_protected: isProtected, |             is_protected: isProtected, | ||||||
|             extraClasses: isProtected ? "protected" : "" |             extraClasses: isProtected ? "protected" : "" | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         glob.allNoteIds.push(result.note_id); |         glob.allNoteIds.push(result.note_tree_id); | ||||||
|  |  | ||||||
|         newNoteCreated = true; |         newNoteCreated = true; | ||||||
|  |  | ||||||
| @@ -167,11 +168,6 @@ const noteEditor = (function() { | |||||||
|         showMessage("Created!"); |         showMessage("Created!"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function setTreeBasedOnProtectedStatus(note) { |  | ||||||
|         const node = treeUtils.getNodeByKey(note.detail.note_id); |  | ||||||
|         node.toggleClass("protected", !!note.detail.is_protected); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     function setNoteBackgroundIfProtected(note) { |     function setNoteBackgroundIfProtected(note) { | ||||||
|         if (note.detail.is_protected) { |         if (note.detail.is_protected) { | ||||||
|             $(".note-editable").addClass("protected"); |             $(".note-editable").addClass("protected"); | ||||||
| @@ -184,7 +180,7 @@ const noteEditor = (function() { | |||||||
|             unprotectButton.hide(); |             unprotectButton.hide(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         setTreeBasedOnProtectedStatus(note); |         noteTree.setCurrentNoteTreeBasedOnProtectedStatus(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function loadNoteToEditor(noteId) { |     async function loadNoteToEditor(noteId) { | ||||||
| @@ -217,10 +213,6 @@ const noteEditor = (function() { | |||||||
|  |  | ||||||
|         noteDetailEl.summernote('code', currentNote.detail.note_text); |         noteDetailEl.summernote('code', currentNote.detail.note_text); | ||||||
|  |  | ||||||
|         document.location.hash = noteId; |  | ||||||
|  |  | ||||||
|         recentNotes.addRecentNote(noteId, currentNote.detail.note_id); |  | ||||||
|  |  | ||||||
|         noteChangeDisabled = false; |         noteChangeDisabled = false; | ||||||
|  |  | ||||||
|         setNoteBackgroundIfProtected(currentNote); |         setNoteBackgroundIfProtected(currentNote); | ||||||
|   | |||||||
| @@ -3,35 +3,59 @@ | |||||||
| const noteTree = (function() { | const noteTree = (function() { | ||||||
|     const noteDetailEl = $('#note-detail'); |     const noteDetailEl = $('#note-detail'); | ||||||
|     const treeEl = $("#tree"); |     const treeEl = $("#tree"); | ||||||
|     let startNoteId = null; |     let startNoteTreeId = null; | ||||||
|     let treeLoadTime = null; |     let treeLoadTime = null; | ||||||
|     let clipboardNoteId = null; |     let clipboardNoteTreeId = null; | ||||||
|     let notesMap = {}; |     let notesMap = {}; | ||||||
|     let parentToNotes = {}; |     let parentToNotes = {}; | ||||||
|  |     let counter = 1; | ||||||
|  |     let noteTreeIdToKey = {}; | ||||||
|  |  | ||||||
|  |     function getNoteTreeIdFromKey(key) { | ||||||
|  |         const node = treeUtils.getNodeByKey(key); | ||||||
|  |  | ||||||
|  |         return node.note_tree_id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function getKeyFromNoteTreeId(noteTreeId) { | ||||||
|  |         return noteTreeIdToKey[noteTreeId]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     function getTreeLoadTime() { |     function getTreeLoadTime() { | ||||||
|         return treeLoadTime; |         return treeLoadTime; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getClipboardNoteId() { |     function getClipboardNoteTreeId() { | ||||||
|         return clipboardNoteId; |         return clipboardNoteTreeId; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function setClipboardNoteId(cbNoteId) { |     function setClipboardNoteTreeId(cbNoteId) { | ||||||
|         clipboardNoteId = cbNoteId; |         clipboardNoteTreeId = cbNoteId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function prepareNoteTree(notes) { showAppIfHidden(); | ||||||
|  |         parentToNotes = {}; | ||||||
|  |         notesMap = {}; | ||||||
|  |  | ||||||
|  |         for (const note of notes) { | ||||||
|  |             if (!parentToNotes[note.note_pid]) { | ||||||
|  |                 parentToNotes[note.note_pid] = []; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             notesMap[note.note_tree_id] = note; | ||||||
|  |             parentToNotes[note.note_pid].push(note.note_tree_id); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     function prepareNoteTree() { |  | ||||||
|         glob.allNoteIds = Object.keys(notesMap); |         glob.allNoteIds = Object.keys(notesMap); | ||||||
|  |  | ||||||
|         return prepareNoteTreeInner(parentToNotes['root']); |         return prepareNoteTreeInner(parentToNotes['root']); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function prepareNoteTreeInner(noteIds) { |     function prepareNoteTreeInner(noteTreeIds) { | ||||||
|         const noteList = []; |         const noteList = []; | ||||||
|  |  | ||||||
|         for (const noteId of noteIds) { |         for (const noteTreeId of noteTreeIds) { | ||||||
|             const note = notesMap[noteId]; |             const note = notesMap[noteTreeId]; | ||||||
|  |  | ||||||
|             note.title = note.note_title; |             note.title = note.note_title; | ||||||
|  |  | ||||||
| @@ -39,14 +63,16 @@ const noteTree = (function() { | |||||||
|                 note.extraClasses = "protected"; |                 note.extraClasses = "protected"; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             note.key = note.note_id; |             note.key = counter++ + ""; // key needs to be string | ||||||
|             note.expanded = note.is_expanded; |             note.expanded = note.is_expanded; | ||||||
|  |  | ||||||
|             if (parentToNotes[noteId] && parentToNotes[noteId].length > 0) { |             noteTreeIdToKey[noteTreeId] = note.key; | ||||||
|  |  | ||||||
|  |             if (parentToNotes[noteTreeId] && parentToNotes[noteTreeId].length > 0) { | ||||||
|                 note.folder = true; |                 note.folder = true; | ||||||
|  |  | ||||||
|                 if (note.expanded) { |                 if (note.expanded) { | ||||||
|                     note.children = prepareNoteTreeInner(parentToNotes[noteId], notesMap, parentToNotes); |                     note.children = prepareNoteTreeInner(parentToNotes[noteTreeId], notesMap, parentToNotes); | ||||||
|                 } |                 } | ||||||
|                 else { |                 else { | ||||||
|                     note.lazy = true; |                     note.lazy = true; | ||||||
| @@ -59,27 +85,27 @@ const noteTree = (function() { | |||||||
|         return noteList; |         return noteList; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function setExpandedToServer(note_id, is_expanded) { |     function setExpandedToServer(noteTreeId, isExpanded) { | ||||||
|         const expandedNum = is_expanded ? 1 : 0; |         const expandedNum = isExpanded ? 1 : 0; | ||||||
|  |  | ||||||
|         $.ajax({ |         $.ajax({ | ||||||
|             url: baseApiUrl + 'notes/' + note_id + '/expanded/' + expandedNum, |             url: baseApiUrl + 'notes/' + noteTreeId + '/expanded/' + expandedNum, | ||||||
|             type: 'PUT', |             type: 'PUT', | ||||||
|             contentType: "application/json", |             contentType: "application/json", | ||||||
|             success: result => {} |             success: result => {} | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function initFancyTree(notes) { |     function initFancyTree(noteTree) { | ||||||
|         const keybindings = { |         const keybindings = { | ||||||
|             "insert": node => { |             "insert": node => { | ||||||
|                 const parentKey = treeUtils.getParentKey(node); |                 const parentNoteTreeId = treeUtils.getParentNoteTreeId(node); | ||||||
|                 const isProtected = treeUtils.getParentProtectedStatus(node); |                 const isProtected = treeUtils.getParentProtectedStatus(node); | ||||||
|  |  | ||||||
|                 noteEditor.createNote(node, parentKey, 'after', isProtected); |                 noteEditor.createNote(node, parentNoteTreeId, 'after', isProtected); | ||||||
|             }, |             }, | ||||||
|             "ctrl+insert": node => { |             "ctrl+insert": node => { | ||||||
|                 noteEditor.createNote(node, node.key, 'into', node.data.is_protected); |                 noteEditor.createNote(node, node.note_id, 'into', node.data.is_protected); | ||||||
|             }, |             }, | ||||||
|             "del": node => { |             "del": node => { | ||||||
|                 treeChanges.deleteNode(node); |                 treeChanges.deleteNode(node); | ||||||
| @@ -116,22 +142,26 @@ const noteTree = (function() { | |||||||
|         treeEl.fancytree({ |         treeEl.fancytree({ | ||||||
|             autoScroll: true, |             autoScroll: true, | ||||||
|             extensions: ["hotkeys", "filter", "dnd"], |             extensions: ["hotkeys", "filter", "dnd"], | ||||||
|             source: notes, |             source: noteTree, | ||||||
|             scrollParent: $("#tree"), |             scrollParent: $("#tree"), | ||||||
|             activate: (event, data) => { |             activate: (event, data) => { | ||||||
|                 const node = data.node.data; |                 const node = data.node.data; | ||||||
|  |  | ||||||
|  |                 document.location.hash = node.note_tree_id; | ||||||
|  |  | ||||||
|  |                 recentNotes.addRecentNote(node.note_tree_id); | ||||||
|  |  | ||||||
|                 noteEditor.switchToNote(node.note_id); |                 noteEditor.switchToNote(node.note_id); | ||||||
|             }, |             }, | ||||||
|             expand: (event, data) => { |             expand: (event, data) => { | ||||||
|                 setExpandedToServer(data.node.key, true); |                 setExpandedToServer(getNoteTreeIdFromKey(data.node.key), true); | ||||||
|             }, |             }, | ||||||
|             collapse: (event, data) => { |             collapse: (event, data) => { | ||||||
|                 setExpandedToServer(data.node.key, false); |                 setExpandedToServer(getNoteTreeIdFromKey(data.node.key), false); | ||||||
|             }, |             }, | ||||||
|             init: (event, data) => { |             init: (event, data) => { | ||||||
|                 if (startNoteId) { |                 if (startNoteTreeId) { | ||||||
|                     data.tree.activateKey(startNoteId); |                     treeUtils.activateNode(startNoteTreeId); | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             hotkeys: { |             hotkeys: { | ||||||
| @@ -197,13 +227,14 @@ const noteTree = (function() { | |||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             lazyLoad: function(event, data){ |             lazyLoad: function(event, data){ | ||||||
|                 const node = data.node; |                 const node = data.node.data; | ||||||
|  |                 const noteTreeId = node.note_tree_id; | ||||||
|  |  | ||||||
|                 if (parentToNotes[node.key]) { |                 if (parentToNotes[noteTreeId]) { | ||||||
|                     data.result = prepareNoteTreeInner(parentToNotes[node.key]); |                     data.result = prepareNoteTreeInner(parentToNotes[noteTreeId]); | ||||||
|                 } |                 } | ||||||
|                 else { |                 else { | ||||||
|                     console.log("No children. This shouldn't happen."); |                     console.log("No children for " + noteTreeId + ". This shouldn't happen."); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| @@ -212,28 +243,26 @@ const noteTree = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function reload() { |     async function reload() { | ||||||
|         const notesMap = await loadTree(); |         const notes = await loadTree(); | ||||||
|  |  | ||||||
|         // this will also reload the note content |         // this will also reload the note content | ||||||
|         await treeEl.fancytree('getTree').reload(notesMap); |         await treeEl.fancytree('getTree').reload(notes); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function loadTree() { |     function loadTree() { | ||||||
|         return $.get(baseApiUrl + 'tree').then(resp => { |         return $.get(baseApiUrl + 'tree').then(resp => { | ||||||
|             notesMap = resp.notes_map; |             startNoteTreeId = resp.start_note_tree_id; | ||||||
|             parentToNotes = resp.parent_to_notes; |  | ||||||
|             startNoteId = resp.start_note_id; |  | ||||||
|             treeLoadTime = resp.tree_load_time; |             treeLoadTime = resp.tree_load_time; | ||||||
|  |  | ||||||
|             if (document.location.hash) { |             if (document.location.hash) { | ||||||
|                 startNoteId = document.location.hash.substr(1); // strip initial # |                 startNoteTreeId = document.location.hash.substr(1); // strip initial # | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return prepareNoteTree(); |             return prepareNoteTree(resp.notes); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $(() => loadTree().then(notesMap => initFancyTree(notesMap))); |     $(() => loadTree().then(noteTree => initFancyTree(noteTree))); | ||||||
|  |  | ||||||
|     function collapseTree() { |     function collapseTree() { | ||||||
|         treeEl.fancytree("getRootNode").visit(node => { |         treeEl.fancytree("getRootNode").visit(node => { | ||||||
| @@ -244,7 +273,7 @@ const noteTree = (function() { | |||||||
|     $(document).bind('keydown', 'alt+c', collapseTree); |     $(document).bind('keydown', 'alt+c', collapseTree); | ||||||
|  |  | ||||||
|     function scrollToCurrentNote() { |     function scrollToCurrentNote() { | ||||||
|         const node = treeUtils.getNodeByKey(noteEditor.getCurrentNoteId()); |         const node = treeUtils.getNodeByNoteTreeId(noteEditor.getCurrentNoteId()); | ||||||
|  |  | ||||||
|         if (node) { |         if (node) { | ||||||
|             node.makeVisible({scrollIntoView: true}); |             node.makeVisible({scrollIntoView: true}); | ||||||
| @@ -281,6 +310,22 @@ const noteTree = (function() { | |||||||
|         return notesMap[noteId]; |         return notesMap[noteId]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // note that if you want to access data like note_id or is_protected, you need to go into "data" property | ||||||
|  |     function getCurrentNode() { | ||||||
|  |         return treeEl.fancytree("getActiveNode"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function getCurrentNoteTreeId() { | ||||||
|  |         const node = getCurrentNode(); | ||||||
|  |         return node.note_tree_id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function setCurrentNoteTreeBasedOnProtectedStatus() { | ||||||
|  |         const node = getCurrentNode(); | ||||||
|  |  | ||||||
|  |         node.toggleClass("protected", !!node.data.is_protected); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     $("button#reset-search-button").click(resetSearch); |     $("button#reset-search-button").click(resetSearch); | ||||||
|  |  | ||||||
|     $("input[name=search]").keyup(e => { |     $("input[name=search]").keyup(e => { | ||||||
| @@ -308,12 +353,17 @@ const noteTree = (function() { | |||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         getTreeLoadTime, |         getTreeLoadTime, | ||||||
|         getClipboardNoteId, |         getClipboardNoteTreeId, | ||||||
|         setClipboardNoteId, |         setClipboardNoteTreeId, | ||||||
|         reload, |         reload, | ||||||
|         collapseTree, |         collapseTree, | ||||||
|         scrollToCurrentNote, |         scrollToCurrentNote, | ||||||
|         toggleSearch, |         toggleSearch, | ||||||
|         getByNoteId |         getByNoteId, | ||||||
|  |         getKeyFromNoteTreeId, | ||||||
|  |         getNoteTreeIdFromKey, | ||||||
|  |         setCurrentNoteTreeBasedOnProtectedStatus, | ||||||
|  |         getCurrentNode, | ||||||
|  |         getCurrentNoteTreeId, | ||||||
|     }; |     }; | ||||||
| })(); | })(); | ||||||
| @@ -37,7 +37,7 @@ const protected_session = (function() { | |||||||
|                 open: () => { |                 open: () => { | ||||||
|                     if (!modal) { |                     if (!modal) { | ||||||
|                         // dialog steals focus for itself, which is not what we want for non-modal (viewing) |                         // dialog steals focus for itself, which is not what we want for non-modal (viewing) | ||||||
|                         treeUtils.getNodeByKey(noteEditor.getCurrentNoteId()).setFocus(); |                         noteTree.getCurrentNode().setFocus(); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ const treeChanges = (function() { | |||||||
|  |  | ||||||
|                     glob.allNoteIds = glob.allNoteIds.filter(e => e !== node.key); |                     glob.allNoteIds = glob.allNoteIds.filter(e => e !== node.key); | ||||||
|  |  | ||||||
|                     recentNotes.removeRecentNote(node.key); |                     recentNotes.removeRecentNote(node.note_tree_id); | ||||||
|  |  | ||||||
|                     let next = node.getNextSibling(); |                     let next = node.getNextSibling(); | ||||||
|                     if (!next) { |                     if (!next) { | ||||||
|   | |||||||
| @@ -3,37 +3,41 @@ | |||||||
| const treeUtils = (function() { | const treeUtils = (function() { | ||||||
|     const treeEl = $("#tree"); |     const treeEl = $("#tree"); | ||||||
|  |  | ||||||
|     function getParentKey(node) { |     function getParentNoteTreeId(node) { | ||||||
|         return (node.getParent() === null || node.getParent().key === "root_1") ? "root" : node.getParent().key; |         return node.note_pid; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getParentProtectedStatus(node) { |     function getParentProtectedStatus(node) { | ||||||
|         return node.getParent() === null ? 0 : node.getParent().data.is_protected; |         return node.getParent() === null ? 0 : node.getParent().data.is_protected; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getNodeByKey(noteId) { |     function getNodeByKey(key) { | ||||||
|         return treeEl.fancytree('getNodeByKey', noteId); |         return treeEl.fancytree('getNodeByKey', key); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function activateNode(noteIdToActivate) { |     function getNodeByNoteTreeId(noteTreeId) { | ||||||
|         const noteIdPath = [ noteIdToActivate ]; |         const key = noteTree.getKeyFromNoteTreeId(noteTreeId); | ||||||
|  |  | ||||||
|         let note = noteTree.getByNoteId(noteIdToActivate); |         return getNodeByKey(key); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async function activateNode(noteTreeIdToActivate) { | ||||||
|  |         const noteTreeIdPath = [ noteTreeIdToActivate ]; | ||||||
|  |  | ||||||
|  |         let note = noteTree.getByNoteId(noteTreeIdToActivate); | ||||||
|  |  | ||||||
|         while (note) { |         while (note) { | ||||||
|             if (note.note_pid !== 'root') { |             if (note.note_pid !== 'root') { | ||||||
|                 noteIdPath.push(note.note_pid); |                 noteTreeIdPath.push(note.note_pid); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             note = noteTree.getByNoteId(note.note_pid); |             note = noteTree.getByNoteId(note.note_pid); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         for (const noteId of noteIdPath.reverse()) { |         for (const noteTreeId of noteTreeIdPath.reverse()) { | ||||||
|             console.log("Activating/expanding " + noteId); |             const node = treeUtils.getNodeByNoteTreeId(noteTreeId); | ||||||
|  |  | ||||||
|             const node = treeUtils.getNodeByKey(noteId); |             if (noteTreeId !== noteTreeIdToActivate) { | ||||||
|  |  | ||||||
|             if (noteId !== noteIdToActivate) { |  | ||||||
|                 await node.setExpanded(); |                 await node.setExpanded(); | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
| @@ -57,8 +61,8 @@ const treeUtils = (function() { | |||||||
|         return noteTitle; |         return noteTitle; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getFullName(noteId) { |     function getFullName(noteTreeId) { | ||||||
|         let note = noteTree.getByNoteId(noteId); |         let note = noteTree.getByNoteId(noteTreeId); | ||||||
|  |  | ||||||
|         if (note === null) { |         if (note === null) { | ||||||
|             return "[unknown]"; |             return "[unknown]"; | ||||||
| @@ -76,9 +80,10 @@ const treeUtils = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         getParentKey, |         getParentNoteTreeId, | ||||||
|         getParentProtectedStatus, |         getParentProtectedStatus, | ||||||
|         getNodeByKey, |         getNodeByKey, | ||||||
|  |         getNodeByNoteTreeId, | ||||||
|         activateNode, |         activateNode, | ||||||
|         getNoteTitle, |         getNoteTitle, | ||||||
|         getFullName |         getFullName | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ const express = require('express'); | |||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| const auth = require('../../services/auth'); | const auth = require('../../services/auth'); | ||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const options = require('../../services/options'); |  | ||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
| const notes = require('../../services/notes'); | const notes = require('../../services/notes'); | ||||||
| const protected_session = require('../../services/protected_session'); | const protected_session = require('../../services/protected_session'); | ||||||
| @@ -14,33 +13,32 @@ const RequestContext = require('../../services/request_context'); | |||||||
| router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { | router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { | ||||||
|     const noteId = req.params.noteId; |     const noteId = req.params.noteId; | ||||||
|  |  | ||||||
|     await options.setOption('start_node', noteId); |  | ||||||
|  |  | ||||||
|     const detail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); |     const detail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); | ||||||
|  |  | ||||||
|     if (detail.is_protected) { |     if (detail.is_protected) { | ||||||
|         const dataKey = protected_session.getDataKey(req); |         const dataKey = protected_session.getDataKey(req); | ||||||
|  |  | ||||||
|         detail.note_title = data_encryption.decryptString(dataKey, data_encryption.noteTitleIv(noteId), detail.note_title); |         detail.note_title = data_encryption.decryptString(dataKey, data_encryption.noteTitleIv(detail.note_id), detail.note_title); | ||||||
|         detail.note_text = data_encryption.decryptString(dataKey, data_encryption.noteTextIv(noteId), detail.note_text); |         detail.note_text = data_encryption.decryptString(dataKey, data_encryption.noteTextIv(detail.note_id), detail.note_text); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     res.send({ |     res.send({ | ||||||
|         detail: detail, |         detail: detail, | ||||||
|         images: await sql.getResults("select * from images where note_id = ? order by note_offset", [noteId]), |         images: await sql.getResults("select * from images where note_id = ? order by note_offset", [detail.note_id]), | ||||||
|         loadTime: utils.nowTimestamp() |         loadTime: utils.nowTimestamp() | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| router.post('/:parentNoteId/children', async (req, res, next) => { | router.post('/:parentNoteTreeId/children', async (req, res, next) => { | ||||||
|     const parentNoteId = req.params.parentNoteId; |     const parentNoteTreeId = req.params.parentNoteTreeId; | ||||||
|     const browserId = utils.browserId(req); |     const browserId = utils.browserId(req); | ||||||
|     const note = req.body; |     const note = req.body; | ||||||
|  |  | ||||||
|     const noteId = await notes.createNewNote(parentNoteId, note, browserId); |     const { noteId, noteTreeId } = await notes.createNewNote(parentNoteTreeId, note, browserId); | ||||||
|  |  | ||||||
|     res.send({ |     res.send({ | ||||||
|         'note_id': noteId |         'note_id': noteId, | ||||||
|  |         'note_tree_id': noteTreeId | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,27 +6,32 @@ const sql = require('../../services/sql'); | |||||||
| const auth = require('../../services/auth'); | const auth = require('../../services/auth'); | ||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
| const sync_table = require('../../services/sync_table'); | const sync_table = require('../../services/sync_table'); | ||||||
|  | const options = require('../../services/options'); | ||||||
|  |  | ||||||
| router.get('', auth.checkApiAuth, async (req, res, next) => { | router.get('', auth.checkApiAuth, async (req, res, next) => { | ||||||
|     res.send(await getRecentNotes()); |     res.send(await getRecentNotes()); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| router.put('/:noteId', auth.checkApiAuth, async (req, res, next) => { | router.put('/:noteTreeId', auth.checkApiAuth, async (req, res, next) => { | ||||||
|  |     const noteTreeId = req.params.noteTreeId; | ||||||
|  |  | ||||||
|     await sql.replace('recent_notes', { |     await sql.replace('recent_notes', { | ||||||
|         note_id: req.params.noteId, |         note_tree_id: noteTreeId, | ||||||
|         date_accessed: utils.nowTimestamp(), |         date_accessed: utils.nowTimestamp(), | ||||||
|         is_deleted: 0 |         is_deleted: 0 | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     await sync_table.addRecentNoteSync(req.params.noteId); |     await sync_table.addRecentNoteSync(noteTreeId); | ||||||
|  |  | ||||||
|  |     await options.setOption('start_note_tree_id', noteTreeId); | ||||||
|  |  | ||||||
|     res.send(await getRecentNotes()); |     res.send(await getRecentNotes()); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| router.delete('/:noteId', auth.checkApiAuth, async (req, res, next) => { | router.delete('/:noteTreeId', auth.checkApiAuth, async (req, res, next) => { | ||||||
|     await sql.execute('UPDATE recent_notes SET is_deleted = 1 WHERE note_id = ?', [req.params.noteId]); |     await sql.execute('UPDATE recent_notes SET is_deleted = 1 WHERE note_tree_id = ?', [req.params.noteTreeId]); | ||||||
|  |  | ||||||
|     await sync_table.addRecentNoteSync(req.params.noteId); |     await sync_table.addRecentNoteSync(req.params.noteTreeId); | ||||||
|  |  | ||||||
|     res.send(await getRecentNotes()); |     res.send(await getRecentNotes()); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -21,28 +21,17 @@ router.get('/', auth.checkApiAuth, async (req, res, next) => { | |||||||
|         + "where notes.is_deleted = 0 and notes_tree.is_deleted = 0 " |         + "where notes.is_deleted = 0 and notes_tree.is_deleted = 0 " | ||||||
|         + "order by note_pid, note_pos"); |         + "order by note_pid, note_pos"); | ||||||
|  |  | ||||||
|     const parentToNotes = {}; |  | ||||||
|     const notesMap = {}; |  | ||||||
|  |  | ||||||
|     const dataKey = protected_session.getDataKey(req); |     const dataKey = protected_session.getDataKey(req); | ||||||
|  |  | ||||||
|     for (const note of notes) { |     for (const note of notes) { | ||||||
|         if (note.is_protected) { |         if (note.is_protected) { | ||||||
|             note.note_title = data_encryption.decryptString(dataKey, data_encryption.noteTitleIv(note.note_id), note.note_title); |             note.note_title = data_encryption.decryptString(dataKey, data_encryption.noteTitleIv(note.note_id), note.note_title); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!parentToNotes[note.note_pid]) { |  | ||||||
|             parentToNotes[note.note_pid] = []; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         notesMap[note.note_id] = note; |  | ||||||
|         parentToNotes[note.note_pid].push(note.note_id); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     res.send({ |     res.send({ | ||||||
|         notes_map: notesMap, |         notes: notes, | ||||||
|         parent_to_notes: parentToNotes, |         start_note_tree_id: await options.getOption('start_note_tree_id'), | ||||||
|         start_note_id: await options.getOption('start_node'), |  | ||||||
|         tree_load_time: utils.nowTimestamp() |         tree_load_time: utils.nowTimestamp() | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ const options = require('./options'); | |||||||
| const fs = require('fs-extra'); | const fs = require('fs-extra'); | ||||||
| const log = require('./log'); | const log = require('./log'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 36; | const APP_DB_VERSION = 39; | ||||||
| const MIGRATIONS_DIR = "migrations"; | const MIGRATIONS_DIR = "migrations"; | ||||||
|  |  | ||||||
| async function migrate() { | async function migrate() { | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ const sync_table = require('./sync_table'); | |||||||
|  |  | ||||||
| async function createNewNote(parentNoteId, note, browserId) { | async function createNewNote(parentNoteId, note, browserId) { | ||||||
|     const noteId = utils.newNoteId(); |     const noteId = utils.newNoteId(); | ||||||
|  |     const noteTreeId = utils.newNoteId(); | ||||||
|  |  | ||||||
|     let newNotePos = 0; |     let newNotePos = 0; | ||||||
|  |  | ||||||
| @@ -50,6 +51,7 @@ async function createNewNote(parentNoteId, note, browserId) { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         await sql.insert("notes_tree", { |         await sql.insert("notes_tree", { | ||||||
|  |             'note_tree_id': noteTreeId, | ||||||
|             'note_id': noteId, |             'note_id': noteId, | ||||||
|             'note_pid': parentNoteId, |             'note_pid': parentNoteId, | ||||||
|             'note_pos': newNotePos, |             'note_pos': newNotePos, | ||||||
| @@ -58,7 +60,11 @@ async function createNewNote(parentNoteId, note, browserId) { | |||||||
|             'is_deleted': 0 |             'is_deleted': 0 | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|     return noteId; |  | ||||||
|  |     return { | ||||||
|  |         noteId, | ||||||
|  |         noteTreeId | ||||||
|  |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function encryptNote(note, ctx) { | async function encryptNote(note, ctx) { | ||||||
|   | |||||||
| @@ -22,8 +22,8 @@ async function addOptionsSync(optName, sourceId) { | |||||||
|     await addEntitySync("options", optName, sourceId); |     await addEntitySync("options", optName, sourceId); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function addRecentNoteSync(noteId, sourceId) { | async function addRecentNoteSync(noteTreeId, sourceId) { | ||||||
|     await addEntitySync("recent_notes", noteId, sourceId); |     await addEntitySync("recent_notes", noteTreeId, sourceId); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function addEntitySync(entityName, entityId, sourceId) { | async function addEntitySync(entityName, entityId, sourceId) { | ||||||
|   | |||||||
| @@ -137,10 +137,10 @@ | |||||||
|       </form> |       </form> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <div id="protected-session-password-dialog" title="Encrypted note" style="display: none;"> |     <div id="protected-session-password-dialog" title="Protected session" style="display: none;"> | ||||||
|       <form id="protected-session-password-form"> |       <form id="protected-session-password-form"> | ||||||
|         <div class="form-group"> |         <div class="form-group"> | ||||||
|           <label for="protected-session-password">This note is encrypted. Enter password to show it:</label> |           <label for="protected-session-password">To proceed with requested action you need to enter protected session by entering password:</label> | ||||||
|           <input id="protected-session-password" style="width: 250px;" type="password"> |           <input id="protected-session-password" style="width: 250px;" type="password"> | ||||||
|           <button class="btn btn-sm">Show</button> |           <button class="btn btn-sm">Show</button> | ||||||
|         </div> |         </div> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user