2020-05-17 09:48:24 +02:00
"use strict" ;
2020-05-17 10:11:19 +02:00
const protectedSessionService = require ( '../../protected_session' ) ;
2021-04-03 21:48:16 +02:00
const log = require ( '../../log' ) ;
2020-05-17 09:48:24 +02:00
2021-04-17 20:52:46 +02:00
const LABEL = 'label' ;
const RELATION = 'relation' ;
2020-05-17 09:48:24 +02:00
class Note {
2021-04-16 23:00:08 +02:00
constructor ( becca , row ) {
/** @param {Becca} */
this . becca = becca ;
2020-09-03 21:37:06 +02:00
this . update ( row ) ;
/** @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-04-16 23:00:08 +02:00
this . becca . notes [ this . noteId ] = this ;
2020-09-03 21:37:06 +02:00
/** @param {Note[]|null} */
this . ancestorCache = null ;
2021-01-22 22:20:17 +01:00
// following attributes are filled during searching from database
/** @param {int} size of the content in bytes */
this . contentSize = null ;
/** @param {int} size of the content and note revision contents in bytes */
this . noteSize = null ;
/** @param {int} number of note revisions for this note */
this . revisionCount = null ;
2020-09-03 21:37:06 +02:00
}
update ( row ) {
2020-05-16 23:12:29 +02:00
/** @param {string} */
this . noteId = row . noteId ;
/** @param {string} */
this . title = row . title ;
2020-05-23 20:52:55 +02:00
/** @param {string} */
this . type = row . type ;
/** @param {string} */
this . mime = row . mime ;
/** @param {string} */
this . dateCreated = row . dateCreated ;
/** @param {string} */
this . dateModified = row . dateModified ;
/** @param {string} */
this . utcDateCreated = row . utcDateCreated ;
/** @param {string} */
this . utcDateModified = row . utcDateModified ;
2020-05-16 23:12:29 +02:00
/** @param {boolean} */
this . isProtected = ! ! row . isProtected ;
/** @param {boolean} */
this . isDecrypted = ! row . isProtected || ! ! row . isContentAvailable ;
2020-09-03 21:37:06 +02:00
this . decrypt ( ) ;
2020-05-16 23:12:29 +02:00
/** @param {string|null} */
this . flatTextCache = null ;
}
/** @return {Attribute[]} */
get attributes ( ) {
2020-06-04 23:58:31 +02:00
return this . _ _getAttributes ( [ ] ) ;
}
_ _getAttributes ( path ) {
if ( path . includes ( this . noteId ) ) {
return [ ] ;
}
2020-05-16 23:12:29 +02:00
if ( ! this . attributeCache ) {
const parentAttributes = this . ownedAttributes . slice ( ) ;
2020-06-04 23:58:31 +02:00
const newPath = [ ... path , this . noteId ] ;
2020-05-16 23:12:29 +02:00
if ( this . noteId !== 'root' ) {
for ( const parentNote of this . parents ) {
2020-06-04 23:58:31 +02:00
parentAttributes . push ( ... parentNote . _ _getInheritableAttributes ( newPath ) ) ;
2020-05-16 23:12:29 +02:00
}
}
const templateAttributes = [ ] ;
for ( const ownedAttr of parentAttributes ) { // parentAttributes so we process also inherited templates
if ( ownedAttr . type === 'relation' && ownedAttr . name === 'template' ) {
2021-04-16 23:00:08 +02:00
const templateNote = this . becca . notes [ ownedAttr . value ] ;
2020-05-16 23:12:29 +02:00
if ( templateNote ) {
2020-06-04 23:58:31 +02:00
templateAttributes . push ( ... templateNote . _ _getAttributes ( newPath ) ) ;
2020-05-16 23:12:29 +02:00
}
}
}
2020-09-30 22:34:18 +02:00
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 ) ;
}
}
2020-05-16 23:12:29 +02:00
this . inheritableAttributeCache = [ ] ;
for ( const attr of this . attributeCache ) {
if ( attr . isInheritable ) {
this . inheritableAttributeCache . push ( attr ) ;
}
}
}
return this . attributeCache ;
}
/** @return {Attribute[]} */
2020-06-04 23:58:31 +02:00
_ _getInheritableAttributes ( path ) {
if ( path . includes ( this . noteId ) ) {
return [ ] ;
}
2020-05-16 23:12:29 +02:00
if ( ! this . inheritableAttributeCache ) {
2020-06-04 23:58:31 +02:00
this . _ _getAttributes ( path ) ; // will refresh also this.inheritableAttributeCache
2020-05-16 23:12:29 +02:00
}
return this . inheritableAttributeCache ;
}
hasAttribute ( type , name ) {
2020-08-20 00:15:08 +02:00
return ! ! this . attributes . find ( attr => attr . type === type && attr . name === name ) ;
2020-05-16 23:12:29 +02:00
}
2020-12-15 15:09:00 +01:00
getAttributeCaseInsensitive ( type , name , value ) {
name = name . toLowerCase ( ) ;
value = value ? value . toLowerCase ( ) : null ;
return this . attributes . find (
attr => attr . type === type
&& attr . name . toLowerCase ( ) === name
&& ( ! value || attr . value . toLowerCase ( ) === value ) ) ;
}
2021-04-17 20:52:46 +02:00
getRelationTarget ( name ) {
const relation = this . attributes . find ( attr => attr . type === 'relation' && attr . name === name ) ;
return relation ? relation . targetNote : null ;
2021-01-23 21:00:59 +01:00
}
2021-04-17 20:52:46 +02:00
/ * *
* @ 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 ) ;
2021-01-23 21:00:59 +01:00
}
2021-04-17 20:52:46 +02:00
/ * *
* @ 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 ( ) ;
2020-05-25 00:25:47 +02:00
2021-04-17 20:52:46 +02:00
return attributes . find ( attr => attr . type === type && attr . name === name ) ;
2020-05-25 00:25:47 +02:00
}
2021-04-17 20:52:46 +02:00
/ * *
* @ 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 ) ;
2020-05-25 00:25:47 +02:00
2021-04-17 20:52:46 +02:00
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 ;
2020-05-25 00:25:47 +02:00
}
2020-05-16 23:12:29 +02:00
get isArchived ( ) {
return this . hasAttribute ( 'label' , 'archived' ) ;
}
get hasInheritableOwnedArchivedLabel ( ) {
return ! ! this . ownedAttributes . find ( attr => attr . type === 'label' && attr . name === 'archived' && attr . isInheritable ) ;
}
2021-02-24 22:38:26 +01:00
// 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
2020-05-16 23:12:29 +02:00
resortParents ( ) {
2021-02-24 22:38:26 +01:00
this . parentBranches . sort ( ( a , b ) =>
a . branchId . startsWith ( 'virt-' )
|| a . parentNote . hasInheritableOwnedArchivedLabel ? 1 : - 1 ) ;
this . parents = this . parentBranches . map ( branch => branch . parentNote ) ;
2020-05-16 23:12:29 +02:00
}
/ * *
2020-09-07 00:05:01 +02:00
* This is used for :
* - fast searching
* - note similarity evaluation
*
* @ return { string } - returns flattened textual representation of note , prefixes and attributes
2020-05-16 23:12:29 +02:00
* /
get flatText ( ) {
if ( ! this . flatTextCache ) {
2020-09-10 21:01:46 +02:00
this . flatTextCache = this . noteId + ' ' + this . type + ' ' + this . mime + ' ' ;
2020-05-16 23:12:29 +02:00
for ( const branch of this . parentBranches ) {
if ( branch . prefix ) {
2020-09-07 00:05:01 +02:00
this . flatTextCache += branch . prefix + ' ' ;
2020-05-16 23:12:29 +02:00
}
}
2020-08-06 23:55:17 +02:00
this . flatTextCache += this . title + ' ' ;
2020-05-16 23:12:29 +02:00
for ( const attr of this . attributes ) {
// it's best to use space as separator since spaces are filtered from the search string by the tokenization into words
2020-09-07 00:05:01 +02:00
this . flatTextCache += ( attr . type === 'label' ? '#' : '~' ) + attr . name ;
2020-05-16 23:12:29 +02:00
if ( attr . value ) {
this . flatTextCache += '=' + attr . value ;
}
2020-09-07 00:05:01 +02:00
this . flatTextCache += ' ' ;
2020-05-16 23:12:29 +02:00
}
this . flatTextCache = this . flatTextCache . toLowerCase ( ) ;
}
return this . flatTextCache ;
}
invalidateThisCache ( ) {
this . flatTextCache = null ;
this . attributeCache = null ;
this . inheritableAttributeCache = null ;
2020-05-23 23:44:55 +02:00
this . ancestorCache = null ;
2020-05-16 23:12:29 +02:00
}
2021-04-17 20:52:46 +02:00
invalidateSubTree ( path = [ ] ) {
2021-03-30 21:39:42 +02:00
if ( path . includes ( this . noteId ) ) {
return ;
}
2020-05-16 23:12:29 +02:00
this . invalidateThisCache ( ) ;
2021-03-30 21:39:42 +02:00
if ( this . children . length || this . targetRelations . length ) {
path = [ ... path , this . noteId ] ;
}
2020-05-16 23:12:29 +02:00
for ( const childNote of this . children ) {
2021-04-17 20:52:46 +02:00
childNote . invalidateSubTree ( path ) ;
2020-05-16 23:12:29 +02:00
}
for ( const targetRelation of this . targetRelations ) {
if ( targetRelation . name === 'template' ) {
const note = targetRelation . note ;
if ( note ) {
2021-04-17 20:52:46 +02:00
note . invalidateSubTree ( path ) ;
2020-05-16 23:12:29 +02:00
}
}
}
}
invalidateSubtreeFlatText ( ) {
this . flatTextCache = null ;
for ( const childNote of this . children ) {
childNote . invalidateSubtreeFlatText ( ) ;
}
for ( const targetRelation of this . targetRelations ) {
if ( targetRelation . name === 'template' ) {
const note = targetRelation . note ;
if ( note ) {
note . invalidateSubtreeFlatText ( ) ;
}
}
}
}
get isTemplate ( ) {
return ! ! this . targetRelations . find ( rel => rel . name === 'template' ) ;
}
/** @return {Note[]} */
get subtreeNotesIncludingTemplated ( ) {
const arr = [ [ this ] ] ;
for ( const childNote of this . children ) {
arr . push ( childNote . subtreeNotesIncludingTemplated ) ;
}
for ( const targetRelation of this . targetRelations ) {
if ( targetRelation . name === 'template' ) {
const note = targetRelation . note ;
if ( note ) {
arr . push ( note . subtreeNotesIncludingTemplated ) ;
}
}
}
return arr . flat ( ) ;
}
/** @return {Note[]} */
get subtreeNotes ( ) {
const arr = [ [ this ] ] ;
for ( const childNote of this . children ) {
arr . push ( childNote . subtreeNotes ) ;
}
return arr . flat ( ) ;
}
2021-01-23 21:00:59 +01:00
/** @return {String[]} */
get subtreeNoteIds ( ) {
return this . subtreeNotes . map ( note => note . noteId ) ;
}
2020-05-23 20:52:55 +02:00
get parentCount ( ) {
return this . parents . length ;
}
get childrenCount ( ) {
return this . children . length ;
}
get labelCount ( ) {
return this . attributes . filter ( attr => attr . type === 'label' ) . length ;
}
2021-02-19 20:42:34 +01:00
get ownedLabelCount ( ) {
return this . ownedAttributes . filter ( attr => attr . type === 'label' ) . length ;
}
2020-05-23 20:52:55 +02:00
get relationCount ( ) {
2021-02-19 20:42:34 +01:00
return this . attributes . filter ( attr => attr . type === 'relation' && ! attr . isAutoLink ( ) ) . length ;
}
get relationCountIncludingLinks ( ) {
2020-05-23 20:52:55 +02:00
return this . attributes . filter ( attr => attr . type === 'relation' ) . length ;
}
2021-02-19 20:42:34 +01:00
get ownedRelationCount ( ) {
return this . ownedAttributes . filter ( attr => attr . type === 'relation' && ! attr . isAutoLink ( ) ) . length ;
}
get ownedRelationCountIncludingLinks ( ) {
return this . ownedAttributes . filter ( attr => attr . type === 'relation' ) . length ;
}
get targetRelationCount ( ) {
return this . targetRelations . filter ( attr => ! attr . isAutoLink ( ) ) . length ;
}
get targetRelationCountIncludingLinks ( ) {
return this . targetRelations . length ;
}
2020-05-23 20:52:55 +02:00
get attributeCount ( ) {
return this . attributes . length ;
}
2021-02-19 20:42:34 +01:00
get ownedAttributeCount ( ) {
return this . attributes . length ;
}
2020-05-23 23:44:55 +02:00
get ancestors ( ) {
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 . ancestors ) {
if ( ! noteIds . has ( ancestorNote . noteId ) ) {
this . ancestorCache . push ( ancestorNote ) ;
noteIds . add ( ancestorNote . noteId ) ;
}
}
}
}
return this . ancestorCache ;
}
2020-05-16 23:12:29 +02:00
/ * * @ 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 * /
get templatedNotes ( ) {
const arr = [ this ] ;
for ( const targetRelation of this . targetRelations ) {
if ( targetRelation . name === 'template' ) {
const note = targetRelation . note ;
if ( note ) {
arr . push ( note ) ;
}
}
}
return arr ;
}
2020-05-17 09:48:24 +02:00
2021-01-26 23:25:18 +01:00
getDistanceToAncestor ( ancestorNoteId ) {
if ( this . noteId === ancestorNoteId ) {
return 0 ;
}
2021-03-15 20:31:12 +01:00
let minDistance = 999999 ;
2021-01-26 23:25:18 +01:00
for ( const parent of this . parents ) {
minDistance = Math . min ( minDistance , parent . getDistanceToAncestor ( ancestorNoteId ) + 1 ) ;
}
return minDistance ;
}
2021-04-17 20:52:46 +02:00
getChildBranches ( ) {
return this . children . map ( childNote => this . becca . getBranch ( childNote . noteId , this . noteId ) ) ;
}
2020-05-17 09:48:24 +02:00
decrypt ( ) {
if ( this . isProtected && ! this . isDecrypted && protectedSessionService . isProtectedSessionAvailable ( ) ) {
2021-04-03 21:48:16 +02:00
try {
this . title = protectedSessionService . decryptString ( this . title ) ;
2020-05-17 09:48:24 +02:00
2021-04-03 21:48:16 +02:00
this . isDecrypted = true ;
}
catch ( e ) {
log . error ( ` Could not decrypt note ${ this . noteId } : ${ e . message } ${ e . stack } ` ) ;
}
2020-05-17 09:48:24 +02:00
}
}
2020-09-17 14:34:10 +02:00
// for logging etc
get pojo ( ) {
const pojo = { ... this } ;
2021-04-16 23:00:08 +02:00
delete pojo . becca ;
2020-09-17 14:34:10 +02:00
delete pojo . ancestorCache ;
delete pojo . attributeCache ;
delete pojo . flatTextCache ;
2020-09-18 21:47:59 +02:00
delete pojo . children ;
delete pojo . parents ;
delete pojo . parentBranches ;
2020-09-17 14:34:10 +02:00
return pojo ;
}
2020-05-16 23:12:29 +02:00
}
2020-05-17 09:48:24 +02:00
module . exports = Note ;