diff --git a/src/public/app/menus/electron_context_menu.js b/src/public/app/menus/electron_context_menu.js index 6c455020b..a74691508 100644 --- a/src/public/app/menus/electron_context_menu.js +++ b/src/public/app/menus/electron_context_menu.js @@ -99,7 +99,7 @@ function setupContextMenu() { searchEngineName = customSearchEngineName; searchEngineUrl = customSearchEngineUrl; } else { - searchEngineName = "Duckduckgo"; + searchEngineName = "DuckDuckGo"; searchEngineUrl = "https://duckduckgo.com/?q={keyword}"; } diff --git a/src/public/app/services/ws.js b/src/public/app/services/ws.js index cc77164a1..0da410692 100644 --- a/src/public/app/services/ws.js +++ b/src/public/app/services/ws.js @@ -122,6 +122,9 @@ async function handleMessage(event) { else if (message.type === 'api-log-messages') { appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages}); } + else if (message.type === 'toast') { + toastService.showMessage(message.message); + } } let entityChangeIdReachedListeners = []; diff --git a/src/public/app/widgets/attachment_detail.js b/src/public/app/widgets/attachment_detail.js index 12156ac18..b85bea9e3 100644 --- a/src/public/app/widgets/attachment_detail.js +++ b/src/public/app/widgets/attachment_detail.js @@ -147,7 +147,7 @@ export default class AttachmentDetailWidget extends BasicWidget { this.$wrapper.addClass("scheduled-for-deletion"); const scheduledSinceTimestamp = utils.parseDate(utcDateScheduledForErasureSince)?.getTime(); - const intervalMs = options.getInt('eraseUnusedImageAttachmentsAfterSeconds') * 1000; + const intervalMs = options.getInt('eraseUnusedAttachmentsAfterSeconds') * 1000; const deletionTimestamp = scheduledSinceTimestamp + intervalMs; const willBeDeletedInMs = deletionTimestamp - Date.now(); @@ -159,7 +159,7 @@ export default class AttachmentDetailWidget extends BasicWidget { $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 { this.$wrapper.removeClass("scheduled-for-deletion"); $deletionWarning.hide(); @@ -198,8 +198,6 @@ export default class AttachmentDetailWidget extends BasicWidget { if (attachmentChange.isDeleted) { this.toggleInt(false); } else { - this.attachment = await server.get(`attachments/${this.attachment.attachmentId}`); - this.refresh(); } } diff --git a/src/public/app/widgets/type_widgets/options/other/attachment_erasure_timeout.js b/src/public/app/widgets/type_widgets/options/other/attachment_erasure_timeout.js index 8eb90ab9b..b78fde5a2 100644 --- a/src/public/app/widgets/type_widgets/options/other/attachment_erasure_timeout.js +++ b/src/public/app/widgets/type_widgets/options/other/attachment_erasure_timeout.js @@ -6,10 +6,10 @@ const TPL = `

Attachment erasure timeout

-

Attachment images get automatically deleted (and erased) if they are not referenced by their note anymore after a defined time out.

+

Attachments get automatically deleted (and erased) if they are not referenced by their note anymore after a defined time out.

- +
@@ -22,17 +22,17 @@ export default class AttachmentErasureTimeoutOptions extends OptionsWidget { doRender() { this.$widget = $(TPL); 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.on('click', () => { 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) { - this.$eraseUnusedAttachmentsAfterTimeInSeconds.val(options.eraseUnusedImageAttachmentsAfterSeconds); + this.$eraseUnusedAttachmentsAfterTimeInSeconds.val(options.eraseUnusedAttachmentsAfterSeconds); } } diff --git a/src/public/app/widgets/type_widgets/options/other/search_engine.js b/src/public/app/widgets/type_widgets/options/other/search_engine.js index 081aa0aa8..a27a5fedf 100644 --- a/src/public/app/widgets/type_widgets/options/other/search_engine.js +++ b/src/public/app/widgets/type_widgets/options/other/search_engine.js @@ -15,7 +15,7 @@ const TPL = `
@@ -39,7 +39,7 @@ const TPL = ` const SEARCH_ENGINES = { "Bing": "https://www.bing.com/search?q={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}", } diff --git a/src/routes/api/options.js b/src/routes/api/options.js index 201d4e266..74baeb0a0 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -62,7 +62,7 @@ const ALLOWED_OPTIONS = new Set([ 'minTocHeadings', 'checkForUpdates', 'disableTray', - 'eraseUnusedImageAttachmentsAfterSeconds', + 'eraseUnusedAttachmentsAfterSeconds', 'disableTray', 'customSearchEngineName', 'customSearchEngineUrl', diff --git a/src/services/notes.js b/src/services/notes.js index fea136870..8984286d3 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -21,6 +21,7 @@ const htmlSanitizer = require("./html_sanitizer"); const ValidationError = require("../errors/validation_error"); const noteTypesService = require("./note_types"); const fs = require("fs"); +const ws = require("./ws.js"); /** @param {BNote} parentNote */ function getNewNotePosition(parentNote) { @@ -333,29 +334,34 @@ function protectNote(note, protect) { } function checkImageAttachments(note, content) { - const re = /src="[^"]*api\/attachments\/([a-zA-Z0-9_]+)\/image/g; const foundAttachmentIds = new Set(); 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]); } - 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 imageInContent = foundAttachmentIds.has(attachment.attachmentId); + const attachments = note.getAttachments(); - if (attachment.utcDateScheduledForErasureSince && imageInContent) { + for (const attachment of attachments) { + const attachmentInContent = foundAttachmentIds.has(attachment.attachmentId); + + if (attachment.utcDateScheduledForErasureSince && attachmentInContent) { attachment.utcDateScheduledForErasureSince = null; attachment.save(); - } else if (!attachment.utcDateScheduledForErasureSince && !imageInContent) { + } else if (!attachment.utcDateScheduledForErasureSince && !attachmentInContent) { attachment.utcDateScheduledForErasureSince = dateUtils.utcNowDateTime(); 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 unknownAttachments = becca.getAttachments(unknownAttachmentIds); @@ -366,6 +372,9 @@ function checkImageAttachments(note, content) { newAttachment.setContent(unknownAttachment.getContent(), { forceSave: true }); 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}'`); } @@ -1077,12 +1086,12 @@ function getNoteIdMapping(origNote) { return noteIdMapping; } -function eraseScheduledAttachments(eraseUnusedImageAttachmentsAfterSeconds = null) { - if (eraseUnusedImageAttachmentsAfterSeconds === null) { - eraseUnusedImageAttachmentsAfterSeconds = optionService.getOptionInt('eraseUnusedImageAttachmentsAfterSeconds'); +function eraseScheduledAttachments(eraseUnusedAttachmentsAfterSeconds = null) { + if (eraseUnusedAttachmentsAfterSeconds === null) { + 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]); eraseAttachments(attachmentIdsToErase); diff --git a/src/services/options_init.js b/src/services/options_init.js index f97813d28..7aaa1044a 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -88,9 +88,9 @@ const defaultOptions = [ { name: 'minTocHeadings', value: '5', isSynced: true }, { name: 'checkForUpdates', value: 'true', isSynced: true }, { name: 'disableTray', value: 'false', isSynced: false }, - { name: 'eraseUnusedImageAttachmentsAfterSeconds', value: '86400', isSynced: false }, - { name: 'customSearchEngineName', value: 'Duckduckgo', isSynced: false }, - { name: 'customSearchEngineUrl', value: 'https://duckduckgo.com/?q={keyword}', isSynced: false }, + { name: 'eraseUnusedAttachmentsAfterSeconds', value: '2592000', isSynced: true }, + { name: 'customSearchEngineName', value: 'DuckDuckGo', isSynced: true }, + { name: 'customSearchEngineUrl', value: 'https://duckduckgo.com/?q={keyword}', isSynced: true }, ]; function initStartupOptions() {