2021-10-16 22:13:34 +02:00
"use strict" ;
2021-10-17 14:44:59 +02:00
const sql = require ( '../../sql' ) ;
const utils = require ( '../../../services/utils' ) ;
const AbstractEntity = require ( './abstract_entity' ) ;
2021-10-16 22:13:34 +02:00
const LABEL = 'label' ;
const RELATION = 'relation' ;
2021-10-17 14:44:59 +02:00
class Note extends AbstractEntity {
constructor ( [ noteId , title , type , mime ] ) {
super ( ) ;
2021-10-16 22:13:34 +02:00
/** @param {string} */
this . noteId = noteId ;
/** @param {string} */
this . title = title ;
/** @param {string} */
this . type = type ;
/** @param {string} */
this . mime = mime ;
/** @param {Branch[]} */
this . parentBranches = [ ] ;
/** @param {Note[]} */
this . parents = [ ] ;
/** @param {Note[]} */
this . children = [ ] ;
/** @param {Attribute[]} */
this . ownedAttributes = [ ] ;
/** @param {Attribute[]|null} */
this . _ _attributeCache = null ;
/** @param {Attribute[]|null} */
this . inheritableAttributeCache = null ;
/** @param {Attribute[]} */
this . targetRelations = [ ] ;
2021-10-17 14:44:59 +02:00
this . shaca . notes [ this . noteId ] = this ;
2021-10-16 22:13:34 +02:00
/** @param {Note[]|null} */
this . ancestorCache = null ;
}
getParentBranches ( ) {
return this . parentBranches ;
}
getBranches ( ) {
return this . parentBranches ;
}
getParentNotes ( ) {
return this . parents ;
}
getChildNotes ( ) {
return this . children ;
}
hasChildren ( ) {
return this . children && this . children . length > 0 ;
}
getChildBranches ( ) {
2021-10-17 14:44:59 +02:00
return this . children . map ( childNote => this . shaca . getBranchFromChildAndParent ( childNote . noteId , this . noteId ) ) ;
2021-10-16 22:13:34 +02:00
}
getContent ( silentNotFoundError = false ) {
const row = sql . getRow ( ` SELECT content FROM note_contents WHERE noteId = ? ` , [ this . noteId ] ) ;
if ( ! row ) {
if ( silentNotFoundError ) {
return undefined ;
}
else {
throw new Error ( "Cannot find note content for noteId=" + this . noteId ) ;
}
}
let content = row . content ;
if ( this . isStringNote ( ) ) {
return content === null
? ""
: content . toString ( "UTF-8" ) ;
}
else {
return content ;
}
}
/** @returns {*} */
getJsonContent ( ) {
const content = this . getContent ( ) ;
if ( ! content || ! content . trim ( ) ) {
return null ;
}
return JSON . parse ( content ) ;
}
/** @returns {boolean} true if this note is of application/json content type */
isJson ( ) {
return this . mime === "application/json" ;
}
/** @returns {boolean} true if this note is JavaScript (code or attachment) */
isJavaScript ( ) {
return ( this . type === "code" || this . type === "file" )
&& ( this . mime . startsWith ( "application/javascript" )
|| this . mime === "application/x-javascript"
|| this . mime === "text/javascript" ) ;
}
/** @returns {boolean} true if this note is HTML */
isHtml ( ) {
return [ "code" , "file" , "render" ] . includes ( this . type )
&& this . mime === "text/html" ;
}
/** @returns {boolean} true if the note has string content (not binary) */
isStringNote ( ) {
return utils . isStringNote ( this . type , this . mime ) ;
}
/ * *
* @ param { string } [ type ] - ( optional ) attribute type to filter
* @ param { string } [ name ] - ( optional ) attribute name to filter
* @ returns { Attribute [ ] } all note ' s attributes , including inherited ones
* /
getAttributes ( type , name ) {
this . _ _getAttributes ( [ ] ) ;
if ( type && name ) {
return this . _ _attributeCache . filter ( attr => attr . type === type && attr . name === name ) ;
}
else if ( type ) {
return this . _ _attributeCache . filter ( attr => attr . type === type ) ;
}
else if ( name ) {
return this . _ _attributeCache . filter ( attr => attr . name === name ) ;
}
else {
return this . _ _attributeCache . slice ( ) ;
}
}
_ _getAttributes ( path ) {
if ( path . includes ( this . noteId ) ) {
return [ ] ;
}
if ( ! this . _ _attributeCache ) {
const parentAttributes = this . ownedAttributes . slice ( ) ;
const newPath = [ ... path , this . noteId ] ;
if ( this . noteId !== 'root' ) {
for ( const parentNote of this . parents ) {
parentAttributes . push ( ... parentNote . _ _getInheritableAttributes ( newPath ) ) ;
}
}
const templateAttributes = [ ] ;
for ( const ownedAttr of parentAttributes ) { // parentAttributes so we process also inherited templates
if ( ownedAttr . type === 'relation' && ownedAttr . name === 'template' ) {
2021-10-17 14:44:59 +02:00
const templateNote = this . shaca . notes [ ownedAttr . value ] ;
2021-10-16 22:13:34 +02:00
if ( templateNote ) {
templateAttributes . push ( ... templateNote . _ _getAttributes ( newPath ) ) ;
}
}
}
this . _ _attributeCache = [ ] ;
const addedAttributeIds = new Set ( ) ;
for ( const attr of parentAttributes . concat ( templateAttributes ) ) {
if ( ! addedAttributeIds . has ( attr . attributeId ) ) {
addedAttributeIds . add ( attr . attributeId ) ;
this . _ _attributeCache . push ( attr ) ;
}
}
this . inheritableAttributeCache = [ ] ;
for ( const attr of this . _ _attributeCache ) {
if ( attr . isInheritable ) {
this . inheritableAttributeCache . push ( attr ) ;
}
}
}
return this . _ _attributeCache ;
}
/** @return {Attribute[]} */
_ _getInheritableAttributes ( path ) {
if ( path . includes ( this . noteId ) ) {
return [ ] ;
}
if ( ! this . inheritableAttributeCache ) {
this . _ _getAttributes ( path ) ; // will refresh also this.inheritableAttributeCache
}
return this . inheritableAttributeCache ;
}
hasAttribute ( type , name ) {
return ! ! this . getAttributes ( ) . find ( attr => attr . type === type && attr . name === name ) ;
}
getAttributeCaseInsensitive ( type , name , value ) {
name = name . toLowerCase ( ) ;
value = value ? value . toLowerCase ( ) : null ;
return this . getAttributes ( ) . find (
attr => attr . type === type
&& attr . name . toLowerCase ( ) === name
&& ( ! value || attr . value . toLowerCase ( ) === value ) ) ;
}
getRelationTarget ( name ) {
const relation = this . getAttributes ( ) . find ( attr => attr . type === 'relation' && attr . name === name ) ;
return relation ? relation . targetNote : null ;
}
/ * *
* @ param { string } name - label name
* @ returns { boolean } true if label exists ( including inherited )
* /
hasLabel ( name ) { return this . hasAttribute ( LABEL , name ) ; }
/ * *
* @ param { string } name - label name
* @ returns { boolean } true if label exists ( excluding inherited )
* /
hasOwnedLabel ( name ) { return this . hasOwnedAttribute ( LABEL , name ) ; }
/ * *
* @ param { string } name - relation name
* @ returns { boolean } true if relation exists ( including inherited )
* /
hasRelation ( name ) { return this . hasAttribute ( RELATION , name ) ; }
/ * *
* @ param { string } name - relation name
* @ returns { boolean } true if relation exists ( excluding inherited )
* /
hasOwnedRelation ( name ) { return this . hasOwnedAttribute ( RELATION , name ) ; }
/ * *
* @ param { string } name - label name
* @ returns { Attribute | null } label if it exists , null otherwise
* /
getLabel ( name ) { return this . getAttribute ( LABEL , name ) ; }
/ * *
* @ param { string } name - label name
* @ returns { Attribute | null } label if it exists , null otherwise
* /
getOwnedLabel ( name ) { return this . getOwnedAttribute ( LABEL , name ) ; }
/ * *
* @ param { string } name - relation name
* @ returns { Attribute | null } relation if it exists , null otherwise
* /
getRelation ( name ) { return this . getAttribute ( RELATION , name ) ; }
/ * *
* @ param { string } name - relation name
* @ returns { Attribute | null } relation if it exists , null otherwise
* /
getOwnedRelation ( name ) { return this . getOwnedAttribute ( RELATION , name ) ; }
/ * *
* @ param { string } name - label name
* @ returns { string | null } label value if label exists , null otherwise
* /
getLabelValue ( name ) { return this . getAttributeValue ( LABEL , name ) ; }
/ * *
* @ param { string } name - label name
* @ returns { string | null } label value if label exists , null otherwise
* /
getOwnedLabelValue ( name ) { return this . getOwnedAttributeValue ( LABEL , name ) ; }
/ * *
* @ param { string } name - relation name
* @ returns { string | null } relation value if relation exists , null otherwise
* /
getRelationValue ( name ) { return this . getAttributeValue ( RELATION , name ) ; }
/ * *
* @ param { string } name - relation name
* @ returns { string | null } relation value if relation exists , null otherwise
* /
getOwnedRelationValue ( name ) { return this . getOwnedAttributeValue ( RELATION , name ) ; }
/ * *
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ returns { boolean } true if note has an attribute with given type and name ( excluding inherited )
* /
hasOwnedAttribute ( type , name ) {
return ! ! this . getOwnedAttribute ( type , name ) ;
}
/ * *
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ 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 .
* /
getAttribute ( type , name ) {
const attributes = 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 { string | null } attribute value of given type and name or null if no such attribute exists .
* /
getAttributeValue ( type , name ) {
const attr = this . getAttribute ( type , name ) ;
return attr ? attr . value : null ;
}
/ * *
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ returns { string | null } attribute value of given type and name or null if no such attribute exists .
* /
getOwnedAttributeValue ( type , name ) {
const attr = this . getOwnedAttribute ( type , name ) ;
return attr ? attr . value : null ;
}
/ * *
* @ param { string } [ name ] - label name to filter
* @ returns { Attribute [ ] } all note ' s labels ( attributes with type label ) , including inherited ones
* /
getLabels ( name ) {
return this . getAttributes ( LABEL , name ) ;
}
/ * *
* @ param { string } [ name ] - label name to filter
* @ returns { string [ ] } all note ' s label values , including inherited ones
* /
getLabelValues ( name ) {
return this . getLabels ( name ) . map ( l => l . value ) ;
}
/ * *
* @ param { string } [ name ] - label name to filter
* @ returns { Attribute [ ] } all note ' s labels ( attributes with type label ) , excluding inherited ones
* /
getOwnedLabels ( name ) {
return this . getOwnedAttributes ( LABEL , name ) ;
}
/ * *
* @ param { string } [ name ] - label name to filter
* @ returns { string [ ] } all note ' s label values , excluding inherited ones
* /
getOwnedLabelValues ( name ) {
return this . getOwnedAttributes ( LABEL , name ) . map ( l => l . value ) ;
}
/ * *
* @ param { string } [ name ] - relation name to filter
* @ returns { Attribute [ ] } all note ' s relations ( attributes with type relation ) , including inherited ones
* /
getRelations ( name ) {
return this . getAttributes ( RELATION , name ) ;
}
/ * *
* @ param { string } [ name ] - relation name to filter
* @ returns { Attribute [ ] } all note ' s relations ( attributes with type relation ) , excluding inherited ones
* /
getOwnedRelations ( name ) {
return this . getOwnedAttributes ( RELATION , name ) ;
}
/ * *
* @ param { string } [ type ] - ( optional ) attribute type to filter
* @ param { string } [ name ] - ( optional ) attribute name to filter
* @ returns { Attribute [ ] } note ' s "owned" attributes - excluding inherited ones
* /
getOwnedAttributes ( type , name ) {
// it's a common mistake to include # or ~ into attribute name
if ( name && [ "#" , "~" ] . includes ( name [ 0 ] ) ) {
name = name . substr ( 1 ) ;
}
if ( type && name ) {
return this . ownedAttributes . filter ( attr => attr . type === type && attr . name === name ) ;
}
else if ( type ) {
return this . ownedAttributes . filter ( attr => attr . type === type ) ;
}
else if ( name ) {
return this . ownedAttributes . filter ( attr => attr . name === name ) ;
}
else {
return this . ownedAttributes . slice ( ) ;
}
}
/ * *
* @ returns { Attribute } attribute belonging to this specific note ( excludes inherited attributes )
*
* This method can be significantly faster than the getAttribute ( )
* /
getOwnedAttribute ( type , name ) {
const attrs = this . getOwnedAttributes ( type , name ) ;
return attrs . length > 0 ? attrs [ 0 ] : null ;
}
get isArchived ( ) {
return this . hasAttribute ( 'label' , 'archived' ) ;
}
hasInheritableOwnedArchivedLabel ( ) {
return ! ! this . ownedAttributes . find ( attr => attr . type === 'label' && attr . name === 'archived' && attr . isInheritable ) ;
}
// will sort the parents so that non-search & non-archived are first and archived at the end
// this is done so that non-search & non-archived paths are always explored as first when looking for note path
resortParents ( ) {
this . parentBranches . sort ( ( a , b ) =>
a . branchId . startsWith ( 'virt-' )
|| a . parentNote . hasInheritableOwnedArchivedLabel ( ) ? 1 : - 1 ) ;
this . parents = this . parentBranches . map ( branch => branch . parentNote ) ;
}
isTemplate ( ) {
return ! ! this . targetRelations . find ( rel => rel . name === 'template' ) ;
}
/** @return {Note[]} */
getSubtreeNotesIncludingTemplated ( ) {
const arr = [ [ this ] ] ;
for ( const childNote of this . children ) {
arr . push ( childNote . getSubtreeNotesIncludingTemplated ( ) ) ;
}
for ( const targetRelation of this . targetRelations ) {
if ( targetRelation . name === 'template' ) {
const note = targetRelation . note ;
if ( note ) {
arr . push ( note . getSubtreeNotesIncludingTemplated ( ) ) ;
}
}
}
return arr . flat ( ) ;
}
/** @return {Note[]} */
getSubtreeNotes ( includeArchived = true ) {
const noteSet = new Set ( ) ;
function addSubtreeNotesInner ( note ) {
if ( ! includeArchived && note . isArchived ) {
return ;
}
noteSet . add ( note ) ;
for ( const childNote of note . children ) {
addSubtreeNotesInner ( childNote ) ;
}
}
addSubtreeNotesInner ( this ) ;
return Array . from ( noteSet ) ;
}
/** @return {String[]} */
getSubtreeNoteIds ( ) {
return this . getSubtreeNotes ( ) . map ( note => note . noteId ) ;
}
getDescendantNoteIds ( ) {
return this . getSubtreeNoteIds ( ) ;
}
getAncestors ( ) {
if ( ! this . ancestorCache ) {
const noteIds = new Set ( ) ;
this . ancestorCache = [ ] ;
for ( const parent of this . parents ) {
if ( ! noteIds . has ( parent . noteId ) ) {
this . ancestorCache . push ( parent ) ;
noteIds . add ( parent . noteId ) ;
}
for ( const ancestorNote of parent . getAncestors ( ) ) {
if ( ! noteIds . has ( ancestorNote . noteId ) ) {
this . ancestorCache . push ( ancestorNote ) ;
noteIds . add ( ancestorNote . noteId ) ;
}
}
}
}
return this . ancestorCache ;
}
getTargetRelations ( ) {
return this . targetRelations ;
}
/ * * @ r e t u r n { N o t e [ ] } - r e t u r n s o n l y n o t e s w h i c h a r e t e m p l a t e d , d o e s n o t i n c l u d e t h e i r s u b t r e e s
* in effect returns notes which are influenced by note ' s non - inheritable attributes * /
getTemplatedNotes ( ) {
const arr = [ this ] ;
for ( const targetRelation of this . targetRelations ) {
if ( targetRelation . name === 'template' ) {
const note = targetRelation . note ;
if ( note ) {
arr . push ( note ) ;
}
}
}
return arr ;
}
/ * *
* @ param ancestorNoteId
* @ return { boolean } - true if ancestorNoteId occurs in at least one of the note ' s paths
* /
isDescendantOfNote ( ancestorNoteId ) {
const notePaths = this . getAllNotePaths ( ) ;
return notePaths . some ( path => path . includes ( ancestorNoteId ) ) ;
}
}
module . exports = Note ;