mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 04:51:31 +08:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into next
# Conflicts: # package-lock.json # src/public/app/services/note_content_renderer.js # src/public/stylesheets/style.css # src/routes/api/files.js # src/routes/routes.js
This commit is contained in:
		
						commit
						7494491560
					
				
							
								
								
									
										1
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							| @ -2,6 +2,5 @@ | |||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="VcsDirectoryMappings"> |   <component name="VcsDirectoryMappings"> | ||||||
|     <mapping directory="" vcs="Git" /> |     <mapping directory="" vcs="Git" /> | ||||||
|     <mapping directory="$PROJECT_DIR$/../.." vcs="Git" /> |  | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
| @ -10,7 +10,7 @@ fi | |||||||
| 
 | 
 | ||||||
| cd dist | cd dist | ||||||
| wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz | wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz | ||||||
| tar xvfJ node-v${NODE_VERSION}-linux-x64.tar.xz | tar xfJ node-v${NODE_VERSION}-linux-x64.tar.xz | ||||||
| rm node-v${NODE_VERSION}-linux-x64.tar.xz | rm node-v${NODE_VERSION}-linux-x64.tar.xz | ||||||
| cd .. | cd .. | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ if [[ $# -eq 0 ]] ; then | |||||||
|     exit 1 |     exit 1 | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
| npm run webpack | n exec 12 npm run webpack | ||||||
| 
 | 
 | ||||||
| DIR=$1 | DIR=$1 | ||||||
| 
 | 
 | ||||||
| @ -27,7 +27,7 @@ cp -r electron.js $DIR/ | |||||||
| cp webpack-* $DIR/ | cp webpack-* $DIR/ | ||||||
| 
 | 
 | ||||||
| # run in subshell (so we return to original dir) | # run in subshell (so we return to original dir) | ||||||
| (cd $DIR && npm install --only=prod) | (cd $DIR && n exec 12 npm install --only=prod) | ||||||
| 
 | 
 | ||||||
| # cleanup of useless files in dependencies | # cleanup of useless files in dependencies | ||||||
| rm -r $DIR/node_modules/image-q/demo | rm -r $DIR/node_modules/image-q/demo | ||||||
|  | |||||||
| @ -8,9 +8,9 @@ fi | |||||||
| VERSION=$1 | VERSION=$1 | ||||||
| SERIES=${VERSION:0:4}-latest | SERIES=${VERSION:0:4}-latest | ||||||
| 
 | 
 | ||||||
| sudo docker push zadam/trilium:$VERSION | docker push zadam/trilium:$VERSION | ||||||
| sudo docker push zadam/trilium:$SERIES | docker push zadam/trilium:$SERIES | ||||||
| 
 | 
 | ||||||
| if [[ $1 != *"beta"* ]]; then | if [[ $1 != *"beta"* ]]; then | ||||||
|   sudo docker push zadam/trilium:latest |   docker push zadam/trilium:latest | ||||||
| fi | fi | ||||||
|  | |||||||
| @ -55,47 +55,20 @@ echo "Creating release in GitHub" | |||||||
| EXTRA= | EXTRA= | ||||||
| 
 | 
 | ||||||
| if [[ $TAG == *"beta"* ]]; then | if [[ $TAG == *"beta"* ]]; then | ||||||
|   EXTRA=--pre-release |   EXTRA=--prerelease | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
| github-release release \ | echo "$GITHUB_CLI_AUTH_TOKEN" | gh auth login --with-token | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$TAG release" $EXTRA |  | ||||||
| 
 | 
 | ||||||
| echo "Uploading debian x64 package" | gh release create "$TAG" \ | ||||||
| 
 |     --title "$TAG release" \ | ||||||
| github-release upload \ |     --notes "" \ | ||||||
|     --tag $TAG \ |     $EXTRA \ | ||||||
|     --name "$DEBIAN_X64_BUILD" \ |     "dist/$DEBIAN_X64_BUILD" \ | ||||||
|     --file "dist/$DEBIAN_X64_BUILD" |     "dist/$LINUX_X64_BUILD" \ | ||||||
| 
 |     "dist/$WINDOWS_X64_BUILD" \ | ||||||
| echo "Uploading linux x64 build" |     "dist/$MAC_X64_BUILD" \ | ||||||
| 
 |     "dist/$SERVER_BUILD" | ||||||
| github-release upload \ |  | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$LINUX_X64_BUILD" \ |  | ||||||
|     --file "dist/$LINUX_X64_BUILD" |  | ||||||
| 
 |  | ||||||
| echo "Uploading windows x64 build" |  | ||||||
| 
 |  | ||||||
| github-release upload \ |  | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$WINDOWS_X64_BUILD" \ |  | ||||||
|     --file "dist/$WINDOWS_X64_BUILD" |  | ||||||
| 
 |  | ||||||
| echo "Uploading mac x64 build" |  | ||||||
| 
 |  | ||||||
| github-release upload \ |  | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$MAC_X64_BUILD" \ |  | ||||||
|     --file "dist/$MAC_X64_BUILD" |  | ||||||
| 
 |  | ||||||
| echo "Uploading linux x64 server build" |  | ||||||
| 
 |  | ||||||
| github-release upload \ |  | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$SERVER_BUILD" \ |  | ||||||
|     --file "dist/$SERVER_BUILD" |  | ||||||
| 
 | 
 | ||||||
| echo "Building docker image" | echo "Building docker image" | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ app.on('window-all-closed', () => { | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| app.on('ready', async () => { | app.on('ready', async () => { | ||||||
|     app.setAppUserModelId('com.github.zadam.trilium'); | //    app.setAppUserModelId('com.github.zadam.trilium');
 | ||||||
| 
 | 
 | ||||||
|     // if db is not initialized -> setup process
 |     // if db is not initialized -> setup process
 | ||||||
|     // if db is initialized, then we need to wait until the migration process is finished
 |     // if db is initialized, then we need to wait until the migration process is finished
 | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										17
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								package.json
									
									
									
									
									
								
							| @ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.47.0-beta", |   "version": "0.47.1-beta", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
| @ -28,6 +28,7 @@ | |||||||
|     "axios": "0.21.1", |     "axios": "0.21.1", | ||||||
|     "better-sqlite3": "7.1.4", |     "better-sqlite3": "7.1.4", | ||||||
|     "body-parser": "1.19.0", |     "body-parser": "1.19.0", | ||||||
|  |     "chokidar": "^3.5.1", | ||||||
|     "cls-hooked": "4.2.2", |     "cls-hooked": "4.2.2", | ||||||
|     "commonmark": "0.29.3", |     "commonmark": "0.29.3", | ||||||
|     "cookie-parser": "1.4.5", |     "cookie-parser": "1.4.5", | ||||||
| @ -42,7 +43,7 @@ | |||||||
|     "express-partial-content": "^1.0.2", |     "express-partial-content": "^1.0.2", | ||||||
|     "express-session": "1.17.1", |     "express-session": "1.17.1", | ||||||
|     "fs-extra": "9.1.0", |     "fs-extra": "9.1.0", | ||||||
|     "helmet": "4.4.1", |     "helmet": "4.5.0", | ||||||
|     "html": "1.0.0", |     "html": "1.0.0", | ||||||
|     "html2plaintext": "2.1.2", |     "html2plaintext": "2.1.2", | ||||||
|     "http-proxy-agent": "4.0.1", |     "http-proxy-agent": "4.0.1", | ||||||
| @ -53,11 +54,11 @@ | |||||||
|     "is-svg": "4.3.1", |     "is-svg": "4.3.1", | ||||||
|     "jimp": "0.16.1", |     "jimp": "0.16.1", | ||||||
|     "joplin-turndown-plugin-gfm": "1.0.12", |     "joplin-turndown-plugin-gfm": "1.0.12", | ||||||
|     "jsdom": "16.5.2", |     "jsdom": "16.5.3", | ||||||
|     "mime-types": "2.1.30", |     "mime-types": "2.1.30", | ||||||
|     "multer": "1.4.2", |     "multer": "1.4.2", | ||||||
|     "node-abi": "2.21.0", |     "node-abi": "2.26.0", | ||||||
|     "open": "8.0.5", |     "open": "8.0.6", | ||||||
|     "portscanner": "2.2.0", |     "portscanner": "2.2.0", | ||||||
|     "rand-token": "1.0.1", |     "rand-token": "1.0.1", | ||||||
|     "request": "^2.88.2", |     "request": "^2.88.2", | ||||||
| @ -73,13 +74,13 @@ | |||||||
|     "tmp": "^0.2.1", |     "tmp": "^0.2.1", | ||||||
|     "turndown": "7.0.0", |     "turndown": "7.0.0", | ||||||
|     "unescape": "1.0.1", |     "unescape": "1.0.1", | ||||||
|     "ws": "7.4.4", |     "ws": "7.4.5", | ||||||
|     "yauzl": "2.10.0", |     "yauzl": "2.10.0", | ||||||
|     "yazl": "2.5.1" |     "yazl": "2.5.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "cross-env": "7.0.3", |     "cross-env": "7.0.3", | ||||||
|     "electron": "13.0.0-beta.12", |     "electron": "13.0.0-beta.17", | ||||||
|     "electron-builder": "22.10.5", |     "electron-builder": "22.10.5", | ||||||
|     "electron-packager": "15.2.0", |     "electron-packager": "15.2.0", | ||||||
|     "electron-rebuild": "2.3.5", |     "electron-rebuild": "2.3.5", | ||||||
| @ -88,7 +89,7 @@ | |||||||
|     "jsdoc": "3.6.6", |     "jsdoc": "3.6.6", | ||||||
|     "lorem-ipsum": "2.0.3", |     "lorem-ipsum": "2.0.3", | ||||||
|     "rcedit": "3.0.0", |     "rcedit": "3.0.0", | ||||||
|     "webpack": "5.31.2", |     "webpack": "5.35.1", | ||||||
|     "webpack-cli": "4.6.0" |     "webpack-cli": "4.6.0" | ||||||
|   }, |   }, | ||||||
|   "optionalDependencies": { |   "optionalDependencies": { | ||||||
|  | |||||||
| @ -121,14 +121,14 @@ ws.subscribeToMessages(async message => { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (message.type === 'task-error') { |     if (message.type === 'taskError') { | ||||||
|         toastService.closePersistent(message.taskId); |         toastService.closePersistent(message.taskId); | ||||||
|         toastService.showError(message.message); |         toastService.showError(message.message); | ||||||
|     } |     } | ||||||
|     else if (message.type === 'task-progress-count') { |     else if (message.type === 'taskProgressCount') { | ||||||
|         toastService.showPersistent(makeToast(message.taskId, "Export in progress: " + message.progressCount)); |         toastService.showPersistent(makeToast(message.taskId, "Export in progress: " + message.progressCount)); | ||||||
|     } |     } | ||||||
|     else if (message.type === 'task-succeeded') { |     else if (message.type === 'taskSucceeded') { | ||||||
|         const toast = makeToast(message.taskId, "Export finished successfully."); |         const toast = makeToast(message.taskId, "Export finished successfully."); | ||||||
|         toast.closeAfter = 5000; |         toast.closeAfter = 5000; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ import SearchResultWidget from "../widgets/search_result.js"; | |||||||
| import SyncStatusWidget from "../widgets/sync_status.js"; | import SyncStatusWidget from "../widgets/sync_status.js"; | ||||||
| import ScrollingContainer from "../widgets/containers/scrolling_container.js"; | import ScrollingContainer from "../widgets/containers/scrolling_container.js"; | ||||||
| import RootContainer from "../widgets/containers/root_container.js"; | import RootContainer from "../widgets/containers/root_container.js"; | ||||||
|  | import NoteUpdateStatusWidget from "../widgets/note_update_status.js"; | ||||||
| 
 | 
 | ||||||
| const RIGHT_PANE_CSS = ` | const RIGHT_PANE_CSS = ` | ||||||
| <style> | <style> | ||||||
| @ -177,6 +178,7 @@ export default class DesktopLayout { | |||||||
|                             .child(new InheritedAttributesWidget()) |                             .child(new InheritedAttributesWidget()) | ||||||
|                         ) |                         ) | ||||||
|                     ) |                     ) | ||||||
|  |                     .child(new NoteUpdateStatusWidget()) | ||||||
|                     .child( |                     .child( | ||||||
|                         new TabCachingWidget(() => new ScrollingContainer() |                         new TabCachingWidget(() => new ScrollingContainer() | ||||||
|                             .child(new SqlTableSchemasWidget()) |                             .child(new SqlTableSchemasWidget()) | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import froca from "./froca.js"; | import froca from "./froca.js"; | ||||||
| import bundleService from "./bundle.js"; | import bundleService from "./bundle.js"; | ||||||
| import DialogCommandExecutor from "./dialog_command_executor.js"; | import RootCommandExecutor from "./root_command_executor.js"; | ||||||
| import Entrypoints from "./entrypoints.js"; | import Entrypoints from "./entrypoints.js"; | ||||||
| import options from "./options.js"; | import options from "./options.js"; | ||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| @ -57,7 +57,7 @@ class AppContext extends Component { | |||||||
| 
 | 
 | ||||||
|         this.executors = [ |         this.executors = [ | ||||||
|             this.tabManager, |             this.tabManager, | ||||||
|             new DialogCommandExecutor(), |             new RootCommandExecutor(), | ||||||
|             new Entrypoints(), |             new Entrypoints(), | ||||||
|             new MainTreeExecutors() |             new MainTreeExecutors() | ||||||
|         ]; |         ]; | ||||||
|  | |||||||
| @ -153,12 +153,12 @@ ws.subscribeToMessages(async message => { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (message.type === 'task-error') { |     if (message.type === 'taskError') { | ||||||
|         toastService.closePersistent(message.taskId); |         toastService.closePersistent(message.taskId); | ||||||
|         toastService.showError(message.message); |         toastService.showError(message.message); | ||||||
|     } else if (message.type === 'task-progress-count') { |     } else if (message.type === 'taskProgressCount') { | ||||||
|         toastService.showPersistent(makeToast(message.taskId, "Delete notes in progress: " + message.progressCount)); |         toastService.showPersistent(makeToast(message.taskId, "Delete notes in progress: " + message.progressCount)); | ||||||
|     } else if (message.type === 'task-succeeded') { |     } else if (message.type === 'taskSucceeded') { | ||||||
|         const toast = makeToast(message.taskId, "Delete finished successfully."); |         const toast = makeToast(message.taskId, "Delete finished successfully."); | ||||||
|         toast.closeAfter = 5000; |         toast.closeAfter = 5000; | ||||||
| 
 | 
 | ||||||
| @ -167,16 +167,16 @@ ws.subscribeToMessages(async message => { | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| ws.subscribeToMessages(async message => { | ws.subscribeToMessages(async message => { | ||||||
|     if (message.taskType !== 'undelete-notes') { |     if (message.taskType !== 'undeleteNotes') { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (message.type === 'task-error') { |     if (message.type === 'taskError') { | ||||||
|         toastService.closePersistent(message.taskId); |         toastService.closePersistent(message.taskId); | ||||||
|         toastService.showError(message.message); |         toastService.showError(message.message); | ||||||
|     } else if (message.type === 'task-progress-count') { |     } else if (message.type === 'taskProgressCount') { | ||||||
|         toastService.showPersistent(makeToast(message.taskId, "Undeleting notes in progress: " + message.progressCount)); |         toastService.showPersistent(makeToast(message.taskId, "Undeleting notes in progress: " + message.progressCount)); | ||||||
|     } else if (message.type === 'task-succeeded') { |     } else if (message.type === 'taskSucceeded') { | ||||||
|         const toast = makeToast(message.taskId, "Undeleting notes finished successfully."); |         const toast = makeToast(message.taskId, "Undeleting notes finished successfully."); | ||||||
|         toast.closeAfter = 5000; |         toast.closeAfter = 5000; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -64,13 +64,12 @@ async function getWidgetBundlesByParent() { | |||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             widget = await executeBundle(bundle); |             widget = await executeBundle(bundle); | ||||||
|  |             widgetsByParent.add(widget); | ||||||
|         } |         } | ||||||
|         catch (e) { |         catch (e) { | ||||||
|             logError("Widget initialization failed: ", e); |             logError("Widget initialization failed: ", e); | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         widgetsByParent.add(widget); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return widgetsByParent; |     return widgetsByParent; | ||||||
|  | |||||||
							
								
								
									
										36
									
								
								src/public/app/services/file_watcher.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/public/app/services/file_watcher.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | import ws from "./ws.js"; | ||||||
|  | import appContext from "./app_context.js"; | ||||||
|  | 
 | ||||||
|  | const fileModificationStatus = {}; | ||||||
|  | 
 | ||||||
|  | function getFileModificationStatus(noteId) { | ||||||
|  |     return fileModificationStatus[noteId]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function fileModificationUploaded(noteId) { | ||||||
|  |     delete fileModificationStatus[noteId]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function ignoreModification(noteId) { | ||||||
|  |     delete fileModificationStatus[noteId]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ws.subscribeToMessages(async message => { | ||||||
|  |     if (message.type !== 'openedFileUpdated') { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fileModificationStatus[message.noteId] = message; | ||||||
|  | 
 | ||||||
|  |     appContext.triggerEvent('openedFileUpdated', { | ||||||
|  |         noteId: message.noteId, | ||||||
|  |         lastModifiedMs: message.lastModifiedMs, | ||||||
|  |         filePath: message.filePath | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     getFileModificationStatus, | ||||||
|  |     fileModificationUploaded, | ||||||
|  |     ignoreModification | ||||||
|  | } | ||||||
| @ -52,12 +52,12 @@ ws.subscribeToMessages(async message => { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (message.type === 'task-error') { |     if (message.type === 'taskError') { | ||||||
|         toastService.closePersistent(message.taskId); |         toastService.closePersistent(message.taskId); | ||||||
|         toastService.showError(message.message); |         toastService.showError(message.message); | ||||||
|     } else if (message.type === 'task-progress-count') { |     } else if (message.type === 'taskProgressCount') { | ||||||
|         toastService.showPersistent(makeToast(message.taskId, "Import in progress: " + message.progressCount)); |         toastService.showPersistent(makeToast(message.taskId, "Import in progress: " + message.progressCount)); | ||||||
|     } else if (message.type === 'task-succeeded') { |     } else if (message.type === 'taskSucceeded') { | ||||||
|         const toast = makeToast(message.taskId, "Import finished successfully."); |         const toast = makeToast(message.taskId, "Import finished successfully."); | ||||||
|         toast.closeAfter = 5000; |         toast.closeAfter = 5000; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -89,7 +89,11 @@ function updateDisplayedShortcuts($container) { | |||||||
| 		const action = await getAction(actionName, true); | 		const action = await getAction(actionName, true); | ||||||
| 
 | 
 | ||||||
| 		if (action) { | 		if (action) { | ||||||
| 			$(el).text(action.effectiveShortcuts.join(', ')); | 			const keyboardActions = action.effectiveShortcuts.join(', '); | ||||||
|  | 
 | ||||||
|  | 			if (keyboardActions || $(el).text() !== "not set") { | ||||||
|  | 				$(el).text(keyboardActions); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -39,12 +39,12 @@ async function getRenderedContent(note, options = {}) { | |||||||
|                 .css("max-width", "100%") |                 .css("max-width", "100%") | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|     else if (!options.tooltip && ['file', 'pdf', 'audio', 'video']) { |     else if (!options.tooltip && ['file', 'pdf', 'audio', 'video'].includes(type)) { | ||||||
|         const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>'); |         const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>'); | ||||||
|         const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>'); |         const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>'); | ||||||
| 
 | 
 | ||||||
|         $downloadButton.on('click', () => openService.downloadFileNote(note.noteId)); |         $downloadButton.on('click', () => openService.downloadFileNote(note.noteId)); | ||||||
|         $openButton.on('click', () => openService.openFileNote(note.noteId)); |         $openButton.on('click', () => openService.openNoteExternally(note.noteId)); | ||||||
| 
 | 
 | ||||||
|         // open doesn't work for protected notes since it works through browser which isn't in protected session
 |         // open doesn't work for protected notes since it works through browser which isn't in protected session
 | ||||||
|         $openButton.toggle(!note.isProtected); |         $openButton.toggle(!note.isProtected); | ||||||
| @ -59,7 +59,7 @@ async function getRenderedContent(note, options = {}) { | |||||||
|         } |         } | ||||||
|         else if (type === 'audio') { |         else if (type === 'audio') { | ||||||
|             const $audioPreview = $('<audio controls></audio>') |             const $audioPreview = $('<audio controls></audio>') | ||||||
|                 .attr("src", openService.getUrlForDownload("api/notes/" + note.noteId + "/open")) |                 .attr("src", openService.getUrlForStreaming("api/notes/" + note.noteId + "/open-partial")) | ||||||
|                 .attr("type", note.mime) |                 .attr("type", note.mime) | ||||||
|                 .css("width", "100%"); |                 .css("width", "100%"); | ||||||
| 
 | 
 | ||||||
| @ -67,7 +67,7 @@ async function getRenderedContent(note, options = {}) { | |||||||
|         } |         } | ||||||
|         else if (type === 'video') { |         else if (type === 'video') { | ||||||
|             const $videoPreview = $('<video controls></video>') |             const $videoPreview = $('<video controls></video>') | ||||||
|                 .attr("src", openService.getUrlForDownload("api/notes/" + note.noteId + "/open")) |                 .attr("src", openService.getUrlForDownload("api/notes/" + note.noteId + "/open-partial")) | ||||||
|                 .attr("type", note.mime) |                 .attr("type", note.mime) | ||||||
|                 .css("width", "100%"); |                 .css("width", "100%"); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -317,7 +317,9 @@ class NoteListRenderer { | |||||||
|         const $expander = $('<span class="note-expander bx bx-chevron-right"></span>'); |         const $expander = $('<span class="note-expander bx bx-chevron-right"></span>'); | ||||||
| 
 | 
 | ||||||
|         const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note); |         const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note); | ||||||
|         const notePath = this.parentNote.noteId + '/' + note.noteId; |         const notePath = this.parentNote.type === 'search' | ||||||
|  |             ? note.noteId // for search note parent we want to display non-search path
 | ||||||
|  |             : this.parentNote.noteId + '/' + note.noteId; | ||||||
| 
 | 
 | ||||||
|         const $card = $('<div class="note-book-card">') |         const $card = $('<div class="note-book-card">') | ||||||
|             .attr('data-note-id', note.noteId) |             .attr('data-note-id', note.noteId) | ||||||
|  | |||||||
| @ -21,9 +21,9 @@ function downloadFileNote(noteId) { | |||||||
|     download(url); |     download(url); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function openFileNote(noteId) { | async function openNoteExternally(noteId) { | ||||||
|     if (utils.isElectron()) { |     if (utils.isElectron()) { | ||||||
|         const resp = await server.post("notes/" + noteId + "/saveToTmpDir"); |         const resp = await server.post("notes/" + noteId + "/save-to-tmp-dir"); | ||||||
| 
 | 
 | ||||||
|         const electron = utils.dynamicRequire('electron'); |         const electron = utils.dynamicRequire('electron'); | ||||||
|         const res = await electron.shell.openPath(resp.tmpFilePath); |         const res = await electron.shell.openPath(resp.tmpFilePath); | ||||||
| @ -66,7 +66,7 @@ function getHost() { | |||||||
| export default { | export default { | ||||||
|     download, |     download, | ||||||
|     downloadFileNote, |     downloadFileNote, | ||||||
|     openFileNote, |     openNoteExternally, | ||||||
|     downloadNoteRevision, |     downloadNoteRevision, | ||||||
|     getUrlForDownload |     getUrlForDownload | ||||||
| } | } | ||||||
|  | |||||||
| @ -89,18 +89,18 @@ function makeToast(message, protectingLabel, text) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ws.subscribeToMessages(async message => { | ws.subscribeToMessages(async message => { | ||||||
|     if (message.taskType !== 'protect-notes') { |     if (message.taskType !== 'protectNotes') { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const protectingLabel = message.data.protect ? "Protecting" : "Unprotecting"; |     const protectingLabel = message.data.protect ? "Protecting" : "Unprotecting"; | ||||||
| 
 | 
 | ||||||
|     if (message.type === 'task-error') { |     if (message.type === 'taskError') { | ||||||
|         toastService.closePersistent(message.taskId); |         toastService.closePersistent(message.taskId); | ||||||
|         toastService.showError(message.message); |         toastService.showError(message.message); | ||||||
|     } else if (message.type === 'task-progress-count') { |     } else if (message.type === 'taskProgressCount') { | ||||||
|         toastService.showPersistent(makeToast(message, protectingLabel,protectingLabel + " in progress: " + message.progressCount)); |         toastService.showPersistent(makeToast(message, protectingLabel,protectingLabel + " in progress: " + message.progressCount)); | ||||||
|     } else if (message.type === 'task-succeeded') { |     } else if (message.type === 'taskSucceeded') { | ||||||
|         const toast = makeToast(message, protectingLabel, protectingLabel + " finished successfully."); |         const toast = makeToast(message, protectingLabel, protectingLabel + " finished successfully."); | ||||||
|         toast.closeAfter = 3000; |         toast.closeAfter = 3000; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,8 +2,9 @@ import Component from "../widgets/component.js"; | |||||||
| import appContext from "./app_context.js"; | import appContext from "./app_context.js"; | ||||||
| import dateNoteService from "../services/date_notes.js"; | import dateNoteService from "../services/date_notes.js"; | ||||||
| import treeService from "../services/tree.js"; | import treeService from "../services/tree.js"; | ||||||
|  | import openService from "./open.js"; | ||||||
| 
 | 
 | ||||||
| export default class DialogCommandExecutor extends Component { | export default class RootCommandExecutor extends Component { | ||||||
|     jumpToNoteCommand() { |     jumpToNoteCommand() { | ||||||
|         import("../dialogs/jump_to_note.js").then(d => d.showDialog()); |         import("../dialogs/jump_to_note.js").then(d => d.showDialog()); | ||||||
|     } |     } | ||||||
| @ -84,4 +85,12 @@ export default class DialogCommandExecutor extends Component { | |||||||
|     showBackendLogCommand() { |     showBackendLogCommand() { | ||||||
|         import("../dialogs/backend_log.js").then(d => d.showDialog()); |         import("../dialogs/backend_log.js").then(d => d.showDialog()); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     openNoteExternallyCommand() { | ||||||
|  |         const noteId = appContext.tabManager.getActiveTabNoteId(); | ||||||
|  | 
 | ||||||
|  |         if (noteId) { | ||||||
|  |             openService.openNoteExternally(noteId); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| @ -127,7 +127,7 @@ async function sortAlphabetically(noteId) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ws.subscribeToMessages(message => { | ws.subscribeToMessages(message => { | ||||||
|    if (message.type === 'open-note') { |    if (message.type === 'openNote') { | ||||||
|        appContext.tabManager.activateOrOpenNote(message.noteId); |        appContext.tabManager.activateOrOpenNote(message.noteId); | ||||||
| 
 | 
 | ||||||
|        if (utils.isElectron()) { |        if (utils.isElectron()) { | ||||||
|  | |||||||
| @ -59,7 +59,7 @@ async function handleMessage(event) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (message.type === 'frontend-update') { |     if (message.type === 'frontend-update') { | ||||||
|         let {entityChanges, lastSyncedPush} = message.data; |         let {entityChanges} = message.data; | ||||||
|         lastPingTs = Date.now(); |         lastPingTs = Date.now(); | ||||||
| 
 | 
 | ||||||
|         if (entityChanges.length > 0) { |         if (entityChanges.length > 0) { | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ const TPL = ` | |||||||
|         padding-left: 10px; |         padding-left: 10px; | ||||||
|         padding-right: 10px; |         padding-right: 10px; | ||||||
|         position: relative; |         position: relative; | ||||||
|  |         top: -2px; | ||||||
|         border-radius: 0; |         border-radius: 0; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  | |||||||
| @ -1,11 +1,12 @@ | |||||||
| import TabAwareWidget from "./tab_aware_widget.js"; | import TabAwareWidget from "./tab_aware_widget.js"; | ||||||
| import protectedSessionService from "../services/protected_session.js"; | import protectedSessionService from "../services/protected_session.js"; | ||||||
|  | import utils from "../services/utils.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="dropdown note-actions"> | <div class="dropdown note-actions"> | ||||||
|     <style> |     <style> | ||||||
|     .note-actions .dropdown-menu { |     .note-actions .dropdown-menu { | ||||||
|         width: 15em; |         width: 20em; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     .note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover { |     .note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover { | ||||||
| @ -84,6 +85,7 @@ const TPL = ` | |||||||
|         <a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a> |         <a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a> | ||||||
|         <a data-trigger-command="showLinkMap" class="dropdown-item show-link-map-button"><kbd data-command="showLinkMap"></kbd> Link map</a> |         <a data-trigger-command="showLinkMap" class="dropdown-item show-link-map-button"><kbd data-command="showLinkMap"></kbd> Link map</a> | ||||||
|         <a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a> |         <a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a> | ||||||
|  |         <a data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button"><kbd data-command="openNoteExternally"></kbd> Open note externally</a> | ||||||
|         <a class="dropdown-item import-files-button">Import files</a> |         <a class="dropdown-item import-files-button">Import files</a> | ||||||
|         <a class="dropdown-item export-note-button">Export note</a> |         <a class="dropdown-item export-note-button">Export note</a> | ||||||
|         <a data-trigger-command="printActiveNote" class="dropdown-item print-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a> |         <a data-trigger-command="printActiveNote" class="dropdown-item print-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a> | ||||||
| @ -119,6 +121,8 @@ export default class NoteActionsWidget extends TabAwareWidget { | |||||||
| 
 | 
 | ||||||
|         this.$widget.on('click', '.dropdown-item', |         this.$widget.on('click', '.dropdown-item', | ||||||
|             () => this.$widget.find('.dropdown-toggle').dropdown('toggle')); |             () => this.$widget.find('.dropdown-toggle').dropdown('toggle')); | ||||||
|  | 
 | ||||||
|  |         this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     refreshWithNote(note) { |     refreshWithNote(note) { | ||||||
| @ -128,6 +132,8 @@ export default class NoteActionsWidget extends TabAwareWidget { | |||||||
| 
 | 
 | ||||||
|         this.$protectButton.toggle(!note.isProtected); |         this.$protectButton.toggle(!note.isProtected); | ||||||
|         this.$unprotectButton.toggle(!!note.isProtected); |         this.$unprotectButton.toggle(!!note.isProtected); | ||||||
|  | 
 | ||||||
|  |         this.$openNoteExternallyButton.toggle(utils.isElectron()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     toggleDisabled($el, enable) { |     toggleDisabled($el, enable) { | ||||||
|  | |||||||
| @ -382,8 +382,6 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|                     } |                     } | ||||||
|                     else { |                     else { | ||||||
|                         node.setActive(); |                         node.setActive(); | ||||||
| 
 |  | ||||||
|                         this.clearSelectedNodes(); |  | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     return false; |                     return false; | ||||||
| @ -393,6 +391,8 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|                 // click event won't propagate so let's close context menu manually
 |                 // click event won't propagate so let's close context menu manually
 | ||||||
|                 contextMenu.hide(); |                 contextMenu.hide(); | ||||||
| 
 | 
 | ||||||
|  |                 this.clearSelectedNodes(); | ||||||
|  | 
 | ||||||
|                 const notePath = treeService.getNotePath(data.node); |                 const notePath = treeService.getNotePath(data.node); | ||||||
| 
 | 
 | ||||||
|                 const activeTabContext = appContext.tabManager.getActiveTabContext(); |                 const activeTabContext = appContext.tabManager.getActiveTabContext(); | ||||||
| @ -1144,11 +1144,12 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (node) { |             if (node) { | ||||||
|                 node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused}); |  | ||||||
| 
 |  | ||||||
|                 if (activeNodeFocused) { |                 if (activeNodeFocused) { | ||||||
|                     node.setFocus(true); |                     // needed by Firefox: https://github.com/zadam/trilium/issues/1865
 | ||||||
|  |                     this.tree.$container.focus(); | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|  |                 await node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused}); | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|                 // this is used when original note has been deleted and we want to move the focus to the note above/below
 |                 // this is used when original note has been deleted and we want to move the focus to the note above/below
 | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								src/public/app/widgets/note_update_status.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/public/app/widgets/note_update_status.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | import TabAwareWidget from "./tab_aware_widget.js"; | ||||||
|  | import server from "../services/server.js"; | ||||||
|  | import fileWatcher from "../services/file_watcher.js"; | ||||||
|  | 
 | ||||||
|  | const TPL = ` | ||||||
|  | <div class="dropdown note-update-status-widget alert alert-warning"> | ||||||
|  |     <style> | ||||||
|  |         .note-update-status-widget { | ||||||
|  |             margin: 10px; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|  |      | ||||||
|  |     <p>File <code class="file-path"></code> has been last modified on <span class="file-last-modified"></span>.</p>  | ||||||
|  |          | ||||||
|  |     <div style="display: flex; flex-direction: row; justify-content: space-evenly;"> | ||||||
|  |         <button class="btn btn-sm file-upload-button">Upload modified file</button> | ||||||
|  |          | ||||||
|  |         <button class="btn btn-sm ignore-this-change-button">Ignore this change</button> | ||||||
|  |     </div> | ||||||
|  | </div>`; | ||||||
|  | 
 | ||||||
|  | export default class NoteUpdateStatusWidget extends TabAwareWidget { | ||||||
|  |     isEnabled() { | ||||||
|  |         return super.isEnabled() | ||||||
|  |             && !!fileWatcher.getFileModificationStatus(this.noteId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     doRender() { | ||||||
|  |         this.$widget = $(TPL); | ||||||
|  |         this.overflowing(); | ||||||
|  | 
 | ||||||
|  |         this.$filePath = this.$widget.find(".file-path"); | ||||||
|  |         this.$fileLastModified = this.$widget.find(".file-last-modified"); | ||||||
|  |         this.$fileUploadButton = this.$widget.find(".file-upload-button"); | ||||||
|  | 
 | ||||||
|  |         this.$fileUploadButton.on("click", async () => { | ||||||
|  |             await server.post(`notes/${this.noteId}/upload-modified-file`, { | ||||||
|  |                 filePath: this.$filePath.text() | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             fileWatcher.fileModificationUploaded(this.noteId); | ||||||
|  |             this.refresh(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.$ignoreThisChangeButton = this.$widget.find(".ignore-this-change-button"); | ||||||
|  |         this.$ignoreThisChangeButton.on('click', () => { | ||||||
|  |             fileWatcher.ignoreModification(this.noteId); | ||||||
|  |             this.refresh(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     refreshWithNote(note) { | ||||||
|  |         const status = fileWatcher.getFileModificationStatus(note.noteId); | ||||||
|  | 
 | ||||||
|  |         this.$filePath.text(status.filePath); | ||||||
|  |         this.$fileLastModified.text(dayjs.unix(status.lastModifiedMs / 1000).format("HH:mm:ss")); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     openedFileUpdatedEvent(data) { | ||||||
|  |         if (data.noteId === this.noteId) { | ||||||
|  |             this.refresh(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -5,7 +5,7 @@ const TPL = ` | |||||||
|     <td colspan="2"> |     <td colspan="2"> | ||||||
|         <span class="bx bx-trash"></span> |         <span class="bx bx-trash"></span> | ||||||
|      |      | ||||||
|         Delete matched note |         Delete matched notes | ||||||
|     </td> |     </td> | ||||||
|     <td class="button-column"> |     <td class="button-column"> | ||||||
|         <span class="bx bx-x icon-action action-conf-del"></span> |         <span class="bx bx-x icon-action action-conf-del"></span> | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ const TPL = ` | |||||||
|             <option value="ownedLabelCount">Number of labels</option> |             <option value="ownedLabelCount">Number of labels</option> | ||||||
|             <option value="ownedRelationCount">Number of relations</option> |             <option value="ownedRelationCount">Number of relations</option> | ||||||
|             <option value="targetRelationCount">Number of relations targeting the note</option> |             <option value="targetRelationCount">Number of relations targeting the note</option> | ||||||
|  |             <option value="random">Random order</option> | ||||||
|         </select> |         </select> | ||||||
|          |          | ||||||
|         <select name="orderDirection" class="form-control w-auto d-inline"> |         <select name="orderDirection" class="form-control w-auto d-inline"> | ||||||
|  | |||||||
| @ -82,7 +82,7 @@ export default class FilePropertiesWidget extends TabAwareWidget { | |||||||
|         this.$uploadNewRevisionInput = this.$widget.find(".file-upload-new-revision-input"); |         this.$uploadNewRevisionInput = this.$widget.find(".file-upload-new-revision-input"); | ||||||
| 
 | 
 | ||||||
|         this.$downloadButton.on('click', () => openService.downloadFileNote(this.noteId)); |         this.$downloadButton.on('click', () => openService.downloadFileNote(this.noteId)); | ||||||
|         this.$openButton.on('click', () => openService.openFileNote(this.noteId)); |         this.$openButton.on('click', () => openService.openNoteExternally(this.noteId)); | ||||||
| 
 | 
 | ||||||
|         this.$uploadNewRevisionButton.on("click", () => { |         this.$uploadNewRevisionButton.on("click", () => { | ||||||
|             this.$uploadNewRevisionInput.trigger("click"); |             this.$uploadNewRevisionInput.trigger("click"); | ||||||
|  | |||||||
| @ -26,6 +26,8 @@ const TPL = ` | |||||||
|     <div class="no-print" style="display: flex; justify-content: space-evenly; margin: 10px;"> |     <div class="no-print" style="display: flex; justify-content: space-evenly; margin: 10px;"> | ||||||
|         <button class="image-download btn btn-sm btn-primary" type="button">Download</button> |         <button class="image-download btn btn-sm btn-primary" type="button">Download</button> | ||||||
| 
 | 
 | ||||||
|  |         <button class="image-open btn btn-sm btn-primary" type="button">Open</button> | ||||||
|  | 
 | ||||||
|         <button class="image-copy-to-clipboard btn btn-sm btn-primary" type="button">Copy to clipboard</button> |         <button class="image-copy-to-clipboard btn btn-sm btn-primary" type="button">Copy to clipboard</button> | ||||||
| 
 | 
 | ||||||
|         <button class="image-upload-new-revision btn btn-sm btn-primary" type="button">Upload new revision</button> |         <button class="image-upload-new-revision btn btn-sm btn-primary" type="button">Upload new revision</button> | ||||||
| @ -59,6 +61,9 @@ export default class ImagePropertiesWidget extends TabAwareWidget { | |||||||
|         this.$fileType = this.$widget.find(".image-filetype"); |         this.$fileType = this.$widget.find(".image-filetype"); | ||||||
|         this.$fileSize = this.$widget.find(".image-filesize"); |         this.$fileSize = this.$widget.find(".image-filesize"); | ||||||
| 
 | 
 | ||||||
|  |         this.$openButton = this.$widget.find(".image-open"); | ||||||
|  |         this.$openButton.on('click', () => openService.openNoteExternally(this.noteId)); | ||||||
|  | 
 | ||||||
|         this.$imageDownloadButton = this.$widget.find(".image-download"); |         this.$imageDownloadButton = this.$widget.find(".image-download"); | ||||||
|         this.$imageDownloadButton.on('click', () => openService.downloadFileNote(this.noteId)); |         this.$imageDownloadButton.on('click', () => openService.downloadFileNote(this.noteId)); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import utils from "../../services/utils.js"; |  | ||||||
| import openService from "../../services/open.js"; | import openService from "../../services/open.js"; | ||||||
| import TypeWidget from "./type_widget.js"; | import TypeWidget from "./type_widget.js"; | ||||||
|  | import fileWatcher from "../../services/file_watcher.js"; | ||||||
|  | import server from "../../services/server.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="note-detail-file note-detail-printable"> | <div class="note-detail-file note-detail-printable"> | ||||||
| @ -50,9 +51,6 @@ export default class FileTypeWidget extends TypeWidget { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async doRefresh(note) { |     async doRefresh(note) { | ||||||
|         const attributes = note.getAttributes(); |  | ||||||
|         const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); |  | ||||||
| 
 |  | ||||||
|         this.$widget.show(); |         this.$widget.show(); | ||||||
| 
 | 
 | ||||||
|         const noteComplement = await this.tabContext.getNoteComplement(); |         const noteComplement = await this.tabContext.getNoteComplement(); | ||||||
| @ -73,14 +71,14 @@ export default class FileTypeWidget extends TypeWidget { | |||||||
|         else if (note.mime.startsWith('video/')) { |         else if (note.mime.startsWith('video/')) { | ||||||
|             this.$videoPreview |             this.$videoPreview | ||||||
|                 .show() |                 .show() | ||||||
|                 .attr("src", openService.getUrlForDownload("api/notes/" + this.noteId + "/open")) |                 .attr("src", openService.getUrlForDownload("api/notes/" + this.noteId + "/open-partial")) | ||||||
|                 .attr("type", this.note.mime) |                 .attr("type", this.note.mime) | ||||||
|                 .css("width", this.$widget.width()); |                 .css("width", this.$widget.width()); | ||||||
|         } |         } | ||||||
|         else if (note.mime.startsWith('audio/')) { |         else if (note.mime.startsWith('audio/')) { | ||||||
|             this.$audioPreview |             this.$audioPreview | ||||||
|                 .show() |                 .show() | ||||||
|                 .attr("src", openService.getUrlForDownload("api/notes/" + this.noteId + "/open")) |                 .attr("src", openService.getUrlForDownload("api/notes/" + this.noteId + "/open-partial")) | ||||||
|                 .attr("type", this.note.mime) |                 .attr("type", this.note.mime) | ||||||
|                 .css("width", this.$widget.width()); |                 .css("width", this.$widget.width()); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -164,11 +164,6 @@ div.ui-tooltip { | |||||||
|     overflow: auto; |     overflow: auto; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .alert { |  | ||||||
|     padding: 5px; |  | ||||||
|     width: auto; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* | /* | ||||||
| * .search-inactive is added to search window <webview> when the window | * .search-inactive is added to search window <webview> when the window | ||||||
| * is inactive. | * is inactive. | ||||||
| @ -761,9 +756,14 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href | |||||||
|     width: 100%; |     width: 100%; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .alert { | ||||||
|  |     padding: 8px 14px; | ||||||
|  |     width: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .alert-warning, .alert-info { | .alert-warning, .alert-info { | ||||||
|     color: var(--main-text-color) !important; |     color: var(--main-text-color) !important; | ||||||
|     background-color: var(--accented-background-color) !important; |     background-color: transparent !important; | ||||||
|     border-color: var(--main-border-color) !important; |     border-color: var(--main-border-color) !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -107,7 +107,11 @@ function processContent(images, note, content) { | |||||||
|             const filename = path.basename(src); |             const filename = path.basename(src); | ||||||
| 
 | 
 | ||||||
|             if (!dataUrl || !dataUrl.startsWith("data:image")) { |             if (!dataUrl || !dataUrl.startsWith("data:image")) { | ||||||
|                 log.info("Image could not be recognized as data URL:", dataUrl.substr(0, Math.min(100, dataUrl.length))); |                 const excerpt = dataUrl | ||||||
|  |                     ? dataUrl.substr(0, Math.min(100, dataUrl.length)) | ||||||
|  |                     : "null"; | ||||||
|  | 
 | ||||||
|  |                 log.info("Image could not be recognized as data URL: " + excerpt); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -140,7 +144,7 @@ function processContent(images, note, content) { | |||||||
| function openNote(req) { | function openNote(req) { | ||||||
|     if (utils.isElectron()) { |     if (utils.isElectron()) { | ||||||
|         ws.sendMessageToAllClients({ |         ws.sendMessageToAllClients({ | ||||||
|             type: 'open-note', |             type: 'openNote', | ||||||
|             noteId: req.params.noteId |             noteId: req.params.noteId | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,10 +3,13 @@ | |||||||
| const protectedSessionService = require('../../services/protected_session'); | const protectedSessionService = require('../../services/protected_session'); | ||||||
| const repository = require('../../services/repository'); | const repository = require('../../services/repository'); | ||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
|  | const log = require('../../services/log'); | ||||||
| const noteRevisionService = require('../../services/note_revisions'); | const noteRevisionService = require('../../services/note_revisions'); | ||||||
| const tmp = require('tmp'); | const tmp = require('tmp'); | ||||||
| const fs = require('fs'); | const fs = require('fs'); | ||||||
| const { Readable } = require('stream'); | const { Readable } = require('stream'); | ||||||
|  | const chokidar = require('chokidar'); | ||||||
|  | const ws = require('../../services/ws'); | ||||||
| 
 | 
 | ||||||
| function updateFile(req) { | function updateFile(req) { | ||||||
|     const {noteId} = req.params; |     const {noteId} = req.params; | ||||||
| @ -120,6 +123,19 @@ function saveToTmpDir(req) { | |||||||
|     fs.writeSync(tmpObj.fd, note.getContent()); |     fs.writeSync(tmpObj.fd, note.getContent()); | ||||||
|     fs.closeSync(tmpObj.fd); |     fs.closeSync(tmpObj.fd); | ||||||
| 
 | 
 | ||||||
|  |     log.info(`Saved temporary file for note ${noteId} into ${tmpObj.name}`); | ||||||
|  | 
 | ||||||
|  |     if (utils.isElectron()) { | ||||||
|  |         chokidar.watch(tmpObj.name).on('change', (path, stats) => { | ||||||
|  |             ws.sendMessageToAllClients({ | ||||||
|  |                 type: 'openedFileUpdated', | ||||||
|  |                 noteId: noteId, | ||||||
|  |                 lastModifiedMs: stats.atimeMs, | ||||||
|  |                 filePath: tmpObj.name | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return { |     return { | ||||||
|         tmpFilePath: tmpObj.name |         tmpFilePath: tmpObj.name | ||||||
|     }; |     }; | ||||||
|  | |||||||
| @ -7,6 +7,8 @@ const sql = require('../../services/sql'); | |||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
| const log = require('../../services/log'); | const log = require('../../services/log'); | ||||||
| const TaskContext = require('../../services/task_context'); | const TaskContext = require('../../services/task_context'); | ||||||
|  | const fs = require('fs'); | ||||||
|  | const noteRevisionService = require("../../services/note_revisions.js"); | ||||||
| 
 | 
 | ||||||
| function getNote(req) { | function getNote(req) { | ||||||
|     const noteId = req.params.noteId; |     const noteId = req.params.noteId; | ||||||
| @ -80,7 +82,7 @@ function deleteNote(req) { | |||||||
| function undeleteNote(req) { | function undeleteNote(req) { | ||||||
|     const note = repository.getNote(req.params.noteId); |     const note = repository.getNote(req.params.noteId); | ||||||
| 
 | 
 | ||||||
|     const taskContext = TaskContext.getInstance(utils.randomString(10), 'undelete-notes'); |     const taskContext = TaskContext.getInstance(utils.randomString(10), 'undeleteNotes'); | ||||||
| 
 | 
 | ||||||
|     noteService.undeleteNote(note, note.deleteId, taskContext); |     noteService.undeleteNote(note, note.deleteId, taskContext); | ||||||
| 
 | 
 | ||||||
| @ -109,7 +111,7 @@ function protectNote(req) { | |||||||
|     const protect = !!parseInt(req.params.isProtected); |     const protect = !!parseInt(req.params.isProtected); | ||||||
|     const includingSubTree = !!parseInt(req.query.subtree); |     const includingSubTree = !!parseInt(req.query.subtree); | ||||||
| 
 | 
 | ||||||
|     const taskContext = new TaskContext(utils.randomString(10), 'protect-notes', {protect}); |     const taskContext = new TaskContext(utils.randomString(10), 'protectNotes', {protect}); | ||||||
| 
 | 
 | ||||||
|     noteService.protectNoteRecursively(note, protect, includingSubTree, taskContext); |     noteService.protectNoteRecursively(note, protect, includingSubTree, taskContext); | ||||||
| 
 | 
 | ||||||
| @ -273,6 +275,29 @@ function getDeleteNotesPreview(req) { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function uploadModifiedFile(req) { | ||||||
|  |     const noteId = req.params.noteId; | ||||||
|  |     const {filePath} = req.body; | ||||||
|  | 
 | ||||||
|  |     const note = repository.getNote(noteId); | ||||||
|  | 
 | ||||||
|  |     if (!note) { | ||||||
|  |         return [404, `Note ${noteId} has not been found`]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     log.info(`Updating note ${noteId} with content from ${filePath}`); | ||||||
|  | 
 | ||||||
|  |     noteRevisionService.createNoteRevision(note); | ||||||
|  | 
 | ||||||
|  |     const fileContent = fs.readFileSync(filePath); | ||||||
|  | 
 | ||||||
|  |     if (!fileContent) { | ||||||
|  |         return [400, `File ${fileContent} is empty`]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     note.setContent(fileContent); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     getNote, |     getNote, | ||||||
|     updateNote, |     updateNote, | ||||||
| @ -286,5 +311,6 @@ module.exports = { | |||||||
|     changeTitle, |     changeTitle, | ||||||
|     duplicateSubtree, |     duplicateSubtree, | ||||||
|     eraseDeletedNotesNow, |     eraseDeletedNotesNow, | ||||||
|     getDeleteNotesPreview |     getDeleteNotesPreview, | ||||||
|  |     uploadModifiedFile | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| const utils = require('../services/utils'); | const utils = require('../services/utils'); | ||||||
| const optionService = require('../services/options'); | const optionService = require('../services/options'); | ||||||
| const myScryptService = require('../services/my_scrypt'); | const myScryptService = require('../services/my_scrypt'); | ||||||
|  | const log = require('../services/log'); | ||||||
| 
 | 
 | ||||||
| function loginPage(req, res) { | function loginPage(req, res) { | ||||||
|     res.render('login', { failedAuth: false }); |     res.render('login', { failedAuth: false }); | ||||||
| @ -28,6 +29,9 @@ function login(req, res) { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|  |         // note that logged IP address is usually meaningless since the traffic should come from a reverse proxy
 | ||||||
|  |         log.info(`WARNING: Wrong username / password from ${req.ip}, rejecting.`); | ||||||
|  | 
 | ||||||
|         res.render('login', {'failedAuth': true}); |         res.render('login', {'failedAuth': true}); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -165,6 +165,7 @@ function register(app) { | |||||||
|     apiRoute(POST, '/api/notes/erase-deleted-notes-now', notesApiRoute.eraseDeletedNotesNow); |     apiRoute(POST, '/api/notes/erase-deleted-notes-now', notesApiRoute.eraseDeletedNotesNow); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle); |     apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle); | ||||||
|     apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree); |     apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree); | ||||||
|  |     apiRoute(POST, '/api/notes/:noteId/upload-modified-file', notesApiRoute.uploadModifiedFile); | ||||||
| 
 | 
 | ||||||
|     apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate); |     apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate); | ||||||
| 
 | 
 | ||||||
| @ -177,14 +178,15 @@ function register(app) { | |||||||
|     route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], |     route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], | ||||||
|         filesRoute.updateFile, apiResultHandler); |         filesRoute.updateFile, apiResultHandler); | ||||||
| 
 | 
 | ||||||
|     route(GET, '/api/notes/:noteId/open', [auth.checkApiAuthOrElectron], |     route(GET, '/api/notes/:noteId/open', [auth.checkApiAuthOrElectron], filesRoute.openFile); | ||||||
|  |     route(GET, '/api/notes/:noteId/open-partial', [auth.checkApiAuthOrElectron], | ||||||
|         createPartialContentHandler(filesRoute.fileContentProvider, { |         createPartialContentHandler(filesRoute.fileContentProvider, { | ||||||
|             debug: (string, extra) => { console.log(string, extra); } |             debug: (string, extra) => { console.log(string, extra); } | ||||||
|         })); |         })); | ||||||
|     route(GET, '/api/notes/:noteId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadFile); |     route(GET, '/api/notes/:noteId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadFile); | ||||||
|     // this "hacky" path is used for easier referencing of CSS resources
 |     // this "hacky" path is used for easier referencing of CSS resources
 | ||||||
|     route(GET, '/api/notes/download/:noteId', [auth.checkApiAuthOrElectron], filesRoute.downloadFile); |     route(GET, '/api/notes/download/:noteId', [auth.checkApiAuthOrElectron], filesRoute.downloadFile); | ||||||
|     apiRoute(POST, '/api/notes/:noteId/saveToTmpDir', filesRoute.saveToTmpDir); |     apiRoute(POST, '/api/notes/:noteId/save-to-tmp-dir', filesRoute.saveToTmpDir); | ||||||
| 
 | 
 | ||||||
|     apiRoute(GET, '/api/notes/:noteId/attributes', attributesRoute.getEffectiveNoteAttributes); |     apiRoute(GET, '/api/notes/:noteId/attributes', attributesRoute.getEffectiveNoteAttributes); | ||||||
|     apiRoute(POST, '/api/notes/:noteId/attributes', attributesRoute.addNoteAttribute); |     apiRoute(POST, '/api/notes/:noteId/attributes', attributesRoute.addNoteAttribute); | ||||||
|  | |||||||
										
											Binary file not shown.
										
									
								
							| @ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2021-04-11T22:29:56+02:00", buildRevision: "58e4bd4974275a113c50e4ed7a554987921d55fc" }; | module.exports = { buildDate:"2021-04-19T22:43:03+02:00", buildRevision: "6136243d6117910b80feafad4fc7121ecc42d794" }; | ||||||
|  | |||||||
| @ -36,6 +36,12 @@ async function importOpml(taskContext, fileBuffer, parentNote) { | |||||||
|         if (opmlVersion === 1) { |         if (opmlVersion === 1) { | ||||||
|             title = outline.$.title; |             title = outline.$.title; | ||||||
|             content = toHtml(outline.$.text); |             content = toHtml(outline.$.text); | ||||||
|  | 
 | ||||||
|  |             if (!title || !title.trim()) { | ||||||
|  |                 // https://github.com/zadam/trilium/issues/1862
 | ||||||
|  |                 title = outline.$.text; | ||||||
|  |                 content = ''; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         else if (opmlVersion === 2) { |         else if (opmlVersion === 2) { | ||||||
|             title = outline.$.text; |             title = outline.$.text; | ||||||
|  | |||||||
| @ -352,6 +352,12 @@ const DEFAULT_KEYBOARD_ACTIONS = [ | |||||||
|         defaultShortcuts: [], |         defaultShortcuts: [], | ||||||
|         scope: "window" |         scope: "window" | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |         actionName: "openNoteExternally", | ||||||
|  |         defaultShortcuts: [], | ||||||
|  |         description: "Open note as a file with default application", | ||||||
|  |         scope: "window" | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|         actionName: "renderActiveNote", |         actionName: "renderActiveNote", | ||||||
|         defaultShortcuts: [], |         defaultShortcuts: [], | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const Expression = require('./expression'); | const Expression = require('./expression'); | ||||||
|  | const TrueExp = require("./true.js"); | ||||||
| 
 | 
 | ||||||
| class AndExp extends Expression { | class AndExp extends Expression { | ||||||
|     static of(subExpressions) { |     static of(subExpressions) { | ||||||
| @ -10,6 +11,8 @@ class AndExp extends Expression { | |||||||
|             return subExpressions[0]; |             return subExpressions[0]; | ||||||
|         } else if (subExpressions.length > 0) { |         } else if (subExpressions.length > 0) { | ||||||
|             return new AndExp(subExpressions); |             return new AndExp(subExpressions); | ||||||
|  |         } else { | ||||||
|  |             return new TrueExp(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| const Expression = require('./expression'); | const Expression = require('./expression'); | ||||||
| const NoteSet = require('../note_set'); | const NoteSet = require('../note_set'); | ||||||
|  | const TrueExp = require("./true"); | ||||||
| 
 | 
 | ||||||
| class OrExp extends Expression { | class OrExp extends Expression { | ||||||
|     static of(subExpressions) { |     static of(subExpressions) { | ||||||
| @ -13,6 +14,9 @@ class OrExp extends Expression { | |||||||
|         else if (subExpressions.length > 0) { |         else if (subExpressions.length > 0) { | ||||||
|             return new OrExp(subExpressions); |             return new OrExp(subExpressions); | ||||||
|         } |         } | ||||||
|  |         else { | ||||||
|  |             return new TrueExp(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     constructor(subExpressions) { |     constructor(subExpressions) { | ||||||
|  | |||||||
| @ -28,17 +28,33 @@ class OrderByAndLimitExp extends Expression { | |||||||
|                 let valA = valueExtractor.extract(a); |                 let valA = valueExtractor.extract(a); | ||||||
|                 let valB = valueExtractor.extract(b); |                 let valB = valueExtractor.extract(b); | ||||||
| 
 | 
 | ||||||
|                 if (!isNaN(valA) && !isNaN(valB)) { |                 if (valA === null && valB === null) { | ||||||
|  |                     // neither has attribute at all
 | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 else if (valB === null) { | ||||||
|  |                     return smaller; | ||||||
|  |                 } | ||||||
|  |                 else if (valA === null) { | ||||||
|  |                     return larger; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // if both are numbers then parse them for numerical comparison
 | ||||||
|  |                 // beware that isNaN will return false for empty string and null
 | ||||||
|  |                 if (valA.trim() !== "" && valB.trim() !== "" && !isNaN(valA) && !isNaN(valB)) { | ||||||
|                     valA = parseFloat(valA); |                     valA = parseFloat(valA); | ||||||
|                     valB = parseFloat(valB); |                     valB = parseFloat(valB); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if (valA < valB) { |                 if (!valA && !valB) { | ||||||
|  |                     // the attribute is not defined in either note so continue to next order definition
 | ||||||
|  |                     continue; | ||||||
|  |                 } else if (!valB || valA < valB) { | ||||||
|                     return smaller; |                     return smaller; | ||||||
|                 } else if (valA > valB) { |                 } else if (!valA || valA > valB) { | ||||||
|                     return larger; |                     return larger; | ||||||
|                 } |                 } | ||||||
|                 // else go to next order definition
 |                 // else the values are equal and continue to next order definition
 | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return 0; |             return 0; | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								src/services/search/expressions/true.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/services/search/expressions/true.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | const Expression = require('./expression'); | ||||||
|  | 
 | ||||||
|  | class TrueExp extends Expression { | ||||||
|  |     execute(inputNoteSet, executionContext) { | ||||||
|  |         return inputNoteSet; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = TrueExp; | ||||||
| @ -329,6 +329,9 @@ function getExpression(tokens, searchContext, level = 0) { | |||||||
|         else if (op === 'or') { |         else if (op === 'or') { | ||||||
|             return OrExp.of(expressions); |             return OrExp.of(expressions); | ||||||
|         } |         } | ||||||
|  |         else { | ||||||
|  |             throw new Error(`Unrecognized op=${op}`); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (i = 0; i < tokens.length; i++) { |     for (i = 0; i < tokens.length; i++) { | ||||||
| @ -358,8 +361,7 @@ function getExpression(tokens, searchContext, level = 0) { | |||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             exp.subExpression = getAggregateExpression(); |             exp.subExpression = getAggregateExpression();console.log(exp); | ||||||
| 
 |  | ||||||
|             return exp; |             return exp; | ||||||
|         } |         } | ||||||
|         else if (token === 'not') { |         else if (token === 'not') { | ||||||
|  | |||||||
| @ -69,7 +69,7 @@ class ValueExtractor { | |||||||
| 
 | 
 | ||||||
|                 i++; |                 i++; | ||||||
|             } |             } | ||||||
|             else if (pathEl in PROP_MAPPING) { |             else if (pathEl in PROP_MAPPING || pathEl === 'random') { | ||||||
|                 if (i !== this.propertyPath.length - 1) { |                 if (i !== this.propertyPath.length - 1) { | ||||||
|                     return `${pathEl} is a terminal property specifier and must be at the end`; |                     return `${pathEl} is a terminal property specifier and must be at the end`; | ||||||
|                 } |                 } | ||||||
| @ -113,6 +113,9 @@ class ValueExtractor { | |||||||
|             else if (cur() === 'children') { |             else if (cur() === 'children') { | ||||||
|                 cursor = cursor.children[0]; |                 cursor = cursor.children[0]; | ||||||
|             } |             } | ||||||
|  |             else if (cur() === 'random') { | ||||||
|  |                 return Math.random(); | ||||||
|  |             } | ||||||
|             else if (cur() in PROP_MAPPING) { |             else if (cur() in PROP_MAPPING) { | ||||||
|                 return cursor[PROP_MAPPING[cur()]]; |                 return cursor[PROP_MAPPING[cur()]]; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -59,6 +59,7 @@ async function sync() { | |||||||
|         if (e.message && |         if (e.message && | ||||||
|                 (e.message.includes('ECONNREFUSED') || |                 (e.message.includes('ECONNREFUSED') || | ||||||
|                  e.message.includes('ERR_CONNECTION_REFUSED') || |                  e.message.includes('ERR_CONNECTION_REFUSED') || | ||||||
|  |                  e.message.includes('ERR_ADDRESS_UNREACHABLE') || | ||||||
|                  e.message.includes('Bad Gateway'))) { |                  e.message.includes('Bad Gateway'))) { | ||||||
| 
 | 
 | ||||||
|             ws.syncFailed(); |             ws.syncFailed(); | ||||||
|  | |||||||
| @ -38,7 +38,7 @@ class TaskContext { | |||||||
|             this.lastSentCountTs = Date.now(); |             this.lastSentCountTs = Date.now(); | ||||||
| 
 | 
 | ||||||
|             ws.sendMessageToAllClients({ |             ws.sendMessageToAllClients({ | ||||||
|                 type: 'task-progress-count', |                 type: 'taskProgressCount', | ||||||
|                 taskId: this.taskId, |                 taskId: this.taskId, | ||||||
|                 taskType: this.taskType, |                 taskType: this.taskType, | ||||||
|                 data: this.data, |                 data: this.data, | ||||||
| @ -49,7 +49,7 @@ class TaskContext { | |||||||
| 
 | 
 | ||||||
|     reportError(message) { |     reportError(message) { | ||||||
|         ws.sendMessageToAllClients({ |         ws.sendMessageToAllClients({ | ||||||
|             type: 'task-error', |             type: 'taskError', | ||||||
|             taskId: this.taskId, |             taskId: this.taskId, | ||||||
|             taskType: this.taskType, |             taskType: this.taskType, | ||||||
|             data: this.data, |             data: this.data, | ||||||
| @ -59,7 +59,7 @@ class TaskContext { | |||||||
| 
 | 
 | ||||||
|     taskSucceeded(result) { |     taskSucceeded(result) { | ||||||
|         ws.sendMessageToAllClients({ |         ws.sendMessageToAllClients({ | ||||||
|             type: 'task-succeeded', |             type: 'taskSucceeded', | ||||||
|             taskId: this.taskId, |             taskId: this.taskId, | ||||||
|             taskType: this.taskType, |             taskType: this.taskType, | ||||||
|             data: this.data, |             data: this.data, | ||||||
|  | |||||||
| @ -188,6 +188,8 @@ function formatDownloadTitle(filename, type, mime) { | |||||||
|         filename = "untitled"; |         filename = "untitled"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     filename = sanitize(filename); | ||||||
|  | 
 | ||||||
|     if (type === 'text') { |     if (type === 'text') { | ||||||
|         return filename + '.html'; |         return filename + '.html'; | ||||||
|     } else if (['relation-map', 'search'].includes(type)) { |     } else if (['relation-map', 'search'].includes(type)) { | ||||||
|  | |||||||
| @ -18,12 +18,12 @@ | |||||||
|                                 <ul> |                                 <ul> | ||||||
|                                     <li><kbd>UP</kbd>, <kbd>DOWN</kbd> - go up/down in the list of notes</li> |                                     <li><kbd>UP</kbd>, <kbd>DOWN</kbd> - go up/down in the list of notes</li> | ||||||
|                                     <li><kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - collapse/expand node</li> |                                     <li><kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - collapse/expand node</li> | ||||||
|                                     <li><kbd data-command="backInNoteHistory"></kbd>, <kbd data-command="forwardInNoteHistory"></kbd> - go back / forwards in the history</li> |                                     <li><kbd data-command="backInNoteHistory">not set</kbd>, <kbd data-command="forwardInNoteHistory">not set</kbd> - go back / forwards in the history</li> | ||||||
|                                     <li><kbd data-command="jumpToNote"></kbd> - show <a class="external" href="https://github.com/zadam/trilium/wiki/Note-navigation#jump-to-note">"Jump to" dialog</a></li> |                                     <li><kbd data-command="jumpToNote">not set</kbd> - show <a class="external" href="https://github.com/zadam/trilium/wiki/Note-navigation#jump-to-note">"Jump to" dialog</a></li> | ||||||
|                                     <li><kbd data-command="scrollToActiveNote"></kbd> - scroll to active note</li> |                                     <li><kbd data-command="scrollToActiveNote">not set</kbd> - scroll to active note</li> | ||||||
|                                     <li><kbd>Backspace</kbd> - jump to parent note</li> |                                     <li><kbd>Backspace</kbd> - jump to parent note</li> | ||||||
|                                     <li><kbd data-command="collapseTree"></kbd> - collapse whole note tree</li> |                                     <li><kbd data-command="collapseTree">not set</kbd> - collapse whole note tree</li> | ||||||
|                                     <li><kbd data-command="collapseSubtree"></kbd> - collapse sub-tree</li> |                                     <li><kbd data-command="collapseSubtree">not set</kbd> - collapse sub-tree</li> | ||||||
|                                 </ul> |                                 </ul> | ||||||
|                             </p> |                             </p> | ||||||
|                         </div> |                         </div> | ||||||
| @ -40,10 +40,10 @@ | |||||||
| 
 | 
 | ||||||
|                             Only in desktop (electron build): |                             Only in desktop (electron build): | ||||||
|                             <ul> |                             <ul> | ||||||
|                                 <li><kbd data-command="openNewTab"></kbd> open empty tab</li> |                                 <li><kbd data-command="openNewTab">not set</kbd> open empty tab</li> | ||||||
|                                 <li><kbd data-command="closeActiveTab"></kbd> close active tab</li> |                                 <li><kbd data-command="closeActiveTab">not set</kbd> close active tab</li> | ||||||
|                                 <li><kbd data-command="activateNextTab"></kbd> activate next tab</li> |                                 <li><kbd data-command="activateNextTab">not set</kbd> activate next tab</li> | ||||||
|                                 <li><kbd data-command="activatePreviousTab"></kbd> activate previous tab</li> |                                 <li><kbd data-command="activatePreviousTab">not set</kbd> activate previous tab</li> | ||||||
|                             </ul> |                             </ul> | ||||||
|                             </p> |                             </p> | ||||||
|                         </div> |                         </div> | ||||||
| @ -55,9 +55,9 @@ | |||||||
| 
 | 
 | ||||||
|                             <p class="card-text"> |                             <p class="card-text"> | ||||||
|                                 <ul> |                                 <ul> | ||||||
|                                     <li><kbd data-command="createNoteAfter"></kbd> - create new note after the active note</li> |                                     <li><kbd data-command="createNoteAfter">not set</kbd> - create new note after the active note</li> | ||||||
|                                     <li><kbd data-command="createNoteInto"></kbd> - create new sub-note into active note</li> |                                     <li><kbd data-command="createNoteInto">not set</kbd> - create new sub-note into active note</li> | ||||||
|                                     <li><kbd data-command="editBranchPrefix"></kbd> - edit <a class="external" href="https://github.com/zadam/trilium/wiki/Tree concepts#prefix">prefix</a> of active note clone</li> |                                     <li><kbd data-command="editBranchPrefix">not set</kbd> - edit <a class="external" href="https://github.com/zadam/trilium/wiki/Tree concepts#prefix">prefix</a> of active note clone</li> | ||||||
|                                 </ul> |                                 </ul> | ||||||
|                             </p> |                             </p> | ||||||
|                         </div> |                         </div> | ||||||
| @ -69,15 +69,15 @@ | |||||||
| 
 | 
 | ||||||
|                             <p class="card-text"> |                             <p class="card-text"> | ||||||
|                                 <ul> |                                 <ul> | ||||||
|                                     <li><kbd data-command="moveNoteUp"></kbd>, <kbd data-command="moveNoteDown"></kbd> - move note up/down in the note list</li> |                                     <li><kbd data-command="moveNoteUp">not set</kbd>, <kbd data-command="moveNoteDown">not set</kbd> - move note up/down in the note list</li> | ||||||
|                                     <li><kbd data-command="moveNoteUpInHierarchy"></kbd>, <kbd data-command="moveNoteDownInHierarchy"></kbd> - move note up in the hierarchy</li> |                                     <li><kbd data-command="moveNoteUpInHierarchy">not set</kbd>, <kbd data-command="moveNoteDownInHierarchy">not set</kbd> - move note up in the hierarchy</li> | ||||||
|                                     <li><kbd data-command="addNoteAboveToSelection"></kbd>, <kbd data-command="addNoteBelowToSelection"></kbd> - multi-select note above/below</li> |                                     <li><kbd data-command="addNoteAboveToSelection">not set</kbd>, <kbd data-command="addNoteBelowToSelection">not set</kbd> - multi-select note above/below</li> | ||||||
|                                     <li><kbd data-command="selectAllNotesInParent"></kbd> - select all notes in the current level</li> |                                     <li><kbd data-command="selectAllNotesInParent">not set</kbd> - select all notes in the current level</li> | ||||||
|                                     <li><kbd>Shift+click</kbd> - select note</li> |                                     <li><kbd>Shift+click</kbd> - select note</li> | ||||||
|                                     <li><kbd data-command="copyNotesToClipboard"></kbd> - copy active note (or current selection) into clipboard (used for <a class="external" href="https://github.com/zadam/trilium/wiki/Cloning notes">cloning</a>)</li> |                                     <li><kbd data-command="copyNotesToClipboard">not set</kbd> - copy active note (or current selection) into clipboard (used for <a class="external" href="https://github.com/zadam/trilium/wiki/Cloning notes">cloning</a>)</li> | ||||||
|                                     <li><kbd data-command="cutNotesToClipboard"></kbd> - cut current (or current selection) note into clipboard (used for moving notes)</li> |                                     <li><kbd data-command="cutNotesToClipboard">not set</kbd> - cut current (or current selection) note into clipboard (used for moving notes)</li> | ||||||
|                                     <li><kbd data-command="pasteNotesFromClipboard"></kbd> - paste note(s) as sub-note into active note (which is either move or clone depending on whether it was copied or cut into clipboard)</li> |                                     <li><kbd data-command="pasteNotesFromClipboard">not set</kbd> - paste note(s) as sub-note into active note (which is either move or clone depending on whether it was copied or cut into clipboard)</li> | ||||||
|                                     <li><kbd data-command="deleteNotes"></kbd> - delete note / sub-tree</li> |                                     <li><kbd data-command="deleteNotes">not set</kbd> - delete note / sub-tree</li> | ||||||
|                                 </ul> |                                 </ul> | ||||||
|                             </p> |                             </p> | ||||||
|                         </div> |                         </div> | ||||||
| @ -89,12 +89,12 @@ | |||||||
| 
 | 
 | ||||||
|                             <p class="card-text"> |                             <p class="card-text"> | ||||||
|                                 <ul> |                                 <ul> | ||||||
|                                     <li><kbd data-command="editNoteTitle"></kbd> in tree pane will switch from tree pane into note title. Enter from note title will switch focus to text editor. |                                     <li><kbd data-command="editNoteTitle">not set</kbd> in tree pane will switch from tree pane into note title. Enter from note title will switch focus to text editor. | ||||||
|                                         <kbd data-command="scrollToActiveNote"></kbd> will switch back from editor to tree pane.</li> |                                         <kbd data-command="scrollToActiveNote">not set</kbd> will switch back from editor to tree pane.</li> | ||||||
|                                     <li><kbd>Ctrl+K</kbd> - create / edit external link</li> |                                     <li><kbd>Ctrl+K</kbd> - create / edit external link</li> | ||||||
|                                     <li><kbd data-command="addLinkToText"></kbd> - create internal link</li> |                                     <li><kbd data-command="addLinkToText">not set</kbd> - create internal link</li> | ||||||
|                                     <li><kbd data-command="insertDateTimeToText"></kbd> - insert current date and time at caret position</li> |                                     <li><kbd data-command="insertDateTimeToText">not set</kbd> - insert current date and time at caret position</li> | ||||||
|                                     <li><kbd data-command="scrollToActiveNote"></kbd> - jump away to the tree pane and scroll to active note</li> |                                     <li><kbd data-command="scrollToActiveNote">not set</kbd> - jump away to the tree pane and scroll to active note</li> | ||||||
|                                 </ul> |                                 </ul> | ||||||
|                             </p> |                             </p> | ||||||
|                         </div> |                         </div> | ||||||
| @ -121,9 +121,9 @@ | |||||||
| 
 | 
 | ||||||
|                             <p class="card-text"> |                             <p class="card-text"> | ||||||
|                                 <ul> |                                 <ul> | ||||||
|                                     <li><kbd data-command="reloadFrontendApp"></kbd> - reload Trilium frontend</li> |                                     <li><kbd data-command="reloadFrontendApp">not set</kbd> - reload Trilium frontend</li> | ||||||
|                                     <li><kbd data-command="openDevTools"></kbd> - show developer tools</li> |                                     <li><kbd data-command="openDevTools">not set</kbd> - show developer tools</li> | ||||||
|                                     <li><kbd data-command="showSQLConsole"></kbd> - show SQL console</li> |                                     <li><kbd data-command="showSQLConsole">not set</kbd> - show SQL console</li> | ||||||
|                                 </ul> |                                 </ul> | ||||||
|                             </p> |                             </p> | ||||||
|                         </div> |                         </div> | ||||||
| @ -135,9 +135,9 @@ | |||||||
| 
 | 
 | ||||||
|                             <p class="card-text"> |                             <p class="card-text"> | ||||||
|                                 <ul> |                                 <ul> | ||||||
|                                     <li><kbd data-command="toggleZenMode"></kbd> - Zen mode - display only note editor, everything else is hidden</li> |                                     <li><kbd data-command="toggleZenMode">not set</kbd> - Zen mode - display only note editor, everything else is hidden</li> | ||||||
|                                     <li><kbd data-command="searchNotes"></kbd> - toggle search form in tree pane</li> |                                     <li><kbd data-command="quickSearch">not set</kbd> - focus on quick search input</li> | ||||||
|                                     <li><kbd data-command="findInText"></kbd> - in page search</li> |                                     <li><kbd data-command="findInText">not set</kbd> - in page search</li> | ||||||
|                                 </ul> |                                 </ul> | ||||||
|                             </p> |                             </p> | ||||||
|                         </div> |                         </div> | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								src/www
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/www
									
									
									
									
									
								
							| @ -8,10 +8,13 @@ process.on('unhandledRejection', error => { | |||||||
|     require('./services/log').info(error); |     require('./services/log').info(error); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| process.on('SIGINT', function() { | function exit() { | ||||||
| 	console.log("Caught interrupt signal. Exiting."); |     console.log("Caught interrupt/termination signal. Exiting."); | ||||||
| 	process.exit(); |     process.exit(0); | ||||||
| }); | } | ||||||
|  | 
 | ||||||
|  | process.on('SIGINT', exit); | ||||||
|  | process.on('SIGTERM', exit); | ||||||
| 
 | 
 | ||||||
| const { app, sessionParser } = require('./app'); | const { app, sessionParser } = require('./app'); | ||||||
| const fs = require('fs'); | const fs = require('fs'); | ||||||
|  | |||||||
| @ -10,6 +10,9 @@ | |||||||
|       <excludeFolder url="file://$MODULE_DIR$/dist" /> |       <excludeFolder url="file://$MODULE_DIR$/dist" /> | ||||||
|       <excludeFolder url="file://$MODULE_DIR$/src/public/app-dist" /> |       <excludeFolder url="file://$MODULE_DIR$/src/public/app-dist" /> | ||||||
|       <excludeFolder url="file://$MODULE_DIR$/libraries" /> |       <excludeFolder url="file://$MODULE_DIR$/libraries" /> | ||||||
|  |       <excludeFolder url="file://$MODULE_DIR$/libraries" /> | ||||||
|  |       <excludeFolder url="file://$MODULE_DIR$/docs" /> | ||||||
|  |       <excludeFolder url="file://$MODULE_DIR$/bin/better-sqlite3" /> | ||||||
|     </content> |     </content> | ||||||
|     <orderEntry type="inheritedJdk" /> |     <orderEntry type="inheritedJdk" /> | ||||||
|     <orderEntry type="sourceFolder" forTests="false" /> |     <orderEntry type="sourceFolder" forTests="false" /> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam