Merge remote-tracking branch 'origin/master'

This commit is contained in:
zadam 2023-06-02 16:05:10 +02:00
commit 095da691a4
10 changed files with 261 additions and 110 deletions

15
package-lock.json generated
View File

@ -5,13 +5,14 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "trilium",
"version": "0.60.1-beta", "version": "0.60.1-beta",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "6.0.2", "@braintree/sanitize-url": "6.0.2",
"@electron/remote": "2.0.9", "@electron/remote": "2.0.9",
"@excalidraw/excalidraw": "0.15.2", "@excalidraw/excalidraw": "0.14.2",
"archiver": "5.3.1", "archiver": "5.3.1",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"axios": "1.4.0", "axios": "1.4.0",
@ -461,9 +462,9 @@
} }
}, },
"node_modules/@excalidraw/excalidraw": { "node_modules/@excalidraw/excalidraw": {
"version": "0.15.2", "version": "0.14.2",
"resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.15.2.tgz", "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.14.2.tgz",
"integrity": "sha512-rTI02kgWSTXiUdIkBxt9u/581F3eXcqQgJdIxmz54TFtG3ughoxO5fr4t7Fr2LZIturBPqfocQHGKZ0t2KLKgw==", "integrity": "sha512-8LdjpTBWEK5waDWB7Bt/G9YBI4j0OxkstUhvaDGz7dwQGfzF6FW5CXBoYHNEoX0qmb+Fg/NPOlZ7FrKsrSVCqg==",
"peerDependencies": { "peerDependencies": {
"react": "^17.0.2 || ^18.2.0", "react": "^17.0.2 || ^18.2.0",
"react-dom": "^17.0.2 || ^18.2.0" "react-dom": "^17.0.2 || ^18.2.0"
@ -13590,9 +13591,9 @@
"dev": true "dev": true
}, },
"@excalidraw/excalidraw": { "@excalidraw/excalidraw": {
"version": "0.15.2", "version": "0.14.2",
"resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.15.2.tgz", "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.14.2.tgz",
"integrity": "sha512-rTI02kgWSTXiUdIkBxt9u/581F3eXcqQgJdIxmz54TFtG3ughoxO5fr4t7Fr2LZIturBPqfocQHGKZ0t2KLKgw==", "integrity": "sha512-8LdjpTBWEK5waDWB7Bt/G9YBI4j0OxkstUhvaDGz7dwQGfzF6FW5CXBoYHNEoX0qmb+Fg/NPOlZ7FrKsrSVCqg==",
"requires": {} "requires": {}
}, },
"@gar/promisify": { "@gar/promisify": {

View File

@ -33,7 +33,7 @@
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "6.0.2", "@braintree/sanitize-url": "6.0.2",
"@electron/remote": "2.0.9", "@electron/remote": "2.0.9",
"@excalidraw/excalidraw": "0.15.2", "@excalidraw/excalidraw": "0.14.2",
"archiver": "5.3.1", "archiver": "5.3.1",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"axios": "1.4.0", "axios": "1.4.0",

View File

@ -69,18 +69,6 @@ function reload() {
require('../services/ws').reloadFrontend(); require('../services/ws').reloadFrontend();
} }
function postProcessEntityUpdate(entityName, entity) {
if (entityName === 'notes') {
noteUpdated(entity);
} else if (entityName === 'branches') {
branchUpdated(entity);
} else if (entityName === 'attributes') {
attributeUpdated(entity);
} else if (entityName === 'note_reordering') {
noteReorderingUpdated(entity);
}
}
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => { eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => {
if (!becca.loaded) { if (!becca.loaded) {
return; return;
@ -112,6 +100,25 @@ eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, en
postProcessEntityUpdate(entityName, entity); postProcessEntityUpdate(entityName, entity);
}); });
/**
* This gets run on entity being created or updated.
*
* @param entityName
* @param entityRow - can be a becca entity (change comes from this trilium instance) or just a row (from sync).
* Should be therefore treated as a row.
*/
function postProcessEntityUpdate(entityName, entityRow) {
if (entityName === 'notes') {
noteUpdated(entityRow);
} else if (entityName === 'branches') {
branchUpdated(entityRow);
} else if (entityName === 'attributes') {
attributeUpdated(entityRow);
} else if (entityName === 'note_reordering') {
noteReorderingUpdated(entityRow);
}
}
eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => { eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => {
if (!becca.loaded) { if (!becca.loaded) {
return; return;
@ -149,6 +156,7 @@ function branchDeleted(branchId) {
.filter(parentBranch => parentBranch.branchId !== branch.branchId); .filter(parentBranch => parentBranch.branchId !== branch.branchId);
if (childNote.parents.length > 0) { if (childNote.parents.length > 0) {
// subtree notes might lose some inherited attributes
childNote.invalidateSubTree(); childNote.invalidateSubTree();
} }
} }
@ -163,8 +171,8 @@ function branchDeleted(branchId) {
delete becca.branches[branch.branchId]; delete becca.branches[branch.branchId];
} }
function noteUpdated(entity) { function noteUpdated(entityRow) {
const note = becca.notes[entity.noteId]; const note = becca.notes[entityRow.noteId];
if (note) { if (note) {
// type / mime could have been changed, and they are present in flatTextCache // type / mime could have been changed, and they are present in flatTextCache
@ -172,15 +180,19 @@ function noteUpdated(entity) {
} }
} }
function branchUpdated(branch) { function branchUpdated(branchRow) {
const childNote = becca.notes[branch.noteId]; const childNote = becca.notes[branchRow.noteId];
if (childNote) { if (childNote) {
childNote.flatTextCache = null; childNote.flatTextCache = null;
childNote.sortParents(); childNote.sortParents();
// notes in the subtree can get new inherited attributes
// this is in theory needed upon branch creation, but there's no create event for sync changes
childNote.invalidateSubTree();
} }
const parentNote = becca.notes[branch.parentNoteId]; const parentNote = becca.notes[branchRow.parentNoteId];
if (parentNote) { if (parentNote) {
parentNote.sortChildren(); parentNote.sortChildren();
@ -222,8 +234,10 @@ function attributeDeleted(attributeId) {
} }
} }
function attributeUpdated(attribute) { /** @param {BAttribute} attributeRow */
const note = becca.notes[attribute.noteId]; function attributeUpdated(attributeRow) {
const attribute = becca.attributes[attributeRow.attributeId];
const note = becca.notes[attributeRow.noteId];
if (note) { if (note) {
if (attribute.isAffectingSubtree || note.isInherited()) { if (attribute.isAffectingSubtree || note.isInherited()) {

View File

@ -12,6 +12,7 @@ const TaskContext = require("../../services/task_context");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const utc = require('dayjs/plugin/utc'); const utc = require('dayjs/plugin/utc');
const eventService = require("../../services/events"); const eventService = require("../../services/events");
const cls = require("../../services/cls.js");
dayjs.extend(utc); dayjs.extend(utc);
const LABEL = 'label'; const LABEL = 'label';
@ -84,7 +85,7 @@ class BNote extends AbstractBeccaEntity {
this.decrypt(); this.decrypt();
/** @type {string|null} */ /** @type {string|null} */
this.flatTextCache = null; this.__flatTextCache = null;
return this; return this;
} }
@ -108,7 +109,7 @@ class BNote extends AbstractBeccaEntity {
this.__attributeCache = null; this.__attributeCache = null;
/** @type {BAttribute[]|null} /** @type {BAttribute[]|null}
* @private */ * @private */
this.inheritableAttributeCache = null; this.__inheritableAttributeCache = null;
/** @type {BAttribute[]} /** @type {BAttribute[]}
* @private */ * @private */
@ -118,7 +119,7 @@ class BNote extends AbstractBeccaEntity {
/** @type {BNote[]|null} /** @type {BNote[]|null}
* @private */ * @private */
this.ancestorCache = null; this.__ancestorCache = null;
// following attributes are filled during searching from database // following attributes are filled during searching from database
@ -316,10 +317,12 @@ class BNote extends AbstractBeccaEntity {
isSynced: true isSynced: true
}); });
eventService.emit(eventService.ENTITY_CHANGED, { if (!cls.isEntityEventsDisabled()) {
entityName: 'note_contents', eventService.emit(eventService.ENTITY_CHANGED, {
entity: this entityName: 'note_contents',
}); entity: this
});
}
} }
setJsonContent(content) { setJsonContent(content) {
@ -454,11 +457,11 @@ class BNote extends AbstractBeccaEntity {
} }
} }
this.inheritableAttributeCache = []; this.__inheritableAttributeCache = [];
for (const attr of this.__attributeCache) { for (const attr of this.__attributeCache) {
if (attr.isInheritable) { if (attr.isInheritable) {
this.inheritableAttributeCache.push(attr); this.__inheritableAttributeCache.push(attr);
} }
} }
} }
@ -475,11 +478,11 @@ class BNote extends AbstractBeccaEntity {
return []; return [];
} }
if (!this.inheritableAttributeCache) { if (!this.__inheritableAttributeCache) {
this.__getAttributes(path); // will refresh also this.inheritableAttributeCache this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache
} }
return this.inheritableAttributeCache; return this.__inheritableAttributeCache;
} }
__validateTypeName(type, name) { __validateTypeName(type, name) {
@ -813,40 +816,40 @@ class BNote extends AbstractBeccaEntity {
* @returns {string} - returns flattened textual representation of note, prefixes and attributes * @returns {string} - returns flattened textual representation of note, prefixes and attributes
*/ */
getFlatText() { getFlatText() {
if (!this.flatTextCache) { if (!this.__flatTextCache) {
this.flatTextCache = `${this.noteId} ${this.type} ${this.mime} `; this.__flatTextCache = `${this.noteId} ${this.type} ${this.mime} `;
for (const branch of this.parentBranches) { for (const branch of this.parentBranches) {
if (branch.prefix) { if (branch.prefix) {
this.flatTextCache += `${branch.prefix} `; this.__flatTextCache += `${branch.prefix} `;
} }
} }
this.flatTextCache += `${this.title} `; this.__flatTextCache += `${this.title} `;
for (const attr of this.getAttributes()) { 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 // 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}`; this.__flatTextCache += `${attr.type === 'label' ? '#' : '~'}${attr.name}`;
if (attr.value) { if (attr.value) {
this.flatTextCache += `=${attr.value}`; this.__flatTextCache += `=${attr.value}`;
} }
this.flatTextCache += ' '; this.__flatTextCache += ' ';
} }
this.flatTextCache = utils.normalize(this.flatTextCache); this.__flatTextCache = utils.normalize(this.__flatTextCache);
} }
return this.flatTextCache; return this.__flatTextCache;
} }
invalidateThisCache() { invalidateThisCache() {
this.flatTextCache = null; this.__flatTextCache = null;
this.__attributeCache = null; this.__attributeCache = null;
this.inheritableAttributeCache = null; this.__inheritableAttributeCache = null;
this.ancestorCache = null; this.__ancestorCache = null;
} }
invalidateSubTree(path = []) { invalidateSubTree(path = []) {
@ -875,24 +878,6 @@ class BNote extends AbstractBeccaEntity {
} }
} }
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() { getRelationDefinitions() {
return this.getLabels() return this.getLabels()
.filter(l => l.name.startsWith("relation:")); .filter(l => l.name.startsWith("relation:"));
@ -1083,28 +1068,28 @@ class BNote extends AbstractBeccaEntity {
/** @returns {BNote[]} */ /** @returns {BNote[]} */
getAncestors() { getAncestors() {
if (!this.ancestorCache) { if (!this.__ancestorCache) {
const noteIds = new Set(); const noteIds = new Set();
this.ancestorCache = []; this.__ancestorCache = [];
for (const parent of this.parents) { for (const parent of this.parents) {
if (noteIds.has(parent.noteId)) { if (noteIds.has(parent.noteId)) {
continue; continue;
} }
this.ancestorCache.push(parent); this.__ancestorCache.push(parent);
noteIds.add(parent.noteId); noteIds.add(parent.noteId);
for (const ancestorNote of parent.getAncestors()) { for (const ancestorNote of parent.getAncestors()) {
if (!noteIds.has(ancestorNote.noteId)) { if (!noteIds.has(ancestorNote.noteId)) {
this.ancestorCache.push(ancestorNote); this.__ancestorCache.push(ancestorNote);
noteIds.add(ancestorNote.noteId); noteIds.add(ancestorNote.noteId);
} }
} }
} }
} }
return this.ancestorCache; return this.__ancestorCache;
} }
/** @returns {boolean} */ /** @returns {boolean} */
@ -1491,7 +1476,7 @@ class BNote extends AbstractBeccaEntity {
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
try { try {
this.title = protectedSessionService.decryptString(this.title); this.title = protectedSessionService.decryptString(this.title);
this.flatTextCache = null; this.__flatTextCache = null;
this.isDecrypted = true; this.isDecrypted = true;
} }

View File

@ -1,3 +1,4 @@
/** @param {BNote} note */
function mapNoteToPojo(note) { function mapNoteToPojo(note) {
return { return {
noteId: note.noteId, noteId: note.noteId,
@ -17,6 +18,7 @@ function mapNoteToPojo(note) {
}; };
} }
/** @param {BBranch} branch */
function mapBranchToPojo(branch) { function mapBranchToPojo(branch) {
return { return {
branchId: branch.branchId, branchId: branch.branchId,
@ -29,6 +31,7 @@ function mapBranchToPojo(branch) {
}; };
} }
/** @param {BAttribute} attr */
function mapAttributeToPojo(attr) { function mapAttributeToPojo(attr) {
return { return {
attributeId: attr.attributeId, attributeId: attr.attributeId,

View File

@ -56,13 +56,15 @@ export default class TitleBarButtonsWidget extends BasicWidget {
const $maximizeBtn = this.$widget.find(".maximize-btn"); const $maximizeBtn = this.$widget.find(".maximize-btn");
const $closeBtn = this.$widget.find(".close-btn"); const $closeBtn = this.$widget.find(".close-btn");
//When the window is restarted, the window will not be reset when it is set to the top, so get the window status and set the icon background // When the window is restarted, the window will not be reset when it is set to the top,
(function () { // so get the window status and set the icon background
setTimeout(() => {
const remote = utils.dynamicRequire('@electron/remote'); const remote = utils.dynamicRequire('@electron/remote');
if (remote.BrowserWindow.getFocusedWindow().isAlwaysOnTop()) { if (remote.BrowserWindow.getFocusedWindow()?.isAlwaysOnTop()) {
$topBtn.addClass('active'); $topBtn.addClass('active');
} }
}()); }, 1000);
$topBtn.on('click', () => { $topBtn.on('click', () => {
$topBtn.trigger('blur'); $topBtn.trigger('blur');
const remote = utils.dynamicRequire('@electron/remote'); const remote = utils.dynamicRequire('@electron/remote');

View File

@ -54,11 +54,10 @@ function deriveMime(type, mime) {
} }
function copyChildAttributes(parentNote, childNote) { function copyChildAttributes(parentNote, childNote) {
const hasAlreadyTemplate = childNote.hasRelation('template');
for (const attr of parentNote.getAttributes()) { for (const attr of parentNote.getAttributes()) {
if (attr.name.startsWith("child:")) { if (attr.name.startsWith("child:")) {
const name = attr.name.substr(6); const name = attr.name.substr(6);
const hasAlreadyTemplate = childNote.hasRelation('template');
if (hasAlreadyTemplate && attr.type === 'relation' && name === 'template') { if (hasAlreadyTemplate && attr.type === 'relation' && name === 'template') {
// if the note already has a template, it means the template was chosen by the user explicitly // if the note already has a template, it means the template was chosen by the user explicitly
@ -174,7 +173,7 @@ function createNewNote(params) {
// TODO: think about what can happen if the note already exists with the forced ID // TODO: think about what can happen if the note already exists with the forced ID
// I guess on DB it's going to be fine, but becca references between entities // I guess on DB it's going to be fine, but becca references between entities
// might get messed up (two Note instance for the same ID existing in the references) // might get messed up (two note instances for the same ID existing in the references)
note = new BNote({ note = new BNote({
noteId: params.noteId, // optionally can force specific noteId noteId: params.noteId, // optionally can force specific noteId
title: params.title, title: params.title,
@ -195,7 +194,7 @@ function createNewNote(params) {
} }
finally { finally {
if (!isEntityEventsDisabled) { if (!isEntityEventsDisabled) {
// re-enable entity events only if there were previously enabled // re-enable entity events only if they were previously enabled
// (they can be disabled in case of import) // (they can be disabled in case of import)
cls.enableEntityEvents(); cls.enableEntityEvents();
} }
@ -215,27 +214,14 @@ function createNewNote(params) {
copyChildAttributes(parentNote, note); copyChildAttributes(parentNote, note);
eventService.emit(eventService.ENTITY_CREATED, { entityName: 'notes', entity: note });
eventService.emit(eventService.ENTITY_CHANGED, { entityName: 'notes', entity: note });
triggerNoteTitleChanged(note); triggerNoteTitleChanged(note);
// note_contents doesn't use "created" event
eventService.emit(eventService.ENTITY_CREATED, { eventService.emit(eventService.ENTITY_CHANGED, { entityName: 'note_contents', entity: note });
entityName: 'notes', eventService.emit(eventService.ENTITY_CREATED, { entityName: 'branches', entity: branch });
entity: note eventService.emit(eventService.ENTITY_CHANGED, { entityName: 'branches', entity: branch });
}); eventService.emit(eventService.CHILD_NOTE_CREATED, { childNote: note, parentNote: parentNote });
eventService.emit(eventService.ENTITY_CREATED, {
entityName: 'note_contents',
entity: note
});
eventService.emit(eventService.ENTITY_CREATED, {
entityName: 'branches',
entity: branch
});
eventService.emit(eventService.CHILD_NOTE_CREATED, {
childNote: note,
parentNote: parentNote
});
log.info(`Created new note '${note.noteId}', branch '${branch.branchId}' of type '${note.type}', mime '${note.mime}'`); log.info(`Created new note '${note.noteId}', branch '${branch.branchId}' of type '${note.type}', mime '${note.mime}'`);

View File

@ -40,7 +40,7 @@ class SNote extends AbstractShacaEntity {
/** @param {SAttribute[]|null} */ /** @param {SAttribute[]|null} */
this.__attributeCache = null; this.__attributeCache = null;
/** @param {SAttribute[]|null} */ /** @param {SAttribute[]|null} */
this.inheritableAttributeCache = null; this.__inheritableAttributeCache = null;
/** @param {SAttribute[]} */ /** @param {SAttribute[]} */
this.targetRelations = []; this.targetRelations = [];
@ -190,11 +190,11 @@ class SNote extends AbstractShacaEntity {
} }
} }
this.inheritableAttributeCache = []; this.__inheritableAttributeCache = [];
for (const attr of this.__attributeCache) { for (const attr of this.__attributeCache) {
if (attr.isInheritable) { if (attr.isInheritable) {
this.inheritableAttributeCache.push(attr); this.__inheritableAttributeCache.push(attr);
} }
} }
} }
@ -208,11 +208,11 @@ class SNote extends AbstractShacaEntity {
return []; return [];
} }
if (!this.inheritableAttributeCache) { if (!this.__inheritableAttributeCache) {
this.__getAttributes(path); // will refresh also this.inheritableAttributeCache this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache
} }
return this.inheritableAttributeCache; return this.__inheritableAttributeCache;
} }
/** @returns {boolean} */ /** @returns {boolean} */

View File

@ -0,0 +1,116 @@
POST {{triliumHost}}/etapi/create-note
Authorization: {{authToken}}
Content-Type: application/json
{
"parentNoteId": "root",
"title": "Hello parent",
"type": "text",
"content": "Hi there!"
}
> {%
client.assert(response.status === 201);
client.global.set("parentNoteId", response.body.note.noteId);
client.global.set("parentBranchId", response.body.branch.branchId);
%}
### Create inheritable parent attribute
POST {{triliumHost}}/etapi/attributes
Authorization: {{authToken}}
Content-Type: application/json
{
"noteId": "{{parentNoteId}}",
"type": "label",
"name": "mylabel",
"value": "",
"isInheritable": true,
"position": 10
}
> {%
client.assert(response.status === 201);
client.global.set("parentAttributeId", response.body.attributeId);
%}
### Create child note under root
POST {{triliumHost}}/etapi/create-note
Authorization: {{authToken}}
Content-Type: application/json
{
"parentNoteId": "root",
"title": "Hello child",
"type": "text",
"content": "Hi there!"
}
> {%
client.assert(response.status === 201);
client.global.set("childNoteId", response.body.note.noteId);
client.global.set("childBranchId", response.body.branch.branchId);
%}
### Create child attribute
POST {{triliumHost}}/etapi/attributes
Authorization: {{authToken}}
Content-Type: application/json
{
"noteId": "{{childNoteId}}",
"type": "label",
"name": "mylabel",
"value": "val",
"isInheritable": false,
"position": 10
}
> {%
client.assert(response.status === 201);
client.global.set("childAttributeId", response.body.attributeId);
%}
### Clone child to parent
POST {{triliumHost}}/etapi/branches
Authorization: {{authToken}}
Content-Type: application/json
{
"noteId": "{{childNoteId}}",
"parentNoteId": "{{parentNoteId}}"
}
> {%
client.assert(response.status === 201);
client.assert(response.body.parentNoteId == client.global.get("parentNoteId"));
%}
###
GET {{triliumHost}}/etapi/notes/{{childNoteId}}
Authorization: {{authToken}}
> {%
function hasAttribute(list, attributeId) {
for (let i = 0; i < list.length; i++) {
if (list[i]["attributeId"] === attributeId) {
return true;
}
}
return false;
}
client.assert(response.status === 200);
client.assert(response.body.noteId == client.global.get("childNoteId"));
client.assert(response.body.attributes.length == 2);
client.assert(hasAttribute(response.body.attributes,
client.global.get("parentAttributeId")));
client.assert(hasAttribute(response.body.attributes,
client.global.get("childAttributeId")));
%}

View File

@ -0,0 +1,44 @@
POST {{triliumHost}}/etapi/attributes
Authorization: {{authToken}}
Content-Type: application/json
{
"noteId": "root",
"type": "label",
"name": "mylabel",
"value": "val",
"isInheritable": true
}
> {% client.global.set("createdAttributeId", response.body.attributeId); %}
###
POST {{triliumHost}}/etapi/create-note
Authorization: {{authToken}}
Content-Type: application/json
{
"parentNoteId": "root",
"title": "Hello",
"type": "text",
"content": "Hi there!"
}
> {%
client.global.set("createdNoteId", response.body.note.noteId);
client.global.set("createdBranchId", response.body.branch.branchId);
%}
###
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
Authorization: {{authToken}}
> {%
client.assert(response.status === 200);
client.assert(response.body.noteId == client.global.get("createdNoteId"));
client.assert(response.body.attributes.length == 1);
client.assert(response.body.attributes[0].attributeId ==
client.global.get("createdAttributeId"));
%}