2020-05-17 09:48:24 +02:00
"use strict" ;
2024-02-17 00:44:44 +02:00
import protectedSessionService = require ( '../../services/protected_session' ) ;
import log = require ( '../../services/log' ) ;
import sql = require ( '../../services/sql' ) ;
import utils = require ( '../../services/utils' ) ;
import dateUtils = require ( '../../services/date_utils' ) ;
2024-02-17 11:16:00 +02:00
import AbstractBeccaEntity = require ( './abstract_becca_entity' ) ;
2024-02-17 10:56:27 +02:00
import BRevision = require ( './brevision' ) ;
import BAttachment = require ( './battachment' ) ;
import TaskContext = require ( '../../services/task_context' ) ;
2024-02-17 00:44:44 +02:00
import dayjs = require ( "dayjs" ) ;
import utc = require ( 'dayjs/plugin/utc' ) ;
2024-02-17 10:56:27 +02:00
import eventService = require ( '../../services/events' ) ;
import { AttachmentRow , NoteRow , NoteType , RevisionRow } from './rows' ;
import BBranch = require ( './bbranch' ) ;
import BAttribute = require ( './battribute' ) ;
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' ;
2024-02-17 10:56:27 +02:00
interface NotePathRecord {
isArchived : boolean ;
isInHoistedSubTree : boolean ;
notePath : string [ ] ;
isHidden : boolean ;
}
2023-08-21 04:15:53 -04:00
2024-02-17 10:56:27 +02:00
interface ContentOpts {
/** will also save this BNote entity */
forceSave? : boolean ;
/** override frontend heuristics on when to reload, instruct to reload */
forceFrontendReload? : boolean ;
}
interface AttachmentOpts {
includeContentLength? : boolean ;
}
interface Relationship {
parentNoteId : string ;
childNoteId : string
}
interface ConvertOpts {
/** if true, the action is not triggered by user, but e.g. by migration, and only perfect candidates will be migrated */
autoConversion? : boolean ;
}
2023-08-21 04:15:53 -04:00
2021-11-10 21:30:54 +01:00
/ * *
2023-06-30 11:18:34 +02:00
* Trilium ' s main entity , which can represent text note , image , code note , file attachment etc .
2021-11-10 21:30:54 +01:00
* /
2024-02-17 10:56:27 +02:00
class BNote extends AbstractBeccaEntity < BNote > {
2021-04-25 20:00:42 +02:00
static get entityName() { return "notes" ; }
static get primaryKeyName() { return "noteId" ; }
2023-06-28 21:05:31 +02:00
static get hashedProperties() { return [ "noteId" , "title" , "isProtected" , "type" , "mime" , "blobId" ] ; }
2021-04-25 20:00:42 +02:00
2024-02-17 10:56:27 +02:00
noteId ! : string ;
title ! : string ;
type ! : NoteType ;
mime ! : string ;
/** set during the deletion operation, before it is completed (removed from becca completely). */
isBeingDeleted ! : boolean ;
isDecrypted ! : boolean ;
2024-02-17 11:13:53 +02:00
ownedAttributes ! : BAttribute [ ] ;
parentBranches ! : BBranch [ ] ;
parents ! : BNote [ ] ;
children ! : BNote [ ] ;
targetRelations ! : BAttribute [ ] ;
2024-02-17 20:45:31 +02:00
__flatTextCache ! : string | null ;
2024-02-17 10:56:27 +02:00
private __attributeCache ! : BAttribute [ ] | null ;
private __inheritableAttributeCache ! : BAttribute [ ] | null ;
private __ancestorCache ! : BNote [ ] | null ;
// following attributes are filled during searching in the database
/** size of the content in bytes */
private contentSize ! : number | null ;
/** size of the note content, attachment contents in bytes */
private contentAndAttachmentsSize ! : number | null ;
/** size of the note content, attachment contents and revision contents in bytes */
private contentAndAttachmentsAndRevisionsSize ! : number | null ;
/** number of note revisions for this note */
private revisionCount ! : number | null ;
2024-02-17 20:45:31 +02:00
constructor ( row? : Partial < NoteRow > ) {
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 ( ) ;
}
2024-02-17 11:13:53 +02:00
updateFromRow ( row : Partial < NoteRow > ) {
2021-07-24 21:10:16 +02:00
this . update ( [
row . noteId ,
row . title ,
row . type ,
row . mime ,
row . isProtected ,
2023-03-15 22:44:08 +01:00
row . blobId ,
2021-07-24 21:10:16 +02:00
row . dateCreated ,
row . dateModified ,
row . utcDateCreated ,
row . utcDateModified
] ) ;
}
2024-02-17 10:56:27 +02:00
update ( [ noteId , title , type , mime , isProtected , blobId , dateCreated , dateModified , utcDateCreated , utcDateModified ] : any ) {
2021-07-24 21:10:16 +02:00
// ------ Database persisted attributes ------
this . noteId = noteId ;
this . title = title ;
this . type = type ;
this . mime = mime ;
2023-03-15 22:44:08 +01:00
this . isProtected = ! ! isProtected ;
this . blobId = blobId ;
2021-07-24 21:10:16 +02:00
this . dateCreated = dateCreated || dateUtils . localNowDateTime ( ) ;
this . dateModified = dateModified ;
this . utcDateCreated = utcDateCreated || dateUtils . utcNowDateTime ( ) ;
this . utcDateModified = utcDateModified ;
2022-12-06 16:11:43 +01:00
this . isBeingDeleted = false ;
2021-07-24 21:10:16 +02:00
// ------ Derived attributes ------
2021-10-28 22:02:18 +02:00
this . isDecrypted = ! this . noteId || ! this . isProtected ;
2021-07-24 21:10:16 +02:00
this . decrypt ( ) ;
2023-06-01 00:07:57 +02:00
this . __flatTextCache = null ;
2021-07-24 21:10:16 +02:00
return this ;
}
2020-09-03 21:37:06 +02:00
2021-07-24 21:10:16 +02:00
init() {
2020-09-03 21:37:06 +02:00
this . parentBranches = [ ] ;
this . parents = [ ] ;
this . children = [ ] ;
this . ownedAttributes = [ ] ;
2021-04-25 21:19:18 +02:00
this . __attributeCache = null ;
2023-05-31 23:21:06 +02:00
this . __inheritableAttributeCache = null ;
2020-09-03 21:37:06 +02:00
this . targetRelations = [ ] ;
2021-10-06 19:59:29 +02:00
this . becca . addNote ( this . noteId , this ) ;
2023-06-01 00:07:57 +02:00
this . __ancestorCache = null ;
2021-01-22 22:20:17 +01:00
this . contentSize = null ;
2023-10-02 22:02:25 +02:00
this . contentAndAttachmentsSize = null ;
this . contentAndAttachmentsAndRevisionsSize = null ;
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 ;
}
2024-02-17 11:39:29 +02:00
getChildBranches ( ) : ( BBranch | null ) [ ] {
2024-02-17 11:54:55 +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 )
* /
2024-02-17 22:58:54 +02:00
getContent() {
return this . _getContent ( ) ;
2021-04-25 20:00:42 +02:00
}
2023-09-08 21:53:57 +02:00
/ * *
* @throws Error in case of invalid JSON * /
2024-02-17 11:13:53 +02:00
getJsonContent ( ) : { } | null {
const content = this . getContent ( ) ;
2021-04-25 20:00:42 +02:00
2024-02-17 22:58:54 +02:00
if ( typeof content !== "string" || ! content || ! content . trim ( ) ) {
2021-04-25 20:00:42 +02:00
return null ;
}
return JSON . parse ( content ) ;
}
2023-09-08 21:53:57 +02:00
/** @returns {*|null} valid object or null if the content cannot be parsed as JSON */
getJsonContentSafely() {
try {
return this . getJsonContent ( ) ;
}
catch ( e ) {
return null ;
}
}
2024-02-17 22:58:54 +02:00
setContent ( content : Buffer | string , opts : ContentOpts = { } ) {
2023-03-16 16:37:31 +01:00
this . _setContent ( content , opts ) ;
2023-05-05 15:42:53 +02:00
eventService . emit ( eventService . NOTE_CONTENT_CHANGE , { entity : this } ) ;
2023-03-16 15:19:26 +01:00
}
2021-04-25 20:00:42 +02:00
2024-02-17 10:56:27 +02:00
setJsonContent ( content : { } ) {
2023-03-16 15:19:26 +01:00
this . setContent ( JSON . stringify ( content , null , '\t' ) ) ;
}
2021-04-25 20:00:42 +02:00
2023-03-16 15:19:26 +01:00
get dateCreatedObj() {
return this . dateCreated === null ? null : dayjs ( this . dateCreated ) ;
}
2021-04-25 20:00:42 +02:00
2023-03-16 15:19:26 +01:00
get utcDateCreatedObj() {
return this . utcDateCreated === null ? null : dayjs . utc ( this . utcDateCreated ) ;
}
2023-03-15 22:44:08 +01:00
2023-03-16 15:19:26 +01:00
get dateModifiedObj() {
return this . dateModified === null ? null : dayjs ( this . dateModified ) ;
2021-04-25 20:00:42 +02:00
}
2023-03-16 15:19:26 +01:00
get utcDateModifiedObj() {
return this . utcDateModified === null ? null : dayjs . utc ( this . utcDateModified ) ;
2021-04-25 20:00:42 +02:00
}
/** @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" ;
}
2023-02-17 14:49:45 +01:00
/** @returns {boolean} true if this note is JavaScript (code or attachment) */
2021-04-25 20:00:42 +02:00
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/' ) ) ;
}
2023-05-05 16:37:39 +02:00
/** @deprecated use hasStringContent() instead */
2021-04-25 20:00:42 +02:00
isStringNote() {
2023-05-05 16:37:39 +02:00
return this . hasStringContent ( ) ;
}
/** @returns {boolean} true if the note has string content (not binary) */
hasStringContent() {
2021-04-25 20:00:42 +02:00
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
/ * *
2023-07-17 22:53:54 +02:00
* Beware that the method must not create a copy of the array , but actually returns its internal array
* ( for performance reasons )
*
2024-02-17 10:56:27 +02:00
* @param type - ( optional ) attribute type to filter
* @param name - ( optional ) attribute name to filter
* @returns all note ' s attributes , including inherited ones
2021-04-25 21:19:18 +02:00
* /
2024-02-17 10:56:27 +02:00
getAttributes ( type ? : string , name? : string ) : BAttribute [ ] {
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
2024-02-17 10:56:27 +02:00
if ( ! this . __attributeCache ) {
throw new Error ( "Attribute cache not available." ) ;
}
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
return this . __attributeCache ;
2021-04-25 21:19:18 +02:00
}
2020-06-04 23:58:31 +02:00
}
2024-02-17 10:56:27 +02:00
private __ensureAttributeCacheIsAvailable() {
2023-01-27 08:46:04 +01:00
if ( ! this . __attributeCache ) {
this . __getAttributes ( [ ] ) ;
}
}
2024-02-17 10:56:27 +02:00
private __getAttributes ( path : string [ ] ) {
2020-06-04 23:58:31 +02:00
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
}
}
2023-05-31 23:21:06 +02:00
this . __inheritableAttributeCache = [ ] ;
2020-05-16 23:12:29 +02:00
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 ) {
2023-05-31 23:21:06 +02:00
this . __inheritableAttributeCache . push ( attr ) ;
2020-05-16 23:12:29 +02:00
}
}
}
2021-04-25 21:19:18 +02:00
return this . __attributeCache ;
2020-05-16 23:12:29 +02:00
}
2024-02-17 10:56:27 +02:00
private __getInheritableAttributes ( path : string [ ] ) : BAttribute [ ] {
2020-06-04 23:58:31 +02:00
if ( path . includes ( this . noteId ) ) {
return [ ] ;
}
2023-05-31 23:21:06 +02:00
if ( ! this . __inheritableAttributeCache ) {
this . __getAttributes ( path ) ; // will refresh also this.__inheritableAttributeCache
2020-05-16 23:12:29 +02:00
}
2024-02-17 10:56:27 +02:00
return this . __inheritableAttributeCache || [ ] ;
2020-05-16 23:12:29 +02:00
}
2024-02-17 10:56:27 +02:00
__validateTypeName ( type ? : string | null , name? : string | null ) {
2022-12-17 21:46:51 +01:00
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. ` ) ;
}
}
}
2024-02-17 10:56:27 +02:00
hasAttribute ( type : string , name : string , value : string | null = null ) : boolean {
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
}
2024-02-17 10:56:27 +02:00
getAttributeCaseInsensitive ( type : string , name : string , value : string | null ) {
2020-12-15 15:09:00 +01:00
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
}
2024-02-17 10:56:27 +02:00
getRelationTarget ( name : string ) {
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
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name
* @param value - label value
* @returns true if label exists ( including inherited )
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
hasLabel ( name : string , value? : string ) : boolean {
return this . hasAttribute ( LABEL , name , value ) ;
}
2021-04-17 20:52:46 +02:00
2023-04-03 21:08:32 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name
* @returns true if label exists ( including inherited ) and does not have "false" value .
2023-04-03 21:08:32 +02:00
* /
2024-02-17 10:56:27 +02:00
isLabelTruthy ( name : string ) : boolean {
2023-04-03 21:08:32 +02:00
const label = this . getLabel ( name ) ;
if ( ! label ) {
return false ;
}
return label && label . value !== 'false' ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name
* @param value - label value
* @returns true if label exists ( excluding inherited )
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
hasOwnedLabel ( name : string , value? : string ) : boolean {
return this . hasOwnedAttribute ( LABEL , name , value ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - relation name
* @param value - relation value
* @returns true if relation exists ( including inherited )
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
hasRelation ( name : string , value? : string ) : boolean {
return this . hasAttribute ( RELATION , name , value ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - relation name
* @param value - relation value
* @returns true if relation exists ( excluding inherited )
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
hasOwnedRelation ( name : string , value? : string ) : boolean {
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
* /
2024-02-17 10:56:27 +02:00
getLabel ( name : string ) : BAttribute | null {
return this . getAttribute ( LABEL , name ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name
* @returns label if it exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
getOwnedLabel ( name : string ) : BAttribute | null {
return this . getOwnedAttribute ( LABEL , name ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - relation name
* @returns relation if it exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
getRelation ( name : string ) : BAttribute | null {
return this . getAttribute ( RELATION , name ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - relation name
* @returns relation if it exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
getOwnedRelation ( name : string ) : BAttribute | null {
return this . getOwnedAttribute ( RELATION , name ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name
* @returns label value if label exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
getLabelValue ( name : string ) : string | null {
return this . getAttributeValue ( LABEL , name ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name
* @returns label value if label exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
getOwnedLabelValue ( name : string ) : string | null {
return this . getOwnedAttributeValue ( LABEL , name ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - relation name
* @returns relation value if relation exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
getRelationValue ( name : string ) : string | null {
return this . getAttributeValue ( RELATION , name ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param name - relation name
* @returns relation value if relation exists , null otherwise
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
getOwnedRelationValue ( name : string ) : string | null {
return this . getOwnedAttributeValue ( RELATION , name ) ;
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param attribute type ( label , relation , etc . )
* @param name - attribute name
* @param value - attribute value
* @returns true if note has an attribute with given type and name ( excluding inherited )
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
hasOwnedAttribute ( type : string , name : string , value? : string ) : boolean {
2022-06-19 14:06:00 +02:00
return ! ! this . getOwnedAttribute ( type , name , value ) ;
2021-01-23 21:00:59 +01:00
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param type - attribute type ( label , relation , etc . )
* @param name - attribute name
* @returns attribute of the given type and name . If there are 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
* /
2024-02-17 10:56:27 +02:00
getAttribute ( type : string , name : string ) : BAttribute | null {
2021-04-17 20:52:46 +02:00
const attributes = this . getAttributes ( ) ;
2020-05-25 00:25:47 +02:00
2024-02-17 10:56:27 +02:00
return attributes . find ( attr = > attr . name === name && attr . type === type ) || null ;
2020-05-25 00:25:47 +02:00
}
2021-04-17 20:52:46 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @param type - attribute type ( label , relation , etc . )
* @param name - attribute name
* @returns attribute value of given type and name or null if no such attribute exists .
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
getAttributeValue ( type : string , name : string ) : string | null {
2021-04-17 20:52:46 +02:00
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 ;
}
/ * *
2024-02-17 10:56:27 +02:00
* @param type - attribute type ( label , relation , etc . )
* @param name - attribute name
* @returns attribute value of given type and name or null if no such attribute exists .
2021-04-17 20:52:46 +02:00
* /
2024-02-17 10:56:27 +02:00
getOwnedAttributeValue ( type : string , name : string ) : string | null {
2021-04-17 20:52:46 +02:00
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
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name to filter
* @returns all note ' s labels ( attributes with type label ) , including inherited ones
2021-04-25 21:19:18 +02:00
* /
2024-02-17 10:56:27 +02:00
getLabels ( name? : string ) : BAttribute [ ] {
2021-04-25 21:19:18 +02:00
return this . getAttributes ( LABEL , name ) ;
}
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name to filter
* @returns all note ' s label values , including inherited ones
2021-04-25 21:19:18 +02:00
* /
2024-02-17 10:56:27 +02:00
getLabelValues ( name : string ) : string [ ] {
2021-04-25 21:19:18 +02:00
return this . getLabels ( name ) . map ( l = > l . value ) ;
}
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name to filter
* @returns all note ' s labels ( attributes with type label ) , excluding inherited ones
2021-04-25 21:19:18 +02:00
* /
2024-02-17 10:56:27 +02:00
getOwnedLabels ( name : string ) : BAttribute [ ] {
2021-04-25 21:19:18 +02:00
return this . getOwnedAttributes ( LABEL , name ) ;
}
/ * *
2024-02-17 10:56:27 +02:00
* @param name - label name to filter
* @returns all note ' s label values , excluding inherited ones
2021-04-25 21:19:18 +02:00
* /
2024-02-17 10:56:27 +02:00
getOwnedLabelValues ( name : string ) : string [ ] {
2021-04-25 21:19:18 +02:00
return this . getOwnedAttributes ( LABEL , name ) . map ( l = > l . value ) ;
}
/ * *
2024-02-17 10:56:27 +02:00
* @param name - relation name to filter
* @returns all note ' s relations ( attributes with type relation ) , including inherited ones
2021-04-25 21:19:18 +02:00
* /
2024-02-17 22:58:54 +02:00
getRelations ( name? : string ) : BAttribute [ ] {
2021-04-25 21:19:18 +02:00
return this . getAttributes ( RELATION , name ) ;
}
/ * *
2024-02-17 10:56:27 +02:00
* @param name - relation name to filter
* @returns all note ' s relations ( attributes with type relation ) , excluding inherited ones
2021-04-25 21:19:18 +02:00
* /
2024-02-17 10:56:27 +02:00
getOwnedRelations ( name : string ) : BAttribute [ ] {
2021-04-25 21:19:18 +02:00
return this . getOwnedAttributes ( RELATION , name ) ;
}
2021-04-25 22:02:32 +02:00
/ * *
2023-07-17 22:53:54 +02:00
* Beware that the method must not create a copy of the array , but actually returns its internal array
* ( for performance reasons )
*
2024-02-17 10:56:27 +02:00
* @param type - ( optional ) attribute type to filter
* @param name - ( optional ) attribute name to filter
* @param 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
* /
2024-02-17 10:56:27 +02:00
getOwnedAttributes ( type : string | null = null , name : string | null = null , value : string | null = 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 {
2023-06-27 23:03:17 +02:00
return this . ownedAttributes ;
2021-04-25 22:02:32 +02:00
}
}
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 ( )
* /
2024-02-17 10:56:27 +02:00
getOwnedAttribute ( type : string , name : string , value : string | null = 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-04-16 09:22:24 +02:00
areAllNotePathsArchived() {
// there's a slight difference between note being itself archived and all its note paths being archived
// - note is archived when it itself has an archived label or inherits it
2023-06-30 11:18:34 +02:00
// - note does not have or inherit archived label, but each note path contains a note with (non-inheritable)
2023-04-16 09:22:24 +02:00
// archived label
const bestNotePathRecord = this . getSortedNotePathRecords ( ) [ 0 ] ;
if ( ! bestNotePathRecord ) {
throw new Error ( ` No note path available for note ' ${ this . noteId } ' ` ) ;
}
return bestNotePathRecord . isArchived ;
}
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 {
2023-11-03 01:11:47 +01:00
return 0 ;
2023-01-27 16:57:23 +01:00
}
} ) ;
2021-02-24 22:38:26 +01:00
2022-08-16 23:46:56 +02:00
this . parents = this . parentBranches
. map ( branch = > branch . parentNote )
2024-02-17 11:39:29 +02:00
. filter ( note = > ! ! note ) as BNote [ ] ;
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 ) ;
2024-02-17 11:39:29 +02:00
return ( ( aBranch ? . notePosition || 0 ) - ( bBranch ? . notePosition || 0 ) ) || 0 ;
2023-01-06 15:07:18 +01:00
} ) ;
}
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() {
2023-06-01 00:07:57 +02:00
if ( ! this . __flatTextCache ) {
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 ) {
2023-06-01 00:07:57 +02:00
this . __flatTextCache += ` ${ branch . prefix } ` ;
2020-05-16 23:12:29 +02:00
}
}
2023-06-01 00:07:57 +02: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
2023-06-01 00:07:57 +02:00
this . __flatTextCache += ` ${ attr . type === 'label' ? '#' : '~' } ${ attr . name } ` ;
2020-05-16 23:12:29 +02:00
if ( attr . value ) {
2023-06-01 00:07:57 +02:00
this . __flatTextCache += ` = ${ attr . value } ` ;
2020-05-16 23:12:29 +02:00
}
2020-09-07 00:05:01 +02:00
2023-06-01 00:07:57 +02:00
this . __flatTextCache += ' ' ;
2020-05-16 23:12:29 +02:00
}
2023-06-01 00:07:57 +02:00
this . __flatTextCache = utils . normalize ( this . __flatTextCache ) ;
2020-05-16 23:12:29 +02:00
}
2023-06-01 00:07:57 +02:00
return this . __flatTextCache ;
2020-05-16 23:12:29 +02:00
}
invalidateThisCache() {
2023-06-01 00:07:57 +02:00
this . __flatTextCache = null ;
2020-05-16 23:12:29 +02:00
2021-04-25 21:19:18 +02:00
this . __attributeCache = null ;
2023-05-31 23:21:06 +02:00
this . __inheritableAttributeCache = null ;
2023-06-01 00:07:57 +02:00
this . __ancestorCache = null ;
2020-05-16 23:12:29 +02:00
}
2024-02-17 10:56:27 +02:00
invalidateSubTree ( path : string [ ] = [ ] ) {
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
}
}
}
}
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
}
2024-02-17 10:56:27 +02:00
getSubtreeNotesIncludingTemplated ( ) : BNote [ ] {
const set = new Set < BNote > ( ) ;
2020-05-16 23:12:29 +02:00
2024-02-17 10:56:27 +02:00
function inner ( note : BNote ) {
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
}
2024-02-17 10:56:27 +02:00
getSearchResultNotes ( ) : BNote [ ] {
2022-11-07 23:56:53 +01:00
if ( this . type !== 'search' ) {
return [ ] ;
}
try {
2024-02-17 11:16:00 +02:00
const searchService = require ( '../../services/search/services/search' ) ;
2022-11-07 23:56:53 +01:00
const { searchResultNoteIds } = searchService . searchFromNote ( this ) ;
const becca = this . becca ;
2024-02-18 20:29:23 +02:00
return ( searchResultNoteIds as string [ ] ) // TODO: remove cast once search is converted
2022-11-07 23:56:53 +01:00
. map ( resultNoteId = > becca . notes [ resultNoteId ] )
. filter ( note = > ! ! note ) ;
}
2024-02-17 10:56:27 +02:00
catch ( e : any ) {
2022-11-07 23:56:53 +01:00
log . error ( ` Could not resolve search note ${ this . noteId } : ${ e . message } ` ) ;
return [ ] ;
}
}
2024-02-17 10:56:27 +02:00
getSubtree ( { includeArchived = true , includeHidden = false , resolveSearch = false } = { } ) : {
notes : BNote [ ] ,
relationships : Relationship [ ]
} {
const noteSet = new Set < BNote > ( ) ;
const relationships : Relationship [ ] = [ ] ; // list of tuples parentNoteId -> childNoteId
2022-11-05 22:32:50 +01:00
2024-02-17 10:56:27 +02:00
function resolveSearchNote ( searchNote : BNote ) {
2022-11-05 22:32:50 +01:00
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
}
}
2024-02-17 10:56:27 +02:00
catch ( e : any ) {
2022-11-05 22:32:50 +01:00
log . error ( ` Could not resolve search note ${ searchNote ? . noteId } : ${ e . message } ` ) ;
}
}
2024-02-17 10:56:27 +02:00
function addSubtreeNotesInner ( note : BNote , parentNote : BNote | null = 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
}
2023-05-05 23:17:23 +02:00
/** @returns {string[]} - includes the subtree root note as well */
2022-12-27 14:44:28 +01:00
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() {
2023-06-01 00:07:57 +02:00
if ( ! this . __ancestorCache ) {
2020-05-23 23:44:55 +02:00
const noteIds = new Set ( ) ;
2023-06-01 00:07:57 +02:00
this . __ancestorCache = [ ] ;
2020-05-23 23:44:55 +02:00
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
}
2023-06-01 00:07:57 +02:00
this . __ancestorCache . push ( parent ) ;
2022-01-13 21:28:51 +01:00
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 ) ) {
2023-06-01 00:07:57 +02:00
this . __ancestorCache . push ( ancestorNote ) ;
2020-05-23 23:44:55 +02:00
noteIds . add ( ancestorNote . noteId ) ;
}
}
}
}
2023-06-01 00:07:57 +02:00
return this . __ancestorCache ;
2020-05-23 23:44:55 +02:00
}
2024-02-17 10:56:27 +02:00
getAncestorNoteIds ( ) : string [ ] {
2023-04-14 16:49:06 +02:00
return this . getAncestors ( ) . map ( note = > note . noteId ) ;
}
2024-02-17 10:56:27 +02:00
hasAncestor ( ancestorNoteId : string ) : boolean {
2021-10-24 14:53:45 +02:00
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' ) ;
}
2023-03-24 09:13:35 +01:00
/** @returns {BAttribute[]} */
2021-05-08 23:31:20 +02:00
getTargetRelations() {
return this . targetRelations ;
}
2024-02-17 10:56:27 +02:00
/ * * @ r e t u r n s 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 * /
getInheritingNotes ( ) : BNote [ ] {
const arr : BNote [ ] = [ this ] ;
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 ) {
arr . push ( note ) ;
}
}
}
return arr ;
}
2020-05-17 09:48:24 +02:00
2024-02-17 10:56:27 +02:00
getDistanceToAncestor ( ancestorNoteId : string ) {
2021-01-26 23:25:18 +01:00
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 ;
}
2024-02-17 10:56:27 +02:00
getRevisions ( ) : BRevision [ ] {
return sql . getRows < RevisionRow > ( "SELECT * FROM revisions WHERE noteId = ?" , [ this . noteId ] )
2023-06-04 23:01:40 +02:00
. map ( row = > new BRevision ( row ) ) ;
2021-04-26 22:00:55 +02:00
}
2023-03-16 12:17:55 +01:00
/** @returns {BAttachment[]} */
2024-02-17 10:56:27 +02:00
getAttachments ( opts : AttachmentOpts = { } ) {
2023-05-21 18:14:17 +02:00
opts . includeContentLength = ! ! opts . includeContentLength ;
2023-06-30 11:18:34 +02:00
// from testing, it looks like calculating length does not make a difference in performance even on large-ish DB
2023-05-29 00:19:54 +02:00
// given that we're always fetching attachments only for a specific note, we might just do it always
2023-05-21 18:14:17 +02:00
const query = opts . includeContentLength
? ` SELECT attachments.*, LENGTH(blobs.content) AS contentLength
FROM attachments
JOIN blobs USING ( blobId )
2023-07-14 17:01:56 +02:00
WHERE ownerId = ? AND isDeleted = 0
2023-05-21 18:14:17 +02:00
ORDER BY position `
2023-07-14 17:01:56 +02:00
: ` SELECT * FROM attachments WHERE ownerId = ? AND isDeleted = 0 ORDER BY position ` ;
2023-05-21 18:14:17 +02:00
2024-02-17 10:56:27 +02:00
return sql . getRows < AttachmentRow > ( query , [ this . noteId ] )
2023-03-16 12:17:55 +01:00
. map ( row = > new BAttachment ( row ) ) ;
2023-03-08 09:01:23 +01:00
}
2023-03-30 23:48:26 +02:00
/** @returns {BAttachment|null} */
2024-02-17 10:56:27 +02:00
getAttachmentById ( attachmentId : string , opts : AttachmentOpts = { } ) {
2023-05-21 18:14:17 +02:00
opts . includeContentLength = ! ! opts . includeContentLength ;
const query = opts . includeContentLength
? ` SELECT attachments.*, LENGTH(blobs.content) AS contentLength
FROM attachments
JOIN blobs USING ( blobId )
2023-07-14 17:01:56 +02:00
WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0 `
: ` SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0 ` ;
2023-05-21 18:14:17 +02:00
2024-02-17 10:56:27 +02:00
return sql . getRows < AttachmentRow > ( query , [ this . noteId , attachmentId ] )
2023-03-30 23:48:26 +02:00
. map ( row = > new BAttachment ( row ) ) [ 0 ] ;
2023-03-08 09:01:23 +01:00
}
2024-02-17 10:56:27 +02:00
getAttachmentsByRole ( role : string ) : BAttachment [ ] {
return sql . getRows < AttachmentRow > ( `
2023-04-20 00:11:09 +02:00
SELECT attachments . *
FROM attachments
2023-07-14 17:01:56 +02:00
WHERE ownerId = ?
2023-04-20 00:11:09 +02:00
AND role = ?
2023-05-06 22:50:28 +02:00
AND isDeleted = 0
ORDER BY position ` , [this.noteId, role])
2023-04-20 00:11:09 +02:00
. map ( row = > new BAttachment ( row ) ) ;
}
2024-02-17 10:56:27 +02:00
getAttachmentByTitle ( title : string ) : BAttachment {
2023-11-12 23:43:04 +01:00
// cannot use SQL to filter by title since it can be encrypted
return this . getAttachments ( ) . filter ( attachment = > attachment . title === title ) [ 0 ] ;
2023-09-08 21:53:57 +02:00
}
2021-05-09 20:46:32 +02:00
/ * *
2023-04-15 00:06:13 +02:00
* Gives all possible note paths leading to this note . Paths containing search note are ignored ( could form cycles )
*
2024-02-17 10:56:27 +02:00
* @returns array of notePaths ( each represented by array of noteIds constituting the particular note path )
2021-05-09 20:46:32 +02:00
* /
2024-02-17 10:56:27 +02:00
getAllNotePaths ( ) : string [ ] [ ] {
2021-05-09 20:46:32 +02:00
if ( this . noteId === 'root' ) {
return [ [ 'root' ] ] ;
}
2023-04-15 00:06:13 +02:00
const parentNotes = this . getParentNotes ( ) ;
2021-05-09 20:46:32 +02:00
2023-05-05 23:17:23 +02:00
const notePaths = parentNotes . length === 1
2023-06-30 11:18:34 +02:00
? parentNotes [ 0 ] . getAllNotePaths ( ) // optimization for the most common case
2023-05-05 23:17:23 +02:00
: parentNotes . flatMap ( parentNote = > parentNote . getAllNotePaths ( ) ) ;
2021-05-09 20:46:32 +02:00
2023-04-15 00:06:13 +02:00
for ( const notePath of notePaths ) {
notePath . push ( this . noteId ) ;
2021-05-09 20:46:32 +02:00
}
return notePaths ;
}
2024-02-17 10:56:27 +02:00
getSortedNotePathRecords ( hoistedNoteId : string = 'root' ) : NotePathRecord [ ] {
2023-04-15 00:06:13 +02:00
const isHoistedRoot = hoistedNoteId === 'root' ;
const notePaths = this . getAllNotePaths ( ) . map ( path = > ( {
notePath : path ,
isInHoistedSubTree : isHoistedRoot || path . includes ( hoistedNoteId ) ,
2023-04-16 09:26:52 +02:00
isArchived : path.some ( noteId = > this . becca . notes [ noteId ] . isArchived ) ,
2023-04-15 00:06:13 +02:00
isHidden : path.includes ( '_hidden' )
} ) ) ;
notePaths . sort ( ( a , b ) = > {
if ( a . isInHoistedSubTree !== b . isInHoistedSubTree ) {
return a . isInHoistedSubTree ? - 1 : 1 ;
} else if ( a . isArchived !== b . isArchived ) {
return a . isArchived ? 1 : - 1 ;
} else if ( a . isHidden !== b . isHidden ) {
return a . isHidden ? 1 : - 1 ;
} else {
return a . notePath . length - b . notePath . length ;
}
} ) ;
2021-05-09 20:46:32 +02:00
return notePaths ;
}
2023-04-15 00:06:13 +02:00
/ * *
2023-06-30 11:18:34 +02:00
* Returns a note path considered to be the "best"
2023-04-15 00:06:13 +02:00
*
2024-02-17 10:56:27 +02:00
* @return array of noteIds constituting the particular note path
2023-04-15 00:06:13 +02:00
* /
2024-02-17 10:56:27 +02:00
getBestNotePath ( hoistedNoteId : string = 'root' ) : string [ ] {
2023-04-15 00:06:13 +02:00
return this . getSortedNotePathRecords ( hoistedNoteId ) [ 0 ] ? . notePath ;
}
/ * *
2023-06-30 11:18:34 +02:00
* Returns a note path considered to be the "best"
2023-04-15 00:06:13 +02:00
*
2024-02-17 10:56:27 +02:00
* @return serialized note path ( e . g . 'root/a1h315/js725h' )
2023-04-15 00:06:13 +02:00
* /
2024-02-17 10:56:27 +02:00
getBestNotePathString ( hoistedNoteId : string = 'root' ) : string {
2023-04-15 00:06:13 +02:00
const notePath = this . getBestNotePath ( hoistedNoteId ) ;
return notePath ? . join ( "/" ) ;
}
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 ;
2023-02-28 23:23:17 +01:00
} else if ( ! parentNote . isHiddenCompletely ( ) ) {
2023-01-27 16:57:23 +01:00
return false ;
}
}
return true ;
2023-01-13 10:09:41 +01:00
}
2021-05-09 20:46:32 +02:00
/ * *
2024-02-17 10:56:27 +02:00
* @returns true if ancestorNoteId occurs in at least one of the note ' s paths
2021-05-09 20:46:32 +02:00
* /
2024-02-17 10:56:27 +02:00
isDescendantOfNote ( ancestorNoteId : string ) : boolean {
2021-05-09 20:46:32 +02:00
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
*
2024-02-17 10:56:27 +02:00
* @param type - attribute type ( label , relation , etc . )
* @param name - attribute name
* @param value - attribute value ( optional )
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
setAttribute ( type : string , name : string , value? : string ) {
2021-05-11 22:00:16 +02:00
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 {
2024-02-17 11:16:00 +02: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 .
*
2024-02-17 10:56:27 +02:00
* @param type - attribute type ( label , relation , etc . )
* @param name - attribute name
* @param value - attribute value ( optional )
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
removeAttribute ( type : string , name : string , value? : string ) {
2021-05-11 22:00:16 +02:00
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 .
*
2024-02-17 10:56:27 +02:00
* @param type - attribute type ( label / relation )
* @param name - name of the attribute , not including the leading ~ / #
* @param value - value of the attribute - text for labels , target note ID for relations ; optional .
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
addAttribute ( type : string , name : string , value : string = "" , isInheritable : boolean = false , position : number | null = null ) : BAttribute {
2024-02-17 11:16:00 +02: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 .
*
2024-02-17 10:56:27 +02:00
* @param name - name of the label , not including the leading #
* @param value - text value of the label ; optional
2022-08-27 13:40:01 -07:00
* /
2024-02-17 10:56:27 +02:00
addLabel ( name : string , value : string = "" , isInheritable : boolean = false ) : BAttribute {
2021-05-11 22:00:16 +02:00
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 .
*
2024-02-17 10:56:27 +02:00
* @param name - name of the relation , not including the leading ~
2022-08-27 13:40:01 -07:00
* /
2024-02-17 10:56:27 +02:00
addRelation ( name : string , targetNoteId : string , isInheritable : boolean = false ) : BAttribute {
2021-05-11 22:00:16 +02:00
return this . addAttribute ( RELATION , name , targetNoteId , isInheritable ) ;
}
/ * *
2023-06-30 11:18:34 +02:00
* Based on enabled , the attribute is either set or removed .
2021-05-11 22:00:16 +02:00
*
2024-02-17 10:56:27 +02:00
* @param type - attribute type ( 'relation' , 'label' etc . )
* @param enabled - toggle On or Off
* @param name - attribute name
* @param value - attribute value ( optional )
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
toggleAttribute ( type : string , enabled : boolean , name : string , value? : string ) {
2021-05-11 22:00:16 +02:00
if ( enabled ) {
this . setAttribute ( type , name , value ) ;
}
else {
this . removeAttribute ( type , name , value ) ;
}
}
/ * *
* Based on enabled , label is either set or removed .
*
2024-02-17 10:56:27 +02:00
* @param enabled - toggle On or Off
* @param name - label name
* @param value - label value ( optional )
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
toggleLabel ( enabled : boolean , name : string , value? : string ) {
return this . toggleAttribute ( LABEL , enabled , name , value ) ;
}
2021-05-11 22:00:16 +02:00
/ * *
* Based on enabled , relation is either set or removed .
*
2024-02-17 10:56:27 +02:00
* @param enabled - toggle On or Off
* @param name - relation name
* @param value - relation value ( noteId )
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
toggleRelation ( enabled : boolean , name : string , value? : string ) {
return this . toggleAttribute ( RELATION , enabled , name , value ) ;
}
2021-05-11 22:00:16 +02:00
/ * *
* Update 's given label' s value or creates it if it doesn ' t exist
*
2024-02-17 10:56:27 +02:00
* @param name - label name
* @param value label value
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
setLabel ( name : string , value? : string ) {
return this . setAttribute ( LABEL , name , value ) ;
}
2021-05-11 22:00:16 +02:00
/ * *
* Update 's given relation' s value or creates it if it doesn ' t exist
*
2024-02-17 10:56:27 +02:00
* @param name - relation name
* @param value - relation value ( noteId )
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
setRelation ( name : string , value : string ) {
return this . setAttribute ( RELATION , name , value ) ;
}
2021-05-11 22:00:16 +02:00
/ * *
* Remove label name - value pair , if it exists .
*
2024-02-17 10:56:27 +02:00
* @param name - label name
* @param value - label value
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
removeLabel ( name : string , value? : string ) {
return this . removeAttribute ( LABEL , name , value ) ;
}
2021-05-11 22:00:16 +02:00
/ * *
2023-06-30 11:18:34 +02:00
* Remove the relation name - value pair , if it exists .
2021-05-11 22:00:16 +02:00
*
2024-02-17 10:56:27 +02:00
* @param name - relation name
* @param value - relation value ( noteId )
2021-05-11 22:00:16 +02:00
* /
2024-02-17 10:56:27 +02:00
removeRelation ( name : string , value? : string ) {
return this . removeAttribute ( RELATION , name , value ) ;
}
2021-05-11 22:00:16 +02:00
2024-02-17 10:56:27 +02:00
searchNotesInSubtree ( searchString : string ) {
2024-02-17 11:16:00 +02:00
const searchService = require ( '../../services/search/services/search' ) ;
2021-06-06 11:01:10 +02:00
return searchService . searchNotes ( searchString ) ;
}
2024-02-17 10:56:27 +02:00
searchNoteInSubtree ( searchString : string ) {
2021-06-06 11:01:10 +02:00
return this . searchNotesInSubtree ( searchString ) [ 0 ] ;
}
2024-02-17 10:56:27 +02:00
cloneTo ( parentNoteId : string ) {
2024-02-17 11:16:00 +02:00
const cloningService = require ( '../../services/cloning' ) ;
2021-06-06 11:01:10 +02:00
2024-02-17 11:39:29 +02:00
const branch = this . becca . getNote ( parentNoteId ) ? . getParentBranches ( ) [ 0 ] ;
2021-06-06 11:01:10 +02:00
2024-02-17 11:39:29 +02:00
return cloningService . cloneNoteToBranch ( this . noteId , branch ? . branchId ) ;
2021-06-06 11:01:10 +02:00
}
2023-05-02 22:46:39 +02:00
2024-02-17 10:56:27 +02:00
isEligibleForConversionToAttachment ( opts : ConvertOpts = { autoConversion : false } ) {
2023-05-02 22:46:39 +02:00
if ( this . type !== 'image' || ! this . isContentAvailable ( ) || this . hasChildren ( ) || this . getParentBranches ( ) . length !== 1 ) {
return false ;
}
const targetRelations = this . getTargetRelations ( ) . filter ( relation = > relation . name === 'imageLink' ) ;
2023-07-14 18:24:15 +02:00
if ( opts . autoConversion && targetRelations . length === 0 ) {
return false ;
} else if ( targetRelations . length > 1 ) {
2023-05-02 22:46:39 +02:00
return false ;
}
const parentNote = this . getParentNotes ( ) [ 0 ] ; // at this point note can have only one parent
2023-05-04 22:16:18 +02:00
const referencingNote = targetRelations [ 0 ] ? . getNote ( ) ;
2023-05-02 22:46:39 +02:00
2023-05-04 22:16:18 +02:00
if ( referencingNote && parentNote !== referencingNote ) {
return false ;
} else if ( parentNote . type !== 'text' || ! parentNote . isContentAvailable ( ) ) {
2023-05-02 22:46:39 +02:00
return false ;
}
return true ;
}
2023-03-24 09:13:35 +01:00
/ * *
* Some notes are eligible for conversion into an attachment of its parent , note must have these properties :
* - it has exactly one target relation
* - it has a relation from its parent note
* - it has no children
* - it has no clones
2023-06-30 11:18:34 +02:00
* - the parent is of type text
2023-03-24 09:13:35 +01:00
* - both notes are either unprotected or user is in protected session
*
* Currently , works only for image notes .
*
2023-07-14 18:24:15 +02:00
* In the future , this functionality might get more generic and some of the requirements relaxed .
2023-03-24 09:13:35 +01:00
*
2024-02-17 10:56:27 +02:00
* @returns null if note is not eligible for conversion
2023-03-24 09:13:35 +01:00
* /
2024-02-17 10:56:27 +02:00
convertToParentAttachment ( opts : ConvertOpts = { autoConversion : false } ) : BAttachment | null {
2023-07-14 18:24:15 +02:00
if ( ! this . isEligibleForConversionToAttachment ( opts ) ) {
2023-03-24 09:13:35 +01:00
return null ;
}
2024-02-17 11:13:53 +02:00
const content = this . getContent ( ) ;
2023-03-24 09:13:35 +01:00
2023-05-02 22:46:39 +02:00
const parentNote = this . getParentNotes ( ) [ 0 ] ;
2023-03-24 09:13:35 +01:00
const attachment = parentNote . saveAttachment ( {
role : 'image' ,
mime : this.mime ,
title : this.title ,
content : content
} ) ;
2024-02-17 11:13:53 +02:00
let parentContent = parentNote . getContent ( ) ;
2023-03-24 09:13:35 +01:00
const oldNoteUrl = ` api/images/ ${ this . noteId } / ` ;
2023-04-17 22:40:53 +02:00
const newAttachmentUrl = ` api/attachments/ ${ attachment . attachmentId } /image/ ` ;
2023-03-24 09:13:35 +01:00
2024-02-17 22:58:54 +02:00
if ( typeof parentContent !== "string" ) {
throw new Error ( "Unable to convert image note into attachment because parent note does not have a string content." ) ;
}
2023-03-24 09:13:35 +01:00
const fixedContent = utils . replaceAll ( parentContent , oldNoteUrl , newAttachmentUrl ) ;
parentNote . setContent ( fixedContent ) ;
2024-02-17 11:16:00 +02:00
const noteService = require ( '../../services/notes' ) ;
2023-07-14 21:28:32 +02:00
noteService . asyncPostProcessContent ( parentNote , fixedContent ) ; // to mark an unused attachment for deletion
2023-07-14 21:18:56 +02:00
2023-03-24 09:13:35 +01:00
this . deleteNote ( ) ;
return attachment ;
}
2021-06-06 11:01:10 +02:00
2022-04-19 23:06:46 +02:00
/ * *
* ( Soft ) delete a note and all its descendants .
*
2024-02-17 10:56:27 +02:00
* @param deleteId - optional delete identified
2022-04-19 23:06:46 +02:00
* /
2024-02-17 10:56:27 +02:00
deleteNote ( deleteId : string | null = null , taskContext : TaskContext | null = null ) {
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
2024-02-17 11:16:00 +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 {
2024-02-17 10:56:27 +02:00
this . title = protectedSessionService . decryptString ( this . title ) || "" ;
2023-06-01 00:07:57 +02:00
this . __flatTextCache = null ;
2020-05-17 09:48:24 +02:00
2021-04-03 21:48:16 +02:00
this . isDecrypted = true ;
}
2024-02-17 10:56:27 +02:00
catch ( e : any ) {
2021-04-03 21:48:16 +02:00
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() {
2023-06-30 11:18:34 +02:00
// isBeingDeleted is relevant only in the transition period when the deletion process has begun, but not yet
2023-06-05 09:23:42 +02:00
// finished (note is still in becca)
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-06-04 23:01:40 +02:00
* @returns { BRevision | null }
2022-06-02 17:25:58 +02:00
* /
2023-06-04 23:01:40 +02:00
saveRevision() {
2023-04-11 23:24:39 +02:00
return sql . transactional ( ( ) = > {
2024-02-17 11:13:53 +02:00
let noteContent = this . getContent ( ) ;
2022-06-02 17:25:58 +02:00
2023-06-04 23:01:40 +02:00
const revision = new BRevision ( {
2023-04-11 23:24:39 +02:00
noteId : this.noteId ,
// title and text should be decrypted now
title : this.title ,
type : this . type ,
mime : this.mime ,
isProtected : this.isProtected ,
2023-09-06 22:54:31 +02:00
utcDateLastEdited : this.utcDateModified ,
2023-04-11 23:24:39 +02:00
utcDateCreated : dateUtils.utcNowDateTime ( ) ,
utcDateModified : dateUtils.utcNowDateTime ( ) ,
2023-09-06 22:54:31 +02:00
dateLastEdited : this.dateModified ,
2023-04-11 23:24:39 +02:00
dateCreated : dateUtils.localNowDateTime ( )
} , true ) ;
2023-06-29 12:19:01 +02:00
revision . save ( ) ; // to generate revisionId, which is then used to save attachments
2023-04-11 23:24:39 +02:00
2023-10-02 15:24:40 +02:00
for ( const noteAttachment of this . getAttachments ( ) ) {
const revisionAttachment = noteAttachment . copy ( ) ;
2024-02-17 11:39:29 +02:00
if ( ! revision . revisionId ) {
throw new Error ( "Revision ID is missing." ) ;
}
2023-10-02 15:24:40 +02:00
revisionAttachment . ownerId = revision . revisionId ;
2024-02-17 10:56:27 +02:00
revisionAttachment . setContent ( noteAttachment . getContent ( ) , { forceSave : true } ) ;
2023-04-17 23:21:28 +02:00
2024-02-17 22:58:54 +02:00
if ( this . type === 'text' && typeof noteContent === "string" ) {
2023-06-29 12:19:01 +02:00
// content is rewritten to point to the revision attachments
2023-07-10 20:30:04 +02:00
noteContent = noteContent . replaceAll ( ` attachments/ ${ noteAttachment . attachmentId } ` ,
` attachments/ ${ revisionAttachment . attachmentId } ` ) ;
noteContent = noteContent . replaceAll ( new RegExp ( ` href="[^"]*attachmentId= ${ noteAttachment . attachmentId } [^"]*" ` , 'gi' ) ,
` href="api/attachments/ ${ revisionAttachment . attachmentId } /download" ` ) ;
2023-06-29 12:19:01 +02:00
}
}
2023-04-17 23:21:28 +02:00
2023-08-17 16:39:57 +02:00
revision . setContent ( noteContent ) ;
2023-06-04 23:01:40 +02:00
return revision ;
2023-04-11 23:24:39 +02:00
} ) ;
2022-06-02 17:25:58 +02:00
}
2023-03-08 09:01:23 +01:00
/ * *
2023-10-21 00:23:16 +02:00
* @param { string } matchBy - choose by which property we detect if to update an existing attachment .
* Supported values are either 'attachmentId' ( default ) or 'title'
2023-03-16 12:17:55 +01:00
* @returns { BAttachment }
2023-03-08 09:01:23 +01:00
* /
2024-02-17 10:56:27 +02:00
saveAttachment ( { attachmentId , role , mime , title , content , position } : AttachmentRow , matchBy = 'attachmentId' ) {
2023-10-21 00:23:16 +02:00
if ( ! [ 'attachmentId' , 'title' ] . includes ( matchBy ) ) {
throw new Error ( ` Unsupported value ' ${ matchBy } ' for matchBy param, has to be either 'attachmentId' or 'title'. ` ) ;
}
2023-03-16 16:37:31 +01:00
let attachment ;
2023-03-08 09:01:23 +01:00
2024-02-17 10:56:27 +02:00
if ( matchBy === 'title' && title ) {
2023-10-21 00:23:16 +02:00
attachment = this . getAttachmentByTitle ( title ) ;
} else if ( matchBy === 'attachmentId' && attachmentId ) {
2023-05-08 00:02:08 +02:00
attachment = this . becca . getAttachmentOrThrow ( attachmentId ) ;
2023-03-16 16:37:31 +01:00
}
2023-03-08 09:01:23 +01:00
2023-10-21 00:23:16 +02:00
attachment = attachment || new BAttachment ( {
ownerId : this.noteId ,
title ,
role ,
mime ,
isProtected : this.isProtected ,
position
} ) ;
2024-03-30 11:09:45 +02:00
if ( ! content ) {
throw new Error ( "Attempted to save an attachment with no content." ) ;
}
2023-06-05 09:23:42 +02:00
attachment . setContent ( content , { forceSave : true } ) ;
2023-03-08 09:01:23 +01:00
2023-03-16 12:17:55 +01:00
return attachment ;
2023-03-08 09:01:23 +01:00
}
2023-05-03 10:23:20 +02:00
getFileName() {
return utils . formatDownloadTitle ( this . title , this . type , this . mime ) ;
}
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 ,
2024-02-17 10:56:27 +02:00
title : this.title || undefined ,
2021-04-25 20:00:42 +02:00
isProtected : this.isProtected ,
type : this . type ,
mime : this.mime ,
2023-03-15 22:44:08 +01:00
blobId : this.blobId ,
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 ) {
2024-02-17 10:56:27 +02:00
if ( this . isDecrypted && pojo . title ) {
pojo . title = protectedSessionService . encrypt ( pojo . title ) || undefined ;
2021-04-25 21:19:18 +02:00
}
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
2024-02-17 01:00:38 +02:00
export = BNote ;