mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	detect existing attachment in target note
This commit is contained in:
		| @@ -62,7 +62,7 @@ class NoteContext extends Component { | ||||
|  | ||||
|         this.notePath = resolvedNotePath; | ||||
|         this.viewScope = opts.viewScope; | ||||
|         ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath)); | ||||
|         ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(resolvedNotePath)); | ||||
|  | ||||
|         this.saveToRecentNotes(resolvedNotePath); | ||||
|  | ||||
|   | ||||
| @@ -41,7 +41,7 @@ export default class RootCommandExecutor extends Component { | ||||
|     } | ||||
|  | ||||
|     async searchInSubtreeCommand({notePath}) { | ||||
|         const noteId = treeService.getNoteIdFromNotePath(notePath); | ||||
|         const noteId = treeService.getNoteIdFromUrl(notePath); | ||||
|  | ||||
|         this.searchNotesCommand({ancestorNoteId: noteId}); | ||||
|     } | ||||
|   | ||||
| @@ -57,7 +57,7 @@ export default class TabManager extends Component { | ||||
|             // preload all notes at once | ||||
|             await froca.getNotes([ | ||||
|                     ...noteContextsToOpen.flatMap(tab => | ||||
|                         [ treeService.getNoteIdFromNotePath(tab.notePath), tab.hoistedNoteId] | ||||
|                         [ treeService.getNoteIdFromUrl(tab.notePath), tab.hoistedNoteId] | ||||
|                     ), | ||||
|             ], true); | ||||
|  | ||||
| @@ -66,7 +66,7 @@ export default class TabManager extends Component { | ||||
|                     return !!openTab.active; | ||||
|                 } | ||||
|  | ||||
|                 const noteId = treeService.getNoteIdFromNotePath(openTab.notePath); | ||||
|                 const noteId = treeService.getNoteIdFromUrl(openTab.notePath); | ||||
|                 if (!(noteId in froca.notes)) { | ||||
|                     // note doesn't exist so don't try to open tab for it | ||||
|                     return false; | ||||
|   | ||||
| @@ -94,7 +94,7 @@ async function renderText(note, options, $renderedContent) { | ||||
|             renderMathInElement($renderedContent[0], {trust: true}); | ||||
|         } | ||||
|  | ||||
|         const getNoteIdFromLink = el => treeService.getNoteIdFromNotePath($(el).attr('href')); | ||||
|         const getNoteIdFromLink = el => treeService.getNoteIdFromUrl($(el).attr('href')); | ||||
|         const referenceLinks = $renderedContent.find("a.reference-link"); | ||||
|         const noteIdsToPrefetch = referenceLinks.map(el => getNoteIdFromLink(el)); | ||||
|         await froca.getNotes(noteIdsToPrefetch); | ||||
|   | ||||
| @@ -360,6 +360,8 @@ class Froca { | ||||
|         opts.preview = !!opts.preview; | ||||
|         const key = `${entityType}-${entityId}-${opts.preview}`; | ||||
|  | ||||
|         console.log(key); | ||||
|  | ||||
|         if (!this.blobPromises[key]) { | ||||
|             this.blobPromises[key] = server.get(`${entityType}/${entityId}/blob?preview=${opts.preview}`) | ||||
|                 .then(row => new FBlob(row)) | ||||
|   | ||||
| @@ -21,7 +21,13 @@ async function processEntityChanges(entityChanges) { | ||||
|             } else if (ec.entityName === 'note_reordering') { | ||||
|                 processNoteReordering(loadResults, ec); | ||||
|             } else if (ec.entityName === 'blobs') { | ||||
|                 delete froca.blobPromises[ec.entityId]; | ||||
|                 for (const affectedNoteId of ec.noteIds) { | ||||
|                     for (const key of Object.keys(froca.blobPromises)) { | ||||
|                         if (key.includes(affectedNoteId)) { | ||||
|                             delete froca.blobPromises[key]; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 loadResults.addNoteContent(ec.noteIds, ec.componentId); | ||||
|             } else if (ec.entityName === 'note_revisions') { | ||||
|   | ||||
| @@ -49,7 +49,7 @@ async function checkNoteAccess(notePath, noteContext) { | ||||
|     const hoistedNoteId = noteContext.hoistedNoteId; | ||||
|  | ||||
|     if (!resolvedNotePath.includes(hoistedNoteId) && !resolvedNotePath.includes('_hidden')) { | ||||
|         const requestedNote = await froca.getNote(treeService.getNoteIdFromNotePath(resolvedNotePath)); | ||||
|         const requestedNote = await froca.getNote(treeService.getNoteIdFromUrl(resolvedNotePath)); | ||||
|         const hoistedNote = await froca.getNote(hoistedNoteId); | ||||
|  | ||||
|         if (!hoistedNote.hasAncestor('_hidden') | ||||
|   | ||||
| @@ -43,7 +43,7 @@ async function createLink(notePath, options = {}) { | ||||
|     const showNoteIcon = options.showNoteIcon === undefined ? false : options.showNoteIcon; | ||||
|     const referenceLink = options.referenceLink === undefined ? false : options.referenceLink; | ||||
|  | ||||
|     const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromNotePath(notePath); | ||||
|     const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath); | ||||
|     const viewScope = options.viewScope || {}; | ||||
|     const viewMode = viewScope.viewMode || 'default'; | ||||
|     let linkTitle = options.title; | ||||
| @@ -174,7 +174,7 @@ function parseNavigationStateFromUrl(url) { | ||||
|  | ||||
|     return { | ||||
|         notePath, | ||||
|         noteId: treeService.getNoteIdFromNotePath(notePath), | ||||
|         noteId: treeService.getNoteIdFromUrl(notePath), | ||||
|         ntxId, | ||||
|         hoistedNoteId, | ||||
|         viewScope | ||||
|   | ||||
| @@ -27,7 +27,7 @@ async function createNote(parentNotePath, options = {}) { | ||||
|         [options.title, options.content] = parseSelectedHtml(options.textEditor.getSelectedHtml()); | ||||
|     } | ||||
|  | ||||
|     const parentNoteId = treeService.getNoteIdFromNotePath(parentNotePath); | ||||
|     const parentNoteId = treeService.getNoteIdFromUrl(parentNotePath); | ||||
|  | ||||
|     if (options.type === 'mermaid' && !options.content) { | ||||
|         options.content = `graph TD; | ||||
| @@ -110,7 +110,7 @@ function parseSelectedHtml(selectedHtml) { | ||||
| } | ||||
|  | ||||
| async function duplicateSubtree(noteId, parentNotePath) { | ||||
|     const parentNoteId = treeService.getNoteIdFromNotePath(parentNotePath); | ||||
|     const parentNoteId = treeService.getNoteIdFromUrl(parentNotePath); | ||||
|     const {note} = await server.post(`notes/${noteId}/duplicate/${parentNoteId}`); | ||||
|  | ||||
|     await ws.waitForMaxKnownEntityChangeId(); | ||||
|   | ||||
| @@ -103,7 +103,7 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr | ||||
|         return effectivePathSegments; | ||||
|     } | ||||
|     else { | ||||
|         const note = await froca.getNote(getNoteIdFromNotePath(notePath)); | ||||
|         const note = await froca.getNote(getNoteIdFromUrl(notePath)); | ||||
|  | ||||
|         const bestNotePath = note.getBestNotePath(hoistedNoteId); | ||||
|  | ||||
| @@ -132,26 +132,30 @@ function getParentProtectedStatus(node) { | ||||
|     return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected; | ||||
| } | ||||
|  | ||||
| function getNoteIdFromNotePath(notePath) { | ||||
|     if (!notePath) { | ||||
| function getNoteIdFromUrl(url) { | ||||
|     if (!url) { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     const path = notePath.split("/"); | ||||
|     const [notePath] = url.split("?"); | ||||
|     const segments = notePath.split("/"); | ||||
|  | ||||
|     const lastSegment = path[path.length - 1]; | ||||
|  | ||||
|     // path could have also params suffix | ||||
|     return lastSegment.split("?")[0]; | ||||
|     return segments[segments.length - 1]; | ||||
| } | ||||
|  | ||||
| async function getBranchIdFromNotePath(notePath) { | ||||
|     const {noteId, parentNoteId} = getNoteIdAndParentIdFromNotePath(notePath); | ||||
| async function getBranchIdFromUrl(url) { | ||||
|     const {noteId, parentNoteId} = getNoteIdAndParentIdFromUrl(url); | ||||
|  | ||||
|     return await froca.getBranchId(parentNoteId, noteId); | ||||
| } | ||||
|  | ||||
| function getNoteIdAndParentIdFromNotePath(notePath) { | ||||
| function getNoteIdAndParentIdFromUrl(url) { | ||||
|     if (!url) { | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     const [notePath] = url.split("?"); | ||||
|  | ||||
|     if (notePath === 'root') { | ||||
|         return { | ||||
|             noteId: 'root', | ||||
| @@ -163,15 +167,12 @@ function getNoteIdAndParentIdFromNotePath(notePath) { | ||||
|     let noteId = ''; | ||||
|  | ||||
|     if (notePath) { | ||||
|         const path = notePath.split("/"); | ||||
|         const segments = notePath.split("/"); | ||||
|  | ||||
|         const lastSegment = path[path.length - 1]; | ||||
|         noteId = segments[segments.length - 1]; | ||||
|  | ||||
|         // path could have also params suffix | ||||
|         noteId = lastSegment.split("?")[0]; | ||||
|  | ||||
|         if (path.length > 1) { | ||||
|             parentNoteId = path[path.length - 2]; | ||||
|         if (segments.length > 1) { | ||||
|             parentNoteId = segments[segments.length - 2]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -288,9 +289,9 @@ export default { | ||||
|     resolveNotePathToSegments, | ||||
|     getParentProtectedStatus, | ||||
|     getNotePath, | ||||
|     getNoteIdFromNotePath, | ||||
|     getNoteIdAndParentIdFromNotePath, | ||||
|     getBranchIdFromNotePath, | ||||
|     getNoteIdFromUrl, | ||||
|     getNoteIdAndParentIdFromUrl, | ||||
|     getBranchIdFromUrl, | ||||
|     getNoteTitle, | ||||
|     getNotePathTitle, | ||||
|     getNoteTitleWithPathAsSuffix, | ||||
|   | ||||
| @@ -132,7 +132,7 @@ export default class AddLinkDialog extends BasicWidget { | ||||
|  | ||||
|             this.updateTitleSettingsVisibility(); | ||||
|  | ||||
|             const noteId = treeService.getNoteIdFromNotePath(suggestion.notePath); | ||||
|             const noteId = treeService.getNoteIdFromUrl(suggestion.notePath); | ||||
|  | ||||
|             if (noteId) { | ||||
|                 setDefaultLinkTitle(noteId); | ||||
| @@ -154,7 +154,7 @@ export default class AddLinkDialog extends BasicWidget { | ||||
|                 this.$linkTitle.val(suggestion.externalLink) | ||||
|             } | ||||
|             else { | ||||
|                 const noteId = treeService.getNoteIdFromNotePath(suggestion.notePath); | ||||
|                 const noteId = treeService.getNoteIdFromUrl(suggestion.notePath); | ||||
|  | ||||
|                 if (noteId) { | ||||
|                     setDefaultLinkTitle(noteId); | ||||
|   | ||||
| @@ -59,7 +59,7 @@ export default class BranchPrefixDialog extends BasicWidget { | ||||
|     } | ||||
|  | ||||
|     async refresh(notePath) { | ||||
|         const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(notePath); | ||||
|         const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath); | ||||
|  | ||||
|         if (!noteId || !parentNoteId) { | ||||
|             return; | ||||
|   | ||||
| @@ -110,7 +110,7 @@ export default class CloneToDialog extends BasicWidget { | ||||
|     } | ||||
|  | ||||
|     async cloneNotesTo(notePath) { | ||||
|         const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(notePath); | ||||
|         const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath); | ||||
|         const targetBranchId = await froca.getBranchId(parentNoteId, noteId); | ||||
|  | ||||
|         for (const cloneNoteId of this.clonedNoteIds) { | ||||
|   | ||||
| @@ -210,7 +210,7 @@ export default class ExportDialog extends BasicWidget { | ||||
|  | ||||
|         utils.openDialog(this.$widget); | ||||
|  | ||||
|         const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(notePath); | ||||
|         const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath); | ||||
|  | ||||
|         this.branchId = await froca.getBranchId(parentNoteId, noteId); | ||||
|         this.$noteTitle.text(await treeService.getNoteTitle(noteId)); | ||||
|   | ||||
| @@ -92,7 +92,7 @@ export default class IncludeNoteDialog extends BasicWidget { | ||||
|     } | ||||
|  | ||||
|     async includeNote(notePath) { | ||||
|         const noteId = treeService.getNoteIdFromNotePath(notePath); | ||||
|         const noteId = treeService.getNoteIdFromUrl(notePath); | ||||
|         const note = await froca.getNote(noteId); | ||||
|  | ||||
|         const boxSize = $("input[name='include-note-box-size']:checked").val(); | ||||
|   | ||||
| @@ -59,7 +59,7 @@ export default class MoveToDialog extends BasicWidget { | ||||
|             if (notePath) { | ||||
|                 this.$widget.modal('hide'); | ||||
|  | ||||
|                 const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(notePath); | ||||
|                 const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath); | ||||
|                 froca.getBranchId(parentNoteId, noteId).then(branchId => this.moveNotesTo(branchId)); | ||||
|             } | ||||
|             else { | ||||
|   | ||||
| @@ -29,7 +29,7 @@ class MobileDetailMenuWidget extends BasicWidget { | ||||
|                     } | ||||
|                     else if (command === "delete") { | ||||
|                         const notePath = appContext.tabManager.getActiveContextNotePath(); | ||||
|                         const branchId = await treeService.getBranchIdFromNotePath(notePath); | ||||
|                         const branchId = await treeService.getBranchIdFromUrl(notePath); | ||||
|  | ||||
|                         if (!branchId) { | ||||
|                             throw new Error(`Cannot get branchId for notePath '${notePath}'`); | ||||
|   | ||||
| @@ -297,7 +297,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { | ||||
|         else if ($attr.attr("data-attribute-type") === "relation") { | ||||
|             const selectedPath = $attr.getSelectedNotePath(); | ||||
|  | ||||
|             value = selectedPath ? treeService.getNoteIdFromNotePath(selectedPath) : ""; | ||||
|             value = selectedPath ? treeService.getNoteIdFromUrl(selectedPath) : ""; | ||||
|         } | ||||
|         else { | ||||
|             value = $attr.val(); | ||||
|   | ||||
| @@ -45,7 +45,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { | ||||
|         this.$wrapper.empty(); | ||||
|         this.children = []; | ||||
|  | ||||
|         this.$linksWrapper.append( | ||||
|         this.$linksWrapper.empty().append( | ||||
|             "Owning note: ", | ||||
|             await linkService.createLink(this.noteId), | ||||
|             ", you can also open the ", | ||||
|   | ||||
| @@ -37,7 +37,7 @@ export default class AttachmentListTypeWidget extends TypeWidget { | ||||
|     } | ||||
|  | ||||
|     async doRefresh(note) { | ||||
|         this.$linksWrapper.append( | ||||
|         this.$linksWrapper.empty().append( | ||||
|             $('<div>').append( | ||||
|                 "Owning note: ", | ||||
|                 await linkService.createLink(this.noteId), | ||||
|   | ||||
| @@ -366,17 +366,32 @@ function checkImageAttachments(note, content) { | ||||
|     const unknownAttachments = becca.getAttachments(unknownAttachmentIds); | ||||
|  | ||||
|     for (const unknownAttachment of unknownAttachments) { | ||||
|         // the attachment belongs to a different note (was copy pasted), we need to make a copy for this note. | ||||
|         const newAttachment = unknownAttachment.copy(); | ||||
|         newAttachment.parentId = note.noteId; | ||||
|         newAttachment.setContent(unknownAttachment.getContent(), { forceSave: true }); | ||||
|         // the attachment belongs to a different note (was copy pasted). Attachments can be linked only from the note | ||||
|         // which owns it, so either find an existing attachment having the same content or make a copy. | ||||
|         let localAttachment = note.getAttachments().find(att => att.role === unknownAttachment.role && att.blobId === unknownAttachment.blobId); | ||||
|  | ||||
|         content = content.replace(`api/attachments/${unknownAttachment.attachmentId}/image`, `api/attachments/${newAttachment.attachmentId}/image`); | ||||
|         content = content.replace(`attachmentId=${unknownAttachment.attachmentId}`, `attachmentId=${newAttachment.attachmentId}`); | ||||
|         if (localAttachment) { | ||||
|             if (localAttachment.utcDateScheduledForErasureSince) { | ||||
|                 // the attachment is for sure linked now, so reset the scheduled deletion | ||||
|                 localAttachment.utcDateScheduledForErasureSince = null; | ||||
|                 localAttachment.save(); | ||||
|             } | ||||
|  | ||||
|         ws.sendMessageToAllClients({ type: 'toast', message: `Attachment '${newAttachment.title}' has been copied to note '${note.title}'.`}); | ||||
|             log.info(`Found equivalent attachment '${localAttachment.attachmentId}' of note '${note.noteId}' for the linked foreign attachment '${unknownAttachment.attachmentId}' of note '${unknownAttachment.parentId}'`); | ||||
|         } else { | ||||
|             localAttachment = unknownAttachment.copy(); | ||||
|             localAttachment.parentId = note.noteId; | ||||
|             localAttachment.setContent(unknownAttachment.getContent(), {forceSave: true}); | ||||
|  | ||||
|         log.info(`Copied attachment '${unknownAttachment.attachmentId}' to new '${newAttachment.attachmentId}'`); | ||||
|             ws.sendMessageToAllClients({ type: 'toast', message: `Attachment '${localAttachment.title}' has been copied to note '${note.title}'.`}); | ||||
|             log.info(`Copied attachment '${unknownAttachment.attachmentId}' of note '${unknownAttachment.parentId}' to new '${localAttachment.attachmentId}' of note '${note.noteId}'`); | ||||
|         } | ||||
|  | ||||
|         // replace image links | ||||
|         content = content.replace(`api/attachments/${unknownAttachment.attachmentId}/image`, `api/attachments/${localAttachment.attachmentId}/image`); | ||||
|         // replace reference links | ||||
|         content = content.replace(new RegExp(`href="[^"]+attachmentId=${unknownAttachment.attachmentId}[^"]*"`, "g"), | ||||
|             `href="#root/${localAttachment.parentId}?viewMode=attachments&attachmentId=${localAttachment.attachmentId}"`); | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|   | ||||
| @@ -150,7 +150,11 @@ function fillInAdditionalProperties(entityChange) { | ||||
|     } else if (entityChange.entityName === 'blobs') { | ||||
|         entityChange.noteIds = sql.getColumn("SELECT noteId FROM notes WHERE blobId = ? AND isDeleted = 0", [entityChange.entityId]); | ||||
|     } else if (entityChange.entityName === 'attachments') { | ||||
|         entityChange.entity = sql.getRow(`SELECT * FROM attachments WHERE attachmentId = ?`, [entityChange.entityId]); | ||||
|         entityChange.entity = sql.getRow(` | ||||
|             SELECT attachments.*, LENGTH(blobs.content)  | ||||
|             FROM attachments  | ||||
|             JOIN blobs ON blobs.blobId = attachments.blobId | ||||
|             WHERE attachmentId = ?`, [entityChange.entityId]); | ||||
|     } | ||||
|  | ||||
|     if (entityChange.entity instanceof AbstractBeccaEntity) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user