mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	basic implementation of note tree's config
This commit is contained in:
		
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "version": "0.41.5", |   "version": "0.41.6", | ||||||
|   "lockfileVersion": 1, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
| @@ -2832,9 +2832,9 @@ | |||||||
|       "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" |       "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" | ||||||
|     }, |     }, | ||||||
|     "dayjs": { |     "dayjs": { | ||||||
|       "version": "1.8.25", |       "version": "1.8.26", | ||||||
|       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.25.tgz", |       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.26.tgz", | ||||||
|       "integrity": "sha512-Pk36juDfQQGDCgr0Lqd1kw15w3OS6xt21JaLPE3lCfsEf8KrERGwDNwvK1tRjrjqFC0uZBJncT4smZQ4F+uV5g==" |       "integrity": "sha512-KqtAuIfdNfZR5sJY1Dixr2Is4ZvcCqhb0dZpCOt5dGEFiMzoIbjkTSzUb4QKTCsP+WNpGwUjAFIZrnZvUxxkhw==" | ||||||
|     }, |     }, | ||||||
|     "debug": { |     "debug": { | ||||||
|       "version": "4.1.1", |       "version": "4.1.1", | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ | |||||||
|     "commonmark": "0.29.1", |     "commonmark": "0.29.1", | ||||||
|     "cookie-parser": "1.4.5", |     "cookie-parser": "1.4.5", | ||||||
|     "csurf": "1.11.0", |     "csurf": "1.11.0", | ||||||
|     "dayjs": "1.8.25", |     "dayjs": "1.8.26", | ||||||
|     "debug": "4.1.1", |     "debug": "4.1.1", | ||||||
|     "ejs": "3.1.2", |     "ejs": "3.1.2", | ||||||
|     "electron-debug": "3.0.1", |     "electron-debug": "3.0.1", | ||||||
|   | |||||||
| @@ -103,7 +103,7 @@ export default class DesktopMainWindowLayout { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     getRootWidget(appContext) { |     getRootWidget(appContext) { | ||||||
|         appContext.mainTreeWidget = new NoteTreeWidget(); |         appContext.mainTreeWidget = new NoteTreeWidget("main"); | ||||||
|  |  | ||||||
|         return new FlexContainer('column') |         return new FlexContainer('column') | ||||||
|             .setParent(appContext) |             .setParent(appContext) | ||||||
|   | |||||||
| @@ -73,7 +73,7 @@ export default class MobileLayout { | |||||||
|             .child(new ScreenContainer("tree", 'column') |             .child(new ScreenContainer("tree", 'column') | ||||||
|                 .class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-5 col-md-4 col-lg-4 col-xl-4") |                 .class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-5 col-md-4 col-lg-4 col-xl-4") | ||||||
|                 .child(new MobileGlobalButtonsWidget()) |                 .child(new MobileGlobalButtonsWidget()) | ||||||
|                 .child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS))) |                 .child(new NoteTreeWidget("main").cssBlock(FANCYTREE_CSS))) | ||||||
|             .child(new ScreenContainer("detail", "column") |             .child(new ScreenContainer("detail", "column") | ||||||
|                 .class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-8") |                 .class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-8") | ||||||
|                 .child(new FlexContainer('row') |                 .child(new FlexContainer('row') | ||||||
|   | |||||||
| @@ -1,188 +0,0 @@ | |||||||
| import utils from "./utils.js"; |  | ||||||
| import treeCache from "./tree_cache.js"; |  | ||||||
| import ws from "./ws.js"; |  | ||||||
| import hoistedNoteService from "./hoisted_note.js"; |  | ||||||
|  |  | ||||||
| async function prepareRootNode() { |  | ||||||
|     await treeCache.initializedPromise; |  | ||||||
|  |  | ||||||
|     const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); |  | ||||||
|  |  | ||||||
|     let hoistedBranch; |  | ||||||
|  |  | ||||||
|     if (hoistedNoteId === 'root') { |  | ||||||
|         hoistedBranch = treeCache.getBranch('root'); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         const hoistedNote = await treeCache.getNote(hoistedNoteId); |  | ||||||
|         hoistedBranch = (await hoistedNote.getBranches())[0]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return await prepareNode(hoistedBranch); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function prepareChildren(note) { |  | ||||||
|     if (note.type === 'search') { |  | ||||||
|         return await prepareSearchNoteChildren(note); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         return await prepareNormalNoteChildren(note); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const NOTE_TYPE_ICONS = { |  | ||||||
|     "file": "bx bx-file", |  | ||||||
|     "image": "bx bx-image", |  | ||||||
|     "code": "bx bx-code", |  | ||||||
|     "render": "bx bx-extension", |  | ||||||
|     "search": "bx bx-file-find", |  | ||||||
|     "relation-map": "bx bx-map-alt", |  | ||||||
|     "book": "bx bx-book" |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| function getIconClass(note) { |  | ||||||
|     const labels = note.getLabels('iconClass'); |  | ||||||
|  |  | ||||||
|     return labels.map(l => l.value).join(' '); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getIcon(note) { |  | ||||||
|     const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); |  | ||||||
|  |  | ||||||
|     const iconClass = getIconClass(note); |  | ||||||
|  |  | ||||||
|     if (iconClass) { |  | ||||||
|         return iconClass; |  | ||||||
|     } |  | ||||||
|     else if (note.noteId === 'root') { |  | ||||||
|         return "bx bx-chevrons-right"; |  | ||||||
|     } |  | ||||||
|     else if (note.noteId === hoistedNoteId) { |  | ||||||
|         return "bx bxs-arrow-from-bottom"; |  | ||||||
|     } |  | ||||||
|     else if (note.type === 'text') { |  | ||||||
|         if (note.hasChildren()) { |  | ||||||
|             return "bx bx-folder"; |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             return "bx bx-note"; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         return NOTE_TYPE_ICONS[note.type]; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function prepareNode(branch) { |  | ||||||
|     const note = await branch.getNote(); |  | ||||||
|  |  | ||||||
|     if (!note) { |  | ||||||
|         throw new Error(`Branch has no note ` + branch.noteId); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; |  | ||||||
|     const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); |  | ||||||
|  |  | ||||||
|     const node = { |  | ||||||
|         noteId: note.noteId, |  | ||||||
|         parentNoteId: branch.parentNoteId, |  | ||||||
|         branchId: branch.branchId, |  | ||||||
|         isProtected: note.isProtected, |  | ||||||
|         noteType: note.type, |  | ||||||
|         title: utils.escapeHtml(title), |  | ||||||
|         extraClasses: getExtraClasses(note), |  | ||||||
|         icon: getIcon(note), |  | ||||||
|         refKey: note.noteId, |  | ||||||
|         expanded: branch.isExpanded || hoistedNoteId === note.noteId, |  | ||||||
|         key: utils.randomString(12) // this should prevent some "duplicate key" errors |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     const childBranches = getChildBranchesWithoutImages(note); |  | ||||||
|  |  | ||||||
|     node.folder = childBranches.length > 0 |  | ||||||
|                || note.type === 'search' |  | ||||||
|  |  | ||||||
|     node.lazy = node.folder && !node.expanded; |  | ||||||
|  |  | ||||||
|     if (node.folder && node.expanded) { |  | ||||||
|         node.children = await prepareChildren(note); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return node; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function prepareNormalNoteChildren(parentNote) { |  | ||||||
|     utils.assertArguments(parentNote); |  | ||||||
|  |  | ||||||
|     const noteList = []; |  | ||||||
|  |  | ||||||
|     for (const branch of getChildBranchesWithoutImages(parentNote)) { |  | ||||||
|         const node = await prepareNode(branch); |  | ||||||
|  |  | ||||||
|         noteList.push(node); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return noteList; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getChildBranchesWithoutImages(parentNote) { |  | ||||||
|     const childBranches = parentNote.getChildBranches(); |  | ||||||
|  |  | ||||||
|     if (!childBranches) { |  | ||||||
|         ws.logError(`No children for ${parentNote}. This shouldn't happen.`); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const imageLinks = parentNote.getRelations('imageLink'); |  | ||||||
|  |  | ||||||
|     // image is already visible in the parent note so no need to display it separately in the book |  | ||||||
|     return childBranches.filter(branch => !imageLinks.find(rel => rel.value === branch.noteId)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function prepareSearchNoteChildren(note) { |  | ||||||
|     await treeCache.reloadNotes([note.noteId]); |  | ||||||
|  |  | ||||||
|     const newNote = await treeCache.getNote(note.noteId); |  | ||||||
|  |  | ||||||
|     return await prepareNormalNoteChildren(newNote); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getExtraClasses(note) { |  | ||||||
|     utils.assertArguments(note); |  | ||||||
|  |  | ||||||
|     const extraClasses = []; |  | ||||||
|  |  | ||||||
|     if (note.isProtected) { |  | ||||||
|         extraClasses.push("protected"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (note.getParentNoteIds().length > 1) { |  | ||||||
|         extraClasses.push("multiple-parents"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const cssClass = note.getCssClass(); |  | ||||||
|  |  | ||||||
|     if (cssClass) { |  | ||||||
|         extraClasses.push(cssClass); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     extraClasses.push(utils.getNoteTypeClass(note.type)); |  | ||||||
|  |  | ||||||
|     if (note.mime) { // some notes should not have mime type (e.g. render) |  | ||||||
|         extraClasses.push(utils.getMimeTypeClass(note.mime)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (note.hasLabel('archived')) { |  | ||||||
|         extraClasses.push("archived"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return extraClasses.join(" "); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export default { |  | ||||||
|     prepareRootNode, |  | ||||||
|     prepareBranch: prepareChildren, |  | ||||||
|     getExtraClasses, |  | ||||||
|     getIcon, |  | ||||||
|     getChildBranchesWithoutImages |  | ||||||
| } |  | ||||||
| @@ -3,7 +3,6 @@ import treeService from "../services/tree.js"; | |||||||
| import utils from "../services/utils.js"; | import utils from "../services/utils.js"; | ||||||
| import contextMenu from "../services/context_menu.js"; | import contextMenu from "../services/context_menu.js"; | ||||||
| import treeCache from "../services/tree_cache.js"; | import treeCache from "../services/tree_cache.js"; | ||||||
| import treeBuilder from "../services/tree_builder.js"; |  | ||||||
| import branchService from "../services/branches.js"; | import branchService from "../services/branches.js"; | ||||||
| import ws from "../services/ws.js"; | import ws from "../services/ws.js"; | ||||||
| import TabAwareWidget from "./tab_aware_widget.js"; | import TabAwareWidget from "./tab_aware_widget.js"; | ||||||
| @@ -15,17 +14,24 @@ import keyboardActionsService from "../services/keyboard_actions.js"; | |||||||
| import clipboard from "../services/clipboard.js"; | import clipboard from "../services/clipboard.js"; | ||||||
| import protectedSessionService from "../services/protected_session.js"; | import protectedSessionService from "../services/protected_session.js"; | ||||||
| import syncService from "../services/sync.js"; | import syncService from "../services/sync.js"; | ||||||
|  | import options from "../services/options.js"; | ||||||
|  |  | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="tree"> | <div class="tree-wrapper"> | ||||||
|     <style> |     <style> | ||||||
|     .tree { |     .tree-wrapper { | ||||||
|         overflow: auto; |  | ||||||
|         flex-grow: 1; |         flex-grow: 1; | ||||||
|         flex-shrink: 1; |         flex-shrink: 1; | ||||||
|         flex-basis: 60%; |         flex-basis: 60%; | ||||||
|         font-family: var(--tree-font-family); |         font-family: var(--tree-font-family); | ||||||
|         font-size: var(--tree-font-size); |         font-size: var(--tree-font-size); | ||||||
|  |         position: relative; | ||||||
|  |         min-height: 0; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     .tree { | ||||||
|  |         height: 100%; | ||||||
|  |         overflow: auto; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     .refresh-search-button { |     .refresh-search-button { | ||||||
| @@ -40,19 +46,79 @@ const TPL = ` | |||||||
|     .refresh-search-button:hover { |     .refresh-search-button:hover { | ||||||
|         border-color: var(--button-border-color); |         border-color: var(--button-border-color); | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  |     .tree-settings-button { | ||||||
|  |         position: absolute; | ||||||
|  |         top: 10px; | ||||||
|  |         right: 20px; | ||||||
|  |         z-index: 1000; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     .tree-settings-popup { | ||||||
|  |         display: none;  | ||||||
|  |         position: absolute;  | ||||||
|  |         background-color: var(--accented-background-color);  | ||||||
|  |         border: 1px solid var(--main-border-color);  | ||||||
|  |         padding: 20px;  | ||||||
|  |         z-index: 1000; | ||||||
|  |         width: 300px;  | ||||||
|  |         border-radius: 10px 0 10px 10px; | ||||||
|  |     } | ||||||
|     </style> |     </style> | ||||||
|  |      | ||||||
|  |     <button class="btn btn-sm icon-button bx bx-cog tree-settings-button" title="Tree settings"></button> | ||||||
|  |      | ||||||
|  |     <div class="tree-settings-popup"> | ||||||
|  |         <div class="form-check"> | ||||||
|  |             <label class="form-check-label"> | ||||||
|  |                 <input class="form-check-input hide-archived-notes" type="checkbox" value=""> | ||||||
|  |              | ||||||
|  |                 Hide archived notes | ||||||
|  |             </label> | ||||||
|  |         </div> | ||||||
|  |         <div class="form-check"> | ||||||
|  |             <label class="form-check-label"> | ||||||
|  |                 <input class="form-check-input hide-included-images" type="checkbox" value=""> | ||||||
|  |                  | ||||||
|  |                 Hide images included in a note | ||||||
|  |             </label> | ||||||
|  |         </div> | ||||||
|  |      | ||||||
|  |         <br/> | ||||||
|  |      | ||||||
|  |         <button class="btn btn-sm btn-primary save-tree-settings-button" type="submit">Save & apply changes</button> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <div class="tree"></div> | ||||||
| </div> | </div> | ||||||
| `; | `; | ||||||
|  |  | ||||||
|  | const NOTE_TYPE_ICONS = { | ||||||
|  |     "file": "bx bx-file", | ||||||
|  |     "image": "bx bx-image", | ||||||
|  |     "code": "bx bx-code", | ||||||
|  |     "render": "bx bx-extension", | ||||||
|  |     "search": "bx bx-file-find", | ||||||
|  |     "relation-map": "bx bx-map-alt", | ||||||
|  |     "book": "bx bx-book" | ||||||
|  | }; | ||||||
|  |  | ||||||
| export default class NoteTreeWidget extends TabAwareWidget { | export default class NoteTreeWidget extends TabAwareWidget { | ||||||
|  |     constructor(treeName) { | ||||||
|  |         super(); | ||||||
|  |  | ||||||
|  |         this.treeName = treeName; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|  |         this.$tree = this.$widget.find('.tree'); | ||||||
|  |  | ||||||
|         this.$widget.on("click", ".unhoist-button", hoistedNoteService.unhoist); |         this.$tree.on("click", ".unhoist-button", hoistedNoteService.unhoist); | ||||||
|         this.$widget.on("click", ".refresh-search-button", () => this.refreshSearch()); |         this.$tree.on("click", ".refresh-search-button", () => this.refreshSearch()); | ||||||
|  |  | ||||||
|         // fancytree doesn't support middle click so this is a way to support it |         // fancytree doesn't support middle click so this is a way to support it | ||||||
|         this.$widget.on('mousedown', '.fancytree-title', e => { |         this.$tree.on('mousedown', '.fancytree-title', e => { | ||||||
|             if (e.which === 2) { |             if (e.which === 2) { | ||||||
|                 const node = $.ui.fancytree.getNode(e); |                 const node = $.ui.fancytree.getNode(e); | ||||||
|  |  | ||||||
| @@ -67,20 +133,76 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         this.$treeSettingsPopup = this.$widget.find('.tree-settings-popup'); | ||||||
|  |         this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find('.hide-archived-notes'); | ||||||
|  |         this.$hideIncludedImages = this.$treeSettingsPopup.find('.hide-included-images'); | ||||||
|  |  | ||||||
|  |         this.$treeSettingsButton = this.$widget.find('.tree-settings-button'); | ||||||
|  |         this.$treeSettingsButton.on("click", e => { | ||||||
|  |             if (this.$treeSettingsPopup.is(":visible")) { | ||||||
|  |                 this.$treeSettingsPopup.hide(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes); | ||||||
|  |             this.$hideIncludedImages.prop("checked", this.hideIncludedImages); | ||||||
|  |  | ||||||
|  |             let top = this.$treeSettingsButton[0].offsetTop; | ||||||
|  |             let left = this.$treeSettingsButton[0].offsetLeft; | ||||||
|  |             top += this.$treeSettingsButton.outerHeight(); | ||||||
|  |             left += this.$treeSettingsButton.outerWidth() - this.$treeSettingsPopup.outerWidth(); | ||||||
|  |  | ||||||
|  |             if (left < 0) { | ||||||
|  |                 left = 0; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.$treeSettingsPopup.css({ | ||||||
|  |                 display: "block", | ||||||
|  |                 top: top, | ||||||
|  |                 left: left | ||||||
|  |             }).addClass("show"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         this.$saveTreeSettingsButton = this.$treeSettingsPopup.find('.save-tree-settings-button'); | ||||||
|  |         this.$saveTreeSettingsButton.on('click', async () => { | ||||||
|  |             await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked")); | ||||||
|  |             await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked")); | ||||||
|  |  | ||||||
|  |             this.$treeSettingsPopup.hide(); | ||||||
|  |  | ||||||
|  |             this.reloadTreeFromCache(); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|         this.initialized = this.initFancyTree(); |         this.initialized = this.initFancyTree(); | ||||||
|  |  | ||||||
|         return this.$widget; |         return this.$widget; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async initFancyTree() { |     get hideArchivedNotes() { | ||||||
|         const treeData = [await treeBuilder.prepareRootNode()]; |         return options.is("hideArchivedNotes_" + this.treeName); | ||||||
|  |     } | ||||||
|  |  | ||||||
|         this.$widget.fancytree({ |     async setHideArchivedNotes(val) { | ||||||
|  |         await options.save("hideArchivedNotes_" + this.treeName, val.toString()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     get hideIncludedImages() { | ||||||
|  |         return options.is("hideIncludedImages_" + this.treeName); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async setHideIncludedImages(val) { | ||||||
|  |         await options.save("hideIncludedImages_" + this.treeName, val.toString()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async initFancyTree() { | ||||||
|  |         const treeData = [await this.prepareRootNode()]; | ||||||
|  |  | ||||||
|  |         this.$tree.fancytree({ | ||||||
|             autoScroll: true, |             autoScroll: true, | ||||||
|             keyboard: false, // we takover keyboard handling in the hotkeys plugin |             keyboard: false, // we takover keyboard handling in the hotkeys plugin | ||||||
|             extensions: utils.isMobile() ? ["dnd5", "clones"] : ["hotkeys", "dnd5", "clones"], |             extensions: utils.isMobile() ? ["dnd5", "clones"] : ["hotkeys", "dnd5", "clones"], | ||||||
|             source: treeData, |             source: treeData, | ||||||
|             scrollParent: this.$widget, |             scrollParent: this.$tree, | ||||||
|             minExpandLevel: 2, // root can't be collapsed |             minExpandLevel: 2, // root can't be collapsed | ||||||
|             click: (event, data) => { |             click: (event, data) => { | ||||||
|                 const targetType = data.targetType; |                 const targetType = data.targetType; | ||||||
| @@ -191,10 +313,10 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             lazyLoad: function(event, data) { |             lazyLoad: (event, data) => { | ||||||
|                 const noteId = data.node.data.noteId; |                 const noteId = data.node.data.noteId; | ||||||
|  |  | ||||||
|                 data.result = treeCache.getNote(noteId).then(note => treeBuilder.prepareBranch(note)); |                 data.result = treeCache.getNote(noteId).then(note => this.prepareChildren(note)); | ||||||
|             }, |             }, | ||||||
|             clones: { |             clones: { | ||||||
|                 highlightActiveClones: true |                 highlightActiveClones: true | ||||||
| @@ -236,7 +358,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         this.$widget.on('contextmenu', '.fancytree-node', e => { |         this.$tree.on('contextmenu', '.fancytree-node', e => { | ||||||
|             const node = $.ui.fancytree.getNode(e); |             const node = $.ui.fancytree.getNode(e); | ||||||
|  |  | ||||||
|             import("../services/tree_context_menu.js").then(({default: TreeContextMenu}) => { |             import("../services/tree_context_menu.js").then(({default: TreeContextMenu}) => { | ||||||
| @@ -247,7 +369,191 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|             return false; // blocks default browser right click menu |             return false; // blocks default browser right click menu | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         this.tree = $.ui.fancytree.getTree(this.$widget); |         this.tree = $.ui.fancytree.getTree(this.$tree); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async prepareRootNode() { | ||||||
|  |         await treeCache.initializedPromise; | ||||||
|  |  | ||||||
|  |         const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); | ||||||
|  |  | ||||||
|  |         let hoistedBranch; | ||||||
|  |  | ||||||
|  |         if (hoistedNoteId === 'root') { | ||||||
|  |             hoistedBranch = treeCache.getBranch('root'); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             const hoistedNote = await treeCache.getNote(hoistedNoteId); | ||||||
|  |             hoistedBranch = (await hoistedNote.getBranches())[0]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return await this.prepareNode(hoistedBranch); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async prepareChildren(note) { | ||||||
|  |         if (note.type === 'search') { | ||||||
|  |             return await this.prepareSearchNoteChildren(note); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             return await this.prepareNormalNoteChildren(note); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getIconClass(note) { | ||||||
|  |         const labels = note.getLabels('iconClass'); | ||||||
|  |  | ||||||
|  |         return labels.map(l => l.value).join(' '); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getIcon(note) { | ||||||
|  |         const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); | ||||||
|  |  | ||||||
|  |         const iconClass = this.getIconClass(note); | ||||||
|  |  | ||||||
|  |         if (iconClass) { | ||||||
|  |             return iconClass; | ||||||
|  |         } | ||||||
|  |         else if (note.noteId === 'root') { | ||||||
|  |             return "bx bx-chevrons-right"; | ||||||
|  |         } | ||||||
|  |         else if (note.noteId === hoistedNoteId) { | ||||||
|  |             return "bx bxs-arrow-from-bottom"; | ||||||
|  |         } | ||||||
|  |         else if (note.type === 'text') { | ||||||
|  |             if (note.hasChildren()) { | ||||||
|  |                 return "bx bx-folder"; | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 return "bx bx-note"; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             return NOTE_TYPE_ICONS[note.type]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async prepareNode(branch) { | ||||||
|  |         const note = await branch.getNote(); | ||||||
|  |  | ||||||
|  |         if (!note) { | ||||||
|  |             throw new Error(`Branch has no note ` + branch.noteId); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; | ||||||
|  |         const hoistedNoteId = hoistedNoteService.getHoistedNoteId(); | ||||||
|  |  | ||||||
|  |         const node = { | ||||||
|  |             noteId: note.noteId, | ||||||
|  |             parentNoteId: branch.parentNoteId, | ||||||
|  |             branchId: branch.branchId, | ||||||
|  |             isProtected: note.isProtected, | ||||||
|  |             noteType: note.type, | ||||||
|  |             title: utils.escapeHtml(title), | ||||||
|  |             extraClasses: this.getExtraClasses(note), | ||||||
|  |             icon: this.getIcon(note), | ||||||
|  |             refKey: note.noteId, | ||||||
|  |             expanded: branch.isExpanded || hoistedNoteId === note.noteId, | ||||||
|  |             key: utils.randomString(12) // this should prevent some "duplicate key" errors | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         const childBranches = await this.getChildBranches(note); | ||||||
|  |  | ||||||
|  |         node.folder = childBranches.length > 0 | ||||||
|  |             || note.type === 'search' | ||||||
|  |  | ||||||
|  |         node.lazy = node.folder && !node.expanded; | ||||||
|  |  | ||||||
|  |         if (node.folder && node.expanded) { | ||||||
|  |             node.children = await this.prepareChildren(note); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return node; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async prepareNormalNoteChildren(parentNote) { | ||||||
|  |         utils.assertArguments(parentNote); | ||||||
|  |  | ||||||
|  |         const noteList = []; | ||||||
|  |  | ||||||
|  |         for (const branch of await this.getChildBranches(parentNote)) { | ||||||
|  |             const node = await this.prepareNode(branch); | ||||||
|  |  | ||||||
|  |             noteList.push(node); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return noteList; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async getChildBranches(parentNote) { | ||||||
|  |         let childBranches = parentNote.getChildBranches(); | ||||||
|  |  | ||||||
|  |         if (!childBranches) { | ||||||
|  |             ws.logError(`No children for ${parentNote}. This shouldn't happen.`); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (this.hideIncludedImages) { | ||||||
|  |             const imageLinks = parentNote.getRelations('imageLink'); | ||||||
|  |  | ||||||
|  |             // image is already visible in the parent note so no need to display it separately in the book | ||||||
|  |             childBranches = childBranches.filter(branch => !imageLinks.find(rel => rel.value === branch.noteId)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (this.hideArchivedNotes) { | ||||||
|  |             const filteredBranches = []; | ||||||
|  |  | ||||||
|  |             for (const childBranch of childBranches) { | ||||||
|  |                 const childNote = await childBranch.getNote(); | ||||||
|  |  | ||||||
|  |                 if (!childNote.hasLabel('archived')) { | ||||||
|  |                     filteredBranches.push(childBranch); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             childBranches = filteredBranches; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return childBranches; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async prepareSearchNoteChildren(note) { | ||||||
|  |         await treeCache.reloadNotes([note.noteId]); | ||||||
|  |  | ||||||
|  |         const newNote = await treeCache.getNote(note.noteId); | ||||||
|  |  | ||||||
|  |         return await this.prepareNormalNoteChildren(newNote); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getExtraClasses(note) { | ||||||
|  |         utils.assertArguments(note); | ||||||
|  |  | ||||||
|  |         const extraClasses = []; | ||||||
|  |  | ||||||
|  |         if (note.isProtected) { | ||||||
|  |             extraClasses.push("protected"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (note.getParentNoteIds().length > 1) { | ||||||
|  |             extraClasses.push("multiple-parents"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const cssClass = note.getCssClass(); | ||||||
|  |  | ||||||
|  |         if (cssClass) { | ||||||
|  |             extraClasses.push(cssClass); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         extraClasses.push(utils.getNoteTypeClass(note.type)); | ||||||
|  |  | ||||||
|  |         if (note.mime) { // some notes should not have mime type (e.g. render) | ||||||
|  |             extraClasses.push(utils.getMimeTypeClass(note.mime)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (note.hasLabel('archived')) { | ||||||
|  |             extraClasses.push("archived"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return extraClasses.join(" "); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @return {FancytreeNode[]} */ |     /** @return {FancytreeNode[]} */ | ||||||
| @@ -374,6 +680,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|                 let foundChildNode = this.findChildNode(parentNode, childNoteId); |                 let foundChildNode = this.findChildNode(parentNode, childNoteId); | ||||||
|  |  | ||||||
|                 if (!foundChildNode) { // note might be recently created so we'll force reload and try again |                 if (!foundChildNode) { // note might be recently created so we'll force reload and try again | ||||||
|  |                     parentNode.lazy = true; | ||||||
|                     await parentNode.load(true); |                     await parentNode.load(true); | ||||||
|  |  | ||||||
|                     foundChildNode = this.findChildNode(parentNode, childNoteId); |                     foundChildNode = this.findChildNode(parentNode, childNoteId); | ||||||
| @@ -410,16 +717,16 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|         return this.getNodeFromPath(notePath, true, expandOpts); |         return this.getNodeFromPath(notePath, true, expandOpts); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     updateNode(node) { |     async updateNode(node) { | ||||||
|         const note = treeCache.getNoteFromCache(node.data.noteId); |         const note = treeCache.getNoteFromCache(node.data.noteId); | ||||||
|         const branch = treeCache.getBranch(node.data.branchId); |         const branch = treeCache.getBranch(node.data.branchId); | ||||||
|  |  | ||||||
|         node.data.isProtected = note.isProtected; |         node.data.isProtected = note.isProtected; | ||||||
|         node.data.noteType = note.type; |         node.data.noteType = note.type; | ||||||
|         node.folder = treeBuilder.getChildBranchesWithoutImages(note).length > 0 |         node.folder = (await this.getChildBranches(note)).length > 0 | ||||||
|                    || note.type === 'search'; |                    || note.type === 'search'; | ||||||
|         node.icon = treeBuilder.getIcon(note); |         node.icon = this.getIcon(note); | ||||||
|         node.extraClasses = treeBuilder.getExtraClasses(note); |         node.extraClasses = this.getExtraClasses(note); | ||||||
|         node.title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; |         node.title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; | ||||||
|         node.renderTitle(); |         node.renderTitle(); | ||||||
|     } |     } | ||||||
| @@ -476,6 +783,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|     async refreshSearch() { |     async refreshSearch() { | ||||||
|         const activeNode = this.getActiveNode(); |         const activeNode = this.getActiveNode(); | ||||||
|  |  | ||||||
|  |         activeNode.lazy = true; | ||||||
|         activeNode.load(true); |         activeNode.load(true); | ||||||
|         activeNode.setExpanded(true); |         activeNode.setExpanded(true); | ||||||
|  |  | ||||||
| @@ -569,6 +877,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|  |  | ||||||
|         for (const noteId of noteIdsToReload) { |         for (const noteId of noteIdsToReload) { | ||||||
|             for (const node of this.getNodesByNoteId(noteId)) { |             for (const node of this.getNodesByNoteId(noteId)) { | ||||||
|  |                 node.lazy = true; | ||||||
|                 await node.load(true); |                 await node.load(true); | ||||||
|  |  | ||||||
|                 this.updateNode(node); |                 this.updateNode(node); | ||||||
| @@ -631,7 +940,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|  |  | ||||||
|         const activeNotePath = activeNode !== null ? treeService.getNotePath(activeNode) : null; |         const activeNotePath = activeNode !== null ? treeService.getNotePath(activeNode) : null; | ||||||
|  |  | ||||||
|         const rootNode = await treeBuilder.prepareRootNode(); |         const rootNode = await this.prepareRootNode(); | ||||||
|  |  | ||||||
|         await this.batchUpdate(async () => { |         await this.batchUpdate(async () => { | ||||||
|             await this.tree.reload([rootNode]); |             await this.tree.reload([rootNode]); | ||||||
|   | |||||||
| @@ -110,7 +110,9 @@ async function getUserThemes() { | |||||||
| function isAllowed(name) { | function isAllowed(name) { | ||||||
|     return ALLOWED_OPTIONS.has(name) |     return ALLOWED_OPTIONS.has(name) | ||||||
|         || name.startsWith("keyboardShortcuts") |         || name.startsWith("keyboardShortcuts") | ||||||
|         || name.endsWith("Collapsed"); |         || name.endsWith("Collapsed") | ||||||
|  |         || name.startsWith("hideArchivedNotes") | ||||||
|  |         || name.startsWith("hideIncludedImages"); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -82,7 +82,9 @@ const defaultOptions = [ | |||||||
|     { name: 'rightPaneWidth', value: '25', isSynced: false }, |     { name: 'rightPaneWidth', value: '25', isSynced: false }, | ||||||
|     { name: 'rightPaneVisible', value: 'true', isSynced: false }, |     { name: 'rightPaneVisible', value: 'true', isSynced: false }, | ||||||
|     { name: 'nativeTitleBarVisible', value: 'false', isSynced: false }, |     { name: 'nativeTitleBarVisible', value: 'false', isSynced: false }, | ||||||
|     { name: 'eraseNotesAfterTimeInSeconds', value: '604800', isSynced: true } // default is 7 days |     { name: 'eraseNotesAfterTimeInSeconds', value: '604800', isSynced: true }, // default is 7 days | ||||||
|  |     { name: 'hideArchivedNotes_main', value: 'false', isSynced: false }, // default is 7 days | ||||||
|  |     { name: 'hideIncludedImages_main', value: 'true', isSynced: false } // default is 7 days | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| async function initStartupOptions() { | async function initStartupOptions() { | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ | |||||||
|  |  | ||||||
| <!-- Include Fancytree skin and library --> | <!-- Include Fancytree skin and library --> | ||||||
| <link href="libraries/fancytree/skin-win8/ui.fancytree.min.css" rel="stylesheet"> | <link href="libraries/fancytree/skin-win8/ui.fancytree.min.css" rel="stylesheet"> | ||||||
| <script src="libraries/fancytree/jquery.fancytree-all-deps.js"></script> | <script src="libraries/fancytree/jquery.fancytree-all-deps.min.js"></script> | ||||||
|  |  | ||||||
| <script src="libraries/jquery.hotkeys.js"></script> | <script src="libraries/jquery.hotkeys.js"></script> | ||||||
| <script src="libraries/jquery.fancytree.hotkeys.js"></script> | <script src="libraries/jquery.fancytree.hotkeys.js"></script> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user