detect existing attachment in target note

This commit is contained in:
zadam 2023-05-29 22:37:19 +02:00
parent df17840dbc
commit 235b779dec
22 changed files with 80 additions and 52 deletions

View File

@ -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);

View File

@ -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});
}

View File

@ -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;

View File

@ -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);

View File

@ -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))

View File

@ -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') {

View File

@ -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')

View File

@ -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

View File

@ -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();

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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));

View File

@ -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();

View File

@ -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 {

View File

@ -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}'`);

View File

@ -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();

View File

@ -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 ",

View File

@ -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),

View File

@ -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&amp;attachmentId=${localAttachment.attachmentId}"`);
}
return {

View File

@ -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) {