2020-05-17 09:48:24 +02:00
"use strict" ;
2021-05-17 22:09:49 +02:00
const protectedSessionService = require ( '../../services/protected_session' ) ;
const log = require ( '../../services/log' ) ;
const sql = require ( '../../services/sql' ) ;
const utils = require ( '../../services/utils' ) ;
const dateUtils = require ( '../../services/date_utils' ) ;
const entityChangesService = require ( '../../services/entity_changes' ) ;
2023-01-03 13:52:37 +01:00
const AbstractBeccaEntity = require ( "./abstract_becca_entity" ) ;
const BNoteRevision = require ( "./bnote_revision" ) ;
2023-01-22 23:36:05 +01:00
const BNoteAttachment = require ( "./bnote_attachment" ) ;
2022-06-05 14:58:19 +02:00
const TaskContext = require ( "../../services/task_context" ) ;
2022-06-12 23:29:11 +02:00
const dayjs = require ( "dayjs" ) ;
2022-11-05 22:32:50 +01:00
const utc = require ( 'dayjs/plugin/utc' ) ;
2023-01-14 12:57:50 +01:00
const eventService = require ( "../../services/events" ) ;
2023-01-22 23:36:05 +01:00
dayjs . extend ( utc ) ;
2020-05-17 09:48:24 +02:00
2021-04-17 20:52:46 +02:00
const LABEL = 'label' ;
const RELATION = 'relation' ;
2021-11-10 21:30:54 +01:00
/ * *
* Trilium ' s main entity which can represent text note , image , code note , file attachment etc .
2022-04-16 00:17:32 +02:00
*
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 BNote extends AbstractBeccaEntity {
2021-04-25 20:00:42 +02:00
static get entityName ( ) { return "notes" ; }
static get primaryKeyName ( ) { return "noteId" ; }
static get hashedProperties ( ) { return [ "noteId" , "title" , "isProtected" , "type" , "mime" ] ; }
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 . noteId ,
row . title ,
row . type ,
row . mime ,
row . isProtected ,
row . dateCreated ,
row . dateModified ,
row . utcDateCreated ,
row . utcDateModified
] ) ;
}
update ( [ noteId , title , type , mime , isProtected , dateCreated , dateModified , utcDateCreated , utcDateModified ] ) {
// ------ Database persisted attributes ------
2021-10-29 21:36:23 +02:00
/** @type {string} */
2021-07-24 21:10:16 +02:00
this . noteId = noteId ;
2021-10-29 21:36:23 +02:00
/** @type {string} */
2021-07-24 21:10:16 +02:00
this . title = title ;
2021-10-29 21:36:23 +02:00
/** @type {boolean} */
2021-07-24 21:10:16 +02:00
this . isProtected = ! ! isProtected ;
2021-10-29 21:36:23 +02:00
/** @type {string} */
2021-07-24 21:10:16 +02:00
this . type = type ;
2021-10-29 21:36:23 +02:00
/** @type {string} */
2021-07-24 21:10:16 +02:00
this . mime = mime ;
2021-10-29 21:36:23 +02:00
/** @type {string} */
2021-07-24 21:10:16 +02:00
this . dateCreated = dateCreated || dateUtils . localNowDateTime ( ) ;
2021-10-29 21:36:23 +02:00
/** @type {string} */
2021-07-24 21:10:16 +02:00
this . dateModified = dateModified ;
2021-10-29 21:36:23 +02:00
/** @type {string} */
2021-07-24 21:10:16 +02:00
this . utcDateCreated = utcDateCreated || dateUtils . utcNowDateTime ( ) ;
2021-10-29 21:36:23 +02:00
/** @type {string} */
2021-07-24 21:10:16 +02:00
this . utcDateModified = utcDateModified ;
2022-12-06 16:11:43 +01:00
/** @type {boolean} - set during the deletion operation, before it is completed (removed from becca completely) */
this . isBeingDeleted = false ;
2021-07-24 21:10:16 +02:00
// ------ Derived attributes ------
2021-10-29 21:36:23 +02:00
/** @type {boolean} */
2021-10-28 22:02:18 +02:00
this . isDecrypted = ! this . noteId || ! this . isProtected ;
2021-07-24 21:10:16 +02:00
this . decrypt ( ) ;
2021-10-29 21:36:23 +02:00
/** @type {string|null} */
2021-07-24 21:10:16 +02:00
this . flatTextCache = null ;
return this ;
}
2020-09-03 21:37:06 +02:00
2021-07-24 21:10:16 +02:00
init ( ) {
2023-01-03 14:31:46 +01:00
/ * * @ t y p e { B B r a n c h [ ] }
2022-06-12 23:29:11 +02:00
* @ private * /
2020-09-03 21:37:06 +02:00
this . parentBranches = [ ] ;
2023-01-03 13:52:37 +01:00
/ * * @ t y p e { B N o t e [ ] }
2022-06-12 23:29:11 +02:00
* @ private * /
2020-09-03 21:37:06 +02:00
this . parents = [ ] ;
2023-01-03 13:52:37 +01:00
/ * * @ t y p e { B N o t e [ ] }
2022-06-12 23:29:11 +02:00
* @ private * /
2020-09-03 21:37:06 +02:00
this . children = [ ] ;
2023-01-05 23:38:41 +01:00
/ * * @ t y p e { B A t t r i b u t e [ ] }
2022-06-12 23:29:11 +02:00
* @ private * /
2020-09-03 21:37:06 +02:00
this . ownedAttributes = [ ] ;
2023-01-05 23:38:41 +01:00
/ * * @ t y p e { B A t t r i b u t e [ ] | n u l l }
2021-11-10 21:30:54 +01:00
* @ private * /
2021-04-25 21:19:18 +02:00
this . _ _attributeCache = null ;
2023-01-05 23:38:41 +01:00
/ * * @ t y p e { B A t t r i b u t e [ ] | n u l l }
2021-11-10 21:30:54 +01:00
* @ private * /
2020-09-03 21:37:06 +02:00
this . inheritableAttributeCache = null ;
2023-01-05 23:38:41 +01:00
/ * * @ t y p e { B A t t r i b u t e [ ] }
2022-06-12 23:29:11 +02:00
* @ private * /
2020-09-03 21:37:06 +02:00
this . targetRelations = [ ] ;
2021-10-06 19:59:29 +02:00
this . becca . addNote ( this . noteId , this ) ;
2020-09-03 21:37:06 +02:00
2023-01-03 13:52:37 +01:00
/ * * @ t y p e { B N o t e [ ] | n u l l }
2021-11-10 21:30:54 +01:00
* @ private * /
2020-09-03 21:37:06 +02:00
this . ancestorCache = null ;
2021-01-22 22:20:17 +01:00
// following attributes are filled during searching from database
2021-11-10 21:30:54 +01:00
/ * *
* size of the content in bytes
* @ type { int | null }
2022-06-12 23:29:11 +02:00
* @ private
2021-11-10 21:30:54 +01:00
* /
2021-01-22 22:20:17 +01:00
this . contentSize = null ;
2021-11-10 21:30:54 +01:00
/ * *
* size of the content and note revision contents in bytes
* @ type { int | null }
2022-06-12 23:29:11 +02:00
* @ private
2021-11-10 21:30:54 +01:00
* /
2021-01-22 22:20:17 +01:00
this . noteSize = null ;
2021-11-10 21:30:54 +01:00
/ * *
* number of note revisions for this note
* @ type { int | null }
2022-06-12 23:29:11 +02:00
* @ private
2021-11-10 21:30:54 +01:00
* /
2021-01-22 22:20:17 +01:00
this . revisionCount = null ;
2020-09-03 21:37:06 +02:00
}
2021-05-17 22:35:36 +02:00
isContentAvailable ( ) {
2021-05-09 11:12:53 +02:00
return ! this . noteId // new note which was not encrypted yet
|| ! this . isProtected
|| protectedSessionService . isProtectedSessionAvailable ( )
}
2022-01-08 21:50:16 +01:00
getTitleOrProtected ( ) {
return this . isContentAvailable ( ) ? this . title : '[protected]' ;
}
2023-01-03 14:31:46 +01:00
/** @returns {BBranch[]} */
2021-04-25 21:19:18 +02:00
getParentBranches ( ) {
return this . parentBranches ;
}
2022-12-04 13:16:05 +01:00
/ * *
* Returns < i > strong < /i> (as opposed to <i>weak</i > ) parent branches . See isWeak for details .
*
2023-01-03 14:31:46 +01:00
* @ returns { BBranch [ ] }
2022-12-04 13:16:05 +01:00
* /
getStrongParentBranches ( ) {
return this . getParentBranches ( ) . filter ( branch => ! branch . isWeak ) ;
}
2021-12-20 17:30:47 +01:00
/ * *
2023-01-03 14:31:46 +01:00
* @ returns { BBranch [ ] }
2021-12-20 17:30:47 +01:00
* @ deprecated use getParentBranches ( ) instead
* /
2021-04-25 22:02:32 +02:00
getBranches ( ) {
return this . parentBranches ;
}
2023-01-03 13:52:37 +01:00
/** @returns {BNote[]} */
2021-04-25 21:19:18 +02:00
getParentNotes ( ) {
return this . parents ;
}
2023-01-03 13:52:37 +01:00
/** @returns {BNote[]} */
2021-04-25 22:02:32 +02:00
getChildNotes ( ) {
2021-04-25 21:19:18 +02:00
return this . children ;
}
2021-10-24 14:53:45 +02:00
/** @returns {boolean} */
2021-09-03 22:33:40 +02:00
hasChildren ( ) {
return this . children && this . children . length > 0 ;
}
2023-01-03 14:31:46 +01:00
/** @returns {BBranch[]} */
2021-04-25 22:02:32 +02:00
getChildBranches ( ) {
2021-04-26 22:18:14 +02:00
return this . children . map ( childNote => this . becca . getBranchFromChildAndParent ( childNote . noteId , this . noteId ) ) ;
2021-04-25 22:02:32 +02:00
}
2021-04-25 20:00:42 +02:00
/ *
* Note content has quite special handling - it ' s not a separate entity , but a lazily loaded
2023-01-15 21:04:17 +01:00
* part of Note entity with its own sync . Reasons behind this hybrid design has been :
2021-04-25 20:00:42 +02:00
*
2023-01-15 21:04:17 +01:00
* - 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
2021-04-25 20:00:42 +02:00
* - changes in the note metadata or title should not trigger note content sync ( so we keep separate utcDateModified and entity changes records )
* - 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 {*} */
getContent ( silentNotFoundError = false ) {
const row = sql . getRow ( ` SELECT content FROM note_contents WHERE noteId = ? ` , [ this . noteId ] ) ;
if ( ! row ) {
if ( silentNotFoundError ) {
return undefined ;
}
else {
2022-12-21 15:19:05 +01:00
throw new Error ( ` Cannot find note content for noteId= ${ this . noteId } ` ) ;
2021-04-25 20:00:42 +02:00
}
}
let content = row . content ;
if ( this . isProtected ) {
if ( protectedSessionService . isProtectedSessionAvailable ( ) ) {
content = content === null ? null : protectedSessionService . decrypt ( content ) ;
}
else {
content = "" ;
}
}
if ( this . isStringNote ( ) ) {
return content === null
? ""
: content . toString ( "UTF-8" ) ;
}
else {
return content ;
}
}
/** @returns {{contentLength, dateModified, utcDateModified}} */
getContentMetadata ( ) {
return sql . getRow ( `
SELECT
LENGTH ( content ) AS contentLength ,
dateModified ,
utcDateModified
FROM note _contents
WHERE noteId = ? ` , [this.noteId]);
}
2022-06-12 23:29:11 +02:00
get dateCreatedObj ( ) {
return this . dateCreated === null ? null : dayjs ( this . dateCreated ) ;
}
get utcDateCreatedObj ( ) {
return this . utcDateCreated === null ? null : dayjs . utc ( this . utcDateCreated ) ;
}
get dateModifiedObj ( ) {
return this . dateModified === null ? null : dayjs ( this . dateModified ) ;
}
get utcDateModifiedObj ( ) {
return this . utcDateModified === null ? null : dayjs . utc ( this . utcDateModified ) ;
}
2021-04-25 20:00:42 +02:00
/** @returns {*} */
getJsonContent ( ) {
const content = this . getContent ( ) ;
if ( ! content || ! content . trim ( ) ) {
return null ;
}
return JSON . parse ( content ) ;
}
2021-08-07 21:21:30 +02:00
setContent ( content , ignoreMissingProtectedSession = false ) {
2021-04-25 20:00:42 +02:00
if ( content === null || content === undefined ) {
2022-04-19 23:36:21 +02:00
throw new Error ( ` Cannot set null content to note ' ${ this . noteId } ' ` ) ;
2021-04-25 20:00:42 +02:00
}
if ( this . isStringNote ( ) ) {
content = content . toString ( ) ;
}
else {
content = Buffer . isBuffer ( content ) ? content : Buffer . from ( content ) ;
}
const pojo = {
noteId : this . noteId ,
content : content ,
dateModified : dateUtils . localNowDateTime ( ) ,
utcDateModified : dateUtils . utcNowDateTime ( )
} ;
if ( this . isProtected ) {
if ( protectedSessionService . isProtectedSessionAvailable ( ) ) {
pojo . content = protectedSessionService . encrypt ( pojo . content ) ;
}
2021-08-07 21:21:30 +02:00
else if ( ! ignoreMissingProtectedSession ) {
2022-04-19 23:36:21 +02:00
throw new Error ( ` Cannot update content of noteId ' ${ this . noteId } ' since we're out of protected session. ` ) ;
2021-04-25 20:00:42 +02:00
}
}
sql . upsert ( "note_contents" , "noteId" , pojo ) ;
2022-12-21 15:19:05 +01:00
const hash = utils . hash ( ` ${ this . noteId } | ${ pojo . content . toString ( ) } ` ) ;
2021-04-25 20:00:42 +02:00
entityChangesService . addEntityChange ( {
entityName : 'note_contents' ,
entityId : this . noteId ,
hash : hash ,
isErased : false ,
2021-06-29 23:45:45 +02:00
utcDateChanged : pojo . utcDateModified ,
isSynced : true
2021-06-30 20:54:15 +02:00
} ) ;
2023-01-14 12:57:50 +01:00
eventService . emit ( eventService . ENTITY _CHANGED , {
entityName : 'note_contents' ,
entity : this
} ) ;
2021-04-25 20:00:42 +02:00
}
setJsonContent ( content ) {
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 */
isRoot ( ) {
return this . noteId === 'root' ;
}
/** @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 ( ) {
2022-12-17 21:46:51 +01:00
return ( this . type === "code" || this . type === "file" || this . type === 'launcher' )
2021-04-25 20:00:42 +02:00
&& ( 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" ;
}
2023-01-26 20:32:27 +01:00
/** @returns {boolean} true if this note is an image */
isImage ( ) {
return this . type === 'image'
|| ( this . type === 'file' && this . mime ? . startsWith ( 'image/' ) ) ;
}
2021-04-25 20:00:42 +02:00
/** @returns {boolean} true if the note has string content (not binary) */
isStringNote ( ) {
return utils . isStringNote ( this . type , this . mime ) ;
}
/** @returns {string|null} JS script environment - either "frontend" or "backend" */
getScriptEnv ( ) {
if ( this . isHtml ( ) || ( this . isJavaScript ( ) && this . mime . endsWith ( 'env=frontend' ) ) ) {
return "frontend" ;
}
if ( this . type === 'render' ) {
return "frontend" ;
}
if ( this . isJavaScript ( ) && this . mime . endsWith ( 'env=backend' ) ) {
return "backend" ;
}
return null ;
}
2021-04-25 21:19:18 +02:00
/ * *
* @ param { string } [ type ] - ( optional ) attribute type to filter
* @ param { string } [ name ] - ( optional ) attribute name to filter
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute [ ] } all note ' s attributes , including inherited ones
2021-04-25 21:19:18 +02:00
* /
getAttributes ( type , name ) {
2022-12-17 21:46:51 +01:00
this . _ _validateTypeName ( type , name ) ;
2023-01-27 08:46:04 +01:00
this . _ _ensureAttributeCacheIsAvailable ( ) ;
2021-04-25 21:19:18 +02:00
if ( type && name ) {
2023-01-27 08:46:04 +01:00
return this . _ _attributeCache . filter ( attr => attr . name === name && attr . type === type ) ;
2021-04-25 21:19:18 +02:00
}
else if ( type ) {
return this . _ _attributeCache . filter ( attr => attr . type === type ) ;
}
else if ( name ) {
return this . _ _attributeCache . filter ( attr => attr . name === name ) ;
}
else {
2022-12-14 23:44:26 +01:00
// a bit unsafe to return the original array, but defensive copy would be costly
return this . _ _attributeCache ;
2021-04-25 21:19:18 +02:00
}
2020-06-04 23:58:31 +02:00
}
2023-01-27 08:46:04 +01:00
/** @private */
_ _ensureAttributeCacheIsAvailable ( ) {
if ( ! this . _ _attributeCache ) {
this . _ _getAttributes ( [ ] ) ;
}
}
2022-06-12 23:29:11 +02:00
/** @private */
2020-06-04 23:58:31 +02:00
_ _getAttributes ( path ) {
if ( path . includes ( this . noteId ) ) {
return [ ] ;
}
2021-04-25 21:19:18 +02:00
if ( ! this . _ _attributeCache ) {
2020-05-16 23:12:29 +02:00
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
2023-01-17 22:14:53 +01:00
// inheritable attrs on root are typically not intended to be applied to hidden subtree #3537
if ( this . noteId !== 'root' && this . noteId !== '_hidden' ) {
2020-05-16 23:12:29 +02:00
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
2023-01-06 20:31:55 +01:00
if ( ownedAttr . type === 'relation' && [ 'template' , 'inherit' ] . includes ( ownedAttr . name ) ) {
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 ) {
2022-06-19 11:36:29 +02:00
templateAttributes . push (
... templateNote . _ _getAttributes ( newPath )
// template attr is used as a marker for templates, but it's not meant to be inherited
2022-09-16 23:03:02 +02:00
. filter ( attr => ! ( attr . type === 'label' && ( attr . name === 'template' || attr . name === 'workspacetemplate' ) ) )
2022-06-19 11:36:29 +02:00
) ;
2020-05-16 23:12:29 +02:00
}
}
}
2021-04-25 21:19:18 +02:00
this . _ _attributeCache = [ ] ;
2020-09-30 22:34:18 +02:00
const addedAttributeIds = new Set ( ) ;
for ( const attr of parentAttributes . concat ( templateAttributes ) ) {
if ( ! addedAttributeIds . has ( attr . attributeId ) ) {
addedAttributeIds . add ( attr . attributeId ) ;
2021-04-25 21:19:18 +02:00
this . _ _attributeCache . push ( attr ) ;
2020-09-30 22:34:18 +02:00
}
}
2020-05-16 23:12:29 +02:00
this . inheritableAttributeCache = [ ] ;
2021-04-25 21:19:18 +02:00
for ( const attr of this . _ _attributeCache ) {
2020-05-16 23:12:29 +02:00
if ( attr . isInheritable ) {
this . inheritableAttributeCache . push ( attr ) ;
}
}
}
2021-04-25 21:19:18 +02:00
return this . _ _attributeCache ;
2020-05-16 23:12:29 +02:00
}
2022-06-12 23:29:11 +02:00
/ * *
* @ private
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute [ ] }
2022-06-12 23:29:11 +02:00
* /
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 ;
}
2022-12-17 21:46:51 +01:00
_ _validateTypeName ( type , name ) {
if ( type && type !== 'label' && type !== 'relation' ) {
throw new Error ( ` Unrecognized attribute type ' ${ type } '. Only 'label' and 'relation' are possible values. ` ) ;
}
if ( name ) {
const firstLetter = name . charAt ( 0 ) ;
if ( firstLetter === '#' || firstLetter === '~' ) {
throw new Error ( ` Detect '#' or '~' in the attribute's name. In the API, attribute names should be set without these characters. ` ) ;
}
}
}
2022-06-19 14:06:00 +02:00
/ * *
* @ param type
* @ param name
* @ param [ value ]
* @ returns { boolean }
* /
2022-12-14 23:44:26 +01:00
hasAttribute ( type , name , value = null ) {
2022-06-19 14:06:00 +02:00
return ! ! this . getAttributes ( ) . find ( attr =>
2023-01-27 08:46:04 +01:00
attr . name === name
2022-06-19 14:06:00 +02:00
&& ( value === undefined || value === null || attr . value === value )
2023-01-27 08:46:04 +01:00
&& attr . type === type
2022-06-19 14:06:00 +02:00
) ;
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 ;
2021-04-25 21:19:18 +02:00
return this . getAttributes ( ) . find (
2023-01-27 08:46:04 +01:00
attr => attr . name . toLowerCase ( ) === name
&& ( ! value || attr . value . toLowerCase ( ) === value )
&& attr . type === type ) ;
2020-12-15 15:09:00 +01:00
}
2021-04-17 20:52:46 +02:00
getRelationTarget ( name ) {
2023-01-27 08:46:04 +01:00
const relation = this . getAttributes ( ) . find ( attr => attr . name === name && attr . type === 'relation' ) ;
2021-04-17 20:52:46 +02:00
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
2022-06-19 14:06:00 +02:00
* @ param { string } [ value ] - label value
2021-04-17 20:52:46 +02:00
* @ returns { boolean } true if label exists ( including inherited )
* /
2022-06-19 14:06:00 +02:00
hasLabel ( name , value ) { return this . hasAttribute ( LABEL , name , value ) ; }
2021-04-17 20:52:46 +02:00
/ * *
* @ param { string } name - label name
2022-06-19 14:06:00 +02:00
* @ param { string } [ value ] - label value
2021-04-17 20:52:46 +02:00
* @ returns { boolean } true if label exists ( excluding inherited )
* /
2022-06-19 14:06:00 +02:00
hasOwnedLabel ( name , value ) { return this . hasOwnedAttribute ( LABEL , name , value ) ; }
2021-04-17 20:52:46 +02:00
/ * *
* @ param { string } name - relation name
2022-06-19 14:06:00 +02:00
* @ param { string } [ value ] - relation value
2021-04-17 20:52:46 +02:00
* @ returns { boolean } true if relation exists ( including inherited )
* /
2022-06-19 14:06:00 +02:00
hasRelation ( name , value ) { return this . hasAttribute ( RELATION , name , value ) ; }
2021-04-17 20:52:46 +02:00
/ * *
* @ param { string } name - relation name
2022-06-19 14:06:00 +02:00
* @ param { string } [ value ] - relation value
2021-04-17 20:52:46 +02:00
* @ returns { boolean } true if relation exists ( excluding inherited )
* /
2022-06-19 14:06:00 +02:00
hasOwnedRelation ( name , value ) { return this . hasOwnedAttribute ( RELATION , name , value ) ; }
2021-04-17 20:52:46 +02:00
/ * *
* @ param { string } name - label name
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute | null } label if it exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
getLabel ( name ) { return this . getAttribute ( LABEL , name ) ; }
/ * *
* @ param { string } name - label name
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute | null } label if it exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
getOwnedLabel ( name ) { return this . getOwnedAttribute ( LABEL , name ) ; }
/ * *
* @ param { string } name - relation name
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute | null } relation if it exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
getRelation ( name ) { return this . getAttribute ( RELATION , name ) ; }
/ * *
* @ param { string } name - relation name
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute | null } relation if it exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
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
2022-06-19 14:06:00 +02:00
* @ param { string } [ value ] - attribute value
2021-04-17 20:52:46 +02:00
* @ returns { boolean } true if note has an attribute with given type and name ( excluding inherited )
* /
2022-06-19 14:06:00 +02:00
hasOwnedAttribute ( type , name , value ) {
return ! ! this . getOwnedAttribute ( type , name , value ) ;
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
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute } 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 .
2021-04-17 20:52:46 +02:00
* /
getAttribute ( type , name ) {
const attributes = this . getAttributes ( ) ;
2020-05-25 00:25:47 +02:00
2023-01-27 08:46:04 +01:00
return attributes . find ( attr => attr . name === name && attr . type === type ) ;
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
}
2021-04-25 21:19:18 +02:00
/ * *
* @ param { string } [ name ] - label name to filter
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute [ ] } all note ' s labels ( attributes with type label ) , including inherited ones
2021-04-25 21:19:18 +02:00
* /
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
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute [ ] } all note ' s labels ( attributes with type label ) , excluding inherited ones
2021-04-25 21:19:18 +02:00
* /
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
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute [ ] } all note ' s relations ( attributes with type relation ) , including inherited ones
2021-04-25 21:19:18 +02:00
* /
getRelations ( name ) {
return this . getAttributes ( RELATION , name ) ;
}
/ * *
* @ param { string } [ name ] - relation name to filter
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute [ ] } all note ' s relations ( attributes with type relation ) , excluding inherited ones
2021-04-25 21:19:18 +02:00
* /
getOwnedRelations ( name ) {
return this . getOwnedAttributes ( RELATION , name ) ;
}
2021-04-25 22:02:32 +02:00
/ * *
2022-12-14 23:44:26 +01:00
* @ param { string | null } [ type ] - ( optional ) attribute type to filter
* @ param { string | null } [ name ] - ( optional ) attribute name to filter
* @ param { string | null } [ value ] - ( optional ) attribute value to filter
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute [ ] } note ' s "owned" attributes - excluding inherited ones
2021-04-25 22:02:32 +02:00
* /
2022-12-14 23:44:26 +01:00
getOwnedAttributes ( type = null , name = null , value = null ) {
2022-12-17 21:46:51 +01:00
this . _ _validateTypeName ( type , name ) ;
2021-09-02 22:14:22 +02:00
2022-06-19 14:06:00 +02:00
if ( type && name && value !== undefined && value !== null ) {
2023-01-27 08:46:04 +01:00
return this . ownedAttributes . filter ( attr => attr . name === name && attr . value === value && attr . type === type ) ;
2022-06-19 14:06:00 +02:00
}
else if ( type && name ) {
2023-01-27 08:46:04 +01:00
return this . ownedAttributes . filter ( attr => attr . name === name && attr . type === type ) ;
2021-04-25 22:02:32 +02:00
}
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 ( ) ;
}
}
2021-05-08 23:31:20 +02:00
/ * *
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute } attribute belonging to this specific note ( excludes inherited attributes )
2021-05-08 23:31:20 +02:00
*
* This method can be significantly faster than the getAttribute ( )
* /
2022-12-14 23:44:26 +01:00
getOwnedAttribute ( type , name , value = null ) {
2022-06-19 14:06:00 +02:00
const attrs = this . getOwnedAttributes ( type , name , value ) ;
2021-05-08 23:31:20 +02:00
return attrs . length > 0 ? attrs [ 0 ] : null ;
}
2021-05-18 20:56:49 +02:00
get isArchived ( ) {
2020-05-16 23:12:29 +02:00
return this . hasAttribute ( 'label' , 'archived' ) ;
}
2023-01-27 08:46:04 +01:00
hasInheritableArchivedLabel ( ) {
for ( const attr of this . getAttributes ( ) ) {
if ( attr . name === 'archived' && attr . type === LABEL && attr . isInheritable ) {
return true ;
}
}
return false ;
2020-05-16 23:12:29 +02:00
}
2023-01-27 08:46:04 +01:00
// will sort the parents so that the non-archived are first and archived at the end
// this is done so that the non-archived paths are always explored as first when looking for note path
2021-10-24 14:37:41 +02:00
sortParents ( ) {
2023-01-27 16:57:23 +01:00
this . parentBranches . sort ( ( a , b ) => {
if ( a . parentNote ? . isArchived ) {
return 1 ;
} else if ( a . parentNote ? . isHiddenCompletely ( ) ) {
return 1 ;
} else {
return - 1 ;
}
} ) ;
2021-02-24 22:38:26 +01:00
2022-08-16 23:46:56 +02:00
this . parents = this . parentBranches
. map ( branch => branch . parentNote )
. filter ( note => ! ! note ) ;
2020-05-16 23:12:29 +02:00
}
2023-01-06 15:07:18 +01:00
sortChildren ( ) {
if ( this . children . length === 0 ) {
return ;
}
const becca = this . becca ;
this . children . sort ( ( a , b ) => {
const aBranch = becca . getBranchFromChildAndParent ( a . noteId , this . noteId ) ;
const bBranch = becca . getBranchFromChildAndParent ( b . noteId , this . noteId ) ;
return aBranch ? . notePosition < bBranch ? . notePosition ? - 1 : 1 ;
} ) ;
}
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
*
2023-01-05 23:38:41 +01:00
* @ returns { string } - returns flattened textual representation of note , prefixes and attributes
2020-05-16 23:12:29 +02:00
* /
2021-05-17 22:35:36 +02:00
getFlatText ( ) {
2020-05-16 23:12:29 +02:00
if ( ! this . flatTextCache ) {
2022-12-21 15:19:05 +01: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 ) {
2022-12-21 15:19:05 +01:00
this . flatTextCache += ` ${ branch . prefix } ` ;
2020-05-16 23:12:29 +02:00
}
}
2022-12-21 15:19:05 +01:00
this . flatTextCache += ` ${ this . title } ` ;
2020-05-16 23:12:29 +02:00
2021-04-25 21:19:18 +02:00
for ( const attr of this . getAttributes ( ) ) {
2020-05-16 23:12:29 +02:00
// it's best to use space as separator since spaces are filtered from the search string by the tokenization into words
2022-12-21 15:19:05 +01:00
this . flatTextCache += ` ${ attr . type === 'label' ? '#' : '~' } ${ attr . name } ` ;
2020-05-16 23:12:29 +02:00
if ( attr . value ) {
2022-12-21 15:19:05 +01:00
this . flatTextCache += ` = ${ attr . value } ` ;
2020-05-16 23:12:29 +02:00
}
2020-09-07 00:05:01 +02:00
this . flatTextCache += ' ' ;
2020-05-16 23:12:29 +02:00
}
2021-09-28 10:02:25 +02:00
this . flatTextCache = utils . normalize ( this . flatTextCache ) ;
2020-05-16 23:12:29 +02:00
}
return this . flatTextCache ;
}
invalidateThisCache ( ) {
this . flatTextCache = null ;
2021-04-25 21:19:18 +02:00
this . _ _attributeCache = null ;
2020-05-16 23:12:29 +02:00
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 ) {
2023-01-06 20:31:55 +01:00
if ( targetRelation . name === 'template' || targetRelation . name === 'inherit' ) {
2020-05-16 23:12:29 +02:00
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 ) {
2023-01-06 20:31:55 +01:00
if ( targetRelation . name === 'template' || targetRelation . name === 'inherit' ) {
2020-05-16 23:12:29 +02:00
const note = targetRelation . note ;
if ( note ) {
note . invalidateSubtreeFlatText ( ) ;
}
}
}
}
2021-06-03 12:32:48 +02:00
getRelationDefinitions ( ) {
return this . getLabels ( )
. filter ( l => l . name . startsWith ( "relation:" ) ) ;
}
getLabelDefinitions ( ) {
return this . getLabels ( )
. filter ( l => l . name . startsWith ( "relation:" ) ) ;
}
2023-01-06 20:31:55 +01:00
isInherited ( ) {
return ! ! this . targetRelations . find ( rel => rel . name === 'template' || rel . name === 'inherit' ) ;
2020-05-16 23:12:29 +02:00
}
2023-01-03 13:52:37 +01:00
/** @returns {BNote[]} */
2021-05-17 22:35:36 +02:00
getSubtreeNotesIncludingTemplated ( ) {
2021-10-29 21:36:23 +02:00
const set = new Set ( ) ;
2020-05-16 23:12:29 +02:00
2021-10-29 21:36:23 +02:00
function inner ( note ) {
2023-01-17 22:14:53 +01:00
// _hidden is not counted as subtree for the purpose of inheritance
if ( set . has ( note ) || note . noteId === '_hidden' ) {
2021-10-29 21:36:23 +02:00
return ;
}
2020-05-16 23:12:29 +02:00
2021-10-29 21:36:23 +02:00
set . add ( note ) ;
2020-05-16 23:12:29 +02:00
2021-10-29 21:36:23 +02:00
for ( const childNote of note . children ) {
inner ( childNote ) ;
}
for ( const targetRelation of note . targetRelations ) {
2023-01-06 20:31:55 +01:00
if ( targetRelation . name === 'template' || targetRelation . name === 'inherit' ) {
2021-10-29 21:36:23 +02:00
const targetNote = targetRelation . note ;
if ( targetNote ) {
inner ( targetNote ) ;
}
2020-05-16 23:12:29 +02:00
}
}
}
2021-10-29 21:36:23 +02:00
inner ( this ) ;
return Array . from ( set ) ;
2020-05-16 23:12:29 +02:00
}
2023-01-05 23:38:41 +01:00
/** @returns {BNote[]} */
2022-11-07 23:56:53 +01:00
getSearchResultNotes ( ) {
if ( this . type !== 'search' ) {
return [ ] ;
}
try {
const searchService = require ( "../../services/search/services/search" ) ;
const { searchResultNoteIds } = searchService . searchFromNote ( this ) ;
const becca = this . becca ;
return searchResultNoteIds
. map ( resultNoteId => becca . notes [ resultNoteId ] )
. filter ( note => ! ! note ) ;
}
catch ( e ) {
log . error ( ` Could not resolve search note ${ this . noteId } : ${ e . message } ` ) ;
return [ ] ;
}
}
2022-11-05 22:32:50 +01:00
/ * *
2023-01-03 13:52:37 +01:00
* @ returns { { notes : BNote [ ] , relationships : Array . < { parentNoteId : string , childNoteId : string } > } }
2022-11-05 22:32:50 +01:00
* /
2022-12-23 15:32:11 +01:00
getSubtree ( { includeArchived = true , includeHidden = false , resolveSearch = false } = { } ) {
2021-09-21 22:45:06 +02:00
const noteSet = new Set ( ) ;
2022-11-05 22:32:50 +01:00
const relationships = [ ] ; // list of tuples parentNoteId -> childNoteId
function resolveSearchNote ( searchNote ) {
try {
2022-11-07 23:56:53 +01:00
for ( const resultNote of searchNote . getSearchResultNotes ( ) ) {
addSubtreeNotesInner ( resultNote , searchNote ) ;
2022-11-05 22:32:50 +01:00
}
}
catch ( e ) {
log . error ( ` Could not resolve search note ${ searchNote ? . noteId } : ${ e . message } ` ) ;
}
}
function addSubtreeNotesInner ( note , parentNote = null ) {
2022-12-23 15:32:11 +01:00
if ( note . noteId === '_hidden' && ! includeHidden ) {
2022-11-06 14:38:41 +01:00
return ;
}
2022-11-05 22:32:50 +01:00
if ( parentNote ) {
// this needs to happen first before noteSet check to include all clone relationships
relationships . push ( {
parentNoteId : parentNote . noteId ,
childNoteId : note . noteId
} ) ;
}
if ( noteSet . has ( note ) ) {
return ;
}
2021-09-20 23:04:41 +02:00
2021-09-21 22:45:06 +02:00
if ( ! includeArchived && note . isArchived ) {
return ;
}
2020-05-16 23:12:29 +02:00
2021-09-21 22:45:06 +02:00
noteSet . add ( note ) ;
2022-11-05 22:32:50 +01:00
if ( note . type === 'search' ) {
if ( resolveSearch ) {
resolveSearchNote ( note ) ;
}
}
else {
for ( const childNote of note . children ) {
addSubtreeNotesInner ( childNote , note ) ;
}
2021-09-21 22:45:06 +02:00
}
2020-05-16 23:12:29 +02:00
}
2021-09-21 22:45:06 +02:00
addSubtreeNotesInner ( this ) ;
2022-11-05 22:32:50 +01:00
return {
notes : Array . from ( noteSet ) ,
relationships
} ;
2020-05-16 23:12:29 +02:00
}
2022-12-27 14:44:28 +01:00
/** @returns {String[]} - includes the subtree node as well */
getSubtreeNoteIds ( { includeArchived = true , includeHidden = false , resolveSearch = false } = { } ) {
return this . getSubtree ( { includeArchived , includeHidden , resolveSearch } )
2022-11-05 22:32:50 +01:00
. notes
. map ( note => note . noteId ) ;
2021-01-23 21:00:59 +01:00
}
2022-12-27 14:44:28 +01:00
/** @deprecated use getSubtreeNoteIds() instead */
2021-04-25 22:02:32 +02:00
getDescendantNoteIds ( ) {
2021-05-17 22:35:36 +02:00
return this . getSubtreeNoteIds ( ) ;
2021-04-25 22:02:32 +02:00
}
2020-05-23 20:52:55 +02:00
get parentCount ( ) {
return this . parents . length ;
}
get childrenCount ( ) {
return this . children . length ;
}
get labelCount ( ) {
2021-04-25 21:19:18 +02:00
return this . getAttributes ( ) . filter ( attr => attr . type === 'label' ) . length ;
2020-05-23 20:52:55 +02:00
}
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-04-25 21:19:18 +02:00
return this . getAttributes ( ) . filter ( attr => attr . type === 'relation' && ! attr . isAutoLink ( ) ) . length ;
2021-02-19 20:42:34 +01:00
}
get relationCountIncludingLinks ( ) {
2021-04-25 21:19:18 +02:00
return this . getAttributes ( ) . filter ( attr => attr . type === 'relation' ) . length ;
2020-05-23 20:52:55 +02:00
}
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 ( ) {
2021-04-25 21:19:18 +02:00
return this . getAttributes ( ) . length ;
2020-05-23 20:52:55 +02:00
}
2021-02-19 20:42:34 +01:00
get ownedAttributeCount ( ) {
2023-01-27 08:46:04 +01:00
return this . getOwnedAttributes ( ) . length ;
2021-02-19 20:42:34 +01:00
}
2023-01-03 13:52:37 +01:00
/** @returns {BNote[]} */
2021-05-17 22:35:36 +02:00
getAncestors ( ) {
2020-05-23 23:44:55 +02:00
if ( ! this . ancestorCache ) {
const noteIds = new Set ( ) ;
this . ancestorCache = [ ] ;
for ( const parent of this . parents ) {
2022-01-13 21:28:51 +01:00
if ( noteIds . has ( parent . noteId ) ) {
continue ;
2020-05-23 23:44:55 +02:00
}
2022-01-13 21:28:51 +01:00
this . ancestorCache . push ( parent ) ;
noteIds . add ( parent . noteId ) ;
2021-05-17 22:35:36 +02:00
for ( const ancestorNote of parent . getAncestors ( ) ) {
2020-05-23 23:44:55 +02:00
if ( ! noteIds . has ( ancestorNote . noteId ) ) {
this . ancestorCache . push ( ancestorNote ) ;
noteIds . add ( ancestorNote . noteId ) ;
}
}
}
}
return this . ancestorCache ;
}
2021-10-24 14:53:45 +02:00
/** @returns {boolean} */
hasAncestor ( ancestorNoteId ) {
for ( const ancestorNote of this . getAncestors ( ) ) {
if ( ancestorNote . noteId === ancestorNoteId ) {
return true ;
}
}
return false ;
}
2022-12-23 15:32:11 +01:00
isInHiddenSubtree ( ) {
return this . noteId === '_hidden' || this . hasAncestor ( '_hidden' ) ;
}
2021-05-08 23:31:20 +02:00
getTargetRelations ( ) {
return this . targetRelations ;
}
2023-01-03 13:52:37 +01:00
/ * * @ r e t u r n s { B 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
2020-05-16 23:12:29 +02:00
* in effect returns notes which are influenced by note ' s non - inheritable attributes * /
2023-01-06 20:31:55 +01:00
getInheritingNotes ( ) {
2020-05-16 23:12:29 +02:00
const arr = [ this ] ;
for ( const targetRelation of this . targetRelations ) {
2023-01-06 20:31:55 +01:00
if ( targetRelation . name === 'template' || targetRelation . name === 'inherit' ) {
2020-05-16 23:12:29 +02:00
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 ;
}
2023-01-23 23:37:58 +01:00
/** @returns {BNoteRevision[]} */
2021-04-26 22:00:55 +02:00
getNoteRevisions ( ) {
return sql . getRows ( "SELECT * FROM note_revisions WHERE noteId = ?" , [ this . noteId ] )
2023-01-03 13:52:37 +01:00
. map ( row => new BNoteRevision ( row ) ) ;
2021-04-26 22:00:55 +02:00
}
2023-01-23 23:37:58 +01:00
/** @returns {BNoteAttachment[]} */
2023-01-22 23:36:05 +01:00
getNoteAttachments ( ) {
return sql . getRows ( "SELECT * FROM note_attachments WHERE noteId = ? AND isDeleted = 0" , [ this . noteId ] )
. map ( row => new BNoteAttachment ( row ) ) ;
}
2023-01-23 23:37:58 +01:00
/** @returns {BNoteAttachment|undefined} */
2023-01-23 16:57:28 +01:00
getNoteAttachmentByName ( name ) {
return sql . getRows ( "SELECT * FROM note_attachments WHERE noteId = ? AND name = ? AND isDeleted = 0" , [ this . noteId , name ] )
. map ( row => new BNoteAttachment ( row ) )
[ 0 ] ;
}
2021-05-09 20:46:32 +02:00
/ * *
2023-01-05 23:38:41 +01:00
* @ returns { string [ ] [ ] } - array of notePaths ( each represented by array of noteIds constituting the particular note path )
2021-05-09 20:46:32 +02:00
* /
getAllNotePaths ( ) {
if ( this . noteId === 'root' ) {
return [ [ 'root' ] ] ;
}
const notePaths = [ ] ;
for ( const parentNote of this . getParentNotes ( ) ) {
for ( const parentPath of parentNote . getAllNotePaths ( ) ) {
parentPath . push ( this . noteId ) ;
notePaths . push ( parentPath ) ;
}
}
return notePaths ;
}
2023-01-13 10:09:41 +01:00
/ * *
* @ return boolean - true if there ' s no non - hidden path , note is not cloned to the visible tree
* /
isHiddenCompletely ( ) {
2023-01-27 16:57:23 +01:00
if ( this . noteId === 'root' ) {
return false ;
}
for ( const parentNote of this . parents ) {
if ( parentNote . noteId === 'root' ) {
return false ;
} else if ( parentNote . noteId === '_hidden' ) {
continue ;
}
if ( ! parentNote . isHiddenCompletely ( ) ) {
return false ;
}
}
return true ;
2023-01-13 10:09:41 +01:00
}
2021-05-09 20:46:32 +02:00
/ * *
* @ param ancestorNoteId
2023-01-05 23:38:41 +01:00
* @ returns { boolean } - true if ancestorNoteId occurs in at least one of the note ' s paths
2021-05-09 20:46:32 +02:00
* /
isDescendantOfNote ( ancestorNoteId ) {
const notePaths = this . getAllNotePaths ( ) ;
return notePaths . some ( path => path . includes ( ancestorNoteId ) ) ;
}
2021-05-11 22:00:16 +02:00
/ * *
* Update 's given attribute' s value or creates it if it doesn ' t exist
*
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ param { string } [ value ] - attribute value ( optional )
* /
setAttribute ( type , name , value ) {
const attributes = this . getOwnedAttributes ( ) ;
const attr = attributes . find ( attr => attr . type === type && attr . name === name ) ;
2022-12-22 14:57:00 +01:00
value = value ? . toString ( ) || "" ;
2021-08-12 20:42:35 +02:00
2021-05-11 22:00:16 +02:00
if ( attr ) {
if ( attr . value !== value ) {
attr . value = value ;
attr . save ( ) ;
}
}
else {
2023-01-03 13:52:37 +01:00
const BAttribute = require ( "./battribute" ) ;
2021-05-11 22:00:16 +02:00
2023-01-03 13:52:37 +01:00
new BAttribute ( {
2021-05-11 22:00:16 +02:00
noteId : this . noteId ,
type : type ,
name : name ,
2021-08-12 20:42:35 +02:00
value : value
2021-05-11 22:00:16 +02:00
} ) . save ( ) ;
}
}
/ * *
* Removes given attribute name - value pair if it exists .
*
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ param { string } [ value ] - attribute value ( optional )
* /
removeAttribute ( type , name , value ) {
const attributes = this . getOwnedAttributes ( ) ;
for ( const attribute of attributes ) {
if ( attribute . type === type && attribute . name === name && ( value === undefined || value === attribute . value ) ) {
attribute . markAsDeleted ( ) ;
}
}
}
/ * *
2022-08-27 13:40:01 -07:00
* Adds a new attribute to this note . The attribute is saved and returned .
* See addLabel , addRelation for more specific methods .
*
* @ param { string } type - attribute type ( label / relation )
* @ param { string } name - name of the attribute , not including the leading ~ / #
* @ param { string } [ value ] - value of the attribute - text for labels , target note ID for relations ; optional .
2022-12-27 14:44:28 +01:00
* @ param { boolean } [ isInheritable = false ]
* @ param { int } [ position ]
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute }
2021-05-11 22:00:16 +02:00
* /
addAttribute ( type , name , value = "" , isInheritable = false , position = 1000 ) {
2023-01-03 13:52:37 +01:00
const BAttribute = require ( "./battribute" ) ;
2021-05-11 22:00:16 +02:00
2023-01-08 12:46:26 +01:00
return new BAttribute ( {
2021-05-11 22:00:16 +02:00
noteId : this . noteId ,
type : type ,
name : name ,
value : value ,
isInheritable : isInheritable ,
position : position
} ) . save ( ) ;
}
2022-08-27 13:40:01 -07:00
/ * *
* Adds a new label to this note . The label attribute is saved and returned .
*
* @ param { string } name - name of the label , not including the leading #
* @ param { string } [ value ] - text value of the label ; optional
2022-12-27 14:44:28 +01:00
* @ param { boolean } [ isInheritable = false ]
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute }
2022-08-27 13:40:01 -07:00
* /
2021-05-11 22:00:16 +02:00
addLabel ( name , value = "" , isInheritable = false ) {
return this . addAttribute ( LABEL , name , value , isInheritable ) ;
}
2022-08-27 13:40:01 -07:00
/ * *
* Adds a new relation to this note . The relation attribute is saved and
* returned .
*
* @ param { string } name - name of the relation , not including the leading ~
2022-12-27 14:44:28 +01:00
* @ param { string } targetNoteId
* @ param { boolean } [ isInheritable = false ]
2023-01-05 23:38:41 +01:00
* @ returns { BAttribute }
2022-08-27 13:40:01 -07:00
* /
2021-05-11 22:00:16 +02:00
addRelation ( name , targetNoteId , isInheritable = false ) {
return this . addAttribute ( RELATION , name , targetNoteId , isInheritable ) ;
}
/ * *
* Based on enabled , attribute is either set or removed .
*
* @ param { string } type - attribute type ( 'relation' , 'label' etc . )
* @ param { boolean } enabled - toggle On or Off
* @ param { string } name - attribute name
* @ param { string } [ value ] - attribute value ( optional )
* /
toggleAttribute ( type , enabled , name , value ) {
if ( enabled ) {
this . setAttribute ( type , name , value ) ;
}
else {
this . removeAttribute ( type , name , value ) ;
}
}
/ * *
* Based on enabled , label is either set or removed .
*
* @ param { boolean } enabled - toggle On or Off
* @ param { string } name - label name
* @ param { string } [ value ] - label value ( optional )
* /
toggleLabel ( enabled , name , value ) { return this . toggleAttribute ( LABEL , enabled , name , value ) ; }
/ * *
* Based on enabled , relation is either set or removed .
*
* @ param { boolean } enabled - toggle On or Off
* @ param { string } name - relation name
* @ param { string } [ value ] - relation value ( noteId )
* /
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
*
* @ param { string } name - label name
* @ param { string } [ value ] - label value
* /
setLabel ( name , value ) { return this . setAttribute ( LABEL , name , value ) ; }
/ * *
* Update 's given relation' s value or creates it if it doesn ' t exist
*
* @ param { string } name - relation name
* @ param { string } value - relation value ( noteId )
* /
setRelation ( name , value ) { return this . setAttribute ( RELATION , name , value ) ; }
/ * *
* Remove label name - value pair , if it exists .
*
* @ param { string } name - label name
* @ param { string } [ value ] - label value
* /
removeLabel ( name , value ) { return this . removeAttribute ( LABEL , name , value ) ; }
/ * *
* Remove relation name - value pair , if it exists .
*
* @ param { string } name - relation name
* @ param { string } [ value ] - relation value ( noteId )
* /
removeRelation ( name , value ) { return this . removeAttribute ( RELATION , name , value ) ; }
2021-06-06 11:01:10 +02:00
searchNotesInSubtree ( searchString ) {
const searchService = require ( "../../services/search/services/search" ) ;
return searchService . searchNotes ( searchString ) ;
}
searchNoteInSubtree ( searchString ) {
return this . searchNotesInSubtree ( searchString ) [ 0 ] ;
}
2022-07-19 23:32:03 +02:00
/ * *
* @ param parentNoteId
* @ returns { { success : boolean , message : string } }
* /
2021-06-06 11:01:10 +02:00
cloneTo ( parentNoteId ) {
const cloningService = require ( "../../services/cloning" ) ;
const branch = this . becca . getNote ( parentNoteId ) . getParentBranches ( ) [ 0 ] ;
2021-12-27 23:39:46 +01:00
return cloningService . cloneNoteToBranch ( this . noteId , branch . branchId ) ;
2021-06-06 11:01:10 +02:00
}
2022-04-19 23:06:46 +02:00
/ * *
* ( Soft ) delete a note and all its descendants .
*
* @ param { string } [ deleteId ] - optional delete identified
* @ param { TaskContext } [ taskContext ]
* /
deleteNote ( deleteId , taskContext ) {
2022-06-08 22:25:00 +02:00
if ( this . isDeleted ) {
return ;
}
2022-04-19 23:06:46 +02:00
if ( ! deleteId ) {
deleteId = utils . randomString ( 10 ) ;
}
if ( ! taskContext ) {
taskContext = new TaskContext ( 'no-progress-reporting' ) ;
}
2022-06-05 14:58:19 +02:00
// needs to be run before branches and attributes are deleted and thus attached relations disappear
2022-06-06 21:59:44 +02:00
const handlers = require ( "../../services/handlers" ) ;
2022-06-05 14:58:19 +02:00
handlers . runAttachedRelations ( this , 'runOnNoteDeletion' , this ) ;
taskContext . noteDeletionHandlerTriggered = true ;
2022-04-19 23:06:46 +02:00
for ( const branch of this . getParentBranches ( ) ) {
branch . deleteBranch ( deleteId , taskContext ) ;
}
}
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 ) ;
2022-03-20 22:23:48 +01:00
this . flatTextCache = null ;
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
2022-12-06 23:48:44 +01:00
isLaunchBarConfig ( ) {
2022-12-21 16:11:00 +01:00
return this . type === 'launcher' || [ '_lbRoot' , '_lbAvailableLaunchers' , '_lbVisibleLaunchers' ] . includes ( this . noteId ) ;
2022-12-06 23:48:44 +01:00
}
2022-12-08 15:29:14 +01:00
isOptions ( ) {
2023-01-13 11:34:35 +01:00
return this . noteId . startsWith ( "_options" ) ;
2022-12-08 15:29:14 +01:00
}
2021-10-31 21:03:26 +01:00
get isDeleted ( ) {
2022-12-06 16:11:43 +01:00
return ! ( this . noteId in this . becca . notes ) || this . isBeingDeleted ;
2021-10-31 21:03:26 +01:00
}
2022-06-02 17:25:58 +02:00
/ * *
2023-01-05 23:38:41 +01:00
* @ returns { BNoteRevision | null }
2022-06-02 17:25:58 +02:00
* /
saveNoteRevision ( ) {
const content = this . getContent ( ) ;
if ( ! content || ( Buffer . isBuffer ( content ) && content . byteLength === 0 ) ) {
return null ;
}
const contentMetadata = this . getContentMetadata ( ) ;
2023-01-03 13:52:37 +01:00
const noteRevision = new BNoteRevision ( {
2022-06-02 17:25:58 +02:00
noteId : this . noteId ,
// title and text should be decrypted now
title : this . title ,
type : this . type ,
mime : this . mime ,
2022-06-13 22:38:59 +02:00
isProtected : this . isProtected ,
2022-06-02 17:25:58 +02:00
utcDateLastEdited : this . utcDateModified > contentMetadata . utcDateModified
? this . utcDateModified
: contentMetadata . utcDateModified ,
utcDateCreated : dateUtils . utcNowDateTime ( ) ,
utcDateModified : dateUtils . utcNowDateTime ( ) ,
dateLastEdited : this . dateModified > contentMetadata . dateModified
? this . dateModified
: contentMetadata . dateModified ,
dateCreated : dateUtils . localNowDateTime ( )
2022-07-01 22:49:10 +02:00
} , true ) . save ( ) ;
2022-06-02 17:25:58 +02:00
noteRevision . setContent ( content ) ;
return noteRevision ;
}
2023-01-23 16:57:28 +01:00
/ * *
* @ returns { BNoteAttachment }
* /
saveNoteAttachment ( name , mime , content ) {
2023-01-24 16:55:48 +01:00
let noteAttachment = this . getNoteAttachmentByName ( name ) ;
2023-01-23 16:57:28 +01:00
2023-01-24 16:55:48 +01:00
if ( noteAttachment
&& noteAttachment . mime === mime
&& noteAttachment . contentCheckSum === noteAttachment . calculateCheckSum ( content ) ) {
return noteAttachment ; // no change
}
noteAttachment = new BNoteAttachment ( {
2023-01-23 23:37:58 +01:00
noteId : this . noteId ,
2023-01-23 16:57:28 +01:00
name ,
mime ,
isProtected : this . isProtected
} ) ;
noteAttachment . setContent ( content ) ;
return noteAttachment ;
}
2021-05-08 21:10:58 +02:00
beforeSaving ( ) {
super . beforeSaving ( ) ;
2021-10-06 19:59:29 +02:00
this . becca . addNote ( this . noteId , this ) ;
2021-05-09 20:46:32 +02:00
2021-05-08 21:10:58 +02:00
this . dateModified = dateUtils . localNowDateTime ( ) ;
this . utcDateModified = dateUtils . utcNowDateTime ( ) ;
}
2021-04-25 21:19:18 +02:00
getPojo ( ) {
2021-09-08 22:35:32 +02:00
return {
2021-04-25 20:00:42 +02:00
noteId : this . noteId ,
title : this . title ,
isProtected : this . isProtected ,
type : this . type ,
mime : this . mime ,
2021-05-09 11:12:53 +02:00
isDeleted : false ,
2021-04-25 20:00:42 +02:00
dateCreated : this . dateCreated ,
dateModified : this . dateModified ,
utcDateCreated : this . utcDateCreated ,
utcDateModified : this . utcDateModified
} ;
2021-09-08 22:35:32 +02:00
}
getPojoToSave ( ) {
const pojo = this . getPojo ( ) ;
2021-04-25 21:19:18 +02:00
if ( pojo . isProtected ) {
if ( this . isDecrypted ) {
pojo . title = protectedSessionService . encrypt ( pojo . title ) ;
}
else {
// updating protected note outside of protected session means we will keep original ciphertexts
delete pojo . title ;
}
}
return pojo ;
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 = BNote ;