diff --git a/docs/backend_api/AbstractBeccaEntity.html b/docs/backend_api/AbstractBeccaEntity.html index c520b336a..e13c799bc 100644 --- a/docs/backend_api/AbstractBeccaEntity.html +++ b/docs/backend_api/AbstractBeccaEntity.html @@ -90,6 +90,11 @@ + +
Source:
+
@@ -175,6 +180,11 @@ + +
Source:
+
@@ -246,6 +256,11 @@ + +
Source:
+
@@ -325,6 +340,11 @@ + +
Source:
+
@@ -404,6 +424,11 @@ + +
Source:
+
@@ -483,6 +508,11 @@ + +
Source:
+
@@ -562,6 +592,11 @@ + +
Source:
+
@@ -641,6 +676,11 @@ + +
Source:
+
@@ -790,6 +830,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
Source:
+
@@ -873,6 +918,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
Source:
+
@@ -941,7 +991,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BAttribute.html b/docs/backend_api/BAttribute.html index a2d37d4c5..8f6af88c6 100644 --- a/docs/backend_api/BAttribute.html +++ b/docs/backend_api/BAttribute.html @@ -91,6 +91,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -197,6 +202,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -256,6 +266,11 @@ and relation (representing named relationship between source and target note)Source: +
+ @@ -318,6 +333,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -381,6 +401,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -444,6 +469,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -507,6 +537,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -570,6 +605,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -633,6 +673,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -696,6 +741,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -773,6 +823,11 @@ and relation (representing named relationship between source and target note)Source: +
+ @@ -857,6 +912,11 @@ and relation (representing named relationship between source and target note)Source: +
+ @@ -941,6 +1001,11 @@ and relation (representing named relationship between source and target note)Source: +
+ @@ -1025,6 +1090,11 @@ and relation (representing named relationship between source and target note)Source: +
+ @@ -1103,6 +1173,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -1209,6 +1284,11 @@ and relation (representing named relationship between source and target note)Source: +
+ @@ -1287,6 +1367,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -1393,6 +1478,11 @@ and relation (representing named relationship between source and target note)Source: +
+ @@ -1471,6 +1561,11 @@ and relation (representing named relationship between source and target note)Source: +
@@ -1644,6 +1739,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
Source:
+
+ @@ -1732,6 +1832,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
Source:
+
+ @@ -1799,7 +1904,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BBranch.html b/docs/backend_api/BBranch.html index e12dbf829..ae6e4e014 100644 --- a/docs/backend_api/BBranch.html +++ b/docs/backend_api/BBranch.html @@ -94,6 +94,11 @@ Always check noteId instead. + +
Source:
+
@@ -196,6 +201,11 @@ Always check noteId instead. +
Source:
+
+ @@ -258,6 +268,11 @@ Always check noteId instead. + +
Source:
+
@@ -311,6 +326,11 @@ Always check noteId instead. + +
Source:
+
@@ -374,6 +394,11 @@ Always check noteId instead. + +
Source:
+
@@ -435,6 +460,11 @@ of deletion should not act as a clone. + +
Source:
+
@@ -498,6 +528,11 @@ of deletion should not act as a clone. + +
Source:
+
@@ -561,6 +596,11 @@ of deletion should not act as a clone. + +
Source:
+
@@ -614,6 +654,11 @@ of deletion should not act as a clone. + +
Source:
+
@@ -677,6 +722,11 @@ of deletion should not act as a clone. + +
Source:
+
@@ -693,7 +743,7 @@ of deletion should not act as a clone. -

prefix :string

+

prefix :string|null

@@ -705,6 +755,9 @@ of deletion should not act as a clone.
  • string +| + +null
  • @@ -740,6 +793,11 @@ of deletion should not act as a clone. + +
    Source:
    +
    @@ -803,6 +861,11 @@ of deletion should not act as a clone. + +
    Source:
    +
    @@ -880,6 +943,11 @@ of deletion should not act as a clone. +
    Source:
    +
    + @@ -964,6 +1032,11 @@ of deletion should not act as a clone. +
    Source:
    +
    + @@ -1140,6 +1213,11 @@ of deletion should not act as a clone. + +
    Source:
    +
    @@ -1247,6 +1325,11 @@ of deletion should not act as a clone. +
    Source:
    +
    + @@ -1331,6 +1414,11 @@ of deletion should not act as a clone. +
    Source:
    +
    + @@ -1415,6 +1503,11 @@ of deletion should not act as a clone. +
    Source:
    +
    + @@ -1499,6 +1592,11 @@ of deletion should not act as a clone. +
    Source:
    +
    + @@ -1653,6 +1751,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -1741,6 +1844,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -1808,7 +1916,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BEtapiToken.html b/docs/backend_api/BEtapiToken.html index 3ef744252..1fab94869 100644 --- a/docs/backend_api/BEtapiToken.html +++ b/docs/backend_api/BEtapiToken.html @@ -96,6 +96,11 @@ from tokenHash and token. + +
    Source:
    +
    @@ -198,6 +203,11 @@ from tokenHash and token. +
    Source:
    +
    + @@ -260,6 +270,11 @@ from tokenHash and token. + +
    Source:
    +
    @@ -323,6 +338,11 @@ from tokenHash and token. + +
    Source:
    +
    @@ -386,6 +406,11 @@ from tokenHash and token. + +
    Source:
    +
    @@ -449,6 +474,11 @@ from tokenHash and token. + +
    Source:
    +
    @@ -512,6 +542,11 @@ from tokenHash and token. + +
    Source:
    +
    @@ -575,6 +610,11 @@ from tokenHash and token. + +
    Source:
    +
    @@ -652,6 +692,11 @@ from tokenHash and token. +
    Source:
    +
    + @@ -736,6 +781,11 @@ from tokenHash and token. +
    Source:
    +
    + @@ -820,6 +870,11 @@ from tokenHash and token. +
    Source:
    +
    + @@ -904,6 +959,11 @@ from tokenHash and token. +
    Source:
    +
    + @@ -988,6 +1048,11 @@ from tokenHash and token. +
    Source:
    +
    + @@ -1072,6 +1137,11 @@ from tokenHash and token. +
    Source:
    +
    + @@ -1226,6 +1296,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -1314,6 +1389,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -1381,7 +1461,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BNote.html b/docs/backend_api/BNote.html index 1ca80c827..d38f35c0f 100644 --- a/docs/backend_api/BNote.html +++ b/docs/backend_api/BNote.html @@ -90,6 +90,11 @@ + +
    Source:
    +
    @@ -192,6 +197,11 @@ +
    Source:
    +
    + @@ -254,6 +264,11 @@ + +
    Source:
    +
    @@ -317,6 +332,11 @@ + +
    Source:
    +
    @@ -383,6 +403,11 @@ + +
    Source:
    +
    @@ -450,6 +475,11 @@ + +
    Source:
    +
    @@ -513,6 +543,11 @@ + +
    Source:
    +
    @@ -576,6 +611,11 @@ + +
    Source:
    +
    @@ -639,6 +679,11 @@ + +
    Source:
    +
    @@ -702,6 +747,11 @@ + +
    Source:
    +
    @@ -765,6 +815,11 @@ + +
    Source:
    +
    @@ -828,6 +883,11 @@ + +
    Source:
    +
    @@ -891,6 +951,11 @@ + +
    Source:
    +
    @@ -954,6 +1019,11 @@ + +
    Source:
    +
    @@ -980,7 +1050,7 @@ -

    addAttribute(type, name, valueopt, isInheritableopt, positionopt) → {Attribute}

    +

    addAttribute(type, name, valueopt, isInheritableopt, positionopt) → {BAttribute}

    @@ -1245,6 +1315,11 @@ See addLabel, addRelation for more specific methods. + +
    Source:
    +
    @@ -1279,7 +1354,7 @@ See addLabel, addRelation for more specific methods.
    -Attribute +BAttribute
    @@ -1348,6 +1423,11 @@ See addLabel, addRelation for more specific methods. +
    Source:
    +
    + @@ -1381,7 +1461,7 @@ See addLabel, addRelation for more specific methods. -

    addLabel(name, valueopt, isInheritableopt) → {Attribute}

    +

    addLabel(name, valueopt, isInheritableopt) → {BAttribute}

    @@ -1571,6 +1651,11 @@ See addLabel, addRelation for more specific methods. + +
    Source:
    +
    @@ -1605,7 +1690,7 @@ See addLabel, addRelation for more specific methods.
    -Attribute +BAttribute
    @@ -1623,7 +1708,7 @@ See addLabel, addRelation for more specific methods. -

    addRelation(name, targetNoteId, isInheritableopt) → {Attribute}

    +

    addRelation(name, targetNoteId, isInheritableopt) → {BAttribute}

    @@ -1812,6 +1897,11 @@ returned. + +
    Source:
    +
    @@ -1846,7 +1936,7 @@ returned.
    -Attribute +BAttribute
    @@ -1915,6 +2005,11 @@ returned. +
    Source:
    +
    + @@ -2037,6 +2132,11 @@ returned. + +
    Source:
    +
    @@ -2232,6 +2332,11 @@ returned. + +
    Source:
    +
    @@ -2317,6 +2422,11 @@ returned. +
    Source:
    +
    + @@ -2401,6 +2511,11 @@ returned. +
    Source:
    +
    + @@ -2479,6 +2594,11 @@ returned. + +
    Source:
    +
    @@ -2580,6 +2700,11 @@ returned. + +
    Source:
    +
    @@ -2632,7 +2757,7 @@ returned. -

    getAttribute(type, name) → {Attribute}

    +

    getAttribute(type, name) → {BAttribute}

    @@ -2749,6 +2874,11 @@ returned. + +
    Source:
    +
    @@ -2787,7 +2917,7 @@ returned.
    -Attribute +BAttribute
    @@ -2922,6 +3052,11 @@ returned. + +
    Source:
    +
    @@ -2981,7 +3116,7 @@ returned. -

    getAttributes(typeopt, nameopt) → {Array.<Attribute>}

    +

    getAttributes(typeopt, nameopt) → {Array.<BAttribute>}

    @@ -3120,6 +3255,11 @@ returned. + +
    Source:
    +
    @@ -3158,7 +3298,7 @@ returned.
    -Array.<Attribute> +Array.<BAttribute>
    @@ -3224,6 +3364,11 @@ returned. +
    Source:
    +
    + @@ -3320,6 +3465,11 @@ returned. + +
    Source:
    +
    @@ -3417,6 +3567,11 @@ returned. + +
    Source:
    +
    @@ -3514,6 +3669,11 @@ returned. + +
    Source:
    +
    @@ -3611,6 +3771,11 @@ returned. + +
    Source:
    +
    @@ -3711,6 +3876,11 @@ returned. +
    Source:
    +
    + @@ -3795,6 +3965,11 @@ returned. + +
    Source:
    +
    @@ -3851,6 +4026,113 @@ returned. +

    getInheritingNotes() → {Array.<BNote>}

    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    + - returns only notes which are templated, does not include their subtrees + in effect returns notes which are influenced by note's non-inheritable attributes +
    + + + +
    +
    + Type +
    +
    + +Array.<BNote> + + +
    +
    + + + + + + + + + + + + +

    getJsonContent() → {*}

    @@ -3896,6 +4178,11 @@ returned. + +
    Source:
    +
    @@ -3948,7 +4235,7 @@ returned. -

    getLabel(name) → {Attribute|null}

    +

    getLabel(name) → {BAttribute|null}

    @@ -4042,6 +4329,11 @@ returned. + +
    Source:
    +
    @@ -4080,7 +4372,7 @@ returned.
    -Attribute +BAttribute | null @@ -4195,6 +4487,11 @@ returned. + +
    Source:
    +
    @@ -4360,6 +4657,11 @@ returned. + +
    Source:
    +
    @@ -4416,7 +4718,7 @@ returned. -

    getLabels(nameopt) → {Array.<Attribute>}

    +

    getLabels(nameopt) → {Array.<BAttribute>}

    @@ -4522,6 +4824,11 @@ returned. + +
    Source:
    +
    @@ -4560,7 +4867,7 @@ returned.
    -Array.<Attribute> +Array.<BAttribute>
    @@ -4578,7 +4885,7 @@ returned. -

    getOwnedAttribute() → {Attribute}

    +

    getNoteRevisions() → {Array.<BNoteRevision>}

    @@ -4623,6 +4930,113 @@ returned. + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Array.<BNoteRevision> + + +
    +
    + + + + + + + + + + + + + +

    getOwnedAttribute() → {BAttribute}

    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    @@ -4663,7 +5077,7 @@ This method can be significantly faster than the getAttribute()
    -Attribute +BAttribute
    @@ -4798,6 +5212,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -4857,7 +5276,7 @@ This method can be significantly faster than the getAttribute() -

    getOwnedAttributes(typeopt, nameopt, valueopt) → {Array.<Attribute>}

    +

    getOwnedAttributes(typeopt, nameopt, valueopt) → {Array.<BAttribute>}

    @@ -5058,6 +5477,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -5096,7 +5520,7 @@ This method can be significantly faster than the getAttribute()
    -Array.<Attribute> +Array.<BAttribute>
    @@ -5114,7 +5538,7 @@ This method can be significantly faster than the getAttribute() -

    getOwnedLabel(name) → {Attribute|null}

    +

    getOwnedLabel(name) → {BAttribute|null}

    @@ -5208,6 +5632,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -5246,7 +5675,7 @@ This method can be significantly faster than the getAttribute()
    -Attribute +BAttribute | null @@ -5361,6 +5790,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -5526,6 +5960,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -5582,7 +6021,7 @@ This method can be significantly faster than the getAttribute() -

    getOwnedLabels(nameopt) → {Array.<Attribute>}

    +

    getOwnedLabels(nameopt) → {Array.<BAttribute>}

    @@ -5688,6 +6127,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -5726,7 +6170,7 @@ This method can be significantly faster than the getAttribute()
    -Array.<Attribute> +Array.<BAttribute>
    @@ -5744,7 +6188,7 @@ This method can be significantly faster than the getAttribute() -

    getOwnedRelation(name) → {Attribute|null}

    +

    getOwnedRelation(name) → {BAttribute|null}

    @@ -5838,6 +6282,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -5876,7 +6325,7 @@ This method can be significantly faster than the getAttribute()
    -Attribute +BAttribute | null @@ -5991,6 +6440,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -6050,7 +6504,7 @@ This method can be significantly faster than the getAttribute() -

    getOwnedRelations(nameopt) → {Array.<Attribute>}

    +

    getOwnedRelations(nameopt) → {Array.<BAttribute>}

    @@ -6156,6 +6610,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -6194,7 +6653,7 @@ This method can be significantly faster than the getAttribute()
    -Array.<Attribute> +Array.<BAttribute>
    @@ -6257,6 +6716,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -6354,6 +6818,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -6457,6 +6926,11 @@ This method can be significantly faster than the getAttribute() +
    Source:
    +
    + @@ -6490,7 +6964,7 @@ This method can be significantly faster than the getAttribute() -

    getRelation(name) → {Attribute|null}

    +

    getRelation(name) → {BAttribute|null}

    @@ -6584,6 +7058,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -6622,7 +7101,7 @@ This method can be significantly faster than the getAttribute()
    -Attribute +BAttribute | null @@ -6737,6 +7216,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -6796,7 +7280,7 @@ This method can be significantly faster than the getAttribute() -

    getRelations(nameopt) → {Array.<Attribute>}

    +

    getRelations(nameopt) → {Array.<BAttribute>}

    @@ -6902,6 +7386,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -6940,7 +7429,7 @@ This method can be significantly faster than the getAttribute()
    -Array.<Attribute> +Array.<BAttribute>
    @@ -7003,6 +7492,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -7107,6 +7601,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -7208,6 +7707,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -7305,6 +7809,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -7402,6 +7911,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -7504,102 +8018,10 @@ This method can be significantly faster than the getAttribute() - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - - - -
    -
    - Type -
    -
    - -Array.<BNote> - - -
    -
    - - - - - - - - - - - - - -

    getTemplatedNotes() → {Array.<BNote>}

    - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - +
    Source:
    +
    @@ -7626,11 +8048,6 @@ This method can be significantly faster than the getAttribute()
    Returns:
    -
    - - returns only notes which are templated, does not include their subtrees - in effect returns notes which are influenced by note's non-inheritable attributes -
    -
    @@ -7708,6 +8125,11 @@ This method can be significantly faster than the getAttribute() +
    Source:
    +
    + @@ -7786,6 +8208,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -8007,6 +8434,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -8104,6 +8536,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -8293,6 +8730,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -8517,6 +8959,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -8710,6 +9157,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -8903,6 +9355,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -9096,6 +9553,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -9241,6 +9703,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -9297,6 +9764,100 @@ This method can be significantly faster than the getAttribute() +

    isHiddenCompletely()

    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    + boolean - true if there's no non-hidden path, note is not cloned to the visible tree +
    + + + + + + + + + + + + + + +

    isHtml() → {boolean}

    @@ -9342,6 +9903,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -9398,6 +9964,112 @@ This method can be significantly faster than the getAttribute() +

    isImage() → {boolean}

    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    + true if this note is an image +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + + + +

    isJavaScript() → {boolean}

    @@ -9443,6 +10115,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -9544,6 +10221,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -9645,6 +10327,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -9746,6 +10433,11 @@ This method can be significantly faster than the getAttribute() + +
    Source:
    +
    @@ -9923,6 +10615,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -10128,6 +10825,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -10303,6 +11005,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -10478,6 +11185,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -10567,6 +11279,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -10663,6 +11380,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -10890,6 +11612,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -11065,6 +11792,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -11220,6 +11952,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -11457,6 +12194,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -11663,6 +12405,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -11869,6 +12616,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' + +
    Source:
    +
    @@ -11919,7 +12671,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BNoteRevision.html b/docs/backend_api/BNoteRevision.html index 5b9b4b336..0951fa14d 100644 --- a/docs/backend_api/BNoteRevision.html +++ b/docs/backend_api/BNoteRevision.html @@ -91,6 +91,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -193,6 +198,11 @@ It's used for seamless note versioning. +
    Source:
    +
    + @@ -255,6 +265,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -318,6 +333,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -381,6 +401,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -444,6 +469,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -507,6 +537,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -570,6 +605,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -633,6 +673,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -696,6 +741,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -759,6 +809,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -822,6 +877,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -885,6 +945,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -948,6 +1013,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -1025,6 +1095,11 @@ It's used for seamless note versioning. +
    Source:
    +
    + @@ -1109,6 +1184,11 @@ It's used for seamless note versioning. +
    Source:
    +
    + @@ -1193,6 +1273,11 @@ It's used for seamless note versioning. +
    Source:
    +
    + @@ -1277,6 +1362,11 @@ It's used for seamless note versioning. +
    Source:
    +
    + @@ -1355,6 +1445,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -1452,6 +1547,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -1555,6 +1655,11 @@ It's used for seamless note versioning. +
    Source:
    +
    + @@ -1639,6 +1744,11 @@ It's used for seamless note versioning. +
    Source:
    +
    + @@ -1717,6 +1827,11 @@ It's used for seamless note versioning. + +
    Source:
    +
    @@ -1894,6 +2009,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -1982,6 +2102,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -2049,7 +2174,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BOption.html b/docs/backend_api/BOption.html index d99af8540..b5990cb92 100644 --- a/docs/backend_api/BOption.html +++ b/docs/backend_api/BOption.html @@ -90,6 +90,11 @@ + +
    Source:
    +
    @@ -192,6 +197,11 @@ +
    Source:
    +
    + @@ -254,6 +264,11 @@ + +
    Source:
    +
    @@ -317,6 +332,11 @@ + +
    Source:
    +
    @@ -380,6 +400,11 @@ + +
    Source:
    +
    @@ -443,6 +468,11 @@ + +
    Source:
    +
    @@ -520,6 +550,11 @@ +
    Source:
    +
    + @@ -604,6 +639,11 @@ +
    Source:
    +
    + @@ -688,6 +728,11 @@ +
    Source:
    +
    + @@ -772,6 +817,11 @@ +
    Source:
    +
    + @@ -856,6 +906,11 @@ +
    Source:
    +
    + @@ -940,6 +995,11 @@ +
    Source:
    +
    + @@ -1094,6 +1154,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -1182,6 +1247,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -1249,7 +1319,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BRecentNote.html b/docs/backend_api/BRecentNote.html index 33f9799e9..b36b38298 100644 --- a/docs/backend_api/BRecentNote.html +++ b/docs/backend_api/BRecentNote.html @@ -90,6 +90,11 @@ + +
    Source:
    +
    @@ -192,6 +197,11 @@ +
    Source:
    +
    + @@ -254,6 +264,11 @@ + +
    Source:
    +
    @@ -317,6 +332,11 @@ + +
    Source:
    +
    @@ -380,6 +400,11 @@ + +
    Source:
    +
    @@ -457,6 +482,11 @@ +
    Source:
    +
    + @@ -541,6 +571,11 @@ +
    Source:
    +
    + @@ -625,6 +660,11 @@ +
    Source:
    +
    + @@ -709,6 +749,11 @@ +
    Source:
    +
    + @@ -793,6 +838,11 @@ +
    Source:
    +
    + @@ -877,6 +927,11 @@ +
    Source:
    +
    + @@ -1031,6 +1086,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -1119,6 +1179,11 @@ This is a low level method, for notes and branches use `note.deleteNote()` and ' +
    Source:
    +
    + @@ -1186,7 +1251,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BackendScriptApi.html b/docs/backend_api/BackendScriptApi.html index 2f5a312c3..12800f161 100644 --- a/docs/backend_api/BackendScriptApi.html +++ b/docs/backend_api/BackendScriptApi.html @@ -28,7 +28,7 @@
    -

    BackendScriptApi

    +

    BackendScriptApi()

    @@ -38,7 +38,20 @@ + + +

    new BackendScriptApi()

    + + + + + + +
    +

    This is the main backend API interface for scripts. All the properties and methods are published in the "api" object +available in the JS backend notes. You can use e.g. api.log(api.startNote.title);

    +
    @@ -78,6 +91,11 @@ + +
    Source:
    +
    @@ -219,6 +237,11 @@ + +
    Source:
    +
    @@ -288,7 +311,7 @@ - library for HTTP requests. See https://axios-http.com/ for documentation + library for HTTP requests. See https://axios-http.com for documentation @@ -315,7 +338,7 @@ - +
    Deprecated:
    • use native (browser compatible) fetch() instead
    @@ -327,6 +350,13 @@ +
    Source:
    +
    + + + @@ -429,6 +459,11 @@ + +
    Source:
    +
    @@ -498,7 +533,7 @@ - library for date manipulation. See https://day.js.org/ for documentation + library for date manipulation. See https://day.js.org for documentation @@ -534,6 +569,11 @@ + +
    Source:
    +
    @@ -593,7 +633,7 @@ -Entity +AbstractBeccaEntity @@ -639,6 +679,11 @@ + +
    Source:
    +
    @@ -744,6 +789,11 @@ + +
    Source:
    +
    @@ -849,6 +899,11 @@ + +
    Source:
    +
    @@ -918,7 +973,7 @@ - library for XML parsing. See https://github.com/Leonidas-from-XIV/node-xml2js for documentation + library for XML parsing. See https://github.com/Leonidas-from-XIV/node-xml2js for documentation @@ -954,6 +1009,11 @@ + +
    Source:
    +
    @@ -1125,6 +1185,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -1623,6 +1688,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -2239,6 +2309,11 @@ JSON MIME type. See also createNewNote() for more options. +
    Source:
    +
    + @@ -2739,6 +2814,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -2935,6 +3015,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -3112,6 +3197,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -3308,6 +3398,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -3454,6 +3549,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -3510,7 +3610,7 @@ JSON MIME type. See also createNewNote() for more options. -

    exportSubtreeToZipFile(noteId, format, zipFilePath) → {Promise}

    +

    exportSubtreeToZipFile(noteId, format, zipFilePath) → {Promise.<void>}

    @@ -3650,6 +3750,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -3684,7 +3789,7 @@ JSON MIME type. See also createNewNote() for more options.
    -Promise +Promise.<void>
    @@ -3747,6 +3852,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -3806,7 +3916,7 @@ JSON MIME type. See also createNewNote() for more options. -

    getAttribute(attributeId) → {Attribute|null}

    +

    getAttribute(attributeId) → {BAttribute|null}

    @@ -3900,6 +4010,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -3934,7 +4049,7 @@ JSON MIME type. See also createNewNote() for more options.
    -Attribute +BAttribute | null @@ -4049,6 +4164,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -4104,204 +4224,6 @@ JSON MIME type. See also createNewNote() for more options. -

    getDateNote(date, rootNoteopt) → {BNote|null}

    - - - - - - -
    - Returns day note for given date. If such note doesn't exist, it is created. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDescription
    date - - -string - - - - - - - - - - in YYYY-MM-DD format
    rootNote - - -BNote - - - - - - <optional>
    - - - - - -
    specify calendar root note, normally leave empty to use default calendar
    - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • use getDayNote instead
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - - - -
    -
    - Type -
    -
    - -BNote -| - -null - - -
    -
    - - - - - - - - - - - - -

    getDayNote(date, rootNoteopt) → {BNote|null}

    @@ -4405,7 +4327,7 @@ JSON MIME type. See also createNewNote() for more options. - specify calendar root note, normally leave empty to use default calendar + specify calendar root note, normally leave empty to use the default calendar @@ -4443,6 +4365,11 @@ JSON MIME type. See also createNewNote() for more options. + +
    Source:
    +
    @@ -4548,6 +4475,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -4706,7 +4638,7 @@ if some action needs to happen on only one specific instance. - specify calendar root note, normally leave empty to use default calendar + specify calendar root note, normally leave empty to use the default calendar @@ -4744,6 +4676,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -4893,6 +4830,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -5089,6 +5031,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -5285,6 +5232,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -5386,6 +5338,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -5513,7 +5470,7 @@ if some action needs to happen on only one specific instance. - specify calendar root note, normally leave empty to use default calendar + specify calendar root note, normally leave empty to use the default calendar @@ -5551,6 +5508,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -5709,7 +5671,76 @@ if some action needs to happen on only one specific instance. - "startOfTheWeek" - either "monday" (default) or "sunday" + +
    Properties
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    startOfTheWeek + + +string + + + + + + <optional>
    + + + + + +
    + + monday + + either "monday" (default) or "sunday"
    + + @@ -5742,7 +5773,7 @@ if some action needs to happen on only one specific instance. - specify calendar root note, normally leave empty to use default calendar + specify calendar root note, normally leave empty to use the default calendar @@ -5780,6 +5811,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -5938,7 +5974,7 @@ if some action needs to happen on only one specific instance. - specify calendar root note, normally leave empty to use default calendar + specify calendar root note, normally leave empty to use the default calendar @@ -5976,6 +6012,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -6031,7 +6072,7 @@ if some action needs to happen on only one specific instance. -

    log(message)

    +

    log(message) → {void}

    @@ -6124,6 +6165,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -6147,6 +6193,24 @@ if some action needs to happen on only one specific instance. +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +void + + +
    +
    + + @@ -6256,6 +6320,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -6306,87 +6375,6 @@ if some action needs to happen on only one specific instance. - - - - - - -

    refreshTree()

    - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • - this is now no-op since all the changes should be gracefully handled per widget
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - @@ -6402,7 +6390,7 @@ if some action needs to happen on only one specific instance.
    This is a powerful search method - you can search by attributes and their values, e.g.: -"#dateModified =* MONTH AND #log". See full documentation for all options at: https://github.com/zadam/trilium/wiki/Search +"#dateModified =* MONTH AND #log". See https://github.com/zadam/trilium/wiki/Search for full documentation for all options
    @@ -6535,6 +6523,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -6599,7 +6592,7 @@ if some action needs to happen on only one specific instance.
    This is a powerful search method - you can search by attributes and their values, e.g.: -"#dateModified =* MONTH AND #log". See full documentation for all options at: https://github.com/zadam/trilium/wiki/Search +"#dateModified =* MONTH AND #log". See https://github.com/zadam/trilium/wiki/Search for full documentation for all options
    @@ -6732,6 +6725,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -6784,7 +6782,7 @@ if some action needs to happen on only one specific instance. -

    setNoteToParent(noteId, prefix, parentNoteId)

    +

    setNoteToParent(noteId, prefix, parentNoteId) → {void}

    @@ -6925,7 +6923,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look -
    Deprecated:
    • - this method is pretty confusing and serves specialized purpose only
    +
    Deprecated:
    • this method is pretty confusing and serves specialized purpose only
    @@ -6937,6 +6935,11 @@ This method looks similar to toggleNoteInParent() but differs because we're look +
    Source:
    +
    + @@ -6959,6 +6962,24 @@ This method looks similar to toggleNoteInParent() but differs because we're look +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +void + + +
    +
    + + @@ -6970,7 +6991,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look -

    sortNotes(parentNoteId, sortConfigopt)

    +

    sortNotes(parentNoteId, sortConfigopt) → {void}

    @@ -7146,7 +7167,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look 'title', 'dateCreated', 'dateModified' or a label name - see https://github.com/zadam/trilium/wiki/Sorting for details. + See https://github.com/zadam/trilium/wiki/Sorting for details. @@ -7257,135 +7278,10 @@ This method looks similar to toggleNoteInParent() but differs because we're look - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    sortNotesByTitle(parentNoteId)

    - - - - - - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    parentNoteId - - -string - - - - this note's child notes will be sorted
    - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • - use sortNotes instead
    - - - - - - - - - - +
    Source:
    +
    @@ -7409,6 +7305,24 @@ This method looks similar to toggleNoteInParent() but differs because we're look +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +void + + +
    +
    + + @@ -7587,6 +7501,11 @@ This method looks similar to toggleNoteInParent() but differs because we're look + +
    Source:
    +
    @@ -7738,6 +7657,11 @@ exists, then we'll use that transaction. + +
    Source:
    +
    @@ -7888,6 +7812,11 @@ exists, then we'll use that transaction. + +
    Source:
    +
    @@ -7960,7 +7889,7 @@ exists, then we'll use that transaction.
    diff --git a/docs/backend_api/becca_entities_abstract_becca_entity.js.html b/docs/backend_api/becca_entities_abstract_becca_entity.js.html new file mode 100644 index 000000000..4036de3f6 --- /dev/null +++ b/docs/backend_api/becca_entities_abstract_becca_entity.js.html @@ -0,0 +1,221 @@ + + + + + JSDoc: Source: becca/entities/abstract_becca_entity.js + + + + + + + + + + +
    + +

    Source: becca/entities/abstract_becca_entity.js

    + + + + + + +
    +
    +
    "use strict";
    +
    +const utils = require('../../services/utils');
    +const sql = require('../../services/sql');
    +const entityChangesService = require('../../services/entity_changes');
    +const eventService = require("../../services/events");
    +const dateUtils = require("../../services/date_utils");
    +const cls = require("../../services/cls");
    +const log = require("../../services/log");
    +
    +let becca = null;
    +
    +/**
    + * Base class for all backend entities.
    + */
    +class AbstractBeccaEntity {
    +    /** @protected */
    +    beforeSaving() {
    +        this.generateIdIfNecessary();
    +    }
    +
    +    /** @protected */
    +    generateIdIfNecessary() {
    +        if (!this[this.constructor.primaryKeyName]) {
    +            this[this.constructor.primaryKeyName] = utils.newEntityId();
    +        }
    +    }
    +
    +    /** @protected */
    +    generateHash(isDeleted = false) {
    +        let contentToHash = "";
    +
    +        for (const propertyName of this.constructor.hashedProperties) {
    +            contentToHash += `|${this[propertyName]}`;
    +        }
    +
    +        if (isDeleted) {
    +            contentToHash += "|deleted";
    +        }
    +
    +        return utils.hash(contentToHash).substr(0, 10);
    +    }
    +
    +    /** @protected */
    +    getUtcDateChanged() {
    +        return this.utcDateModified || this.utcDateCreated;
    +    }
    +
    +    /**
    +     * @protected
    +     * @returns {Becca}
    +     */
    +    get becca() {
    +        if (!becca) {
    +            becca = require('../becca');
    +        }
    +
    +        return becca;
    +    }
    +
    +    /** @protected */
    +    addEntityChange(isDeleted = false) {
    +        entityChangesService.addEntityChange({
    +            entityName: this.constructor.entityName,
    +            entityId: this[this.constructor.primaryKeyName],
    +            hash: this.generateHash(isDeleted),
    +            isErased: false,
    +            utcDateChanged: this.getUtcDateChanged(),
    +            isSynced: this.constructor.entityName !== 'options' || !!this.isSynced
    +        });
    +    }
    +
    +    /** @protected */
    +    getPojoToSave() {
    +        return this.getPojo();
    +    }
    +
    +    /**
    +     * Saves entity - executes SQL, but doesn't commit the transaction on its own
    +     *
    +     * @returns {this}
    +     */
    +    save(opts = {}) {
    +        const entityName = this.constructor.entityName;
    +        const primaryKeyName = this.constructor.primaryKeyName;
    +
    +        const isNewEntity = !this[primaryKeyName];
    +
    +        if (this.beforeSaving) {
    +            this.beforeSaving(opts);
    +        }
    +
    +        const pojo = this.getPojoToSave();
    +
    +        sql.transactional(() => {
    +            sql.upsert(entityName, primaryKeyName, pojo);
    +
    +            if (entityName === 'recent_notes') {
    +                return;
    +            }
    +
    +            this.addEntityChange(false);
    +
    +            if (!cls.isEntityEventsDisabled()) {
    +                const eventPayload = {
    +                    entityName,
    +                    entity: this
    +                };
    +
    +                if (isNewEntity) {
    +                    eventService.emit(eventService.ENTITY_CREATED, eventPayload);
    +                }
    +
    +                eventService.emit(eventService.ENTITY_CHANGED, eventPayload);
    +            }
    +        });
    +
    +        return this;
    +    }
    +
    +    /**
    +     * Mark the entity as (soft) deleted. It will be completely erased later.
    +     *
    +     * This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
    +     *
    +     * @param [deleteId=null]
    +     */
    +    markAsDeleted(deleteId = null) {
    +        const entityId = this[this.constructor.primaryKeyName];
    +        const entityName = this.constructor.entityName;
    +
    +        this.utcDateModified = dateUtils.utcNowDateTime();
    +
    +        sql.execute(`UPDATE ${entityName} SET isDeleted = 1, deleteId = ?, utcDateModified = ?
    +                           WHERE ${this.constructor.primaryKeyName} = ?`,
    +            [deleteId, this.utcDateModified, entityId]);
    +
    +        if (this.dateModified) {
    +            this.dateModified = dateUtils.localNowDateTime();
    +
    +            sql.execute(`UPDATE ${entityName} SET dateModified = ? WHERE ${this.constructor.primaryKeyName} = ?`,
    +                [this.dateModified, entityId]);
    +        }
    +
    +        log.info(`Marking ${entityName} ${entityId} as deleted`);
    +
    +        this.addEntityChange(true);
    +
    +        eventService.emit(eventService.ENTITY_DELETED, { entityName, entityId, entity: this });
    +    }
    +
    +    markAsDeletedSimple() {
    +        const entityId = this[this.constructor.primaryKeyName];
    +        const entityName = this.constructor.entityName;
    +
    +        this.utcDateModified = dateUtils.utcNowDateTime();
    +
    +        sql.execute(`UPDATE ${entityName} SET isDeleted = 1, utcDateModified = ?
    +                           WHERE ${this.constructor.primaryKeyName} = ?`,
    +            [this.utcDateModified, entityId]);
    +
    +        log.info(`Marking ${entityName} ${entityId} as deleted`);
    +
    +        this.addEntityChange(true);
    +
    +        eventService.emit(eventService.ENTITY_DELETED, { entityName, entityId, entity: this });
    +    }
    +}
    +
    +module.exports = AbstractBeccaEntity;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/backend_api/becca_entities_battribute.js.html b/docs/backend_api/becca_entities_battribute.js.html new file mode 100644 index 000000000..83fff96e5 --- /dev/null +++ b/docs/backend_api/becca_entities_battribute.js.html @@ -0,0 +1,285 @@ + + + + + JSDoc: Source: becca/entities/battribute.js + + + + + + + + + + +
    + +

    Source: becca/entities/battribute.js

    + + + + + + +
    +
    +
    "use strict";
    +
    +const BNote = require('./bnote');
    +const AbstractBeccaEntity = require("./abstract_becca_entity");
    +const sql = require("../../services/sql");
    +const dateUtils = require("../../services/date_utils");
    +const promotedAttributeDefinitionParser = require("../../services/promoted_attribute_definition_parser");
    +const {sanitizeAttributeName} = require("../../services/sanitize_attribute_name");
    +
    +/**
    + * Attribute is an abstract concept which has two real uses - label (key - value pair)
    + * and relation (representing named relationship between source and target note)
    + *
    + * @extends AbstractBeccaEntity
    + */
    +class BAttribute extends AbstractBeccaEntity {
    +    static get entityName() { return "attributes"; }
    +    static get primaryKeyName() { return "attributeId"; }
    +    static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "isInheritable"]; }
    +
    +    constructor(row) {
    +        super();
    +
    +        if (!row) {
    +            return;
    +        }
    +
    +        this.updateFromRow(row);
    +        this.init();
    +    }
    +
    +    updateFromRow(row) {
    +        this.update([
    +            row.attributeId,
    +            row.noteId,
    +            row.type,
    +            row.name,
    +            row.value,
    +            row.isInheritable,
    +            row.position,
    +            row.utcDateModified
    +        ]);
    +    }
    +
    +    update([attributeId, noteId, type, name, value, isInheritable, position, utcDateModified]) {
    +        /** @type {string} */
    +        this.attributeId = attributeId;
    +        /** @type {string} */
    +        this.noteId = noteId;
    +        /** @type {string} */
    +        this.type = type;
    +        /** @type {string} */
    +        this.name = name;
    +        /** @type {int} */
    +        this.position = position;
    +        /** @type {string} */
    +        this.value = value || "";
    +        /** @type {boolean} */
    +        this.isInheritable = !!isInheritable;
    +        /** @type {string} */
    +        this.utcDateModified = utcDateModified;
    +
    +        return this;
    +    }
    +
    +    init() {
    +        if (this.attributeId) {
    +            this.becca.attributes[this.attributeId] = this;
    +        }
    +
    +        if (!(this.noteId in this.becca.notes)) {
    +            // entities can come out of order in sync, create skeleton which will be filled later
    +            this.becca.addNote(this.noteId, new BNote({noteId: this.noteId}));
    +        }
    +
    +        this.becca.notes[this.noteId].ownedAttributes.push(this);
    +
    +        const key = `${this.type}-${this.name.toLowerCase()}`;
    +        this.becca.attributeIndex[key] = this.becca.attributeIndex[key] || [];
    +        this.becca.attributeIndex[key].push(this);
    +
    +        const targetNote = this.targetNote;
    +
    +        if (targetNote) {
    +            targetNote.targetRelations.push(this);
    +        }
    +    }
    +
    +    validate() {
    +        if (!["label", "relation"].includes(this.type)) {
    +            throw new Error(`Invalid attribute type '${this.type}' in attribute '${this.attributeId}' of note '${this.noteId}'`);
    +        }
    +
    +        if (!this.name?.trim()) {
    +            throw new Error(`Invalid empty name in attribute '${this.attributeId}' of note '${this.noteId}'`);
    +        }
    +
    +        if (this.type === 'relation' && !(this.value in this.becca.notes)) {
    +            throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it target not existing note '${this.value}'.`);
    +        }
    +    }
    +
    +    get isAffectingSubtree() {
    +        return this.isInheritable
    +            || (this.type === 'relation' && ['template', 'inherit'].includes(this.name));
    +    }
    +
    +    get targetNoteId() { // alias
    +        return this.type === 'relation' ? this.value : undefined;
    +    }
    +
    +    isAutoLink() {
    +        return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name);
    +    }
    +
    +    get note() {
    +        return this.becca.notes[this.noteId];
    +    }
    +
    +    get targetNote() {
    +        if (this.type === 'relation') {
    +            return this.becca.notes[this.value];
    +        }
    +    }
    +
    +    /**
    +     * @returns {BNote|null}
    +     */
    +    getNote() {
    +        const note = this.becca.getNote(this.noteId);
    +
    +        if (!note) {
    +            throw new Error(`Note '${this.noteId}' of attribute '${this.attributeId}', type '${this.type}', name '${this.name}' does not exist.`);
    +        }
    +
    +        return note;
    +    }
    +
    +    /**
    +     * @returns {BNote|null}
    +     */
    +    getTargetNote() {
    +        if (this.type !== 'relation') {
    +            throw new Error(`Attribute ${this.attributeId} is not relation`);
    +        }
    +
    +        if (!this.value) {
    +            return null;
    +        }
    +
    +        return this.becca.getNote(this.value);
    +    }
    +
    +    /**
    +     * @returns {boolean}
    +     */
    +    isDefinition() {
    +        return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:'));
    +    }
    +
    +    getDefinition() {
    +        return promotedAttributeDefinitionParser.parse(this.value);
    +    }
    +
    +    getDefinedName() {
    +        if (this.type === 'label' && this.name.startsWith('label:')) {
    +            return this.name.substr(6);
    +        } else if (this.type === 'label' && this.name.startsWith('relation:')) {
    +            return this.name.substr(9);
    +        } else {
    +            return this.name;
    +        }
    +    }
    +
    +    get isDeleted() {
    +        return !(this.attributeId in this.becca.attributes);
    +    }
    +
    +    beforeSaving(opts = {}) {
    +        if (!opts.skipValidation) {
    +            this.validate();
    +        }
    +
    +        this.name = sanitizeAttributeName(this.name);
    +
    +        if (!this.value) {
    +            // null value isn't allowed
    +            this.value = "";
    +        }
    +
    +        if (this.position === undefined) {
    +            // TODO: can be calculated from becca
    +            this.position = 1 + sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [this.noteId]);
    +        }
    +
    +        if (!this.isInheritable) {
    +            this.isInheritable = false;
    +        }
    +
    +        this.utcDateModified = dateUtils.utcNowDateTime();
    +
    +        super.beforeSaving();
    +
    +        this.becca.attributes[this.attributeId] = this;
    +    }
    +
    +    getPojo() {
    +        return {
    +            attributeId: this.attributeId,
    +            noteId: this.noteId,
    +            type: this.type,
    +            name: this.name,
    +            position: this.position,
    +            value: this.value,
    +            isInheritable: this.isInheritable,
    +            utcDateModified: this.utcDateModified,
    +            isDeleted: false
    +        };
    +    }
    +
    +    createClone(type, name, value, isInheritable) {
    +        return new BAttribute({
    +            noteId: this.noteId,
    +            type: type,
    +            name: name,
    +            value: value,
    +            position: this.position,
    +            isInheritable: isInheritable,
    +            utcDateModified: this.utcDateModified
    +        });
    +    }
    +}
    +
    +module.exports = BAttribute;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/backend_api/becca_entities_bbranch.js.html b/docs/backend_api/becca_entities_bbranch.js.html new file mode 100644 index 000000000..b4fa7f567 --- /dev/null +++ b/docs/backend_api/becca_entities_bbranch.js.html @@ -0,0 +1,328 @@ + + + + + JSDoc: Source: becca/entities/bbranch.js + + + + + + + + + + +
    + +

    Source: becca/entities/bbranch.js

    + + + + + + +
    +
    +
    "use strict";
    +
    +const BNote = require('./bnote');
    +const AbstractBeccaEntity = require("./abstract_becca_entity");
    +const dateUtils = require("../../services/date_utils");
    +const utils = require("../../services/utils");
    +const TaskContext = require("../../services/task_context");
    +const cls = require("../../services/cls");
    +const log = require("../../services/log");
    +
    +/**
    + * Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple
    + * parents.
    + *
    + * Note that you should not rely on the branch's identity, since it can change easily with a note's move.
    + * Always check noteId instead.
    + *
    + * @extends AbstractBeccaEntity
    + */
    +class BBranch extends AbstractBeccaEntity {
    +    static get entityName() { return "branches"; }
    +    static get primaryKeyName() { return "branchId"; }
    +    // notePosition is not part of hash because it would produce a lot of updates in case of reordering
    +    static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "prefix"]; }
    +
    +    constructor(row) {
    +        super();
    +
    +        if (!row) {
    +            return;
    +        }
    +
    +        this.updateFromRow(row);
    +        this.init();
    +    }
    +
    +    updateFromRow(row) {
    +        this.update([
    +            row.branchId,
    +            row.noteId,
    +            row.parentNoteId,
    +            row.prefix,
    +            row.notePosition,
    +            row.isExpanded,
    +            row.utcDateModified
    +        ]);
    +    }
    +
    +    update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified]) {
    +        /** @type {string} */
    +        this.branchId = branchId;
    +        /** @type {string} */
    +        this.noteId = noteId;
    +        /** @type {string} */
    +        this.parentNoteId = parentNoteId;
    +        /** @type {string|null} */
    +        this.prefix = prefix;
    +        /** @type {int} */
    +        this.notePosition = notePosition;
    +        /** @type {boolean} */
    +        this.isExpanded = !!isExpanded;
    +        /** @type {string} */
    +        this.utcDateModified = utcDateModified;
    +
    +        return this;
    +    }
    +
    +    init() {
    +        if (this.branchId) {
    +            this.becca.branches[this.branchId] = this;
    +        }
    +
    +        this.becca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this;
    +
    +        const childNote = this.childNote;
    +
    +        if (!childNote.parentBranches.includes(this)) {
    +            childNote.parentBranches.push(this);
    +        }
    +
    +        if (this.noteId === 'root') {
    +            return;
    +        }
    +
    +        const parentNote = this.parentNote;
    +
    +        if (!childNote.parents.includes(parentNote)) {
    +            childNote.parents.push(parentNote);
    +        }
    +
    +        if (!parentNote.children.includes(childNote)) {
    +            parentNote.children.push(childNote);
    +        }
    +    }
    +
    +    /** @returns {BNote} */
    +    get childNote() {
    +        if (!(this.noteId in this.becca.notes)) {
    +            // entities can come out of order in sync/import, create skeleton which will be filled later
    +            this.becca.addNote(this.noteId, new BNote({noteId: this.noteId}));
    +        }
    +
    +        return this.becca.notes[this.noteId];
    +    }
    +
    +    getNote() {
    +        return this.childNote;
    +    }
    +
    +    /** @returns {BNote|undefined} - root branch will have undefined parent, all other branches have to have a parent note */
    +    get parentNote() {
    +        if (!(this.parentNoteId in this.becca.notes) && this.parentNoteId !== 'none') {
    +            // entities can come out of order in sync/import, create skeleton which will be filled later
    +            this.becca.addNote(this.parentNoteId, new BNote({noteId: this.parentNoteId}));
    +        }
    +
    +        return this.becca.notes[this.parentNoteId];
    +    }
    +
    +    get isDeleted() {
    +        return !(this.branchId in this.becca.branches);
    +    }
    +
    +    /**
    +     * Branch is weak when its existence should not hinder deletion of its note.
    +     * As a result, note with only weak branches should be immediately deleted.
    +     * An example is shared or bookmarked clones - they are created automatically and exist for technical reasons,
    +     * not as user-intended actions. From user perspective, they don't count as real clones and for the purpose
    +     * of deletion should not act as a clone.
    +     *
    +     * @returns {boolean}
    +     */
    +    get isWeak() {
    +        return ['_share', '_lbBookmarks'].includes(this.parentNoteId);
    +    }
    +
    +    /**
    +     * Delete a branch. If this is a last note's branch, delete the note as well.
    +     *
    +     * @param {string} [deleteId] - optional delete identified
    +     * @param {TaskContext} [taskContext]
    +     *
    +     * @returns {boolean} - true if note has been deleted, false otherwise
    +     */
    +    deleteBranch(deleteId, taskContext) {
    +        if (!deleteId) {
    +            deleteId = utils.randomString(10);
    +        }
    +
    +        if (!taskContext) {
    +            taskContext = new TaskContext('no-progress-reporting');
    +        }
    +
    +        taskContext.increaseProgressCount();
    +
    +        const note = this.getNote();
    +
    +        if (!taskContext.noteDeletionHandlerTriggered) {
    +            const parentBranches = note.getParentBranches();
    +
    +            if (parentBranches.length === 1 && parentBranches[0] === this) {
    +                // needs to be run before branches and attributes are deleted and thus attached relations disappear
    +                const handlers = require("../../services/handlers");
    +                handlers.runAttachedRelations(note, 'runOnNoteDeletion', note);
    +            }
    +        }
    +
    +        if (this.noteId === 'root'
    +            || this.noteId === cls.getHoistedNoteId()) {
    +
    +            throw new Error("Can't delete root or hoisted branch/note");
    +        }
    +
    +        this.markAsDeleted(deleteId);
    +
    +        const notDeletedBranches = note.getStrongParentBranches();
    +
    +        if (notDeletedBranches.length === 0) {
    +            for (const weakBranch of note.getParentBranches()) {
    +                weakBranch.markAsDeleted(deleteId);
    +            }
    +
    +            for (const childBranch of note.getChildBranches()) {
    +                childBranch.deleteBranch(deleteId, taskContext);
    +            }
    +
    +            // first delete children and then parent - this will show up better in recent changes
    +
    +            log.info(`Deleting note ${note.noteId}`);
    +
    +            this.becca.notes[note.noteId].isBeingDeleted = true;
    +
    +            for (const attribute of note.getOwnedAttributes()) {
    +                attribute.markAsDeleted(deleteId);
    +            }
    +
    +            for (const relation of note.getTargetRelations()) {
    +                relation.markAsDeleted(deleteId);
    +            }
    +
    +            note.markAsDeleted(deleteId);
    +
    +            return true;
    +        }
    +        else {
    +            return false;
    +        }
    +    }
    +
    +    beforeSaving() {
    +        if (!this.noteId || !this.parentNoteId) {
    +            throw new Error(`noteId and parentNoteId are mandatory properties for Branch`);
    +        }
    +
    +        this.branchId = `${this.parentNoteId}_${this.noteId}`;
    +
    +        if (this.notePosition === undefined || this.notePosition === null) {
    +            let maxNotePos = 0;
    +
    +            for (const childBranch of this.parentNote.getChildBranches()) {
    +                if (maxNotePos < childBranch.notePosition
    +                    && childBranch.noteId !== '_hidden' // hidden has very large notePosition to always stay last
    +                ) {
    +                    maxNotePos = childBranch.notePosition;
    +                }
    +            }
    +
    +            this.notePosition = maxNotePos + 10;
    +        }
    +
    +        if (!this.isExpanded) {
    +            this.isExpanded = false;
    +        }
    +
    +        if (!this.prefix?.trim()) {
    +            this.prefix = null;
    +        }
    +
    +        this.utcDateModified = dateUtils.utcNowDateTime();
    +
    +        super.beforeSaving();
    +
    +        this.becca.branches[this.branchId] = this;
    +    }
    +
    +    getPojo() {
    +        return {
    +            branchId: this.branchId,
    +            noteId: this.noteId,
    +            parentNoteId: this.parentNoteId,
    +            prefix: this.prefix,
    +            notePosition: this.notePosition,
    +            isExpanded: this.isExpanded,
    +            isDeleted: false,
    +            utcDateModified: this.utcDateModified
    +        };
    +    }
    +
    +    createClone(parentNoteId, notePosition) {
    +        const existingBranch = this.becca.getBranchFromChildAndParent(this.noteId, parentNoteId);
    +
    +        if (existingBranch) {
    +            existingBranch.notePosition = notePosition;
    +            return existingBranch;
    +        } else {
    +            return new BBranch({
    +                noteId: this.noteId,
    +                parentNoteId: parentNoteId,
    +                notePosition: notePosition,
    +                prefix: this.prefix,
    +                isExpanded: this.isExpanded
    +            });
    +        }
    +    }
    +}
    +
    +module.exports = BBranch;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/backend_api/becca_entities_betapi_token.js.html b/docs/backend_api/becca_entities_betapi_token.js.html new file mode 100644 index 000000000..92ab19328 --- /dev/null +++ b/docs/backend_api/becca_entities_betapi_token.js.html @@ -0,0 +1,129 @@ + + + + + JSDoc: Source: becca/entities/betapi_token.js + + + + + + + + + + +
    + +

    Source: becca/entities/betapi_token.js

    + + + + + + +
    +
    +
    "use strict";
    +
    +const dateUtils = require('../../services/date_utils');
    +const AbstractBeccaEntity = require("./abstract_becca_entity");
    +
    +/**
    + * EtapiToken is an entity representing token used to authenticate against Trilium REST API from client applications.
    + * Used by:
    + * - Trilium Sender
    + * - ETAPI clients
    + *
    + * The format user is presented with is "<etapiTokenId>_<tokenHash>". This is also called "authToken" to distinguish it
    + * from tokenHash and token.
    + *
    + * @extends AbstractBeccaEntity
    + */
    +class BEtapiToken extends AbstractBeccaEntity {
    +    static get entityName() { return "etapi_tokens"; }
    +    static get primaryKeyName() { return "etapiTokenId"; }
    +    static get hashedProperties() { return ["etapiTokenId", "name", "tokenHash", "utcDateCreated", "utcDateModified", "isDeleted"]; }
    +
    +    constructor(row) {
    +        super();
    +
    +        if (!row) {
    +            return;
    +        }
    +
    +        this.updateFromRow(row);
    +        this.init();
    +    }
    +
    +    updateFromRow(row) {
    +        /** @type {string} */
    +        this.etapiTokenId = row.etapiTokenId;
    +        /** @type {string} */
    +        this.name = row.name;
    +        /** @type {string} */
    +        this.tokenHash = row.tokenHash;
    +        /** @type {string} */
    +        this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
    +        /** @type {string} */
    +        this.utcDateModified = row.utcDateModified || this.utcDateCreated;
    +        /** @type {boolean} */
    +        this.isDeleted = !!row.isDeleted;
    +
    +        if (this.etapiTokenId) {
    +            this.becca.etapiTokens[this.etapiTokenId] = this;
    +        }
    +    }
    +
    +    init() {
    +        if (this.etapiTokenId) {
    +            this.becca.etapiTokens[this.etapiTokenId] = this;
    +        }
    +    }
    +
    +    getPojo() {
    +        return {
    +            etapiTokenId: this.etapiTokenId,
    +            name: this.name,
    +            tokenHash: this.tokenHash,
    +            utcDateCreated: this.utcDateCreated,
    +            utcDateModified: this.utcDateModified,
    +            isDeleted: this.isDeleted
    +        }
    +    }
    +
    +    beforeSaving() {
    +        this.utcDateModified = dateUtils.utcNowDateTime();
    +
    +        super.beforeSaving();
    +
    +        this.becca.etapiTokens[this.etapiTokenId] = this;
    +    }
    +}
    +
    +module.exports = BEtapiToken;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/backend_api/becca_entities_bnote.js.html b/docs/backend_api/becca_entities_bnote.js.html new file mode 100644 index 000000000..97889071a --- /dev/null +++ b/docs/backend_api/becca_entities_bnote.js.html @@ -0,0 +1,1559 @@ + + + + + JSDoc: Source: becca/entities/bnote.js + + + + + + + + + + +
    + +

    Source: becca/entities/bnote.js

    + + + + + + +
    +
    +
    "use strict";
    +
    +const protectedSessionService = require('../../services/protected_session');
    +const log = require('../../services/log');
    +const sql = require('../../services/sql');
    +const utils = require('../../services/utils');
    +const dateUtils = require('../../services/date_utils');
    +const entityChangesService = require('../../services/entity_changes');
    +const AbstractBeccaEntity = require("./abstract_becca_entity");
    +const BNoteRevision = require("./bnote_revision");
    +const TaskContext = require("../../services/task_context");
    +const dayjs = require("dayjs");
    +const utc = require('dayjs/plugin/utc');
    +const eventService = require("../../services/events");
    +dayjs.extend(utc);
    +
    +const LABEL = 'label';
    +const RELATION = 'relation';
    +
    +/**
    + * Trilium's main entity which can represent text note, image, code note, file attachment etc.
    + *
    + * @extends AbstractBeccaEntity
    + */
    +class BNote extends AbstractBeccaEntity {
    +    static get entityName() { return "notes"; }
    +    static get primaryKeyName() { return "noteId"; }
    +    static get hashedProperties() { return ["noteId", "title", "isProtected", "type", "mime"]; }
    +
    +    constructor(row) {
    +        super();
    +
    +        if (!row) {
    +            return;
    +        }
    +
    +        this.updateFromRow(row);
    +        this.init();
    +    }
    +
    +    updateFromRow(row) {
    +        this.update([
    +            row.noteId,
    +            row.title,
    +            row.type,
    +            row.mime,
    +            row.isProtected,
    +            row.dateCreated,
    +            row.dateModified,
    +            row.utcDateCreated,
    +            row.utcDateModified
    +        ]);
    +    }
    +
    +    update([noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified]) {
    +        // ------ Database persisted attributes ------
    +
    +        /** @type {string} */
    +        this.noteId = noteId;
    +        /** @type {string} */
    +        this.title = title;
    +        /** @type {boolean} */
    +        this.isProtected = !!isProtected;
    +        /** @type {string} */
    +        this.type = type;
    +        /** @type {string} */
    +        this.mime = mime;
    +        /** @type {string} */
    +        this.dateCreated = dateCreated || dateUtils.localNowDateTime();
    +        /** @type {string} */
    +        this.dateModified = dateModified;
    +        /** @type {string} */
    +        this.utcDateCreated = utcDateCreated || dateUtils.utcNowDateTime();
    +        /** @type {string} */
    +        this.utcDateModified = utcDateModified;
    +        /** @type {boolean} - set during the deletion operation, before it is completed (removed from becca completely) */
    +        this.isBeingDeleted = false;
    +
    +        // ------ Derived attributes ------
    +
    +        /** @type {boolean} */
    +        this.isDecrypted = !this.noteId || !this.isProtected;
    +
    +        this.decrypt();
    +
    +        /** @type {string|null} */
    +        this.flatTextCache = null;
    +
    +        return this;
    +    }
    +
    +    init() {
    +        /** @type {BBranch[]}
    +         * @private */
    +        this.parentBranches = [];
    +        /** @type {BNote[]}
    +         * @private */
    +        this.parents = [];
    +        /** @type {BNote[]}
    +         * @private*/
    +        this.children = [];
    +        /** @type {BAttribute[]}
    +         * @private */
    +        this.ownedAttributes = [];
    +
    +        /** @type {BAttribute[]|null}
    +         * @private */
    +        this.__attributeCache = null;
    +        /** @type {BAttribute[]|null}
    +         * @private*/
    +        this.inheritableAttributeCache = null;
    +
    +        /** @type {BAttribute[]}
    +         * @private*/
    +        this.targetRelations = [];
    +
    +        this.becca.addNote(this.noteId, this);
    +
    +        /** @type {BNote[]|null}
    +         * @private */
    +        this.ancestorCache = null;
    +
    +        // following attributes are filled during searching from database
    +
    +        /**
    +         * size of the content in bytes
    +         * @type {int|null}
    +         * @private
    +         */
    +        this.contentSize = null;
    +        /**
    +         * size of the content and note revision contents in bytes
    +         * @type {int|null}
    +         * @private
    +         */
    +        this.noteSize = null;
    +        /**
    +         * number of note revisions for this note
    +         * @type {int|null}
    +         * @private
    +         */
    +        this.revisionCount = null;
    +    }
    +
    +    isContentAvailable() {
    +        return !this.noteId // new note which was not encrypted yet
    +            || !this.isProtected
    +            || protectedSessionService.isProtectedSessionAvailable()
    +    }
    +
    +    getTitleOrProtected() {
    +        return this.isContentAvailable() ? this.title : '[protected]';
    +    }
    +
    +    /** @returns {BBranch[]} */
    +    getParentBranches() {
    +        return this.parentBranches;
    +    }
    +
    +    /**
    +     * Returns <i>strong</i> (as opposed to <i>weak</i>) parent branches. See isWeak for details.
    +     *
    +     * @returns {BBranch[]}
    +     */
    +    getStrongParentBranches() {
    +        return this.getParentBranches().filter(branch => !branch.isWeak);
    +    }
    +
    +    /**
    +     * @returns {BBranch[]}
    +     * @deprecated use getParentBranches() instead
    +     */
    +    getBranches() {
    +        return this.parentBranches;
    +    }
    +
    +    /** @returns {BNote[]} */
    +    getParentNotes() {
    +        return this.parents;
    +    }
    +
    +    /** @returns {BNote[]} */
    +    getChildNotes() {
    +        return this.children;
    +    }
    +
    +    /** @returns {boolean} */
    +    hasChildren() {
    +        return this.children && this.children.length > 0;
    +    }
    +
    +    /** @returns {BBranch[]} */
    +    getChildBranches() {
    +        return this.children.map(childNote => this.becca.getBranchFromChildAndParent(childNote.noteId, this.noteId));
    +    }
    +
    +    /*
    +     * Note content has quite special handling - it's not a separate entity, but a lazily loaded
    +     * part of Note entity with its 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 entity changes records)
    +     * - but to the user note content and title changes are one and the same - single dateModified (so all changes must go through Note and content is not a separate entity)
    +     */
    +
    +    /** @returns {*} */
    +    getContent(silentNotFoundError = false) {
    +        const row = sql.getRow(`SELECT content FROM note_contents WHERE noteId = ?`, [this.noteId]);
    +
    +        if (!row) {
    +            if (silentNotFoundError) {
    +                return undefined;
    +            }
    +            else {
    +                throw new Error(`Cannot find note content for noteId=${this.noteId}`);
    +            }
    +        }
    +
    +        let content = row.content;
    +
    +        if (this.isProtected) {
    +            if (protectedSessionService.isProtectedSessionAvailable()) {
    +                content = content === null ? null : protectedSessionService.decrypt(content);
    +            }
    +            else {
    +                content = "";
    +            }
    +        }
    +
    +        if (this.isStringNote()) {
    +            return content === null
    +                ? ""
    +                : content.toString("UTF-8");
    +        }
    +        else {
    +            return content;
    +        }
    +    }
    +
    +    /** @returns {{contentLength, dateModified, utcDateModified}} */
    +    getContentMetadata() {
    +        return sql.getRow(`
    +            SELECT 
    +                LENGTH(content) AS contentLength, 
    +                dateModified,
    +                utcDateModified 
    +            FROM note_contents 
    +            WHERE noteId = ?`, [this.noteId]);
    +    }
    +
    +    get dateCreatedObj() {
    +        return this.dateCreated === null ? null : dayjs(this.dateCreated);
    +    }
    +
    +    get utcDateCreatedObj() {
    +        return this.utcDateCreated === null ? null : dayjs.utc(this.utcDateCreated);
    +    }
    +
    +    get dateModifiedObj() {
    +        return this.dateModified === null ? null : dayjs(this.dateModified);
    +    }
    +
    +    get utcDateModifiedObj() {
    +        return this.utcDateModified === null ? null : dayjs.utc(this.utcDateModified);
    +    }
    +
    +    /** @returns {*} */
    +    getJsonContent() {
    +        const content = this.getContent();
    +
    +        if (!content || !content.trim()) {
    +            return null;
    +        }
    +
    +        return JSON.parse(content);
    +    }
    +
    +    setContent(content, ignoreMissingProtectedSession = false) {
    +        if (content === null || content === undefined) {
    +            throw new Error(`Cannot set null content to note '${this.noteId}'`);
    +        }
    +
    +        if (this.isStringNote()) {
    +            content = content.toString();
    +        }
    +        else {
    +            content = Buffer.isBuffer(content) ? content : Buffer.from(content);
    +        }
    +
    +        const pojo = {
    +            noteId: this.noteId,
    +            content: content,
    +            dateModified: dateUtils.localNowDateTime(),
    +            utcDateModified: dateUtils.utcNowDateTime()
    +        };
    +
    +        if (this.isProtected) {
    +            if (protectedSessionService.isProtectedSessionAvailable()) {
    +                pojo.content = protectedSessionService.encrypt(pojo.content);
    +            }
    +            else if (!ignoreMissingProtectedSession) {
    +                throw new Error(`Cannot update content of noteId '${this.noteId}' since we're out of protected session.`);
    +            }
    +        }
    +
    +        sql.upsert("note_contents", "noteId", pojo);
    +
    +        const hash = utils.hash(`${this.noteId}|${pojo.content.toString()}`);
    +
    +        entityChangesService.addEntityChange({
    +            entityName: 'note_contents',
    +            entityId: this.noteId,
    +            hash: hash,
    +            isErased: false,
    +            utcDateChanged: pojo.utcDateModified,
    +            isSynced: true
    +        });
    +
    +        eventService.emit(eventService.ENTITY_CHANGED, {
    +            entityName: 'note_contents',
    +            entity: this
    +        });
    +    }
    +
    +    setJsonContent(content) {
    +        this.setContent(JSON.stringify(content, null, '\t'));
    +    }
    +
    +    /** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
    +    isRoot() {
    +        return this.noteId === 'root';
    +    }
    +
    +    /** @returns {boolean} true if this note is of application/json content type */
    +    isJson() {
    +        return this.mime === "application/json";
    +    }
    +
    +    /** @returns {boolean} true if this note is JavaScript (code or attachment) */
    +    isJavaScript() {
    +        return (this.type === "code" || this.type === "file" || this.type === 'launcher')
    +            && (this.mime.startsWith("application/javascript")
    +                || this.mime === "application/x-javascript"
    +                || this.mime === "text/javascript");
    +    }
    +
    +    /** @returns {boolean} true if this note is HTML */
    +    isHtml() {
    +        return ["code", "file", "render"].includes(this.type)
    +            && this.mime === "text/html";
    +    }
    +
    +    /** @returns {boolean} true if this note is an image */
    +    isImage() {
    +        return this.type === 'image'
    +            || (this.type === 'file' && this.mime?.startsWith('image/'));
    +    }
    +
    +    /** @returns {boolean} true if the note has string content (not binary) */
    +    isStringNote() {
    +        return utils.isStringNote(this.type, this.mime);
    +    }
    +
    +    /** @returns {string|null} JS script environment - either "frontend" or "backend" */
    +    getScriptEnv() {
    +        if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) {
    +            return "frontend";
    +        }
    +
    +        if (this.type === 'render') {
    +            return "frontend";
    +        }
    +
    +        if (this.isJavaScript() && this.mime.endsWith('env=backend')) {
    +            return "backend";
    +        }
    +
    +        return null;
    +    }
    +
    +    /**
    +     * @param {string} [type] - (optional) attribute type to filter
    +     * @param {string} [name] - (optional) attribute name to filter
    +     * @returns {BAttribute[]} all note's attributes, including inherited ones
    +     */
    +    getAttributes(type, name) {
    +        this.__validateTypeName(type, name);
    +        this.__ensureAttributeCacheIsAvailable();
    +
    +        if (type && name) {
    +            return this.__attributeCache.filter(attr => attr.name === name && attr.type === type);
    +        }
    +        else if (type) {
    +            return this.__attributeCache.filter(attr => attr.type === type);
    +        }
    +        else if (name) {
    +            return this.__attributeCache.filter(attr => attr.name === name);
    +        }
    +        else {
    +            // a bit unsafe to return the original array, but defensive copy would be costly
    +            return this.__attributeCache;
    +        }
    +    }
    +
    +    /** @private */
    +    __ensureAttributeCacheIsAvailable() {
    +        if (!this.__attributeCache) {
    +            this.__getAttributes([]);
    +        }
    +    }
    +
    +    /** @private */
    +    __getAttributes(path) {
    +        if (path.includes(this.noteId)) {
    +            return [];
    +        }
    +
    +        if (!this.__attributeCache) {
    +            const parentAttributes = this.ownedAttributes.slice();
    +            const newPath = [...path, this.noteId];
    +
    +            // inheritable attrs on root are typically not intended to be applied to hidden subtree #3537
    +            if (this.noteId !== 'root' && this.noteId !== '_hidden') {
    +                for (const parentNote of this.parents) {
    +                    parentAttributes.push(...parentNote.__getInheritableAttributes(newPath));
    +                }
    +            }
    +
    +            const templateAttributes = [];
    +
    +            for (const ownedAttr of parentAttributes) { // parentAttributes so we process also inherited templates
    +                if (ownedAttr.type === 'relation' && ['template', 'inherit'].includes(ownedAttr.name)) {
    +                    const templateNote = this.becca.notes[ownedAttr.value];
    +
    +                    if (templateNote) {
    +                        templateAttributes.push(
    +                            ...templateNote.__getAttributes(newPath)
    +                                // template attr is used as a marker for templates, but it's not meant to be inherited
    +                                .filter(attr => !(attr.type === 'label' && (attr.name === 'template' || attr.name === 'workspacetemplate')))
    +                        );
    +                    }
    +                }
    +            }
    +
    +            this.__attributeCache = [];
    +
    +            const addedAttributeIds = new Set();
    +
    +            for (const attr of parentAttributes.concat(templateAttributes)) {
    +                if (!addedAttributeIds.has(attr.attributeId)) {
    +                    addedAttributeIds.add(attr.attributeId);
    +
    +                    this.__attributeCache.push(attr);
    +                }
    +            }
    +
    +            this.inheritableAttributeCache = [];
    +
    +            for (const attr of this.__attributeCache) {
    +                if (attr.isInheritable) {
    +                    this.inheritableAttributeCache.push(attr);
    +                }
    +            }
    +        }
    +
    +        return this.__attributeCache;
    +    }
    +
    +    /**
    +     * @private
    +     * @returns {BAttribute[]}
    +     */
    +    __getInheritableAttributes(path) {
    +        if (path.includes(this.noteId)) {
    +            return [];
    +        }
    +
    +        if (!this.inheritableAttributeCache) {
    +            this.__getAttributes(path); // will refresh also this.inheritableAttributeCache
    +        }
    +
    +        return this.inheritableAttributeCache;
    +    }
    +
    +    __validateTypeName(type, name) {
    +        if (type && type !== 'label' && type !== 'relation') {
    +            throw new Error(`Unrecognized attribute type '${type}'. Only 'label' and 'relation' are possible values.`);
    +        }
    +
    +        if (name) {
    +            const firstLetter = name.charAt(0);
    +            if (firstLetter === '#' || firstLetter === '~') {
    +                throw new Error(`Detect '#' or '~' in the attribute's name. In the API, attribute names should be set without these characters.`);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * @param type
    +     * @param name
    +     * @param [value]
    +     * @returns {boolean}
    +     */
    +    hasAttribute(type, name, value = null) {
    +        return !!this.getAttributes().find(attr =>
    +            attr.name === name
    +            && (value === undefined || value === null || attr.value === value)
    +            && attr.type === type
    +        );
    +    }
    +
    +    getAttributeCaseInsensitive(type, name, value) {
    +        name = name.toLowerCase();
    +        value = value ? value.toLowerCase() : null;
    +
    +        return this.getAttributes().find(
    +            attr => attr.name.toLowerCase() === name
    +            && (!value || attr.value.toLowerCase() === value)
    +            && attr.type === type);
    +    }
    +
    +    getRelationTarget(name) {
    +        const relation = this.getAttributes().find(attr => attr.name === name && attr.type === 'relation');
    +
    +        return relation ? relation.targetNote : null;
    +    }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @param {string} [value] - label value
    +     * @returns {boolean} true if label exists (including inherited)
    +     */
    +    hasLabel(name, value) { return this.hasAttribute(LABEL, name, value); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @param {string} [value] - label value
    +     * @returns {boolean} true if label exists (excluding inherited)
    +     */
    +    hasOwnedLabel(name, value) { return this.hasOwnedAttribute(LABEL, name, value); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @param {string} [value] - relation value
    +     * @returns {boolean} true if relation exists (including inherited)
    +     */
    +    hasRelation(name, value) { return this.hasAttribute(RELATION, name, value); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @param {string} [value] - relation value
    +     * @returns {boolean} true if relation exists (excluding inherited)
    +     */
    +    hasOwnedRelation(name, value) { return this.hasOwnedAttribute(RELATION, name, value); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {BAttribute|null} label if it exists, null otherwise
    +     */
    +    getLabel(name) { return this.getAttribute(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {BAttribute|null} label if it exists, null otherwise
    +     */
    +    getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {BAttribute|null} relation if it exists, null otherwise
    +     */
    +    getRelation(name) { return this.getAttribute(RELATION, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {BAttribute|null} relation if it exists, null otherwise
    +     */
    +    getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {string|null} label value if label exists, null otherwise
    +     */
    +    getLabelValue(name) { return this.getAttributeValue(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {string|null} label value if label exists, null otherwise
    +     */
    +    getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {string|null} relation value if relation exists, null otherwise
    +     */
    +    getRelationValue(name) { return this.getAttributeValue(RELATION, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {string|null} relation value if relation exists, null otherwise
    +     */
    +    getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @param {string} [value] - attribute value
    +     * @returns {boolean} true if note has an attribute with given type and name (excluding inherited)
    +     */
    +    hasOwnedAttribute(type, name, value) {
    +        return !!this.getOwnedAttribute(type, name, value);
    +    }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @returns {BAttribute} attribute of given type and name. If there's more such attributes, first is  returned. Returns null if there's no such attribute belonging to this note.
    +     */
    +    getAttribute(type, name) {
    +        const attributes = this.getAttributes();
    +
    +        return attributes.find(attr => attr.name === name && attr.type === type);
    +    }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @returns {string|null} attribute value of given type and name or null if no such attribute exists.
    +     */
    +    getAttributeValue(type, name) {
    +        const attr = this.getAttribute(type, name);
    +
    +        return attr ? attr.value : null;
    +    }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @returns {string|null} attribute value of given type and name or null if no such attribute exists.
    +     */
    +    getOwnedAttributeValue(type, name) {
    +        const attr = this.getOwnedAttribute(type, name);
    +
    +        return attr ? attr.value : null;
    +    }
    +
    +    /**
    +     * @param {string} [name] - label name to filter
    +     * @returns {BAttribute[]} all note's labels (attributes with type label), including inherited ones
    +     */
    +    getLabels(name) {
    +        return this.getAttributes(LABEL, name);
    +    }
    +
    +    /**
    +     * @param {string} [name] - label name to filter
    +     * @returns {string[]} all note's label values, including inherited ones
    +     */
    +    getLabelValues(name) {
    +        return this.getLabels(name).map(l => l.value);
    +    }
    +
    +    /**
    +     * @param {string} [name] - label name to filter
    +     * @returns {BAttribute[]} all note's labels (attributes with type label), excluding inherited ones
    +     */
    +    getOwnedLabels(name) {
    +        return this.getOwnedAttributes(LABEL, name);
    +    }
    +
    +    /**
    +     * @param {string} [name] - label name to filter
    +     * @returns {string[]} all note's label values, excluding inherited ones
    +     */
    +    getOwnedLabelValues(name) {
    +        return this.getOwnedAttributes(LABEL, name).map(l => l.value);
    +    }
    +
    +    /**
    +     * @param {string} [name] - relation name to filter
    +     * @returns {BAttribute[]} all note's relations (attributes with type relation), including inherited ones
    +     */
    +    getRelations(name) {
    +        return this.getAttributes(RELATION, name);
    +    }
    +
    +    /**
    +     * @param {string} [name] - relation name to filter
    +     * @returns {BAttribute[]} all note's relations (attributes with type relation), excluding inherited ones
    +     */
    +    getOwnedRelations(name) {
    +        return this.getOwnedAttributes(RELATION, name);
    +    }
    +
    +    /**
    +     * @param {string|null} [type] - (optional) attribute type to filter
    +     * @param {string|null} [name] - (optional) attribute name to filter
    +     * @param {string|null} [value] - (optional) attribute value to filter
    +     * @returns {BAttribute[]} note's "owned" attributes - excluding inherited ones
    +     */
    +    getOwnedAttributes(type = null, name = null, value = null) {
    +        this.__validateTypeName(type, name);
    +
    +        if (type && name && value !== undefined && value !== null) {
    +            return this.ownedAttributes.filter(attr => attr.name === name && attr.value === value && attr.type === type);
    +        }
    +        else if (type && name) {
    +            return this.ownedAttributes.filter(attr => attr.name === name && attr.type === type);
    +        }
    +        else if (type) {
    +            return this.ownedAttributes.filter(attr => attr.type === type);
    +        }
    +        else if (name) {
    +            return this.ownedAttributes.filter(attr => attr.name === name);
    +        }
    +        else {
    +            return this.ownedAttributes.slice();
    +        }
    +    }
    +
    +    /**
    +     * @returns {BAttribute} attribute belonging to this specific note (excludes inherited attributes)
    +     *
    +     * This method can be significantly faster than the getAttribute()
    +     */
    +    getOwnedAttribute(type, name, value = null) {
    +        const attrs = this.getOwnedAttributes(type, name, value);
    +
    +        return attrs.length > 0 ? attrs[0] : null;
    +    }
    +
    +    get isArchived() {
    +        return this.hasAttribute('label', 'archived');
    +    }
    +
    +    hasInheritableArchivedLabel() {
    +        for (const attr of this.getAttributes()) {
    +            if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) {
    +                return true;
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    // 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
    +    sortParents() {
    +        this.parentBranches.sort((a, b) => {
    +            if (a.parentNote?.isArchived) {
    +                return 1;
    +            } else if (a.parentNote?.isHiddenCompletely()) {
    +                return 1;
    +            } else {
    +                return -1;
    +            }
    +        });
    +
    +        this.parents = this.parentBranches
    +            .map(branch => branch.parentNote)
    +            .filter(note => !!note);
    +    }
    +
    +    sortChildren() {
    +        if (this.children.length === 0) {
    +            return;
    +        }
    +
    +        const becca = this.becca;
    +
    +        this.children.sort((a, b) => {
    +            const aBranch = becca.getBranchFromChildAndParent(a.noteId, this.noteId);
    +            const bBranch = becca.getBranchFromChildAndParent(b.noteId, this.noteId);
    +
    +            return aBranch?.notePosition < bBranch?.notePosition ? -1 : 1;
    +        });
    +    }
    +
    +    /**
    +     * This is used for:
    +     * - fast searching
    +     * - note similarity evaluation
    +     *
    +     * @returns {string} - returns flattened textual representation of note, prefixes and attributes
    +     */
    +    getFlatText() {
    +        if (!this.flatTextCache) {
    +            this.flatTextCache = `${this.noteId} ${this.type} ${this.mime} `;
    +
    +            for (const branch of this.parentBranches) {
    +                if (branch.prefix) {
    +                    this.flatTextCache += `${branch.prefix} `;
    +                }
    +            }
    +
    +            this.flatTextCache += `${this.title} `;
    +
    +            for (const attr of this.getAttributes()) {
    +                // it's best to use space as separator since spaces are filtered from the search string by the tokenization into words
    +                this.flatTextCache += `${attr.type === 'label' ? '#' : '~'}${attr.name}`;
    +
    +                if (attr.value) {
    +                    this.flatTextCache += `=${attr.value}`;
    +                }
    +
    +                this.flatTextCache += ' ';
    +            }
    +
    +            this.flatTextCache = utils.normalize(this.flatTextCache);
    +        }
    +
    +        return this.flatTextCache;
    +    }
    +
    +    invalidateThisCache() {
    +        this.flatTextCache = null;
    +
    +        this.__attributeCache = null;
    +        this.inheritableAttributeCache = null;
    +        this.ancestorCache = null;
    +    }
    +
    +    invalidateSubTree(path = []) {
    +        if (path.includes(this.noteId)) {
    +            return;
    +        }
    +
    +        this.invalidateThisCache();
    +
    +        if (this.children.length || this.targetRelations.length) {
    +            path = [...path, this.noteId];
    +        }
    +
    +        for (const childNote of this.children) {
    +            childNote.invalidateSubTree(path);
    +        }
    +
    +        for (const targetRelation of this.targetRelations) {
    +            if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
    +                const note = targetRelation.note;
    +
    +                if (note) {
    +                    note.invalidateSubTree(path);
    +                }
    +            }
    +        }
    +    }
    +
    +    invalidateSubtreeFlatText() {
    +        this.flatTextCache = null;
    +
    +        for (const childNote of this.children) {
    +            childNote.invalidateSubtreeFlatText();
    +        }
    +
    +        for (const targetRelation of this.targetRelations) {
    +            if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
    +                const note = targetRelation.note;
    +
    +                if (note) {
    +                    note.invalidateSubtreeFlatText();
    +                }
    +            }
    +        }
    +    }
    +
    +    getRelationDefinitions() {
    +        return this.getLabels()
    +            .filter(l => l.name.startsWith("relation:"));
    +    }
    +
    +    getLabelDefinitions() {
    +        return this.getLabels()
    +            .filter(l => l.name.startsWith("relation:"));
    +    }
    +
    +    isInherited() {
    +        return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit');
    +    }
    +
    +    /** @returns {BNote[]} */
    +    getSubtreeNotesIncludingTemplated() {
    +        const set = new Set();
    +
    +        function inner(note) {
    +            // _hidden is not counted as subtree for the purpose of inheritance
    +            if (set.has(note) || note.noteId === '_hidden') {
    +                return;
    +            }
    +
    +            set.add(note);
    +
    +            for (const childNote of note.children) {
    +                inner(childNote);
    +            }
    +
    +            for (const targetRelation of note.targetRelations) {
    +                if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
    +                    const targetNote = targetRelation.note;
    +
    +                    if (targetNote) {
    +                        inner(targetNote);
    +                    }
    +                }
    +            }
    +        }
    +
    +        inner(this);
    +
    +        return Array.from(set);
    +    }
    +
    +    /** @returns {BNote[]} */
    +    getSearchResultNotes() {
    +        if (this.type !== 'search') {
    +            return [];
    +        }
    +
    +        try {
    +            const searchService = require("../../services/search/services/search");
    +            const {searchResultNoteIds} = searchService.searchFromNote(this);
    +
    +            const becca = this.becca;
    +            return searchResultNoteIds
    +                .map(resultNoteId => becca.notes[resultNoteId])
    +                .filter(note => !!note);
    +        }
    +        catch (e) {
    +            log.error(`Could not resolve search note ${this.noteId}: ${e.message}`);
    +            return [];
    +        }
    +    }
    +
    +    /**
    +     * @returns {{notes: BNote[], relationships: Array.<{parentNoteId: string, childNoteId: string}>}}
    +     */
    +    getSubtree({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) {
    +        const noteSet = new Set();
    +        const relationships = []; // list of tuples parentNoteId -> childNoteId
    +
    +        function resolveSearchNote(searchNote) {
    +            try {
    +                for (const resultNote of searchNote.getSearchResultNotes()) {
    +                    addSubtreeNotesInner(resultNote, searchNote);
    +                }
    +            }
    +            catch (e) {
    +                log.error(`Could not resolve search note ${searchNote?.noteId}: ${e.message}`);
    +            }
    +        }
    +
    +        function addSubtreeNotesInner(note, parentNote = null) {
    +            if (note.noteId === '_hidden' && !includeHidden) {
    +                return;
    +            }
    +
    +            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;
    +            }
    +
    +            if (!includeArchived && note.isArchived) {
    +                return;
    +            }
    +
    +            noteSet.add(note);
    +
    +            if (note.type === 'search') {
    +                if (resolveSearch) {
    +                    resolveSearchNote(note);
    +                }
    +            }
    +            else {
    +                for (const childNote of note.children) {
    +                    addSubtreeNotesInner(childNote, note);
    +                }
    +            }
    +        }
    +
    +        addSubtreeNotesInner(this);
    +
    +        return {
    +            notes: Array.from(noteSet),
    +            relationships
    +        };
    +    }
    +
    +    /** @returns {String[]} - includes the subtree node as well */
    +    getSubtreeNoteIds({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) {
    +        return this.getSubtree({includeArchived, includeHidden, resolveSearch})
    +            .notes
    +            .map(note => note.noteId);
    +    }
    +
    +    /** @deprecated use getSubtreeNoteIds() instead */
    +    getDescendantNoteIds() {
    +        return this.getSubtreeNoteIds();
    +    }
    +
    +    get parentCount() {
    +        return this.parents.length;
    +    }
    +
    +    get childrenCount() {
    +        return this.children.length;
    +    }
    +
    +    get labelCount() {
    +        return this.getAttributes().filter(attr => attr.type === 'label').length;
    +    }
    +
    +    get ownedLabelCount() {
    +        return this.ownedAttributes.filter(attr => attr.type === 'label').length;
    +    }
    +
    +    get relationCount() {
    +        return this.getAttributes().filter(attr => attr.type === 'relation' && !attr.isAutoLink()).length;
    +    }
    +
    +    get relationCountIncludingLinks() {
    +        return this.getAttributes().filter(attr => attr.type === 'relation').length;
    +    }
    +
    +    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;
    +    }
    +
    +    get attributeCount() {
    +        return this.getAttributes().length;
    +    }
    +
    +    get ownedAttributeCount() {
    +        return this.getOwnedAttributes().length;
    +    }
    +
    +    /** @returns {BNote[]} */
    +    getAncestors() {
    +        if (!this.ancestorCache) {
    +            const noteIds = new Set();
    +            this.ancestorCache = [];
    +
    +            for (const parent of this.parents) {
    +                if (noteIds.has(parent.noteId)) {
    +                    continue;
    +                }
    +
    +                this.ancestorCache.push(parent);
    +                noteIds.add(parent.noteId);
    +
    +                for (const ancestorNote of parent.getAncestors()) {
    +                    if (!noteIds.has(ancestorNote.noteId)) {
    +                        this.ancestorCache.push(ancestorNote);
    +                        noteIds.add(ancestorNote.noteId);
    +                    }
    +                }
    +            }
    +        }
    +
    +        return this.ancestorCache;
    +    }
    +
    +    /** @returns {boolean} */
    +    hasAncestor(ancestorNoteId) {
    +        for (const ancestorNote of this.getAncestors()) {
    +            if (ancestorNote.noteId === ancestorNoteId) {
    +                return true;
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    isInHiddenSubtree() {
    +        return this.noteId === '_hidden' || this.hasAncestor('_hidden');
    +    }
    +
    +    getTargetRelations() {
    +        return this.targetRelations;
    +    }
    +
    +    /** @returns {BNote[]} - returns only notes which are templated, does not include their subtrees
    +     *                     in effect returns notes which are influenced by note's non-inheritable attributes */
    +    getInheritingNotes() {
    +        const arr = [this];
    +
    +        for (const targetRelation of this.targetRelations) {
    +            if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
    +                const note = targetRelation.note;
    +
    +                if (note) {
    +                    arr.push(note);
    +                }
    +            }
    +        }
    +
    +        return arr;
    +    }
    +
    +    getDistanceToAncestor(ancestorNoteId) {
    +        if (this.noteId === ancestorNoteId) {
    +            return 0;
    +        }
    +
    +        let minDistance = 999999;
    +
    +        for (const parent of this.parents) {
    +            minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1);
    +        }
    +
    +        return minDistance;
    +    }
    +
    +    /** @returns {BNoteRevision[]} */
    +    getNoteRevisions() {
    +        return sql.getRows("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId])
    +            .map(row => new BNoteRevision(row));
    +    }
    +
    +    /**
    +     * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path)
    +     */
    +    getAllNotePaths() {
    +        if (this.noteId === 'root') {
    +            return [['root']];
    +        }
    +
    +        const notePaths = [];
    +
    +        for (const parentNote of this.getParentNotes()) {
    +            for (const parentPath of parentNote.getAllNotePaths()) {
    +                parentPath.push(this.noteId);
    +                notePaths.push(parentPath);
    +            }
    +        }
    +
    +        return notePaths;
    +    }
    +
    +    /**
    +     * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
    +     */
    +    isHiddenCompletely() {
    +        if (this.noteId === 'root') {
    +            return false;
    +        }
    +
    +        for (const parentNote of this.parents) {
    +            if (parentNote.noteId === 'root') {
    +                return false;
    +            } else if (parentNote.noteId === '_hidden') {
    +                continue;
    +            }
    +
    +            if (!parentNote.isHiddenCompletely()) {
    +                return false;
    +            }
    +        }
    +
    +        return true;
    +    }
    +
    +    /**
    +     * @param ancestorNoteId
    +     * @returns {boolean} - true if ancestorNoteId occurs in at least one of the note's paths
    +     */
    +    isDescendantOfNote(ancestorNoteId) {
    +        const notePaths = this.getAllNotePaths();
    +
    +        return notePaths.some(path => path.includes(ancestorNoteId));
    +    }
    +
    +    /**
    +     * Update's given attribute's value or creates it if it doesn't exist
    +     *
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @param {string} [value] - attribute value (optional)
    +     */
    +    setAttribute(type, name, value) {
    +        const attributes = this.getOwnedAttributes();
    +        const attr = attributes.find(attr => attr.type === type && attr.name === name);
    +
    +        value = value?.toString() || "";
    +
    +        if (attr) {
    +            if (attr.value !== value) {
    +                attr.value = value;
    +                attr.save();
    +            }
    +        }
    +        else {
    +            const BAttribute = require("./battribute");
    +
    +            new BAttribute({
    +                noteId: this.noteId,
    +                type: type,
    +                name: name,
    +                value: value
    +            }).save();
    +        }
    +    }
    +
    +    /**
    +     * Removes given attribute name-value pair if it exists.
    +     *
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @param {string} [value] - attribute value (optional)
    +     */
    +    removeAttribute(type, name, value) {
    +        const attributes = this.getOwnedAttributes();
    +
    +        for (const attribute of attributes) {
    +            if (attribute.type === type && attribute.name === name && (value === undefined || value === attribute.value)) {
    +                attribute.markAsDeleted();
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Adds a new attribute to this note. The attribute is saved and returned.
    +     * See addLabel, addRelation for more specific methods.
    +     *
    +     * @param {string} type - attribute type (label / relation)
    +     * @param {string} name - name of the attribute, not including the leading ~/#
    +     * @param {string} [value] - value of the attribute - text for labels, target note ID for relations; optional.
    +     * @param {boolean} [isInheritable=false]
    +     * @param {int} [position]
    +     * @returns {BAttribute}
    +     */
    +    addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
    +        const BAttribute = require("./battribute");
    +
    +        return new BAttribute({
    +            noteId: this.noteId,
    +            type: type,
    +            name: name,
    +            value: value,
    +            isInheritable: isInheritable,
    +            position: position
    +        }).save();
    +    }
    +
    +    /**
    +     * Adds a new label to this note. The label attribute is saved and returned.
    +     *
    +     * @param {string} name - name of the label, not including the leading #
    +     * @param {string} [value] - text value of the label; optional
    +     * @param {boolean} [isInheritable=false]
    +     * @returns {BAttribute}
    +     */
    +    addLabel(name, value = "", isInheritable = false) {
    +        return this.addAttribute(LABEL, name, value, isInheritable);
    +    }
    +
    +    /**
    +     * Adds a new relation to this note. The relation attribute is saved and
    +     * returned.
    +     *
    +     * @param {string} name - name of the relation, not including the leading ~
    +     * @param {string} targetNoteId
    +     * @param {boolean} [isInheritable=false]
    +     * @returns {BAttribute}
    +     */
    +    addRelation(name, targetNoteId, isInheritable = false) {
    +        return this.addAttribute(RELATION, name, targetNoteId, isInheritable);
    +    }
    +
    +    /**
    +     * Based on enabled, attribute is either set or removed.
    +     *
    +     * @param {string} type - attribute type ('relation', 'label' etc.)
    +     * @param {boolean} enabled - toggle On or Off
    +     * @param {string} name - attribute name
    +     * @param {string} [value] - attribute value (optional)
    +     */
    +    toggleAttribute(type, enabled, name, value) {
    +        if (enabled) {
    +            this.setAttribute(type, name, value);
    +        }
    +        else {
    +            this.removeAttribute(type, name, value);
    +        }
    +    }
    +
    +    /**
    +     * Based on enabled, label is either set or removed.
    +     *
    +     * @param {boolean} enabled - toggle On or Off
    +     * @param {string} name - label name
    +     * @param {string} [value] - label value (optional)
    +     */
    +    toggleLabel(enabled, name, value) { return this.toggleAttribute(LABEL, enabled, name, value); }
    +
    +    /**
    +     * Based on enabled, relation is either set or removed.
    +     *
    +     * @param {boolean} enabled - toggle On or Off
    +     * @param {string} name - relation name
    +     * @param {string} [value] - relation value (noteId)
    +     */
    +    toggleRelation(enabled, name, value) { return this.toggleAttribute(RELATION, enabled, name, value); }
    +
    +    /**
    +     * Update's given label's value or creates it if it doesn't exist
    +     *
    +     * @param {string} name - label name
    +     * @param {string} [value] - label value
    +     */
    +    setLabel(name, value) { return this.setAttribute(LABEL, name, value); }
    +
    +    /**
    +     * Update's given relation's value or creates it if it doesn't exist
    +     *
    +     * @param {string} name - relation name
    +     * @param {string} value - relation value (noteId)
    +     */
    +    setRelation(name, value) { return this.setAttribute(RELATION, name, value); }
    +
    +    /**
    +     * Remove label name-value pair, if it exists.
    +     *
    +     * @param {string} name - label name
    +     * @param {string} [value] - label value
    +     */
    +    removeLabel(name, value) { return this.removeAttribute(LABEL, name, value); }
    +
    +    /**
    +     * Remove relation name-value pair, if it exists.
    +     *
    +     * @param {string} name - relation name
    +     * @param {string} [value] - relation value (noteId)
    +     */
    +    removeRelation(name, value) { return this.removeAttribute(RELATION, name, value); }
    +
    +    searchNotesInSubtree(searchString) {
    +        const searchService = require("../../services/search/services/search");
    +
    +        return searchService.searchNotes(searchString);
    +    }
    +
    +    searchNoteInSubtree(searchString) {
    +        return this.searchNotesInSubtree(searchString)[0];
    +    }
    +
    +    /**
    +     * @param parentNoteId
    +     * @returns {{success: boolean, message: string}}
    +     */
    +    cloneTo(parentNoteId) {
    +        const cloningService = require("../../services/cloning");
    +
    +        const branch = this.becca.getNote(parentNoteId).getParentBranches()[0];
    +
    +        return cloningService.cloneNoteToBranch(this.noteId, branch.branchId);
    +    }
    +
    +    /**
    +     * (Soft) delete a note and all its descendants.
    +     *
    +     * @param {string} [deleteId] - optional delete identified
    +     * @param {TaskContext} [taskContext]
    +     */
    +    deleteNote(deleteId, taskContext) {
    +        if (this.isDeleted) {
    +            return;
    +        }
    +
    +        if (!deleteId) {
    +            deleteId = utils.randomString(10);
    +        }
    +
    +        if (!taskContext) {
    +            taskContext = new TaskContext('no-progress-reporting');
    +        }
    +
    +        // needs to be run before branches and attributes are deleted and thus attached relations disappear
    +        const handlers = require("../../services/handlers");
    +        handlers.runAttachedRelations(this, 'runOnNoteDeletion', this);
    +        taskContext.noteDeletionHandlerTriggered = true;
    +
    +        for (const branch of this.getParentBranches()) {
    +            branch.deleteBranch(deleteId, taskContext);
    +        }
    +    }
    +
    +    decrypt() {
    +        if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
    +            try {
    +                this.title = protectedSessionService.decryptString(this.title);
    +                this.flatTextCache = null;
    +
    +                this.isDecrypted = true;
    +            }
    +            catch (e) {
    +                log.error(`Could not decrypt note ${this.noteId}: ${e.message} ${e.stack}`);
    +            }
    +        }
    +    }
    +
    +    isLaunchBarConfig() {
    +        return this.type === 'launcher' || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(this.noteId);
    +    }
    +
    +    isOptions() {
    +        return this.noteId.startsWith("_options");
    +    }
    +
    +    get isDeleted() {
    +        return !(this.noteId in this.becca.notes) || this.isBeingDeleted;
    +    }
    +
    +    /**
    +     * @returns {BNoteRevision|null}
    +     */
    +    saveNoteRevision() {
    +        const content = this.getContent();
    +
    +        if (!content || (Buffer.isBuffer(content) && content.byteLength === 0)) {
    +            return null;
    +        }
    +
    +        const contentMetadata = this.getContentMetadata();
    +
    +        const noteRevision = new BNoteRevision({
    +            noteId: this.noteId,
    +            // title and text should be decrypted now
    +            title: this.title,
    +            type: this.type,
    +            mime: this.mime,
    +            isProtected: this.isProtected,
    +            utcDateLastEdited: this.utcDateModified > contentMetadata.utcDateModified
    +                ? this.utcDateModified
    +                : contentMetadata.utcDateModified,
    +            utcDateCreated: dateUtils.utcNowDateTime(),
    +            utcDateModified: dateUtils.utcNowDateTime(),
    +            dateLastEdited: this.dateModified > contentMetadata.dateModified
    +                ? this.dateModified
    +                : contentMetadata.dateModified,
    +            dateCreated: dateUtils.localNowDateTime()
    +        }, true).save();
    +
    +        noteRevision.setContent(content);
    +
    +        return noteRevision;
    +    }
    +
    +    beforeSaving() {
    +        super.beforeSaving();
    +
    +        this.becca.addNote(this.noteId, this);
    +
    +        this.dateModified = dateUtils.localNowDateTime();
    +        this.utcDateModified = dateUtils.utcNowDateTime();
    +    }
    +
    +    getPojo() {
    +        return {
    +            noteId: this.noteId,
    +            title: this.title,
    +            isProtected: this.isProtected,
    +            type: this.type,
    +            mime: this.mime,
    +            isDeleted: false,
    +            dateCreated: this.dateCreated,
    +            dateModified: this.dateModified,
    +            utcDateCreated: this.utcDateCreated,
    +            utcDateModified: this.utcDateModified
    +        };
    +    }
    +
    +    getPojoToSave() {
    +        const pojo = this.getPojo();
    +
    +        if (pojo.isProtected) {
    +            if (this.isDecrypted) {
    +                pojo.title = protectedSessionService.encrypt(pojo.title);
    +            }
    +            else {
    +                // updating protected note outside of protected session means we will keep original ciphertexts
    +                delete pojo.title;
    +            }
    +        }
    +
    +        return pojo;
    +    }
    +}
    +
    +module.exports = BNote;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/backend_api/becca_entities_bnote_revision.js.html b/docs/backend_api/becca_entities_bnote_revision.js.html new file mode 100644 index 000000000..c140f1d10 --- /dev/null +++ b/docs/backend_api/becca_entities_bnote_revision.js.html @@ -0,0 +1,242 @@ + + + + + JSDoc: Source: becca/entities/bnote_revision.js + + + + + + + + + + +
    + +

    Source: becca/entities/bnote_revision.js

    + + + + + + +
    +
    +
    "use strict";
    +
    +const protectedSessionService = require('../../services/protected_session');
    +const utils = require('../../services/utils');
    +const sql = require('../../services/sql');
    +const dateUtils = require('../../services/date_utils');
    +const becca = require('../becca');
    +const entityChangesService = require('../../services/entity_changes');
    +const AbstractBeccaEntity = require("./abstract_becca_entity");
    +
    +/**
    + * NoteRevision represents snapshot of note's title and content at some point in the past.
    + * It's used for seamless note versioning.
    + *
    + * @extends AbstractBeccaEntity
    + */
    +class BNoteRevision extends AbstractBeccaEntity {
    +    static get entityName() { return "note_revisions"; }
    +    static get primaryKeyName() { return "noteRevisionId"; }
    +    static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; }
    +
    +    constructor(row, titleDecrypted = false) {
    +        super();
    +
    +        /** @type {string} */
    +        this.noteRevisionId = row.noteRevisionId;
    +        /** @type {string} */
    +        this.noteId = row.noteId;
    +        /** @type {string} */
    +        this.type = row.type;
    +        /** @type {string} */
    +        this.mime = row.mime;
    +        /** @type {boolean} */
    +        this.isProtected = !!row.isProtected;
    +        /** @type {string} */
    +        this.title = row.title;
    +        /** @type {string} */
    +        this.dateLastEdited = row.dateLastEdited;
    +        /** @type {string} */
    +        this.dateCreated = row.dateCreated;
    +        /** @type {string} */
    +        this.utcDateLastEdited = row.utcDateLastEdited;
    +        /** @type {string} */
    +        this.utcDateCreated = row.utcDateCreated;
    +        /** @type {string} */
    +        this.utcDateModified = row.utcDateModified;
    +        /** @type {number} */
    +        this.contentLength = row.contentLength;
    +
    +        if (this.isProtected && !titleDecrypted) {
    +            this.title = protectedSessionService.isProtectedSessionAvailable()
    +                ? protectedSessionService.decryptString(this.title)
    +                : "[protected]";
    +        }
    +    }
    +
    +    getNote() {
    +        return becca.notes[this.noteId];
    +    }
    +
    +    /** @returns {boolean} true if the note has string content (not binary) */
    +    isStringNote() {
    +        return utils.isStringNote(this.type, this.mime);
    +    }
    +
    +    /*
    +     * Note revision content has quite special handling - it's not a separate entity, but a lazily loaded
    +     * part of NoteRevision entity with its own sync. Reason behind this hybrid design is that
    +     * 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.
    +     *
    +     * This is the same approach as is used for Note's content.
    +     */
    +
    +    /** @returns {*} */
    +    getContent(silentNotFoundError = false) {
    +        const res = sql.getRow(`SELECT content FROM note_revision_contents WHERE noteRevisionId = ?`, [this.noteRevisionId]);
    +
    +        if (!res) {
    +            if (silentNotFoundError) {
    +                return undefined;
    +            }
    +            else {
    +                throw new Error(`Cannot find note revision content for noteRevisionId=${this.noteRevisionId}`);
    +            }
    +        }
    +
    +        let content = res.content;
    +
    +        if (this.isProtected) {
    +            if (protectedSessionService.isProtectedSessionAvailable()) {
    +                content = protectedSessionService.decrypt(content);
    +            }
    +            else {
    +                content = "";
    +            }
    +        }
    +
    +        if (this.isStringNote()) {
    +            return content === null
    +                ? ""
    +                : content.toString("UTF-8");
    +        }
    +        else {
    +            return content;
    +        }
    +    }
    +
    +    setContent(content) {
    +        const pojo = {
    +            noteRevisionId: this.noteRevisionId,
    +            content: content,
    +            utcDateModified: dateUtils.utcNowDateTime()
    +        };
    +
    +        if (this.isProtected) {
    +            if (protectedSessionService.isProtectedSessionAvailable()) {
    +                pojo.content = protectedSessionService.encrypt(pojo.content);
    +            }
    +            else {
    +                throw new Error(`Cannot update content of noteRevisionId=${this.noteRevisionId} since we're out of protected session.`);
    +            }
    +        }
    +
    +        sql.upsert("note_revision_contents", "noteRevisionId", pojo);
    +
    +        const hash = utils.hash(`${this.noteRevisionId}|${pojo.content.toString()}`);
    +
    +        entityChangesService.addEntityChange({
    +            entityName: 'note_revision_contents',
    +            entityId: this.noteRevisionId,
    +            hash: hash,
    +            isErased: false,
    +            utcDateChanged: this.getUtcDateChanged(),
    +            isSynced: true
    +        });
    +    }
    +
    +    /** @returns {{contentLength, dateModified, utcDateModified}} */
    +    getContentMetadata() {
    +        return sql.getRow(`
    +            SELECT 
    +                LENGTH(content) AS contentLength, 
    +                dateModified,
    +                utcDateModified 
    +            FROM note_revision_contents 
    +            WHERE noteRevisionId = ?`, [this.noteRevisionId]);
    +    }
    +
    +    beforeSaving() {
    +        super.beforeSaving();
    +
    +        this.utcDateModified = dateUtils.utcNowDateTime();
    +    }
    +
    +    getPojo() {
    +        return {
    +            noteRevisionId: this.noteRevisionId,
    +            noteId: this.noteId,
    +            type: this.type,
    +            mime: this.mime,
    +            isProtected: this.isProtected,
    +            title: this.title,
    +            dateLastEdited: this.dateLastEdited,
    +            dateCreated: this.dateCreated,
    +            utcDateLastEdited: this.utcDateLastEdited,
    +            utcDateCreated: this.utcDateCreated,
    +            utcDateModified: this.utcDateModified,
    +            contentLength: this.contentLength
    +        };
    +    }
    +
    +    getPojoToSave() {
    +        const pojo = this.getPojo();
    +        delete pojo.contentLength; // not getting persisted
    +
    +        if (pojo.isProtected) {
    +            if (protectedSessionService.isProtectedSessionAvailable()) {
    +                pojo.title = protectedSessionService.encrypt(this.title);
    +            }
    +            else {
    +                // updating protected note outside of protected session means we will keep original ciphertexts
    +                delete pojo.title;
    +            }
    +        }
    +
    +        return pojo;
    +    }
    +}
    +
    +module.exports = BNoteRevision;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/backend_api/becca_entities_boption.js.html b/docs/backend_api/becca_entities_boption.js.html new file mode 100644 index 000000000..ab2c22f51 --- /dev/null +++ b/docs/backend_api/becca_entities_boption.js.html @@ -0,0 +1,98 @@ + + + + + JSDoc: Source: becca/entities/boption.js + + + + + + + + + + +
    + +

    Source: becca/entities/boption.js

    + + + + + + +
    +
    +
    "use strict";
    +
    +const dateUtils = require('../../services/date_utils');
    +const AbstractBeccaEntity = require("./abstract_becca_entity");
    +
    +/**
    + * Option represents name-value pair, either directly configurable by the user or some system property.
    + *
    + * @extends AbstractBeccaEntity
    + */
    +class BOption extends AbstractBeccaEntity {
    +    static get entityName() { return "options"; }
    +    static get primaryKeyName() { return "name"; }
    +    static get hashedProperties() { return ["name", "value"]; }
    +
    +    constructor(row) {
    +        super();
    +
    +        /** @type {string} */
    +        this.name = row.name;
    +        /** @type {string} */
    +        this.value = row.value;
    +        /** @type {boolean} */
    +        this.isSynced = !!row.isSynced;
    +        /** @type {string} */
    +        this.utcDateModified = row.utcDateModified;
    +
    +        this.becca.options[this.name] = this;
    +    }
    +
    +    beforeSaving() {
    +        super.beforeSaving();
    +
    +        this.utcDateModified = dateUtils.utcNowDateTime();
    +    }
    +
    +    getPojo() {
    +        return {
    +            name: this.name,
    +            value: this.value,
    +            isSynced: this.isSynced,
    +            utcDateModified: this.utcDateModified
    +        }
    +    }
    +}
    +
    +module.exports = BOption;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/backend_api/becca_entities_brecent_note.js.html b/docs/backend_api/becca_entities_brecent_note.js.html new file mode 100644 index 000000000..2e2f5ccb1 --- /dev/null +++ b/docs/backend_api/becca_entities_brecent_note.js.html @@ -0,0 +1,86 @@ + + + + + JSDoc: Source: becca/entities/brecent_note.js + + + + + + + + + + +
    + +

    Source: becca/entities/brecent_note.js

    + + + + + + +
    +
    +
    "use strict";
    +
    +const dateUtils = require('../../services/date_utils');
    +const AbstractBeccaEntity = require("./abstract_becca_entity");
    +
    +/**
    + * RecentNote represents recently visited note.
    + *
    + * @extends AbstractBeccaEntity
    + */
    +class BRecentNote extends AbstractBeccaEntity {
    +    static get entityName() { return "recent_notes"; }
    +    static get primaryKeyName() { return "noteId"; }
    +
    +    constructor(row) {
    +        super();
    +
    +        /** @type {string} */
    +        this.noteId = row.noteId;
    +        /** @type {string} */
    +        this.notePath = row.notePath;
    +        /** @type {string} */
    +        this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
    +    }
    +
    +    getPojo() {
    +        return {
    +            noteId: this.noteId,
    +            notePath: this.notePath,
    +            utcDateCreated: this.utcDateCreated
    +        }
    +    }
    +}
    +
    +module.exports = BRecentNote;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/backend_api/index.html b/docs/backend_api/index.html index 455fa5fdc..55d778a7e 100644 --- a/docs/backend_api/index.html +++ b/docs/backend_api/index.html @@ -56,7 +56,7 @@
    diff --git a/docs/backend_api/module-sql.html b/docs/backend_api/module-sql.html index 73bde2df5..6517bc0d4 100644 --- a/docs/backend_api/module-sql.html +++ b/docs/backend_api/module-sql.html @@ -64,6 +64,11 @@ + +
    Source:
    +
    @@ -242,6 +247,11 @@ + +
    Source:
    +
    @@ -417,6 +427,11 @@ + +
    Source:
    +
    @@ -614,6 +629,11 @@ + +
    Source:
    +
    @@ -811,6 +831,11 @@ + +
    Source:
    +
    @@ -1008,6 +1033,11 @@ + +
    Source:
    +
    @@ -1205,6 +1235,11 @@ + +
    Source:
    +
    @@ -1265,7 +1300,7 @@
    diff --git a/docs/backend_api/services_backend_script_api.js.html b/docs/backend_api/services_backend_script_api.js.html new file mode 100644 index 000000000..44f40bed6 --- /dev/null +++ b/docs/backend_api/services_backend_script_api.js.html @@ -0,0 +1,593 @@ + + + + + JSDoc: Source: services/backend_script_api.js + + + + + + + + + + +
    + +

    Source: services/backend_script_api.js

    + + + + + + +
    +
    +
    const log = require('./log');
    +const noteService = require('./notes');
    +const sql = require('./sql');
    +const utils = require('./utils');
    +const attributeService = require('./attributes');
    +const dateNoteService = require('./date_notes');
    +const treeService = require('./tree');
    +const config = require('./config');
    +const axios = require('axios');
    +const dayjs = require('dayjs');
    +const xml2js = require('xml2js');
    +const cloningService = require('./cloning');
    +const appInfo = require('./app_info');
    +const searchService = require('./search/services/search');
    +const SearchContext = require("./search/search_context");
    +const becca = require("../becca/becca");
    +const ws = require("./ws");
    +const SpacedUpdate = require("./spaced_update");
    +const specialNotesService = require("./special_notes");
    +const branchService = require("./branches");
    +const exportService = require("./export/zip");
    +
    +/**
    + * <p>This is the main backend API interface for scripts. All the properties and methods are published in the "api" object
    + * available in the JS backend notes. You can use e.g. <code>api.log(api.startNote.title);</code></p>
    + *
    + * @constructor
    + */
    +function BackendScriptApi(currentNote, apiParams) {
    +    /** @property {BNote} note where script started executing */
    +    this.startNote = apiParams.startNote;
    +    /** @property {BNote} note where script is currently executing. Don't mix this up with concept of active note */
    +    this.currentNote = currentNote;
    +    /** @property {AbstractBeccaEntity} entity whose event triggered this executions */
    +    this.originEntity = apiParams.originEntity;
    +
    +    for (const key in apiParams) {
    +        this[key] = apiParams[key];
    +    }
    +
    +    /**
    +     * @property {axios} Axios library for HTTP requests. See {@link https://axios-http.com} for documentation
    +     * @deprecated use native (browser compatible) fetch() instead
    +     */
    +    this.axios = axios;
    +    /** @property {dayjs} day.js library for date manipulation. See {@link https://day.js.org} for documentation */
    +    this.dayjs = dayjs;
    +    /** @property {axios} xml2js library for XML parsing. See {@link https://github.com/Leonidas-from-XIV/node-xml2js} for documentation */
    +    this.xml2js = xml2js;
    +
    +    /**
    +     * Instance name identifies particular Trilium instance. It can be useful for scripts
    +     * if some action needs to happen on only one specific instance.
    +     *
    +     * @returns {string|null}
    +     */
    +    this.getInstanceName = () => config.General ? config.General.instanceName : null;
    +
    +    /**
    +     * @method
    +     * @param {string} noteId
    +     * @returns {BNote|null}
    +     */
    +    this.getNote = noteId => becca.getNote(noteId);
    +
    +    /**
    +     * @method
    +     * @param {string} branchId
    +     * @returns {BBranch|null}
    +     */
    +    this.getBranch = branchId => becca.getBranch(branchId);
    +
    +    /**
    +     * @method
    +     * @param {string} attributeId
    +     * @returns {BAttribute|null}
    +     */
    +    this.getAttribute = attributeId => becca.getAttribute(attributeId);
    +
    +    /**
    +     * This is a powerful search method - you can search by attributes and their values, e.g.:
    +     * "#dateModified =* MONTH AND #log". See {@link https://github.com/zadam/trilium/wiki/Search} for full documentation for all options
    +     *
    +     * @method
    +     * @param {string} query
    +     * @param {Object} [searchParams]
    +     * @returns {BNote[]}
    +     */
    +    this.searchForNotes = (query, searchParams = {}) => {
    +        if (searchParams.includeArchivedNotes === undefined) {
    +            searchParams.includeArchivedNotes = true;
    +        }
    +
    +        if (searchParams.ignoreHoistedNote === undefined) {
    +            searchParams.ignoreHoistedNote = true;
    +        }
    +
    +        const noteIds = searchService.findResultsWithQuery(query, new SearchContext(searchParams))
    +            .map(sr => sr.noteId);
    +
    +        return becca.getNotes(noteIds);
    +    };
    +
    +    /**
    +     * This is a powerful search method - you can search by attributes and their values, e.g.:
    +     * "#dateModified =* MONTH AND #log". See {@link https://github.com/zadam/trilium/wiki/Search} for full documentation for all options
    +     *
    +     * @method
    +     * @param {string} query
    +     * @param {Object} [searchParams]
    +     * @returns {BNote|null}
    +     */
    +    this.searchForNote = (query, searchParams = {}) => {
    +        const notes = this.searchForNotes(query, searchParams);
    +
    +        return notes.length > 0 ? notes[0] : null;
    +    };
    +
    +    /**
    +     * Retrieves notes with given label name & value
    +     *
    +     * @method
    +     * @param {string} name - attribute name
    +     * @param {string} [value] - attribute value
    +     * @returns {BNote[]}
    +     */
    +    this.getNotesWithLabel = attributeService.getNotesWithLabel;
    +
    +    /**
    +     * Retrieves first note with given label name & value
    +     *
    +     * @method
    +     * @param {string} name - attribute name
    +     * @param {string} [value] - attribute value
    +     * @returns {BNote|null}
    +     */
    +    this.getNoteWithLabel = attributeService.getNoteWithLabel;
    +
    +    /**
    +     * If there's no branch between note and parent note, create one. Otherwise, do nothing.
    +     *
    +     * @method
    +     * @param {string} noteId
    +     * @param {string} parentNoteId
    +     * @param {string} prefix - if branch will be created between note and parent note, set this prefix
    +     * @returns {void}
    +     */
    +    this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent;
    +
    +    /**
    +     * If there's a branch between note and parent note, remove it. Otherwise, do nothing.
    +     *
    +     * @method
    +     * @param {string} noteId
    +     * @param {string} parentNoteId
    +     * @returns {void}
    +     */
    +    this.ensureNoteIsAbsentFromParent = cloningService.ensureNoteIsAbsentFromParent;
    +
    +    /**
    +     * Based on the value, either create or remove branch between note and parent note.
    +     *
    +     * @method
    +     * @param {boolean} present - true if we want the branch to exist, false if we want it gone
    +     * @param {string} noteId
    +     * @param {string} parentNoteId
    +     * @param {string} prefix - if branch will be created between note and parent note, set this prefix
    +     * @returns {void}
    +     */
    +    this.toggleNoteInParent = cloningService.toggleNoteInParent;
    +
    +    /**
    +     * Create text note. See also createNewNote() for more options.
    +     *
    +     * @method
    +     * @param {string} parentNoteId
    +     * @param {string} title
    +     * @param {string} content
    +     * @returns {{note: BNote, branch: BBranch}} - object having "note" and "branch" keys representing respective objects
    +     */
    +    this.createTextNote = (parentNoteId, title, content = '') => noteService.createNewNote({
    +        parentNoteId,
    +        title,
    +        content,
    +        type: 'text'
    +    });
    +
    +    /**
    +     * Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and
    +     * JSON MIME type. See also createNewNote() for more options.
    +     *
    +     * @method
    +     * @param {string} parentNoteId
    +     * @param {string} title
    +     * @param {object} content
    +     * @returns {{note: BNote, branch: BBranch}} object having "note" and "branch" keys representing respective objects
    +     */
    +    this.createDataNote = (parentNoteId, title, content = {}) => noteService.createNewNote({
    +        parentNoteId,
    +        title,
    +        content: JSON.stringify(content, null, '\t'),
    +        type: 'code',
    +        mime: 'application/json'
    +    });
    +
    +    /**
    +     * @method
    +     *
    +     * @property {object} params
    +     * @property {string} params.parentNoteId
    +     * @property {string} params.title
    +     * @property {string|buffer} params.content
    +     * @property {string} params.type - text, code, file, image, search, book, relationMap, canvas
    +     * @property {string} [params.mime] - value is derived from default mimes for type
    +     * @property {boolean} [params.isProtected=false]
    +     * @property {boolean} [params.isExpanded=false]
    +     * @property {string} [params.prefix='']
    +     * @property {int} [params.notePosition] - default is last existing notePosition in a parent + 10
    +     * @returns {{note: BNote, branch: BBranch}} object contains newly created entities note and branch
    +     */
    +    this.createNewNote = noteService.createNewNote;
    +
    +    /**
    +     * @method
    +     * @deprecated please use createTextNote() with similar API for simpler use cases or createNewNote() for more complex needs
    +     *
    +     * @param {string} parentNoteId - create new note under this parent
    +     * @param {string} title
    +     * @param {string} [content=""]
    +     * @param {object} [extraOptions={}]
    +     * @property {boolean} [extraOptions.json=false] - should the note be JSON
    +     * @property {boolean} [extraOptions.isProtected=false] - should the note be protected
    +     * @property {string} [extraOptions.type='text'] - note type
    +     * @property {string} [extraOptions.mime='text/html'] - MIME type of the note
    +     * @property {object[]} [extraOptions.attributes=[]] - attributes to be created for this note
    +     * @property {string} extraOptions.attributes.type - attribute type - label, relation etc.
    +     * @property {string} extraOptions.attributes.name - attribute name
    +     * @property {string} [extraOptions.attributes.value] - attribute value
    +     * @returns {{note: BNote, branch: BBranch}} object contains newly created entities note and branch
    +     */
    +    this.createNote = (parentNoteId, title, content = "", extraOptions= {}) => {
    +        extraOptions.parentNoteId = parentNoteId;
    +        extraOptions.title = title;
    +
    +        const parentNote = becca.getNote(parentNoteId);
    +
    +        // code note type can be inherited, otherwise text is default
    +        extraOptions.type = parentNote.type === 'code' ? 'code' : 'text';
    +        extraOptions.mime = parentNote.type === 'code' ? parentNote.mime : 'text/html';
    +
    +        if (extraOptions.json) {
    +            extraOptions.content = JSON.stringify(content || {}, null, '\t');
    +            extraOptions.type = 'code';
    +            extraOptions.mime = 'application/json';
    +        }
    +        else {
    +            extraOptions.content = content;
    +        }
    +
    +        return sql.transactional(() => {
    +            const {note, branch} = noteService.createNewNote(extraOptions);
    +
    +            for (const attr of extraOptions.attributes || []) {
    +                attributeService.createAttribute({
    +                    noteId: note.noteId,
    +                    type: attr.type,
    +                    name: attr.name,
    +                    value: attr.value,
    +                    isInheritable: !!attr.isInheritable
    +                });
    +            }
    +
    +            return {note, branch};
    +        });
    +    };
    +
    +    this.logMessages = {};
    +    this.logSpacedUpdates = {};
    +
    +    /**
    +     * Log given message to trilium logs and log pane in UI
    +     *
    +     * @method
    +     * @param message
    +     * @returns {void}
    +     */
    +    this.log = message => {
    +        log.info(message);
    +
    +        const {noteId} = this.startNote;
    +
    +        this.logMessages[noteId] = this.logMessages[noteId] || [];
    +        this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => {
    +            const messages = this.logMessages[noteId];
    +            this.logMessages[noteId] = [];
    +
    +            ws.sendMessageToAllClients({
    +                type: 'api-log-messages',
    +                noteId,
    +                messages
    +            });
    +        }, 100);
    +
    +        this.logMessages[noteId].push(message);
    +        this.logSpacedUpdates[noteId].scheduleUpdate();
    +    };
    +
    +    /**
    +     * Returns root note of the calendar.
    +     *
    +     * @method
    +     * @returns {BNote|null}
    +     */
    +    this.getRootCalendarNote = dateNoteService.getRootCalendarNote;
    +
    +    /**
    +     * Returns day note for given date. If such note doesn't exist, it is created.
    +     *
    +     * @method
    +     * @param {string} date in YYYY-MM-DD format
    +     * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar
    +     * @returns {BNote|null}
    +     */
    +    this.getDayNote = dateNoteService.getDayNote;
    +
    +    /**
    +     * Returns today's day note. If such note doesn't exist, it is created.
    +     *
    +     * @method
    +     * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar
    +     * @returns {BNote|null}
    +     */
    +    this.getTodayNote = dateNoteService.getTodayNote;
    +
    +    /**
    +     * Returns note for the first date of the week of the given date.
    +     *
    +     * @method
    +     * @param {string} date in YYYY-MM-DD format
    +     * @param {object} [options]
    +     * @param {string} [options.startOfTheWeek=monday] - either "monday" (default) or "sunday"
    +     * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar
    +     * @returns {BNote|null}
    +     */
    +    this.getWeekNote = dateNoteService.getWeekNote;
    +
    +    /**
    +     * Returns month note for given date. If such note doesn't exist, it is created.
    +     *
    +     * @method
    +     * @param {string} date in YYYY-MM format
    +     * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar
    +     * @returns {BNote|null}
    +     */
    +    this.getMonthNote = dateNoteService.getMonthNote;
    +
    +    /**
    +     * Returns year note for given year. If such note doesn't exist, it is created.
    +     *
    +     * @method
    +     * @param {string} year in YYYY format
    +     * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar
    +     * @returns {BNote|null}
    +     */
    +    this.getYearNote = dateNoteService.getYearNote;
    +
    +    /**
    +     * Sort child notes of a given note.
    +     *
    +     * @method
    +     * @param {string} parentNoteId - this note's child notes will be sorted
    +     * @param {object} [sortConfig]
    +     * @property {string} [sortConfig.sortBy=title] - 'title', 'dateCreated', 'dateModified' or a label name
    +     *                                See {@link https://github.com/zadam/trilium/wiki/Sorting} for details.
    +     * @property {boolean} [sortConfig.reverse=false]
    +     * @property {boolean} [sortConfig.foldersFirst=false]
    +     * @returns {void}
    +     */
    +    this.sortNotes = (parentNoteId, sortConfig = {}) => treeService.sortNotes(
    +        parentNoteId,
    +        sortConfig.sortBy || "title",
    +        !!sortConfig.reverse,
    +        !!sortConfig.foldersFirst
    +    );
    +
    +    /**
    +     * This method finds note by its noteId and prefix and either sets it to the given parentNoteId
    +     * or removes the branch (if parentNoteId is not given).
    +     *
    +     * This method looks similar to toggleNoteInParent() but differs because we're looking up branch by prefix.
    +     *
    +     * @method
    +     * @deprecated this method is pretty confusing and serves specialized purpose only
    +     * @param {string} noteId
    +     * @param {string} prefix
    +     * @param {string|null} parentNoteId
    +     * @returns {void}
    +     */
    +    this.setNoteToParent = treeService.setNoteToParent;
    +
    +    /**
    +     * This functions wraps code which is supposed to be running in transaction. If transaction already
    +     * exists, then we'll use that transaction.
    +     *
    +     * @method
    +     * @param {function} func
    +     * @returns {?} result of func callback
    +     */
    +    this.transactional = sql.transactional;
    +
    +    /**
    +     * Return randomly generated string of given length. This random string generation is NOT cryptographically secure.
    +     *
    +     * @method
    +     * @param {number} length of the string
    +     * @returns {string} random string
    +     */
    +    this.randomString = utils.randomString;
    +
    +    /**
    +     * @method
    +     * @param {string} string to escape
    +     * @returns {string} escaped string
    +     */
    +    this.escapeHtml = utils.escapeHtml;
    +
    +    /**
    +     * @method
    +     * @param {string} string to unescape
    +     * @returns {string} unescaped string
    +     */
    +    this.unescapeHtml = utils.unescapeHtml;
    +
    +    /**
    +     * @property {module:sql} sql
    +     */
    +    this.sql = sql;
    +
    +    /**
    +     * @method
    +     * @returns {{syncVersion, appVersion, buildRevision, dbVersion, dataDirectory, buildDate}|*} - object representing basic info about running Trilium version
    +     */
    +    this.getAppInfo = () => appInfo
    +
    +    /**
    +     * Creates a new launcher to the launchbar. If the launcher (id) already exists, it will be updated.
    +     *
    +     * @method
    +     * @param {object} opts
    +     * @property {string} opts.id - id of the launcher, only alphanumeric at least 6 characters long
    +     * @property {string} opts.type - one of
    +     *                          * "note" - activating the launcher will navigate to the target note (specified in targetNoteId param)
    +     *                          * "script" -  activating the launcher will execute the script (specified in scriptNoteId param)
    +     *                          * "customWidget" - the launcher will be rendered with a custom widget (specified in widgetNoteId param)
    +     * @property {string} opts.title
    +     * @property {boolean} [opts.isVisible=false] - if true, will be created in the "Visible launchers", otherwise in "Available launchers"
    +     * @property {string} [opts.icon] - name of the boxicon to be used (e.g. "bx-time")
    +     * @property {string} [opts.keyboardShortcut] - will activate the target note/script upon pressing, e.g. "ctrl+e"
    +     * @property {string} [opts.targetNoteId] - for type "note"
    +     * @property {string} [opts.scriptNoteId] - for type "script"
    +     * @property {string} [opts.widgetNoteId] - for type "customWidget"
    +     * @returns {{note: BNote}}
    +     */
    +    this.createOrUpdateLauncher = opts => {
    +        if (!opts.id) { throw new Error("ID is a mandatory parameter for api.createOrUpdateLauncher(opts)"); }
    +        if (!opts.id.match(/[a-z0-9]{6,1000}/i)) { throw new Error(`ID must be an alphanumeric string at least 6 characters long.`); }
    +        if (!opts.type) { throw new Error("Launcher Type is a mandatory parameter for api.createOrUpdateLauncher(opts)"); }
    +        if (!["note", "script", "customWidget"].includes(opts.type)) { throw new Error(`Given launcher type '${opts.type}'`); }
    +        if (!opts.title?.trim()) { throw new Error("Title is a mandatory parameter for api.createOrUpdateLauncher(opts)"); }
    +        if (opts.type === 'note' && !opts.targetNoteId) { throw new Error("targetNoteId is mandatory for launchers of type 'note'"); }
    +        if (opts.type === 'script' && !opts.scriptNoteId) { throw new Error("scriptNoteId is mandatory for launchers of type 'script'"); }
    +        if (opts.type === 'customWidget' && !opts.widgetNoteId) { throw new Error("widgetNoteId is mandatory for launchers of type 'customWidget'"); }
    +
    +        const parentNoteId = !!opts.isVisible ? '_lbVisibleLaunchers' : '_lbAvailableLaunchers';
    +        const noteId = 'al_' + opts.id;
    +
    +        const launcherNote =
    +            becca.getNote(opts.id) ||
    +            specialNotesService.createLauncher({
    +                noteId: noteId,
    +                parentNoteId: parentNoteId,
    +                launcherType: opts.type,
    +            }).note;
    +
    +        if (launcherNote.title !== opts.title) {
    +            launcherNote.title = opts.title;
    +            launcherNote.save();
    +        }
    +
    +        if (launcherNote.getParentBranches().length === 1) {
    +            const branch = launcherNote.getParentBranches()[0];
    +
    +            if (branch.parentNoteId !== parentNoteId) {
    +                branchService.moveBranchToNote(branch, parentNoteId);
    +            }
    +        }
    +
    +        if (opts.type === 'note') {
    +            launcherNote.setRelation('target', opts.targetNoteId);
    +        } else if (opts.type === 'script') {
    +            launcherNote.setRelation('script', opts.scriptNoteId);
    +        } else if (opts.type === 'customWidget') {
    +            launcherNote.setRelation('widget', opts.widgetNoteId);
    +        } else {
    +            throw new Error(`Unrecognized launcher type '${opts.type}'`);
    +        }
    +
    +        if (opts.keyboardShortcut) {
    +            launcherNote.setLabel('keyboardShortcut', opts.keyboardShortcut);
    +        } else {
    +            launcherNote.removeLabel('keyboardShortcut');
    +        }
    +
    +        if (opts.icon) {
    +            launcherNote.setLabel('iconClass', `bx ${opts.icon}`);
    +        } else {
    +            launcherNote.removeLabel('keyboardShortcut');
    +        }
    +
    +        return {note: launcherNote};
    +    };
    +
    +    /**
    +     * @method
    +     * @param {string} noteId
    +     * @param {string} format - either 'html' or 'markdown'
    +     * @param {string} zipFilePath
    +     * @returns {Promise<void>}
    +     */
    +    this.exportSubtreeToZipFile = async (noteId, format, zipFilePath) => await exportService.exportToZipFile(noteId, format, zipFilePath);
    +
    +    /**
    +     * This object contains "at your risk" and "no BC guarantees" objects for advanced use cases.
    +     *
    +     * @property {Becca} becca - provides access to the backend in-memory object graph, see {@link https://github.com/zadam/trilium/blob/master/src/becca/becca.js}
    +     */
    +    this.__private = {
    +        becca
    +    }
    +}
    +
    +module.exports = BackendScriptApi;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/backend_api/services_sql.js.html b/docs/backend_api/services_sql.js.html new file mode 100644 index 000000000..ec2ac56e6 --- /dev/null +++ b/docs/backend_api/services_sql.js.html @@ -0,0 +1,422 @@ + + + + + JSDoc: Source: services/sql.js + + + + + + + + + + +
    + +

    Source: services/sql.js

    + + + + + + +
    +
    +
    "use strict";
    +
    +/**
    + * @module sql
    + */
    +
    +const log = require('./log');
    +const Database = require('better-sqlite3');
    +const dataDir = require('./data_dir');
    +const cls = require('./cls');
    +const fs = require("fs-extra");
    +
    +const dbConnection = new Database(dataDir.DOCUMENT_PATH);
    +dbConnection.pragma('journal_mode = WAL');
    +
    +const LOG_ALL_QUERIES = false;
    +
    +[`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `SIGTERM`].forEach(eventType => {
    +    process.on(eventType, () => {
    +        if (dbConnection) {
    +            // closing connection is especially important to fold -wal file into the main DB file
    +            // (see https://sqlite.org/tempfiles.html for details)
    +            dbConnection.close();
    +        }
    +    });
    +});
    +
    +function insert(tableName, rec, replace = false) {
    +    const keys = Object.keys(rec);
    +    if (keys.length === 0) {
    +        log.error(`Can't insert empty object into table ${tableName}`);
    +        return;
    +    }
    +
    +    const columns = keys.join(", ");
    +    const questionMarks = keys.map(p => "?").join(", ");
    +
    +    const query = `INSERT
    +    ${replace ? "OR REPLACE" : ""} INTO
    +    ${tableName}
    +    (
    +    ${columns}
    +    )
    +    VALUES (${questionMarks})`;
    +
    +    const res = execute(query, Object.values(rec));
    +
    +    return res ? res.lastInsertRowid : null;
    +}
    +
    +function replace(tableName, rec) {
    +    return insert(tableName, rec, true);
    +}
    +
    +function upsert(tableName, primaryKey, rec) {
    +    const keys = Object.keys(rec);
    +    if (keys.length === 0) {
    +        log.error(`Can't upsert empty object into table ${tableName}`);
    +        return;
    +    }
    +
    +    const columns = keys.join(", ");
    +
    +    const questionMarks = keys.map(colName => `@${colName}`).join(", ");
    +
    +    const updateMarks = keys.map(colName => `${colName} = @${colName}`).join(", ");
    +
    +    const query = `INSERT INTO ${tableName} (${columns}) VALUES (${questionMarks}) 
    +                   ON CONFLICT (${primaryKey}) DO UPDATE SET ${updateMarks}`;
    +
    +    for (const idx in rec) {
    +        if (rec[idx] === true || rec[idx] === false) {
    +            rec[idx] = rec[idx] ? 1 : 0;
    +        }
    +    }
    +
    +    execute(query, rec);
    +}
    +
    +const statementCache = {};
    +
    +function stmt(sql) {
    +    if (!(sql in statementCache)) {
    +        statementCache[sql] = dbConnection.prepare(sql);
    +    }
    +
    +    return statementCache[sql];
    +}
    +
    +function getRow(query, params = []) {
    +    return wrap(query, s => s.get(params));
    +}
    +
    +function getRowOrNull(query, params = []) {
    +    const all = getRows(query, params);
    +
    +    return all.length > 0 ? all[0] : null;
    +}
    +
    +function getValue(query, params = []) {
    +    return wrap(query, s => s.pluck().get(params));
    +}
    +
    +// smaller values can result in better performance due to better usage of statement cache
    +const PARAM_LIMIT = 100;
    +
    +function getManyRows(query, params) {
    +    let results = [];
    +
    +    while (params.length > 0) {
    +        const curParams = params.slice(0, Math.min(params.length, PARAM_LIMIT));
    +        params = params.slice(curParams.length);
    +
    +        const curParamsObj = {};
    +
    +        let j = 1;
    +        for (const param of curParams) {
    +            curParamsObj['param' + j++] = param;
    +        }
    +
    +        let i = 1;
    +        const questionMarks = curParams.map(() => ":param" + i++).join(",");
    +        const curQuery = query.replace(/\?\?\?/g, questionMarks);
    +
    +        const statement = curParams.length === PARAM_LIMIT
    +            ? stmt(curQuery)
    +            : dbConnection.prepare(curQuery);
    +
    +        const subResults = statement.all(curParamsObj);
    +        results = results.concat(subResults);
    +    }
    +
    +    return results;
    +}
    +
    +function getRows(query, params = []) {
    +    return wrap(query, s => s.all(params));
    +}
    +
    +function getRawRows(query, params = []) {
    +    return wrap(query, s => s.raw().all(params));
    +}
    +
    +function iterateRows(query, params = []) {
    +    if (LOG_ALL_QUERIES) {
    +        console.log(query);
    +    }
    +
    +    return stmt(query).iterate(params);
    +}
    +
    +function getMap(query, params = []) {
    +    const map = {};
    +    const results = getRawRows(query, params);
    +
    +    for (const row of results) {
    +        map[row[0]] = row[1];
    +    }
    +
    +    return map;
    +}
    +
    +function getColumn(query, params = []) {
    +    return wrap(query, s => s.pluck().all(params));
    +}
    +
    +function execute(query, params = []) {
    +    return wrap(query, s => s.run(params));
    +}
    +
    +function executeMany(query, params) {
    +    if (LOG_ALL_QUERIES) {
    +        console.log(query);
    +    }
    +
    +    while (params.length > 0) {
    +        const curParams = params.slice(0, Math.min(params.length, PARAM_LIMIT));
    +        params = params.slice(curParams.length);
    +
    +        const curParamsObj = {};
    +
    +        let j = 1;
    +        for (const param of curParams) {
    +            curParamsObj['param' + j++] = param;
    +        }
    +
    +        let i = 1;
    +        const questionMarks = curParams.map(() => ":param" + i++).join(",");
    +        const curQuery = query.replace(/\?\?\?/g, questionMarks);
    +
    +        dbConnection.prepare(curQuery).run(curParamsObj);
    +    }
    +}
    +
    +function executeScript(query) {
    +    if (LOG_ALL_QUERIES) {
    +        console.log(query);
    +    }
    +
    +    return dbConnection.exec(query);
    +}
    +
    +function wrap(query, func) {
    +    const startTimestamp = Date.now();
    +    let result;
    +
    +    if (LOG_ALL_QUERIES) {
    +        console.log(query);
    +    }
    +
    +    try {
    +        result = func(stmt(query));
    +    }
    +    catch (e) {
    +        if (e.message.includes("The database connection is not open")) {
    +            // this often happens on killing the app which puts these alerts in front of user
    +            // in these cases error should be simply ignored.
    +            console.log(e.message);
    +
    +            return null
    +        }
    +
    +        throw e;
    +    }
    +
    +    const milliseconds = Date.now() - startTimestamp;
    +
    +    if (milliseconds >= 20) {
    +        if (query.includes("WITH RECURSIVE")) {
    +            log.info(`Slow recursive query took ${milliseconds}ms.`);
    +        }
    +        else {
    +            log.info(`Slow query took ${milliseconds}ms: ${query.trim().replace(/\s+/g, " ")}`);
    +        }
    +    }
    +
    +    return result;
    +}
    +
    +function transactional(func) {
    +    try {
    +        const ret = dbConnection.transaction(func).deferred();
    +
    +        if (!dbConnection.inTransaction) { // i.e. transaction was really committed (and not just savepoint released)
    +            require('./ws').sendTransactionEntityChangesToAllClients();
    +        }
    +
    +        return ret;
    +    }
    +    catch (e) {
    +        const entityChangeIds = cls.getAndClearEntityChangeIds();
    +
    +        if (entityChangeIds.length > 0) {
    +            log.info("Transaction rollback dirtied the becca, forcing reload.");
    +
    +            require('../becca/becca_loader').load();
    +        }
    +
    +        // the maxEntityChangeId has been incremented during failed transaction, need to recalculate
    +        require('./entity_changes').recalculateMaxEntityChangeId();
    +
    +        throw e;
    +    }
    +}
    +
    +function fillParamList(paramIds, truncate = true) {
    +    if (paramIds.length === 0) {
    +        return;
    +    }
    +
    +    if (truncate) {
    +        execute("DELETE FROM param_list");
    +    }
    +
    +    paramIds = Array.from(new Set(paramIds));
    +
    +    if (paramIds.length > 30000) {
    +        fillParamList(paramIds.slice(30000), false);
    +
    +        paramIds = paramIds.slice(0, 30000);
    +    }
    +
    +    // doing it manually to avoid this showing up on the sloq query list
    +    const s = stmt(`INSERT INTO param_list VALUES ${paramIds.map(paramId => `(?)`).join(',')}`, paramIds);
    +
    +    s.run(paramIds);
    +}
    +
    +async function copyDatabase(targetFilePath) {
    +    try {
    +        fs.unlinkSync(targetFilePath);
    +    } catch (e) {
    +    } // unlink throws exception if the file did not exist
    +
    +    await dbConnection.backup(targetFilePath);
    +}
    +
    +module.exports = {
    +    dbConnection,
    +    insert,
    +    replace,
    +
    +    /**
    +     * Get single value from the given query - first column from first returned row.
    +     *
    +     * @method
    +     * @param {string} query - SQL query with ? used as parameter placeholder
    +     * @param {object[]} [params] - array of params if needed
    +     * @returns [object] - single value
    +     */
    +    getValue,
    +
    +    /**
    +     * Get first returned row.
    +     *
    +     * @method
    +     * @param {string} query - SQL query with ? used as parameter placeholder
    +     * @param {object[]} [params] - array of params if needed
    +     * @returns {object} - map of column name to column value
    +     */
    +    getRow,
    +    getRowOrNull,
    +
    +    /**
    +     * Get all returned rows.
    +     *
    +     * @method
    +     * @param {string} query - SQL query with ? used as parameter placeholder
    +     * @param {object[]} [params] - array of params if needed
    +     * @returns {object[]} - array of all rows, each row is a map of column name to column value
    +     */
    +    getRows,
    +    getRawRows,
    +    iterateRows,
    +    getManyRows,
    +
    +    /**
    +     * Get a map of first column mapping to second column.
    +     *
    +     * @method
    +     * @param {string} query - SQL query with ? used as parameter placeholder
    +     * @param {object[]} [params] - array of params if needed
    +     * @returns {object} - map of first column to second column
    +     */
    +    getMap,
    +
    +    /**
    +     * Get a first column in an array.
    +     *
    +     * @method
    +     * @param {string} query - SQL query with ? used as parameter placeholder
    +     * @param {object[]} [params] - array of params if needed
    +     * @returns {object[]} - array of first column of all returned rows
    +     */
    +    getColumn,
    +
    +    /**
    +     * Execute SQL
    +     *
    +     * @method
    +     * @param {string} query - SQL query with ? used as parameter placeholder
    +     * @param {object[]} [params] - array of params if needed
    +     */
    +    execute,
    +    executeMany,
    +    executeScript,
    +    transactional,
    +    upsert,
    +    fillParamList,
    +    copyDatabase
    +};
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/frontend_api/FAttribute.html b/docs/frontend_api/FAttribute.html index 19721bc55..641c3a737 100644 --- a/docs/frontend_api/FAttribute.html +++ b/docs/frontend_api/FAttribute.html @@ -91,6 +91,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -186,6 +191,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -249,6 +259,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -312,6 +327,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -375,6 +395,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -438,6 +463,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -501,6 +531,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -564,6 +599,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -635,6 +675,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -732,6 +777,11 @@ and relation (representing named relationship between source and target note)Source: +
    @@ -800,7 +850,7 @@ and relation (representing named relationship between source and target note) diff --git a/docs/frontend_api/FBranch.html b/docs/frontend_api/FBranch.html index 07792adcb..284bc72d5 100644 --- a/docs/frontend_api/FBranch.html +++ b/docs/frontend_api/FBranch.html @@ -91,6 +91,11 @@ parents. + +
    Source:
    +
    @@ -190,6 +195,11 @@ parents. + +
    Source:
    +
    @@ -253,6 +263,11 @@ parents. + +
    Source:
    +
    @@ -316,6 +331,11 @@ parents. + +
    Source:
    +
    @@ -379,6 +399,11 @@ parents. + +
    Source:
    +
    @@ -442,6 +467,11 @@ parents. + +
    Source:
    +
    @@ -505,6 +535,11 @@ parents. + +
    Source:
    +
    @@ -568,6 +603,11 @@ parents. + +
    Source:
    +
    @@ -639,6 +679,11 @@ parents. + +
    Source:
    +
    @@ -736,6 +781,11 @@ parents. + +
    Source:
    +
    @@ -833,6 +883,11 @@ parents. + +
    Source:
    +
    @@ -930,6 +985,11 @@ parents. + +
    Source:
    +
    @@ -1002,7 +1062,7 @@ parents.
    diff --git a/docs/frontend_api/FNote.html b/docs/frontend_api/FNote.html index a3afeef6e..cff8d1e8f 100644 --- a/docs/frontend_api/FNote.html +++ b/docs/frontend_api/FNote.html @@ -158,6 +158,11 @@ + +
    Source:
    +
    @@ -253,6 +258,11 @@ + +
    Source:
    +
    @@ -316,6 +326,11 @@ + +
    Source:
    +
    @@ -379,6 +394,11 @@ + +
    Source:
    +
    @@ -442,6 +462,11 @@ + +
    Source:
    +
    @@ -509,6 +534,11 @@ + +
    Source:
    +
    @@ -572,6 +602,11 @@ + +
    Source:
    +
    @@ -635,6 +670,11 @@ + +
    Source:
    +
    @@ -698,6 +738,11 @@ + +
    Source:
    +
    @@ -761,6 +806,11 @@ + +
    Source:
    +
    @@ -824,6 +874,11 @@ + +
    Source:
    +
    @@ -891,6 +946,11 @@ + +
    Source:
    +
    @@ -1034,6 +1094,11 @@ + +
    Source:
    +
    @@ -1207,6 +1272,11 @@ + +
    Source:
    +
    @@ -1402,6 +1472,11 @@ + +
    Source:
    +
    @@ -1506,6 +1581,11 @@ +
    Source:
    +
    + @@ -1605,6 +1685,11 @@ +
    Source:
    +
    + @@ -1701,6 +1786,11 @@ + +
    Source:
    +
    @@ -1798,6 +1888,11 @@ + +
    Source:
    +
    @@ -1895,6 +1990,11 @@ + +
    Source:
    +
    @@ -2041,6 +2141,11 @@ + +
    Source:
    +
    @@ -2191,6 +2296,11 @@ + +
    Source:
    +
    @@ -2353,6 +2463,11 @@ + +
    Source:
    +
    @@ -2458,6 +2573,11 @@ + +
    Source:
    +
    @@ -2510,6 +2630,108 @@ +

    getNotesToInheritAttributesFrom() → {Array.<FNote>}

    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Array.<FNote> + + +
    +
    + + + + + + + + + + + + +

    getOwnedAttribute(type, name) → {FAttribute}

    @@ -2627,6 +2849,11 @@ + +
    Source:
    +
    @@ -2800,6 +3027,11 @@ + +
    Source:
    +
    @@ -2995,6 +3227,11 @@ + +
    Source:
    +
    @@ -3145,6 +3382,11 @@ + +
    Source:
    +
    @@ -3295,6 +3537,11 @@ + +
    Source:
    +
    @@ -3457,6 +3704,11 @@ + +
    Source:
    +
    @@ -3607,6 +3859,11 @@ + +
    Source:
    +
    @@ -3757,6 +4014,11 @@ + +
    Source:
    +
    @@ -3919,6 +4181,11 @@ + +
    Source:
    +
    @@ -4020,6 +4287,11 @@ + +
    Source:
    +
    @@ -4117,6 +4389,11 @@ + +
    Source:
    +
    @@ -4214,6 +4491,11 @@ + +
    Source:
    +
    @@ -4311,6 +4593,11 @@ + +
    Source:
    +
    @@ -4457,6 +4744,11 @@ + +
    Source:
    +
    @@ -4607,6 +4899,11 @@ + +
    Source:
    +
    @@ -4772,6 +5069,11 @@ + +
    Source:
    +
    @@ -4918,6 +5220,11 @@ + +
    Source:
    +
    @@ -5080,6 +5387,11 @@ + +
    Source:
    +
    @@ -5181,6 +5493,11 @@ + +
    Source:
    +
    @@ -5289,6 +5606,11 @@ + +
    Source:
    +
    @@ -5390,6 +5712,11 @@ + +
    Source:
    +
    @@ -5442,103 +5769,6 @@ -

    getTemplateNotes() → {Array.<FNote>}

    - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - - - -
    -
    - Type -
    -
    - -Array.<FNote> - - -
    -
    - - - - - - - - - - - - -

    hasAttribute(type, name) → {boolean}

    @@ -5656,6 +5886,11 @@ + +
    Source:
    +
    @@ -5757,6 +5992,11 @@ + +
    Source:
    +
    @@ -5903,6 +6143,11 @@ + +
    Source:
    +
    @@ -6076,6 +6321,11 @@ + +
    Source:
    +
    @@ -6226,6 +6476,11 @@ + +
    Source:
    +
    @@ -6376,6 +6631,11 @@ + +
    Source:
    +
    @@ -6526,6 +6786,11 @@ + +
    Source:
    +
    @@ -6630,6 +6895,11 @@ +
    Source:
    +
    + @@ -6657,6 +6927,100 @@ + + + + + + +

    isHiddenCompletely()

    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    + boolean - true if there's no non-hidden path, note is not cloned to the visible tree +
    + + + + + + + + + @@ -6708,6 +7072,11 @@ + +
    Source:
    +
    @@ -6809,6 +7178,11 @@ + +
    Source:
    +
    @@ -6836,7 +7210,7 @@
    - true if this note is JavaScript (code or attachment) + true if this note is JavaScript (code or file)
    @@ -6910,6 +7284,11 @@ + +
    Source:
    +
    @@ -6978,7 +7357,7 @@
    diff --git a/docs/frontend_api/FNoteComplement.html b/docs/frontend_api/FNoteComplement.html index 121ffd256..8c1bb3d94 100644 --- a/docs/frontend_api/FNoteComplement.html +++ b/docs/frontend_api/FNoteComplement.html @@ -90,6 +90,11 @@ + +
    Source:
    +
    @@ -185,6 +190,11 @@ + +
    Source:
    +
    @@ -248,6 +258,11 @@ + +
    Source:
    +
    @@ -315,6 +330,11 @@ + +
    Source:
    +
    @@ -378,6 +398,11 @@ + +
    Source:
    +
    @@ -441,6 +466,11 @@ + +
    Source:
    +
    @@ -504,6 +534,11 @@ + +
    Source:
    +
    @@ -567,6 +602,11 @@ + +
    Source:
    +
    @@ -630,6 +670,11 @@ + +
    Source:
    +
    @@ -693,6 +738,11 @@ + +
    Source:
    +
    @@ -731,7 +781,7 @@
    diff --git a/docs/frontend_api/FrontendScriptApi.html b/docs/frontend_api/FrontendScriptApi.html index 2e5667bca..e866a8473 100644 --- a/docs/frontend_api/FrontendScriptApi.html +++ b/docs/frontend_api/FrontendScriptApi.html @@ -28,7 +28,7 @@
    -

    FrontendScriptApi

    +

    FrontendScriptApi()

    @@ -38,7 +38,20 @@ + + +

    new FrontendScriptApi()

    + + + + + + +
    +

    This is the main frontend API interface for scripts. All the properties and methods are published in the "api" object +available in the JS frontend notes. You can use e.g. api.showMessage(api.startNote.title);

    +
    @@ -78,6 +91,11 @@ + +
    Source:
    +
    @@ -215,6 +233,11 @@ + +
    Source:
    +
    @@ -316,6 +339,11 @@ + +
    Source:
    +
    @@ -420,6 +448,11 @@ +
    Source:
    +
    + @@ -520,6 +553,11 @@ + +
    Source:
    +
    @@ -624,6 +662,11 @@ +
    Source:
    +
    + @@ -724,6 +767,11 @@ + +
    Source:
    +
    @@ -828,6 +876,11 @@ +
    Source:
    +
    + @@ -931,6 +984,11 @@ +
    Source:
    +
    + @@ -1035,6 +1093,121 @@ + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    dayjs

    + + + + + + + + + + +
    Properties:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    day.js + + +dayjs + + + + library for date manipulation. See https://day.js.org for documentation
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    @@ -1143,6 +1316,11 @@ + +
    Source:
    +
    @@ -1248,6 +1426,11 @@ + +
    Source:
    +
    @@ -1372,6 +1555,11 @@ + +
    Source:
    +
    @@ -1522,6 +1710,11 @@ + +
    Source:
    +
    @@ -1859,6 +2052,11 @@ +
    Source:
    +
    + @@ -1991,139 +2189,10 @@ - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    addTextToActiveTabEditor(text)

    - - - - - - -
    - Adds given text to the editor cursor -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    text - - -string - - - - this must be clear text, HTML is not supported.
    - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • use addTextToActiveContextEditor() instead
    - - - - - - - - - - +
    Source:
    +
    @@ -2158,7 +2227,7 @@ -

    bindGlobalShortcut(keyboardShortcut, handler, namespaceopt)

    +

    bindGlobalShortcut(keyboardShortcut, handler, namespaceopt) → {Promise.<void>}

    @@ -2327,6 +2396,11 @@ + +
    Source:
    +
    @@ -2350,6 +2424,24 @@ +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<void> + + +
    +
    + + @@ -2686,6 +2778,11 @@ + +
    Source:
    +
    @@ -2814,6 +2911,11 @@ + +
    Source:
    +
    @@ -2919,6 +3021,11 @@ + +
    Source:
    +
    @@ -3020,6 +3127,11 @@ + +
    Source:
    +
    @@ -3121,6 +3233,11 @@ + +
    Source:
    +
    @@ -3226,6 +3343,11 @@ + +
    Source:
    +
    @@ -3332,6 +3454,11 @@ implementation of actual widget type. + +
    Source:
    +
    @@ -3378,353 +3505,6 @@ implementation of actual widget type. - - - - - - -

    getActiveTabNote() → {FNote}

    - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • use getActiveContextNote() instead
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - active note (loaded into right pane) -
    - - - -
    -
    - Type -
    -
    - -FNote - - -
    -
    - - - - - - - - - - - - - -

    getActiveTabNotePath() → {Promise.<(string|null)>}

    - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • use getActiveContextNotePath() instead
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - returns note path of active note or null if there isn't active note -
    - - - -
    -
    - Type -
    -
    - -Promise.<(string|null)> - - -
    -
    - - - - - - - - - - - - - -

    getActiveTabTextEditor(callbackopt)

    - - - - - - -
    - See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDescription
    callback - - - - <optional>
    - - - - - -
    callback receiving "textEditor" instance
    - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • use getActiveContextTextEditor()
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - @@ -3829,6 +3609,11 @@ implementation of actual widget type. + +
    Source:
    +
    @@ -3881,158 +3666,6 @@ implementation of actual widget type. -

    getDateNote(date) → {Promise.<FNote>}

    - - - - - - -
    - Returns day note for a given date. If it doesn't exist, it is automatically created. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    date - - -string - - - - e.g. "2019-04-29"
    - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • use getDayNote instead
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - - - -
    -
    - Type -
    -
    - -Promise.<FNote> - - -
    -
    - - - - - - - - - - - - -

    getDayNote(date) → {Promise.<FNote>}

    @@ -4131,6 +3764,11 @@ implementation of actual widget type. + +
    Source:
    +
    @@ -4233,6 +3871,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -4383,6 +4026,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -4534,6 +4182,11 @@ if some action needs to happen on only one specific instance. + +
    Source:
    +
    @@ -4730,6 +4383,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -4831,6 +4489,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -4981,6 +4644,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -5131,6 +4799,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -5183,7 +4856,7 @@ otherwise (by e.g. createNoteLink()) -

    log(message)

    +

    log(message) → {void}

    @@ -5276,6 +4949,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -5299,6 +4977,24 @@ otherwise (by e.g. createNoteLink()) +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +void + + +
    +
    + + @@ -5431,6 +5127,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -5604,6 +5305,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -5750,6 +5456,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -5806,88 +5517,7 @@ otherwise (by e.g. createNoteLink()) -

    protectActiveNote()

    - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • use protectNote and protectSubtree instead
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    protectNote(noteId, protect)

    +

    protectNote(noteId, protect) → {Promise.<void>}

    @@ -6004,6 +5634,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -6027,6 +5662,24 @@ otherwise (by e.g. createNoteLink()) +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<void> + + +
    +
    + + @@ -6038,7 +5691,7 @@ otherwise (by e.g. createNoteLink()) -

    protectSubTree(noteId, protect)

    +

    protectSubTree(noteId, protect) → {Promise.<void>}

    @@ -6155,6 +5808,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -6178,6 +5836,24 @@ otherwise (by e.g. createNoteLink()) +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<void> + + +
    +
    + + @@ -6287,6 +5963,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -6343,7 +6024,7 @@ otherwise (by e.g. createNoteLink()) -

    refreshIncludedNote(includedNoteId)

    +

    refreshIncludedNote(includedNoteId) → {Promise.<void>}

    @@ -6436,6 +6117,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -6459,87 +6145,24 @@ otherwise (by e.g. createNoteLink()) - - - - +
    Returns:
    - - - - - -

    refreshTree()

    - - - +
    +
    + Type +
    +
    + +Promise.<void> - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • - this is now no-op since all the changes should be gracefully handled per widget
    - - - - - - - - - - - - - - - - - +
    - - - - - - - - - - - - - - + @@ -6649,6 +6272,11 @@ otherwise (by e.g. createNoteLink()) + +
    Source:
    +
    @@ -6767,7 +6395,7 @@ Internally this serializes the anonymous function into string and sends it to ba - list of parameters to the anonymous function to be send to backend + list of parameters to the anonymous function to be sent to backend @@ -6805,6 +6433,11 @@ Internally this serializes the anonymous function into string and sends it to ba + +
    Source:
    +
    @@ -6855,87 +6488,6 @@ Internally this serializes the anonymous function into string and sends it to ba - - - - - - -

    runOnServer()

    - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    Deprecated:
    • new name of this API call is runOnBackend so use that
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - @@ -7041,6 +6593,11 @@ Internally this serializes the anonymous function into string and sends it to ba + +
    Source:
    +
    @@ -7192,6 +6749,11 @@ Internally this serializes the anonymous function into string and sends it to ba + +
    Source:
    +
    @@ -7244,7 +6806,7 @@ Internally this serializes the anonymous function into string and sends it to ba -

    setHoistedNoteId(noteId) → {Promise}

    +

    setHoistedNoteId(noteId) → {Promise.<void>}

    @@ -7342,6 +6904,11 @@ Internally this serializes the anonymous function into string and sends it to ba + +
    Source:
    +
    @@ -7376,7 +6943,7 @@ Internally this serializes the anonymous function into string and sends it to ba
    -Promise +Promise.<void>
    @@ -7394,7 +6961,7 @@ Internally this serializes the anonymous function into string and sends it to ba -

    setupElementTooltip($el)

    +

    setupElementTooltip($el) → {Promise.<void>}

    @@ -7450,7 +7017,7 @@ Internally this serializes the anonymous function into string and sends it to ba - jquery object on which to setup the tooltip + jquery object on which to set up the tooltip @@ -7488,6 +7055,11 @@ Internally this serializes the anonymous function into string and sends it to ba + +
    Source:
    +
    @@ -7511,6 +7083,24 @@ Internally this serializes the anonymous function into string and sends it to ba +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<void> + + +
    +
    + + @@ -7620,6 +7210,11 @@ Internally this serializes the anonymous function into string and sends it to ba + +
    Source:
    +
    @@ -7752,6 +7347,11 @@ Internally this serializes the anonymous function into string and sends it to ba + +
    Source:
    +
    @@ -7907,6 +7507,11 @@ Internally this serializes the anonymous function into string and sends it to ba + +
    Source:
    +
    @@ -8062,6 +7667,11 @@ Internally this serializes the anonymous function into string and sends it to ba + +
    Source:
    +
    @@ -8096,7 +7706,7 @@ Internally this serializes the anonymous function into string and sends it to ba -

    waitUntilSynced()

    +

    waitUntilSynced() → {Promise.<void>}

    @@ -8149,6 +7759,11 @@ Typical use case is when new note has been created, we should wait until it is s + +
    Source:
    +
    @@ -8172,6 +7787,24 @@ Typical use case is when new note has been created, we should wait until it is s +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<void> + + +
    +
    + + @@ -8199,7 +7832,7 @@ Typical use case is when new note has been created, we should wait until it is s
    diff --git a/docs/frontend_api/entities_fattribute.js.html b/docs/frontend_api/entities_fattribute.js.html new file mode 100644 index 000000000..dffe972bc --- /dev/null +++ b/docs/frontend_api/entities_fattribute.js.html @@ -0,0 +1,130 @@ + + + + + JSDoc: Source: entities/fattribute.js + + + + + + + + + + +
    + +

    Source: entities/fattribute.js

    + + + + + + +
    +
    +
    import promotedAttributeDefinitionParser from '../services/promoted_attribute_definition_parser.js';
    +
    +/**
    + * Attribute is an abstract concept which has two real uses - label (key - value pair)
    + * and relation (representing named relationship between source and target note)
    + */
    +class FAttribute {
    +    constructor(froca, row) {
    +        this.froca = froca;
    +
    +        this.update(row);
    +    }
    +
    +    update(row) {
    +        /** @type {string} */
    +        this.attributeId = row.attributeId;
    +        /** @type {string} */
    +        this.noteId = row.noteId;
    +        /** @type {string} */
    +        this.type = row.type;
    +        /** @type {string} */
    +        this.name = row.name;
    +        /** @type {string} */
    +        this.value = row.value;
    +        /** @type {int} */
    +        this.position = row.position;
    +        /** @type {boolean} */
    +        this.isInheritable = !!row.isInheritable;
    +    }
    +
    +    /** @returns {FNote} */
    +    getNote() {
    +        return this.froca.notes[this.noteId];
    +    }
    +
    +    /** @returns {Promise<FNote>} */
    +    async getTargetNote() {
    +        const targetNoteId = this.targetNoteId;
    +
    +        return await this.froca.getNote(targetNoteId, true);
    +    }
    +
    +    get targetNoteId() { // alias
    +        if (this.type !== 'relation') {
    +            throw new Error(`Attribute ${this.attributeId} is not a relation`);
    +        }
    +
    +        return this.value;
    +    }
    +
    +    get isAutoLink() {
    +        return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name);
    +    }
    +
    +    get toString() {
    +        return `FAttribute(attributeId=${this.attributeId}, type=${this.type}, name=${this.name}, value=${this.value})`;
    +    }
    +
    +    isDefinition() {
    +        return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:'));
    +    }
    +
    +    getDefinition() {
    +        return promotedAttributeDefinitionParser.parse(this.value);
    +    }
    +
    +    isDefinitionFor(attr) {
    +        return this.type === 'label' && this.name === `${attr.type}:${attr.name}`;
    +    }
    +
    +    get dto() {
    +        const dto = Object.assign({}, this);
    +        delete dto.froca;
    +
    +        return dto;
    +    }
    +}
    +
    +export default FAttribute;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/frontend_api/entities_fbranch.js.html b/docs/frontend_api/entities_fbranch.js.html new file mode 100644 index 000000000..2b7ced98d --- /dev/null +++ b/docs/frontend_api/entities_fbranch.js.html @@ -0,0 +1,114 @@ + + + + + JSDoc: Source: entities/fbranch.js + + + + + + + + + + +
    + +

    Source: entities/fbranch.js

    + + + + + + +
    +
    +
    /**
    + * Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple
    + * parents.
    + */
    +class FBranch {
    +    constructor(froca, row) {
    +        this.froca = froca;
    +
    +        this.update(row);
    +    }
    +
    +    update(row) {
    +        /**
    +         * primary key
    +         * @type {string}
    +         */
    +        this.branchId = row.branchId;
    +        /** @type {string} */
    +        this.noteId = row.noteId;
    +        /** @type {string} */
    +        this.parentNoteId = row.parentNoteId;
    +        /** @type {int} */
    +        this.notePosition = row.notePosition;
    +        /** @type {string} */
    +        this.prefix = row.prefix;
    +        /** @type {boolean} */
    +        this.isExpanded = !!row.isExpanded;
    +        /** @type {boolean} */
    +        this.fromSearchNote = !!row.fromSearchNote;
    +    }
    +
    +    /** @returns {FNote} */
    +    async getNote() {
    +        return this.froca.getNote(this.noteId);
    +    }
    +
    +    /** @returns {FNote} */
    +    getNoteFromCache() {
    +        return this.froca.getNoteFromCache(this.noteId);
    +    }
    +
    +    /** @returns {FNote} */
    +    async getParentNote() {
    +        return this.froca.getNote(this.parentNoteId);
    +    }
    +
    +    /** @returns {boolean} true if it's top level, meaning its parent is root note */
    +    isTopLevel() {
    +        return this.parentNoteId === 'root';
    +    }
    +
    +    get toString() {
    +        return `FBranch(branchId=${this.branchId})`;
    +    }
    +
    +    get pojo() {
    +        const pojo = {...this};
    +        delete pojo.froca;
    +        return pojo;
    +    }
    +}
    +
    +export default FBranch;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/frontend_api/entities_fnote.js.html b/docs/frontend_api/entities_fnote.js.html new file mode 100644 index 000000000..a31f615a2 --- /dev/null +++ b/docs/frontend_api/entities_fnote.js.html @@ -0,0 +1,939 @@ + + + + + JSDoc: Source: entities/fnote.js + + + + + + + + + + +
    + +

    Source: entities/fnote.js

    + + + + + + +
    +
    +
    import server from '../services/server.js';
    +import noteAttributeCache from "../services/note_attribute_cache.js";
    +import ws from "../services/ws.js";
    +import options from "../services/options.js";
    +import froca from "../services/froca.js";
    +import protectedSessionHolder from "../services/protected_session_holder.js";
    +import cssClassManager from "../services/css_class_manager.js";
    +
    +const LABEL = 'label';
    +const RELATION = 'relation';
    +
    +const NOTE_TYPE_ICONS = {
    +    "file": "bx bx-file",
    +    "image": "bx bx-image",
    +    "code": "bx bx-code",
    +    "render": "bx bx-extension",
    +    "search": "bx bx-file-find",
    +    "relationMap": "bx bx-map-alt",
    +    "book": "bx bx-book",
    +    "noteMap": "bx bx-map-alt",
    +    "mermaid": "bx bx-selection",
    +    "canvas": "bx bx-pen",
    +    "webView": "bx bx-globe-alt",
    +    "launcher": "bx bx-link",
    +    "doc": "bx bxs-file-doc",
    +    "contentWidget": "bx bxs-widget"
    +};
    +
    +class FNote {
    +    /**
    +     * @param {Froca} froca
    +     * @param {Object.<string, Object>} row
    +     */
    +    constructor(froca, row) {
    +        this.froca = froca;
    +
    +        /** @type {string[]} */
    +        this.attributes = [];
    +
    +        /** @type {string[]} */
    +        this.targetRelations = [];
    +
    +        /** @type {string[]} */
    +        this.parents = [];
    +        /** @type {string[]} */
    +        this.children = [];
    +
    +        /** @type {Object.<string, string>} */
    +        this.parentToBranch = {};
    +
    +        /** @type {Object.<string, string>} */
    +        this.childToBranch = {};
    +
    +        this.update(row);
    +    }
    +
    +    update(row) {
    +        /** @type {string} */
    +        this.noteId = row.noteId;
    +        /** @type {string} */
    +        this.title = row.title;
    +        /** @type {boolean} */
    +        this.isProtected = !!row.isProtected;
    +        /**
    +         * one of 'text', 'code', 'file' or 'render'
    +         * @type {string}
    +         */
    +        this.type = row.type;
    +        /**
    +         * content-type, e.g. "application/json"
    +         * @type {string}
    +         */
    +        this.mime = row.mime;
    +    }
    +
    +    addParent(parentNoteId, branchId) {
    +        if (parentNoteId === 'none') {
    +            return;
    +        }
    +
    +        if (!this.parents.includes(parentNoteId)) {
    +            this.parents.push(parentNoteId);
    +        }
    +
    +        this.parentToBranch[parentNoteId] = branchId;
    +    }
    +
    +    addChild(childNoteId, branchId, sort = true) {
    +        if (!(childNoteId in this.childToBranch)) {
    +            this.children.push(childNoteId);
    +        }
    +
    +        this.childToBranch[childNoteId] = branchId;
    +
    +        if (sort) {
    +            this.sortChildren();
    +        }
    +    }
    +
    +    sortChildren() {
    +        const branchIdPos = {};
    +
    +        for (const branchId of Object.values(this.childToBranch)) {
    +            branchIdPos[branchId] = this.froca.getBranch(branchId).notePosition;
    +        }
    +
    +        this.children.sort((a, b) => branchIdPos[this.childToBranch[a]] < branchIdPos[this.childToBranch[b]] ? -1 : 1);
    +    }
    +
    +    /** @returns {boolean} */
    +    isJson() {
    +        return this.mime === "application/json";
    +    }
    +
    +    async getContent() {
    +        // we're not caching content since these objects are in froca and as such pretty long-lived
    +        const note = await server.get(`notes/${this.noteId}`);
    +
    +        return note.content;
    +    }
    +
    +    async getJsonContent() {
    +        const content = await this.getContent();
    +
    +        try {
    +            return JSON.parse(content);
    +        }
    +        catch (e) {
    +            console.log(`Cannot parse content of note '${this.noteId}': `, e.message);
    +
    +            return null;
    +        }
    +    }
    +
    +    /**
    +     * @returns {string[]}
    +     */
    +    getParentBranchIds() {
    +        return Object.values(this.parentToBranch);
    +    }
    +
    +    /**
    +     * @returns {string[]}
    +     * @deprecated use getParentBranchIds() instead
    +     */
    +    getBranchIds() {
    +        return this.getParentBranchIds();
    +    }
    +
    +    /**
    +     * @returns {FBranch[]}
    +     */
    +    getParentBranches() {
    +        const branchIds = Object.values(this.parentToBranch);
    +
    +        return this.froca.getBranches(branchIds);
    +    }
    +
    +    /**
    +     * @returns {FBranch[]}
    +     * @deprecated use getParentBranches() instead
    +     */
    +    getBranches() {
    +        return this.getParentBranches();
    +    }
    +
    +    /** @returns {boolean} */
    +    hasChildren() {
    +        return this.children.length > 0;
    +    }
    +
    +    /** @returns {FBranch[]} */
    +    getChildBranches() {
    +        // don't use Object.values() to guarantee order
    +        const branchIds = this.children.map(childNoteId => this.childToBranch[childNoteId]);
    +
    +        return this.froca.getBranches(branchIds);
    +    }
    +
    +    /** @returns {string[]} */
    +    getParentNoteIds() {
    +        return this.parents;
    +    }
    +
    +    /** @returns {FNote[]} */
    +    getParentNotes() {
    +        return this.froca.getNotesFromCache(this.parents);
    +    }
    +
    +    // will sort the parents so that non-search & non-archived are first and archived at the end
    +    // this is done so that non-search & non-archived paths are always explored as first when looking for note path
    +    resortParents() {
    +        this.parents.sort((aNoteId, bNoteId) => {
    +            const aBranchId = this.parentToBranch[aNoteId];
    +
    +            if (aBranchId && aBranchId.startsWith('virt-')) {
    +                return 1;
    +            }
    +
    +            const aNote = this.froca.getNoteFromCache([aNoteId]);
    +
    +            if (aNote.isArchived || aNote.isHiddenCompletely()) {
    +                return 1;
    +            }
    +
    +            return -1;
    +        });
    +    }
    +
    +    get isArchived() {
    +        return this.hasAttribute('label', 'archived');
    +    }
    +
    +    /** @returns {string[]} */
    +    getChildNoteIds() {
    +        return this.children;
    +    }
    +
    +    /** @returns {Promise<FNote[]>} */
    +    async getChildNotes() {
    +        return await this.froca.getNotes(this.children);
    +    }
    +
    +    /**
    +     * @param {string} [type] - (optional) attribute type to filter
    +     * @param {string} [name] - (optional) attribute name to filter
    +     * @returns {FAttribute[]} all note's attributes, including inherited ones
    +     */
    +    getOwnedAttributes(type, name) {
    +        const attrs = this.attributes
    +            .map(attributeId => this.froca.attributes[attributeId])
    +            .filter(Boolean); // filter out nulls;
    +
    +        return this.__filterAttrs(attrs, type, name);
    +    }
    +
    +    /**
    +     * @param {string} [type] - (optional) attribute type to filter
    +     * @param {string} [name] - (optional) attribute name to filter
    +     * @returns {FAttribute[]} all note's attributes, including inherited ones
    +     */
    +    getAttributes(type, name) {
    +        return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
    +    }
    +
    +    __getCachedAttributes(path) {
    +        // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
    +        // when template instance is a parent of template itself
    +        if (path.includes(this.noteId)) {
    +            return [];
    +        }
    +
    +        if (!(this.noteId in noteAttributeCache.attributes)) {
    +            const newPath = [...path, this.noteId];
    +            const attrArrs = [ this.getOwnedAttributes() ];
    +
    +            // inheritable attrs on root are typically not intended to be applied to hidden subtree #3537
    +            if (this.noteId !== 'root' && this.noteId !== '_hidden') {
    +                for (const parentNote of this.getParentNotes()) {
    +                    // these virtual parent-child relationships are also loaded into froca
    +                    if (parentNote.type !== 'search') {
    +                        attrArrs.push(parentNote.__getInheritableAttributes(newPath));
    +                    }
    +                }
    +            }
    +
    +            for (const templateAttr of attrArrs.flat().filter(attr => attr.type === 'relation' && ['template', 'inherit'].includes(attr.name))) {
    +                const templateNote = this.froca.notes[templateAttr.value];
    +
    +                if (templateNote && templateNote.noteId !== this.noteId) {
    +                    attrArrs.push(
    +                        templateNote.__getCachedAttributes(newPath)
    +                            // template attr is used as a marker for templates, but it's not meant to be inherited
    +                            .filter(attr => !(attr.type === 'label' && (attr.name === 'template' || attr.name === 'workspacetemplate')))
    +                    );
    +                }
    +            }
    +
    +            noteAttributeCache.attributes[this.noteId] = [];
    +            const addedAttributeIds = new Set();
    +
    +            for (const attr of attrArrs.flat()) {
    +                if (!addedAttributeIds.has(attr.attributeId)) {
    +                    addedAttributeIds.add(attr.attributeId);
    +
    +                    noteAttributeCache.attributes[this.noteId].push(attr);
    +                }
    +            }
    +        }
    +
    +        return noteAttributeCache.attributes[this.noteId];
    +    }
    +
    +    isRoot() {
    +        return this.noteId === 'root';
    +    }
    +
    +    getAllNotePaths(encounteredNoteIds = null) {
    +        if (this.noteId === 'root') {
    +            return [['root']];
    +        }
    +
    +        if (!encounteredNoteIds) {
    +            encounteredNoteIds = new Set();
    +        }
    +
    +        encounteredNoteIds.add(this.noteId);
    +
    +        const parentNotes = this.getParentNotes();
    +        let paths;
    +
    +        if (parentNotes.length === 1) { // optimization for the most common case
    +            if (encounteredNoteIds.has(parentNotes[0].noteId)) {
    +                return [];
    +            }
    +            else {
    +                paths = parentNotes[0].getAllNotePaths(encounteredNoteIds);
    +            }
    +        }
    +        else {
    +            paths = [];
    +
    +            for (const parentNote of parentNotes) {
    +                if (encounteredNoteIds.has(parentNote.noteId)) {
    +                    continue;
    +                }
    +
    +                const newSet = new Set(encounteredNoteIds);
    +
    +                paths.push(...parentNote.getAllNotePaths(newSet));
    +            }
    +        }
    +
    +        for (const path of paths) {
    +            path.push(this.noteId);
    +        }
    +
    +        return paths;
    +    }
    +
    +    getSortedNotePaths(hoistedNotePath = 'root') {
    +        const notePaths = this.getAllNotePaths().map(path => ({
    +            notePath: path,
    +            isInHoistedSubTree: path.includes(hoistedNotePath),
    +            isArchived: path.find(noteId => froca.notes[noteId].isArchived),
    +            isSearch: path.find(noteId => froca.notes[noteId].type === 'search'),
    +            isHidden: path.includes('_hidden')
    +        }));
    +
    +        notePaths.sort((a, b) => {
    +            if (a.isInHoistedSubTree !== b.isInHoistedSubTree) {
    +                return a.isInHoistedSubTree ? -1 : 1;
    +            } else if (a.isSearch !== b.isSearch) {
    +                return a.isSearch ? 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;
    +            }
    +        });
    +
    +        return notePaths;
    +    }
    +
    +    /**
    +     * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
    +     */
    +    isHiddenCompletely() {
    +        if (this.noteId === 'root') {
    +            return false;
    +        }
    +
    +        for (const parentNote of this.getParentNotes()) {
    +            if (parentNote.noteId === 'root') {
    +                return false;
    +            } else if (parentNote.noteId === '_hidden') {
    +                continue;
    +            }
    +
    +            if (!parentNote.isHiddenCompletely()) {
    +                return false;
    +            }
    +        }
    +
    +        return true;
    +    }
    +
    +    __filterAttrs(attributes, type, name) {
    +        this.__validateTypeName(type, name);
    +
    +        if (!type && !name) {
    +            return attributes;
    +        } else if (type && name) {
    +            return attributes.filter(attr => attr.name === name && attr.type === type);
    +        } else if (type) {
    +            return attributes.filter(attr => attr.type === type);
    +        } else if (name) {
    +            return attributes.filter(attr => attr.name === name);
    +        }
    +    }
    +
    +    __getInheritableAttributes(path) {
    +        const attrs = this.__getCachedAttributes(path);
    +
    +        return attrs.filter(attr => attr.isInheritable);
    +    }
    +
    +    __validateTypeName(type, name) {
    +        if (type && type !== 'label' && type !== 'relation') {
    +            throw new Error(`Unrecognized attribute type '${type}'. Only 'label' and 'relation' are possible values.`);
    +        }
    +
    +        if (name) {
    +            const firstLetter = name.charAt(0);
    +            if (firstLetter === '#' || firstLetter === '~') {
    +                throw new Error(`Detect '#' or '~' in the attribute's name. In the API, attribute names should be set without these characters.`);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * @param {string} [name] - label name to filter
    +     * @returns {FAttribute[]} all note's labels (attributes with type label), including inherited ones
    +     */
    +    getOwnedLabels(name) {
    +        return this.getOwnedAttributes(LABEL, name);
    +    }
    +
    +    /**
    +     * @param {string} [name] - label name to filter
    +     * @returns {FAttribute[]} all note's labels (attributes with type label), including inherited ones
    +     */
    +    getLabels(name) {
    +        return this.getAttributes(LABEL, name);
    +    }
    +
    +    getIcon() {
    +        const iconClassLabels = this.getLabels('iconClass');
    +        const workspaceIconClass = this.getWorkspaceIconClass();
    +
    +        if (iconClassLabels.length > 0) {
    +            return iconClassLabels[0].value;
    +        }
    +        else if (workspaceIconClass) {
    +            return workspaceIconClass;
    +        }
    +        else if (this.noteId === 'root') {
    +            return "bx bx-chevrons-right";
    +        }
    +        if (this.noteId === '_share') {
    +            return "bx bx-share-alt";
    +        }
    +        else if (this.type === 'text') {
    +            if (this.isFolder()) {
    +                return "bx bx-folder";
    +            }
    +            else {
    +                return "bx bx-note";
    +            }
    +        }
    +        else if (this.type === 'code' && this.mime.startsWith('text/x-sql')) {
    +            return "bx bx-data";
    +        }
    +        else {
    +            return NOTE_TYPE_ICONS[this.type];
    +        }
    +    }
    +
    +    getColorClass() {
    +        const color = this.getLabelValue("color");
    +        return cssClassManager.createClassForColor(color);
    +    }
    +
    +    isFolder() {
    +        return this.type === 'search'
    +            || this.getFilteredChildBranches().length > 0;
    +    }
    +
    +    getFilteredChildBranches() {
    +        let childBranches = this.getChildBranches();
    +
    +        if (!childBranches) {
    +            ws.logError(`No children for '${this.noteId}'. This shouldn't happen.`);
    +            return;
    +        }
    +
    +        if (options.is("hideIncludedImages_main")) {
    +            const imageLinks = this.getRelations('imageLink');
    +
    +            // image is already visible in the parent note so no need to display it separately in the book
    +            childBranches = childBranches.filter(branch => !imageLinks.find(rel => rel.value === branch.noteId));
    +        }
    +
    +        // we're not checking hideArchivedNotes since that would mean we need to lazy load the child notes
    +        // which would seriously slow down everything.
    +        // we check this flag only once user chooses to expand the parent. This has the negative consequence that
    +        // note may appear as folder but not contain any children when all of them are archived
    +
    +        return childBranches;
    +    }
    +
    +    /**
    +     * @param {string} [name] - relation name to filter
    +     * @returns {FAttribute[]} all note's relations (attributes with type relation), including inherited ones
    +     */
    +    getOwnedRelations(name) {
    +        return this.getOwnedAttributes(RELATION, name);
    +    }
    +
    +    /**
    +     * @param {string} [name] - relation name to filter
    +     * @returns {FAttribute[]} all note's relations (attributes with type relation), including inherited ones
    +     */
    +    getRelations(name) {
    +        return this.getAttributes(RELATION, name);
    +    }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @returns {boolean} true if note has an attribute with given type and name (including inherited)
    +     */
    +    hasAttribute(type, name) {
    +        return !!this.getAttribute(type, name);
    +    }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @returns {boolean} true if note has an attribute with given type and name (including inherited)
    +     */
    +    hasOwnedAttribute(type, name) {
    +        return !!this.getOwnedAttribute(type, name);
    +    }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @returns {FAttribute} 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.
    +     */
    +    getOwnedAttribute(type, name) {
    +        const attributes = this.getOwnedAttributes();
    +
    +        return attributes.find(attr => attr.name === name && attr.type === type);
    +    }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @returns {FAttribute} attribute of given type and name. If there's more such attributes, first is  returned. Returns null if there's no such attribute belonging to this note.
    +     */
    +    getAttribute(type, name) {
    +        const attributes = this.getAttributes();
    +
    +        return attributes.find(attr => attr.name === name && attr.type === type);
    +    }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @returns {string} attribute value of given type and name or null if no such attribute exists.
    +     */
    +    getOwnedAttributeValue(type, name) {
    +        const attr = this.getOwnedAttribute(type, name);
    +
    +        return attr ? attr.value : null;
    +    }
    +
    +    /**
    +     * @param {string} type - attribute type (label, relation, etc.)
    +     * @param {string} name - attribute name
    +     * @returns {string} attribute value of given type and name or null if no such attribute exists.
    +     */
    +    getAttributeValue(type, name) {
    +        const attr = this.getAttribute(type, name);
    +
    +        return attr ? attr.value : null;
    +    }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {boolean} true if label exists (excluding inherited)
    +     */
    +    hasOwnedLabel(name) { return this.hasOwnedAttribute(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {boolean} true if label exists (including inherited)
    +     */
    +    hasLabel(name) { return this.hasAttribute(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {boolean} true if relation exists (excluding inherited)
    +     */
    +    hasOwnedRelation(name) { return this.hasOwnedAttribute(RELATION, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {boolean} true if relation exists (including inherited)
    +     */
    +    hasRelation(name) { return this.hasAttribute(RELATION, name); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {FAttribute} label if it exists, null otherwise
    +     */
    +    getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {FAttribute} label if it exists, null otherwise
    +     */
    +    getLabel(name) { return this.getAttribute(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {FAttribute} relation if it exists, null otherwise
    +     */
    +    getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {FAttribute} relation if it exists, null otherwise
    +     */
    +    getRelation(name) { return this.getAttribute(RELATION, name); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {string} label value if label exists, null otherwise
    +     */
    +    getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - label name
    +     * @returns {string} label value if label exists, null otherwise
    +     */
    +    getLabelValue(name) { return this.getAttributeValue(LABEL, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {string} relation value if relation exists, null otherwise
    +     */
    +    getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); }
    +
    +    /**
    +     * @param {string} name - relation name
    +     * @returns {string} relation value if relation exists, null otherwise
    +     */
    +    getRelationValue(name) { return this.getAttributeValue(RELATION, name); }
    +
    +    /**
    +     * @param {string} name
    +     * @returns {Promise<FNote>|null} target note of the relation or null (if target is empty or note was not found)
    +     */
    +    async getRelationTarget(name) {
    +        const targets = await this.getRelationTargets(name);
    +
    +        return targets.length > 0 ? targets[0] : null;
    +    }
    +
    +    /**
    +     * @param {string} [name] - relation name to filter
    +     * @returns {Promise<FNote[]>}
    +     */
    +    async getRelationTargets(name) {
    +        const relations = this.getRelations(name);
    +        const targets = [];
    +
    +        for (const relation of relations) {
    +            targets.push(await this.froca.getNote(relation.value));
    +        }
    +
    +        return targets;
    +    }
    +
    +    /**
    +     * @returns {FNote[]}
    +     */
    +    getNotesToInheritAttributesFrom() {
    +        const relations = [
    +            ...this.getRelations('template'),
    +            ...this.getRelations('inherit')
    +        ];
    +
    +        return relations.map(rel => this.froca.notes[rel.value]);
    +    }
    +
    +    getPromotedDefinitionAttributes() {
    +        if (this.hasLabel('hidePromotedAttributes')) {
    +            return [];
    +        }
    +
    +        const promotedAttrs = this.getAttributes()
    +            .filter(attr => attr.isDefinition())
    +            .filter(attr => {
    +                const def = attr.getDefinition();
    +
    +                return def && def.isPromoted;
    +            });
    +
    +        // attrs are not resorted if position changes after initial load
    +        promotedAttrs.sort((a, b) => a.position < b.position ? -1 : 1);
    +
    +        return promotedAttrs;
    +    }
    +
    +    hasAncestor(ancestorNoteId, followTemplates = false, visitedNoteIds = null) {
    +        if (this.noteId === ancestorNoteId) {
    +            return true;
    +        }
    +
    +        if (!visitedNoteIds) {
    +            visitedNoteIds = new Set();
    +        } else if (visitedNoteIds.has(this.noteId)) {
    +            // to avoid infinite cycle when template is descendent of the instance
    +            return false;
    +        }
    +
    +        visitedNoteIds.add(this.noteId);
    +
    +        if (followTemplates) {
    +            for (const templateNote of this.getNotesToInheritAttributesFrom()) {
    +                if (templateNote.hasAncestor(ancestorNoteId, followTemplates, visitedNoteIds)) {
    +                    return true;
    +                }
    +            }
    +        }
    +
    +        for (const parentNote of this.getParentNotes()) {
    +            if (parentNote.hasAncestor(ancestorNoteId, followTemplates, visitedNoteIds)) {
    +                return true;
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    isInHiddenSubtree() {
    +        return this.noteId === '_hidden' || this.hasAncestor('_hidden');
    +    }
    +
    +    /**
    +     * @deprecated NOOP
    +     */
    +    invalidateAttributeCache() {}
    +
    +    /**
    +     * Get relations which target this note
    +     *
    +     * @returns {FAttribute[]}
    +     */
    +    getTargetRelations() {
    +        return this.targetRelations
    +            .map(attributeId => this.froca.attributes[attributeId]);
    +    }
    +
    +    /**
    +     * Get relations which target this note
    +     *
    +     * @returns {FNote[]}
    +     */
    +    async getTargetRelationSourceNotes() {
    +        const targetRelations = this.getTargetRelations();
    +
    +        return await this.froca.getNotes(targetRelations.map(tr => tr.noteId));
    +    }
    +
    +    /**
    +     * Return note complement which is most importantly note's content
    +     *
    +     * @returns {Promise<FNoteComplement>}
    +     */
    +    async getNoteComplement() {
    +        return await this.froca.getNoteComplement(this.noteId);
    +    }
    +
    +    toString() {
    +        return `Note(noteId=${this.noteId}, title=${this.title})`;
    +    }
    +
    +    get dto() {
    +        const dto = Object.assign({}, this);
    +        delete dto.froca;
    +
    +        return dto;
    +    }
    +
    +    getCssClass() {
    +        const labels = this.getLabels('cssClass');
    +        return labels.map(l => l.value).join(' ');
    +    }
    +
    +    getWorkspaceIconClass() {
    +        const labels = this.getLabels('workspaceIconClass');
    +        return labels.length > 0 ? labels[0].value : "";
    +    }
    +
    +    getWorkspaceTabBackgroundColor() {
    +        const labels = this.getLabels('workspaceTabBackgroundColor');
    +        return labels.length > 0 ? labels[0].value : "";
    +    }
    +
    +    /** @returns {boolean} true if this note is JavaScript (code or file) */
    +    isJavaScript() {
    +        return (this.type === "code" || this.type === "file" || this.type === 'launcher')
    +            && (this.mime.startsWith("application/javascript")
    +                || this.mime === "application/x-javascript"
    +                || this.mime === "text/javascript");
    +    }
    +
    +    /** @returns {boolean} true if this note is HTML */
    +    isHtml() {
    +        return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
    +    }
    +
    +    /** @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;
    +    }
    +
    +    async executeScript() {
    +        if (!this.isJavaScript()) {
    +            throw new Error(`Note ${this.noteId} is of type ${this.type} and mime ${this.mime} and thus cannot be executed`);
    +        }
    +
    +        const env = this.getScriptEnv();
    +
    +        if (env === "frontend") {
    +            const bundleService = (await import("../services/bundle.js")).default;
    +            return await bundleService.getAndExecuteBundle(this.noteId);
    +        }
    +        else if (env === "backend") {
    +            const resp = await server.post(`script/run/${this.noteId}`);
    +        }
    +        else {
    +            throw new Error(`Unrecognized env type ${env} for note ${this.noteId}`);
    +        }
    +    }
    +
    +    isShared() {
    +        for (const parentNoteId of this.parents) {
    +            if (parentNoteId === 'root' || parentNoteId === 'none') {
    +                continue;
    +            }
    +
    +            const parentNote = froca.notes[parentNoteId];
    +
    +            if (!parentNote || parentNote.type === 'search') {
    +                continue;
    +            }
    +
    +            if (parentNote.noteId === '_share' || parentNote.isShared()) {
    +                return true;
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    isContentAvailable() {
    +        return !this.isProtected || protectedSessionHolder.isProtectedSessionAvailable()
    +    }
    +
    +    isLaunchBarConfig() {
    +        return this.type === 'launcher' || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(this.noteId);
    +    }
    +
    +    isOptions() {
    +        return this.noteId.startsWith("_options");
    +    }
    +}
    +
    +export default FNote;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/frontend_api/entities_fnote_complement.js.html b/docs/frontend_api/entities_fnote_complement.js.html new file mode 100644 index 000000000..6ea35e95a --- /dev/null +++ b/docs/frontend_api/entities_fnote_complement.js.html @@ -0,0 +1,91 @@ + + + + + JSDoc: Source: entities/fnote_complement.js + + + + + + + + + + +
    + +

    Source: entities/fnote_complement.js

    + + + + + + +
    +
    +
    /**
    + * Complements the FNote with the main note content and other extra attributes
    + */
    +class FNoteComplement {
    +    constructor(row) {
    +        /** @type {string} */
    +        this.noteId = row.noteId;
    +
    +        /**
    +         * can either contain the whole content (in e.g. string notes), only part (large text notes) or nothing at all (binary notes, images)
    +         * @type {string}
    +         */
    +        this.content = row.content;
    +
    +        /** @type {int} */
    +        this.contentLength = row.contentLength;
    +
    +        /** @type {string} */
    +        this.dateCreated = row.dateCreated;
    +
    +        /** @type {string} */
    +        this.dateModified = row.dateModified;
    +
    +        /** @type {string} */
    +        this.utcDateCreated = row.utcDateCreated;
    +
    +        /** @type {string} */
    +        this.utcDateModified = row.utcDateModified;
    +
    +        // "combined" date modified give larger out of note's and note_content's dateModified
    +
    +        /** @type {string} */
    +        this.combinedDateModified = row.combinedDateModified;
    +
    +        /** @type {string} */
    +        this.combinedUtcDateModified = row.combinedUtcDateModified;
    +    }
    +}
    +
    +export default FNoteComplement;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/frontend_api/index.html b/docs/frontend_api/index.html index e6417de05..08a8eb159 100644 --- a/docs/frontend_api/index.html +++ b/docs/frontend_api/index.html @@ -56,7 +56,7 @@
    diff --git a/docs/frontend_api/services_frontend_script_api.js.html b/docs/frontend_api/services_frontend_script_api.js.html new file mode 100644 index 000000000..a326810c3 --- /dev/null +++ b/docs/frontend_api/services_frontend_script_api.js.html @@ -0,0 +1,590 @@ + + + + + JSDoc: Source: services/frontend_script_api.js + + + + + + + + + + +
    + +

    Source: services/frontend_script_api.js

    + + + + + + +
    +
    +
    import server from './server.js';
    +import utils from './utils.js';
    +import toastService from './toast.js';
    +import linkService from './link.js';
    +import froca from './froca.js';
    +import noteTooltipService from './note_tooltip.js';
    +import protectedSessionService from './protected_session.js';
    +import dateNotesService from './date_notes.js';
    +import searchService from './search.js';
    +import RightPanelWidget from '../widgets/right_panel_widget.js';
    +import ws from "./ws.js";
    +import appContext from "../components/app_context.js";
    +import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
    +import BasicWidget from "../widgets/basic_widget.js";
    +import SpacedUpdate from "./spaced_update.js";
    +import shortcutService from "./shortcuts.js";
    +
    +/**
    + * <p>This is the main frontend API interface for scripts. All the properties and methods are published in the "api" object
    + * available in the JS frontend notes. You can use e.g. <code>api.showMessage(api.startNote.title);</code></p>
    + *
    + * @constructor
    + */
    +function FrontendScriptApi(startNote, currentNote, originEntity = null, $container = null) {
    +    /** @property {jQuery} container of all the rendered script content */
    +    this.$container = $container;
    +
    +    /** @property {object} note where script started executing */
    +    this.startNote = startNote;
    +    /** @property {object} note where script is currently executing */
    +    this.currentNote = currentNote;
    +    /** @property {object|null} entity whose event triggered this execution */
    +    this.originEntity = originEntity;
    +
    +    /** @property {dayjs} day.js library for date manipulation. See {@link https://day.js.org} for documentation */
    +    this.dayjs = dayjs;
    +
    +    /**
    +     * @property {RightPanelWidget}
    +     * @deprecated use api.RightPanelWidget instead
    +     */
    +    this.CollapsibleWidget = RightPanelWidget;
    +
    +    /** @property {RightPanelWidget} */
    +    this.RightPanelWidget = RightPanelWidget;
    +
    +    /** @property {NoteContextAwareWidget} */
    +    this.NoteContextAwareWidget = NoteContextAwareWidget;
    +
    +    /**
    +     * @property {NoteContextAwareWidget}
    +     * @deprecated use NoteContextAwareWidget instead
    +     */
    +    this.TabAwareWidget = NoteContextAwareWidget;
    +
    +    /**
    +     * @property {NoteContextAwareWidget}
    +     * @deprecated use NoteContextAwareWidget instead
    +     */
    +    this.TabCachingWidget = NoteContextAwareWidget;
    +
    +    /**
    +     * @property {NoteContextAwareWidget}
    +     * @deprecated use NoteContextAwareWidget instead
    +     */
    +    this.NoteContextCachingWidget = NoteContextAwareWidget;
    +
    +    /** @property {BasicWidget} */
    +    this.BasicWidget = BasicWidget;
    +
    +    /**
    +     * Activates note in the tree and in the note detail.
    +     *
    +     * @method
    +     * @param {string} notePath (or noteId)
    +     * @returns {Promise<void>}
    +     */
    +    this.activateNote = async notePath => {
    +        await appContext.tabManager.getActiveContext().setNote(notePath);
    +    };
    +
    +    /**
    +     * Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced.
    +     *
    +     * @param {string} notePath (or noteId)
    +     * @returns {Promise<void>}
    +     */
    +    this.activateNewNote = async notePath => {
    +        await ws.waitForMaxKnownEntityChangeId();
    +
    +        await appContext.tabManager.getActiveContext().setNote(notePath);
    +        appContext.triggerEvent('focusAndSelectTitle');
    +    };
    +
    +    /**
    +     * Open a note in a new tab.
    +     *
    +     * @method
    +     * @param {string} notePath (or noteId)
    +     * @param {boolean} activate - set to true to activate the new tab, false to stay on the current tab
    +     * @returns {Promise<void>}
    +     */
    +    this.openTabWithNote = async (notePath, activate) => {
    +        await ws.waitForMaxKnownEntityChangeId();
    +
    +        await appContext.tabManager.openContextWithNote(notePath, { activate });
    +
    +        if (activate) {
    +            appContext.triggerEvent('focusAndSelectTitle');
    +        }
    +    };
    +
    +    /**
    +     * Open a note in a new split.
    +     *
    +     * @method
    +     * @param {string} notePath (or noteId)
    +     * @param {boolean} activate - set to true to activate the new split, false to stay on the current split
    +     * @returns {Promise<void>}
    +     */
    +    this.openSplitWithNote = async (notePath, activate) => {
    +        await ws.waitForMaxKnownEntityChangeId();
    +
    +        const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
    +        const {ntxId} = subContexts[subContexts.length - 1];
    +
    +        appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath});
    +
    +        if (activate) {
    +            appContext.triggerEvent('focusAndSelectTitle');
    +        }
    +    };
    +
    +    /**
    +     * Adds a new launcher to the launchbar. If the launcher (id) already exists, it will be updated.
    +     *
    +     * @method
    +     * @deprecated you can now create/modify launchers in the top-left Menu -> Configure Launchbar
    +     *             for special needs there's also backend API's createOrUpdateLauncher()
    +     * @param {object} opts
    +     * @property {string} [opts.id] - id of the button, used to identify the old instances of this button to be replaced
    +     *                          ID is optional because of BC, but not specifying it is deprecated. ID can be alphanumeric only.
    +     * @property {string} opts.title
    +     * @property {string} [opts.icon] - name of the boxicon to be used (e.g. "time" for "bx-time" icon)
    +     * @property {function} opts.action - callback handling the click on the button
    +     * @property {string} [opts.shortcut] - keyboard shortcut for the button, e.g. "alt+t"
    +     */
    +    this.addButtonToToolbar = async opts => {
    +        console.warn("api.addButtonToToolbar() has been deprecated since v0.58 and may be removed in the future. Use  Menu -> Configure Launchbar to create/update launchers instead.");
    +
    +        const {action, ...reqBody} = opts;
    +        reqBody.action = action.toString();
    +
    +        await server.put('special-notes/api-script-launcher', reqBody);
    +    };
    +
    +    function prepareParams(params) {
    +        if (!params) {
    +            return params;
    +        }
    +
    +        return params.map(p => {
    +            if (typeof p === "function") {
    +                return `!@#Function: ${p.toString()}`;
    +            }
    +            else {
    +                return p;
    +            }
    +        });
    +    }
    +
    +    /**
    +     * Executes given anonymous function on the backend.
    +     * Internally this serializes the anonymous function into string and sends it to backend via AJAX.
    +     *
    +     * @method
    +     * @param {string} script - script to be executed on the backend
    +     * @param {Array.<?>} params - list of parameters to the anonymous function to be sent to backend
    +     * @returns {Promise<*>} return value of the executed function on the backend
    +     */
    +    this.runOnBackend = async (script, params = []) => {
    +        if (typeof script === "function") {
    +            script = script.toString();
    +        }
    +
    +        const ret = await server.post('script/exec', {
    +            script: script,
    +            params: prepareParams(params),
    +            startNoteId: startNote.noteId,
    +            currentNoteId: currentNote.noteId,
    +            originEntityName: "notes", // currently there's no other entity on frontend which can trigger event
    +            originEntityId: originEntity ? originEntity.noteId : null
    +        }, "script");
    +
    +        if (ret.success) {
    +            await ws.waitForMaxKnownEntityChangeId();
    +
    +            return ret.executionResult;
    +        }
    +        else {
    +            throw new Error(`server error: ${ret.error}`);
    +        }
    +    };
    +
    +    /**
    +     * This is a powerful search method - you can search by attributes and their values, e.g.:
    +     * "#dateModified =* MONTH AND #log". See full documentation for all options at: https://github.com/zadam/trilium/wiki/Search
    +     *
    +     * @method
    +     * @param {string} searchString
    +     * @returns {Promise<FNote[]>}
    +     */
    +    this.searchForNotes = async searchString => {
    +        return await searchService.searchForNotes(searchString);
    +    };
    +
    +    /**
    +     * This is a powerful search method - you can search by attributes and their values, e.g.:
    +     * "#dateModified =* MONTH AND #log". See full documentation for all options at: https://github.com/zadam/trilium/wiki/Search
    +     *
    +     * @method
    +     * @param {string} searchString
    +     * @returns {Promise<FNote|null>}
    +     */
    +    this.searchForNote = async searchString => {
    +        const notes = await this.searchForNotes(searchString);
    +
    +        return notes.length > 0 ? notes[0] : null;
    +    };
    +
    +    /**
    +     * Returns note by given noteId. If note is missing from cache, it's loaded.
    +     **
    +     * @method
    +     * @param {string} noteId
    +     * @returns {Promise<FNote>}
    +     */
    +    this.getNote = async noteId => await froca.getNote(noteId);
    +
    +    /**
    +     * Returns list of notes. If note is missing from cache, it's loaded.
    +     *
    +     * This is often used to bulk-fill the cache with notes which would have to be picked one by one
    +     * otherwise (by e.g. createNoteLink())
    +     *
    +     * @method
    +     * @param {string[]} noteIds
    +     * @param {boolean} [silentNotFoundError] - don't report error if the note is not found
    +     * @returns {Promise<FNote[]>}
    +     */
    +    this.getNotes = async (noteIds, silentNotFoundError = false) => await froca.getNotes(noteIds, silentNotFoundError);
    +
    +    /**
    +     * Update frontend tree (note) cache from the backend.
    +     *
    +     * @method
    +     * @param {string[]} noteIds
    +     */
    +    this.reloadNotes = async noteIds => await froca.reloadNotes(noteIds);
    +
    +    /**
    +     * Instance name identifies particular Trilium instance. It can be useful for scripts
    +     * if some action needs to happen on only one specific instance.
    +     *
    +     * @method
    +     * @returns {string}
    +     */
    +    this.getInstanceName = () => window.glob.instanceName;
    +
    +    /**
    +     * @method
    +     * @param {Date} date
    +     * @returns {string} date in YYYY-MM-DD format
    +     */
    +    this.formatDateISO = utils.formatDateISO;
    +
    +    /**
    +     * @method
    +     * @param {string} str
    +     * @returns {Date} parsed object
    +     */
    +    this.parseDate = utils.parseDate;
    +
    +    /**
    +     * Show info message to the user.
    +     *
    +     * @method
    +     * @param {string} message
    +     */
    +    this.showMessage = toastService.showMessage;
    +
    +    /**
    +     * Show error message to the user.
    +     *
    +     * @method
    +     * @param {string} message
    +     */
    +    this.showError = toastService.showError;
    +
    +    /**
    +     * Trigger command.
    +     *
    +     * @method
    +     * @param {string} name
    +     * @param {object} data
    +     */
    +    this.triggerCommand = (name, data) => appContext.triggerCommand(name, data);
    +
    +    /**
    +     * Trigger event.
    +     *
    +     * @method
    +     * @param {string} name
    +     * @param {object} data
    +     */
    +    this.triggerEvent = (name, data) => appContext.triggerEvent(name, data);
    +
    +    /**
    +     * Create note link (jQuery object) for given note.
    +     *
    +     * @method
    +     * @param {string} notePath (or noteId)
    +     * @param {object} [params]
    +     * @param {boolean} [params.showTooltip=true] - enable/disable tooltip on the link
    +     * @param {boolean} [params.showNotePath=false] - show also whole note's path as part of the link
    +     * @param {boolean} [params.showNoteIcon=false] - show also note icon before the title
    +     * @param {string} [params.title=] - custom link tile with note's title as default
    +     */
    +    this.createNoteLink = linkService.createNoteLink;
    +
    +    /**
    +     * Adds given text to the editor cursor
    +     *
    +     * @method
    +     * @param {string} text - this must be clear text, HTML is not supported.
    +     */
    +    this.addTextToActiveContextEditor = text => appContext.triggerCommand('addTextToActiveEditor', {text});
    +
    +    /**
    +     * @method
    +     * @returns {FNote} active note (loaded into right pane)
    +     */
    +    this.getActiveContextNote = () => appContext.tabManager.getActiveContextNote();
    +
    +    /**
    +     * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
    +     *
    +     * @method
    +     * @returns {Promise<CKEditor>} instance of CKEditor
    +     */
    +    this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContext()?.getTextEditor();
    +
    +    /**
    +     * See https://codemirror.net/doc/manual.html#api
    +     *
    +     * @method
    +     * @returns {Promise<CodeMirror>} instance of CodeMirror
    +     */
    +    this.getActiveContextCodeEditor = () => appContext.tabManager.getActiveContext()?.getCodeEditor();
    +
    +    /**
    +     * Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the
    +     * implementation of actual widget type.
    +     *
    +     * @method
    +     * @returns {Promise<NoteDetailWidget>}
    +     */
    +    this.getActiveNoteDetailWidget = () => new Promise(resolve => appContext.triggerCommand('executeInActiveNoteDetailWidget', {callback: resolve}));
    +
    +    /**
    +     * @method
    +     * @returns {Promise<string|null>} returns note path of active note or null if there isn't active note
    +     */
    +    this.getActiveContextNotePath = () => appContext.tabManager.getActiveContextNotePath();
    +
    +    /**
    +     * Returns component which owns given DOM element (the nearest parent component in DOM tree)
    +     *
    +     * @method
    +     * @param {Element} el - DOM element
    +     * @returns {Component}
    +     */
    +    this.getComponentByEl = el => appContext.getComponentByEl(el);
    +
    +    /**
    +     * @method
    +     * @param {object} $el - jquery object on which to set up the tooltip
    +     * @returns {Promise<void>}
    +     */
    +    this.setupElementTooltip = noteTooltipService.setupElementTooltip;
    +
    +    /**
    +     * @method
    +     * @param {string} noteId
    +     * @param {boolean} protect - true to protect note, false to unprotect
    +     * @returns {Promise<void>}
    +     */
    +    this.protectNote = async (noteId, protect) => {
    +        await protectedSessionService.protectNote(noteId, protect, false);
    +    };
    +
    +    /**
    +     * @method
    +     * @param {string} noteId
    +     * @param {boolean} protect - true to protect subtree, false to unprotect
    +     * @returns {Promise<void>}
    +     */
    +    this.protectSubTree = async (noteId, protect) => {
    +        await protectedSessionService.protectNote(noteId, protect, true);
    +    };
    +
    +    /**
    +     * Returns date-note for today. If it doesn't exist, it is automatically created.
    +     *
    +     * @method
    +     * @returns {Promise<FNote>}
    +     */
    +    this.getTodayNote = dateNotesService.getTodayNote;
    +
    +    /**
    +     * Returns day note for a given date. If it doesn't exist, it is automatically created.
    +     *
    +     * @method
    +     * @param {string} date - e.g. "2019-04-29"
    +     * @returns {Promise<FNote>}
    +     */
    +    this.getDayNote = dateNotesService.getDayNote;
    +
    +    /**
    +     * Returns day note for the first date of the week of the given date. If it doesn't exist, it is automatically created.
    +     *
    +     * @method
    +     * @param {string} date - e.g. "2019-04-29"
    +     * @returns {Promise<FNote>}
    +     */
    +     this.getWeekNote = dateNotesService.getWeekNote;
    +
    +    /**
    +     * Returns month-note. If it doesn't exist, it is automatically created.
    +     *
    +     * @method
    +     * @param {string} month - e.g. "2019-04"
    +     * @returns {Promise<FNote>}
    +     */
    +    this.getMonthNote = dateNotesService.getMonthNote;
    +
    +    /**
    +     * Returns year-note. If it doesn't exist, it is automatically created.
    +     *
    +     * @method
    +     * @param {string} year - e.g. "2019"
    +     * @returns {Promise<FNote>}
    +     */
    +    this.getYearNote = dateNotesService.getYearNote;
    +
    +    /**
    +     * Hoist note in the current tab. See https://github.com/zadam/trilium/wiki/Note-hoisting
    +     *
    +     * @method
    +     * @param {string} noteId - set hoisted note. 'root' will effectively unhoist
    +     * @returns {Promise<void>}
    +     */
    +    this.setHoistedNoteId = (noteId) => {
    +        const activeNoteContext = appContext.tabManager.getActiveContext();
    +
    +        if (activeNoteContext) {
    +            activeNoteContext.setHoistedNoteId(noteId);
    +        }
    +    };
    +
    +    /**
    +     * @method
    +     * @param {string} keyboardShortcut - e.g. "ctrl+shift+a"
    +     * @param {function} handler
    +     * @param {string} [namespace] - specify namespace of the handler for the cases where call for bind may be repeated.
    +     *                               If a handler with this ID exists, it's replaced by the new handler.
    +     * @returns {Promise<void>}
    +     */
    +    this.bindGlobalShortcut = shortcutService.bindGlobalShortcut;
    +
    +    /**
    +     * Trilium runs in backend and frontend process, when something is changed on the backend from script,
    +     * frontend will get asynchronously synchronized.
    +     *
    +     * This method returns a promise which resolves once all the backend -> frontend synchronization is finished.
    +     * Typical use case is when new note has been created, we should wait until it is synced into frontend and only then activate it.
    +     *
    +     * @method
    +     * @returns {Promise<void>}
    +     */
    +    this.waitUntilSynced = ws.waitForMaxKnownEntityChangeId;
    +
    +    /**
    +     * This will refresh all currently opened notes which have included note specified in the parameter
    +     *
    +     * @param includedNoteId - noteId of the included note
    +     * @returns {Promise<void>}
    +     */
    +    this.refreshIncludedNote = includedNoteId => appContext.triggerEvent('refreshIncludedNote', {noteId: includedNoteId});
    +
    +    /**
    +     * Return randomly generated string of given length. This random string generation is NOT cryptographically secure.
    +     *
    +     * @method
    +     * @param {number} length of the string
    +     * @returns {string} random string
    +     */
    +    this.randomString = utils.randomString;
    +
    +    this.logMessages = {};
    +    this.logSpacedUpdates = {};
    +
    +    /**
    +     * Log given message to the log pane in UI
    +     *
    +     * @param message
    +     * @returns {void}
    +     */
    +    this.log = message => {
    +        const {noteId} = this.startNote;
    +
    +        message = `${utils.now()}: ${message}`;
    +
    +        console.log(`Script ${noteId}: ${message}`);
    +
    +        this.logMessages[noteId] = this.logMessages[noteId] || [];
    +        this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => {
    +            const messages = this.logMessages[noteId];
    +            this.logMessages[noteId] = [];
    +
    +            appContext.triggerEvent("apiLogMessages", {noteId, messages});
    +        }, 100);
    +
    +        this.logMessages[noteId].push(message);
    +        this.logSpacedUpdates[noteId].scheduleUpdate();
    +    };
    +}
    +
    +export default FrontendScriptApi;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/package.json b/package.json index b2cf4758b..af96077bc 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,13 @@ "esm": "3.2.25", "jasmine": "4.5.0", "jsdoc": "4.0.1", + "build-api-docs": "./bin/build-api-docs.sh", + "postinstall": "node src-build/fix_pdfjs.js" + "better-sqlite3": "8.1.0", + "canvas": "2.11.0", + "ocrad.js": "antimatter15/ocrad.js#master", + "pdfjs-dist": "3.3.122", + "electron": "23.1.0", "lorem-ipsum": "2.0.8", "rcedit": "3.0.1", "webpack": "5.75.0", diff --git a/src/public/app/components/root_command_executor.js b/src/public/app/components/root_command_executor.js index 12bf68156..d5403d990 100644 --- a/src/public/app/components/root_command_executor.js +++ b/src/public/app/components/root_command_executor.js @@ -106,10 +106,6 @@ export default class RootCommandExecutor extends Component { await this.showAndHoistSubtree('_search'); } - async showUserGuideCommand() { - await this.showAndHoistSubtree('_userGuide'); - } - async showAndHoistSubtree(subtreeNoteId) { await appContext.tabManager.openContextWithNote(subtreeNoteId, { activate: true, diff --git a/src/public/app/entities/fattribute.js b/src/public/app/entities/fattribute.js index dc57a6e3d..ce166e038 100644 --- a/src/public/app/entities/fattribute.js +++ b/src/public/app/entities/fattribute.js @@ -42,7 +42,7 @@ class FAttribute { get targetNoteId() { // alias if (this.type !== 'relation') { - throw new Error(`FAttribute ${this.attributeId} is not a relation`); + throw new Error(`Attribute ${this.attributeId} is not a relation`); } return this.value; diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 46de93ae4..3040a4ac3 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -48,7 +48,6 @@ import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js"; import AboutDialog from "../widgets/dialogs/about.js"; import HelpDialog from "../widgets/dialogs/help.js"; import RecentChangesDialog from "../widgets/dialogs/recent_changes.js"; -import BackendLogDialog from "../widgets/dialogs/backend_log.js"; import BranchPrefixDialog from "../widgets/dialogs/branch_prefix.js"; import SortChildNotesDialog from "../widgets/dialogs/sort_child_notes.js"; import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js"; @@ -187,7 +186,6 @@ export default class DesktopLayout { .child(new AboutDialog()) .child(new HelpDialog()) .child(new RecentChangesDialog()) - .child(new BackendLogDialog()) .child(new BranchPrefixDialog()) .child(new SortChildNotesDialog()) .child(new PasswordNoteSetDialog()) diff --git a/src/public/app/widgets/buttons/global_menu.js b/src/public/app/widgets/buttons/global_menu.js index 653cb684a..8108655b7 100644 --- a/src/public/app/widgets/buttons/global_menu.js +++ b/src/public/app/widgets/buttons/global_menu.js @@ -75,7 +75,7 @@ const TPL = ` margin-right: 5px; } - body.mobile .show-user-guide-button, body.mobile .show-about-dialog-button { + body.mobile .show-help-button, body.mobile .show-about-dialog-button { /* hidden because these dialogs are not available for mobile */ display: none; } @@ -195,10 +195,10 @@ const TPL = ` -