mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	opened file change detection now useable on all note types
This commit is contained in:
		| @@ -36,6 +36,7 @@ import SearchResultWidget from "../widgets/search_result.js"; | ||||
| import SyncStatusWidget from "../widgets/sync_status.js"; | ||||
| import ScrollingContainer from "../widgets/containers/scrolling_container.js"; | ||||
| import RootContainer from "../widgets/containers/root_container.js"; | ||||
| import NoteUpdateStatusWidget from "../widgets/note_update_status.js"; | ||||
|  | ||||
| const RIGHT_PANE_CSS = ` | ||||
| <style> | ||||
| @@ -177,6 +178,7 @@ export default class DesktopLayout { | ||||
|                             .child(new InheritedAttributesWidget()) | ||||
|                         ) | ||||
|                     ) | ||||
|                     .child(new NoteUpdateStatusWidget()) | ||||
|                     .child( | ||||
|                         new TabCachingWidget(() => new ScrollingContainer() | ||||
|                             .child(new SqlTableSchemasWidget()) | ||||
|   | ||||
| @@ -11,6 +11,10 @@ function fileModificationUploaded(noteId) { | ||||
|     delete fileModificationStatus[noteId]; | ||||
| } | ||||
|  | ||||
| function ignoreModification(noteId) { | ||||
|     delete fileModificationStatus[noteId]; | ||||
| } | ||||
|  | ||||
| ws.subscribeToMessages(async message => { | ||||
|     if (message.type !== 'openedFileUpdated') { | ||||
|         return; | ||||
| @@ -27,5 +31,6 @@ ws.subscribeToMessages(async message => { | ||||
|  | ||||
| export default { | ||||
|     getFileModificationStatus, | ||||
|     fileModificationUploaded | ||||
|     fileModificationUploaded, | ||||
|     ignoreModification | ||||
| } | ||||
|   | ||||
| @@ -44,7 +44,7 @@ async function getRenderedContent(note, options = {}) { | ||||
|         const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>'); | ||||
|  | ||||
|         $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 | ||||
|         $openButton.toggle(!note.isProtected); | ||||
|   | ||||
| @@ -21,9 +21,9 @@ function downloadFileNote(noteId) { | ||||
|     download(url); | ||||
| } | ||||
|  | ||||
| async function openFileNote(noteId) { | ||||
| async function openNoteExternally(noteId) { | ||||
|     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 res = await electron.shell.openPath(resp.tmpFilePath); | ||||
| @@ -66,7 +66,7 @@ function getHost() { | ||||
| export default { | ||||
|     download, | ||||
|     downloadFileNote, | ||||
|     openFileNote, | ||||
|     openNoteExternally, | ||||
|     downloadNoteRevision, | ||||
|     getUrlForDownload | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,12 @@ | ||||
| import TabAwareWidget from "./tab_aware_widget.js"; | ||||
| import protectedSessionService from "../services/protected_session.js"; | ||||
| import openService from "../services/open.js"; | ||||
|  | ||||
| const TPL = ` | ||||
| <div class="dropdown note-actions"> | ||||
|     <style> | ||||
|     .note-actions .dropdown-menu { | ||||
|         width: 15em; | ||||
|         width: 20em; | ||||
|     } | ||||
|      | ||||
|     .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="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="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 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> | ||||
| @@ -119,6 +121,9 @@ export default class NoteActionsWidget extends TabAwareWidget { | ||||
|  | ||||
|         this.$widget.on('click', '.dropdown-item', | ||||
|             () => this.$widget.find('.dropdown-toggle').dropdown('toggle')); | ||||
|  | ||||
|         this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button"); | ||||
|         this.$openNoteExternallyButton.on('click', () => openService.openNoteExternally(this.noteId)); | ||||
|     } | ||||
|  | ||||
|     refreshWithNote(note) { | ||||
|   | ||||
							
								
								
									
										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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -82,7 +82,7 @@ export default class FilePropertiesWidget extends TabAwareWidget { | ||||
|         this.$uploadNewRevisionInput = this.$widget.find(".file-upload-new-revision-input"); | ||||
|  | ||||
|         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.$uploadNewRevisionInput.trigger("click"); | ||||
|   | ||||
| @@ -26,6 +26,8 @@ const TPL = ` | ||||
|     <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-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-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.$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.on('click', () => openService.downloadFileNote(this.noteId)); | ||||
|  | ||||
|   | ||||
| @@ -24,12 +24,6 @@ const TPL = ` | ||||
|         } | ||||
|     </style> | ||||
|      | ||||
|     <div class="file-watcher-wrapper alert alert-warning"> | ||||
|         <p>File <code class="file-watcher-path"></code> has been last modified on <span class="file-watcher-last-modified"></span>.</p>  | ||||
|          | ||||
|         <button class="btn btn-sm file-watcher-upload-button">Upload modified file</button> | ||||
|     </div> | ||||
|      | ||||
|     <pre class="file-preview-content"></pre> | ||||
|      | ||||
|     <div class="file-preview-not-available alert alert-info"> | ||||
| @@ -54,22 +48,6 @@ export default class FileTypeWidget extends TypeWidget { | ||||
|         this.$pdfPreview = this.$widget.find(".pdf-preview"); | ||||
|         this.$videoPreview = this.$widget.find(".video-preview"); | ||||
|         this.$audioPreview = this.$widget.find(".audio-preview"); | ||||
|  | ||||
|         this.$fileWatcherWrapper = this.$widget.find(".file-watcher-wrapper"); | ||||
|         this.$fileWatcherWrapper.hide(); | ||||
|  | ||||
|         this.$fileWatcherPath = this.$widget.find(".file-watcher-path"); | ||||
|         this.$fileWatcherLastModified = this.$widget.find(".file-watcher-last-modified"); | ||||
|         this.$fileWatcherUploadButton = this.$widget.find(".file-watcher-upload-button"); | ||||
|  | ||||
|         this.$fileWatcherUploadButton.on("click", async () => { | ||||
|             await server.post(`notes/${this.noteId}/upload-modified-file`, { | ||||
|                 filePath: this.$fileWatcherPath.text() | ||||
|             }); | ||||
|  | ||||
|             fileWatcher.fileModificationUploaded(this.noteId); | ||||
|             this.refreshFileWatchingStatus(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     async doRefresh(note) { | ||||
| @@ -107,22 +85,5 @@ export default class FileTypeWidget extends TypeWidget { | ||||
|         else { | ||||
|             this.$previewNotAvailable.show(); | ||||
|         } | ||||
|  | ||||
|         this.refreshFileWatchingStatus(); | ||||
|     } | ||||
|  | ||||
|     refreshFileWatchingStatus() { | ||||
|         const status = fileWatcher.getFileModificationStatus(this.noteId); | ||||
|  | ||||
|         this.$fileWatcherWrapper.toggle(!!status); | ||||
|  | ||||
|         if (status) { | ||||
|             this.$fileWatcherPath.text(status.filePath); | ||||
|             this.$fileWatcherLastModified.text(dayjs.unix(status.lastModifiedMs / 1000).format("HH:mm:ss")); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     openedFileUpdatedEvent(data) { | ||||
|         this.refreshFileWatchingStatus(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| const protectedSessionService = require('../../services/protected_session'); | ||||
| const repository = require('../../services/repository'); | ||||
| const utils = require('../../services/utils'); | ||||
| const log = require('../../services/log'); | ||||
| const noteRevisionService = require('../../services/note_revisions'); | ||||
| const tmp = require('tmp'); | ||||
| const fs = require('fs'); | ||||
| @@ -122,6 +123,8 @@ function saveToTmpDir(req) { | ||||
|     fs.writeSync(tmpObj.fd, note.getContent()); | ||||
|     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({ | ||||
| @@ -130,8 +133,6 @@ function saveToTmpDir(req) { | ||||
|                 lastModifiedMs: stats.atimeMs, | ||||
|                 filePath: tmpObj.name | ||||
|             }); | ||||
|  | ||||
|             console.log(stats, path); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -285,6 +285,8 @@ function uploadModifiedFile(req) { | ||||
|         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); | ||||
|   | ||||
| @@ -186,7 +186,7 @@ function register(app) { | ||||
|     route(GET, '/api/notes/:noteId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadFile); | ||||
|     // this "hacky" path is used for easier referencing of CSS resources | ||||
|     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(POST, '/api/notes/:noteId/attributes', attributesRoute.addNoteAttribute); | ||||
|   | ||||
| @@ -352,6 +352,12 @@ const DEFAULT_KEYBOARD_ACTIONS = [ | ||||
|         defaultShortcuts: [], | ||||
|         scope: "window" | ||||
|     }, | ||||
|     { | ||||
|         actionName: "openNoteExternally", | ||||
|         defaultShortcuts: [], | ||||
|         description: "Open note as a file with default application", | ||||
|         scope: "window" | ||||
|     }, | ||||
|     { | ||||
|         actionName: "renderActiveNote", | ||||
|         defaultShortcuts: [], | ||||
|   | ||||
		Reference in New Issue
	
	Block a user