2018-01-28 23:16:50 -05:00
"use strict" ;
2018-01-29 18:34:59 -05:00
const Entity = require ( './entity' ) ;
2018-08-13 10:59:31 +02:00
const Attribute = require ( './attribute' ) ;
2018-04-20 00:12:01 -04:00
const protectedSessionService = require ( '../services/protected_session' ) ;
2018-03-31 10:51:37 -04:00
const repository = require ( '../services/repository' ) ;
2018-11-25 22:09:52 +01:00
const sql = require ( '../services/sql' ) ;
2019-03-26 22:24:04 +01:00
const utils = require ( '../services/utils' ) ;
2018-04-02 20:46:46 -04:00
const dateUtils = require ( '../services/date_utils' ) ;
2019-03-26 22:24:04 +01:00
const syncTableService = require ( '../services/sync_table' ) ;
2018-01-28 23:16:50 -05:00
2018-08-15 22:06:49 +02:00
const LABEL = 'label' ;
2018-11-13 12:50:08 +01:00
const LABEL _DEFINITION = 'label-definition' ;
2018-08-15 22:06:49 +02:00
const RELATION = 'relation' ;
2018-11-13 12:50:08 +01:00
const RELATION _DEFINITION = 'relation-definition' ;
2018-08-15 22:06:49 +02:00
2018-08-22 23:37:06 +02:00
/ * *
* This represents a Note which is a central object in the Trilium Notes project .
*
* @ property { string } noteId - primary key
* @ property { string } type - one of "text" , "code" , "file" or "render"
* @ property { string } mime - MIME type , e . g . "text/html"
* @ property { string } title - note title
2019-11-09 09:36:08 +01:00
* @ property { int } contentLength - length of content
2018-08-22 23:37:06 +02:00
* @ property { boolean } isProtected - true if note is protected
* @ property { boolean } isDeleted - true if note is deleted
2019-11-01 22:09:51 +01:00
* @ property { boolean } isErased - true if note ' s content is erased after it has been deleted
2019-03-13 22:43:59 +01:00
* @ property { string } dateCreated - local date time ( with offset )
* @ property { string } dateModified - local date time ( with offset )
2019-03-12 20:58:31 +01:00
* @ property { string } utcDateCreated
* @ property { string } utcDateModified
2018-08-22 23:37:06 +02:00
*
* @ extends Entity
* /
2018-01-29 18:34:59 -05:00
class Note extends Entity {
2018-08-16 23:00:04 +02:00
static get entityName ( ) { return "notes" ; }
2018-01-30 20:12:19 -05:00
static get primaryKeyName ( ) { return "noteId" ; }
2019-02-06 20:19:25 +01:00
static get hashedProperties ( ) { return [ "noteId" , "title" , "type" , "isProtected" , "isDeleted" ] ; }
2018-01-29 23:35:36 -05:00
2018-08-22 23:37:06 +02:00
/ * *
* @ param row - object containing database row from "notes" table
* /
2018-03-31 10:51:37 -04:00
constructor ( row ) {
super ( row ) ;
2018-01-29 20:57:55 -05:00
2018-08-07 11:38:00 +02:00
this . isProtected = ! ! this . isProtected ;
2019-10-31 21:58:34 +01:00
/ * t r u e i f c o n t e n t i s e i t h e r n o t e n c r y p t e d
2018-10-30 22:18:20 +01:00
* or encrypted , but with available protected session ( so effectively decrypted ) * /
this . isContentAvailable = true ;
2018-08-07 11:38:00 +02:00
2018-04-08 12:27:10 -04:00
// check if there's noteId, otherwise this is a new entity which wasn't encrypted yet
if ( this . isProtected && this . noteId ) {
2018-10-30 22:18:20 +01:00
this . isContentAvailable = protectedSessionService . isProtectedSessionAvailable ( ) ;
2019-01-13 00:24:51 +01:00
if ( this . isContentAvailable ) {
2019-11-02 07:50:23 +01:00
this . title = protectedSessionService . decryptString ( this . title ) ;
2019-01-13 00:24:51 +01:00
}
else {
this . title = "[protected]" ;
}
2018-01-29 23:35:36 -05:00
}
2018-01-29 20:57:55 -05:00
}
2019-03-27 21:16:13 +01:00
/ *
* Note content has quite special handling - it ' s not a separate entity , but a lazily loaded
* part of Note entity with it ' s own sync . Reasons behind this hybrid design has been :
*
* - content can be quite large and it 's not necessary to load it / fill memory for any note access even if we don' t need a content , especially for bulk operations like search
* - changes in the note metadata or title should not trigger note content sync ( so we keep separate utcDateModified and sync rows )
* - 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 )
* /
2019-03-26 22:24:04 +01:00
/** @returns {Promise<*>} */
2019-04-13 19:34:19 +02:00
async getContent ( silentNotFoundError = false ) {
2019-03-26 22:24:04 +01:00
if ( this . content === undefined ) {
2019-03-27 21:27:29 +01:00
const res = await sql . getRow ( ` SELECT content, hash FROM note_contents WHERE noteId = ? ` , [ this . noteId ] ) ;
2019-04-13 19:34:19 +02:00
if ( ! res ) {
if ( silentNotFoundError ) {
return undefined ;
}
else {
throw new Error ( "Cannot find note content for noteId=" + this . noteId ) ;
}
}
2019-03-27 21:27:29 +01:00
this . content = res . content ;
2019-02-08 21:01:26 +01:00
2019-03-26 22:24:04 +01:00
if ( this . isProtected ) {
if ( this . isContentAvailable ) {
2019-10-31 21:58:34 +01:00
this . content = this . content === null ? null : protectedSessionService . decrypt ( this . content ) ;
2019-03-26 22:24:04 +01:00
}
else {
this . content = "" ;
}
2019-02-08 21:01:26 +01:00
}
2019-02-10 22:45:44 +01:00
if ( this . isStringNote ( ) ) {
2019-03-26 22:24:04 +01:00
this . content = this . content === null
? ""
: this . content . toString ( "UTF-8" ) ;
2019-02-10 22:45:44 +01:00
}
2018-04-08 08:31:19 -04:00
}
2019-02-06 20:19:25 +01:00
2019-03-26 22:24:04 +01:00
return this . content ;
2019-02-06 21:29:23 +01:00
}
2019-02-07 22:16:40 +01:00
/** @returns {Promise<*>} */
async getJsonContent ( ) {
const content = await this . getContent ( ) ;
return JSON . parse ( content ) ;
}
2019-02-08 21:01:26 +01:00
/** @returns {Promise} */
async setContent ( content ) {
2019-11-16 17:00:22 +01:00
if ( content === null || content === undefined ) {
throw new Error ( ` Cannot set null content to note ${ this . noteId } ` ) ;
}
2019-11-01 19:21:48 +01:00
// force updating note itself so that dateModified is represented correctly even for the content
2019-04-11 21:44:35 +02:00
this . forcedChange = true ;
2019-11-09 09:36:08 +01:00
this . contentLength = content . length ;
2019-04-11 21:44:35 +02:00
await this . save ( ) ;
2019-03-26 22:24:04 +01:00
this . content = content ;
const pojo = {
noteId : this . noteId ,
content : content ,
utcDateModified : dateUtils . utcNowDateTime ( ) ,
hash : utils . hash ( this . noteId + "|" + content )
} ;
if ( this . isProtected ) {
if ( this . isContentAvailable ) {
2019-10-31 21:58:34 +01:00
pojo . content = protectedSessionService . encrypt ( pojo . content ) ;
2019-03-26 22:24:04 +01:00
}
else {
throw new Error ( ` Cannot update content of noteId= ${ this . noteId } since we're out of protected session. ` ) ;
}
2019-02-08 21:01:26 +01:00
}
2019-03-26 22:24:04 +01:00
await sql . upsert ( "note_contents" , "noteId" , pojo ) ;
await syncTableService . addNoteContentSync ( this . noteId ) ;
2019-02-08 21:01:26 +01:00
}
/** @returns {Promise} */
async setJsonContent ( content ) {
2019-03-05 20:44:50 +01:00
await this . setContent ( JSON . stringify ( content , null , '\t' ) ) ;
2019-02-08 21:01:26 +01:00
}
2018-08-22 23:37:06 +02:00
/** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
2018-08-01 09:26:02 +02:00
isRoot ( ) {
return this . noteId === 'root' ;
}
2018-08-22 23:37:06 +02:00
/** @returns {boolean} true if this note is of application/json content type */
2018-01-29 23:17:44 -05:00
isJson ( ) {
2018-03-25 23:25:17 -04:00
return this . mime === "application/json" ;
2018-01-29 23:17:44 -05:00
}
2018-08-22 23:37:06 +02:00
/** @returns {boolean} true if this note is JavaScript (code or attachment) */
2018-02-18 10:47:02 -05:00
isJavaScript ( ) {
2018-03-01 22:30:06 -05:00
return ( this . type === "code" || this . type === "file" )
2018-11-30 22:28:30 +01:00
&& ( this . mime . startsWith ( "application/javascript" )
|| this . mime === "application/x-javascript"
|| this . mime === "text/javascript" ) ;
2018-02-18 10:47:02 -05:00
}
2018-08-22 23:37:06 +02:00
/** @returns {boolean} true if this note is HTML */
2018-03-04 21:05:14 -05:00
isHtml ( ) {
2018-05-26 19:27:47 -04:00
return ( this . type === "code" || this . type === "file" || this . type === "render" ) && this . mime === "text/html" ;
2018-03-04 21:05:14 -05:00
}
2019-02-10 22:45:44 +01:00
/** @returns {boolean} true if the note has string content (not binary) */
isStringNote ( ) {
2019-10-31 21:58:34 +01:00
return utils . isStringNote ( this . type , this . mime ) ;
2019-02-10 22:45:44 +01:00
}
2018-08-22 23:37:06 +02:00
/** @returns {string} JS script environment - either "frontend" or "backend" */
2018-03-05 23:09:36 -05:00
getScriptEnv ( ) {
if ( this . isHtml ( ) || ( this . isJavaScript ( ) && this . mime . endsWith ( 'env=frontend' ) ) ) {
return "frontend" ;
}
2018-03-07 00:17:18 -05:00
if ( this . type === 'render' ) {
return "frontend" ;
}
2018-03-05 23:09:36 -05:00
if ( this . isJavaScript ( ) && this . mime . endsWith ( 'env=backend' ) ) {
return "backend" ;
}
return null ;
}
2018-08-22 23:37:06 +02:00
/ * *
2018-08-23 15:33:19 +02:00
* @ returns { Promise < Attribute [ ] > } attributes belonging to this specific note ( excludes inherited attributes )
2019-09-07 21:40:18 +02:00
*
* This method can be significantly faster than the getAttributes ( )
2018-08-22 23:37:06 +02:00
* /
2019-09-07 20:43:59 +02:00
async getOwnedAttributes ( type , name ) {
let query = ` SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ` ;
const params = [ this . noteId ] ;
if ( type ) {
query += ` AND type = ? ` ;
params . push ( type ) ;
}
if ( name ) {
query += ` AND name = ? ` ;
params . push ( name ) ;
}
return await repository . getEntities ( query , params ) ;
}
/ * *
* @ returns { Promise < Attribute > } attribute belonging to this specific note ( excludes inherited attributes )
2019-09-07 21:40:18 +02:00
*
* This method can be significantly faster than the getAttribute ( )
2019-09-07 20:43:59 +02:00
* /
async getOwnedAttribute ( type , name ) {
const attrs = await this . getOwnedAttributes ( type , name ) ;
return attrs . length > 0 ? attrs [ 0 ] : null ;
2018-01-28 23:16:50 -05:00
}
2018-11-01 10:23:21 +01:00
/ * *
* @ returns { Promise < Attribute [ ] > } relations targetting this specific note
* /
async getTargetRelations ( ) {
return await repository . getEntities ( "SELECT * FROM attributes WHERE type = 'relation' AND isDeleted = 0 AND value = ?" , [ this . noteId ] ) ;
}
2018-09-01 13:18:55 +02:00
/ * *
* @ param { string } [ name ] - attribute name to filter
* @ returns { Promise < Attribute [ ] > } all note ' s attributes , including inherited ones
* /
async getAttributes ( name ) {
2018-08-13 17:16:06 +02:00
if ( ! this . _ _attributeCache ) {
await this . loadAttributesToCache ( ) ;
}
2018-09-01 13:18:55 +02:00
if ( name ) {
return this . _ _attributeCache . filter ( attr => attr . name === name ) ;
}
else {
return this . _ _attributeCache ;
}
2018-08-13 17:16:06 +02:00
}
2018-09-01 13:18:55 +02:00
/ * *
* @ param { string } [ name ] - label name to filter
* @ returns { Promise < Attribute [ ] > } all note ' s labels ( attributes with type label ) , including inherited ones
* /
async getLabels ( name ) {
return ( await this . getAttributes ( name ) ) . filter ( attr => attr . type === LABEL ) ;
2018-08-15 22:06:49 +02:00
}
2018-11-13 12:50:08 +01:00
/ * *
* @ param { string } [ name ] - label name to filter
* @ returns { Promise < Attribute [ ] > } all note ' s label definitions , including inherited ones
* /
async getLabelDefinitions ( name ) {
return ( await this . getAttributes ( name ) ) . filter ( attr => attr . type === LABEL _DEFINITION ) ;
}
2018-09-01 13:18:55 +02:00
/ * *
* @ param { string } [ name ] - relation name to filter
* @ returns { Promise < Attribute [ ] > } all note ' s relations ( attributes with type relation ) , including inherited ones
* /
async getRelations ( name ) {
return ( await this . getAttributes ( name ) ) . filter ( attr => attr . type === RELATION ) ;
2018-08-15 22:06:49 +02:00
}
2019-08-17 11:28:36 +02:00
/ * *
* @ param { string } [ name ] - relation name to filter
* @ returns { Promise < Note [ ] > }
* /
async getRelationTargets ( name ) {
const relations = await this . getRelations ( name ) ;
const targets = [ ] ;
for ( const relation of relations ) {
targets . push ( await relation . getTargetNote ( ) ) ;
}
return targets ;
}
2018-11-13 12:50:08 +01:00
/ * *
* @ param { string } [ name ] - relation name to filter
* @ returns { Promise < Attribute [ ] > } all note ' s relation definitions including inherited ones
* /
async getRelationDefinitions ( name ) {
return ( await this . getAttributes ( name ) ) . filter ( attr => attr . type === RELATION _DEFINITION ) ;
}
2018-08-22 23:37:06 +02:00
/ * *
* Clear note ' s attributes cache to force fresh reload for next attribute request .
* Cache is note instance scoped .
* /
2018-08-13 17:16:06 +02:00
invalidateAttributeCache ( ) {
this . _ _attributeCache = null ;
}
2018-08-22 23:37:06 +02:00
/** @returns {Promise<void>} */
2018-08-13 17:16:06 +02:00
async loadAttributesToCache ( ) {
2018-08-07 13:33:10 +02:00
const attributes = await repository . getEntities ( `
2018-08-13 17:05:16 +02:00
WITH RECURSIVE
tree ( noteId , level ) AS (
SELECT ? , 0
UNION
SELECT branches . parentNoteId , tree . level + 1 FROM branches
JOIN tree ON branches . noteId = tree . noteId
JOIN notes ON notes . noteId = branches . parentNoteId
WHERE notes . isDeleted = 0
AND branches . isDeleted = 0
) ,
treeWithAttrs ( noteId , level ) AS (
SELECT * FROM tree
UNION
2019-09-07 11:00:15 +02:00
SELECT attributes . value , treeWithAttrs . level FROM attributes
2018-08-13 17:05:16 +02:00
JOIN treeWithAttrs ON treeWithAttrs . noteId = attributes . noteId
WHERE attributes . isDeleted = 0
AND attributes . type = 'relation'
2018-08-21 12:52:11 +02:00
AND attributes . name = 'template'
2019-09-07 11:00:15 +02:00
AND ( treeWithAttrs . level = 0 OR attributes . isInheritable = 1 )
2018-08-13 17:05:16 +02:00
)
SELECT attributes . * FROM attributes JOIN treeWithAttrs ON attributes . noteId = treeWithAttrs . noteId
2019-09-07 11:00:15 +02:00
WHERE attributes . isDeleted = 0 AND ( attributes . isInheritable = 1 OR treeWithAttrs . level = 0 )
ORDER BY level , noteId , position ` , [this.noteId]);
2018-08-07 13:33:10 +02:00
// attributes are ordered so that "closest" attributes are first
// we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter.
const filteredAttributes = attributes . filter ( ( attr , index ) => {
if ( attr . isDefinition ( ) ) {
const firstDefinitionIndex = attributes . findIndex ( el => el . type === attr . type && el . name === attr . name ) ;
// keep only if this element is the first definition for this type & name
return firstDefinitionIndex === index ;
}
else {
const definitionAttr = attributes . find ( el => el . type === attr . type + '-definition' && el . name === attr . name ) ;
if ( ! definitionAttr ) {
return true ;
}
const definition = definitionAttr . value ;
if ( definition . multiplicityType === 'multivalue' ) {
return true ;
}
else {
const firstAttrIndex = attributes . findIndex ( el => el . type === attr . type && el . name === attr . name ) ;
// in case of single-valued attribute we'll keep it only if it's first (closest)
return firstAttrIndex === index ;
}
}
} ) ;
for ( const attr of filteredAttributes ) {
attr . isOwned = attr . noteId === this . noteId ;
2018-03-03 09:11:41 -05:00
}
2018-08-13 17:16:06 +02:00
this . _ _attributeCache = filteredAttributes ;
2018-03-03 09:11:41 -05:00
}
2018-08-22 23:37:06 +02:00
/ * *
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ returns { Promise < boolean > } true if note has an attribute with given type and name ( including inherited )
* /
2018-08-15 22:06:49 +02:00
async hasAttribute ( type , name ) {
return ! ! await this . getAttribute ( type , name ) ;
2018-03-04 22:09:51 -05:00
}
2018-08-22 23:37:06 +02:00
/ * *
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ returns { Promise < Attribute > } attribute of given type and name . If there 's more such attributes, first is returned. Returns null if there' s no such attribute belonging to this note .
* /
2018-08-15 22:06:49 +02:00
async getAttribute ( type , name ) {
2018-08-07 13:33:10 +02:00
const attributes = await this . getAttributes ( ) ;
2018-08-15 22:06:49 +02:00
return attributes . find ( attr => attr . type === type && attr . name === name ) ;
2018-01-29 17:41:59 -05:00
}
2018-08-22 23:37:06 +02:00
/ * *
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ returns { Promise < string > } attribute value of given type and name or null if no such attribute exists .
* /
2018-08-15 22:06:49 +02:00
async getAttributeValue ( type , name ) {
const attr = await this . getAttribute ( type , name ) ;
2018-08-13 10:59:31 +02:00
2018-08-15 22:06:49 +02:00
return attr ? attr . value : null ;
2018-08-13 10:59:31 +02:00
}
2018-08-22 23:37:06 +02:00
/ * *
* 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 )
* @ returns { Promise < void > }
* /
async toggleAttribute ( type , enabled , name , value ) {
2018-08-13 13:53:08 +02:00
if ( enabled ) {
2018-08-15 22:06:49 +02:00
await this . setAttribute ( type , name , value ) ;
2018-08-13 13:53:08 +02:00
}
else {
2018-08-15 22:06:49 +02:00
await this . removeAttribute ( type , name , value ) ;
2018-08-13 13:53:08 +02:00
}
}
2018-08-22 23:37:06 +02:00
/ * *
2019-11-08 22:34:30 +01:00
* Update 's given attribute' s value or creates it if it doesn ' t exist
2018-08-22 23:37:06 +02:00
*
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ param { string } [ value ] - attribute value ( optional )
* @ returns { Promise < void > }
* /
async setAttribute ( type , name , value ) {
2018-08-13 13:53:08 +02:00
const attributes = await this . getOwnedAttributes ( ) ;
2019-11-08 22:34:30 +01:00
let attr = attributes . find ( attr => attr . type === type && attr . name === name ) ;
2018-08-13 10:59:31 +02:00
2019-11-08 22:34:30 +01:00
if ( attr ) {
if ( attr . value !== value ) {
attr . value = value ;
await attr . save ( ) ;
this . invalidateAttributeCache ( ) ;
}
}
else {
2018-08-15 22:06:49 +02:00
attr = new Attribute ( {
2018-08-13 10:59:31 +02:00
noteId : this . noteId ,
2018-08-15 22:06:49 +02:00
type : type ,
2018-08-13 13:53:08 +02:00
name : name ,
2018-08-22 23:37:06 +02:00
value : value !== undefined ? value : ""
2018-08-13 10:59:31 +02:00
} ) ;
2018-08-15 22:06:49 +02:00
await attr . save ( ) ;
2018-08-13 17:16:06 +02:00
this . invalidateAttributeCache ( ) ;
2018-08-13 13:53:08 +02:00
}
2018-08-13 10:59:31 +02:00
}
2018-08-22 23:37:06 +02:00
/ * *
* 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 )
* @ returns { Promise < void > }
* /
async removeAttribute ( type , name , value ) {
2018-08-13 13:53:08 +02:00
const attributes = await this . getOwnedAttributes ( ) ;
2018-08-13 10:59:31 +02:00
2018-08-13 13:53:08 +02:00
for ( const attribute of attributes ) {
2018-08-22 23:37:06 +02:00
if ( attribute . type === type && ( value === undefined || value === attribute . value ) ) {
2018-08-13 13:53:08 +02:00
attribute . isDeleted = true ;
await attribute . save ( ) ;
2018-08-13 17:16:06 +02:00
this . invalidateAttributeCache ( ) ;
2018-08-13 13:53:08 +02:00
}
2018-08-13 10:59:31 +02:00
}
}
2019-11-14 23:10:56 +01:00
/ * *
* @ return { Promise < Attribute > }
* /
2019-11-16 11:09:52 +01:00
async addAttribute ( type , name , value = "" ) {
2019-11-14 23:10:56 +01:00
const attr = new Attribute ( {
noteId : this . noteId ,
type : type ,
name : name ,
value : value
} ) ;
await attr . save ( ) ;
this . invalidateAttributeCache ( ) ;
return attr ;
}
2019-11-16 11:09:52 +01:00
async addLabel ( name , value = "" ) {
return await this . addAttribute ( LABEL , name , value ) ;
2019-11-14 23:10:56 +01:00
}
2019-11-16 11:09:52 +01:00
async addRelation ( name , targetNoteId ) {
return await this . addAttribute ( RELATION , name , targetNoteId ) ;
2019-11-14 23:10:56 +01:00
}
2018-08-22 23:37:06 +02:00
/ * *
* @ param { string } name - label name
* @ returns { Promise < boolean > } true if label exists ( including inherited )
* /
2018-08-15 22:06:49 +02:00
async hasLabel ( name ) { return await this . hasAttribute ( LABEL , name ) ; }
2018-08-22 23:37:06 +02:00
/ * *
* @ param { string } name - relation name
* @ returns { Promise < boolean > } true if relation exists ( including inherited )
* /
2018-08-15 22:06:49 +02:00
async hasRelation ( name ) { return await this . hasAttribute ( RELATION , name ) ; }
2018-08-22 23:37:06 +02:00
/ * *
* @ param { string } name - label name
* @ returns { Promise < Attribute > } label if it exists , null otherwise
* /
2018-08-15 22:06:49 +02:00
async getLabel ( name ) { return await this . getAttribute ( LABEL , name ) ; }
2018-08-22 23:37:06 +02:00
/ * *
* @ param { string } name - relation name
* @ returns { Promise < Attribute > } relation if it exists , null otherwise
* /
2018-08-15 22:06:49 +02:00
async getRelation ( name ) { return await this . getAttribute ( RELATION , name ) ; }
2018-08-22 23:37:06 +02:00
/ * *
* @ param { string } name - label name
* @ returns { Promise < string > } label value if label exists , null otherwise
* /
2018-08-15 22:06:49 +02:00
async getLabelValue ( name ) { return await this . getAttributeValue ( LABEL , name ) ; }
2018-08-22 23:37:06 +02:00
/ * *
* @ param { string } name - relation name
* @ returns { Promise < string > } relation value if relation exists , null otherwise
* /
async getRelationValue ( name ) { return await this . getAttributeValue ( RELATION , name ) ; }
2018-08-15 22:06:49 +02:00
2018-12-22 22:16:32 +01:00
/ * *
* @ param { string } name
* @ returns { Promise < Note > | 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 ;
}
2018-08-22 23:37:06 +02:00
/ * *
* 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 )
* @ returns { Promise < void > }
* /
async toggleLabel ( enabled , name , value ) { return await 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 )
* @ returns { Promise < void > }
* /
async toggleRelation ( enabled , name , value ) { return await this . toggleAttribute ( RELATION , enabled , name , value ) ; }
/ * *
2019-11-08 22:34:30 +01:00
* Update 's given label' s value or creates it if it doesn ' t exist
2018-08-22 23:37:06 +02:00
*
* @ param { string } name - label name
* @ param { string } [ value ] - label value
* @ returns { Promise < void > }
* /
async setLabel ( name , value ) { return await this . setAttribute ( LABEL , name , value ) ; }
/ * *
2019-11-08 22:34:30 +01:00
* Update 's given relation' s value or creates it if it doesn ' t exist
2018-08-22 23:37:06 +02:00
*
* @ param { string } name - relation name
* @ param { string } [ value ] - relation value ( noteId )
* @ returns { Promise < void > }
* /
async setRelation ( name , value ) { return await this . setAttribute ( RELATION , name , value ) ; }
/ * *
* Remove label name - value pair , if it exists .
*
* @ param { string } name - label name
* @ param { string } [ value ] - label value
* @ returns { Promise < void > }
* /
async removeLabel ( name , value ) { return await this . removeAttribute ( LABEL , name , value ) ; }
/ * *
* Remove relation name - value pair , if it exists .
*
* @ param { string } name - relation name
* @ param { string } [ value ] - relation value ( noteId )
* @ returns { Promise < void > }
* /
async removeRelation ( name , value ) { return await this . removeAttribute ( RELATION , name , value ) ; }
/ * *
2018-11-25 22:09:52 +01:00
* @ return { Promise < string [ ] > } return list of all descendant noteIds of this note . Returning just noteIds because number of notes can be huge . Includes also this note ' s noteId
* /
async getDescendantNoteIds ( ) {
return await sql . getColumn ( `
WITH RECURSIVE
tree ( noteId ) AS (
SELECT ?
UNION
SELECT branches . noteId FROM branches
JOIN tree ON branches . parentNoteId = tree . noteId
JOIN notes ON notes . noteId = branches . noteId
WHERE notes . isDeleted = 0
AND branches . isDeleted = 0
)
SELECT noteId FROM tree ` , [this.noteId]);
}
/ * *
* Finds descendant notes with given attribute name and value . Only own attributes are considered , not inherited ones
2018-08-22 23:37:06 +02:00
*
* @ param { string } type - attribute type ( label , relation , etc . )
* @ param { string } name - attribute name
* @ param { string } [ value ] - attribute value
2018-08-23 15:33:19 +02:00
* @ returns { Promise < Note [ ] > }
2018-08-22 23:37:06 +02:00
* /
2018-11-25 22:09:52 +01:00
async getDescendantNotesWithAttribute ( type , name , value ) {
2018-08-21 12:50:43 +02:00
const params = [ this . noteId , name ] ;
let valueCondition = "" ;
if ( value !== undefined ) {
params . push ( value ) ;
valueCondition = " AND attributes.value = ?" ;
}
const notes = await repository . getEntities ( `
WITH RECURSIVE
tree ( noteId ) AS (
SELECT ?
UNION
SELECT branches . noteId FROM branches
JOIN tree ON branches . parentNoteId = tree . noteId
JOIN notes ON notes . noteId = branches . noteId
WHERE notes . isDeleted = 0
AND branches . isDeleted = 0
)
SELECT notes . * FROM notes
JOIN tree ON tree . noteId = notes . noteId
JOIN attributes ON attributes . noteId = notes . noteId
WHERE attributes . isDeleted = 0
AND attributes . name = ?
$ { valueCondition }
ORDER BY noteId , position ` , params);
return notes ;
}
2018-08-22 23:37:06 +02:00
/ * *
2018-11-25 22:09:52 +01:00
* Finds descendant notes with given label name and value . Only own labels are considered , not inherited ones
2018-08-22 23:37:06 +02:00
*
* @ param { string } name - label name
* @ param { string } [ value ] - label value
2018-08-23 15:33:19 +02:00
* @ returns { Promise < Note [ ] > }
2018-08-22 23:37:06 +02:00
* /
2018-11-25 22:09:52 +01:00
async getDescendantNotesWithLabel ( name , value ) { return await this . getDescendantNotesWithAttribute ( LABEL , name , value ) ; }
2018-08-22 23:37:06 +02:00
/ * *
2018-11-25 22:09:52 +01:00
* Finds descendant notes with given relation name and value . Only own relations are considered , not inherited ones
2018-08-22 23:37:06 +02:00
*
* @ param { string } name - relation name
* @ param { string } [ value ] - relation value
2018-08-23 15:33:19 +02:00
* @ returns { Promise < Note [ ] > }
2018-08-22 23:37:06 +02:00
* /
2018-11-25 22:09:52 +01:00
async getDescendantNotesWithRelation ( name , value ) { return await this . getDescendantNotesWithAttribute ( RELATION , name , value ) ; }
2018-08-21 12:50:43 +02:00
2018-08-22 23:37:06 +02:00
/ * *
* Returns note revisions of this note .
*
2018-08-23 15:33:19 +02:00
* @ returns { Promise < NoteRevision [ ] > }
2018-08-22 23:37:06 +02:00
* /
2018-01-29 18:34:59 -05:00
async getRevisions ( ) {
2018-03-31 10:51:37 -04:00
return await repository . getEntities ( "SELECT * FROM note_revisions WHERE noteId = ?" , [ this . noteId ] ) ;
2018-01-29 18:34:59 -05:00
}
2018-08-22 23:37:06 +02:00
/ * *
2018-11-15 13:58:14 +01:00
* Get list of links coming out of this note .
*
2019-08-19 20:12:00 +02:00
* @ deprecated - not intended for general use
* @ returns { Promise < Attribute [ ] > }
2018-08-22 23:37:06 +02:00
* /
2018-11-08 11:08:16 +01:00
async getLinks ( ) {
2019-08-19 20:12:00 +02:00
return await repository . getEntities ( `
SELECT *
FROM attributes
WHERE noteId = ? AND
isDeleted = 0 AND
type = 'relation' AND
2019-11-23 20:54:49 +01:00
name IN ( 'internalLink' , 'imageLink' , 'relationMapLink' ) ` , [this.noteId]);
2018-03-31 22:15:06 -04:00
}
2018-08-22 23:37:06 +02:00
/ * *
2018-08-23 15:33:19 +02:00
* @ returns { Promise < Branch [ ] > }
2018-08-22 23:37:06 +02:00
* /
2018-03-31 23:08:22 -04:00
async getBranches ( ) {
2018-03-31 10:51:37 -04:00
return await repository . getEntities ( "SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?" , [ this . noteId ] ) ;
2018-01-28 23:16:50 -05:00
}
2018-01-29 23:35:36 -05:00
2018-09-03 09:40:22 +02:00
/ * *
* @ returns { boolean } - true if note has children
* /
async hasChildren ( ) {
return ( await this . getChildNotes ( ) ) . length > 0 ;
}
2018-08-22 23:37:06 +02:00
/ * *
2018-08-23 15:33:19 +02:00
* @ returns { Promise < Note [ ] > } child notes of this note
2018-08-22 23:37:06 +02:00
* /
2018-03-31 23:08:22 -04:00
async getChildNotes ( ) {
2018-03-31 10:51:37 -04:00
return await repository . getEntities ( `
2018-02-24 14:42:52 -05:00
SELECT notes . *
2018-03-24 21:39:15 -04:00
FROM branches
2018-02-24 14:42:52 -05:00
JOIN notes USING ( noteId )
WHERE notes . isDeleted = 0
2018-03-24 21:39:15 -04:00
AND branches . isDeleted = 0
AND branches . parentNoteId = ?
ORDER BY branches . notePosition ` , [this.noteId]);
2018-02-24 14:42:52 -05:00
}
2018-08-22 23:37:06 +02:00
/ * *
2018-08-23 15:33:19 +02:00
* @ returns { Promise < Branch [ ] > } child branches of this note
2018-08-22 23:37:06 +02:00
* /
2018-03-31 23:08:22 -04:00
async getChildBranches ( ) {
return await repository . getEntities ( `
SELECT branches . *
FROM branches
WHERE branches . isDeleted = 0
AND branches . parentNoteId = ?
ORDER BY branches . notePosition ` , [this.noteId]);
}
2018-08-22 23:37:06 +02:00
/ * *
2018-08-23 15:33:19 +02:00
* @ returns { Promise < Note [ ] > } parent notes of this note ( note can have multiple parents because of cloning )
2018-08-22 23:37:06 +02:00
* /
2018-03-31 23:08:22 -04:00
async getParentNotes ( ) {
2018-03-31 10:51:37 -04:00
return await repository . getEntities ( `
2018-02-24 14:42:52 -05:00
SELECT parent _notes . *
FROM
2018-03-24 21:39:15 -04:00
branches AS child _tree
2018-02-24 14:42:52 -05:00
JOIN notes AS parent _notes ON parent _notes . noteId = child _tree . parentNoteId
WHERE child _tree . noteId = ?
AND child _tree . isDeleted = 0
AND parent _notes . isDeleted = 0 ` , [this.noteId]);
}
2019-11-16 19:07:32 +01:00
/ * *
* @ return { Promise < string [ ] [ ] > } - array of notePaths ( each represented by array of noteIds constituting the particular note path )
* /
async getAllNotePaths ( ) {
if ( this . noteId === 'root' ) {
return [ [ 'root' ] ] ;
}
const notePaths = [ ] ;
for ( const parentNote of await this . getParentNotes ( ) ) {
for ( const parentPath of await parentNote . getAllNotePaths ( ) ) {
parentPath . push ( this . noteId ) ;
notePaths . push ( parentPath ) ;
}
}
return notePaths ;
}
2018-01-29 23:35:36 -05:00
beforeSaving ( ) {
2018-04-01 17:38:24 -04:00
if ( ! this . isDeleted ) {
this . isDeleted = false ;
}
2019-03-13 22:43:59 +01:00
if ( ! this . dateCreated ) {
this . dateCreated = dateUtils . localNowDateTime ( ) ;
}
2019-03-12 20:58:31 +01:00
if ( ! this . utcDateCreated ) {
2019-03-13 22:43:59 +01:00
this . utcDateCreated = dateUtils . utcNowDateTime ( ) ;
2018-03-31 22:15:06 -04:00
}
2019-11-09 09:36:08 +01:00
if ( this . contentLength === undefined ) {
this . contentLength = - 1 ;
}
2018-08-12 20:04:48 +02:00
super . beforeSaving ( ) ;
if ( this . isChanged ) {
2019-03-13 22:43:59 +01:00
this . dateModified = dateUtils . localNowDateTime ( ) ;
this . utcDateModified = dateUtils . utcNowDateTime ( ) ;
2018-08-12 20:04:48 +02:00
}
2018-01-29 23:35:36 -05:00
}
2018-11-30 10:20:03 +01:00
// cannot be static!
updatePojo ( pojo ) {
if ( pojo . isProtected ) {
2019-01-13 00:24:51 +01:00
if ( this . isContentAvailable ) {
2019-10-31 21:58:34 +01:00
pojo . title = protectedSessionService . encrypt ( pojo . title ) ;
2019-01-13 00:24:51 +01:00
}
else {
// updating protected note outside of protected session means we will keep original ciphertexts
2019-03-26 22:24:04 +01:00
delete pojo . title ;
2019-01-13 00:24:51 +01:00
}
2018-11-30 10:20:03 +01:00
}
delete pojo . isContentAvailable ;
delete pojo . _ _attributeCache ;
2019-03-26 22:24:04 +01:00
delete pojo . content ;
2019-11-26 22:02:21 +01:00
/** zero references to contentHash, probably can be removed */
2019-03-28 21:17:40 +01:00
delete pojo . contentHash ;
2018-11-30 10:20:03 +01:00
}
2018-01-28 23:16:50 -05:00
}
module . exports = Note ;