Notes/src/becca/entities/bnote.js

1548 lines
48 KiB
JavaScript
Raw Normal View History

2020-05-17 09:48:24 +02:00
"use strict";
2021-05-17 22:09:49 +02:00
const protectedSessionService = require('../../services/protected_session');
const log = require('../../services/log');
const sql = require('../../services/sql');
const utils = require('../../services/utils');
const dateUtils = require('../../services/date_utils');
const entityChangesService = require('../../services/entity_changes');
const AbstractBeccaEntity = require("./abstract_becca_entity");
const BNoteRevision = require("./bnote_revision");
const BNoteAttachment = require("./bnote_attachment");
const TaskContext = require("../../services/task_context");
2022-06-12 23:29:11 +02:00
const dayjs = require("dayjs");
const utc = require('dayjs/plugin/utc');
const eventService = require("../../services/events");
dayjs.extend(utc);
2020-05-17 09:48:24 +02:00
2021-04-17 20:52:46 +02:00
const LABEL = 'label';
const RELATION = 'relation';
/**
* Trilium's main entity which can represent text note, image, code note, file attachment etc.
2022-04-16 00:17:32 +02:00
*
* @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"]; }
2021-04-30 22:13:13 +02:00
constructor(row) {
super();
if (!row) {
return;
}
2021-08-07 21:21:30 +02:00
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 ------
2021-10-29 21:36:23 +02:00
/** @type {string} */
this.noteId = noteId;
2021-10-29 21:36:23 +02:00
/** @type {string} */
this.title = title;
2021-10-29 21:36:23 +02:00
/** @type {boolean} */
this.isProtected = !!isProtected;
2021-10-29 21:36:23 +02:00
/** @type {string} */
this.type = type;
2021-10-29 21:36:23 +02:00
/** @type {string} */
this.mime = mime;
2021-10-29 21:36:23 +02:00
/** @type {string} */
this.dateCreated = dateCreated || dateUtils.localNowDateTime();
2021-10-29 21:36:23 +02:00
/** @type {string} */
this.dateModified = dateModified;
2021-10-29 21:36:23 +02:00
/** @type {string} */
this.utcDateCreated = utcDateCreated || dateUtils.utcNowDateTime();
2021-10-29 21:36:23 +02:00
/** @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 ------
2021-10-29 21:36:23 +02:00
/** @type {boolean} */
this.isDecrypted = !this.noteId || !this.isProtected;
this.decrypt();
2021-10-29 21:36:23 +02:00
/** @type {string|null} */
this.flatTextCache = null;
return this;
}
init() {
2023-01-03 14:31:46 +01:00
/** @type {BBranch[]}
2022-06-12 23:29:11 +02:00
* @private */
this.parentBranches = [];
/** @type {BNote[]}
2022-06-12 23:29:11 +02:00
* @private */
this.parents = [];
/** @type {BNote[]}
2022-06-12 23:29:11 +02:00
* @private*/
this.children = [];
2023-01-05 23:38:41 +01:00
/** @type {BAttribute[]}
2022-06-12 23:29:11 +02:00
* @private */
this.ownedAttributes = [];
2023-01-05 23:38:41 +01:00
/** @type {BAttribute[]|null}
* @private */
2021-04-25 21:19:18 +02:00
this.__attributeCache = null;
2023-01-05 23:38:41 +01:00
/** @type {BAttribute[]|null}
* @private*/
this.inheritableAttributeCache = null;
2023-01-05 23:38:41 +01:00
/** @type {BAttribute[]}
2022-06-12 23:29:11 +02:00
* @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}
2022-06-12 23:29:11 +02:00
* @private
*/
this.contentSize = null;
/**
* size of the content and note revision contents in bytes
* @type {int|null}
2022-06-12 23:29:11 +02:00
* @private
*/
this.noteSize = null;
/**
* number of note revisions for this note
* @type {int|null}
2022-06-12 23:29:11 +02:00
* @private
*/
this.revisionCount = null;
}
2021-05-17 22:35:36 +02:00
isContentAvailable() {
2021-05-09 11:12:53 +02:00
return !this.noteId // new note which was not encrypted yet
|| !this.isProtected
|| protectedSessionService.isProtectedSessionAvailable()
}
getTitleOrProtected() {
return this.isContentAvailable() ? this.title : '[protected]';
}
2023-01-03 14:31:46 +01:00
/** @returns {BBranch[]} */
2021-04-25 21:19:18 +02:00
getParentBranches() {
return this.parentBranches;
}
2022-12-04 13:16:05 +01:00
/**
* Returns <i>strong</i> (as opposed to <i>weak</i>) parent branches. See isWeak for details.
*
2023-01-03 14:31:46 +01:00
* @returns {BBranch[]}
2022-12-04 13:16:05 +01:00
*/
getStrongParentBranches() {
return this.getParentBranches().filter(branch => !branch.isWeak);
}
2021-12-20 17:30:47 +01:00
/**
2023-01-03 14:31:46 +01:00
* @returns {BBranch[]}
2021-12-20 17:30:47 +01:00
* @deprecated use getParentBranches() instead
*/
2021-04-25 22:02:32 +02:00
getBranches() {
return this.parentBranches;
}
/** @returns {BNote[]} */
2021-04-25 21:19:18 +02:00
getParentNotes() {
return this.parents;
}
/** @returns {BNote[]} */
2021-04-25 22:02:32 +02:00
getChildNotes() {
2021-04-25 21:19:18 +02:00
return this.children;
}
/** @returns {boolean} */
hasChildren() {
return this.children && this.children.length > 0;
}
2023-01-03 14:31:46 +01:00
/** @returns {BBranch[]} */
2021-04-25 22:02:32 +02:00
getChildBranches() {
return this.children.map(childNote => this.becca.getBranchFromChildAndParent(childNote.noteId, this.noteId));
2021-04-25 22:02:32 +02:00
}
/*
* 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]);
}
2022-06-12 23:29:11 +02:00
get dateCreatedObj() {
return this.dateCreated === null ? null : dayjs(this.dateCreated);
}
get utcDateCreatedObj() {
return this.utcDateCreated === null ? null : dayjs.utc(this.utcDateCreated);
}
get dateModifiedObj() {
return this.dateModified === null ? null : dayjs(this.dateModified);
}
get utcDateModifiedObj() {
return this.utcDateModified === null ? null : dayjs.utc(this.utcDateModified);
}
/** @returns {*} */
getJsonContent() {
const content = this.getContent();
if (!content || !content.trim()) {
return null;
}
return JSON.parse(content);
}
2021-08-07 21:21:30 +02:00
setContent(content, ignoreMissingProtectedSession = false) {
if (content === null || content === undefined) {
2022-04-19 23:36:21 +02:00
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);
}
2021-08-07 21:21:30 +02:00
else if (!ignoreMissingProtectedSession) {
2022-04-19 23:36:21 +02:00
throw new Error(`Cannot update content of noteId '${this.noteId}' since we're out of protected session.`);
}
}
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,
2021-06-29 23:45:45 +02:00
utcDateChanged: pojo.utcDateModified,
isSynced: true
2021-06-30 20:54:15 +02:00
});
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;
}
2021-04-25 21:19:18 +02:00
/**
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
2023-01-05 23:38:41 +01:00
* @returns {BAttribute[]} all note's attributes, including inherited ones
2021-04-25 21:19:18 +02:00
*/
getAttributes(type, name) {
this.__validateTypeName(type, name);
2023-01-27 08:46:04 +01:00
this.__ensureAttributeCacheIsAvailable();
2021-04-25 21:19:18 +02:00
if (type && name) {
2023-01-27 08:46:04 +01:00
return this.__attributeCache.filter(attr => attr.name === name && attr.type === type);
2021-04-25 21:19:18 +02:00
}
else if (type) {
return this.__attributeCache.filter(attr => attr.type === type);
}
else if (name) {
return this.__attributeCache.filter(attr => attr.name === name);
}
else {
// a bit unsafe to return the original array, but defensive copy would be costly
return this.__attributeCache;
2021-04-25 21:19:18 +02:00
}
}
2023-01-27 08:46:04 +01:00
/** @private */
__ensureAttributeCacheIsAvailable() {
if (!this.__attributeCache) {
this.__getAttributes([]);
}
}
2022-06-12 23:29:11 +02:00
/** @private */
__getAttributes(path) {
if (path.includes(this.noteId)) {
return [];
}
2021-04-25 21:19:18 +02:00
if (!this.__attributeCache) {
2020-05-16 23:12:29 +02:00
const parentAttributes = this.ownedAttributes.slice();
const newPath = [...path, this.noteId];
2020-05-16 23:12:29 +02:00
// inheritable attrs on root are typically not intended to be applied to hidden subtree #3537
if (this.noteId !== 'root' && this.noteId !== '_hidden') {
2020-05-16 23:12:29 +02:00
for (const parentNote of this.parents) {
parentAttributes.push(...parentNote.__getInheritableAttributes(newPath));
2020-05-16 23:12:29 +02:00
}
}
const templateAttributes = [];
for (const ownedAttr of parentAttributes) { // parentAttributes so we process also inherited templates
2023-01-06 20:31:55 +01:00
if (ownedAttr.type === 'relation' && ['template', 'inherit'].includes(ownedAttr.name)) {
2021-04-16 23:00:08 +02:00
const templateNote = this.becca.notes[ownedAttr.value];
2020-05-16 23:12:29 +02:00
if (templateNote) {
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')))
);
2020-05-16 23:12:29 +02:00
}
}
}
2021-04-25 21:19:18 +02:00
this.__attributeCache = [];
const addedAttributeIds = new Set();
for (const attr of parentAttributes.concat(templateAttributes)) {
if (!addedAttributeIds.has(attr.attributeId)) {
addedAttributeIds.add(attr.attributeId);
2021-04-25 21:19:18 +02:00
this.__attributeCache.push(attr);
}
}
2020-05-16 23:12:29 +02:00
this.inheritableAttributeCache = [];
2021-04-25 21:19:18 +02:00
for (const attr of this.__attributeCache) {
2020-05-16 23:12:29 +02:00
if (attr.isInheritable) {
this.inheritableAttributeCache.push(attr);
}
}
}
2021-04-25 21:19:18 +02:00
return this.__attributeCache;
2020-05-16 23:12:29 +02:00
}
2022-06-12 23:29:11 +02:00
/**
* @private
2023-01-05 23:38:41 +01:00
* @returns {BAttribute[]}
2022-06-12 23:29:11 +02:00
*/
__getInheritableAttributes(path) {
if (path.includes(this.noteId)) {
return [];
}
2020-05-16 23:12:29 +02:00
if (!this.inheritableAttributeCache) {
this.__getAttributes(path); // will refresh also this.inheritableAttributeCache
2020-05-16 23:12:29 +02:00
}
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 =>
2023-01-27 08:46:04 +01:00
attr.name === name
&& (value === undefined || value === null || attr.value === value)
2023-01-27 08:46:04 +01:00
&& attr.type === type
);
2020-05-16 23:12:29 +02:00
}
2020-12-15 15:09:00 +01:00
getAttributeCaseInsensitive(type, name, value) {
name = name.toLowerCase();
value = value ? value.toLowerCase() : null;
2021-04-25 21:19:18 +02:00
return this.getAttributes().find(
2023-01-27 08:46:04 +01:00
attr => attr.name.toLowerCase() === name
&& (!value || attr.value.toLowerCase() === value)
&& attr.type === type);
2020-12-15 15:09:00 +01:00
}
2021-04-17 20:52:46 +02:00
getRelationTarget(name) {
2023-01-27 08:46:04 +01:00
const relation = this.getAttributes().find(attr => attr.name === name && attr.type === 'relation');
2021-04-17 20:52:46 +02:00
return relation ? relation.targetNote : null;
}
2021-04-17 20:52:46 +02:00
/**
* @param {string} name - label name
* @param {string} [value] - label value
2021-04-17 20:52:46 +02:00
* @returns {boolean} true if label exists (including inherited)
*/
hasLabel(name, value) { return this.hasAttribute(LABEL, name, value); }
2021-04-17 20:52:46 +02:00
/**
* @param {string} name - label name
* @param {string} [value] - label value
2021-04-17 20:52:46 +02:00
* @returns {boolean} true if label exists (excluding inherited)
*/
hasOwnedLabel(name, value) { return this.hasOwnedAttribute(LABEL, name, value); }
2021-04-17 20:52:46 +02:00
/**
* @param {string} name - relation name
* @param {string} [value] - relation value
2021-04-17 20:52:46 +02:00
* @returns {boolean} true if relation exists (including inherited)
*/
hasRelation(name, value) { return this.hasAttribute(RELATION, name, value); }
2021-04-17 20:52:46 +02:00
/**
* @param {string} name - relation name
* @param {string} [value] - relation value
2021-04-17 20:52:46 +02:00
* @returns {boolean} true if relation exists (excluding inherited)
*/
hasOwnedRelation(name, value) { return this.hasOwnedAttribute(RELATION, name, value); }
2021-04-17 20:52:46 +02:00
/**
* @param {string} name - label name
2023-01-05 23:38:41 +01:00
* @returns {BAttribute|null} label if it exists, null otherwise
2021-04-17 20:52:46 +02:00
*/
getLabel(name) { return this.getAttribute(LABEL, name); }
/**
* @param {string} name - label name
2023-01-05 23:38:41 +01:00
* @returns {BAttribute|null} label if it exists, null otherwise
2021-04-17 20:52:46 +02:00
*/
getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); }
/**
* @param {string} name - relation name
2023-01-05 23:38:41 +01:00
* @returns {BAttribute|null} relation if it exists, null otherwise
2021-04-17 20:52:46 +02:00
*/
getRelation(name) { return this.getAttribute(RELATION, name); }
/**
* @param {string} name - relation name
2023-01-05 23:38:41 +01:00
* @returns {BAttribute|null} relation if it exists, null otherwise
2021-04-17 20:52:46 +02:00
*/
getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); }
/**
* @param {string} name - label name
* @returns {string|null} label value if label exists, null otherwise
*/
getLabelValue(name) { return this.getAttributeValue(LABEL, name); }
/**
* @param {string} name - label name
* @returns {string|null} label value if label exists, null otherwise
*/
getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {string|null} relation value if relation exists, null otherwise
*/
getRelationValue(name) { return this.getAttributeValue(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {string|null} relation value if relation exists, null otherwise
*/
getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); }
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} [value] - attribute value
2021-04-17 20:52:46 +02:00
* @returns {boolean} true if note has an attribute with given type and name (excluding inherited)
*/
hasOwnedAttribute(type, name, value) {
return !!this.getOwnedAttribute(type, name, value);
}
2021-04-17 20:52:46 +02:00
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
2023-01-05 23:38:41 +01:00
* @returns {BAttribute} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
2021-04-17 20:52:46 +02:00
*/
getAttribute(type, name) {
const attributes = this.getAttributes();
2020-05-25 00:25:47 +02:00
2023-01-27 08:46:04 +01:00
return attributes.find(attr => attr.name === name && attr.type === type);
2020-05-25 00:25:47 +02:00
}
2021-04-17 20:52:46 +02:00
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {string|null} attribute value of given type and name or null if no such attribute exists.
*/
getAttributeValue(type, name) {
const attr = this.getAttribute(type, name);
2020-05-25 00:25:47 +02:00
2021-04-17 20:52:46 +02:00
return attr ? attr.value : null;
}
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {string|null} attribute value of given type and name or null if no such attribute exists.
*/
getOwnedAttributeValue(type, name) {
const attr = this.getOwnedAttribute(type, name);
return attr ? attr.value : null;
2020-05-25 00:25:47 +02:00
}
2021-04-25 21:19:18 +02:00
/**
* @param {string} [name] - label name to filter
2023-01-05 23:38:41 +01:00
* @returns {BAttribute[]} all note's labels (attributes with type label), including inherited ones
2021-04-25 21:19:18 +02:00
*/
getLabels(name) {
return this.getAttributes(LABEL, name);
}
/**
* @param {string} [name] - label name to filter
* @returns {string[]} all note's label values, including inherited ones
*/
getLabelValues(name) {
return this.getLabels(name).map(l => l.value);
}
/**
* @param {string} [name] - label name to filter
2023-01-05 23:38:41 +01:00
* @returns {BAttribute[]} all note's labels (attributes with type label), excluding inherited ones
2021-04-25 21:19:18 +02:00
*/
getOwnedLabels(name) {
return this.getOwnedAttributes(LABEL, name);
}
/**
* @param {string} [name] - label name to filter
* @returns {string[]} all note's label values, excluding inherited ones
*/
getOwnedLabelValues(name) {
return this.getOwnedAttributes(LABEL, name).map(l => l.value);
}
/**
* @param {string} [name] - relation name to filter
2023-01-05 23:38:41 +01:00
* @returns {BAttribute[]} all note's relations (attributes with type relation), including inherited ones
2021-04-25 21:19:18 +02:00
*/
getRelations(name) {
return this.getAttributes(RELATION, name);
}
/**
* @param {string} [name] - relation name to filter
2023-01-05 23:38:41 +01:00
* @returns {BAttribute[]} all note's relations (attributes with type relation), excluding inherited ones
2021-04-25 21:19:18 +02:00
*/
getOwnedRelations(name) {
return this.getOwnedAttributes(RELATION, name);
}
2021-04-25 22:02:32 +02:00
/**
* @param {string|null} [type] - (optional) attribute type to filter
* @param {string|null} [name] - (optional) attribute name to filter
* @param {string|null} [value] - (optional) attribute value to filter
2023-01-05 23:38:41 +01:00
* @returns {BAttribute[]} note's "owned" attributes - excluding inherited ones
2021-04-25 22:02:32 +02:00
*/
getOwnedAttributes(type = null, name = null, value = null) {
this.__validateTypeName(type, name);
2021-09-02 22:14:22 +02:00
if (type && name && value !== undefined && value !== null) {
2023-01-27 08:46:04 +01:00
return this.ownedAttributes.filter(attr => attr.name === name && attr.value === value && attr.type === type);
}
else if (type && name) {
2023-01-27 08:46:04 +01:00
return this.ownedAttributes.filter(attr => attr.name === name && attr.type === type);
2021-04-25 22:02:32 +02:00
}
else if (type) {
return this.ownedAttributes.filter(attr => attr.type === type);
}
else if (name) {
return this.ownedAttributes.filter(attr => attr.name === name);
}
else {
return this.ownedAttributes.slice();
}
}
2021-05-08 23:31:20 +02:00
/**
2023-01-05 23:38:41 +01:00
* @returns {BAttribute} attribute belonging to this specific note (excludes inherited attributes)
2021-05-08 23:31:20 +02:00
*
* This method can be significantly faster than the getAttribute()
*/
getOwnedAttribute(type, name, value = null) {
const attrs = this.getOwnedAttributes(type, name, value);
2021-05-08 23:31:20 +02:00
return attrs.length > 0 ? attrs[0] : null;
}
2021-05-18 20:56:49 +02:00
get isArchived() {
2020-05-16 23:12:29 +02:00
return this.hasAttribute('label', 'archived');
}
2023-01-27 08:46:04 +01:00
hasInheritableArchivedLabel() {
for (const attr of this.getAttributes()) {
if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) {
return true;
}
}
return false;
2020-05-16 23:12:29 +02:00
}
2023-01-27 08:46:04 +01:00
// will sort the parents so that the non-archived are first and archived at the end
// this is done so that the non-archived paths are always explored as first when looking for note path
sortParents() {
2023-01-27 16:57:23 +01:00
this.parentBranches.sort((a, b) => {
if (a.parentNote?.isArchived) {
return 1;
} else if (a.parentNote?.isHiddenCompletely()) {
return 1;
} else {
return -1;
}
});
2022-08-16 23:46:56 +02:00
this.parents = this.parentBranches
.map(branch => branch.parentNote)
.filter(note => !!note);
2020-05-16 23:12:29 +02:00
}
sortChildren() {
if (this.children.length === 0) {
return;
}
const becca = this.becca;
this.children.sort((a, b) => {
const aBranch = becca.getBranchFromChildAndParent(a.noteId, this.noteId);
const bBranch = becca.getBranchFromChildAndParent(b.noteId, this.noteId);
return aBranch?.notePosition < bBranch?.notePosition ? -1 : 1;
});
}
2020-05-16 23:12:29 +02:00
/**
* This is used for:
* - fast searching
* - note similarity evaluation
*
2023-01-05 23:38:41 +01:00
* @returns {string} - returns flattened textual representation of note, prefixes and attributes
2020-05-16 23:12:29 +02:00
*/
2021-05-17 22:35:36 +02:00
getFlatText() {
2020-05-16 23:12:29 +02:00
if (!this.flatTextCache) {
this.flatTextCache = `${this.noteId} ${this.type} ${this.mime} `;
2020-05-16 23:12:29 +02:00
for (const branch of this.parentBranches) {
if (branch.prefix) {
this.flatTextCache += `${branch.prefix} `;
2020-05-16 23:12:29 +02:00
}
}
this.flatTextCache += `${this.title} `;
2020-05-16 23:12:29 +02:00
2021-04-25 21:19:18 +02:00
for (const attr of this.getAttributes()) {
2020-05-16 23:12:29 +02:00
// it's best to use space as separator since spaces are filtered from the search string by the tokenization into words
this.flatTextCache += `${attr.type === 'label' ? '#' : '~'}${attr.name}`;
2020-05-16 23:12:29 +02:00
if (attr.value) {
this.flatTextCache += `=${attr.value}`;
2020-05-16 23:12:29 +02:00
}
this.flatTextCache += ' ';
2020-05-16 23:12:29 +02:00
}
2021-09-28 10:02:25 +02:00
this.flatTextCache = utils.normalize(this.flatTextCache);
2020-05-16 23:12:29 +02:00
}
return this.flatTextCache;
}
invalidateThisCache() {
this.flatTextCache = null;
2021-04-25 21:19:18 +02:00
this.__attributeCache = null;
2020-05-16 23:12:29 +02:00
this.inheritableAttributeCache = null;
2020-05-23 23:44:55 +02:00
this.ancestorCache = null;
2020-05-16 23:12:29 +02:00
}
2021-04-17 20:52:46 +02:00
invalidateSubTree(path = []) {
2021-03-30 21:39:42 +02:00
if (path.includes(this.noteId)) {
return;
}
2020-05-16 23:12:29 +02:00
this.invalidateThisCache();
2021-03-30 21:39:42 +02:00
if (this.children.length || this.targetRelations.length) {
path = [...path, this.noteId];
}
2020-05-16 23:12:29 +02:00
for (const childNote of this.children) {
2021-04-17 20:52:46 +02:00
childNote.invalidateSubTree(path);
2020-05-16 23:12:29 +02:00
}
for (const targetRelation of this.targetRelations) {
2023-01-06 20:31:55 +01:00
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
2020-05-16 23:12:29 +02:00
const note = targetRelation.note;
if (note) {
2021-04-17 20:52:46 +02:00
note.invalidateSubTree(path);
2020-05-16 23:12:29 +02:00
}
}
}
}
invalidateSubtreeFlatText() {
this.flatTextCache = null;
for (const childNote of this.children) {
childNote.invalidateSubtreeFlatText();
}
for (const targetRelation of this.targetRelations) {
2023-01-06 20:31:55 +01:00
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
2020-05-16 23:12:29 +02:00
const note = targetRelation.note;
if (note) {
note.invalidateSubtreeFlatText();
}
}
}
}
2021-06-03 12:32:48 +02:00
getRelationDefinitions() {
return this.getLabels()
.filter(l => l.name.startsWith("relation:"));
}
getLabelDefinitions() {
return this.getLabels()
.filter(l => l.name.startsWith("relation:"));
}
2023-01-06 20:31:55 +01:00
isInherited() {
return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit');
2020-05-16 23:12:29 +02:00
}
/** @returns {BNote[]} */
2021-05-17 22:35:36 +02:00
getSubtreeNotesIncludingTemplated() {
2021-10-29 21:36:23 +02:00
const set = new Set();
2020-05-16 23:12:29 +02:00
2021-10-29 21:36:23 +02:00
function inner(note) {
// _hidden is not counted as subtree for the purpose of inheritance
if (set.has(note) || note.noteId === '_hidden') {
2021-10-29 21:36:23 +02:00
return;
}
2020-05-16 23:12:29 +02:00
2021-10-29 21:36:23 +02:00
set.add(note);
2020-05-16 23:12:29 +02:00
2021-10-29 21:36:23 +02:00
for (const childNote of note.children) {
inner(childNote);
}
for (const targetRelation of note.targetRelations) {
2023-01-06 20:31:55 +01:00
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
2021-10-29 21:36:23 +02:00
const targetNote = targetRelation.note;
if (targetNote) {
inner(targetNote);
}
2020-05-16 23:12:29 +02:00
}
}
}
2021-10-29 21:36:23 +02:00
inner(this);
return Array.from(set);
2020-05-16 23:12:29 +02:00
}
2023-01-05 23:38:41 +01:00
/** @returns {BNote[]} */
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} = {}) {
2021-09-21 22:45:06 +02:00
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) {
2022-11-06 14:38:41 +01:00
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;
}
2021-09-20 23:04:41 +02:00
2021-09-21 22:45:06 +02:00
if (!includeArchived && note.isArchived) {
return;
}
2020-05-16 23:12:29 +02:00
2021-09-21 22:45:06 +02:00
noteSet.add(note);
if (note.type === 'search') {
if (resolveSearch) {
resolveSearchNote(note);
}
}
else {
for (const childNote of note.children) {
addSubtreeNotesInner(childNote, note);
}
2021-09-21 22:45:06 +02:00
}
2020-05-16 23:12:29 +02:00
}
2021-09-21 22:45:06 +02:00
addSubtreeNotesInner(this);
return {
notes: Array.from(noteSet),
relationships
};
2020-05-16 23:12:29 +02:00
}
/** @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 */
2021-04-25 22:02:32 +02:00
getDescendantNoteIds() {
2021-05-17 22:35:36 +02:00
return this.getSubtreeNoteIds();
2021-04-25 22:02:32 +02:00
}
2020-05-23 20:52:55 +02:00
get parentCount() {
return this.parents.length;
}
get childrenCount() {
return this.children.length;
}
get labelCount() {
2021-04-25 21:19:18 +02:00
return this.getAttributes().filter(attr => attr.type === 'label').length;
2020-05-23 20:52:55 +02:00
}
get ownedLabelCount() {
return this.ownedAttributes.filter(attr => attr.type === 'label').length;
}
2020-05-23 20:52:55 +02:00
get relationCount() {
2021-04-25 21:19:18 +02:00
return this.getAttributes().filter(attr => attr.type === 'relation' && !attr.isAutoLink()).length;
}
get relationCountIncludingLinks() {
2021-04-25 21:19:18 +02:00
return this.getAttributes().filter(attr => attr.type === 'relation').length;
2020-05-23 20:52:55 +02:00
}
get ownedRelationCount() {
return this.ownedAttributes.filter(attr => attr.type === 'relation' && !attr.isAutoLink()).length;
}
get ownedRelationCountIncludingLinks() {
return this.ownedAttributes.filter(attr => attr.type === 'relation').length;
}
get targetRelationCount() {
return this.targetRelations.filter(attr => !attr.isAutoLink()).length;
}
get targetRelationCountIncludingLinks() {
return this.targetRelations.length;
}
2020-05-23 20:52:55 +02:00
get attributeCount() {
2021-04-25 21:19:18 +02:00
return this.getAttributes().length;
2020-05-23 20:52:55 +02:00
}
get ownedAttributeCount() {
2023-01-27 08:46:04 +01:00
return this.getOwnedAttributes().length;
}
/** @returns {BNote[]} */
2021-05-17 22:35:36 +02:00
getAncestors() {
2020-05-23 23:44:55 +02:00
if (!this.ancestorCache) {
const noteIds = new Set();
this.ancestorCache = [];
for (const parent of this.parents) {
if (noteIds.has(parent.noteId)) {
continue;
2020-05-23 23:44:55 +02:00
}
this.ancestorCache.push(parent);
noteIds.add(parent.noteId);
2021-05-17 22:35:36 +02:00
for (const ancestorNote of parent.getAncestors()) {
2020-05-23 23:44:55 +02:00
if (!noteIds.has(ancestorNote.noteId)) {
this.ancestorCache.push(ancestorNote);
noteIds.add(ancestorNote.noteId);
}
}
}
}
return this.ancestorCache;
}
/** @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');
}
2021-05-08 23:31:20 +02:00
getTargetRelations() {
return this.targetRelations;
}
/** @returns {BNote[]} - returns only notes which are templated, does not include their subtrees
2020-05-16 23:12:29 +02:00
* in effect returns notes which are influenced by note's non-inheritable attributes */
2023-01-06 20:31:55 +01:00
getInheritingNotes() {
2020-05-16 23:12:29 +02:00
const arr = [this];
for (const targetRelation of this.targetRelations) {
2023-01-06 20:31:55 +01:00
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
2020-05-16 23:12:29 +02:00
const note = targetRelation.note;
if (note) {
arr.push(note);
}
}
}
return arr;
}
2020-05-17 09:48:24 +02:00
2021-01-26 23:25:18 +01:00
getDistanceToAncestor(ancestorNoteId) {
if (this.noteId === ancestorNoteId) {
return 0;
}
let minDistance = 999999;
2021-01-26 23:25:18 +01:00
for (const parent of this.parents) {
minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1);
}
return minDistance;
}
2023-01-23 23:37:58 +01:00
/** @returns {BNoteRevision[]} */
getNoteRevisions() {
return sql.getRows("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId])
.map(row => new BNoteRevision(row));
}
2023-01-23 23:37:58 +01:00
/** @returns {BNoteAttachment[]} */
getNoteAttachments() {
return sql.getRows("SELECT * FROM note_attachments WHERE noteId = ? AND isDeleted = 0", [this.noteId])
.map(row => new BNoteAttachment(row));
}
2023-01-23 23:37:58 +01:00
/** @returns {BNoteAttachment|undefined} */
2023-01-23 16:57:28 +01:00
getNoteAttachmentByName(name) {
return sql.getRows("SELECT * FROM note_attachments WHERE noteId = ? AND name = ? AND isDeleted = 0", [this.noteId, name])
.map(row => new BNoteAttachment(row))
[0];
}
2021-05-09 20:46:32 +02:00
/**
2023-01-05 23:38:41 +01:00
* @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path)
2021-05-09 20:46:32 +02:00
*/
getAllNotePaths() {
if (this.noteId === 'root') {
return [['root']];
}
const notePaths = [];
for (const parentNote of this.getParentNotes()) {
for (const parentPath of parentNote.getAllNotePaths()) {
parentPath.push(this.noteId);
notePaths.push(parentPath);
}
}
return notePaths;
}
/**
* @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
*/
isHiddenCompletely() {
2023-01-27 16:57:23 +01:00
if (this.noteId === 'root') {
return false;
}
for (const parentNote of this.parents) {
if (parentNote.noteId === 'root') {
return false;
} else if (parentNote.noteId === '_hidden') {
continue;
}
if (!parentNote.isHiddenCompletely()) {
return false;
}
}
return true;
}
2021-05-09 20:46:32 +02:00
/**
* @param ancestorNoteId
2023-01-05 23:38:41 +01:00
* @returns {boolean} - true if ancestorNoteId occurs in at least one of the note's paths
2021-05-09 20:46:32 +02:00
*/
isDescendantOfNote(ancestorNoteId) {
const notePaths = this.getAllNotePaths();
return notePaths.some(path => path.includes(ancestorNoteId));
}
2021-05-11 22:00:16 +02:00
/**
* Update's given attribute's value or creates it if it doesn't exist
*
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} [value] - attribute value (optional)
*/
setAttribute(type, name, value) {
const attributes = this.getOwnedAttributes();
const attr = attributes.find(attr => attr.type === type && attr.name === name);
2022-12-22 14:57:00 +01:00
value = value?.toString() || "";
2021-05-11 22:00:16 +02:00
if (attr) {
if (attr.value !== value) {
attr.value = value;
attr.save();
}
}
else {
const BAttribute = require("./battribute");
2021-05-11 22:00:16 +02:00
new BAttribute({
2021-05-11 22:00:16 +02:00
noteId: this.noteId,
type: type,
name: name,
value: value
2021-05-11 22:00:16 +02:00
}).save();
}
}
/**
* Removes given attribute name-value pair if it exists.
*
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} [value] - attribute value (optional)
*/
removeAttribute(type, name, value) {
const attributes = this.getOwnedAttributes();
for (const attribute of attributes) {
if (attribute.type === type && attribute.name === name && (value === undefined || value === attribute.value)) {
attribute.markAsDeleted();
}
}
}
/**
* 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]
2023-01-05 23:38:41 +01:00
* @returns {BAttribute}
2021-05-11 22:00:16 +02:00
*/
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
const BAttribute = require("./battribute");
2021-05-11 22:00:16 +02:00
2023-01-08 12:46:26 +01:00
return new BAttribute({
2021-05-11 22:00:16 +02:00
noteId: this.noteId,
type: type,
name: name,
value: value,
isInheritable: isInheritable,
position: position
}).save();
}
/**
* 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]
2023-01-05 23:38:41 +01:00
* @returns {BAttribute}
*/
2021-05-11 22:00:16 +02:00
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]
2023-01-05 23:38:41 +01:00
* @returns {BAttribute}
*/
2021-05-11 22:00:16 +02:00
addRelation(name, targetNoteId, isInheritable = false) {
return this.addAttribute(RELATION, name, targetNoteId, isInheritable);
}
/**
* Based on enabled, attribute is either set or removed.
*
* @param {string} type - attribute type ('relation', 'label' etc.)
* @param {boolean} enabled - toggle On or Off
* @param {string} name - attribute name
* @param {string} [value] - attribute value (optional)
*/
toggleAttribute(type, enabled, name, value) {
if (enabled) {
this.setAttribute(type, name, value);
}
else {
this.removeAttribute(type, name, value);
}
}
/**
* Based on enabled, label is either set or removed.
*
* @param {boolean} enabled - toggle On or Off
* @param {string} name - label name
* @param {string} [value] - label value (optional)
*/
toggleLabel(enabled, name, value) { return this.toggleAttribute(LABEL, enabled, name, value); }
/**
* Based on enabled, relation is either set or removed.
*
* @param {boolean} enabled - toggle On or Off
* @param {string} name - relation name
* @param {string} [value] - relation value (noteId)
*/
toggleRelation(enabled, name, value) { return this.toggleAttribute(RELATION, enabled, name, value); }
/**
* Update's given label's value or creates it if it doesn't exist
*
* @param {string} name - label name
* @param {string} [value] - label value
*/
setLabel(name, value) { return this.setAttribute(LABEL, name, value); }
/**
* Update's given relation's value or creates it if it doesn't exist
*
* @param {string} name - relation name
* @param {string} value - relation value (noteId)
*/
setRelation(name, value) { return this.setAttribute(RELATION, name, value); }
/**
* Remove label name-value pair, if it exists.
*
* @param {string} name - label name
* @param {string} [value] - label value
*/
removeLabel(name, value) { return this.removeAttribute(LABEL, name, value); }
/**
* Remove relation name-value pair, if it exists.
*
* @param {string} name - relation name
* @param {string} [value] - relation value (noteId)
*/
removeRelation(name, value) { return this.removeAttribute(RELATION, name, value); }
2021-06-06 11:01:10 +02:00
searchNotesInSubtree(searchString) {
const searchService = require("../../services/search/services/search");
return searchService.searchNotes(searchString);
}
searchNoteInSubtree(searchString) {
return this.searchNotesInSubtree(searchString)[0];
}
/**
* @param parentNoteId
* @returns {{success: boolean, message: string}}
*/
2021-06-06 11:01:10 +02:00
cloneTo(parentNoteId) {
const cloningService = require("../../services/cloning");
const branch = this.becca.getNote(parentNoteId).getParentBranches()[0];
return cloningService.cloneNoteToBranch(this.noteId, branch.branchId);
2021-06-06 11:01:10 +02:00
}
/**
* (Soft) delete a note and all its descendants.
*
* @param {string} [deleteId] - optional delete identified
* @param {TaskContext} [taskContext]
*/
deleteNote(deleteId, taskContext) {
2022-06-08 22:25:00 +02:00
if (this.isDeleted) {
return;
}
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
2022-06-06 21:59:44 +02:00
const handlers = require("../../services/handlers");
handlers.runAttachedRelations(this, 'runOnNoteDeletion', this);
taskContext.noteDeletionHandlerTriggered = true;
for (const branch of this.getParentBranches()) {
branch.deleteBranch(deleteId, taskContext);
}
}
2020-05-17 09:48:24 +02:00
decrypt() {
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
try {
this.title = protectedSessionService.decryptString(this.title);
this.flatTextCache = null;
2020-05-17 09:48:24 +02:00
this.isDecrypted = true;
}
catch (e) {
log.error(`Could not decrypt note ${this.noteId}: ${e.message} ${e.stack}`);
}
2020-05-17 09:48:24 +02:00
}
}
2020-09-17 14:34:10 +02:00
2022-12-06 23:48:44 +01:00
isLaunchBarConfig() {
2022-12-21 16:11:00 +01:00
return this.type === 'launcher' || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(this.noteId);
2022-12-06 23:48:44 +01:00
}
2022-12-08 15:29:14 +01:00
isOptions() {
2023-01-13 11:34:35 +01:00
return this.noteId.startsWith("_options");
2022-12-08 15:29:14 +01:00
}
get isDeleted() {
return !(this.noteId in this.becca.notes) || this.isBeingDeleted;
}
/**
2023-01-05 23:38:41 +01:00
* @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,
2022-06-13 22:38:59 +02:00
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;
}
2023-01-23 16:57:28 +01:00
/**
* @returns {BNoteAttachment}
*/
saveNoteAttachment(name, mime, content) {
2023-01-24 16:55:48 +01:00
let noteAttachment = this.getNoteAttachmentByName(name);
2023-01-23 16:57:28 +01:00
2023-01-24 16:55:48 +01:00
if (noteAttachment
&& noteAttachment.mime === mime
&& noteAttachment.contentCheckSum === noteAttachment.calculateCheckSum(content)) {
return noteAttachment; // no change
}
noteAttachment = new BNoteAttachment({
2023-01-23 23:37:58 +01:00
noteId: this.noteId,
2023-01-23 16:57:28 +01:00
name,
mime,
isProtected: this.isProtected
});
noteAttachment.setContent(content);
return noteAttachment;
}
2021-05-08 21:10:58 +02:00
beforeSaving() {
super.beforeSaving();
this.becca.addNote(this.noteId, this);
2021-05-09 20:46:32 +02:00
2021-05-08 21:10:58 +02:00
this.dateModified = dateUtils.localNowDateTime();
this.utcDateModified = dateUtils.utcNowDateTime();
}
2021-04-25 21:19:18 +02:00
getPojo() {
return {
noteId: this.noteId,
title: this.title,
isProtected: this.isProtected,
type: this.type,
mime: this.mime,
2021-05-09 11:12:53 +02:00
isDeleted: false,
dateCreated: this.dateCreated,
dateModified: this.dateModified,
utcDateCreated: this.utcDateCreated,
utcDateModified: this.utcDateModified
};
}
getPojoToSave() {
const pojo = this.getPojo();
2021-04-25 21:19:18 +02:00
if (pojo.isProtected) {
if (this.isDecrypted) {
pojo.title = protectedSessionService.encrypt(pojo.title);
}
else {
// updating protected note outside of protected session means we will keep original ciphertexts
delete pojo.title;
}
}
return pojo;
}
2020-05-16 23:12:29 +02:00
}
2020-05-17 09:48:24 +02:00
module.exports = BNote;