@ -31,16 +31,13 @@
const Entity = require('./entity');
const Entity = require('./entity');
const Attribute = require('./attribute');
const Attribute = require('./attribute');
const protectedSessionService = require('../services/protected_session');
const protectedSessionService = require('../services/protected_session');
const repository = require('../services/repository');
const sql = require('../services/sql');
const sql = require('../services/sql');
const utils = require('../services/utils');
const utils = require('../services/utils');
const dateUtils = require('../services/date_utils');
const dateUtils = require('../services/date_utils');
const syncTableService = require('../services/sync_table ');
const entityChangesService = require('../services/entity_changes.js ');
const LABEL = 'label';
const LABEL = 'label';
const LABEL_DEFINITION = 'label-definition';
const RELATION = 'relation';
const RELATION = 'relation';
const RELATION_DEFINITION = 'relation-definition';
/**
/**
* This represents a Note which is a central object in the Trilium Notes project.
* This represents a Note which is a central object in the Trilium Notes project.
@ -49,7 +46,6 @@ const RELATION_DEFINITION = 'relation-definition';
* @property {string} type - one of "text", "code", "file" or "render"
* @property {string} type - one of "text", "code", "file" or "render"
* @property {string} mime - MIME type, e.g. "text/html"
* @property {string} mime - MIME type, e.g. "text/html"
* @property {string} title - note title
* @property {string} title - note title
* @property {int} contentLength - length of content
* @property {boolean} isProtected - true if note is protected
* @property {boolean} isProtected - true if note is protected
* @property {boolean} isDeleted - true if note is deleted
* @property {boolean} isDeleted - true if note is deleted
* @property {string|null} deleteId - ID identifying delete transaction
* @property {string|null} deleteId - ID identifying delete transaction
@ -95,14 +91,14 @@ class Note extends Entity {
* part of Note entity with it's own sync. Reasons behind this hybrid design has been:
* part of Note entity with it's own sync. Reasons behind this hybrid design has been:
*
*
* - content can be quite large and it's not necessary to load it / fill memory for any note access even if we don't need a content, especially for bulk operations like search
* - content can be quite large and it's not necessary to load it / fill memory for any note access even if we don't need a content, especially for bulk operations like search
* - changes in the note metadata or title should not trigger note content sync (so we keep separate utcDateModified and sync row s)
* - changes in the note metadata or title should not trigger note content sync (so we keep separate utcDateModified and entity changes record s)
* - but to the user note content and title changes are one and the same - single dateModified (so all changes must go through Note and content is not a separate entity)
* - but to the user note content and title changes are one and the same - single dateModified (so all changes must go through Note and content is not a separate entity)
*/
*/
/** @returns {Promise< *> } */
/** @returns {*} */
async getContent(silentNotFoundError = false) {
getContent(silentNotFoundError = false) {
if (this.content === undefined) {
if (this.content === undefined) {
const res = await sql.getRow(`SELECT content, hash FROM note_contents WHERE noteId = ?`, [this.noteId]);
const res = sql.getRow(`SELECT content, hash FROM note_contents WHERE noteId = ?`, [this.noteId]);
if (!res) {
if (!res) {
if (silentNotFoundError) {
if (silentNotFoundError) {
@ -135,9 +131,20 @@ class Note extends Entity {
}
}
}
}
/** @returns {Promise< *>} */
/** @returns {{contentLength, dateModified, utcDateModified}} */
async getJsonContent() {
getContentMetadata() {
const content = await this.getContent();
return sql.getRow(`
SELECT
LENGTH(content) AS contentLength,
dateModified,
utcDateModified
FROM note_contents
WHERE noteId = ?`, [this.noteId]);
}
/** @returns {*} */
getJsonContent() {
const content = this.getContent();
if (!content || !content.trim()) {
if (!content || !content.trim()) {
return null;
return null;
@ -146,24 +153,24 @@ class Note extends Entity {
return JSON.parse(content);
return JSON.parse(content);
}
}
/** @returns {Promise} */
setContent(content) {
async setContent(content) {
if (content === null || content === undefined) {
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to note ${this.noteId}`);
throw new Error(`Cannot set null content to note ${this.noteId}`);
}
}
if (this.isStringNote()) {
content = content.toString();
}
else {
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
}
// force updating note itself so that dateModified is represented correctly even for the content
this.forcedChange = true;
this.contentLength = content.byteLength;
await this.save();
this.content = content;
this.content = content;
const pojo = {
const pojo = {
noteId: this.noteId,
noteId: this.noteId,
content: content,
content: content,
dateModified: dateUtils.localNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime(),
hash: utils.hash(this.noteId + "|" + content.toString())
hash: utils.hash(this.noteId + "|" + content.toString())
};
};
@ -177,14 +184,13 @@ class Note extends Entity {
}
}
}
}
await sql.upsert("note_contents", "noteId", pojo);
sql.upsert("note_contents", "noteId", pojo);
await syncTableService.addNoteContentSync (this.noteId);
entityChangesService.addNoteContentEntityChange (this.noteId);
}
}
/** @returns {Promise} */
setJsonContent(content) {
async setJsonContent(content) {
this.setContent(JSON.stringify(content, null, '\t'));
await this.setContent(JSON.stringify(content, null, '\t'));
}
}
/** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
/** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
@ -232,8 +238,8 @@ class Note extends Entity {
return null;
return null;
}
}
async loadOwnedAttributesToCache() {
loadOwnedAttributesToCache() {
this.__ownedAttributeCache = await repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]);
this.__ownedAttributeCache = this. repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]);
return this.__ownedAttributeCache;
return this.__ownedAttributeCache;
}
}
@ -243,11 +249,11 @@ class Note extends Entity {
*
*
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise< Attribute[]> } note's "owned" attributes - excluding inherited ones
* @returns {Attribute[]} note's "owned" attributes - excluding inherited ones
*/
*/
async getOwnedAttributes(type, name) {
getOwnedAttributes(type, name) {
if (!this.__ownedAttributeCache) {
if (!this.__ownedAttributeCache) {
await this.loadOwnedAttributesToCache();
this.loadOwnedAttributesToCache();
}
}
if (type & & name) {
if (type & & name) {
@ -265,31 +271,31 @@ class Note extends Entity {
}
}
/**
/**
* @returns {Promise< Attribute> } attribute belonging to this specific note (excludes inherited attributes)
* @returns {Attribute} attribute belonging to this specific note (excludes inherited attributes)
*
*
* This method can be significantly faster than the getAttribute()
* This method can be significantly faster than the getAttribute()
*/
*/
async getOwnedAttribute(type, name) {
getOwnedAttribute(type, name) {
const attrs = await this.getOwnedAttributes(type, name);
const attrs = this.getOwnedAttributes(type, name);
return attrs.length > 0 ? attrs[0] : null;
return attrs.length > 0 ? attrs[0] : null;
}
}
/**
/**
* @returns {Promise< Attribute[]> } relations targetting this specific note
* @returns {Attribute[]} relations targetting this specific note
*/
*/
async getTargetRelations() {
getTargetRelations() {
return await repository.getEntities("SELECT * FROM attributes WHERE type = 'relation' AND isDeleted = 0 AND value = ?", [this.noteId]);
return this. repository.getEntities("SELECT * FROM attributes WHERE type = 'relation' AND isDeleted = 0 AND value = ?", [this.noteId]);
}
}
/**
/**
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise< Attribute[]> } all note's attributes, including inherited ones
* @returns {Attribute[]} all note's attributes, including inherited ones
*/
*/
async getAttributes(type, name) {
getAttributes(type, name) {
if (!this.__attributeCache) {
if (!this.__attributeCache) {
await this.loadAttributesToCache();
this.loadAttributesToCache();
}
}
if (type & & name) {
if (type & & name) {
@ -308,67 +314,51 @@ class Note extends Entity {
/**
/**
* @param {string} [name] - label name to filter
* @param {string} [name] - label name to filter
* @returns {Promise< Attribute[]> } all note's labels (attributes with type label), including inherited ones
* @returns {Attribute[]} all note's labels (attributes with type label), including inherited ones
*/
*/
async getLabels(name) {
getLabels(name) {
return await this.getAttributes(LABEL, name);
return this.getAttributes(LABEL, name);
}
}
/**
/**
* @param {string} [name] - label name to filter
* @param {string} [name] - label name to filter
* @returns {Promise< Attribute[]> } all note's labels (attributes with type label), excluding inherited ones
* @returns {Attribute[]} all note's labels (attributes with type label), excluding inherited ones
*/
*/
async getOwnedLabels(name) {
getOwnedLabels(name) {
return await this.getOwnedAttributes(LABEL, name);
return this.getOwnedAttributes(LABEL, name);
}
/**
* @param {string} [name] - label name to filter
* @returns {Promise< Attribute[]>} all note's label definitions, including inherited ones
*/
async getLabelDefinitions(name) {
return await this.getAttributes(LABEL_DEFINITION, name);
}
}
/**
/**
* @param {string} [name] - relation name to filter
* @param {string} [name] - relation name to filter
* @returns {Promise< Attribute[]> } all note's relations (attributes with type relation), including inherited ones
* @returns {Attribute[]} all note's relations (attributes with type relation), including inherited ones
*/
*/
async getRelations(name) {
getRelations(name) {
return await this.getAttributes(RELATION, name);
return this.getAttributes(RELATION, name);
}
}
/**
/**
* @param {string} [name] - relation name to filter
* @param {string} [name] - relation name to filter
* @returns {Promise< Attribute[]> } all note's relations (attributes with type relation), excluding inherited ones
* @returns {Attribute[]} all note's relations (attributes with type relation), excluding inherited ones
*/
*/
async getOwnedRelations(name) {
getOwnedRelations(name) {
return await this.getOwnedAttributes(RELATION, name);
return this.getOwnedAttributes(RELATION, name);
}
}
/**
/**
* @param {string} [name] - relation name to filter
* @param {string} [name] - relation name to filter
* @returns {Promise< Note[]> }
* @returns {Note[]}
*/
*/
async getRelationTargets(name) {
getRelationTargets(name) {
const relations = await this.getRelations(name);
const relations = this.getRelations(name);
const targets = [];
const targets = [];
for (const relation of relations) {
for (const relation of relations) {
targets.push(await relation.getTargetNote());
targets.push(relation.getTargetNote());
}
}
return targets;
return targets;
}
}
/**
* @param {string} [name] - relation name to filter
* @returns {Promise< Attribute[]>} all note's relation definitions including inherited ones
*/
async getRelationDefinitions(name) {
return await this.getAttributes(RELATION_DEFINITION, name);
}
/**
/**
* Clear note's attributes cache to force fresh reload for next attribute request.
* Clear note's attributes cache to force fresh reload for next attribute request.
* Cache is note instance scoped.
* Cache is note instance scoped.
@ -378,9 +368,8 @@ class Note extends Entity {
this.__ownedAttributeCache = null;
this.__ownedAttributeCache = null;
}
}
/** @returns {Promise< void>} */
loadAttributesToCache() {
async loadAttributesToCache() {
const attributes = this.repository.getEntities(`
const attributes = await repository.getEntities(`
WITH RECURSIVE
WITH RECURSIVE
tree(noteId, level) AS (
tree(noteId, level) AS (
SELECT ?, 0
SELECT ?, 0
@ -412,6 +401,7 @@ class Note extends Entity {
return false;
return false;
}
}
// FIXME: this code is quite questionable, one problem is that other caches (TreeCache, NoteCache) have nothing like that
if (attr.isDefinition()) {
if (attr.isDefinition()) {
const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type & & el.name === attr.name);
const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type & & el.name === attr.name);
@ -419,15 +409,15 @@ class Note extends Entity {
return firstDefinitionIndex === index;
return firstDefinitionIndex === index;
}
}
else {
else {
const definitionAttr = attributes.find(el => el.type === attr.type + '-definition' & & el.name === attr.name);
const definitionAttr = attributes.find(el => el.type === 'label' & & el.name === attr.type + ':' + attr.name);
if (!definitionAttr) {
if (!definitionAttr) {
return true;
return true;
}
}
const definition = definitionAttr.value ;
const definition = definitionAttr.getDefinition() ;
if (definition.multiplicityType === 'multivalue ') {
if (definition.multiplicity === 'multi') {
return true;
return true;
}
}
else {
else {
@ -439,38 +429,34 @@ class Note extends Entity {
}
}
});
});
for (const attr of filteredAttributes) {
attr.isOwned = attr.noteId === this.noteId;
}
this.__attributeCache = filteredAttributes;
this.__attributeCache = filteredAttributes;
}
}
/**
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} name - attribute name
* @returns {Promise< boolean> } true if note has an attribute with given type and name (including inherited)
* @returns {boolean} true if note has an attribute with given type and name (including inherited)
*/
*/
async hasAttribute(type, name) {
hasAttribute(type, name) {
return !!await this.getAttribute(type, name);
return !!this.getAttribute(type, name);
}
}
/**
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} name - attribute name
* @returns {Promise< boolean> } true if note has an attribute with given type and name (excluding inherited)
* @returns {boolean} true if note has an attribute with given type and name (excluding inherited)
*/
*/
async hasOwnedAttribute(type, name) {
hasOwnedAttribute(type, name) {
return !!await this.getOwnedAttribute(type, name);
return !!this.getOwnedAttribute(type, name);
}
}
/**
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} name - attribute name
* @returns {Promise< Attribute> } 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 {Attribute} 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.
*/
*/
async getAttribute(type, name) {
getAttribute(type, name) {
const attributes = await this.getAttributes();
const attributes = this.getAttributes();
return attributes.find(attr => attr.type === type & & attr.name === name);
return attributes.find(attr => attr.type === type & & attr.name === name);
}
}
@ -478,10 +464,10 @@ class Note extends Entity {
/**
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} name - attribute name
* @returns {Promise< string|null> } attribute value of given type and name or null if no such attribute exists.
* @returns {string|null} attribute value of given type and name or null if no such attribute exists.
*/
*/
async getAttributeValue(type, name) {
getAttributeValue(type, name) {
const attr = await this.getAttribute(type, name);
const attr = this.getAttribute(type, name);
return attr ? attr.value : null;
return attr ? attr.value : null;
}
}
@ -489,10 +475,10 @@ class Note extends Entity {
/**
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} name - attribute name
* @returns {Promise< string|null> } attribute value of given type and name or null if no such attribute exists.
* @returns {string|null} attribute value of given type and name or null if no such attribute exists.
*/
*/
async getOwnedAttributeValue(type, name) {
getOwnedAttributeValue(type, name) {
const attr = await this.getOwnedAttribute(type, name);
const attr = this.getOwnedAttribute(type, name);
return attr ? attr.value : null;
return attr ? attr.value : null;
}
}
@ -504,14 +490,13 @@ class Note extends Entity {
* @param {boolean} enabled - toggle On or Off
* @param {boolean} enabled - toggle On or Off
* @param {string} name - attribute name
* @param {string} name - attribute name
* @param {string} [value] - attribute value (optional)
* @param {string} [value] - attribute value (optional)
* @returns {Promise< void>}
*/
*/
async toggleAttribute(type, enabled, name, value) {
toggleAttribute(type, enabled, name, value) {
if (enabled) {
if (enabled) {
await this.setAttribute(type, name, value);
this.setAttribute(type, name, value);
}
}
else {
else {
await this.removeAttribute(type, name, value);
this.removeAttribute(type, name, value);
}
}
}
}
@ -521,16 +506,15 @@ class Note extends Entity {
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} name - attribute name
* @param {string} [value] - attribute value (optional)
* @param {string} [value] - attribute value (optional)
* @returns {Promise< void>}
*/
*/
async setAttribute(type, name, value) {
setAttribute(type, name, value) {
const attributes = await this.loadOwnedAttributesToCache();
const attributes = this.loadOwnedAttributesToCache();
let attr = attributes.find(attr => attr.type === type & & attr.name === name);
let attr = attributes.find(attr => attr.type === type & & attr.name === name);
if (attr) {
if (attr) {
if (attr.value !== value) {
if (attr.value !== value) {
attr.value = value;
attr.value = value;
await a ttr.save();
attr.save();
this.invalidateAttributeCache();
this.invalidateAttributeCache();
}
}
@ -543,7 +527,7 @@ class Note extends Entity {
value: value !== undefined ? value : ""
value: value !== undefined ? value : ""
});
});
await a ttr.save();
attr.save();
this.invalidateAttributeCache();
this.invalidateAttributeCache();
}
}
@ -555,15 +539,14 @@ class Note extends Entity {
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} name - attribute name
* @param {string} [value] - attribute value (optional)
* @param {string} [value] - attribute value (optional)
* @returns {Promise< void>}
*/
*/
async removeAttribute(type, name, value) {
removeAttribute(type, name, value) {
const attributes = await this.loadOwnedAttributesToCache();
const attributes = this.loadOwnedAttributesToCache();
for (const attribute of attributes) {
for (const attribute of attributes) {
if (attribute.type === type & & attribute.name === name & & (value === undefined || value === attribute.value)) {
if (attribute.type === type & & attribute.name === name & & (value === undefined || value === attribute.value)) {
attribute.isDeleted = true;
attribute.isDeleted = true;
await a ttribute.save();
attribute.save();
this.invalidateAttributeCache();
this.invalidateAttributeCache();
}
}
@ -571,121 +554,123 @@ class Note extends Entity {
}
}
/**
/**
* @return {Promise< Attribute> }
* @return {Attribute}
*/
*/
async a ddAttribute(type, name, value = "") {
addAttribute(type, name, value = "", isInheritable = false, position = 1000 ) {
const attr = new Attribute({
const attr = new Attribute({
noteId: this.noteId,
noteId: this.noteId,
type: type,
type: type,
name: name,
name: name,
value: value
value: value,
isInheritable: isInheritable,
position: position
});
});
await a ttr.save();
attr.save();
this.invalidateAttributeCache();
this.invalidateAttributeCache();
return attr;
return attr;
}
}
async a ddLabel(name, value = "") {
addLabel(name, value = "", isInheritable = false ) {
return await this.addAttribute(LABEL, name, value);
return this.addAttribute(LABEL, name, value, isInheritabl e);
}
}
async a ddRelation(name, targetNoteId) {
addRelation(name, targetNoteId, isInheritable = false ) {
return await this.addAttribute(RELATION, name, targetNoteId);
return this.addAttribute(RELATION, name, targetNoteId, isInheritable );
}
}
/**
/**
* @param {string} name - label name
* @param {string} name - label name
* @returns {Promise< boolean> } true if label exists (including inherited)
* @returns {boolean} true if label exists (including inherited)
*/
*/
async hasLabel(name) { return await this.hasAttribute(LABEL, name); }
hasLabel(name) { return this.hasAttribute(LABEL, name); }
/**
/**
* @param {string} name - label name
* @param {string} name - label name
* @returns {Promise< boolean> } true if label exists (excluding inherited)
* @returns {boolean} true if label exists (excluding inherited)
*/
*/
async hasOwnedLabel(name) { return await this.hasOwnedAttribute(LABEL, name); }
hasOwnedLabel(name) { return this.hasOwnedAttribute(LABEL, name); }
/**
/**
* @param {string} name - relation name
* @param {string} name - relation name
* @returns {Promise< boolean> } true if relation exists (including inherited)
* @returns {boolean} true if relation exists (including inherited)
*/
*/
async hasRelation(name) { return await this.hasAttribute(RELATION, name); }
hasRelation(name) { return this.hasAttribute(RELATION, name); }
/**
/**
* @param {string} name - relation name
* @param {string} name - relation name
* @returns {Promise< boolean> } true if relation exists (excluding inherited)
* @returns {boolean} true if relation exists (excluding inherited)
*/
*/
async hasOwnedRelation(name) { return await this.hasOwnedAttribute(RELATION, name); }
hasOwnedRelation(name) { return this.hasOwnedAttribute(RELATION, name); }
/**
/**
* @param {string} name - label name
* @param {string} name - label name
* @returns {Promise< Attribute|null> } label if it exists, null otherwise
* @returns {Attribute|null} label if it exists, null otherwise
*/
*/
async getLabel(name) { return await this.getAttribute(LABEL, name); }
getLabel(name) { return this.getAttribute(LABEL, name); }
/**
/**
* @param {string} name - label name
* @param {string} name - label name
* @returns {Promise< Attribute|null> } label if it exists, null otherwise
* @returns {Attribute|null} label if it exists, null otherwise
*/
*/
async getOwnedLabel(name) { return await this.getOwnedAttribute(LABEL, name); }
getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); }
/**
/**
* @param {string} name - relation name
* @param {string} name - relation name
* @returns {Promise< Attribute|null> } relation if it exists, null otherwise
* @returns {Attribute|null} relation if it exists, null otherwise
*/
*/
async getRelation(name) { return await this.getAttribute(RELATION, name); }
getRelation(name) { return this.getAttribute(RELATION, name); }
/**
/**
* @param {string} name - relation name
* @param {string} name - relation name
* @returns {Promise< Attribute|null> } relation if it exists, null otherwise
* @returns {Attribute|null} relation if it exists, null otherwise
*/
*/
async getOwnedRelation(name) { return await this.getOwnedAttribute(RELATION, name); }
getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); }
/**
/**
* @param {string} name - label name
* @param {string} name - label name
* @returns {Promise< string|null> } label value if label exists, null otherwise
* @returns {string|null} label value if label exists, null otherwise
*/
*/
async getLabelValue(name) { return await this.getAttributeValue(LABEL, name); }
getLabelValue(name) { return this.getAttributeValue(LABEL, name); }
/**
/**
* @param {string} name - label name
* @param {string} name - label name
* @returns {Promise< string|null> } label value if label exists, null otherwise
* @returns {string|null} label value if label exists, null otherwise
*/
*/
async getOwnedLabelValue(name) { return await this.getOwnedAttributeValue(LABEL, name); }
getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); }
/**
/**
* @param {string} name - relation name
* @param {string} name - relation name
* @returns {Promise< string|null> } relation value if relation exists, null otherwise
* @returns {string|null} relation value if relation exists, null otherwise
*/
*/
async getRelationValue(name) { return await this.getAttributeValue(RELATION, name); }
getRelationValue(name) { return this.getAttributeValue(RELATION, name); }
/**
/**
* @param {string} name - relation name
* @param {string} name - relation name
* @returns {Promise< string|null> } relation value if relation exists, null otherwise
* @returns {string|null} relation value if relation exists, null otherwise
*/
*/
async getOwnedRelationValue(name) { return await this.getOwnedAttributeValue(RELATION, name); }
getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); }
/**
/**
* @param {string} name
* @param {string} name
* @returns {Promise< Note> |null} target note of the relation or null (if target is empty or note was not found)
* @returns {Note|null} target note of the relation or null (if target is empty or note was not found)
*/
*/
async getRelationTarget(name) {
getRelationTarget(name) {
const relation = await this.getRelation(name);
const relation = this.getRelation(name);
return relation ? await repository.getNote(relation.value) : null;
return relation ? this. repository.getNote(relation.value) : null;
}
}
/**
/**
* @param {string} name
* @param {string} name
* @returns {Promise< Note> |null} target note of the relation or null (if target is empty or note was not found)
* @returns {Note|null} target note of the relation or null (if target is empty or note was not found)
*/
*/
async getOwnedRelationTarget(name) {
getOwnedRelationTarget(name) {
const relation = await this.getOwnedRelation(name);
const relation = this.getOwnedRelation(name);
return relation ? await repository.getNote(relation.value) : null;
return relation ? this. repository.getNote(relation.value) : null;
}
}
/**
/**
@ -694,9 +679,8 @@ class Note extends Entity {
* @param {boolean} enabled - toggle On or Off
* @param {boolean} enabled - toggle On or Off
* @param {string} name - label name
* @param {string} name - label name
* @param {string} [value] - label value (optional)
* @param {string} [value] - label value (optional)
* @returns {Promise< void>}
*/
*/
async toggleLabel(enabled, name, value) { return await this.toggleAttribute(LABEL, enabled, name, value); }
toggleLabel(enabled, name, value) { return this.toggleAttribute(LABEL, enabled, name, value); }
/**
/**
* Based on enabled, relation is either set or removed.
* Based on enabled, relation is either set or removed.
@ -704,51 +688,46 @@ class Note extends Entity {
* @param {boolean} enabled - toggle On or Off
* @param {boolean} enabled - toggle On or Off
* @param {string} name - relation name
* @param {string} name - relation name
* @param {string} [value] - relation value (noteId)
* @param {string} [value] - relation value (noteId)
* @returns {Promise< void>}
*/
*/
async toggleRelation(enabled, name, value) { return await this.toggleAttribute(RELATION, enabled, name, value); }
toggleRelation(enabled, name, value) { return this.toggleAttribute(RELATION, enabled, name, value); }
/**
/**
* Update's given label's value or creates it if it doesn't exist
* Update's given label's value or creates it if it doesn't exist
*
*
* @param {string} name - label name
* @param {string} name - label name
* @param {string} [value] - label value
* @param {string} [value] - label value
* @returns {Promise< void>}
*/
*/
async setLabel(name, value) { return await this.setAttribute(LABEL, name, value); }
setLabel(name, value) { return this.setAttribute(LABEL, name, value); }
/**
/**
* Update's given relation's value or creates it if it doesn't exist
* Update's given relation's value or creates it if it doesn't exist
*
*
* @param {string} name - relation name
* @param {string} name - relation name
* @param {string} [value] - relation value (noteId)
* @param {string} [value] - relation value (noteId)
* @returns {Promise< void>}
*/
*/
async setRelation(name, value) { return await this.setAttribute(RELATION, name, value); }
setRelation(name, value) { return this.setAttribute(RELATION, name, value); }
/**
/**
* Remove label name-value pair, if it exists.
* Remove label name-value pair, if it exists.
*
*
* @param {string} name - label name
* @param {string} name - label name
* @param {string} [value] - label value
* @param {string} [value] - label value
* @returns {Promise< void>}
*/
*/
async removeLabel(name, value) { return await this.removeAttribute(LABEL, name, value); }
removeLabel(name, value) { return this.removeAttribute(LABEL, name, value); }
/**
/**
* Remove relation name-value pair, if it exists.
* Remove relation name-value pair, if it exists.
*
*
* @param {string} name - relation name
* @param {string} name - relation name
* @param {string} [value] - relation value (noteId)
* @param {string} [value] - relation value (noteId)
* @returns {Promise< void>}
*/
*/
async removeRelation(name, value) { return await this.removeAttribute(RELATION, name, value); }
removeRelation(name, value) { return this.removeAttribute(RELATION, name, value); }
/**
/**
* @return {Promise< string[]> } return list of all descendant noteIds of this note. Returning just noteIds because number of notes can be huge. Includes also this note's noteId
* @return {string[]} return list of all descendant noteIds of this note. Returning just noteIds because number of notes can be huge. Includes also this note's noteId
*/
*/
async getDescendantNoteIds() {
getDescendantNoteIds() {
return await sql.getColumn(`
return sql.getColumn(`
WITH RECURSIVE
WITH RECURSIVE
tree(noteId) AS (
tree(noteId) AS (
SELECT ?
SELECT ?
@ -768,9 +747,9 @@ class Note extends Entity {
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} name - attribute name
* @param {string} [value] - attribute value
* @param {string} [value] - attribute value
* @returns {Promise< Note[]> }
* @returns {Note[]}
*/
*/
async getDescendantNotesWithAttribute(type, name, value) {
getDescendantNotesWithAttribute(type, name, value) {
const params = [this.noteId, name];
const params = [this.noteId, name];
let valueCondition = "";
let valueCondition = "";
@ -779,7 +758,7 @@ class Note extends Entity {
valueCondition = " AND attributes.value = ?";
valueCondition = " AND attributes.value = ?";
}
}
const notes = await repository.getEntities(`
const notes = this. repository.getEntities(`
WITH RECURSIVE
WITH RECURSIVE
tree(noteId) AS (
tree(noteId) AS (
SELECT ?
SELECT ?
@ -806,36 +785,36 @@ class Note extends Entity {
*
*
* @param {string} name - label name
* @param {string} name - label name
* @param {string} [value] - label value
* @param {string} [value] - label value
* @returns {Promise< Note[]> }
* @returns {Note[]}
*/
*/
async getDescendantNotesWithLabel(name, value) { return await this.getDescendantNotesWithAttribute(LABEL, name, value); }
getDescendantNotesWithLabel(name, value) { return this.getDescendantNotesWithAttribute(LABEL, name, value); }
/**
/**
* Finds descendant notes with given relation name and value. Only own relations are considered, not inherited ones
* Finds descendant notes with given relation name and value. Only own relations are considered, not inherited ones
*
*
* @param {string} name - relation name
* @param {string} name - relation name
* @param {string} [value] - relation value
* @param {string} [value] - relation value
* @returns {Promise< Note[]> }
* @returns {Note[]}
*/
*/
async getDescendantNotesWithRelation(name, value) { return await this.getDescendantNotesWithAttribute(RELATION, name, value); }
getDescendantNotesWithRelation(name, value) { return this.getDescendantNotesWithAttribute(RELATION, name, value); }
/**
/**
* Returns note revisions of this note.
* Returns note revisions of this note.
*
*
* @returns {Promise< NoteRevision[]> }
* @returns {NoteRevision[]}
*/
*/
async getRevisions() {
getRevisions() {
return await repository.getEntities("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId]);
return this. repository.getEntities("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId]);
}
}
/**
/**
* Get list of links coming out of this note.
* Get list of links coming out of this note.
*
*
* @deprecated - not intended for general use
* @deprecated - not intended for general use
* @returns {Promise< Attribute[]> }
* @returns {Attribute[]}
*/
*/
async getLinks() {
getLinks() {
return await repository.getEntities(`
return this. repository.getEntities(`
SELECT *
SELECT *
FROM attributes
FROM attributes
WHERE noteId = ? AND
WHERE noteId = ? AND
@ -845,24 +824,24 @@ class Note extends Entity {
}
}
/**
/**
* @returns {Promise< Branch[]> }
* @returns {Branch[]}
*/
*/
async getBranches() {
getBranches() {
return await repository.getEntities("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
return this. repository.getEntities("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
}
}
/**
/**
* @returns {boolean} - true if note has children
* @returns {boolean} - true if note has children
*/
*/
async hasChildren() {
hasChildren() {
return (await this.getChildNotes()).length > 0;
return (this.getChildNotes()).length > 0;
}
}
/**
/**
* @returns {Promise< Note[]> } child notes of this note
* @returns {Note[]} child notes of this note
*/
*/
async getChildNotes() {
getChildNotes() {
return await repository.getEntities(`
return this. repository.getEntities(`
SELECT notes.*
SELECT notes.*
FROM branches
FROM branches
JOIN notes USING(noteId)
JOIN notes USING(noteId)
@ -873,10 +852,10 @@ class Note extends Entity {
}
}
/**
/**
* @returns {Promise< Branch[]> } child branches of this note
* @returns {Branch[]} child branches of this note
*/
*/
async getChildBranches() {
getChildBranches() {
return await repository.getEntities(`
return this. repository.getEntities(`
SELECT branches.*
SELECT branches.*
FROM branches
FROM branches
WHERE branches.isDeleted = 0
WHERE branches.isDeleted = 0
@ -885,10 +864,10 @@ class Note extends Entity {
}
}
/**
/**
* @returns {Promise< Note[]> } parent notes of this note (note can have multiple parents because of cloning)
* @returns {Note[]} parent notes of this note (note can have multiple parents because of cloning)
*/
*/
async getParentNotes() {
getParentNotes() {
return await repository.getEntities(`
return this. repository.getEntities(`
SELECT parent_notes.*
SELECT parent_notes.*
FROM
FROM
branches AS child_tree
branches AS child_tree
@ -899,17 +878,17 @@ class Note extends Entity {
}
}
/**
/**
* @return {Promise< string[][]> } - array of notePaths (each represented by array of noteIds constituting the particular note path)
* @return {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path)
*/
*/
async getAllNotePaths() {
getAllNotePaths() {
if (this.noteId === 'root') {
if (this.noteId === 'root') {
return [['root']];
return [['root']];
}
}
const notePaths = [];
const notePaths = [];
for (const parentNote of await this.getParentNotes()) {
for (const parentNote of this.getParentNotes()) {
for (const parentPath of await parentNote.getAllNotePaths()) {
for (const parentPath of parentNote.getAllNotePaths()) {
parentPath.push(this.noteId);
parentPath.push(this.noteId);
notePaths.push(parentPath);
notePaths.push(parentPath);
}
}
@ -918,12 +897,22 @@ class Note extends Entity {
return notePaths;
return notePaths;
}
}
getRelationDefinitions() {
return this.getLabels()
.filter(l => l.name.startsWith("relation:"));
}
getLabelDefinitions() {
return this.getLabels()
.filter(l => l.name.startsWith("relation:"));
}
/**
/**
* @param ancestorNoteId
* @param ancestorNoteId
* @return {Promise< boolean>} - true if ancestorNoteId occurs in at least one of the note's paths
* @return {boolean} - true if ancestorNoteId occurs in at least one of the note's paths
*/
*/
async isDescendantOfNote(ancestorNoteId) {
isDescendantOfNote(ancestorNoteId) {
const notePaths = await this.getAllNotePaths();
const notePaths = this.getAllNotePaths();
return notePaths.some(path => path.includes(ancestorNoteId));
return notePaths.some(path => path.includes(ancestorNoteId));
}
}
@ -941,10 +930,6 @@ class Note extends Entity {
this.utcDateCreated = dateUtils.utcNowDateTime();
this.utcDateCreated = dateUtils.utcNowDateTime();
}
}
if (this.contentLength === undefined) {
this.contentLength = -1;
}
super.beforeSaving();
super.beforeSaving();
if (this.isChanged) {
if (this.isChanged) {
@ -974,7 +959,8 @@ class Note extends Entity {
}
}
}
}
module.exports = Note;< / code > < / pre >
module.exports = Note;
< / code > < / pre >
< / article >
< / article >
< / section >
< / section >
@ -990,7 +976,7 @@ module.exports = Note;</code></pre>
< br class = "clear" >
< br class = "clear" >
< footer >
< footer >
Documentation generated by < a href = "https://github.com/jsdoc/jsdoc" > JSDoc 3.6.4 < / a >
Documentation generated by < a href = "https://github.com/jsdoc/jsdoc" > JSDoc 3.6.6 < / a >
< / footer >
< / footer >
< script > prettyPrint ( ) ; < / script >
< script > prettyPrint ( ) ; < / script >