mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Compare commits
	
		
			27 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 2b32addade | ||
|  | 0b251530fa | ||
|  | f5b933149a | ||
|  | 48bbfb8bdb | ||
|  | 889971c4d6 | ||
|  | 0722494d41 | ||
|  | 4b977a3306 | ||
|  | 3ff3021acd | ||
|  | 99e56a9c42 | ||
|  | 77279dfe16 | ||
|  | 93f8050454 | ||
|  | 31cfede7a7 | ||
|  | c8ec86e537 | ||
|  | 05aee884b6 | ||
|  | 012ba9e060 | ||
|  | 8e8fd88857 | ||
|  | 523ccdad6b | ||
|  | ded3f605be | ||
|  | 030d12a465 | ||
|  | 4d15628840 | ||
|  | 81b849898c | ||
|  | 3824486b85 | ||
|  | 081ab00a0a | ||
|  | 04f6af5c9a | ||
|  | 4dc1f1f6eb | ||
|  | 3930a02123 | ||
|  | 3112de105e | 
							
								
								
									
										7
									
								
								.gitpod.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.gitpod.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | tasks: | ||||||
|  |     - before: nvm install 10 && nvm use 10 | ||||||
|  |       init: npm install | ||||||
|  |       command: npm run start | ||||||
|  | ports: | ||||||
|  |     - port: 8080 | ||||||
|  |       onOpen: open-preview | ||||||
| @@ -17,6 +17,7 @@ RUN set -x \ | |||||||
|         libtool \ |         libtool \ | ||||||
|         make \ |         make \ | ||||||
|         nasm \ |         nasm \ | ||||||
|  |         libpng-dev \ | ||||||
|     && npm install --production \ |     && npm install --production \ | ||||||
|     && apk del .build-dependencies |     && apk del .build-dependencies | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							| @@ -35,3 +35,15 @@ Trilium is provided as either desktop application (Linux, Windows, Mac) or web a | |||||||
| [See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/) | [See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/) | ||||||
|  |  | ||||||
| You can also read [Patterns of personal knowledge base](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium. | You can also read [Patterns of personal knowledge base](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium. | ||||||
|  |  | ||||||
|  | ## Contribute | ||||||
|  |  | ||||||
|  | Use a browser based dev environment | ||||||
|  |  | ||||||
|  | [](https://gitpod.io/#https://github.com/zadam/trilium) | ||||||
|  |  | ||||||
|  | Or clone locally and run | ||||||
|  | ``` | ||||||
|  | npm install | ||||||
|  | npm run start | ||||||
|  | ``` | ||||||
| @@ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.27.3", |   "version": "0.27.4", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
| @@ -33,6 +33,7 @@ | |||||||
|     "electron-in-page-search": "1.3.2", |     "electron-in-page-search": "1.3.2", | ||||||
|     "express": "4.16.4", |     "express": "4.16.4", | ||||||
|     "express-session": "1.15.6", |     "express-session": "1.15.6", | ||||||
|  |     "file-type": "10.7.0", | ||||||
|     "fs-extra": "7.0.1", |     "fs-extra": "7.0.1", | ||||||
|     "get-port": "4.1.0", |     "get-port": "4.1.0", | ||||||
|     "helmet": "3.15.0", |     "helmet": "3.15.0", | ||||||
|   | |||||||
| @@ -56,6 +56,9 @@ class Note extends Entity { | |||||||
|     setContent(content) { |     setContent(content) { | ||||||
|         this.content = content; |         this.content = content; | ||||||
|  |  | ||||||
|  |         // if parsing below is not successful then there's no jsonContent as opposed to still having the old unupdated ones | ||||||
|  |         delete this.jsonContent; | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             this.jsonContent = JSON.parse(this.content); |             this.jsonContent = JSON.parse(this.content); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -36,6 +36,7 @@ import hoistedNoteService from './services/hoisted_note.js'; | |||||||
| import noteTypeService from './services/note_type.js'; | import noteTypeService from './services/note_type.js'; | ||||||
| import linkService from './services/link.js'; | import linkService from './services/link.js'; | ||||||
| import noteAutocompleteService from './services/note_autocomplete.js'; | import noteAutocompleteService from './services/note_autocomplete.js'; | ||||||
|  | import macInit from './services/mac_init.js'; | ||||||
|  |  | ||||||
| // required for CKEditor image upload plugin | // required for CKEditor image upload plugin | ||||||
| window.glob.getCurrentNode = treeService.getCurrentNode; | window.glob.getCurrentNode = treeService.getCurrentNode; | ||||||
| @@ -110,28 +111,6 @@ if (utils.isElectron()) { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| function exec(cmd) { |  | ||||||
|     document.execCommand(cmd); |  | ||||||
|  |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| if (utils.isElectron() && utils.isMac()) { |  | ||||||
|     utils.bindShortcut('ctrl+c', () => exec("copy")); |  | ||||||
|     utils.bindShortcut('ctrl+v', () => exec('paste')); |  | ||||||
|     utils.bindShortcut('ctrl+x', () => exec('cut')); |  | ||||||
|     utils.bindShortcut('ctrl+a', () => exec('selectAll')); |  | ||||||
|     utils.bindShortcut('ctrl+z', () => exec('undo')); |  | ||||||
|     utils.bindShortcut('ctrl+y', () => exec('redo')); |  | ||||||
|  |  | ||||||
|     utils.bindShortcut('meta+c', () => exec("copy")); |  | ||||||
|     utils.bindShortcut('meta+v', () => exec('paste')); |  | ||||||
|     utils.bindShortcut('meta+x', () => exec('cut')); |  | ||||||
|     utils.bindShortcut('meta+a', () => exec('selectAll')); |  | ||||||
|     utils.bindShortcut('meta+z', () => exec('undo')); |  | ||||||
|     utils.bindShortcut('meta+y', () => exec('redo')); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| $("#export-note-button").click(function () { | $("#export-note-button").click(function () { | ||||||
|     if ($(this).hasClass("disabled")) { |     if ($(this).hasClass("disabled")) { | ||||||
|         return; |         return; | ||||||
| @@ -140,6 +119,8 @@ $("#export-note-button").click(function () { | |||||||
|     exportDialog.showDialog('single'); |     exportDialog.showDialog('single'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | macInit.init(); | ||||||
|  |  | ||||||
| treeService.showTree(); | treeService.showTree(); | ||||||
|  |  | ||||||
| entrypoints.registerEntrypoints(); | entrypoints.registerEntrypoints(); | ||||||
|   | |||||||
| @@ -6,8 +6,6 @@ const $dialog = $("#jump-to-note-dialog"); | |||||||
| const $autoComplete = $("#jump-to-note-autocomplete"); | const $autoComplete = $("#jump-to-note-autocomplete"); | ||||||
| const $showInFullTextButton = $("#show-in-full-text-button"); | const $showInFullTextButton = $("#show-in-full-text-button"); | ||||||
|  |  | ||||||
| $dialog.on("shown.bs.modal", e => $autoComplete.focus()); |  | ||||||
|  |  | ||||||
| async function showDialog() { | async function showDialog() { | ||||||
|     glob.activeDialog = $dialog; |     glob.activeDialog = $dialog; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -117,10 +117,6 @@ function registerEntrypoints() { | |||||||
|  |  | ||||||
|     utils.bindShortcut('ctrl+f', openInPageSearch); |     utils.bindShortcut('ctrl+f', openInPageSearch); | ||||||
|  |  | ||||||
|     if (utils.isMac()) { |  | ||||||
|         utils.bindShortcut('meta+f', openInPageSearch); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // FIXME: do we really need these at this point? |     // FIXME: do we really need these at this point? | ||||||
|     utils.bindShortcut("ctrl+shift+up", () => { |     utils.bindShortcut("ctrl+shift+up", () => { | ||||||
|         const node = treeService.getCurrentNode(); |         const node = treeService.getCurrentNode(); | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								src/public/javascripts/services/mac_init.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/public/javascripts/services/mac_init.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | /** | ||||||
|  |  * Mac specific initialization | ||||||
|  |  */ | ||||||
|  | import utils from "./utils.js"; | ||||||
|  |  | ||||||
|  | function init() { | ||||||
|  |     if (utils.isElectron() && utils.isMac()) { | ||||||
|  |         utils.bindShortcut('meta+c', () => exec("copy")); | ||||||
|  |         utils.bindShortcut('meta+v', () => exec('paste')); | ||||||
|  |         utils.bindShortcut('meta+x', () => exec('cut')); | ||||||
|  |         utils.bindShortcut('meta+a', () => exec('selectAll')); | ||||||
|  |         utils.bindShortcut('meta+z', () => exec('undo')); | ||||||
|  |         utils.bindShortcut('meta+y', () => exec('redo')); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function exec(cmd) { | ||||||
|  |     document.execCommand(cmd); | ||||||
|  |  | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     init | ||||||
|  | } | ||||||
| @@ -28,7 +28,7 @@ function clearText($el) { | |||||||
| function showRecentNotes($el) { | function showRecentNotes($el) { | ||||||
|     $el.setSelectedPath(""); |     $el.setSelectedPath(""); | ||||||
|     $el.autocomplete("val", ""); |     $el.autocomplete("val", ""); | ||||||
|     $el.autocomplete("open"); |     $el.focus(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function initNoteAutocomplete($el, options) { | function initNoteAutocomplete($el, options) { | ||||||
| @@ -61,7 +61,13 @@ function initNoteAutocomplete($el, options) { | |||||||
|  |  | ||||||
|         $clearTextButton.click(() => clearText($el)); |         $clearTextButton.click(() => clearText($el)); | ||||||
|  |  | ||||||
|         $showRecentNotesButton.click(() => showRecentNotes($el)); |         $showRecentNotesButton.click(e => { | ||||||
|  |             showRecentNotes($el); | ||||||
|  |  | ||||||
|  |             // this will cause the click not give focus to the "show recent notes" button | ||||||
|  |             // this is important because otherwise input will lose focus immediatelly and not show the results | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  |  | ||||||
|         $goToSelectedNoteButton.click(() => { |         $goToSelectedNoteButton.click(() => { | ||||||
|             if ($el.hasClass("disabled")) { |             if ($el.hasClass("disabled")) { | ||||||
|   | |||||||
| @@ -1,10 +1,13 @@ | |||||||
| import noteDetailService from "./note_detail.js"; | import noteDetailService from "./note_detail.js"; | ||||||
|  | import treeService from "./tree.js"; | ||||||
|  | import infoService from './info.js'; | ||||||
|  |  | ||||||
| const $searchString = $("#search-string"); | const $searchString = $("#search-string"); | ||||||
| const $component = $('#note-detail-search'); | const $component = $('#note-detail-search'); | ||||||
|  | const $refreshButton = $('#note-detail-search-refresh-results-button'); | ||||||
|  |  | ||||||
| function getContent() { | function getContent() { | ||||||
|     JSON.stringify({ |     return JSON.stringify({ | ||||||
|         searchString: $searchString.val() |         searchString: $searchString.val() | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| @@ -25,6 +28,14 @@ function show() { | |||||||
|     $searchString.on('input', noteDetailService.noteChanged); |     $searchString.on('input', noteDetailService.noteChanged); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | $refreshButton.click(async () => { | ||||||
|  |     await noteDetailService.saveNoteIfChanged(); | ||||||
|  |  | ||||||
|  |     treeService.reload(); | ||||||
|  |  | ||||||
|  |     infoService.showMessage('Tree has been refreshed.'); | ||||||
|  | }); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     getContent, |     getContent, | ||||||
|     show, |     show, | ||||||
|   | |||||||
| @@ -87,7 +87,7 @@ $searchInput.keyup(e => { | |||||||
|     if (e && e.which === $.ui.keyCode.ENTER) { |     if (e && e.which === $.ui.keyCode.ENTER) { | ||||||
|         doSearch(); |         doSearch(); | ||||||
|     } |     } | ||||||
| }).focus(); | }); | ||||||
|  |  | ||||||
| $doSearchButton.click(() => doSearch()); // keep long form because of argument | $doSearchButton.click(() => doSearch()); // keep long form because of argument | ||||||
| $resetSearchButton.click(resetSearch); | $resetSearchButton.click(resetSearch); | ||||||
|   | |||||||
| @@ -94,9 +94,18 @@ async function expandToNote(notePath, expandOpts) { | |||||||
|  |  | ||||||
|     const noteId = treeUtils.getNoteIdFromNotePath(notePath); |     const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||||
|  |  | ||||||
|  |     const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); | ||||||
|  |     let hoistedNoteFound = false; | ||||||
|  |  | ||||||
|     let parentNoteId = null; |     let parentNoteId = null; | ||||||
|  |  | ||||||
|     for (const childNoteId of runPath) { |     for (const childNoteId of runPath) { | ||||||
|  |         if (childNoteId === hoistedNoteId) { | ||||||
|  |             hoistedNoteFound = true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // we expand only after hoisted note since before then nodes are not actually present in the tree | ||||||
|  |         if (hoistedNoteFound) { | ||||||
|             // for first node (!parentNoteId) it doesn't matter which node is found |             // for first node (!parentNoteId) it doesn't matter which node is found | ||||||
|             let node = getNode(childNoteId, parentNoteId); |             let node = getNode(childNoteId, parentNoteId); | ||||||
|  |  | ||||||
| @@ -117,10 +126,10 @@ async function expandToNote(notePath, expandOpts) { | |||||||
|  |  | ||||||
|             if (childNoteId === noteId) { |             if (childNoteId === noteId) { | ||||||
|                 return node; |                 return node; | ||||||
|         } |             } else { | ||||||
|         else { |  | ||||||
|                 await node.setExpanded(true, expandOpts); |                 await node.setExpanded(true, expandOpts); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         parentNoteId = childNoteId; |         parentNoteId = childNoteId; | ||||||
|     } |     } | ||||||
| @@ -129,9 +138,12 @@ async function expandToNote(notePath, expandOpts) { | |||||||
| async function activateNote(notePath, noteLoadedListener) { | async function activateNote(notePath, noteLoadedListener) { | ||||||
|     utils.assertArguments(notePath); |     utils.assertArguments(notePath); | ||||||
|  |  | ||||||
|  |     // notePath argument can contain only noteId which is not good when hoisted since | ||||||
|  |     // then we need to check the whole note path | ||||||
|  |     const runNotePath = await getRunPath(notePath); | ||||||
|     const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); |     const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); | ||||||
|  |  | ||||||
|     if (hoistedNoteId !== 'root' && !notePath.includes(hoistedNoteId)) { |     if (hoistedNoteId !== 'root' && !runNotePath.includes(hoistedNoteId)) { | ||||||
|         if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree. Do you want to unhoist?")) { |         if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree. Do you want to unhoist?")) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -352,6 +364,7 @@ function clearSelectedNodes() { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function treeInitialized() { | async function treeInitialized() { | ||||||
|  |     // - is used in mobile to indicate that we don't want to activate any note after load | ||||||
|     if (startNotePath === '-') { |     if (startNotePath === '-') { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| @@ -363,7 +376,6 @@ async function treeInitialized() { | |||||||
|         startNotePath = null; |         startNotePath = null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // - is used in mobile to indicate that we don't want to activate any note after load |  | ||||||
|     if (startNotePath) { |     if (startNotePath) { | ||||||
|         const node = await activateNote(startNotePath); |         const node = await activateNote(startNotePath); | ||||||
|  |  | ||||||
| @@ -438,6 +450,16 @@ function initFancyTree(tree) { | |||||||
|  |  | ||||||
|                 $span.append(unhoistButton); |                 $span.append(unhoistButton); | ||||||
|             } |             } | ||||||
|  |         }, | ||||||
|  |         // this is done to automatically lazy load all expanded search notes after tree load | ||||||
|  |         loadChildren: function(event, data) { | ||||||
|  |             data.node.visit(function(subNode){ | ||||||
|  |                 // Load all lazy/unloaded child nodes | ||||||
|  |                 // (which will trigger `loadChildren` recursively) | ||||||
|  |                 if( subNode.isUndefined() && subNode.isExpanded() ) { | ||||||
|  |                     subNode.load(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -86,7 +86,7 @@ async function prepareNode(branch) { | |||||||
|         extraClasses: await getExtraClasses(note), |         extraClasses: await getExtraClasses(note), | ||||||
|         icon: await getIcon(note), |         icon: await getIcon(note), | ||||||
|         refKey: note.noteId, |         refKey: note.noteId, | ||||||
|         expanded: (note.type !== 'search' && branch.isExpanded) || hoistedNoteId === note.noteId |         expanded: branch.isExpanded || hoistedNoteId === note.noteId | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     if (note.hasChildren() || note.type === 'search') { |     if (note.hasChildren() || note.type === 'search') { | ||||||
|   | |||||||
| @@ -137,6 +137,11 @@ function randomString(len) { | |||||||
|  |  | ||||||
| function bindShortcut(keyboardShortcut, handler) { | function bindShortcut(keyboardShortcut, handler) { | ||||||
|     if (isDesktop()) { |     if (isDesktop()) { | ||||||
|  |         if (isMac()) { | ||||||
|  |             // use CMD (meta) instead of CTRL for all shortcuts | ||||||
|  |             keyboardShortcut = keyboardShortcut.replace("ctrl", "meta"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         $(document).bind('keydown', keyboardShortcut, e => { |         $(document).bind('keydown', keyboardShortcut, e => { | ||||||
|             handler(); |             handler(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
| import utils from "./services/utils.js"; | import utils from "./services/utils.js"; | ||||||
|  | import macInit from './services/mac_init.js'; | ||||||
|  |  | ||||||
|  | macInit.init(); | ||||||
|  |  | ||||||
| function SetupModel() { | function SetupModel() { | ||||||
|     if (syncInProgress) { |     if (syncInProgress) { | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ | |||||||
|     padding: 10px 0 10px 0; |     padding: 10px 0 10px 0; | ||||||
|     margin: 0 20px 0 10px; |     margin: 0 20px 0 10px; | ||||||
|     border: 1px solid #ddd; |     border: 1px solid #ddd; | ||||||
|     border-radius: 5px; |     border-radius: 7px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #context-menu-container { | #context-menu-container { | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ body { | |||||||
|     /* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter |     /* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter | ||||||
|        on the last line of the editor. */ |        on the last line of the editor. */ | ||||||
|     position: fixed; |     position: fixed; | ||||||
|  |     width: 100%; | ||||||
| } | } | ||||||
|  |  | ||||||
| #title-container { | #title-container { | ||||||
| @@ -16,6 +17,16 @@ body { | |||||||
|     flex-grow: 100; |     flex-grow: 100; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ul.fancytree-container { | ||||||
|  |     /* override specific size from fancytree.css */ | ||||||
|  |     font-family: inherit !important; | ||||||
|  |     font-size: inherit !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .fancytree-title { | ||||||
|  |     margin-left: 7px !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .fancytree-node:not(.fancytree-loading) .fancytree-expander { | .fancytree-node:not(.fancytree-loading) .fancytree-expander { | ||||||
|     background: none; |     background: none; | ||||||
|     width: auto; |     width: auto; | ||||||
| @@ -131,8 +142,21 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit | |||||||
|  |  | ||||||
| /* By default not focused active tree item is not easily visible, this makes it more visible */ | /* By default not focused active tree item is not easily visible, this makes it more visible */ | ||||||
| span.fancytree-active:not(.fancytree-focused) .fancytree-title { | span.fancytree-active:not(.fancytree-focused) .fancytree-title { | ||||||
|  |     background-color: #eee !important; | ||||||
|  |     border-color: #ddd !important; | ||||||
|  |     border-radius: 3px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | span.fancytree-active.fancytree-focused .fancytree-title { | ||||||
|     background-color: #ddd !important; |     background-color: #ddd !important; | ||||||
|     border-color: #555 !important; |     border-color: #bbb !important; | ||||||
|  |     border-radius: 3px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .fancytree-plain span.fancytree-node:hover span.fancytree-title { | ||||||
|  |     background-color: #eee !important; | ||||||
|  |     border-color: #bbb !important; | ||||||
|  |     border-radius: 3px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ui-autocomplete { | .ui-autocomplete { | ||||||
| @@ -366,7 +390,7 @@ div.ui-tooltip { | |||||||
|     height: 150px; |     height: 150px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .btn:not(.btn-primary):not(.btn-danger) { | .btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger) { | ||||||
|     border-color: #ddd; |     border-color: #ddd; | ||||||
|     background-color: #eee; |     background-color: #eee; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ const tarImportService = require('../../services/import/tar'); | |||||||
| const singleImportService = require('../../services/import/single'); | const singleImportService = require('../../services/import/single'); | ||||||
| const cls = require('../../services/cls'); | const cls = require('../../services/cls'); | ||||||
| const path = require('path'); | const path = require('path'); | ||||||
|  | const noteCacheService = require('../../services/note_cache'); | ||||||
|  |  | ||||||
| async function importToBranch(req) { | async function importToBranch(req) { | ||||||
|     const parentNoteId = req.params.parentNoteId; |     const parentNoteId = req.params.parentNoteId; | ||||||
| @@ -28,24 +29,32 @@ async function importToBranch(req) { | |||||||
|     // and may produce unintended consequences |     // and may produce unintended consequences | ||||||
|     cls.disableEntityEvents(); |     cls.disableEntityEvents(); | ||||||
|  |  | ||||||
|  |     let note; // typically root of the import - client can show it after finishing the import | ||||||
|  |  | ||||||
|     if (extension === '.tar') { |     if (extension === '.tar') { | ||||||
|         return await tarImportService.importTar(file.buffer, parentNote); |         note = await tarImportService.importTar(file.buffer, parentNote); | ||||||
|     } |     } | ||||||
|     else if (extension === '.opml') { |     else if (extension === '.opml') { | ||||||
|         return await opmlImportService.importOpml(file.buffer, parentNote); |         note = await opmlImportService.importOpml(file.buffer, parentNote); | ||||||
|     } |     } | ||||||
|     else if (extension === '.md') { |     else if (extension === '.md') { | ||||||
|         return await singleImportService.importMarkdown(file, parentNote); |         note = await singleImportService.importMarkdown(file, parentNote); | ||||||
|     } |     } | ||||||
|     else if (extension === '.html' || extension === '.htm') { |     else if (extension === '.html' || extension === '.htm') { | ||||||
|         return await singleImportService.importHtml(file, parentNote); |         note = await singleImportService.importHtml(file, parentNote); | ||||||
|     } |     } | ||||||
|     else if (extension === '.enex') { |     else if (extension === '.enex') { | ||||||
|         return await enexImportService.importEnex(file, parentNote); |         note = await enexImportService.importEnex(file, parentNote); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         return [400, `Unrecognized extension ${extension}, must be .tar or .opml`]; |         return [400, `Unrecognized extension ${extension}, must be .tar or .opml`]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // import has deactivated note events so note cache is not updated | ||||||
|  |     // instead we force it to reload (can be async) | ||||||
|  |     noteCacheService.load(); | ||||||
|  |  | ||||||
|  |     return note; | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -23,7 +23,8 @@ async function loginSync(req) { | |||||||
|  |  | ||||||
|     const now = new Date(); |     const now = new Date(); | ||||||
|  |  | ||||||
|     if (Math.abs(timestamp.getTime() - now.getTime()) > 5000) { |     // login token is valid for 5 minutes | ||||||
|  |     if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) { | ||||||
|         return [400, { message: 'Auth request time is out of sync' }]; |         return [400, { message: 'Auth request time is out of sync' }]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,7 +19,6 @@ async function anonymize() { | |||||||
|     await db.run("UPDATE notes SET title = 'title', content = 'text'"); |     await db.run("UPDATE notes SET title = 'title', content = 'text'"); | ||||||
|     await db.run("UPDATE note_revisions SET title = 'title', content = 'text'"); |     await db.run("UPDATE note_revisions SET title = 'title', content = 'text'"); | ||||||
|     await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL"); |     await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL"); | ||||||
|     await db.run("UPDATE images SET data = NULL"); |  | ||||||
|     await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN  |     await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN  | ||||||
|                     ('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',  |                     ('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',  | ||||||
|                      'passwordVerificationSalt', 'passwordDerivedKeySalt')`); |                      'passwordVerificationSalt', 'passwordDerivedKeySalt')`); | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2019-01-05T22:48:11+01:00", buildRevision: "9fca7f09a564c719c834d38d76c3b595c8579b2a" }; | module.exports = { buildDate:"2019-01-10T21:31:30+01:00", buildRevision: "0b251530fa0ee61edc8dcc9235033abb73afc614" }; | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const repository = require('./repository'); | const repository = require('./repository'); | ||||||
|  | const log = require('./log'); | ||||||
| const protectedSessionService = require('./protected_session'); | const protectedSessionService = require('./protected_session'); | ||||||
| const noteService = require('./notes'); | const noteService = require('./notes'); | ||||||
| const imagemin = require('imagemin'); | const imagemin = require('imagemin'); | ||||||
| @@ -13,7 +14,13 @@ const sanitizeFilename = require('sanitize-filename'); | |||||||
|  |  | ||||||
| async function saveImage(buffer, originalName, parentNoteId) { | async function saveImage(buffer, originalName, parentNoteId) { | ||||||
|     const resizedImage = await resize(buffer); |     const resizedImage = await resize(buffer); | ||||||
|     const optimizedImage = await optimize(resizedImage); |     let optimizedImage; | ||||||
|  |     try { | ||||||
|  |       optimizedImage = await optimize(resizedImage); | ||||||
|  |     } catch (e) { | ||||||
|  |       log.error(e); | ||||||
|  |       optimizedImage = resizedImage; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const imageFormat = imageType(optimizedImage); |     const imageFormat = imageType(optimizedImage); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| const sax = require("sax"); | const sax = require("sax"); | ||||||
|  | const fileType = require('file-type'); | ||||||
| const stream = require('stream'); | const stream = require('stream'); | ||||||
| const xml2js = require('xml2js'); | const xml2js = require('xml2js'); | ||||||
| const log = require("../log"); | const log = require("../log"); | ||||||
| @@ -144,7 +145,7 @@ async function importEnex(file, parentNote) { | |||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|             else if (currentTag === 'mime') { |             else if (currentTag === 'mime') { | ||||||
|                 resource.mime = text; |                 resource.mime = text.toLowerCase(); | ||||||
|  |  | ||||||
|                 if (text.startsWith("image/")) { |                 if (text.startsWith("image/")) { | ||||||
|                     resource.title = "image"; |                     resource.title = "image"; | ||||||
| @@ -222,7 +223,26 @@ async function importEnex(file, parentNote) { | |||||||
|  |  | ||||||
|             const mediaRegex = new RegExp(`<en-media hash="${hash}"[^>]*>`, 'g'); |             const mediaRegex = new RegExp(`<en-media hash="${hash}"[^>]*>`, 'g'); | ||||||
|  |  | ||||||
|             if (resource.mime.startsWith("image/")) { |             const fileTypeFromBuffer = fileType(resource.content); | ||||||
|  |             if (fileTypeFromBuffer) { | ||||||
|  |               // If fileType returns something for buffer, then set the mime given | ||||||
|  |               resource.mime = fileTypeFromBuffer.mime; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const createResourceNote = async () => { | ||||||
|  |               const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, { | ||||||
|  |                 attributes: resource.attributes, | ||||||
|  |                 type: 'file', | ||||||
|  |                 mime: resource.mime | ||||||
|  |               })).note; | ||||||
|  |  | ||||||
|  |               const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`; | ||||||
|  |  | ||||||
|  |               noteEntity.content = noteEntity.content.replace(mediaRegex, resourceLink); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (["image/jpeg", "image/png", "image/gif"].includes(resource.mime)) { | ||||||
|  |               try { | ||||||
|                 const originalName = "image." + resource.mime.substr(6); |                 const originalName = "image." + resource.mime.substr(6); | ||||||
|  |  | ||||||
|                 const { url } = await imageService.saveImage(resource.content, originalName, noteEntity.noteId); |                 const { url } = await imageService.saveImage(resource.content, originalName, noteEntity.noteId); | ||||||
| @@ -236,17 +256,13 @@ async function importEnex(file, parentNote) { | |||||||
|                     // otherwise image would be removed since no note would include it |                     // otherwise image would be removed since no note would include it | ||||||
|                     note.content += imageLink; |                     note.content += imageLink; | ||||||
|                 } |                 } | ||||||
|  |               } catch (e) { | ||||||
|  |                 log.error("error when saving image from ENEX file: " + e); | ||||||
|  |                 await createResourceNote(); | ||||||
|  |               } | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|                 const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, { |               await createResourceNote(); | ||||||
|                     attributes: resource.attributes, |  | ||||||
|                     type: 'file', |  | ||||||
|                     mime: resource.mime |  | ||||||
|                 })).note; |  | ||||||
|  |  | ||||||
|                 const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`; |  | ||||||
|  |  | ||||||
|                 noteEntity.content = noteEntity.content.replace(mediaRegex, resourceLink); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -33,9 +33,21 @@ async function load() { | |||||||
|  |  | ||||||
|     archived = await sql.getMap(`SELECT noteId, isInheritable FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name = 'archived'`); |     archived = await sql.getMap(`SELECT noteId, isInheritable FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name = 'archived'`); | ||||||
|  |  | ||||||
|  |     if (protectedSessionService.isProtectedSessionAvailable()) { | ||||||
|  |         await loadProtectedNotes(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     loaded = true; |     loaded = true; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function loadProtectedNotes() { | ||||||
|  |     protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`); | ||||||
|  |  | ||||||
|  |     for (const noteId in protectedNoteTitles) { | ||||||
|  |         protectedNoteTitles[noteId] = protectedSessionService.decryptNoteTitle(noteId, protectedNoteTitles[noteId]); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| function highlightResults(results, allTokens) { | function highlightResults(results, allTokens) { | ||||||
|     // we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks |     // we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks | ||||||
|     // which would make the resulting HTML string invalid. |     // which would make the resulting HTML string invalid. | ||||||
| @@ -313,8 +325,17 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED | |||||||
|             delete noteTitles[note.noteId]; |             delete noteTitles[note.noteId]; | ||||||
|             delete childToParent[note.noteId]; |             delete childToParent[note.noteId]; | ||||||
|         } |         } | ||||||
|  |         else { | ||||||
|  |             if (note.isProtected) { | ||||||
|  |                 // we can assume we have protected session since we managed to update | ||||||
|  |                 // removing from the maps is important when switching between protected & unprotected | ||||||
|  |                 protectedNoteTitles[note.noteId] = note.title; | ||||||
|  |                 delete noteTitles[note.noteId]; | ||||||
|  |             } | ||||||
|             else { |             else { | ||||||
|                 noteTitles[note.noteId] = note.title; |                 noteTitles[note.noteId] = note.title; | ||||||
|  |                 delete protectedNoteTitles[note.noteId]; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else if (entityName === 'branches') { |     else if (entityName === 'branches') { | ||||||
| @@ -355,15 +376,9 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED | |||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => { | eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => { | ||||||
|     if (!loaded) { |     if (loaded) { | ||||||
|         return; |         loadProtectedNotes(); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`); |  | ||||||
|  |  | ||||||
|     for (const noteId in protectedNoteTitles) { |  | ||||||
|         protectedNoteTitles[noteId] = protectedSessionService.decryptNoteTitle(noteId, protectedNoteTitles[noteId]); |  | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -372,5 +387,6 @@ sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load)); | |||||||
| module.exports = { | module.exports = { | ||||||
|     findNotes, |     findNotes, | ||||||
|     getNotePath, |     getNotePath, | ||||||
|     getNoteTitleForPath |     getNoteTitleForPath, | ||||||
|  |     load | ||||||
| }; | }; | ||||||
| @@ -6,7 +6,7 @@ const sourceIdService = require('./source_id'); | |||||||
| const log = require('./log'); | const log = require('./log'); | ||||||
|  |  | ||||||
| async function executeNote(note, originEntity) { | async function executeNote(note, originEntity) { | ||||||
|     if (!note.isJavaScript()) { |     if (!note.isJavaScript() || !note.isContentAvailable) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -80,6 +80,10 @@ function getParams(params) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) { | async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) { | ||||||
|  |     if (!note.isContentAvailable) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (!note.isJavaScript() && !note.isHtml()) { |     if (!note.isJavaScript() && !note.isHtml()) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,7 +1,12 @@ | |||||||
| <div id="note-detail-search" class="note-detail-component"> | <div id="note-detail-search" class="note-detail-component"> | ||||||
|     <div style="display: flex; align-items: center;"> |     <div style="display: flex; align-items: center;"> | ||||||
|         <strong>Search string:    </strong> |         <strong>Search string:    </strong> | ||||||
|         <textarea rows="4" cols="50" id="search-string"></textarea> |         <textarea rows="4" cols="40" id="search-string"></textarea> | ||||||
|  |  | ||||||
|  |         <span> | ||||||
|  |                 | ||||||
|  |             <button type="button" class="btn btn-primary" id="note-detail-search-refresh-results-button">Refresh tree</button> | ||||||
|  |         </span> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <br /> |     <br /> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user