2020-05-17 09:48:24 +02:00
|
|
|
"use strict";
|
|
|
|
|
2023-01-03 13:52:37 +01:00
|
|
|
const BNote = require('./bnote');
|
|
|
|
const AbstractBeccaEntity = require("./abstract_becca_entity");
|
2022-01-10 17:09:20 +01:00
|
|
|
const dateUtils = require("../../services/date_utils");
|
2022-12-09 16:13:22 +01:00
|
|
|
const utils = require("../../services/utils");
|
2022-06-05 14:58:19 +02:00
|
|
|
const TaskContext = require("../../services/task_context");
|
|
|
|
const cls = require("../../services/cls");
|
|
|
|
const log = require("../../services/log");
|
2021-04-25 20:00:42 +02:00
|
|
|
|
2021-11-10 21:30:54 +01:00
|
|
|
/**
|
|
|
|
* Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple
|
|
|
|
* parents.
|
2022-04-16 00:17:32 +02:00
|
|
|
*
|
2022-12-23 21:13:00 +01:00
|
|
|
* Note that you should not rely on the branch's identity, since it can change easily with a note's move.
|
|
|
|
* Always check noteId instead.
|
|
|
|
*
|
2023-01-03 13:52:37 +01:00
|
|
|
* @extends AbstractBeccaEntity
|
2021-11-10 21:30:54 +01:00
|
|
|
*/
|
2023-01-03 13:52:37 +01:00
|
|
|
class BBranch extends AbstractBeccaEntity {
|
2021-04-25 20:00:42 +02:00
|
|
|
static get entityName() { return "branches"; }
|
|
|
|
static get primaryKeyName() { return "branchId"; }
|
|
|
|
// notePosition is not part of hash because it would produce a lot of updates in case of reordering
|
|
|
|
static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "prefix"]; }
|
2020-08-27 22:37:57 +02:00
|
|
|
|
2021-04-30 22:13:13 +02:00
|
|
|
constructor(row) {
|
2021-04-25 20:00:42 +02:00
|
|
|
super();
|
|
|
|
|
2021-07-24 21:10:16 +02:00
|
|
|
if (!row) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-07 21:21:30 +02:00
|
|
|
this.updateFromRow(row);
|
|
|
|
this.init();
|
|
|
|
}
|
|
|
|
|
|
|
|
updateFromRow(row) {
|
2021-07-24 21:10:16 +02:00
|
|
|
this.update([
|
|
|
|
row.branchId,
|
|
|
|
row.noteId,
|
|
|
|
row.parentNoteId,
|
|
|
|
row.prefix,
|
|
|
|
row.notePosition,
|
|
|
|
row.isExpanded,
|
|
|
|
row.utcDateModified
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified]) {
|
2021-10-29 21:37:12 +02:00
|
|
|
/** @type {string} */
|
2021-07-24 21:10:16 +02:00
|
|
|
this.branchId = branchId;
|
2021-10-29 21:37:12 +02:00
|
|
|
/** @type {string} */
|
2021-07-24 21:10:16 +02:00
|
|
|
this.noteId = noteId;
|
2021-10-29 21:37:12 +02:00
|
|
|
/** @type {string} */
|
2021-07-24 21:10:16 +02:00
|
|
|
this.parentNoteId = parentNoteId;
|
2023-01-15 21:04:17 +01:00
|
|
|
/** @type {string|null} */
|
2021-07-24 21:10:16 +02:00
|
|
|
this.prefix = prefix;
|
2021-10-29 21:37:12 +02:00
|
|
|
/** @type {int} */
|
2021-07-24 21:10:16 +02:00
|
|
|
this.notePosition = notePosition;
|
2021-10-29 21:37:12 +02:00
|
|
|
/** @type {boolean} */
|
2021-07-24 21:10:16 +02:00
|
|
|
this.isExpanded = !!isExpanded;
|
2021-10-29 21:37:12 +02:00
|
|
|
/** @type {string} */
|
2021-07-24 21:10:16 +02:00
|
|
|
this.utcDateModified = utcDateModified;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
2020-05-16 23:12:29 +02:00
|
|
|
|
2021-07-24 21:10:16 +02:00
|
|
|
init() {
|
2021-12-23 20:54:48 +01:00
|
|
|
if (this.branchId) {
|
|
|
|
this.becca.branches[this.branchId] = this;
|
|
|
|
}
|
|
|
|
|
2021-11-15 21:23:19 +01:00
|
|
|
this.becca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this;
|
|
|
|
|
2022-07-24 21:30:29 +02:00
|
|
|
const childNote = this.childNote;
|
|
|
|
|
|
|
|
if (!childNote.parentBranches.includes(this)) {
|
|
|
|
childNote.parentBranches.push(this);
|
|
|
|
}
|
|
|
|
|
2022-12-27 14:44:28 +01:00
|
|
|
if (this.noteId === 'root') {
|
2020-05-16 23:12:29 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const parentNote = this.parentNote;
|
|
|
|
|
2021-10-03 23:01:22 +02:00
|
|
|
if (!childNote.parents.includes(parentNote)) {
|
|
|
|
childNote.parents.push(parentNote);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!parentNote.children.includes(childNote)) {
|
|
|
|
parentNote.children.push(childNote);
|
|
|
|
}
|
2020-05-16 23:12:29 +02:00
|
|
|
}
|
|
|
|
|
2023-01-03 13:52:37 +01:00
|
|
|
/** @returns {BNote} */
|
2020-08-27 22:37:57 +02:00
|
|
|
get childNote() {
|
2021-04-16 23:00:08 +02:00
|
|
|
if (!(this.noteId in this.becca.notes)) {
|
2021-12-15 22:36:45 +01:00
|
|
|
// entities can come out of order in sync/import, create skeleton which will be filled later
|
2023-01-03 13:52:37 +01:00
|
|
|
this.becca.addNote(this.noteId, new BNote({noteId: this.noteId}));
|
2020-08-27 22:37:57 +02:00
|
|
|
}
|
|
|
|
|
2021-04-16 23:00:08 +02:00
|
|
|
return this.becca.notes[this.noteId];
|
2020-08-27 22:37:57 +02:00
|
|
|
}
|
2020-05-16 23:12:29 +02:00
|
|
|
|
2023-05-05 16:37:39 +02:00
|
|
|
/** @returns {BNote} */
|
2021-04-25 22:02:32 +02:00
|
|
|
getNote() {
|
|
|
|
return this.childNote;
|
|
|
|
}
|
|
|
|
|
2023-01-03 13:52:37 +01:00
|
|
|
/** @returns {BNote|undefined} - root branch will have undefined parent, all other branches have to have a parent note */
|
2020-08-27 22:37:57 +02:00
|
|
|
get parentNote() {
|
2022-08-16 23:38:11 +02:00
|
|
|
if (!(this.parentNoteId in this.becca.notes) && this.parentNoteId !== 'none') {
|
2021-12-15 22:36:45 +01:00
|
|
|
// entities can come out of order in sync/import, create skeleton which will be filled later
|
2023-01-03 13:52:37 +01:00
|
|
|
this.becca.addNote(this.parentNoteId, new BNote({noteId: this.parentNoteId}));
|
2020-05-16 23:12:29 +02:00
|
|
|
}
|
|
|
|
|
2021-04-16 23:00:08 +02:00
|
|
|
return this.becca.notes[this.parentNoteId];
|
2020-05-16 23:12:29 +02:00
|
|
|
}
|
2021-02-04 22:05:32 +01:00
|
|
|
|
2021-10-31 21:03:26 +01:00
|
|
|
get isDeleted() {
|
|
|
|
return !(this.branchId in this.becca.branches);
|
|
|
|
}
|
|
|
|
|
2022-12-04 13:16:05 +01:00
|
|
|
/**
|
|
|
|
* Branch is weak when its existence should not hinder deletion of its note.
|
|
|
|
* As a result, note with only weak branches should be immediately deleted.
|
|
|
|
* An example is shared or bookmarked clones - they are created automatically and exist for technical reasons,
|
|
|
|
* not as user-intended actions. From user perspective, they don't count as real clones and for the purpose
|
|
|
|
* of deletion should not act as a clone.
|
|
|
|
*
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
get isWeak() {
|
2022-12-24 12:26:32 +01:00
|
|
|
return ['_share', '_lbBookmarks'].includes(this.parentNoteId);
|
2022-12-04 13:16:05 +01:00
|
|
|
}
|
|
|
|
|
2022-04-19 23:06:46 +02:00
|
|
|
/**
|
|
|
|
* Delete a branch. If this is a last note's branch, delete the note as well.
|
|
|
|
*
|
|
|
|
* @param {string} [deleteId] - optional delete identified
|
|
|
|
* @param {TaskContext} [taskContext]
|
|
|
|
*
|
2023-01-05 23:38:41 +01:00
|
|
|
* @returns {boolean} - true if note has been deleted, false otherwise
|
2022-04-19 23:06:46 +02:00
|
|
|
*/
|
|
|
|
deleteBranch(deleteId, taskContext) {
|
|
|
|
if (!deleteId) {
|
|
|
|
deleteId = utils.randomString(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!taskContext) {
|
|
|
|
taskContext = new TaskContext('no-progress-reporting');
|
|
|
|
}
|
|
|
|
|
|
|
|
taskContext.increaseProgressCount();
|
|
|
|
|
2022-06-05 14:58:19 +02:00
|
|
|
const note = this.getNote();
|
|
|
|
|
|
|
|
if (!taskContext.noteDeletionHandlerTriggered) {
|
|
|
|
const parentBranches = note.getParentBranches();
|
|
|
|
|
|
|
|
if (parentBranches.length === 1 && parentBranches[0] === this) {
|
|
|
|
// needs to be run before branches and attributes are deleted and thus attached relations disappear
|
|
|
|
const handlers = require("../../services/handlers");
|
|
|
|
handlers.runAttachedRelations(note, 'runOnNoteDeletion', note);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-27 14:44:28 +01:00
|
|
|
if (this.noteId === 'root'
|
2022-04-19 23:06:46 +02:00
|
|
|
|| this.noteId === cls.getHoistedNoteId()) {
|
|
|
|
|
|
|
|
throw new Error("Can't delete root or hoisted branch/note");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.markAsDeleted(deleteId);
|
|
|
|
|
2022-12-04 13:16:05 +01:00
|
|
|
const notDeletedBranches = note.getStrongParentBranches();
|
2022-04-19 23:06:46 +02:00
|
|
|
|
|
|
|
if (notDeletedBranches.length === 0) {
|
2022-12-04 13:16:05 +01:00
|
|
|
for (const weakBranch of note.getParentBranches()) {
|
|
|
|
weakBranch.markAsDeleted(deleteId);
|
|
|
|
}
|
|
|
|
|
2022-04-19 23:06:46 +02:00
|
|
|
for (const childBranch of note.getChildBranches()) {
|
|
|
|
childBranch.deleteBranch(deleteId, taskContext);
|
|
|
|
}
|
|
|
|
|
|
|
|
// first delete children and then parent - this will show up better in recent changes
|
|
|
|
|
2022-12-21 15:19:05 +01:00
|
|
|
log.info(`Deleting note ${note.noteId}`);
|
2022-04-19 23:06:46 +02:00
|
|
|
|
2022-12-06 16:11:43 +01:00
|
|
|
this.becca.notes[note.noteId].isBeingDeleted = true;
|
2022-10-26 19:14:49 +02:00
|
|
|
|
2022-04-19 23:06:46 +02:00
|
|
|
for (const attribute of note.getOwnedAttributes()) {
|
|
|
|
attribute.markAsDeleted(deleteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const relation of note.getTargetRelations()) {
|
|
|
|
relation.markAsDeleted(deleteId);
|
|
|
|
}
|
|
|
|
|
2023-03-16 12:17:55 +01:00
|
|
|
for (const attachment of note.getAttachments()) {
|
|
|
|
attachment.markAsDeleted(deleteId);
|
2023-03-08 09:01:23 +01:00
|
|
|
}
|
|
|
|
|
2022-04-19 23:06:46 +02:00
|
|
|
note.markAsDeleted(deleteId);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-08 21:10:58 +02:00
|
|
|
beforeSaving() {
|
2022-12-27 14:44:28 +01:00
|
|
|
if (!this.noteId || !this.parentNoteId) {
|
|
|
|
throw new Error(`noteId and parentNoteId are mandatory properties for Branch`);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.branchId = `${this.parentNoteId}_${this.noteId}`;
|
|
|
|
|
2021-05-08 21:10:58 +02:00
|
|
|
if (this.notePosition === undefined || this.notePosition === null) {
|
2022-11-26 14:57:39 +01:00
|
|
|
let maxNotePos = 0;
|
|
|
|
|
|
|
|
for (const childBranch of this.parentNote.getChildBranches()) {
|
2022-12-27 14:44:28 +01:00
|
|
|
if (maxNotePos < childBranch.notePosition
|
|
|
|
&& childBranch.noteId !== '_hidden' // hidden has very large notePosition to always stay last
|
|
|
|
) {
|
2022-11-26 14:57:39 +01:00
|
|
|
maxNotePos = childBranch.notePosition;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.notePosition = maxNotePos + 10;
|
2021-05-08 21:10:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.isExpanded) {
|
|
|
|
this.isExpanded = false;
|
|
|
|
}
|
|
|
|
|
2022-12-27 21:17:40 +01:00
|
|
|
if (!this.prefix?.trim()) {
|
|
|
|
this.prefix = null;
|
|
|
|
}
|
|
|
|
|
2021-05-08 21:10:58 +02:00
|
|
|
this.utcDateModified = dateUtils.utcNowDateTime();
|
|
|
|
|
|
|
|
super.beforeSaving();
|
2021-05-09 20:46:32 +02:00
|
|
|
|
|
|
|
this.becca.branches[this.branchId] = this;
|
2021-05-08 21:10:58 +02:00
|
|
|
}
|
|
|
|
|
2021-04-25 21:19:18 +02:00
|
|
|
getPojo() {
|
2021-05-08 21:10:58 +02:00
|
|
|
return {
|
2021-04-25 20:00:42 +02:00
|
|
|
branchId: this.branchId,
|
|
|
|
noteId: this.noteId,
|
|
|
|
parentNoteId: this.parentNoteId,
|
|
|
|
prefix: this.prefix,
|
|
|
|
notePosition: this.notePosition,
|
2021-04-25 21:19:18 +02:00
|
|
|
isExpanded: this.isExpanded,
|
2021-05-09 11:12:53 +02:00
|
|
|
isDeleted: false,
|
2021-11-18 22:39:12 +01:00
|
|
|
utcDateModified: this.utcDateModified
|
2021-04-25 20:00:42 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
createClone(parentNoteId, notePosition) {
|
2022-12-27 14:44:28 +01:00
|
|
|
const existingBranch = this.becca.getBranchFromChildAndParent(this.noteId, parentNoteId);
|
|
|
|
|
|
|
|
if (existingBranch) {
|
|
|
|
existingBranch.notePosition = notePosition;
|
|
|
|
return existingBranch;
|
|
|
|
} else {
|
2023-01-03 13:52:37 +01:00
|
|
|
return new BBranch({
|
2022-12-27 14:44:28 +01:00
|
|
|
noteId: this.noteId,
|
|
|
|
parentNoteId: parentNoteId,
|
|
|
|
notePosition: notePosition,
|
|
|
|
prefix: this.prefix,
|
|
|
|
isExpanded: this.isExpanded
|
|
|
|
});
|
|
|
|
}
|
2021-04-25 20:00:42 +02:00
|
|
|
}
|
2020-05-16 23:12:29 +02:00
|
|
|
}
|
2020-05-17 09:48:24 +02:00
|
|
|
|
2023-01-03 13:52:37 +01:00
|
|
|
module.exports = BBranch;
|