mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Compare commits
	
		
			30 Commits
		
	
	
		
			v0.27.0-be
			...
			v0.27.3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3b8d7b8fba | ||
|  | 9fca7f09a5 | ||
|  | fd39d6b3a9 | ||
|  | a103886ea5 | ||
|  | 373408e401 | ||
|  | db44c1d8e6 | ||
|  | 95a34c9e2d | ||
|  | 6ce401f260 | ||
|  | 5d74dcd256 | ||
|  | 5a9fc1697b | ||
|  | 927415838c | ||
|  | d72fcefdc7 | ||
|  | 0be173a8f7 | ||
|  | c3913a8735 | ||
|  | e2dfe1b6de | ||
|  | fec3e47eb8 | ||
|  | d72efd2450 | ||
|  | ef1c840aa7 | ||
|  | 1581464d8c | ||
|  | 9de29584a4 | ||
|  | 9e2e6fb50c | ||
|  | c85979b66b | ||
|  | ecdc5865a6 | ||
|  | 1771ddb787 | ||
|  | 3ab657fe46 | ||
|  | 8785dae753 | ||
|  | 2f1c5b29d4 | ||
|  | 7135349a10 | ||
|  | 66c639d5e3 | ||
|  | 6704b755d8 | 
| @@ -18,6 +18,7 @@ Trilium Notes is a hierarchical note taking application with focus on building l | |||||||
| * [Relation maps](https://github.com/zadam/trilium/wiki/Relation-map) for visualizing notes and their relations | * [Relation maps](https://github.com/zadam/trilium/wiki/Relation-map) for visualizing notes and their relations | ||||||
| * [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - see [Advanced showcases](https://github.com/zadam/trilium/wiki/Advanced-showcases) | * [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - see [Advanced showcases](https://github.com/zadam/trilium/wiki/Advanced-showcases) | ||||||
| * Scales well in both usability and performance upwards of 100 000 notes | * Scales well in both usability and performance upwards of 100 000 notes | ||||||
|  | * Touch optimized [mobile frontend](https://github.com/zadam/trilium/wiki/Mobile-frontend) for smartphones and tablets | ||||||
| * [Night theme](https://github.com/zadam/trilium/wiki/Themes) | * [Night theme](https://github.com/zadam/trilium/wiki/Themes) | ||||||
| * [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown) | * [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "version": "0.26.1", |   "version": "0.27.2-beta", | ||||||
|   "lockfileVersion": 1, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
| @@ -399,9 +399,9 @@ | |||||||
|       "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" |       "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" | ||||||
|     }, |     }, | ||||||
|     "@types/node": { |     "@types/node": { | ||||||
|       "version": "8.10.39", |       "version": "10.12.18", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.39.tgz", |       "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", | ||||||
|       "integrity": "sha512-rE7fktr02J8ybFf6eysife+WF+L4sAHWzw09DgdCebEu+qDwMvv4zl6Bc+825ttGZP73kCKxa3dhJOoGJ8+5mA==", |       "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "abab": { |     "abab": { | ||||||
| @@ -2375,12 +2375,12 @@ | |||||||
|       "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" |       "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" | ||||||
|     }, |     }, | ||||||
|     "electron": { |     "electron": { | ||||||
|       "version": "4.0.0", |       "version": "4.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-4.0.0.tgz", |       "resolved": "https://registry.npmjs.org/electron/-/electron-4.0.1.tgz", | ||||||
|       "integrity": "sha512-3XPG/3IXlvnT1oe1K6zEushoD0SKbP8xwdrL10EWGe6k2iOV4hSHqJ8vWnR8yZ7VbSXmBRfomEFDNAo/q/cwKw==", |       "integrity": "sha512-kBWDLn1Vq8Tm6+/HpQc8gkjX7wJyQI8v/lf2kAirfi0Q4cXh6vBjozFvV1U/9gGCbyKnIDM+m8/wpyJIjg4w7g==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@types/node": "^8.0.24", |         "@types/node": "^10.12.18", | ||||||
|         "electron-download": "^4.1.0", |         "electron-download": "^4.1.0", | ||||||
|         "extract-zip": "^1.0.3" |         "extract-zip": "^1.0.3" | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.27.0-beta", |   "version": "0.27.3", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
| @@ -66,7 +66,7 @@ | |||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "devtron": "1.4.0", |     "devtron": "1.4.0", | ||||||
|     "electron": "4.0.0", |     "electron": "4.0.1", | ||||||
|     "electron-compile": "6.4.3", |     "electron-compile": "6.4.3", | ||||||
|     "electron-packager": "13.0.1", |     "electron-packager": "13.0.1", | ||||||
|     "electron-rebuild": "1.8.2", |     "electron-rebuild": "1.8.2", | ||||||
|   | |||||||
| @@ -15,7 +15,8 @@ const ENTITY_NAME_TO_ENTITY = { | |||||||
|     "note_revisions": NoteRevision, |     "note_revisions": NoteRevision, | ||||||
|     "recent_notes": RecentNote, |     "recent_notes": RecentNote, | ||||||
|     "options": Option, |     "options": Option, | ||||||
|     "api_tokens": ApiToken |     "api_tokens": ApiToken, | ||||||
|  |     "links": Link | ||||||
| }; | }; | ||||||
|  |  | ||||||
| function getEntityFromEntityName(entityName) { | function getEntityFromEntityName(entityName) { | ||||||
|   | |||||||
| @@ -198,15 +198,17 @@ addTabHandler((async function () { | |||||||
|     const $syncVersion = $("#sync-version"); |     const $syncVersion = $("#sync-version"); | ||||||
|     const $buildDate = $("#build-date"); |     const $buildDate = $("#build-date"); | ||||||
|     const $buildRevision = $("#build-revision"); |     const $buildRevision = $("#build-revision"); | ||||||
|  |     const $dataDirectory = $("#data-directory"); | ||||||
|  |  | ||||||
|     const appInfo = await server.get('app-info'); |     const appInfo = await server.get('app-info'); | ||||||
|  |  | ||||||
|     $appVersion.html(appInfo.appVersion); |     $appVersion.text(appInfo.appVersion); | ||||||
|     $dbVersion.html(appInfo.dbVersion); |     $dbVersion.text(appInfo.dbVersion); | ||||||
|     $syncVersion.html(appInfo.syncVersion); |     $syncVersion.text(appInfo.syncVersion); | ||||||
|     $buildDate.html(appInfo.buildDate); |     $buildDate.text(appInfo.buildDate); | ||||||
|     $buildRevision.html(appInfo.buildRevision); |     $buildRevision.text(appInfo.buildRevision); | ||||||
|     $buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision); |     $buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision); | ||||||
|  |     $dataDirectory.text(appInfo.dataDirectory); | ||||||
|  |  | ||||||
|     return {}; |     return {}; | ||||||
| })()); | })()); | ||||||
|   | |||||||
| @@ -85,8 +85,11 @@ async function showAttributes() { | |||||||
|         $promotedAttributesContainer.empty().append($tbody); |         $promotedAttributesContainer.empty().append($tbody); | ||||||
|     } |     } | ||||||
|     else if (note.type !== 'relation-map') { |     else if (note.type !== 'relation-map') { | ||||||
|         if (attributes.length > 0) { |         // display only "own" notes | ||||||
|             for (const attribute of attributes) { |         const ownedAttributes = attributes.filter(attr => attr.noteId === note.noteId); | ||||||
|  |  | ||||||
|  |         if (ownedAttributes.length > 0) { | ||||||
|  |             for (const attribute of ownedAttributes) { | ||||||
|                 if (attribute.type === 'label') { |                 if (attribute.type === 'label') { | ||||||
|                     $attributeListInner.append(utils.formatLabel(attribute) + " "); |                     $attributeListInner.append(utils.formatLabel(attribute) + " "); | ||||||
|                 } |                 } | ||||||
| @@ -132,7 +135,9 @@ async function createPromotedAttributeRow(definitionAttr, valueAttr) { | |||||||
|     const $inputCell = $("<td>").append($("<div>").addClass("input-group").append($input)); |     const $inputCell = $("<td>").append($("<div>").addClass("input-group").append($input)); | ||||||
|  |  | ||||||
|     const $actionCell = $("<td>"); |     const $actionCell = $("<td>"); | ||||||
|     const $multiplicityCell = $("<td>").addClass("multiplicity"); |     const $multiplicityCell = $("<td>") | ||||||
|  |         .addClass("multiplicity") | ||||||
|  |         .attr("nowrap", true); | ||||||
|  |  | ||||||
|     $tr |     $tr | ||||||
|         .append($labelCell) |         .append($labelCell) | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import treeCache from './tree_cache.js'; | |||||||
| import noteDetailService from './note_detail.js'; | import noteDetailService from './note_detail.js'; | ||||||
| import noteTypeService from './note_type.js'; | import noteTypeService from './note_type.js'; | ||||||
| import noteTooltipService from './note_tooltip.js'; | import noteTooltipService from './note_tooltip.js'; | ||||||
|  | import protectedSessionService from'./protected_session.js'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * This is the main frontend API interface for scripts. It's published in the local "api" object. |  * This is the main frontend API interface for scripts. It's published in the local "api" object. | ||||||
| @@ -42,7 +43,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) { | |||||||
|     this.activateNewNote = async notePath => { |     this.activateNewNote = async notePath => { | ||||||
|         await treeService.reload(); |         await treeService.reload(); | ||||||
|  |  | ||||||
|         await treeService.activateNote(notePath, true); |         await treeService.activateNote(notePath, noteDetailService.focusOnTitle); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -244,7 +245,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) { | |||||||
|      * @method |      * @method | ||||||
|      * @param {object} $el - jquery object on which to setup the tooltip |      * @param {object} $el - jquery object on which to setup the tooltip | ||||||
|      */ |      */ | ||||||
|     this.setupElementTooltip = noteTooltipService.setupElementTooltip |     this.setupElementTooltip = noteTooltipService.setupElementTooltip; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @method | ||||||
|  |      */ | ||||||
|  |     this.protectCurrentNote = protectedSessionService.protectNoteAndSendToServer; | ||||||
| } | } | ||||||
|  |  | ||||||
| export default FrontendScriptApi; | export default FrontendScriptApi; | ||||||
| @@ -37,6 +37,8 @@ let noteChangeDisabled = false; | |||||||
|  |  | ||||||
| let isNoteChanged = false; | let isNoteChanged = false; | ||||||
|  |  | ||||||
|  | let detailLoadedListeners = []; | ||||||
|  |  | ||||||
| const components = { | const components = { | ||||||
|     'code': noteDetailCode, |     'code': noteDetailCode, | ||||||
|     'text': noteDetailText, |     'text': noteDetailText, | ||||||
| @@ -147,12 +149,6 @@ function setNoteBackgroundIfProtected(note) { | |||||||
|     $unprotectButton.prop("disabled", !protectedSessionHolder.isProtectedSessionAvailable()); |     $unprotectButton.prop("disabled", !protectedSessionHolder.isProtectedSessionAvailable()); | ||||||
| } | } | ||||||
|  |  | ||||||
| let isNewNoteCreated = false; |  | ||||||
|  |  | ||||||
| function newNoteCreated() { |  | ||||||
|     isNewNoteCreated = true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function handleProtectedSession() { | async function handleProtectedSession() { | ||||||
|     const newSessionCreated = await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false); |     const newSessionCreated = await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false); | ||||||
|  |  | ||||||
| @@ -191,12 +187,6 @@ async function loadNoteDetail(noteId) { | |||||||
|         attributeService.invalidateAttributes(); |         attributeService.invalidateAttributes(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (isNewNoteCreated) { |  | ||||||
|         isNewNoteCreated = false; |  | ||||||
|  |  | ||||||
|         $noteTitle.focus().select(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     $noteIdDisplay.html(noteId); |     $noteIdDisplay.html(noteId); | ||||||
|  |  | ||||||
|     setNoteBackgroundIfProtected(currentNote); |     setNoteBackgroundIfProtected(currentNote); | ||||||
| @@ -240,11 +230,13 @@ async function loadNoteDetail(noteId) { | |||||||
|     // after loading new note make sure editor is scrolled to the top |     // after loading new note make sure editor is scrolled to the top | ||||||
|     getComponent(currentNote.type).scrollToTop(); |     getComponent(currentNote.type).scrollToTop(); | ||||||
|  |  | ||||||
|     if (utils.isDesktop()) { |     fireDetailLoaded(); | ||||||
|  |  | ||||||
|     $scriptArea.empty(); |     $scriptArea.empty(); | ||||||
|  |  | ||||||
|     await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView'); |     await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView'); | ||||||
|  |  | ||||||
|  |     if (utils.isDesktop()) { | ||||||
|         await attributeService.showAttributes(); |         await attributeService.showAttributes(); | ||||||
|  |  | ||||||
|         await showChildrenOverview(); |         await showChildrenOverview(); | ||||||
| @@ -291,6 +283,30 @@ function focusOnTitle() { | |||||||
|     $noteTitle.focus(); |     $noteTitle.focus(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Since detail loading may take some time and user might just browse through the notes using UP-DOWN keys, | ||||||
|  |  * we intentionally decouple activation of the note in the tree and full load of the note so just avaiting on | ||||||
|  |  * fancytree's activate() won't wait for the full load. | ||||||
|  |  * | ||||||
|  |  * This causes an issue where in some cases you want to do some action after detail is loaded. For this reason | ||||||
|  |  * we provide the listeners here which will be triggered after the detail is loaded and if the loaded note | ||||||
|  |  * is the one registered in the listener. | ||||||
|  |  */ | ||||||
|  | function addDetailLoadedListener(noteId, callback) { | ||||||
|  |     detailLoadedListeners.push({ noteId, callback }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function fireDetailLoaded() { | ||||||
|  |     for (const {noteId, callback} of detailLoadedListeners) { | ||||||
|  |         if (noteId === currentNote.noteId) { | ||||||
|  |             callback(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // all the listeners are one time only | ||||||
|  |     detailLoadedListeners = []; | ||||||
|  | } | ||||||
|  |  | ||||||
| messagingService.subscribeToSyncMessages(syncData => { | messagingService.subscribeToSyncMessages(syncData => { | ||||||
|     if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getCurrentNoteId())) { |     if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getCurrentNoteId())) { | ||||||
|         infoService.showMessage('Reloading note because of background changes'); |         infoService.showMessage('Reloading note because of background changes'); | ||||||
| @@ -325,11 +341,11 @@ export default { | |||||||
|     getCurrentNote, |     getCurrentNote, | ||||||
|     getCurrentNoteType, |     getCurrentNoteType, | ||||||
|     getCurrentNoteId, |     getCurrentNoteId, | ||||||
|     newNoteCreated, |  | ||||||
|     focusOnTitle, |     focusOnTitle, | ||||||
|     saveNote, |     saveNote, | ||||||
|     saveNoteIfChanged, |     saveNoteIfChanged, | ||||||
|     noteChanged, |     noteChanged, | ||||||
|     getCurrentNoteContent, |     getCurrentNoteContent, | ||||||
|     onNoteChange |     onNoteChange, | ||||||
|  |     addDetailLoadedListener | ||||||
| }; | }; | ||||||
| @@ -184,5 +184,6 @@ export default { | |||||||
|     protectSubtree, |     protectSubtree, | ||||||
|     ensureDialogIsClosed, |     ensureDialogIsClosed, | ||||||
|     enterProtectedSession, |     enterProtectedSession, | ||||||
|     leaveProtectedSession |     leaveProtectedSession, | ||||||
|  |     protectNoteAndSendToServer | ||||||
| }; | }; | ||||||
| @@ -83,6 +83,10 @@ async function setNodeTitleWithPrefix(node) { | |||||||
|     node.setTitle(utils.escapeHtml(title)); |     node.setTitle(utils.escapeHtml(title)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function getNode(childNoteId, parentNoteId) { | ||||||
|  |     return getNodesByNoteId(childNoteId).find(node => !parentNoteId || node.data.parentNoteId === parentNoteId); | ||||||
|  | } | ||||||
|  |  | ||||||
| async function expandToNote(notePath, expandOpts) { | async function expandToNote(notePath, expandOpts) { | ||||||
|     utils.assertArguments(notePath); |     utils.assertArguments(notePath); | ||||||
|  |  | ||||||
| @@ -94,7 +98,18 @@ async function expandToNote(notePath, expandOpts) { | |||||||
|  |  | ||||||
|     for (const childNoteId of runPath) { |     for (const childNoteId of runPath) { | ||||||
|         // for first node (!parentNoteId) it doesn't matter which node is found |         // for first node (!parentNoteId) it doesn't matter which node is found | ||||||
|         const node = getNodesByNoteId(childNoteId).find(node => !parentNoteId || node.data.parentNoteId === parentNoteId); |         let node = getNode(childNoteId, parentNoteId); | ||||||
|  |  | ||||||
|  |         if (!node && parentNoteId) { | ||||||
|  |             const parents = getNodesByNoteId(parentNoteId); | ||||||
|  |  | ||||||
|  |             for (const parent of parents) { | ||||||
|  |                 // force load parents. This is useful when fancytree doesn't contain recently created notes yet. | ||||||
|  |                 await parent.load(true); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             node = getNode(childNoteId, parentNoteId); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (!node) { |         if (!node) { | ||||||
|             console.error(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`); |             console.error(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`); | ||||||
| @@ -111,7 +126,7 @@ async function expandToNote(notePath, expandOpts) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function activateNote(notePath, newNote) { | async function activateNote(notePath, noteLoadedListener) { | ||||||
|     utils.assertArguments(notePath); |     utils.assertArguments(notePath); | ||||||
|  |  | ||||||
|     const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); |     const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); | ||||||
| @@ -131,8 +146,8 @@ async function activateNote(notePath, newNote) { | |||||||
|  |  | ||||||
|     const node = await expandToNote(notePath); |     const node = await expandToNote(notePath); | ||||||
|  |  | ||||||
|     if (newNote) { |     if (noteLoadedListener) { | ||||||
|         noteDetailService.newNoteCreated(); |         noteDetailService.addDetailLoadedListener(node.data.noteId, noteLoadedListener); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // we use noFocus because when we reload the tree because of background changes |     // we use noFocus because when we reload the tree because of background changes | ||||||
| @@ -411,13 +426,17 @@ function initFancyTree(tree) { | |||||||
|         clones: { |         clones: { | ||||||
|             highlightActiveClones: true |             highlightActiveClones: true | ||||||
|         }, |         }, | ||||||
|         renderNode: async function (event, data) { |         enhanceTitle: async function (event, data) { | ||||||
|             const node = data.node; |             const node = data.node; | ||||||
|  |             const $span = $(node.span); | ||||||
|  |  | ||||||
|  |             if (node.data.noteId !== 'root' | ||||||
|  |                 && node.data.noteId === await hoistedNoteService.getHoistedNoteId() | ||||||
|  |                 && $span.find('.unhoist-button').length === 0) { | ||||||
|  |  | ||||||
|             if (node.data.noteId !== 'root' && node.data.noteId === await hoistedNoteService.getHoistedNoteId()) { |  | ||||||
|                 const unhoistButton = $('<span>  (<a class="unhoist-button">unhoist</a>)</span>'); |                 const unhoistButton = $('<span>  (<a class="unhoist-button">unhoist</a>)</span>'); | ||||||
|  |  | ||||||
|                 $(node.span).append(unhoistButton); |                 $span.append(unhoistButton); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| @@ -547,7 +566,7 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection | |||||||
|  |  | ||||||
|     await noteDetailService.saveNoteIfChanged(); |     await noteDetailService.saveNoteIfChanged(); | ||||||
|  |  | ||||||
|     noteDetailService.newNoteCreated(); |     noteDetailService.addDetailLoadedListener(note.noteId, noteDetailService.focusOnTitle); | ||||||
|  |  | ||||||
|     const noteEntity = new NoteShort(treeCache, note); |     const noteEntity = new NoteShort(treeCache, note); | ||||||
|     const branchEntity = new Branch(treeCache, branch); |     const branchEntity = new Branch(treeCache, branch); | ||||||
| @@ -634,11 +653,15 @@ messagingService.subscribeToSyncMessages(syncData => { | |||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| utils.bindShortcut('ctrl+o', () => { | utils.bindShortcut('ctrl+o', async () => { | ||||||
|     const node = getCurrentNode(); |     const node = getCurrentNode(); | ||||||
|     const parentNoteId = node.data.parentNoteId; |     const parentNoteId = node.data.parentNoteId; | ||||||
|     const isProtected = treeUtils.getParentProtectedStatus(node); |     const isProtected = treeUtils.getParentProtectedStatus(node); | ||||||
|  |  | ||||||
|  |     if (node.data.noteId === 'root' || node.data.noteId === await hoistedNoteService.getHoistedNoteId()) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     createNote(node, parentNoteId, 'after', isProtected, true); |     createNote(node, parentNoteId, 'after', isProtected, true); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -158,7 +158,11 @@ class TreeCache { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         treeCache.childParentToBranch[childNoteId + '-' + newParentNoteId] = treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; |         const branchId = treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; | ||||||
|  |         const branch = await this.getBranch(branchId); | ||||||
|  |         branch.parentNoteId = newParentNoteId; | ||||||
|  |  | ||||||
|  |         treeCache.childParentToBranch[childNoteId + '-' + newParentNoteId] = branchId; | ||||||
|         delete treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId |         delete treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId | ||||||
|  |  | ||||||
|         // remove old associations |         // remove old associations | ||||||
|   | |||||||
| @@ -13,8 +13,6 @@ import syncService from "./sync.js"; | |||||||
| import hoistedNoteService from './hoisted_note.js'; | import hoistedNoteService from './hoisted_note.js'; | ||||||
| import ContextMenuItemsContainer from './context_menu_items_container.js'; | import ContextMenuItemsContainer from './context_menu_items_container.js'; | ||||||
|  |  | ||||||
| const $tree = $("#tree"); |  | ||||||
|  |  | ||||||
| let clipboardIds = []; | let clipboardIds = []; | ||||||
| let clipboardMode = null; | let clipboardMode = null; | ||||||
|  |  | ||||||
| @@ -110,11 +108,12 @@ async function getContextMenuItems(event) { | |||||||
|     const note = await treeCache.getNote(node.data.noteId); |     const note = await treeCache.getNote(node.data.noteId); | ||||||
|     const parentNote = await treeCache.getNote(branch.parentNoteId); |     const parentNote = await treeCache.getNote(branch.parentNoteId); | ||||||
|     const isNotRoot = note.noteId !== 'root'; |     const isNotRoot = note.noteId !== 'root'; | ||||||
|  |     const isHoisted = note.noteId === await hoistedNoteService.getHoistedNoteId(); | ||||||
|  |  | ||||||
|     const itemsContainer = new ContextMenuItemsContainer(contextMenuItems); |     const itemsContainer = new ContextMenuItemsContainer(contextMenuItems); | ||||||
|  |  | ||||||
|     // Modify menu entries depending on node status |     // Modify menu entries depending on node status | ||||||
|     itemsContainer.enableItem("insertNoteAfter", isNotRoot && parentNote.type !== 'search'); |     itemsContainer.enableItem("insertNoteAfter", isNotRoot && !isHoisted && parentNote.type !== 'search'); | ||||||
|     itemsContainer.enableItem("insertChildNote", note.type !== 'search'); |     itemsContainer.enableItem("insertChildNote", note.type !== 'search'); | ||||||
|     itemsContainer.enableItem("delete", isNotRoot && parentNote.type !== 'search'); |     itemsContainer.enableItem("delete", isNotRoot && parentNote.type !== 'search'); | ||||||
|     itemsContainer.enableItem("copy", isNotRoot); |     itemsContainer.enableItem("copy", isNotRoot); | ||||||
| @@ -125,10 +124,8 @@ async function getContextMenuItems(event) { | |||||||
|     itemsContainer.enableItem("export", note.type !== 'search'); |     itemsContainer.enableItem("export", note.type !== 'search'); | ||||||
|     itemsContainer.enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search'); |     itemsContainer.enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search'); | ||||||
|  |  | ||||||
|     const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); |     itemsContainer.hideItem("hoist", isHoisted); | ||||||
|  |     itemsContainer.hideItem("unhoist", !isHoisted || !isNotRoot); | ||||||
|     itemsContainer.hideItem("hoist", note.noteId === hoistedNoteId); |  | ||||||
|     itemsContainer.hideItem("unhoist", note.noteId !== hoistedNoteId || !isNotRoot); |  | ||||||
|  |  | ||||||
|     // Activate node on right-click |     // Activate node on right-click | ||||||
|     node.setActive(); |     node.setActive(); | ||||||
|   | |||||||
| @@ -25,26 +25,20 @@ function SetupModel() { | |||||||
|  |  | ||||||
|     this.instanceType = utils.isElectron() ? "desktop" : "server"; |     this.instanceType = utils.isElectron() ? "desktop" : "server"; | ||||||
|  |  | ||||||
|     this.setupTypeSelected = this.getSetupType = () => |     this.setupTypeSelected = () => !!this.setupType(); | ||||||
|         this.setupNewDocument() |  | ||||||
|         || this.setupSyncFromDesktop() |  | ||||||
|         || this.setupSyncFromServer(); |  | ||||||
|  |  | ||||||
|     this.selectSetupType = () => { |     this.selectSetupType = () => { | ||||||
|         this.step(this.getSetupType()); |         this.step(this.setupType()); | ||||||
|         this.setupType(this.getSetupType()); |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     this.back = () => { |     this.back = () => { | ||||||
|         this.step("setup-type"); |         this.step("setup-type"); | ||||||
|  |  | ||||||
|         this.setupNewDocument(false); |         this.setupType(""); | ||||||
|         this.setupSyncFromServer(false); |  | ||||||
|         this.setupSyncFromDesktop(false); |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     this.finish = async () => { |     this.finish = async () => { | ||||||
|         if (this.setupNewDocument()) { |         if (this.setupType() === 'new-document') { | ||||||
|             const username = this.username(); |             const username = this.username(); | ||||||
|             const password1 = this.password1(); |             const password1 = this.password1(); | ||||||
|             const password2 = this.password2(); |             const password2 = this.password2(); | ||||||
| @@ -72,7 +66,7 @@ function SetupModel() { | |||||||
|                 window.location.replace("/"); |                 window.location.replace("/"); | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|         else if (this.setupSyncFromServer()) { |         else if (this.setupType() === 'sync-from-server') { | ||||||
|             const syncServerHost = this.syncServerHost(); |             const syncServerHost = this.syncServerHost(); | ||||||
|             const syncProxy = this.syncProxy(); |             const syncProxy = this.syncProxy(); | ||||||
|             const username = this.username(); |             const username = this.username(); | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -45,14 +45,24 @@ | |||||||
| #header button { | #header button { | ||||||
|     padding: 1px 5px 1px 5px; |     padding: 1px 5px 1px 5px; | ||||||
|     font-size: small; |     font-size: small; | ||||||
|  |     margin-bottom: 2px; | ||||||
|  |     margin-top: 2px; | ||||||
|  |     margin-right: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #history-navigation { | ||||||
|  |     margin: 0 15px 0 5px; | ||||||
|  |     position: relative; | ||||||
|  |     top: 2px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #global-buttons { | #global-buttons { | ||||||
|     display: flex; |     display: flex; | ||||||
|     justify-content: space-around; |     justify-content: space-around; | ||||||
|     padding: 10px 0 10px 0; |     padding: 10px 0 10px 0; | ||||||
|     margin: 0 10px 0 16px; |     margin: 0 20px 0 10px; | ||||||
|     border: 1px solid #ccc; |     border: 1px solid #ddd; | ||||||
|  |     border-radius: 5px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #context-menu-container { | #context-menu-container { | ||||||
|   | |||||||
| @@ -70,6 +70,7 @@ body { | |||||||
|     height: 100%; |     height: 100%; | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|  |     min-height: 200px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .note-detail-component { | .note-detail-component { | ||||||
| @@ -154,17 +155,6 @@ span.fancytree-active:not(.fancytree-focused) .fancytree-title { | |||||||
|     color: #337ab7 !important; |     color: #337ab7 !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| #header-title { |  | ||||||
|     padding: 5px 20px 5px 10px; |  | ||||||
|     font-size: large; |  | ||||||
|     font-weight: bold; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #header .btn-sm { |  | ||||||
|     margin-bottom: 2px; |  | ||||||
|     margin-right: 8px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| div.ui-tooltip { | div.ui-tooltip { | ||||||
|     max-width: 600px; |     max-width: 600px; | ||||||
|     max-height: 600px; |     max-height: 600px; | ||||||
| @@ -311,6 +301,12 @@ div.ui-tooltip { | |||||||
|  |  | ||||||
| .cm-matchhighlight {background-color: #eeeeee} | .cm-matchhighlight {background-color: #eeeeee} | ||||||
|  |  | ||||||
|  | #attribute-list { | ||||||
|  |     overflow: auto; | ||||||
|  |     /* limiting the size since actual note content is more important */ | ||||||
|  |     max-height: 30%; | ||||||
|  | } | ||||||
|  |  | ||||||
| #label-list, #relation-list, #attribute-list { | #label-list, #relation-list, #attribute-list { | ||||||
|     color: #777777; |     color: #777777; | ||||||
|     padding: 5px; |     padding: 5px; | ||||||
| @@ -370,13 +366,8 @@ div.ui-tooltip { | |||||||
|     height: 150px; |     height: 150px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #history-navigation { |  | ||||||
|     margin: 0 20px 0 5px; |  | ||||||
|     display: flex; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .btn:not(.btn-primary):not(.btn-danger) { | .btn:not(.btn-primary):not(.btn-danger) { | ||||||
|     border-color: #bbb; |     border-color: #ddd; | ||||||
|     background-color: #eee; |     background-color: #eee; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -434,8 +425,13 @@ html.theme-dark body { | |||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-promoted-attributes { | #note-detail-promoted-attributes { | ||||||
|     max-width: 70%; |  | ||||||
|     margin: auto; |     margin: auto; | ||||||
|  |     /* setting the display to block since "table" doesn't support scrolling */ | ||||||
|  |     display: block; | ||||||
|  |     flex-basis: content; | ||||||
|  |     flex-shrink: 1; | ||||||
|  |     flex-grow: 0; | ||||||
|  |     overflow: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-promoted-attributes td, #note-detail-promoted-attributes th { | #note-detail-promoted-attributes td, #note-detail-promoted-attributes th { | ||||||
| @@ -532,7 +528,8 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th | |||||||
|     max-height: 300px; |     max-height: 300px; | ||||||
|     overflow: hidden; |     overflow: hidden; | ||||||
|     color: black; |     color: black; | ||||||
|     border: 1px solid #aaa; |     border: 1px solid #ccc; | ||||||
|  |     border-radius: 5px; | ||||||
|     text-align: left; |     text-align: left; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ const appInfo = require('../../services/app_info'); | |||||||
| const eventService = require('../../services/events'); | const eventService = require('../../services/events'); | ||||||
| const cls = require('../../services/cls'); | const cls = require('../../services/cls'); | ||||||
| const sqlInit = require('../../services/sql_init'); | const sqlInit = require('../../services/sql_init'); | ||||||
|  | const sql = require('../../services/sql'); | ||||||
|  |  | ||||||
| async function loginSync(req) { | async function loginSync(req) { | ||||||
|     if (!await sqlInit.schemaExists()) { |     if (!await sqlInit.schemaExists()) { | ||||||
| @@ -44,7 +45,8 @@ async function loginSync(req) { | |||||||
|     req.session.loggedIn = true; |     req.session.loggedIn = true; | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         sourceId: sourceIdService.getCurrentSourceId() |         sourceId: sourceIdService.getCurrentSourceId(), | ||||||
|  |         maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync") | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,14 +2,16 @@ | |||||||
|  |  | ||||||
| const build = require('./build'); | const build = require('./build'); | ||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
|  | const {TRILIUM_DATA_DIR} = require('./data_dir'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 121; | const APP_DB_VERSION = 121; | ||||||
| const SYNC_VERSION = 2; | const SYNC_VERSION = 3; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     appVersion: packageJson.version, |     appVersion: packageJson.version, | ||||||
|     dbVersion: APP_DB_VERSION, |     dbVersion: APP_DB_VERSION, | ||||||
|     syncVersion: SYNC_VERSION, |     syncVersion: SYNC_VERSION, | ||||||
|     buildDate: build.buildDate, |     buildDate: build.buildDate, | ||||||
|     buildRevision: build.buildRevision |     buildRevision: build.buildRevision, | ||||||
|  |     dataDirectory: TRILIUM_DATA_DIR | ||||||
| }; | }; | ||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2018-12-30T22:38:11+01:00", buildRevision: "32220476aa6795bab036b7dd9057ea3357d7dd51" }; | module.exports = { buildDate:"2019-01-05T22:48:11+01:00", buildRevision: "9fca7f09a564c719c834d38d76c3b595c8579b2a" }; | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ const Attribute = require('../entities/attribute'); | |||||||
| const NoteRevision = require('../entities/note_revision'); | const NoteRevision = require('../entities/note_revision'); | ||||||
| const RecentNote = require('../entities/recent_note'); | const RecentNote = require('../entities/recent_note'); | ||||||
| const Option = require('../entities/option'); | const Option = require('../entities/option'); | ||||||
|  | const Link = require('../entities/link'); | ||||||
|  |  | ||||||
| async function getHash(entityConstructor, whereBranch) { | async function getHash(entityConstructor, whereBranch) { | ||||||
|     // subselect is necessary to have correct ordering in GROUP_CONCAT |     // subselect is necessary to have correct ordering in GROUP_CONCAT | ||||||
| @@ -37,7 +38,8 @@ async function getHashes() { | |||||||
|         recent_notes: await getHash(RecentNote), |         recent_notes: await getHash(RecentNote), | ||||||
|         options: await getHash(Option, "isSynced = 1"), |         options: await getHash(Option, "isSynced = 1"), | ||||||
|         attributes: await getHash(Attribute), |         attributes: await getHash(Attribute), | ||||||
|         api_tokens: await getHash(ApiToken) |         api_tokens: await getHash(ApiToken), | ||||||
|  |         links: await getHash(Link) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const elapseTimeMs = new Date().getTime() - startTime.getTime(); |     const elapseTimeMs = new Date().getTime() - startTime.getTime(); | ||||||
|   | |||||||
| @@ -55,10 +55,6 @@ function getTriliumDataDir() { | |||||||
| } | } | ||||||
|  |  | ||||||
| const TRILIUM_DATA_DIR =  getTriliumDataDir(); | const TRILIUM_DATA_DIR =  getTriliumDataDir(); | ||||||
|  |  | ||||||
| // not necessary to log this since if we have logs we already know where data dir is. |  | ||||||
| console.log("Using data dir:", TRILIUM_DATA_DIR); |  | ||||||
|  |  | ||||||
| const DOCUMENT_PATH = TRILIUM_DATA_DIR + "/document.db"; | const DOCUMENT_PATH = TRILIUM_DATA_DIR + "/document.db"; | ||||||
| const BACKUP_DIR = TRILIUM_DATA_DIR + "/backup"; | const BACKUP_DIR = TRILIUM_DATA_DIR + "/backup"; | ||||||
| const LOG_DIR = TRILIUM_DATA_DIR + "/log"; | const LOG_DIR = TRILIUM_DATA_DIR + "/log"; | ||||||
|   | |||||||
| @@ -5,13 +5,24 @@ const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION"; | |||||||
| const ENTITY_CREATED = "ENTITY_CREATED"; | const ENTITY_CREATED = "ENTITY_CREATED"; | ||||||
| const ENTITY_CHANGED = "ENTITY_CHANGED"; | const ENTITY_CHANGED = "ENTITY_CHANGED"; | ||||||
| const ENTITY_DELETED = "ENTITY_DELETED"; | const ENTITY_DELETED = "ENTITY_DELETED"; | ||||||
|  | const ENTITY_SYNCED = "ENTITY_SYNCED"; | ||||||
| const CHILD_NOTE_CREATED = "CHILD_NOTE_CREATED"; | const CHILD_NOTE_CREATED = "CHILD_NOTE_CREATED"; | ||||||
|  |  | ||||||
| const eventListeners = {}; | const eventListeners = {}; | ||||||
|  |  | ||||||
| function subscribe(eventType, listener) { | /** | ||||||
|  |  * @param eventTypes - can be either single event or an array of events | ||||||
|  |  * @param listener | ||||||
|  |  */ | ||||||
|  | function subscribe(eventTypes, listener) { | ||||||
|  |     if (!Array.isArray(eventTypes)) { | ||||||
|  |         eventTypes = [ eventTypes ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for (const eventType of eventTypes) { | ||||||
|         eventListeners[eventType] = eventListeners[eventType] || []; |         eventListeners[eventType] = eventListeners[eventType] || []; | ||||||
|         eventListeners[eventType].push(listener); |         eventListeners[eventType].push(listener); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function emit(eventType, data) { | async function emit(eventType, data) { | ||||||
| @@ -39,5 +50,6 @@ module.exports = { | |||||||
|     ENTITY_CREATED, |     ENTITY_CREATED, | ||||||
|     ENTITY_CHANGED, |     ENTITY_CHANGED, | ||||||
|     ENTITY_DELETED, |     ENTITY_DELETED, | ||||||
|  |     ENTITY_SYNCED, | ||||||
|     CHILD_NOTE_CREATED |     CHILD_NOTE_CREATED | ||||||
| }; | }; | ||||||
| @@ -45,8 +45,6 @@ function request(req) { | |||||||
|     logger.info(req.method + " " + req.url); |     logger.info(req.method + " " + req.url); | ||||||
| } | } | ||||||
|  |  | ||||||
| info("Using data dir: " + dataDir.TRILIUM_DATA_DIR); |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     info, |     info, | ||||||
|     error, |     error, | ||||||
|   | |||||||
| @@ -52,6 +52,7 @@ async function sendMessage(client, message) { | |||||||
| async function sendMessageToAllClients(message) { | async function sendMessageToAllClients(message) { | ||||||
|     const jsonStr = JSON.stringify(message); |     const jsonStr = JSON.stringify(message); | ||||||
|  |  | ||||||
|  |     if (webSocketServer) { | ||||||
|         log.info("Sending message to all clients: " + jsonStr); |         log.info("Sending message to all clients: " + jsonStr); | ||||||
|  |  | ||||||
|         webSocketServer.clients.forEach(function each(client) { |         webSocketServer.clients.forEach(function each(client) { | ||||||
| @@ -59,6 +60,7 @@ async function sendMessageToAllClients(message) { | |||||||
|                 client.send(jsonStr); |                 client.send(jsonStr); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function sendPing(client, lastSentSyncId) { | async function sendPing(client, lastSentSyncId) { | ||||||
|   | |||||||
| @@ -299,7 +299,9 @@ function getNotePath(noteId) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity}) => { | eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_SYNCED], async ({entityName, entity}) => { | ||||||
|  |     // note that entity can also be just POJO without methods if coming from sync | ||||||
|  |  | ||||||
|     if (!loaded) { |     if (!loaded) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| const sql = require('./sql'); | const sql = require('./sql'); | ||||||
|  | const sqlInit = require('./sql_init'); | ||||||
| const optionService = require('./options'); | const optionService = require('./options'); | ||||||
| const dateUtils = require('./date_utils'); | const dateUtils = require('./date_utils'); | ||||||
| const syncTableService = require('./sync_table'); | const syncTableService = require('./sync_table'); | ||||||
| @@ -153,7 +154,8 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {}) | |||||||
|             noteId: note.noteId, |             noteId: note.noteId, | ||||||
|             type: attr.type, |             type: attr.type, | ||||||
|             name: attr.name, |             name: attr.name, | ||||||
|             value: attr.value |             value: attr.value, | ||||||
|  |             isInheritable: !!attr.isInheritable | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -357,10 +359,14 @@ async function deleteNote(branch) { | |||||||
|     const notDeletedBranches = await note.getBranches(); |     const notDeletedBranches = await note.getBranches(); | ||||||
|  |  | ||||||
|     if (notDeletedBranches.length === 0) { |     if (notDeletedBranches.length === 0) { | ||||||
|         note.isDeleted = true; |         // maybe a bit counter-intuitively, protected notes can be deleted also outside of protected session | ||||||
|         // we don't reset content here, that's postponed and done later to give the user |         // this is because protected notes offer only confidentiality which makes some things simpler - e.g. deletion UI | ||||||
|         // a chance to correct a mistake |         // to allow this, we just set the isDeleted flag, otherwise saving would fail because of attempt to encrypt | ||||||
|         await note.save(); |         // content with non-existent protected session key | ||||||
|  |         // we don't reset content here, that's postponed and done later to give the user a chance to correct a mistake | ||||||
|  |         await sql.execute("UPDATE notes SET isDeleted = 1 WHERE noteId = ?", [note.noteId]); | ||||||
|  |         // need to manually trigger sync since it's not taken care of by note save | ||||||
|  |         await syncTableService.addNoteSync(note.noteId); | ||||||
|  |  | ||||||
|         for (const noteRevision of await note.getRevisions()) { |         for (const noteRevision of await note.getRevisions()) { | ||||||
|             await noteRevision.save(); |             await noteRevision.save(); | ||||||
| @@ -403,10 +409,12 @@ async function cleanupDeletedNotes() { | |||||||
|     await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]); |     await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]); | ||||||
| } | } | ||||||
|  |  | ||||||
| // first cleanup kickoff 5 minutes after startup | sqlInit.dbReady.then(() => { | ||||||
| setTimeout(cls.wrap(cleanupDeletedNotes), 5 * 60 * 1000); |     // first cleanup kickoff 5 minutes after startup | ||||||
|  |     setTimeout(cls.wrap(cleanupDeletedNotes), 5 * 60 * 1000); | ||||||
|  |  | ||||||
| setInterval(cls.wrap(cleanupDeletedNotes), 4 * 3600 * 1000); |     setInterval(cls.wrap(cleanupDeletedNotes), 4 * 3600 * 1000); | ||||||
|  | }); | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     createNewNote, |     createNewNote, | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const url = require('url'); |  | ||||||
| const log = require('./log'); | const log = require('./log'); | ||||||
| const sql = require('./sql'); | const sql = require('./sql'); | ||||||
| const sqlInit = require('./sql_init'); | const sqlInit = require('./sql_init'); | ||||||
| @@ -99,6 +98,16 @@ async function doLogin() { | |||||||
|  |  | ||||||
|     syncContext.sourceId = resp.sourceId; |     syncContext.sourceId = resp.sourceId; | ||||||
|  |  | ||||||
|  |     const lastSyncedPull = await getLastSyncedPull(); | ||||||
|  |  | ||||||
|  |     // this is important in a scenario where we setup the sync by manually copying the document | ||||||
|  |     // lastSyncedPull then could be pretty off for the newly cloned client | ||||||
|  |     if (lastSyncedPull > resp.maxSyncId) { | ||||||
|  |         log.info(`Lowering last synced pull from ${lastSyncedPull} to ${resp.maxSyncId}`); | ||||||
|  |  | ||||||
|  |         await setLastSyncedPull(resp.maxSyncId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return syncContext; |     return syncContext; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -256,7 +265,7 @@ async function getEntityRow(entityName, entityId) { | |||||||
|             && entity.content !== null |             && entity.content !== null | ||||||
|             && (entity.type === 'file' || entity.type === 'image')) { |             && (entity.type === 'file' || entity.type === 'image')) { | ||||||
|  |  | ||||||
|             entity.content = entity.content.toString("binary"); |             entity.content = entity.content.toString("base64"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return entity; |         return entity; | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ const sql = require('./sql'); | |||||||
| const log = require('./log'); | const log = require('./log'); | ||||||
| const eventLogService = require('./event_log'); | const eventLogService = require('./event_log'); | ||||||
| const syncTableService = require('./sync_table'); | const syncTableService = require('./sync_table'); | ||||||
|  | const eventService = require('./events'); | ||||||
|  |  | ||||||
| async function updateEntity(sync, entity, sourceId) { | async function updateEntity(sync, entity, sourceId) { | ||||||
|     const {entityName} = sync; |     const {entityName} = sync; | ||||||
| @@ -36,11 +37,20 @@ async function updateEntity(sync, entity, sourceId) { | |||||||
|     else { |     else { | ||||||
|         throw new Error(`Unrecognized entity type ${entityName}`); |         throw new Error(`Unrecognized entity type ${entityName}`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // currently making exception for protected notes and note revisions because here | ||||||
|  |     // the title and content are not available decrypted as listeners would expect | ||||||
|  |     if ((entityName !== 'notes' && entityName !== 'note_revisions') || !entity.isProtected) { | ||||||
|  |         await eventService.emit(eventService.ENTITY_SYNCED, { | ||||||
|  |             entityName, | ||||||
|  |             entity | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function deserializeNoteContentBuffer(note) { | function deserializeNoteContentBuffer(note) { | ||||||
|     if (note.content !== null && (note.type === 'file' || note.type === 'image')) { |     if (note.content !== null && (note.type === 'file' || note.type === 'image')) { | ||||||
|         note.content = new Buffer(note.content, 'binary'); |         note.content = Buffer.from(note.content, 'base64'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ | |||||||
|         <div id="history-navigation" style="display: none;"> |         <div id="history-navigation" style="display: none;"> | ||||||
|             <a id="history-back-button" title="Go to previous note." class="icon-action jam jam-arrow-square-left"></a> |             <a id="history-back-button" title="Go to previous note." class="icon-action jam jam-arrow-square-left"></a> | ||||||
|  |  | ||||||
|                 |               | ||||||
|  |  | ||||||
|             <a id="history-forward-button" title="Go to next note." class="icon-action jam jam-arrow-square-right"></a> |             <a id="history-forward-button" title="Go to next note." class="icon-action jam jam-arrow-square-right"></a> | ||||||
|         </div> |         </div> | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ | |||||||
|                 <div id="confirm-dialog-custom"></div> |                 <div id="confirm-dialog-custom"></div> | ||||||
|             </div> |             </div> | ||||||
|             <div class="modal-footer"> |             <div class="modal-footer"> | ||||||
|                 <button class="btn btn-default btn-sm" id="confirm-dialog-cancel-button">Cancel</button> |                 <button class="btn btn-secondary btn-sm" id="confirm-dialog-cancel-button">Cancel</button> | ||||||
|  |  | ||||||
|                   |                   | ||||||
|  |  | ||||||
|   | |||||||
| @@ -108,7 +108,7 @@ | |||||||
|                                 <div style="display: flex; justify-content: space-between;"> |                                 <div style="display: flex; justify-content: space-between;"> | ||||||
|                                     <button class="btn btn-primary">Save</button> |                                     <button class="btn btn-primary">Save</button> | ||||||
|  |  | ||||||
|                                     <button class="btn btn-default" type="button" data-help-page="Protected-notes">Help</button> |                                     <button class="btn btn-secondary" type="button" data-help-page="Protected-notes">Help</button> | ||||||
|                                 </div> |                                 </div> | ||||||
|                             </form> |                             </form> | ||||||
|                         </div> |                         </div> | ||||||
| @@ -125,7 +125,7 @@ | |||||||
|                                 <div style="display: flex; justify-content: space-between;"> |                                 <div style="display: flex; justify-content: space-between;"> | ||||||
|                                     <button class="btn btn-primary">Save</button> |                                     <button class="btn btn-primary">Save</button> | ||||||
|  |  | ||||||
|                                     <button class="btn btn-default" type="button" data-help-page="Note-revisions">Help</button> |                                     <button class="btn btn-secondary" type="button" data-help-page="Note-revisions">Help</button> | ||||||
|                                 </div> |                                 </div> | ||||||
|                             </form> |                             </form> | ||||||
|                         </div> |                         </div> | ||||||
| @@ -154,7 +154,7 @@ | |||||||
|                                 <div style="display: flex; justify-content: space-between;"> |                                 <div style="display: flex; justify-content: space-between;"> | ||||||
|                                     <button class="btn btn-primary">Save</button> |                                     <button class="btn btn-primary">Save</button> | ||||||
|  |  | ||||||
|                                     <button class="btn btn-default" type="button" data-help-page="Synchronization">Help</button> |                                     <button class="btn btn-secondary" type="button" data-help-page="Synchronization">Help</button> | ||||||
|                                 </div> |                                 </div> | ||||||
|                             </form> |                             </form> | ||||||
|  |  | ||||||
| @@ -164,24 +164,24 @@ | |||||||
|  |  | ||||||
|                             <p>This will test connection and handshake to the sync server. If sync server isn't initialized, this will set it up to sync with local document.</p> |                             <p>This will test connection and handshake to the sync server. If sync server isn't initialized, this will set it up to sync with local document.</p> | ||||||
|  |  | ||||||
|                             <button id="test-sync-button" class="btn btn-default">Test sync</button> |                             <button id="test-sync-button" class="btn btn-secondary">Test sync</button> | ||||||
|                         </div> |                         </div> | ||||||
|  |  | ||||||
|                         <div id="advanced" class="tab-pane"> |                         <div id="advanced" class="tab-pane"> | ||||||
|                             <h4 style="margin-top: 0px;">Sync</h4> |                             <h4 style="margin-top: 0px;">Sync</h4> | ||||||
|                             <button id="force-full-sync-button" class="btn btn-default">Force full sync</button> |                             <button id="force-full-sync-button" class="btn btn-secondary">Force full sync</button> | ||||||
|  |  | ||||||
|                             <br/> |                             <br/> | ||||||
|                             <br/> |                             <br/> | ||||||
|  |  | ||||||
|                             <button id="fill-sync-rows-button" class="btn btn-default">Fill sync rows</button> |                             <button id="fill-sync-rows-button" class="btn btn-secondary">Fill sync rows</button> | ||||||
|  |  | ||||||
|                             <br/> |                             <br/> | ||||||
|                             <br/> |                             <br/> | ||||||
|  |  | ||||||
|                             <h4>Debugging</h4> |                             <h4>Debugging</h4> | ||||||
|  |  | ||||||
|                             <button id="anonymize-button" class="btn btn-default">Save anonymized database</button><br/><br/> |                             <button id="anonymize-button" class="btn btn-secondary">Save anonymized database</button><br/><br/> | ||||||
|  |  | ||||||
|                             <p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata) |                             <p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata) | ||||||
|                                 for sharing online for debugging purposes without fear of leaking your personal data.</p> |                                 for sharing online for debugging purposes without fear of leaking your personal data.</p> | ||||||
| @@ -190,7 +190,7 @@ | |||||||
|  |  | ||||||
|                             <p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p> |                             <p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p> | ||||||
|  |  | ||||||
|                             <button id="vacuum-database-button" class="btn btn-default">Vacuum database</button> |                             <button id="vacuum-database-button" class="btn btn-secondary">Vacuum database</button> | ||||||
|                         </div> |                         </div> | ||||||
|  |  | ||||||
|                         <div id="about" class="tab-pane"> |                         <div id="about" class="tab-pane"> | ||||||
| @@ -216,6 +216,11 @@ | |||||||
|                                     <th>Build revision:</th> |                                     <th>Build revision:</th> | ||||||
|                                     <td><a href="" target="_blank" id="build-revision"></a></td> |                                     <td><a href="" target="_blank" id="build-revision"></a></td> | ||||||
|                                 </tr> |                                 </tr> | ||||||
|  |  | ||||||
|  |                                 <tr> | ||||||
|  |                                     <th>Data directory:</th> | ||||||
|  |                                     <td id="data-directory"></td> | ||||||
|  |                                 </tr> | ||||||
|                             </table> |                             </table> | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
| <div class="container"> | <div class="container"> | ||||||
|     <div class="col-md-5 offset-md-3" style="padding-top: 25px;"> |     <div class="col-xs-12 col-sm-10 col-md-6 col-lg-4 col-xl-4 mx-auto" style="padding-top: 25px;"> | ||||||
|         <h1>Trilium login</h1> |         <h1>Trilium login</h1> | ||||||
|  |  | ||||||
|         <% if (failedAuth) { %> |         <% if (failedAuth) { %> | ||||||
| @@ -60,6 +60,8 @@ | |||||||
|         device = /Mobi/.test(navigator.userAgent) ? "mobile" : "desktop"; |         device = /Mobi/.test(navigator.userAgent) ? "mobile" : "desktop"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     console.log("Setting device cookie to:", device); | ||||||
|  |  | ||||||
|     setCookie("trilium-device", device); |     setCookie("trilium-device", device); | ||||||
|  |  | ||||||
|     function setCookie(name, value) { |     function setCookie(name, value) { | ||||||
|   | |||||||
| @@ -2,10 +2,12 @@ | |||||||
| <html lang="en"> | <html lang="en"> | ||||||
| <head> | <head> | ||||||
|     <meta charset="utf-8"> |     <meta charset="utf-8"> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> | ||||||
|     <title>Setup</title> |     <title>Setup</title> | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
| <div id="setup-dialog" style="width: 700px; margin: auto; padding-top: 50px; display:none; font-size: larger;"> | <div class="container"> | ||||||
|  |     <div id="setup-dialog" class="col-md-12 col-lg-8 col-xl-6 mx-auto" style="padding-top: 25px;"> | ||||||
|         <h1>Trilium Notes setup</h1> |         <h1>Trilium Notes setup</h1> | ||||||
|  |  | ||||||
|         <div class="alert alert-warning" id="alert" style="display: none;"> |         <div class="alert alert-warning" id="alert" style="display: none;"> | ||||||
| @@ -13,15 +15,15 @@ | |||||||
|  |  | ||||||
|         <div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;"> |         <div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;"> | ||||||
|             <div class="radio" style="margin-bottom: 15px;"> |             <div class="radio" style="margin-bottom: 15px;"> | ||||||
|             <label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupNewDocument"> |                 <label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType"> | ||||||
|                     I'm a new user and I want to create new Trilium document for my notes</label> |                     I'm a new user and I want to create new Trilium document for my notes</label> | ||||||
|             </div> |             </div> | ||||||
|             <div class="radio" data-bind="if: instanceType == 'server'" style="margin-bottom: 15px;"> |             <div class="radio" data-bind="if: instanceType == 'server'" style="margin-bottom: 15px;"> | ||||||
|             <label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupSyncFromDesktop"> |                 <label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType"> | ||||||
|                     I have desktop instance already and I want to setup sync with it</label> |                     I have desktop instance already and I want to setup sync with it</label> | ||||||
|             </div> |             </div> | ||||||
|             <div class="radio" data-bind="if: instanceType == 'desktop'" style="margin-bottom: 15px;"> |             <div class="radio" data-bind="if: instanceType == 'desktop'" style="margin-bottom: 15px;"> | ||||||
|             <label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupSyncFromServer"> |                 <label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType"> | ||||||
|                     I have server instance up and I want to setup sync with it</label> |                     I have server instance up and I want to setup sync with it</label> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
| @@ -47,7 +49,7 @@ | |||||||
|                 <input type="password" class="form-control" data-bind="value: password2" placeholder="Password"> |                 <input type="password" class="form-control" data-bind="value: password2" placeholder="Password"> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|         <button type="button" data-bind="click: back" class="btn btn-default">Back</button> |             <button type="button" data-bind="click: back" class="btn btn-secondary">Back</button> | ||||||
|  |  | ||||||
|               |               | ||||||
|  |  | ||||||
| @@ -68,7 +70,7 @@ | |||||||
|                 <li>once you've done all this, click <a href="/">here</a></li> |                 <li>once you've done all this, click <a href="/">here</a></li> | ||||||
|             </ol> |             </ol> | ||||||
|  |  | ||||||
|         <button type="button" data-bind="click: back" class="btn btn-default">Back</button> |             <button type="button" data-bind="click: back" class="btn btn-secondary">Back</button> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <div data-bind="visible: step() == 'sync-from-server'"> |         <div data-bind="visible: step() == 'sync-from-server'"> | ||||||
| @@ -95,7 +97,7 @@ | |||||||
|                 <input type="password" id="password1" class="form-control" data-bind="value: password1" placeholder="Password"> |                 <input type="password" id="password1" class="form-control" data-bind="value: password1" placeholder="Password"> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|         <button type="button" data-bind="click: back" class="btn btn-default">Back</button> |             <button type="button" data-bind="click: back" class="btn btn-secondary">Back</button> | ||||||
|  |  | ||||||
|               |               | ||||||
|  |  | ||||||
| @@ -111,6 +113,7 @@ | |||||||
|                 Outstanding sync items: <strong id="outstanding-syncs">N/A</strong> |                 Outstanding sync items: <strong id="outstanding-syncs">N/A</strong> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|  |     </div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <script type="text/javascript"> | <script type="text/javascript"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user