diff --git a/src/entities/note.js b/src/entities/note.js index 859560940..45cc40d89 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -369,6 +369,16 @@ class Note extends Entity { */ async getRelationValue(name) { return await this.getAttributeValue(RELATION, name); } + /** + * @param {string} name + * @returns {Promise|null} target note of the relation or null (if target is empty or note was not found) + */ + async getRelationTarget(name) { + const relation = await this.getRelation(name); + + return relation ? await repository.getNote(relation.value) : null; + } + /** * Based on enabled, label is either set or removed. * @@ -425,16 +435,6 @@ class Note extends Entity { */ async removeRelation(name, value) { return await this.removeAttribute(RELATION, name, value); } - /** - * @param {string} name - * @returns {Promise|null} target note of the relation or null (if target is empty or note was not found) - */ - async getRelationTarget(name) { - const relation = await this.getRelation(name); - - return relation ? await repository.getNote(relation.value) : null; - } - /** * @return {Promise} 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 */ diff --git a/src/public/javascripts/entities/attribute.js b/src/public/javascripts/entities/attribute.js new file mode 100644 index 000000000..9cc7c361e --- /dev/null +++ b/src/public/javascripts/entities/attribute.js @@ -0,0 +1,34 @@ +class Attribute { + constructor(treeCache, row) { + this.treeCache = treeCache; + /** @param {string} attributeId */ + this.attributeId = row.attributeId; + /** @param {string} noteId */ + this.noteId = row.noteId; + /** @param {string} type */ + this.type = row.type; + /** @param {string} name */ + this.name = row.name; + /** @param {string} value */ + this.value = row.value; + /** @param {int} position */ + this.position = row.position; + /** @param {boolean} isInheritable */ + this.isInheritable = row.isInheritable; + /** @param {boolean} isDeleted */ + this.isDeleted = row.isDeleted; + /** @param {string} dateCreated */ + this.dateCreated = row.dateCreated; + /** @param {string} dateModified */ + this.dateModified = row.dateModified; + } + + /** @returns {NoteShort} */ + async getNote() { + return await this.treeCache.getNote(this.noteId); + } + + get toString() { + return `Attribute(attributeId=${this.attributeId}, type=${this.type}, name=${this.name})`; + } +} \ No newline at end of file diff --git a/src/public/javascripts/entities/note_short.js b/src/public/javascripts/entities/note_short.js index 65cd4d41a..bdb53af31 100644 --- a/src/public/javascripts/entities/note_short.js +++ b/src/public/javascripts/entities/note_short.js @@ -1,3 +1,10 @@ +import server from '../services/server.js'; + +const LABEL = 'label'; +const LABEL_DEFINITION = 'label-definition'; +const RELATION = 'relation'; +const RELATION_DEFINITION = 'relation-definition'; + /** * This note's representation is used in note tree and is kept in TreeCache. * Its notable omission is the note content. @@ -71,6 +78,140 @@ class NoteShort { return await this.treeCache.getNotes(this.getChildNoteIds()); } + /** + * @param {string} [name] - attribute name to filter + * @returns {Promise} + */ + async getAttributes(name) { + if (!this.attributeCache) { + this.attributeCache = await server.get('notes/' + this.noteId + '/attributes'); + } + + if (name) { + return this.attributeCache.filter(attr => attr.name === name); + } + else { + return this.attributeCache; + } + } + + /** + * @param {string} [name] - label name to filter + * @returns {Promise} all note's labels (attributes with type label), including inherited ones + */ + async getLabels(name) { + return (await this.getAttributes(name)).filter(attr => attr.type === LABEL); + } + + /** + * @param {string} [name] - label name to filter + * @returns {Promise} all note's label definitions, including inherited ones + */ + async getLabelDefinitions(name) { + return (await this.getAttributes(name)).filter(attr => attr.type === LABEL_DEFINITION); + } + + /** + * @param {string} [name] - relation name to filter + * @returns {Promise} all note's relations (attributes with type relation), including inherited ones + */ + async getRelations(name) { + return (await this.getAttributes(name)).filter(attr => attr.type === RELATION); + } + + /** + * @param {string} [name] - relation name to filter + * @returns {Promise} all note's relation definitions including inherited ones + */ + async getRelationDefinitions(name) { + return (await this.getAttributes(name)).filter(attr => attr.type === RELATION_DEFINITION); + } + + /** + * @param {string} type - attribute type (label, relation, etc.) + * @param {string} name - attribute name + * @returns {Promise} true if note has an attribute with given type and name (including inherited) + */ + async hasAttribute(type, name) { + return !!await this.getAttribute(type, name); + } + + /** + * @param {string} type - attribute type (label, relation, etc.) + * @param {string} name - attribute name + * @returns {Promise} 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) { + const attributes = await this.getAttributes(); + + return attributes.find(attr => attr.type === type && attr.name === name); + } + + /** + * @param {string} type - attribute type (label, relation, etc.) + * @param {string} name - attribute name + * @returns {Promise} attribute value of given type and name or null if no such attribute exists. + */ + async getAttributeValue(type, name) { + const attr = await this.getAttribute(type, name); + + return attr ? attr.value : null; + } + + /** + * @param {string} name - label name + * @returns {Promise} true if label exists (including inherited) + */ + async hasLabel(name) { return await this.hasAttribute(LABEL, name); } + + /** + * @param {string} name - relation name + * @returns {Promise} true if relation exists (including inherited) + */ + async hasRelation(name) { return await this.hasAttribute(RELATION, name); } + + /** + * @param {string} name - label name + * @returns {Promise} label if it exists, null otherwise + */ + async getLabel(name) { return await this.getAttribute(LABEL, name); } + + /** + * @param {string} name - relation name + * @returns {Promise} relation if it exists, null otherwise + */ + async getRelation(name) { return await this.getAttribute(RELATION, name); } + + /** + * @param {string} name - label name + * @returns {Promise} label value if label exists, null otherwise + */ + async getLabelValue(name) { return await this.getAttributeValue(LABEL, name); } + + /** + * @param {string} name - relation name + * @returns {Promise} relation value if relation exists, null otherwise + */ + async getRelationValue(name) { return await this.getAttributeValue(RELATION, name); } + + /** + * @param {string} name + * @returns {Promise|null} target note of the relation or null (if target is empty or note was not found) + */ + async getRelationTarget(name) { + const relation = await this.getRelation(name); + + return relation ? await repository.getNote(relation.value) : null; + } + + /** + * Clear note's attributes cache to force fresh reload for next attribute request. + * Cache is note instance scoped. + */ + invalidateAttributeCache() { + this.attributeCache = null; + } + get toString() { return `Note(noteId=${this.noteId}, title=${this.title})`; } @@ -79,6 +220,7 @@ class NoteShort { const dto = Object.assign({}, this); delete dto.treeCache; delete dto.archived; + delete dto.attributeCache; return dto; }