mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Compare commits
	
		
			17 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 7672f22ce0 | ||
|  | ef37a52a06 | ||
|  | 2318d615bb | ||
|  | bc14c3d665 | ||
|  | b89ea9a684 | ||
|  | 496767a52b | ||
|  | 9139c597e5 | ||
|  | 942132c01d | ||
|  | 1862acd1ff | ||
|  | ce7e18d0b0 | ||
|  | 65280d5ba3 | ||
|  | 7e3d424e23 | ||
|  | bff04c121a | ||
|  | e8903e82a1 | ||
|  | 0cfd95d9b8 | ||
|  | 1aa5349628 | ||
|  | 4e21d12202 | 
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "version": "0.46.4-beta", | ||||
|   "version": "0.46.5", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   "name": "trilium", | ||||
|   "productName": "Trilium Notes", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.46.5", | ||||
|   "version": "0.46.7", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "bin": { | ||||
| @@ -14,7 +14,7 @@ | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start-server": "cross-env TRILIUM_ENV=dev node ./src/www", | ||||
|     "start-electron": "cross-env TRILIUM_ENV=dev electron .", | ||||
|     "start-electron": "cross-env TRILIUM_ENV=dev electron --inspect=5858 .", | ||||
|     "build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js", | ||||
|     "build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js", | ||||
|     "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", | ||||
|   | ||||
| @@ -49,8 +49,13 @@ class Note extends Entity { | ||||
|             this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable(); | ||||
|  | ||||
|             if (this.isContentAvailable) { | ||||
|                 try { | ||||
|                     this.title = protectedSessionService.decryptString(this.title); | ||||
|                 } | ||||
|                 catch (e) { | ||||
|                     throw new Error(`Could not decrypt title of note ${this.noteId}: ${e.message} ${e.stack}`) | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 this.title = "[protected]"; | ||||
|             } | ||||
| @@ -156,14 +161,14 @@ class Note extends Entity { | ||||
|  | ||||
|         sql.upsert("note_contents", "noteId", pojo); | ||||
|  | ||||
|         const hash = utils.hash(this.noteId + "|" + content.toString()); | ||||
|         const hash = utils.hash(this.noteId + "|" + pojo.content.toString()); | ||||
|  | ||||
|         entityChangesService.addEntityChange({ | ||||
|             entityName: 'note_contents', | ||||
|             entityId: this.noteId, | ||||
|             hash: hash, | ||||
|             isErased: false, | ||||
|             utcDateChanged: this.getUtcDateChanged() | ||||
|             utcDateChanged: pojo.utcDateModified | ||||
|         }, null); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -359,7 +359,7 @@ class NoteShort { | ||||
|         const workspaceIconClass = this.getWorkspaceIconClass(); | ||||
|  | ||||
|         if (iconClassLabels.length > 0) { | ||||
|             return iconClassLabels.map(l => l.value).join(' '); | ||||
|             return iconClassLabels[0].value; | ||||
|         } | ||||
|         else if (workspaceIconClass) { | ||||
|             return workspaceIconClass; | ||||
|   | ||||
| @@ -83,6 +83,9 @@ export default class MobileLayout { | ||||
|                     .child(new NoteTitleWidget()) | ||||
|                     .child(new CloseDetailButtonWidget())) | ||||
|                 .child(new NoteDetailWidget() | ||||
|                     .css('padding', '5px 20px 10px 0'))); | ||||
|                     .css('padding', '5px 20px 10px 0') | ||||
|                     .css('overflow', 'auto') | ||||
|                     .css('height', '100%') | ||||
|                 )); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import utils from "./utils.js"; | ||||
| import options from './options.js'; | ||||
| import server from "./server.js"; | ||||
|  | ||||
| const PROTECTED_SESSION_ID_KEY = 'protectedSessionId'; | ||||
|  | ||||
| @@ -23,11 +24,11 @@ function resetSessionCookie() { | ||||
|     utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null); | ||||
| } | ||||
|  | ||||
| function resetProtectedSession() { | ||||
| async function resetProtectedSession() { | ||||
|     resetSessionCookie(); | ||||
|  | ||||
|     // most secure solution - guarantees nothing remained in memory | ||||
|     // since this expires because user doesn't use the app, it shouldn't be disruptive | ||||
|     await server.post("logout/protected"); | ||||
|  | ||||
|     utils.reloadApp(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -36,7 +36,7 @@ export default class NoteTitleWidget extends TabAwareWidget { | ||||
|  | ||||
|             protectedSessionHolder.touchProtectedSessionIfNecessary(this.note); | ||||
|  | ||||
|             await server.put(`notes/${this.noteId}/change-title`, {title}); | ||||
|             await server.put(`notes/${this.noteId}/change-title`, {title}, this.componentId); | ||||
|         }); | ||||
|  | ||||
|         appContext.addBeforeUnloadListener(this); | ||||
|   | ||||
| @@ -176,6 +176,15 @@ const TPL = ` | ||||
|                       title="Images which are shown in the parent text note will not be displayed in the tree"></span> | ||||
|             </label> | ||||
|         </div> | ||||
|         <div class="form-check"> | ||||
|             <label class="form-check-label"> | ||||
|                 <input class="form-check-input auto-collapse-note-tree" type="checkbox" value=""> | ||||
|                  | ||||
|                 Automatically collapse notes | ||||
|                 <span class="bx bx-info-circle"  | ||||
|                       title="Notes will be collapsed after period of inactivity to declutter the tree."></span> | ||||
|             </label> | ||||
|         </div> | ||||
|      | ||||
|         <br/> | ||||
|      | ||||
| @@ -235,6 +244,7 @@ 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.$autoCollapseNoteTree = this.$treeSettingsPopup.find('.auto-collapse-note-tree'); | ||||
|  | ||||
|         this.$treeSettingsButton = this.$widget.find('.tree-settings-button'); | ||||
|         this.$treeSettingsButton.on("click", e => { | ||||
| @@ -245,6 +255,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|  | ||||
|             this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes); | ||||
|             this.$hideIncludedImages.prop("checked", this.hideIncludedImages); | ||||
|             this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree); | ||||
|  | ||||
|             let top = this.$treeSettingsButton[0].offsetTop; | ||||
|             let left = this.$treeSettingsButton[0].offsetLeft; | ||||
| @@ -272,6 +283,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|         this.$saveTreeSettingsButton.on('click', async () => { | ||||
|             await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked")); | ||||
|             await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked")); | ||||
|             await this.setAutoCollapseNoteTree(this.$autoCollapseNoteTree.prop("checked")); | ||||
|  | ||||
|             this.$treeSettingsPopup.hide(); | ||||
|  | ||||
| @@ -327,6 +339,14 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|         await options.save("hideIncludedImages_" + this.treeName, val.toString()); | ||||
|     } | ||||
|  | ||||
|     get autoCollapseNoteTree() { | ||||
|         return options.is("autoCollapseNoteTree"); | ||||
|     } | ||||
|  | ||||
|     async setAutoCollapseNoteTree(val) { | ||||
|         await options.save("autoCollapseNoteTree", val.toString()); | ||||
|     } | ||||
|  | ||||
|     initFancyTree() { | ||||
|         const treeData = [this.prepareRootNode()]; | ||||
|  | ||||
| @@ -797,10 +817,12 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|  | ||||
|             const node = await this.expandToNote(activeContext.notePath); | ||||
|  | ||||
|             if (node) { | ||||
|                 await node.makeVisible({scrollIntoView: true}); | ||||
|                 node.setActive(true, {noEvents: true, noFocus: false}); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** @return {FancytreeNode} */ | ||||
|     async getNodeFromPath(notePath, expand = false, logErrors = true) { | ||||
| @@ -851,8 +873,12 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|                             // these are real notes with real notePath, user can display them in a detail | ||||
|                             // but they don't have a node in the tree | ||||
|  | ||||
|                             const childNote = await treeCache.getNote(childNoteId); | ||||
|  | ||||
|                             if (!childNote || childNote.type !== 'image') { | ||||
|                                 ws.logError(`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`); | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         return; | ||||
|                     } | ||||
| @@ -955,6 +981,10 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|         } | ||||
|  | ||||
|         this.autoCollapseTimeoutId = setTimeout(() => { | ||||
|             if (!this.autoCollapseNoteTree) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             /* | ||||
|              * We're collapsing notes after period of inactivity to "cleanup" the tree - users rarely | ||||
|              * collapse the notes and the tree becomes unusuably large. | ||||
|   | ||||
| @@ -14,6 +14,10 @@ const TPL = ` | ||||
|         height: 35px; | ||||
|     } | ||||
|      | ||||
|     .standard-top-widget > div { | ||||
|         flex-shrink: 0; /* fixes https://github.com/zadam/trilium/issues/1745 */ | ||||
|     } | ||||
|      | ||||
|     .standard-top-widget button.noborder { | ||||
|         padding: 1px 5px 1px 5px; | ||||
|         font-size: 90%; | ||||
|   | ||||
| @@ -5,6 +5,7 @@ const TPL = ` | ||||
|     <style> | ||||
|     .note-detail-read-only-code { | ||||
|         position: relative; | ||||
|         min-height: 50px; | ||||
|     } | ||||
|      | ||||
|     .note-detail-read-only-code-content { | ||||
|   | ||||
| @@ -25,6 +25,7 @@ const TPL = ` | ||||
|         padding-top: 10px; | ||||
|         font-family: var(--detail-text-font-family); | ||||
|         position: relative; | ||||
|         min-height: 50px; | ||||
|     } | ||||
|          | ||||
|     .note-detail-readonly-text p:first-child, .note-detail-readonly-text::before { | ||||
|   | ||||
| @@ -78,6 +78,12 @@ function loginToProtectedSession(req) { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| function logoutFromProtectedSession() { | ||||
|     protectedSessionService.resetDataKey(); | ||||
|  | ||||
|     eventService.emit(eventService.LEAVE_PROTECTED_SESSION); | ||||
| } | ||||
|  | ||||
| function token(req) { | ||||
|     const username = req.body.username; | ||||
|     const password = req.body.password; | ||||
| @@ -101,5 +107,6 @@ function token(req) { | ||||
| module.exports = { | ||||
|     loginSync, | ||||
|     loginToProtectedSession, | ||||
|     logoutFromProtectedSession, | ||||
|     token | ||||
| }; | ||||
|   | ||||
| @@ -5,6 +5,7 @@ const noteCacheService = require('../../services/note_cache/note_cache_service') | ||||
| const protectedSessionService = require('../../services/protected_session'); | ||||
| const noteRevisionService = require('../../services/note_revisions'); | ||||
| const utils = require('../../services/utils'); | ||||
| const sql = require('../../services/sql'); | ||||
| const path = require('path'); | ||||
|  | ||||
| function getNoteRevisions(req) { | ||||
|   | ||||
| @@ -41,7 +41,8 @@ const ALLOWED_OPTIONS = new Set([ | ||||
|     'attributeListExpanded', | ||||
|     'promotedAttributesExpanded', | ||||
|     'similarNotesExpanded', | ||||
|     'headingStyle' | ||||
|     'headingStyle', | ||||
|     'autoCollapseNoteTree' | ||||
| ]); | ||||
|  | ||||
| function getOptions() { | ||||
|   | ||||
| @@ -270,6 +270,8 @@ function register(app) { | ||||
|     route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); | ||||
|     // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username) | ||||
|     apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); | ||||
|     apiRoute(POST, '/api/logout/protected', loginApiRoute.logoutFromProtectedSession); | ||||
|  | ||||
|     route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler); | ||||
|  | ||||
|     // in case of local electron, local calls are allowed unauthenticated, for server they need auth | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { buildDate:"2021-03-14T22:56:27+01:00", buildRevision: "6c8d20288df302f3a415bd1bdcace98bf29d4bf6" }; | ||||
| module.exports = { buildDate:"2021-04-03T22:37:04+02:00", buildRevision: "ef37a52a06b471e60f9c0f11da704283bbcef6ab" }; | ||||
|   | ||||
| @@ -2,6 +2,7 @@ const log = require('./log'); | ||||
|  | ||||
| const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED"; | ||||
| const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION"; | ||||
| const LEAVE_PROTECTED_SESSION = "LEAVE_PROTECTED_SESSION"; | ||||
| const ENTITY_CREATED = "ENTITY_CREATED"; | ||||
| const ENTITY_CHANGED = "ENTITY_CHANGED"; | ||||
| const ENTITY_DELETED = "ENTITY_DELETED"; | ||||
| @@ -47,6 +48,7 @@ module.exports = { | ||||
|     // event types: | ||||
|     NOTE_TITLE_CHANGED, | ||||
|     ENTER_PROTECTED_SESSION, | ||||
|     LEAVE_PROTECTED_SESSION, | ||||
|     ENTITY_CREATED, | ||||
|     ENTITY_CHANGED, | ||||
|     ENTITY_DELETED, | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const protectedSessionService = require('../../protected_session'); | ||||
| const log = require('../../log'); | ||||
|  | ||||
| class Note { | ||||
|     constructor(noteCache, row) { | ||||
| @@ -405,7 +406,7 @@ class Note { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         let minDistance = 999_999; | ||||
|         let minDistance = 999999; | ||||
|  | ||||
|         for (const parent of this.parents) { | ||||
|             minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1); | ||||
| @@ -416,10 +417,15 @@ class Note { | ||||
|  | ||||
|     decrypt() { | ||||
|         if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { | ||||
|             try { | ||||
|                 this.title = protectedSessionService.decryptString(this.title); | ||||
|  | ||||
|                 this.isDecrypted = true; | ||||
|             } | ||||
|             catch (e) { | ||||
|                 log.error(`Could not decrypt note ${this.noteId}: ${e.message} ${e.stack}`); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // for logging etc | ||||
|   | ||||
| @@ -177,6 +177,10 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => { | ||||
|     } | ||||
| }); | ||||
|  | ||||
| eventService.subscribe(eventService.LEAVE_PROTECTED_SESSION, () => { | ||||
|     load(); | ||||
| }); | ||||
|  | ||||
| module.exports = { | ||||
|     load | ||||
| }; | ||||
|   | ||||
| @@ -233,7 +233,7 @@ async function findSimilarNotes(noteId) { | ||||
|  | ||||
|     const baseNote = noteCache.notes[noteId]; | ||||
|  | ||||
|     if (!baseNote) { | ||||
|     if (!baseNote || !baseNote.utcDateCreated) { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -86,6 +86,7 @@ const defaultOptions = [ | ||||
|     { name: 'similarNotesExpanded', value: 'true', isSynced: true }, | ||||
|     { name: 'debugModeEnabled', value: 'false', isSynced: false }, | ||||
|     { name: 'headingStyle', value: 'markdown', isSynced: true }, | ||||
|     { name: 'autoCollapseNoteTree', value: 'true', isSynced: true }, | ||||
| ]; | ||||
|  | ||||
| function initStartupOptions() { | ||||
|   | ||||
| @@ -5,7 +5,7 @@ const log = require('./log'); | ||||
| const dataEncryptionService = require('./data_encryption'); | ||||
| const cls = require('./cls'); | ||||
|  | ||||
| const dataKeyMap = {}; | ||||
| let dataKeyMap = {}; | ||||
|  | ||||
| function setDataKey(decryptedDataKey) { | ||||
|     const protectedSessionId = utils.randomSecureToken(32); | ||||
| @@ -29,6 +29,10 @@ function getDataKey() { | ||||
|     return dataKeyMap[protectedSessionId]; | ||||
| } | ||||
|  | ||||
| function resetDataKey() { | ||||
|     dataKeyMap = {}; | ||||
| } | ||||
|  | ||||
| function isProtectedSessionAvailable() { | ||||
|     const protectedSessionId = getProtectedSessionId(); | ||||
|  | ||||
| @@ -71,6 +75,7 @@ function decryptString(cipherText) { | ||||
| module.exports = { | ||||
|     setDataKey, | ||||
|     getDataKey, | ||||
|     resetDataKey, | ||||
|     isProtectedSessionAvailable, | ||||
|     encrypt, | ||||
|     decrypt, | ||||
|   | ||||
| @@ -26,9 +26,7 @@ function updateEntity(entityChange, entity, sourceId) { | ||||
|         ? updateNoteReordering(entityChange, entity, sourceId) | ||||
|         : updateNormalEntity(entityChange, entity, sourceId); | ||||
|  | ||||
|     // currently making exception for protected notes and note revisions because here | ||||
|     // the title and content are not available decrypted as listeners would expect | ||||
|     if (updated && !entity.isProtected && !entityChange.isErased) { | ||||
|     if (updated && !entityChange.isErased) { | ||||
|         eventService.emit(eventService.ENTITY_SYNCED, { | ||||
|             entityName: entityChange.entityName, | ||||
|             entity | ||||
| @@ -44,7 +42,7 @@ function updateNormalEntity(remoteEntityChange, entity, sourceId) { | ||||
|  | ||||
|     if (localEntityChange && !localEntityChange.isErased && remoteEntityChange.isErased) { | ||||
|         sql.transactional(() => { | ||||
|             const primaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName; | ||||
|             const primaryKey = entityConstructor.getEntityFromEntityName(remoteEntityChange.entityName).primaryKeyName; | ||||
|  | ||||
|             sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId); | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const sql = require('./sql'); | ||||
| const log = require('./log'); | ||||
| const repository = require('./repository'); | ||||
| const Branch = require('../entities/branch'); | ||||
| const entityChangesService = require('./entity_changes.js'); | ||||
| @@ -139,7 +140,12 @@ function sortNotesByTitle(parentNoteId, foldersFirst = false, reverse = false) { | ||||
|             sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", | ||||
|                 [position, note.branchId]); | ||||
|  | ||||
|             if (note.branchId in noteCache.branches) { | ||||
|                 noteCache.branches[note.branchId].notePosition = position; | ||||
|             } | ||||
|             else { | ||||
|                 log.info(`Branch "${note.branchId}" was not found in note cache.`); | ||||
|             } | ||||
|  | ||||
|             position += 10; | ||||
|         } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user