This commit is contained in:
zadam
2023-07-15 12:07:45 +02:00
parent 9d24499fef
commit 48f03f7a1b
18 changed files with 377 additions and 197 deletions

View File

@@ -46,7 +46,7 @@ const LABEL = 'label';
const RELATION = 'relation';
/**
* Trilium's main entity which can represent text note, image, code note, file attachment etc.
* Trilium's main entity, which can represent text note, image, code note, file attachment etc.
*
* @extends AbstractBeccaEntity
*/
@@ -151,7 +151,7 @@ class BNote extends AbstractBeccaEntity {
* @private */
this.__ancestorCache = null;
// following attributes are filled during searching from database
// following attributes are filled during searching in the database
/**
* size of the content in bytes
@@ -594,7 +594,8 @@ class BNote extends AbstractBeccaEntity {
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {BAttribute} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
* @returns {BAttribute} attribute of the given type and name. If there are more such attributes, first is returned.
* Returns null if there's no such attribute belonging to this note.
*/
getAttribute(type, name) {
const attributes = this.getAttributes();
@@ -694,7 +695,7 @@ class BNote extends AbstractBeccaEntity {
return this.ownedAttributes.filter(attr => attr.name === name);
}
else {
return this.ownedAttributes.slice();
return this.ownedAttributes;
}
}
@@ -716,7 +717,7 @@ class BNote extends AbstractBeccaEntity {
areAllNotePathsArchived() {
// there's a slight difference between note being itself archived and all its note paths being archived
// - note is archived when it itself has an archived label or inherits it
// - note does not have or inherit archived label, but each note paths contains a note with (non-inheritable)
// - note does not have or inherit archived label, but each note path contains a note with (non-inheritable)
// archived label
const bestNotePathRecord = this.getSortedNotePathRecords()[0];
@@ -1121,16 +1122,16 @@ class BNote extends AbstractBeccaEntity {
/** @returns {BAttachment[]} */
getAttachments(opts = {}) {
opts.includeContentLength = !!opts.includeContentLength;
// from testing it looks like calculating length does not make a difference in performance even on large-ish DB
// from testing, it looks like calculating length does not make a difference in performance even on large-ish DB
// given that we're always fetching attachments only for a specific note, we might just do it always
const query = opts.includeContentLength
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
FROM attachments
JOIN blobs USING (blobId)
WHERE parentId = ? AND isDeleted = 0
WHERE ownerId = ? AND isDeleted = 0
ORDER BY position`
: `SELECT * FROM attachments WHERE parentId = ? AND isDeleted = 0 ORDER BY position`;
: `SELECT * FROM attachments WHERE ownerId = ? AND isDeleted = 0 ORDER BY position`;
return sql.getRows(query, [this.noteId])
.map(row => new BAttachment(row));
@@ -1144,8 +1145,8 @@ class BNote extends AbstractBeccaEntity {
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
FROM attachments
JOIN blobs USING (blobId)
WHERE parentId = ? AND attachmentId = ? AND isDeleted = 0`
: `SELECT * FROM attachments WHERE parentId = ? AND attachmentId = ? AND isDeleted = 0`;
WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`
: `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`;
return sql.getRows(query, [this.noteId, attachmentId])
.map(row => new BAttachment(row))[0];
@@ -1156,7 +1157,7 @@ class BNote extends AbstractBeccaEntity {
return sql.getRows(`
SELECT attachments.*
FROM attachments
WHERE parentId = ?
WHERE ownerId = ?
AND role = ?
AND isDeleted = 0
ORDER BY position`, [this.noteId, role])
@@ -1176,7 +1177,7 @@ class BNote extends AbstractBeccaEntity {
const parentNotes = this.getParentNotes();
const notePaths = parentNotes.length === 1
? parentNotes[0].getAllNotePaths() // optimization for most common case
? parentNotes[0].getAllNotePaths() // optimization for the most common case
: parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
for (const notePath of notePaths) {
@@ -1216,7 +1217,7 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Returns note path considered to be the "best"
* Returns a note path considered to be the "best"
*
* @param {string} [hoistedNoteId='root']
* @return {string[]} array of noteIds constituting the particular note path
@@ -1226,7 +1227,7 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Returns note path considered to be the "best"
* Returns a note path considered to be the "best"
*
* @param {string} [hoistedNoteId='root']
* @return {string} serialized note path (e.g. 'root/a1h315/js725h')
@@ -1366,7 +1367,7 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Based on enabled, attribute is either set or removed.
* Based on enabled, the attribute is either set or removed.
*
* @param {string} type - attribute type ('relation', 'label' etc.)
* @param {boolean} enabled - toggle On or Off
@@ -1425,7 +1426,7 @@ class BNote extends AbstractBeccaEntity {
removeLabel(name, value) { return this.removeAttribute(LABEL, name, value); }
/**
* Remove relation name-value pair, if it exists.
* Remove the relation name-value pair, if it exists.
*
* @param {string} name - relation name
* @param {string} [value] - relation value (noteId)
@@ -1454,14 +1455,16 @@ class BNote extends AbstractBeccaEntity {
return cloningService.cloneNoteToBranch(this.noteId, branch.branchId);
}
isEligibleForConversionToAttachment() {
isEligibleForConversionToAttachment(opts = {autoConversion: false}) {
if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) {
return false;
}
const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink');
if (targetRelations.length > 1) {
if (opts.autoConversion && targetRelations.length === 0) {
return false;
} else if (targetRelations.length > 1) {
return false;
}
@@ -1483,21 +1486,21 @@ class BNote extends AbstractBeccaEntity {
* - it has a relation from its parent note
* - it has no children
* - it has no clones
* - parent is of type text
* - the parent is of type text
* - both notes are either unprotected or user is in protected session
*
* Currently, works only for image notes.
*
* In future this functionality might get more generic and some of the requirements relaxed.
* In the future, this functionality might get more generic and some of the requirements relaxed.
*
* @params {Object} [opts]
* @params {bolean} [opts.force=false} it is envisioned that user can force the conversion even if some conditions
* are not satisfied (e.g. relation to parent doesn't exist).
* @params {bolean} [opts.autoConversion=false} if true, the action is not triggered by user, but e.g. by migration,
* and only perfect candidates will be migrated
*
* @returns {BAttachment|null} - null if note is not eligible for conversion
*/
convertToParentAttachment(opts = {force: false}) {
if (!this.isEligibleForConversionToAttachment()) {
convertToParentAttachment(opts = {autoConversion: false}) {
if (!this.isEligibleForConversionToAttachment(opts)) {
return null;
}
@@ -1520,6 +1523,9 @@ class BNote extends AbstractBeccaEntity {
parentNote.setContent(fixedContent);
const noteService = require("../../services/notes");
noteService.asyncPostProcessContent(parentNote, fixedContent); // to mark an unused attachment for deletion
this.deleteNote();
return attachment;
@@ -1577,7 +1583,7 @@ class BNote extends AbstractBeccaEntity {
}
get isDeleted() {
// isBeingDeleted is relevant only in the transition period when the deletion process have begun, but not yet
// isBeingDeleted is relevant only in the transition period when the deletion process has begun, but not yet
// finished (note is still in becca)
return !(this.noteId in this.becca.notes) || this.isBeingDeleted;
}
@@ -1617,11 +1623,15 @@ class BNote extends AbstractBeccaEntity {
}
const revisionAttachment = noteAttachment.copy();
revisionAttachment.parentId = revision.revisionId;
revisionAttachment.ownerId = revision.revisionId;
revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
// content is rewritten to point to the revision attachments
noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`, `attachments/${revisionAttachment.attachmentId}`);
noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
`attachments/${revisionAttachment.attachmentId}`);
noteContent = noteContent.replaceAll(new RegExp(`href="[^"]*attachmentId=${noteAttachment.attachmentId}[^"]*"`, 'gi'),
`href="api/attachments/${revisionAttachment.attachmentId}/download"`);
}
revision.setContent(noteContent, {forceSave: true});
@@ -1641,7 +1651,7 @@ class BNote extends AbstractBeccaEntity {
attachment = this.becca.getAttachmentOrThrow(attachmentId);
} else {
attachment = new BAttachment({
parentId: this.noteId,
ownerId: this.noteId,
title,
role,
mime,