mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	fixes attachments
This commit is contained in:
		| @@ -99,7 +99,7 @@ function setupContextMenu() { | |||||||
|                 searchEngineName = customSearchEngineName; |                 searchEngineName = customSearchEngineName; | ||||||
|                 searchEngineUrl = customSearchEngineUrl; |                 searchEngineUrl = customSearchEngineUrl; | ||||||
|             } else { |             } else { | ||||||
|                 searchEngineName = "Duckduckgo"; |                 searchEngineName = "DuckDuckGo"; | ||||||
|                 searchEngineUrl = "https://duckduckgo.com/?q={keyword}"; |                 searchEngineUrl = "https://duckduckgo.com/?q={keyword}"; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -122,6 +122,9 @@ async function handleMessage(event) { | |||||||
|     else if (message.type === 'api-log-messages') { |     else if (message.type === 'api-log-messages') { | ||||||
|         appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages}); |         appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages}); | ||||||
|     } |     } | ||||||
|  |     else if (message.type === 'toast') { | ||||||
|  |         toastService.showMessage(message.message); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| let entityChangeIdReachedListeners = []; | let entityChangeIdReachedListeners = []; | ||||||
|   | |||||||
| @@ -147,7 +147,7 @@ export default class AttachmentDetailWidget extends BasicWidget { | |||||||
|             this.$wrapper.addClass("scheduled-for-deletion"); |             this.$wrapper.addClass("scheduled-for-deletion"); | ||||||
|  |  | ||||||
|             const scheduledSinceTimestamp = utils.parseDate(utcDateScheduledForErasureSince)?.getTime(); |             const scheduledSinceTimestamp = utils.parseDate(utcDateScheduledForErasureSince)?.getTime(); | ||||||
|             const intervalMs = options.getInt('eraseUnusedImageAttachmentsAfterSeconds') * 1000; |             const intervalMs = options.getInt('eraseUnusedAttachmentsAfterSeconds') * 1000; | ||||||
|             const deletionTimestamp = scheduledSinceTimestamp + intervalMs; |             const deletionTimestamp = scheduledSinceTimestamp + intervalMs; | ||||||
|             const willBeDeletedInMs = deletionTimestamp - Date.now(); |             const willBeDeletedInMs = deletionTimestamp - Date.now(); | ||||||
|  |  | ||||||
| @@ -159,7 +159,7 @@ export default class AttachmentDetailWidget extends BasicWidget { | |||||||
|                 $deletionWarning.text(`This attachment will be deleted soon`); |                 $deletionWarning.text(`This attachment will be deleted soon`); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             $deletionWarning.append(", because the image attachment is not used. To prevent deletion, add the image back into the note."); |             $deletionWarning.append(", because the attachment is not linked in the note's content. To prevent deletion, add the attachment link back into the content."); | ||||||
|         } else { |         } else { | ||||||
|             this.$wrapper.removeClass("scheduled-for-deletion"); |             this.$wrapper.removeClass("scheduled-for-deletion"); | ||||||
|             $deletionWarning.hide(); |             $deletionWarning.hide(); | ||||||
| @@ -198,8 +198,6 @@ export default class AttachmentDetailWidget extends BasicWidget { | |||||||
|             if (attachmentChange.isDeleted) { |             if (attachmentChange.isDeleted) { | ||||||
|                 this.toggleInt(false); |                 this.toggleInt(false); | ||||||
|             } else { |             } else { | ||||||
|                 this.attachment = await server.get(`attachments/${this.attachment.attachmentId}`); |  | ||||||
|  |  | ||||||
|                 this.refresh(); |                 this.refresh(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -6,10 +6,10 @@ const TPL = ` | |||||||
| <div class="options-section"> | <div class="options-section"> | ||||||
|     <h4>Attachment erasure timeout</h4> |     <h4>Attachment erasure timeout</h4> | ||||||
|  |  | ||||||
|     <p>Attachment images get automatically deleted (and erased) if they are not referenced by their note anymore after a defined time out.</p> |     <p>Attachments get automatically deleted (and erased) if they are not referenced by their note anymore after a defined time out.</p> | ||||||
|  |  | ||||||
|     <div class="form-group"> |     <div class="form-group"> | ||||||
|         <label>Erase image attachments after X seconds of not being used in its note</label> |         <label>Erase attachments after X seconds of not being used in its note</label> | ||||||
|         <input class="erase-unused-attachments-after-time-in-seconds form-control" type="number" min="0"> |         <input class="erase-unused-attachments-after-time-in-seconds form-control" type="number" min="0"> | ||||||
|     </div> |     </div> | ||||||
|      |      | ||||||
| @@ -22,17 +22,17 @@ export default class AttachmentErasureTimeoutOptions extends OptionsWidget { | |||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$eraseUnusedAttachmentsAfterTimeInSeconds = this.$widget.find(".erase-unused-attachments-after-time-in-seconds"); |         this.$eraseUnusedAttachmentsAfterTimeInSeconds = this.$widget.find(".erase-unused-attachments-after-time-in-seconds"); | ||||||
|         this.$eraseUnusedAttachmentsAfterTimeInSeconds.on('change', () => this.updateOption('eraseUnusedImageAttachmentsAfterSeconds', this.$eraseUnusedAttachmentsAfterTimeInSeconds.val())); |         this.$eraseUnusedAttachmentsAfterTimeInSeconds.on('change', () => this.updateOption('eraseUnusedAttachmentsAfterSeconds', this.$eraseUnusedAttachmentsAfterTimeInSeconds.val())); | ||||||
|  |  | ||||||
|         this.$eraseUnusedAttachmentsNowButton = this.$widget.find(".erase-unused-attachments-now-button"); |         this.$eraseUnusedAttachmentsNowButton = this.$widget.find(".erase-unused-attachments-now-button"); | ||||||
|         this.$eraseUnusedAttachmentsNowButton.on('click', () => { |         this.$eraseUnusedAttachmentsNowButton.on('click', () => { | ||||||
|             server.post('notes/erase-unused-attachments-now').then(() => { |             server.post('notes/erase-unused-attachments-now').then(() => { | ||||||
|                 toastService.showMessage("Unused image attachments have been erased."); |                 toastService.showMessage("Unused attachments have been erased."); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async optionsLoaded(options) { |     async optionsLoaded(options) { | ||||||
|         this.$eraseUnusedAttachmentsAfterTimeInSeconds.val(options.eraseUnusedImageAttachmentsAfterSeconds); |         this.$eraseUnusedAttachmentsAfterTimeInSeconds.val(options.eraseUnusedAttachmentsAfterSeconds); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ const TPL = ` | |||||||
|             <select class="predefined-search-engine-select form-control"> |             <select class="predefined-search-engine-select form-control"> | ||||||
|                 <option value="Bing">Bing</option> |                 <option value="Bing">Bing</option> | ||||||
|                 <option value="Baidu">Baidu</option> |                 <option value="Baidu">Baidu</option> | ||||||
|                 <option value="Duckduckgo">Duckduckgo</option> |                 <option value="DuckDuckGo">Duckduckgo</option> | ||||||
|                 <option value="Google">Google</option> |                 <option value="Google">Google</option> | ||||||
|             </select> |             </select> | ||||||
|         </div> |         </div> | ||||||
| @@ -39,7 +39,7 @@ const TPL = ` | |||||||
| const SEARCH_ENGINES = { | const SEARCH_ENGINES = { | ||||||
|     "Bing": "https://www.bing.com/search?q={keyword}", |     "Bing": "https://www.bing.com/search?q={keyword}", | ||||||
|     "Baidu": "https://www.baidu.com/s?wd={keyword}", |     "Baidu": "https://www.baidu.com/s?wd={keyword}", | ||||||
|     "Duckduckgo": "https://duckduckgo.com/?q={keyword}", |     "DuckDuckGo": "https://duckduckgo.com/?q={keyword}", | ||||||
|     "Google": "https://www.google.com/search?q={keyword}", |     "Google": "https://www.google.com/search?q={keyword}", | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ const ALLOWED_OPTIONS = new Set([ | |||||||
|     'minTocHeadings', |     'minTocHeadings', | ||||||
|     'checkForUpdates', |     'checkForUpdates', | ||||||
|     'disableTray', |     'disableTray', | ||||||
|     'eraseUnusedImageAttachmentsAfterSeconds', |     'eraseUnusedAttachmentsAfterSeconds', | ||||||
|     'disableTray', |     'disableTray', | ||||||
|     'customSearchEngineName', |     'customSearchEngineName', | ||||||
|     'customSearchEngineUrl', |     'customSearchEngineUrl', | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ const htmlSanitizer = require("./html_sanitizer"); | |||||||
| const ValidationError = require("../errors/validation_error"); | const ValidationError = require("../errors/validation_error"); | ||||||
| const noteTypesService = require("./note_types"); | const noteTypesService = require("./note_types"); | ||||||
| const fs = require("fs"); | const fs = require("fs"); | ||||||
|  | const ws = require("./ws.js"); | ||||||
|  |  | ||||||
| /** @param {BNote} parentNote */ | /** @param {BNote} parentNote */ | ||||||
| function getNewNotePosition(parentNote) { | function getNewNotePosition(parentNote) { | ||||||
| @@ -333,29 +334,34 @@ function protectNote(note, protect) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function checkImageAttachments(note, content) { | function checkImageAttachments(note, content) { | ||||||
|     const re = /src="[^"]*api\/attachments\/([a-zA-Z0-9_]+)\/image/g; |  | ||||||
|     const foundAttachmentIds = new Set(); |     const foundAttachmentIds = new Set(); | ||||||
|     let match; |     let match; | ||||||
|  |  | ||||||
|     while (match = re.exec(content)) { |     const imgRegExp = /src="[^"]*api\/attachments\/([a-zA-Z0-9_]+)\/image/g; | ||||||
|  |     while (match = imgRegExp.exec(content)) { | ||||||
|         foundAttachmentIds.add(match[1]); |         foundAttachmentIds.add(match[1]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const imageAttachments = note.getAttachmentByRole('image'); |     const linkRegExp = /href="[^"]+attachmentId=([a-zA-Z0-9_]+)/g; | ||||||
|  |     while (match = linkRegExp.exec(content)) { | ||||||
|  |         foundAttachmentIds.add(match[1]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     for (const attachment of imageAttachments) { |     const attachments = note.getAttachments(); | ||||||
|         const imageInContent = foundAttachmentIds.has(attachment.attachmentId); |  | ||||||
|  |  | ||||||
|         if (attachment.utcDateScheduledForErasureSince && imageInContent) { |     for (const attachment of attachments) { | ||||||
|  |         const attachmentInContent = foundAttachmentIds.has(attachment.attachmentId); | ||||||
|  |  | ||||||
|  |         if (attachment.utcDateScheduledForErasureSince && attachmentInContent) { | ||||||
|             attachment.utcDateScheduledForErasureSince = null; |             attachment.utcDateScheduledForErasureSince = null; | ||||||
|             attachment.save(); |             attachment.save(); | ||||||
|         } else if (!attachment.utcDateScheduledForErasureSince && !imageInContent) { |         } else if (!attachment.utcDateScheduledForErasureSince && !attachmentInContent) { | ||||||
|             attachment.utcDateScheduledForErasureSince = dateUtils.utcNowDateTime(); |             attachment.utcDateScheduledForErasureSince = dateUtils.utcNowDateTime(); | ||||||
|             attachment.save(); |             attachment.save(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const existingAttachmentIds = new Set(imageAttachments.map(att => att.attachmentId)); |     const existingAttachmentIds = new Set(attachments.map(att => att.attachmentId)); | ||||||
|     const unknownAttachmentIds = Array.from(foundAttachmentIds).filter(foundAttId => !existingAttachmentIds.has(foundAttId)); |     const unknownAttachmentIds = Array.from(foundAttachmentIds).filter(foundAttId => !existingAttachmentIds.has(foundAttId)); | ||||||
|     const unknownAttachments = becca.getAttachments(unknownAttachmentIds); |     const unknownAttachments = becca.getAttachments(unknownAttachmentIds); | ||||||
|  |  | ||||||
| @@ -366,6 +372,9 @@ function checkImageAttachments(note, content) { | |||||||
|         newAttachment.setContent(unknownAttachment.getContent(), { forceSave: true }); |         newAttachment.setContent(unknownAttachment.getContent(), { forceSave: true }); | ||||||
|  |  | ||||||
|         content = content.replace(`api/attachments/${unknownAttachment.attachmentId}/image`, `api/attachments/${newAttachment.attachmentId}/image`); |         content = content.replace(`api/attachments/${unknownAttachment.attachmentId}/image`, `api/attachments/${newAttachment.attachmentId}/image`); | ||||||
|  |         content = content.replace(`attachmentId=${unknownAttachment.attachmentId}`, `attachmentId=${newAttachment.attachmentId}`); | ||||||
|  |  | ||||||
|  |         ws.sendMessageToAllClients({ type: 'toast', message: `Attachment '${newAttachment.title}' has been copied to note '${note.title}'.`}); | ||||||
|  |  | ||||||
|         log.info(`Copied attachment '${unknownAttachment.attachmentId}' to new '${newAttachment.attachmentId}'`); |         log.info(`Copied attachment '${unknownAttachment.attachmentId}' to new '${newAttachment.attachmentId}'`); | ||||||
|     } |     } | ||||||
| @@ -1077,12 +1086,12 @@ function getNoteIdMapping(origNote) { | |||||||
|     return noteIdMapping; |     return noteIdMapping; | ||||||
| } | } | ||||||
|  |  | ||||||
| function eraseScheduledAttachments(eraseUnusedImageAttachmentsAfterSeconds = null) { | function eraseScheduledAttachments(eraseUnusedAttachmentsAfterSeconds = null) { | ||||||
|     if (eraseUnusedImageAttachmentsAfterSeconds === null) { |     if (eraseUnusedAttachmentsAfterSeconds === null) { | ||||||
|         eraseUnusedImageAttachmentsAfterSeconds = optionService.getOptionInt('eraseUnusedImageAttachmentsAfterSeconds'); |         eraseUnusedAttachmentsAfterSeconds = optionService.getOptionInt('eraseUnusedAttachmentsAfterSeconds'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const cutOffDate = dateUtils.utcDateTimeStr(new Date(Date.now() - (eraseUnusedImageAttachmentsAfterSeconds * 1000))); |     const cutOffDate = dateUtils.utcDateTimeStr(new Date(Date.now() - (eraseUnusedAttachmentsAfterSeconds * 1000))); | ||||||
|     const attachmentIdsToErase = sql.getColumn('SELECT attachmentId FROM attachments WHERE utcDateScheduledForErasureSince < ?', [cutOffDate]); |     const attachmentIdsToErase = sql.getColumn('SELECT attachmentId FROM attachments WHERE utcDateScheduledForErasureSince < ?', [cutOffDate]); | ||||||
|  |  | ||||||
|     eraseAttachments(attachmentIdsToErase); |     eraseAttachments(attachmentIdsToErase); | ||||||
|   | |||||||
| @@ -88,9 +88,9 @@ const defaultOptions = [ | |||||||
|     { name: 'minTocHeadings', value: '5', isSynced: true }, |     { name: 'minTocHeadings', value: '5', isSynced: true }, | ||||||
|     { name: 'checkForUpdates', value: 'true', isSynced: true }, |     { name: 'checkForUpdates', value: 'true', isSynced: true }, | ||||||
|     { name: 'disableTray', value: 'false', isSynced: false }, |     { name: 'disableTray', value: 'false', isSynced: false }, | ||||||
|     { name: 'eraseUnusedImageAttachmentsAfterSeconds', value: '86400', isSynced: false }, |     { name: 'eraseUnusedAttachmentsAfterSeconds', value: '2592000', isSynced: true }, | ||||||
|     { name: 'customSearchEngineName', value: 'Duckduckgo', isSynced: false }, |     { name: 'customSearchEngineName', value: 'DuckDuckGo', isSynced: true }, | ||||||
|     { name: 'customSearchEngineUrl', value: 'https://duckduckgo.com/?q={keyword}', isSynced: false }, |     { name: 'customSearchEngineUrl', value: 'https://duckduckgo.com/?q={keyword}', isSynced: true }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| function initStartupOptions() { | function initStartupOptions() { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user