mirror of
https://github.com/zadam/trilium.git
synced 2025-10-29 01:06:36 +01:00
API docs
This commit is contained in:
@@ -33,9 +33,9 @@ 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 BRevision = require("./brevision");
|
||||
const BAttachment = require("./battachment");
|
||||
const TaskContext = require("../../services/task_context");
|
||||
const dayjs = require("dayjs");
|
||||
const utc = require('dayjs/plugin/utc');
|
||||
@@ -53,7 +53,7 @@ const RELATION = 'relation';
|
||||
class BNote extends AbstractBeccaEntity {
|
||||
static get entityName() { return "notes"; }
|
||||
static get primaryKeyName() { return "noteId"; }
|
||||
static get hashedProperties() { return ["noteId", "title", "isProtected", "type", "mime"]; }
|
||||
static get hashedProperties() { return ["noteId", "title", "isProtected", "type", "mime", "blobId"]; }
|
||||
|
||||
constructor(row) {
|
||||
super();
|
||||
@@ -73,6 +73,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
row.type,
|
||||
row.mime,
|
||||
row.isProtected,
|
||||
row.blobId,
|
||||
row.dateCreated,
|
||||
row.dateModified,
|
||||
row.utcDateCreated,
|
||||
@@ -80,19 +81,21 @@ class BNote extends AbstractBeccaEntity {
|
||||
]);
|
||||
}
|
||||
|
||||
update([noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified]) {
|
||||
update([noteId, title, type, mime, isProtected, blobId, 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 {boolean} */
|
||||
this.isProtected = !!isProtected;
|
||||
/** @type {string} */
|
||||
this.blobId = blobId;
|
||||
/** @type {string} */
|
||||
this.dateCreated = dateCreated || dateUtils.localNowDateTime();
|
||||
/** @type {string} */
|
||||
@@ -112,7 +115,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
this.decrypt();
|
||||
|
||||
/** @type {string|null} */
|
||||
this.flatTextCache = null;
|
||||
this.__flatTextCache = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -136,7 +139,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
this.__attributeCache = null;
|
||||
/** @type {BAttribute[]|null}
|
||||
* @private */
|
||||
this.inheritableAttributeCache = null;
|
||||
this.__inheritableAttributeCache = null;
|
||||
|
||||
/** @type {BAttribute[]}
|
||||
* @private */
|
||||
@@ -146,7 +149,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
|
||||
/** @type {BNote[]|null}
|
||||
* @private */
|
||||
this.ancestorCache = null;
|
||||
this.__ancestorCache = null;
|
||||
|
||||
// following attributes are filled during searching from database
|
||||
|
||||
@@ -231,49 +234,41 @@ class BNote extends AbstractBeccaEntity {
|
||||
* - 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 {string|Buffer} */
|
||||
getContent() {
|
||||
return this._getContent();
|
||||
}
|
||||
|
||||
/** @returns {{contentLength, dateModified, utcDateModified}} */
|
||||
/** @returns {{dateModified, utcDateModified}} */
|
||||
getContentMetadata() {
|
||||
return sql.getRow(`
|
||||
SELECT
|
||||
LENGTH(content) AS contentLength,
|
||||
dateModified,
|
||||
utcDateModified
|
||||
FROM note_contents
|
||||
WHERE noteId = ?`, [this.noteId]);
|
||||
return sql.getRow(`SELECT dateModified, utcDateModified FROM blobs WHERE blobId = ?`, [this.blobId]);
|
||||
}
|
||||
|
||||
/** @returns {*} */
|
||||
getJsonContent() {
|
||||
const content = this.getContent();
|
||||
|
||||
if (!content || !content.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param content
|
||||
* @param {object} [opts]
|
||||
* @param {object} [opts.forceSave=false] - will also save this BNote entity
|
||||
* @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload
|
||||
*/
|
||||
setContent(content, opts) {
|
||||
this._setContent(content, opts);
|
||||
|
||||
eventService.emit(eventService.NOTE_CONTENT_CHANGE, { entity: this });
|
||||
}
|
||||
|
||||
setJsonContent(content) {
|
||||
this.setContent(JSON.stringify(content, null, '\t'));
|
||||
}
|
||||
|
||||
get dateCreatedObj() {
|
||||
@@ -292,68 +287,6 @@ class BNote extends AbstractBeccaEntity {
|
||||
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';
|
||||
@@ -384,8 +317,13 @@ class BNote extends AbstractBeccaEntity {
|
||||
|| (this.type === 'file' && this.mime?.startsWith('image/'));
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if the note has string content (not binary) */
|
||||
/** @deprecated use hasStringContent() instead */
|
||||
isStringNote() {
|
||||
return this.hasStringContent();
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if the note has string content (not binary) */
|
||||
hasStringContent() {
|
||||
return utils.isStringNote(this.type, this.mime);
|
||||
}
|
||||
|
||||
@@ -482,11 +420,11 @@ class BNote extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
this.inheritableAttributeCache = [];
|
||||
this.__inheritableAttributeCache = [];
|
||||
|
||||
for (const attr of this.__attributeCache) {
|
||||
if (attr.isInheritable) {
|
||||
this.inheritableAttributeCache.push(attr);
|
||||
this.__inheritableAttributeCache.push(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,11 +441,11 @@ class BNote extends AbstractBeccaEntity {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!this.inheritableAttributeCache) {
|
||||
this.__getAttributes(path); // will refresh also this.inheritableAttributeCache
|
||||
if (!this.__inheritableAttributeCache) {
|
||||
this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache
|
||||
}
|
||||
|
||||
return this.inheritableAttributeCache;
|
||||
return this.__inheritableAttributeCache;
|
||||
}
|
||||
|
||||
__validateTypeName(type, name) {
|
||||
@@ -841,40 +779,40 @@ class BNote extends AbstractBeccaEntity {
|
||||
* @returns {string} - returns flattened textual representation of note, prefixes and attributes
|
||||
*/
|
||||
getFlatText() {
|
||||
if (!this.flatTextCache) {
|
||||
this.flatTextCache = `${this.noteId} ${this.type} ${this.mime} `;
|
||||
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 += `${branch.prefix} `;
|
||||
}
|
||||
}
|
||||
|
||||
this.flatTextCache += `${this.title} `;
|
||||
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}`;
|
||||
this.__flatTextCache += `${attr.type === 'label' ? '#' : '~'}${attr.name}`;
|
||||
|
||||
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() {
|
||||
this.flatTextCache = null;
|
||||
this.__flatTextCache = null;
|
||||
|
||||
this.__attributeCache = null;
|
||||
this.inheritableAttributeCache = null;
|
||||
this.ancestorCache = null;
|
||||
this.__inheritableAttributeCache = null;
|
||||
this.__ancestorCache = null;
|
||||
}
|
||||
|
||||
invalidateSubTree(path = []) {
|
||||
@@ -903,24 +841,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() {
|
||||
return this.getLabels()
|
||||
.filter(l => l.name.startsWith("relation:"));
|
||||
@@ -1049,7 +969,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
};
|
||||
}
|
||||
|
||||
/** @returns {String[]} - includes the subtree node as well */
|
||||
/** @returns {string[]} - includes the subtree root note as well */
|
||||
getSubtreeNoteIds({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) {
|
||||
return this.getSubtree({includeArchived, includeHidden, resolveSearch})
|
||||
.notes
|
||||
@@ -1111,28 +1031,33 @@ class BNote extends AbstractBeccaEntity {
|
||||
|
||||
/** @returns {BNote[]} */
|
||||
getAncestors() {
|
||||
if (!this.ancestorCache) {
|
||||
if (!this.__ancestorCache) {
|
||||
const noteIds = new Set();
|
||||
this.ancestorCache = [];
|
||||
this.__ancestorCache = [];
|
||||
|
||||
for (const parent of this.parents) {
|
||||
if (noteIds.has(parent.noteId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.ancestorCache.push(parent);
|
||||
this.__ancestorCache.push(parent);
|
||||
noteIds.add(parent.noteId);
|
||||
|
||||
for (const ancestorNote of parent.getAncestors()) {
|
||||
if (!noteIds.has(ancestorNote.noteId)) {
|
||||
this.ancestorCache.push(ancestorNote);
|
||||
this.__ancestorCache.push(ancestorNote);
|
||||
noteIds.add(ancestorNote.noteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.ancestorCache;
|
||||
return this.__ancestorCache;
|
||||
}
|
||||
|
||||
/** @returns {string[]} */
|
||||
getAncestorNoteIds() {
|
||||
return this.getAncestors().map(note => note.noteId);
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
@@ -1150,6 +1075,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
return this.noteId === '_hidden' || this.hasAncestor('_hidden');
|
||||
}
|
||||
|
||||
/** @returns {BAttribute[]} */
|
||||
getTargetRelations() {
|
||||
return this.targetRelations;
|
||||
}
|
||||
@@ -1186,10 +1112,55 @@ class BNote extends AbstractBeccaEntity {
|
||||
return minDistance;
|
||||
}
|
||||
|
||||
/** @returns {BNoteRevision[]} */
|
||||
getNoteRevisions() {
|
||||
return sql.getRows("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId])
|
||||
.map(row => new BNoteRevision(row));
|
||||
/** @returns {BRevision[]} */
|
||||
getRevisions() {
|
||||
return sql.getRows("SELECT * FROM revisions WHERE noteId = ?", [this.noteId])
|
||||
.map(row => new BRevision(row));
|
||||
}
|
||||
|
||||
/** @returns {BAttachment[]} */
|
||||
getAttachments(opts = {}) {
|
||||
opts.includeContentLength = !!opts.includeContentLength;
|
||||
// from testing it looks like calculating length does not make a difference in performance even on large-ish DB
|
||||
// given that we're always fetching attachments only for a specific note, we might just do it always
|
||||
|
||||
const query = opts.includeContentLength
|
||||
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
|
||||
FROM attachments
|
||||
JOIN blobs USING (blobId)
|
||||
WHERE parentId = ? AND isDeleted = 0
|
||||
ORDER BY position`
|
||||
: `SELECT * FROM attachments WHERE parentId = ? AND isDeleted = 0 ORDER BY position`;
|
||||
|
||||
return sql.getRows(query, [this.noteId])
|
||||
.map(row => new BAttachment(row));
|
||||
}
|
||||
|
||||
/** @returns {BAttachment|null} */
|
||||
getAttachmentById(attachmentId, opts = {}) {
|
||||
opts.includeContentLength = !!opts.includeContentLength;
|
||||
|
||||
const query = opts.includeContentLength
|
||||
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
|
||||
FROM attachments
|
||||
JOIN blobs USING (blobId)
|
||||
WHERE parentId = ? AND attachmentId = ? AND isDeleted = 0`
|
||||
: `SELECT * FROM attachments WHERE parentId = ? AND attachmentId = ? AND isDeleted = 0`;
|
||||
|
||||
return sql.getRows(query, [this.noteId, attachmentId])
|
||||
.map(row => new BAttachment(row))[0];
|
||||
}
|
||||
|
||||
/** @returns {BAttachment[]} */
|
||||
getAttachmentByRole(role) {
|
||||
return sql.getRows(`
|
||||
SELECT attachments.*
|
||||
FROM attachments
|
||||
WHERE parentId = ?
|
||||
AND role = ?
|
||||
AND isDeleted = 0
|
||||
ORDER BY position`, [this.noteId, role])
|
||||
.map(row => new BAttachment(row));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1203,13 +1174,10 @@ class BNote extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
const parentNotes = this.getParentNotes();
|
||||
let notePaths = [];
|
||||
|
||||
if (parentNotes.length === 1) { // optimization for most common case
|
||||
notePaths = parentNotes[0].getAllNotePaths();
|
||||
} else {
|
||||
notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
|
||||
}
|
||||
const notePaths = parentNotes.length === 1
|
||||
? parentNotes[0].getAllNotePaths() // optimization for most common case
|
||||
: parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
|
||||
|
||||
for (const notePath of notePaths) {
|
||||
notePath.push(this.noteId);
|
||||
@@ -1356,10 +1324,10 @@ class BNote extends AbstractBeccaEntity {
|
||||
* @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]
|
||||
* @param {int|null} [position]
|
||||
* @returns {BAttribute}
|
||||
*/
|
||||
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
|
||||
addAttribute(type, name, value = "", isInheritable = false, position = null) {
|
||||
const BAttribute = require("./battribute");
|
||||
|
||||
return new BAttribute({
|
||||
@@ -1486,13 +1454,84 @@ class BNote extends AbstractBeccaEntity {
|
||||
return cloningService.cloneNoteToBranch(this.noteId, branch.branchId);
|
||||
}
|
||||
|
||||
isEligibleForConversionToAttachment() {
|
||||
if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink');
|
||||
|
||||
if (targetRelations.length > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parentNote = this.getParentNotes()[0]; // at this point note can have only one parent
|
||||
const referencingNote = targetRelations[0]?.getNote();
|
||||
|
||||
if (referencingNote && parentNote !== referencingNote) {
|
||||
return false;
|
||||
} else if (parentNote.type !== 'text' || !parentNote.isContentAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some notes are eligible for conversion into an attachment of its parent, note must have these properties:
|
||||
* - it has exactly one target relation
|
||||
* - it has a relation from its parent note
|
||||
* - it has no children
|
||||
* - it has no clones
|
||||
* - parent is of type text
|
||||
* - both notes are either unprotected or user is in protected session
|
||||
*
|
||||
* Currently, works only for image notes.
|
||||
*
|
||||
* In future this functionality might get more generic and some of the requirements relaxed.
|
||||
*
|
||||
* @params {Object} [opts]
|
||||
* @params {bolean} [opts.force=false} it is envisioned that user can force the conversion even if some conditions
|
||||
* are not satisfied (e.g. relation to parent doesn't exist).
|
||||
*
|
||||
* @returns {BAttachment|null} - null if note is not eligible for conversion
|
||||
*/
|
||||
convertToParentAttachment(opts = {force: false}) {
|
||||
if (!this.isEligibleForConversionToAttachment()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = this.getContent();
|
||||
|
||||
const parentNote = this.getParentNotes()[0];
|
||||
const attachment = parentNote.saveAttachment({
|
||||
role: 'image',
|
||||
mime: this.mime,
|
||||
title: this.title,
|
||||
content: content
|
||||
});
|
||||
|
||||
let parentContent = parentNote.getContent();
|
||||
|
||||
const oldNoteUrl = `api/images/${this.noteId}/`;
|
||||
const newAttachmentUrl = `api/attachments/${attachment.attachmentId}/image/`;
|
||||
|
||||
const fixedContent = utils.replaceAll(parentContent, oldNoteUrl, newAttachmentUrl);
|
||||
|
||||
parentNote.setContent(fixedContent);
|
||||
|
||||
this.deleteNote();
|
||||
|
||||
return attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* (Soft) delete a note and all its descendants.
|
||||
*
|
||||
* @param {string} [deleteId] - optional delete identified
|
||||
* @param {string} [deleteId=null] - optional delete identified
|
||||
* @param {TaskContext} [taskContext]
|
||||
*/
|
||||
deleteNote(deleteId, taskContext) {
|
||||
deleteNote(deleteId = null, taskContext = null) {
|
||||
if (this.isDeleted) {
|
||||
return;
|
||||
}
|
||||
@@ -1519,7 +1558,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
|
||||
try {
|
||||
this.title = protectedSessionService.decryptString(this.title);
|
||||
this.flatTextCache = null;
|
||||
this.__flatTextCache = null;
|
||||
|
||||
this.isDecrypted = true;
|
||||
}
|
||||
@@ -1538,42 +1577,87 @@ class BNote extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
get isDeleted() {
|
||||
// isBeingDeleted is relevant only in the transition period when the deletion process have begun, but not yet
|
||||
// finished (note is still in becca)
|
||||
return !(this.noteId in this.becca.notes) || this.isBeingDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {BNoteRevision|null}
|
||||
* @returns {BRevision|null}
|
||||
*/
|
||||
saveNoteRevision() {
|
||||
const content = this.getContent();
|
||||
saveRevision() {
|
||||
return sql.transactional(() => {
|
||||
let noteContent = this.getContent();
|
||||
const contentMetadata = this.getContentMetadata();
|
||||
|
||||
if (!content || (Buffer.isBuffer(content) && content.byteLength === 0)) {
|
||||
return null;
|
||||
const revision = new BRevision({
|
||||
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);
|
||||
|
||||
revision.save(); // to generate revisionId, which is then used to save attachments
|
||||
|
||||
if (this.type === 'text') {
|
||||
for (const noteAttachment of this.getAttachments()) {
|
||||
if (noteAttachment.utcDateScheduledForErasureSince) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const revisionAttachment = noteAttachment.copy();
|
||||
revisionAttachment.parentId = revision.revisionId;
|
||||
revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
|
||||
|
||||
// content is rewritten to point to the revision attachments
|
||||
noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`, `attachments/${revisionAttachment.attachmentId}`);
|
||||
}
|
||||
|
||||
revision.setContent(noteContent, {forceSave: true});
|
||||
}
|
||||
|
||||
return revision;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {BAttachment}
|
||||
*/
|
||||
saveAttachment({attachmentId, role, mime, title, content, position}) {
|
||||
let attachment;
|
||||
|
||||
if (attachmentId) {
|
||||
attachment = this.becca.getAttachmentOrThrow(attachmentId);
|
||||
} else {
|
||||
attachment = new BAttachment({
|
||||
parentId: this.noteId,
|
||||
title,
|
||||
role,
|
||||
mime,
|
||||
isProtected: this.isProtected,
|
||||
position
|
||||
});
|
||||
}
|
||||
|
||||
const contentMetadata = this.getContentMetadata();
|
||||
content = content || "";
|
||||
attachment.setContent(content, {forceSave: true});
|
||||
|
||||
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();
|
||||
return attachment;
|
||||
}
|
||||
|
||||
noteRevision.setContent(content);
|
||||
|
||||
return noteRevision;
|
||||
getFileName() {
|
||||
return utils.formatDownloadTitle(this.title, this.type, this.mime);
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
@@ -1592,6 +1676,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
isProtected: this.isProtected,
|
||||
type: this.type,
|
||||
mime: this.mime,
|
||||
blobId: this.blobId,
|
||||
isDeleted: false,
|
||||
dateCreated: this.dateCreated,
|
||||
dateModified: this.dateModified,
|
||||
@@ -1628,7 +1713,7 @@ module.exports = BNote;
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
Reference in New Issue
Block a user