mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 19:12:27 +08:00
client-ts: Port services/app/entities
This commit is contained in:
parent
047c3eea69
commit
8fb6b64fa9
@ -1,48 +1,61 @@
|
|||||||
|
import { Froca } from "../services/froca-interface.js";
|
||||||
|
|
||||||
|
export interface FAttachmentRow {
|
||||||
|
attachmentId: string;
|
||||||
|
ownerId: string;
|
||||||
|
role: string;
|
||||||
|
mime: string;
|
||||||
|
title: string;
|
||||||
|
dateModified: string;
|
||||||
|
utcDateModified: string;
|
||||||
|
utcDateScheduledForErasureSince: string;
|
||||||
|
contentLength: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attachment is a file directly tied into a note without
|
* Attachment is a file directly tied into a note without
|
||||||
* being a hidden child.
|
* being a hidden child.
|
||||||
*/
|
*/
|
||||||
class FAttachment {
|
class FAttachment {
|
||||||
constructor(froca, row) {
|
private froca: Froca;
|
||||||
|
attachmentId!: string;
|
||||||
|
private ownerId!: string;
|
||||||
|
role!: string;
|
||||||
|
private mime!: string;
|
||||||
|
private title!: string;
|
||||||
|
private dateModified!: string;
|
||||||
|
private utcDateModified!: string;
|
||||||
|
private utcDateScheduledForErasureSince!: string;
|
||||||
|
/**
|
||||||
|
* optionally added to the entity
|
||||||
|
*/
|
||||||
|
private contentLength!: number;
|
||||||
|
|
||||||
|
constructor(froca: Froca, row: FAttachmentRow) {
|
||||||
/** @type {Froca} */
|
/** @type {Froca} */
|
||||||
this.froca = froca;
|
this.froca = froca;
|
||||||
|
|
||||||
this.update(row);
|
this.update(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(row) {
|
update(row: FAttachmentRow) {
|
||||||
/** @type {string} */
|
|
||||||
this.attachmentId = row.attachmentId;
|
this.attachmentId = row.attachmentId;
|
||||||
/** @type {string} */
|
|
||||||
this.ownerId = row.ownerId;
|
this.ownerId = row.ownerId;
|
||||||
/** @type {string} */
|
|
||||||
this.role = row.role;
|
this.role = row.role;
|
||||||
/** @type {string} */
|
|
||||||
this.mime = row.mime;
|
this.mime = row.mime;
|
||||||
/** @type {string} */
|
|
||||||
this.title = row.title;
|
this.title = row.title;
|
||||||
/** @type {string} */
|
|
||||||
this.dateModified = row.dateModified;
|
this.dateModified = row.dateModified;
|
||||||
/** @type {string} */
|
|
||||||
this.utcDateModified = row.utcDateModified;
|
this.utcDateModified = row.utcDateModified;
|
||||||
/** @type {string} */
|
|
||||||
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
|
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
|
||||||
|
|
||||||
/**
|
|
||||||
* optionally added to the entity
|
|
||||||
* @type {int}
|
|
||||||
*/
|
|
||||||
this.contentLength = row.contentLength;
|
this.contentLength = row.contentLength;
|
||||||
|
|
||||||
this.froca.attachments[this.attachmentId] = this;
|
this.froca.attachments[this.attachmentId] = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FNote} */
|
|
||||||
getNote() {
|
getNote() {
|
||||||
return this.froca.notes[this.ownerId];
|
return this.froca.notes[this.ownerId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return {FBlob} */
|
|
||||||
async getBlob() {
|
async getBlob() {
|
||||||
return await this.froca.getBlob('attachments', this.attachmentId);
|
return await this.froca.getBlob('attachments', this.attachmentId);
|
||||||
}
|
}
|
@ -1,45 +1,56 @@
|
|||||||
|
import { Froca } from '../services/froca-interface.js';
|
||||||
import promotedAttributeDefinitionParser from '../services/promoted_attribute_definition_parser.js';
|
import promotedAttributeDefinitionParser from '../services/promoted_attribute_definition_parser.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There are currently only two types of attributes, labels or relations.
|
* There are currently only two types of attributes, labels or relations.
|
||||||
* @typedef {"label" | "relation"} AttributeType
|
|
||||||
*/
|
*/
|
||||||
|
export type AttributeType = "label" | "relation";
|
||||||
|
|
||||||
|
export interface FAttributeRow {
|
||||||
|
attributeId: string;
|
||||||
|
noteId: string;
|
||||||
|
type: AttributeType;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
position: number;
|
||||||
|
isInheritable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute is an abstract concept which has two real uses - label (key - value pair)
|
* Attribute is an abstract concept which has two real uses - label (key - value pair)
|
||||||
* and relation (representing named relationship between source and target note)
|
* and relation (representing named relationship between source and target note)
|
||||||
*/
|
*/
|
||||||
class FAttribute {
|
class FAttribute {
|
||||||
constructor(froca, row) {
|
private froca: Froca;
|
||||||
/** @type {Froca} */
|
attributeId!: string;
|
||||||
|
noteId!: string;
|
||||||
|
type!: AttributeType;
|
||||||
|
name!: string;
|
||||||
|
value!: string;
|
||||||
|
position!: number;
|
||||||
|
isInheritable!: boolean;
|
||||||
|
|
||||||
|
constructor(froca: Froca, row: FAttributeRow) {
|
||||||
this.froca = froca;
|
this.froca = froca;
|
||||||
|
|
||||||
this.update(row);
|
this.update(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(row) {
|
update(row: FAttributeRow) {
|
||||||
/** @type {string} */
|
|
||||||
this.attributeId = row.attributeId;
|
this.attributeId = row.attributeId;
|
||||||
/** @type {string} */
|
|
||||||
this.noteId = row.noteId;
|
this.noteId = row.noteId;
|
||||||
/** @type {AttributeType} */
|
|
||||||
this.type = row.type;
|
this.type = row.type;
|
||||||
/** @type {string} */
|
|
||||||
this.name = row.name;
|
this.name = row.name;
|
||||||
/** @type {string} */
|
|
||||||
this.value = row.value;
|
this.value = row.value;
|
||||||
/** @type {int} */
|
|
||||||
this.position = row.position;
|
this.position = row.position;
|
||||||
/** @type {boolean} */
|
|
||||||
this.isInheritable = !!row.isInheritable;
|
this.isInheritable = !!row.isInheritable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FNote} */
|
|
||||||
getNote() {
|
getNote() {
|
||||||
return this.froca.notes[this.noteId];
|
return this.froca.notes[this.noteId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FNote>} */
|
|
||||||
async getTargetNote() {
|
async getTargetNote() {
|
||||||
const targetNoteId = this.targetNoteId;
|
const targetNoteId = this.targetNoteId;
|
||||||
|
|
||||||
@ -70,12 +81,12 @@ class FAttribute {
|
|||||||
return promotedAttributeDefinitionParser.parse(this.value);
|
return promotedAttributeDefinitionParser.parse(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefinitionFor(attr) {
|
isDefinitionFor(attr: FAttribute) {
|
||||||
return this.type === 'label' && this.name === `${attr.type}:${attr.name}`;
|
return this.type === 'label' && this.name === `${attr.type}:${attr.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get dto() {
|
get dto(): Omit<FAttribute, "froca"> {
|
||||||
const dto = Object.assign({}, this);
|
const dto: any = Object.assign({}, this);
|
||||||
delete dto.froca;
|
delete dto.froca;
|
||||||
|
|
||||||
return dto;
|
return dto;
|
@ -1,51 +1,65 @@
|
|||||||
|
import { Froca } from "../services/froca-interface.js";
|
||||||
|
|
||||||
|
export interface FBranchRow {
|
||||||
|
branchId: string;
|
||||||
|
noteId: string;
|
||||||
|
parentNoteId: string;
|
||||||
|
notePosition: number;
|
||||||
|
prefix?: string;
|
||||||
|
isExpanded?: boolean;
|
||||||
|
fromSearchNote: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple
|
* Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple
|
||||||
* parents.
|
* parents.
|
||||||
*/
|
*/
|
||||||
class FBranch {
|
class FBranch {
|
||||||
constructor(froca, row) {
|
private froca: Froca;
|
||||||
/** @type {Froca} */
|
|
||||||
|
/**
|
||||||
|
* primary key
|
||||||
|
*/
|
||||||
|
branchId!: string;
|
||||||
|
noteId!: string;
|
||||||
|
parentNoteId!: string;
|
||||||
|
notePosition!: number;
|
||||||
|
prefix?: string;
|
||||||
|
isExpanded?: boolean;
|
||||||
|
fromSearchNote!: boolean;
|
||||||
|
|
||||||
|
constructor(froca: Froca, row: FBranchRow) {
|
||||||
this.froca = froca;
|
this.froca = froca;
|
||||||
|
|
||||||
this.update(row);
|
this.update(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(row) {
|
update(row: FBranchRow) {
|
||||||
/**
|
/**
|
||||||
* primary key
|
* primary key
|
||||||
* @type {string}
|
|
||||||
*/
|
*/
|
||||||
this.branchId = row.branchId;
|
this.branchId = row.branchId;
|
||||||
/** @type {string} */
|
|
||||||
this.noteId = row.noteId;
|
this.noteId = row.noteId;
|
||||||
/** @type {string} */
|
|
||||||
this.parentNoteId = row.parentNoteId;
|
this.parentNoteId = row.parentNoteId;
|
||||||
/** @type {int} */
|
|
||||||
this.notePosition = row.notePosition;
|
this.notePosition = row.notePosition;
|
||||||
/** @type {string} */
|
|
||||||
this.prefix = row.prefix;
|
this.prefix = row.prefix;
|
||||||
/** @type {boolean} */
|
|
||||||
this.isExpanded = !!row.isExpanded;
|
this.isExpanded = !!row.isExpanded;
|
||||||
/** @type {boolean} */
|
|
||||||
this.fromSearchNote = !!row.fromSearchNote;
|
this.fromSearchNote = !!row.fromSearchNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FNote} */
|
|
||||||
async getNote() {
|
async getNote() {
|
||||||
return this.froca.getNote(this.noteId);
|
return this.froca.getNote(this.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FNote} */
|
|
||||||
getNoteFromCache() {
|
getNoteFromCache() {
|
||||||
return this.froca.getNoteFromCache(this.noteId);
|
return this.froca.getNoteFromCache(this.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FNote} */
|
|
||||||
async getParentNote() {
|
async getParentNote() {
|
||||||
return this.froca.getNote(this.parentNoteId);
|
return this.froca.getNote(this.parentNoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {boolean} true if it's top level, meaning its parent is the root note */
|
/** @returns true if it's top level, meaning its parent is the root note */
|
||||||
isTopLevel() {
|
isTopLevel() {
|
||||||
return this.parentNoteId === 'root';
|
return this.parentNoteId === 'root';
|
||||||
}
|
}
|
||||||
@ -54,8 +68,8 @@ class FBranch {
|
|||||||
return `FBranch(branchId=${this.branchId})`;
|
return `FBranch(branchId=${this.branchId})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get pojo() {
|
get pojo(): Omit<FBranch, "froca"> {
|
||||||
const pojo = {...this};
|
const pojo = {...this} as any;
|
||||||
delete pojo.froca;
|
delete pojo.froca;
|
||||||
return pojo;
|
return pojo;
|
||||||
}
|
}
|
@ -4,6 +4,9 @@ import ws from "../services/ws.js";
|
|||||||
import froca from "../services/froca.js";
|
import froca from "../services/froca.js";
|
||||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||||
import cssClassManager from "../services/css_class_manager.js";
|
import cssClassManager from "../services/css_class_manager.js";
|
||||||
|
import { Froca } from '../services/froca-interface.js';
|
||||||
|
import FAttachment from './fattachment.js';
|
||||||
|
import FAttribute, { AttributeType } from './fattribute.js';
|
||||||
|
|
||||||
const LABEL = 'label';
|
const LABEL = 'label';
|
||||||
const RELATION = 'relation';
|
const RELATION = 'relation';
|
||||||
@ -29,76 +32,91 @@ const NOTE_TYPE_ICONS = {
|
|||||||
* There are many different Note types, some of which are entirely opaque to the
|
* There are many different Note types, some of which are entirely opaque to the
|
||||||
* end user. Those types should be used only for checking against, they are
|
* end user. Those types should be used only for checking against, they are
|
||||||
* not for direct use.
|
* not for direct use.
|
||||||
* @typedef {"file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code"} NoteType
|
|
||||||
*/
|
*/
|
||||||
|
type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code";
|
||||||
|
|
||||||
/**
|
interface NotePathRecord {
|
||||||
* @typedef {Object} NotePathRecord
|
isArchived: boolean;
|
||||||
* @property {boolean} isArchived
|
isInHoistedSubTree: boolean;
|
||||||
* @property {boolean} isInHoistedSubTree
|
isSearch: boolean;
|
||||||
* @property {boolean} isSearch
|
notePath: string[];
|
||||||
* @property {Array<string>} notePath
|
isHidden: boolean;
|
||||||
* @property {boolean} isHidden
|
}
|
||||||
*/
|
|
||||||
|
export interface FNoteRow {
|
||||||
|
noteId: string;
|
||||||
|
title: string;
|
||||||
|
isProtected: boolean;
|
||||||
|
type: NoteType;
|
||||||
|
mime: string;
|
||||||
|
blobId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoteMetaData {
|
||||||
|
dateCreated: string;
|
||||||
|
utcDateCreated: string;
|
||||||
|
dateModified: string;
|
||||||
|
utcDateModified: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note is the main node and concept in Trilium.
|
* Note is the main node and concept in Trilium.
|
||||||
*/
|
*/
|
||||||
class FNote {
|
class FNote {
|
||||||
|
|
||||||
|
private froca: Froca;
|
||||||
|
|
||||||
|
noteId!: string;
|
||||||
|
title!: string;
|
||||||
|
isProtected!: boolean;
|
||||||
|
type!: NoteType;
|
||||||
/**
|
/**
|
||||||
* @param {Froca} froca
|
* content-type, e.g. "application/json"
|
||||||
* @param {Object.<string, Object>} row
|
|
||||||
*/
|
*/
|
||||||
constructor(froca, row) {
|
mime!: string;
|
||||||
/** @type {Froca} */
|
// the main use case to keep this is to detect content change which should trigger refresh
|
||||||
|
blobId!: string;
|
||||||
|
|
||||||
|
attributes: string[];
|
||||||
|
targetRelations: string[];
|
||||||
|
parents: string[];
|
||||||
|
children: string[];
|
||||||
|
|
||||||
|
parentToBranch: Record<string, string>;
|
||||||
|
childToBranch: Record<string, string>;
|
||||||
|
attachments: FAttachment[] | null;
|
||||||
|
|
||||||
|
// Managed by Froca.
|
||||||
|
searchResultsLoaded?: boolean;
|
||||||
|
highlightedTokens?: unknown;
|
||||||
|
|
||||||
|
constructor(froca: Froca, row: FNoteRow) {
|
||||||
this.froca = froca;
|
this.froca = froca;
|
||||||
|
|
||||||
/** @type {string[]} */
|
|
||||||
this.attributes = [];
|
this.attributes = [];
|
||||||
|
|
||||||
/** @type {string[]} */
|
|
||||||
this.targetRelations = [];
|
this.targetRelations = [];
|
||||||
|
|
||||||
/** @type {string[]} */
|
|
||||||
this.parents = [];
|
this.parents = [];
|
||||||
/** @type {string[]} */
|
|
||||||
this.children = [];
|
this.children = [];
|
||||||
|
|
||||||
/** @type {Object.<string, string>} */
|
|
||||||
this.parentToBranch = {};
|
this.parentToBranch = {};
|
||||||
|
|
||||||
/** @type {Object.<string, string>} */
|
|
||||||
this.childToBranch = {};
|
this.childToBranch = {};
|
||||||
|
|
||||||
/** @type {FAttachment[]|null} */
|
|
||||||
this.attachments = null; // lazy loaded
|
this.attachments = null; // lazy loaded
|
||||||
|
|
||||||
this.update(row);
|
this.update(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(row) {
|
update(row: FNoteRow) {
|
||||||
/** @type {string} */
|
|
||||||
this.noteId = row.noteId;
|
this.noteId = row.noteId;
|
||||||
/** @type {string} */
|
|
||||||
this.title = row.title;
|
this.title = row.title;
|
||||||
/** @type {boolean} */
|
|
||||||
this.isProtected = !!row.isProtected;
|
this.isProtected = !!row.isProtected;
|
||||||
/**
|
|
||||||
* See {@see NoteType} for info on values.
|
|
||||||
* @type {NoteType}
|
|
||||||
*/
|
|
||||||
this.type = row.type;
|
this.type = row.type;
|
||||||
/**
|
|
||||||
* content-type, e.g. "application/json"
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.mime = row.mime;
|
this.mime = row.mime;
|
||||||
|
|
||||||
// the main use case to keep this is to detect content change which should trigger refresh
|
|
||||||
this.blobId = row.blobId;
|
this.blobId = row.blobId;
|
||||||
}
|
}
|
||||||
|
|
||||||
addParent(parentNoteId, branchId, sort = true) {
|
addParent(parentNoteId: string, branchId: string, sort = true) {
|
||||||
if (parentNoteId === 'none') {
|
if (parentNoteId === 'none') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -114,7 +132,7 @@ class FNote {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addChild(childNoteId, branchId, sort = true) {
|
addChild(childNoteId: string, branchId: string, sort = true) {
|
||||||
if (!(childNoteId in this.childToBranch)) {
|
if (!(childNoteId in this.childToBranch)) {
|
||||||
this.children.push(childNoteId);
|
this.children.push(childNoteId);
|
||||||
}
|
}
|
||||||
@ -127,16 +145,18 @@ class FNote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sortChildren() {
|
sortChildren() {
|
||||||
const branchIdPos = {};
|
const branchIdPos: Record<string, number> = {};
|
||||||
|
|
||||||
for (const branchId of Object.values(this.childToBranch)) {
|
for (const branchId of Object.values(this.childToBranch)) {
|
||||||
branchIdPos[branchId] = this.froca.getBranch(branchId).notePosition;
|
const notePosition = this.froca.getBranch(branchId)?.notePosition;
|
||||||
|
if (notePosition) {
|
||||||
|
branchIdPos[branchId] = notePosition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.children.sort((a, b) => branchIdPos[this.childToBranch[a]] - branchIdPos[this.childToBranch[b]]);
|
this.children.sort((a, b) => branchIdPos[this.childToBranch[a]] - branchIdPos[this.childToBranch[b]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {boolean} */
|
|
||||||
isJson() {
|
isJson() {
|
||||||
return this.mime === "application/json";
|
return this.mime === "application/json";
|
||||||
}
|
}
|
||||||
@ -150,34 +170,32 @@ class FNote {
|
|||||||
async getJsonContent() {
|
async getJsonContent() {
|
||||||
const content = await this.getContent();
|
const content = await this.getContent();
|
||||||
|
|
||||||
|
if (typeof content !== "string") {
|
||||||
|
console.log(`Unknown note content for '${this.noteId}'.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(content);
|
return JSON.parse(content);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e: any) {
|
||||||
console.log(`Cannot parse content of note '${this.noteId}': `, e.message);
|
console.log(`Cannot parse content of note '${this.noteId}': `, e.message);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
getParentBranchIds() {
|
getParentBranchIds() {
|
||||||
return Object.values(this.parentToBranch);
|
return Object.values(this.parentToBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string[]}
|
|
||||||
* @deprecated use getParentBranchIds() instead
|
* @deprecated use getParentBranchIds() instead
|
||||||
*/
|
*/
|
||||||
getBranchIds() {
|
getBranchIds() {
|
||||||
return this.getParentBranchIds();
|
return this.getParentBranchIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {FBranch[]}
|
|
||||||
*/
|
|
||||||
getParentBranches() {
|
getParentBranches() {
|
||||||
const branchIds = Object.values(this.parentToBranch);
|
const branchIds = Object.values(this.parentToBranch);
|
||||||
|
|
||||||
@ -185,19 +203,16 @@ class FNote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {FBranch[]}
|
|
||||||
* @deprecated use getParentBranches() instead
|
* @deprecated use getParentBranches() instead
|
||||||
*/
|
*/
|
||||||
getBranches() {
|
getBranches() {
|
||||||
return this.getParentBranches();
|
return this.getParentBranches();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {boolean} */
|
|
||||||
hasChildren() {
|
hasChildren() {
|
||||||
return this.children.length > 0;
|
return this.children.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FBranch[]} */
|
|
||||||
getChildBranches() {
|
getChildBranches() {
|
||||||
// don't use Object.values() to guarantee order
|
// don't use Object.values() to guarantee order
|
||||||
const branchIds = this.children.map(childNoteId => this.childToBranch[childNoteId]);
|
const branchIds = this.children.map(childNoteId => this.childToBranch[childNoteId]);
|
||||||
@ -205,12 +220,10 @@ class FNote {
|
|||||||
return this.froca.getBranches(branchIds);
|
return this.froca.getBranches(branchIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {string[]} */
|
|
||||||
getParentNoteIds() {
|
getParentNoteIds() {
|
||||||
return this.parents;
|
return this.parents;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FNote[]} */
|
|
||||||
getParentNotes() {
|
getParentNotes() {
|
||||||
return this.froca.getNotesFromCache(this.parents);
|
return this.froca.getNotesFromCache(this.parents);
|
||||||
}
|
}
|
||||||
@ -239,17 +252,14 @@ class FNote {
|
|||||||
return this.hasAttribute('label', 'archived');
|
return this.hasAttribute('label', 'archived');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {string[]} */
|
|
||||||
getChildNoteIds() {
|
getChildNoteIds() {
|
||||||
return this.children;
|
return this.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FNote[]>} */
|
|
||||||
async getChildNotes() {
|
async getChildNotes() {
|
||||||
return await this.froca.getNotes(this.children);
|
return await this.froca.getNotes(this.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FAttachment[]>} */
|
|
||||||
async getAttachments() {
|
async getAttachments() {
|
||||||
if (!this.attachments) {
|
if (!this.attachments) {
|
||||||
this.attachments = await this.froca.getAttachmentsForNote(this.noteId);
|
this.attachments = await this.froca.getAttachmentsForNote(this.noteId);
|
||||||
@ -258,14 +268,12 @@ class FNote {
|
|||||||
return this.attachments;
|
return this.attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FAttachment[]>} */
|
async getAttachmentsByRole(role: string) {
|
||||||
async getAttachmentsByRole(role) {
|
|
||||||
return (await this.getAttachments())
|
return (await this.getAttachments())
|
||||||
.filter(attachment => attachment.role === role);
|
.filter(attachment => attachment.role === role);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FAttachment>} */
|
async getAttachmentById(attachmentId: string) {
|
||||||
async getAttachmentById(attachmentId) {
|
|
||||||
const attachments = await this.getAttachments();
|
const attachments = await this.getAttachments();
|
||||||
|
|
||||||
return attachments.find(att => att.attachmentId === attachmentId);
|
return attachments.find(att => att.attachmentId === attachmentId);
|
||||||
@ -295,11 +303,11 @@ class FNote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} [type] - (optional) attribute type to filter
|
* @param [type] - attribute type to filter
|
||||||
* @param {string} [name] - (optional) attribute name to filter
|
* @param [name] - attribute name to filter
|
||||||
* @returns {FAttribute[]} all note's attributes, including inherited ones
|
* @returns all note's attributes, including inherited ones
|
||||||
*/
|
*/
|
||||||
getOwnedAttributes(type, name) {
|
getOwnedAttributes(type?: AttributeType, name?: string) {
|
||||||
const attrs = this.attributes
|
const attrs = this.attributes
|
||||||
.map(attributeId => this.froca.attributes[attributeId])
|
.map(attributeId => this.froca.attributes[attributeId])
|
||||||
.filter(Boolean); // filter out nulls;
|
.filter(Boolean); // filter out nulls;
|
||||||
@ -308,20 +316,18 @@ class FNote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} [type] - (optional) attribute type to filter
|
* @param [type] - attribute type to filter
|
||||||
* @param {string} [name] - (optional) attribute name to filter
|
* @param [name] - attribute name to filter
|
||||||
* @returns {FAttribute[]} all note's attributes, including inherited ones
|
* @returns all note's attributes, including inherited ones
|
||||||
*/
|
*/
|
||||||
getAttributes(type, name) {
|
getAttributes(type?: AttributeType, name?: string) {
|
||||||
return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
|
return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string[]} path
|
|
||||||
* @return {FAttribute[]}
|
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
__getCachedAttributes(path) {
|
__getCachedAttributes(path: string[]): FAttribute[] {
|
||||||
// notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
|
// notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
|
||||||
// when template instance is a parent of template itself
|
// when template instance is a parent of template itself
|
||||||
if (path.includes(this.noteId)) {
|
if (path.includes(this.noteId)) {
|
||||||
@ -376,9 +382,9 @@ class FNote {
|
|||||||
/**
|
/**
|
||||||
* Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles)
|
* Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles)
|
||||||
*
|
*
|
||||||
* @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path)
|
* @returns array of notePaths (each represented by array of noteIds constituting the particular note path)
|
||||||
*/
|
*/
|
||||||
getAllNotePaths() {
|
getAllNotePaths(): string[][] {
|
||||||
if (this.noteId === 'root') {
|
if (this.noteId === 'root') {
|
||||||
return [['root']];
|
return [['root']];
|
||||||
}
|
}
|
||||||
@ -396,10 +402,6 @@ class FNote {
|
|||||||
return notePaths;
|
return notePaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} [hoistedNoteId='root']
|
|
||||||
* @return {Array<NotePathRecord>}
|
|
||||||
*/
|
|
||||||
getSortedNotePathRecords(hoistedNoteId = 'root') {
|
getSortedNotePathRecords(hoistedNoteId = 'root') {
|
||||||
const isHoistedRoot = hoistedNoteId === 'root';
|
const isHoistedRoot = hoistedNoteId === 'root';
|
||||||
|
|
||||||
@ -475,14 +477,10 @@ class FNote {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {FAttribute[]} attributes
|
|
||||||
* @param {AttributeType} type
|
|
||||||
* @param {string} name
|
|
||||||
* @return {FAttribute[]}
|
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
__filterAttrs(attributes, type, name) {
|
__filterAttrs(attributes: FAttribute[], type?: AttributeType, name?: string): FAttribute[] {
|
||||||
this.__validateTypeName(type, name);
|
this.__validateTypeName(type, name);
|
||||||
|
|
||||||
if (!type && !name) {
|
if (!type && !name) {
|
||||||
@ -494,15 +492,17 @@ class FNote {
|
|||||||
} else if (name) {
|
} else if (name) {
|
||||||
return attributes.filter(attr => attr.name === name);
|
return attributes.filter(attr => attr.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
__getInheritableAttributes(path) {
|
__getInheritableAttributes(path: string[]) {
|
||||||
const attrs = this.__getCachedAttributes(path);
|
const attrs = this.__getCachedAttributes(path);
|
||||||
|
|
||||||
return attrs.filter(attr => attr.isInheritable);
|
return attrs.filter(attr => attr.isInheritable);
|
||||||
}
|
}
|
||||||
|
|
||||||
__validateTypeName(type, name) {
|
__validateTypeName(type?: string, name?: string) {
|
||||||
if (type && type !== 'label' && type !== 'relation') {
|
if (type && type !== 'label' && type !== 'relation') {
|
||||||
throw new Error(`Unrecognized attribute type '${type}'. Only 'label' and 'relation' are possible values.`);
|
throw new Error(`Unrecognized attribute type '${type}'. Only 'label' and 'relation' are possible values.`);
|
||||||
}
|
}
|
||||||
@ -516,18 +516,18 @@ class FNote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} [name] - label name to filter
|
* @param [name] - label name to filter
|
||||||
* @returns {FAttribute[]} all note's labels (attributes with type label), including inherited ones
|
* @returns all note's labels (attributes with type label), including inherited ones
|
||||||
*/
|
*/
|
||||||
getOwnedLabels(name) {
|
getOwnedLabels(name: string) {
|
||||||
return this.getOwnedAttributes(LABEL, name);
|
return this.getOwnedAttributes(LABEL, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} [name] - label name to filter
|
* @param [name] - label name to filter
|
||||||
* @returns {FAttribute[]} all note's labels (attributes with type label), including inherited ones
|
* @returns all note's labels (attributes with type label), including inherited ones
|
||||||
*/
|
*/
|
||||||
getLabels(name) {
|
getLabels(name: string) {
|
||||||
return this.getAttributes(LABEL, name);
|
return this.getAttributes(LABEL, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,7 +535,7 @@ class FNote {
|
|||||||
const iconClassLabels = this.getLabels('iconClass');
|
const iconClassLabels = this.getLabels('iconClass');
|
||||||
const workspaceIconClass = this.getWorkspaceIconClass();
|
const workspaceIconClass = this.getWorkspaceIconClass();
|
||||||
|
|
||||||
if (iconClassLabels.length > 0) {
|
if (iconClassLabels && iconClassLabels.length > 0) {
|
||||||
return iconClassLabels[0].value;
|
return iconClassLabels[0].value;
|
||||||
}
|
}
|
||||||
else if (workspaceIconClass) {
|
else if (workspaceIconClass) {
|
||||||
@ -578,7 +578,7 @@ class FNote {
|
|||||||
|
|
||||||
if (!childBranches) {
|
if (!childBranches) {
|
||||||
ws.logError(`No children for '${this.noteId}'. This shouldn't happen.`);
|
ws.logError(`No children for '${this.noteId}'. This shouldn't happen.`);
|
||||||
return;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// we're not checking hideArchivedNotes since that would mean we need to lazy load the child notes
|
// we're not checking hideArchivedNotes since that would mean we need to lazy load the child notes
|
||||||
@ -590,102 +590,104 @@ class FNote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} [name] - relation name to filter
|
* @param [name] - relation name to filter
|
||||||
* @returns {FAttribute[]} all note's relations (attributes with type relation), including inherited ones
|
* @returns all note's relations (attributes with type relation), including inherited ones
|
||||||
*/
|
*/
|
||||||
getOwnedRelations(name) {
|
getOwnedRelations(name: string) {
|
||||||
return this.getOwnedAttributes(RELATION, name);
|
return this.getOwnedAttributes(RELATION, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} [name] - relation name to filter
|
* @param [name] - relation name to filter
|
||||||
* @returns {FAttribute[]} all note's relations (attributes with type relation), including inherited ones
|
* @returns all note's relations (attributes with type relation), including inherited ones
|
||||||
*/
|
*/
|
||||||
getRelations(name) {
|
getRelations(name: string) {
|
||||||
return this.getAttributes(RELATION, name);
|
return this.getAttributes(RELATION, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
* @param type - attribute type (label, relation, etc.)
|
||||||
* @param {string} name - attribute name
|
* @param name - attribute name
|
||||||
* @returns {boolean} true if note has an attribute with given type and name (including inherited)
|
* @returns true if note has an attribute with given type and name (including inherited)
|
||||||
*/
|
*/
|
||||||
hasAttribute(type, name) {
|
hasAttribute(type: AttributeType, name: string) {
|
||||||
const attributes = this.getAttributes();
|
const attributes = this.getAttributes();
|
||||||
|
|
||||||
return attributes.some(attr => attr.name === name && attr.type === type);
|
return attributes.some(attr => attr.name === name && attr.type === type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
* @param type - attribute type (label, relation, etc.)
|
||||||
* @param {string} name - attribute name
|
* @param name - attribute name
|
||||||
* @returns {boolean} true if note has an attribute with given type and name (including inherited)
|
* @returns true if note has an attribute with given type and name (including inherited)
|
||||||
*/
|
*/
|
||||||
hasOwnedAttribute(type, name) {
|
hasOwnedAttribute(type: AttributeType, name: string) {
|
||||||
return !!this.getOwnedAttribute(type, name);
|
return !!this.getOwnedAttribute(type, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
* @param type - attribute type (label, relation, etc.)
|
||||||
* @param {string} name - attribute name
|
* @param name - attribute name
|
||||||
* @returns {FAttribute} attribute of the given type and name. If there are more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
|
* @returns attribute of the given type and name. If there are more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
|
||||||
*/
|
*/
|
||||||
getOwnedAttribute(type, name) {
|
getOwnedAttribute(type: AttributeType, name: string) {
|
||||||
const attributes = this.getOwnedAttributes();
|
const attributes = this.getOwnedAttributes();
|
||||||
|
|
||||||
return attributes.find(attr => attr.name === name && attr.type === type);
|
return attributes.find(attr => attr.name === name && attr.type === type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
* @param type - attribute type (label, relation, etc.)
|
||||||
* @param {string} name - attribute name
|
* @param name - attribute name
|
||||||
* @returns {FAttribute} attribute of the given type and name. If there are more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
|
* @returns attribute of the given type and name. If there are more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
|
||||||
*/
|
*/
|
||||||
getAttribute(type, name) {
|
getAttribute(type: AttributeType, name: string) {
|
||||||
const attributes = this.getAttributes();
|
const attributes = this.getAttributes();
|
||||||
|
|
||||||
return attributes.find(attr => attr.name === name && attr.type === type);
|
return attributes.find(attr => attr.name === name && attr.type === type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
* @param type - attribute type (label, relation, etc.)
|
||||||
* @param {string} name - attribute name
|
* @param name - attribute name
|
||||||
* @returns {string} attribute value of the given type and name or null if no such attribute exists.
|
* @returns attribute value of the given type and name or null if no such attribute exists.
|
||||||
*/
|
*/
|
||||||
getOwnedAttributeValue(type, name) {
|
getOwnedAttributeValue(type: AttributeType, name: string) {
|
||||||
const attr = this.getOwnedAttribute(type, name);
|
const attr = this.getOwnedAttribute(type, name);
|
||||||
|
|
||||||
return attr ? attr.value : null;
|
return attr ? attr.value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
* @param type - attribute type (label, relation, etc.)
|
||||||
* @param {string} name - attribute name
|
* @param name - attribute name
|
||||||
* @returns {string} attribute value of the given type and name or null if no such attribute exists.
|
* @returns attribute value of the given type and name or null if no such attribute exists.
|
||||||
*/
|
*/
|
||||||
getAttributeValue(type, name) {
|
getAttributeValue(type: AttributeType, name: string) {
|
||||||
const attr = this.getAttribute(type, name);
|
const attr = this.getAttribute(type, name);
|
||||||
|
|
||||||
return attr ? attr.value : null;
|
return attr ? attr.value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - label name
|
* @param name - label name
|
||||||
* @returns {boolean} true if label exists (excluding inherited)
|
* @returns true if label exists (excluding inherited)
|
||||||
*/
|
*/
|
||||||
hasOwnedLabel(name) { return this.hasOwnedAttribute(LABEL, name); }
|
hasOwnedLabel(name: string) {
|
||||||
|
return this.hasOwnedAttribute(LABEL, name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - label name
|
* @param name - label name
|
||||||
* @returns {boolean} true if label exists (including inherited)
|
* @returns true if label exists (including inherited)
|
||||||
*/
|
*/
|
||||||
hasLabel(name) { return this.hasAttribute(LABEL, name); }
|
hasLabel(name: string) { return this.hasAttribute(LABEL, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - label name
|
* @param name - label name
|
||||||
* @returns {boolean} true if label exists (including inherited) and does not have "false" value.
|
* @returns true if label exists (including inherited) and does not have "false" value.
|
||||||
*/
|
*/
|
||||||
isLabelTruthy(name) {
|
isLabelTruthy(name: string) {
|
||||||
const label = this.getLabel(name);
|
const label = this.getLabel(name);
|
||||||
|
|
||||||
if (!label) {
|
if (!label) {
|
||||||
@ -696,80 +698,79 @@ class FNote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - relation name
|
* @param name - relation name
|
||||||
* @returns {boolean} true if relation exists (excluding inherited)
|
* @returns true if relation exists (excluding inherited)
|
||||||
*/
|
*/
|
||||||
hasOwnedRelation(name) { return this.hasOwnedAttribute(RELATION, name); }
|
hasOwnedRelation(name: string) { return this.hasOwnedAttribute(RELATION, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - relation name
|
* @param name - relation name
|
||||||
* @returns {boolean} true if relation exists (including inherited)
|
* @returns true if relation exists (including inherited)
|
||||||
*/
|
*/
|
||||||
hasRelation(name) { return this.hasAttribute(RELATION, name); }
|
hasRelation(name: string) { return this.hasAttribute(RELATION, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - label name
|
* @param name - label name
|
||||||
* @returns {FAttribute} label if it exists, null otherwise
|
* @returns label if it exists, null otherwise
|
||||||
*/
|
*/
|
||||||
getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); }
|
getOwnedLabel(name: string) { return this.getOwnedAttribute(LABEL, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - label name
|
* @param name - label name
|
||||||
* @returns {FAttribute} label if it exists, null otherwise
|
* @returns label if it exists, null otherwise
|
||||||
*/
|
*/
|
||||||
getLabel(name) { return this.getAttribute(LABEL, name); }
|
getLabel(name: string) { return this.getAttribute(LABEL, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - relation name
|
* @param name - relation name
|
||||||
* @returns {FAttribute} relation if it exists, null otherwise
|
* @returns relation if it exists, null otherwise
|
||||||
*/
|
*/
|
||||||
getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); }
|
getOwnedRelation(name: string) { return this.getOwnedAttribute(RELATION, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - relation name
|
* @param name - relation name
|
||||||
* @returns {FAttribute} relation if it exists, null otherwise
|
* @returns relation if it exists, null otherwise
|
||||||
*/
|
*/
|
||||||
getRelation(name) { return this.getAttribute(RELATION, name); }
|
getRelation(name: string) { return this.getAttribute(RELATION, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - label name
|
* @param name - label name
|
||||||
* @returns {string} label value if label exists, null otherwise
|
* @returns label value if label exists, null otherwise
|
||||||
*/
|
*/
|
||||||
getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); }
|
getOwnedLabelValue(name: string) { return this.getOwnedAttributeValue(LABEL, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - label name
|
* @param name - label name
|
||||||
* @returns {string} label value if label exists, null otherwise
|
* @returns label value if label exists, null otherwise
|
||||||
*/
|
*/
|
||||||
getLabelValue(name) { return this.getAttributeValue(LABEL, name); }
|
getLabelValue(name: string) { return this.getAttributeValue(LABEL, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - relation name
|
* @param name - relation name
|
||||||
* @returns {string} relation value if relation exists, null otherwise
|
* @returns relation value if relation exists, null otherwise
|
||||||
*/
|
*/
|
||||||
getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); }
|
getOwnedRelationValue(name: string) { return this.getOwnedAttributeValue(RELATION, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name - relation name
|
* @param name - relation name
|
||||||
* @returns {string} relation value if relation exists, null otherwise
|
* @returns relation value if relation exists, null otherwise
|
||||||
*/
|
*/
|
||||||
getRelationValue(name) { return this.getAttributeValue(RELATION, name); }
|
getRelationValue(name: string) { return this.getAttributeValue(RELATION, name); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param name
|
||||||
* @returns {Promise<FNote>|null} target note of the relation or null (if target is empty or note was not found)
|
* @returns target note of the relation or null (if target is empty or note was not found)
|
||||||
*/
|
*/
|
||||||
async getRelationTarget(name) {
|
async getRelationTarget(name: string) {
|
||||||
const targets = await this.getRelationTargets(name);
|
const targets = await this.getRelationTargets(name);
|
||||||
|
|
||||||
return targets.length > 0 ? targets[0] : null;
|
return targets.length > 0 ? targets[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} [name] - relation name to filter
|
* @param [name] - relation name to filter
|
||||||
* @returns {Promise<FNote[]>}
|
|
||||||
*/
|
*/
|
||||||
async getRelationTargets(name) {
|
async getRelationTargets(name: string) {
|
||||||
const relations = this.getRelations(name);
|
const relations = this.getRelations(name);
|
||||||
const targets = [];
|
const targets = [];
|
||||||
|
|
||||||
@ -780,9 +781,6 @@ class FNote {
|
|||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {FNote[]}
|
|
||||||
*/
|
|
||||||
getNotesToInheritAttributesFrom() {
|
getNotesToInheritAttributesFrom() {
|
||||||
const relations = [
|
const relations = [
|
||||||
...this.getRelations('template'),
|
...this.getRelations('template'),
|
||||||
@ -818,7 +816,7 @@ class FNote {
|
|||||||
return promotedAttrs;
|
return promotedAttrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAncestor(ancestorNoteId, followTemplates = false, visitedNoteIds = null) {
|
hasAncestor(ancestorNoteId: string, followTemplates = false, visitedNoteIds: Set<string> | null = null) {
|
||||||
if (this.noteId === ancestorNoteId) {
|
if (this.noteId === ancestorNoteId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -860,8 +858,6 @@ class FNote {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get relations which target this note
|
* Get relations which target this note
|
||||||
*
|
|
||||||
* @returns {FAttribute[]}
|
|
||||||
*/
|
*/
|
||||||
getTargetRelations() {
|
getTargetRelations() {
|
||||||
return this.targetRelations
|
return this.targetRelations
|
||||||
@ -870,8 +866,6 @@ class FNote {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get relations which target this note
|
* Get relations which target this note
|
||||||
*
|
|
||||||
* @returns {Promise<FNote[]>}
|
|
||||||
*/
|
*/
|
||||||
async getTargetRelationSourceNotes() {
|
async getTargetRelationSourceNotes() {
|
||||||
const targetRelations = this.getTargetRelations();
|
const targetRelations = this.getTargetRelations();
|
||||||
@ -881,13 +875,11 @@ class FNote {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use getBlob() instead
|
* @deprecated use getBlob() instead
|
||||||
* @return {Promise<FBlob>}
|
|
||||||
*/
|
*/
|
||||||
async getNoteComplement() {
|
async getNoteComplement() {
|
||||||
return this.getBlob();
|
return this.getBlob();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return {Promise<FBlob>} */
|
|
||||||
async getBlob() {
|
async getBlob() {
|
||||||
return await this.froca.getBlob('notes', this.noteId);
|
return await this.froca.getBlob('notes', this.noteId);
|
||||||
}
|
}
|
||||||
@ -896,8 +888,8 @@ class FNote {
|
|||||||
return `Note(noteId=${this.noteId}, title=${this.title})`;
|
return `Note(noteId=${this.noteId}, title=${this.title})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get dto() {
|
get dto(): Omit<FNote, "froca"> {
|
||||||
const dto = Object.assign({}, this);
|
const dto = Object.assign({}, this) as any;
|
||||||
delete dto.froca;
|
delete dto.froca;
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
@ -918,7 +910,7 @@ class FNote {
|
|||||||
return labels.length > 0 ? labels[0].value : "";
|
return labels.length > 0 ? labels[0].value : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {boolean} true if this note is JavaScript (code or file) */
|
/** @returns true if this note is JavaScript (code or file) */
|
||||||
isJavaScript() {
|
isJavaScript() {
|
||||||
return (this.type === "code" || this.type === "file" || this.type === 'launcher')
|
return (this.type === "code" || this.type === "file" || this.type === 'launcher')
|
||||||
&& (this.mime.startsWith("application/javascript")
|
&& (this.mime.startsWith("application/javascript")
|
||||||
@ -926,12 +918,12 @@ class FNote {
|
|||||||
|| this.mime === "text/javascript");
|
|| this.mime === "text/javascript");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {boolean} true if this note is HTML */
|
/** @returns true if this note is HTML */
|
||||||
isHtml() {
|
isHtml() {
|
||||||
return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
|
return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {string|null} JS script environment - either "frontend" or "backend" */
|
/** @returns JS script environment - either "frontend" or "backend" */
|
||||||
getScriptEnv() {
|
getScriptEnv() {
|
||||||
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) {
|
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) {
|
||||||
return "frontend";
|
return "frontend";
|
||||||
@ -958,11 +950,9 @@ class FNote {
|
|||||||
if (env === "frontend") {
|
if (env === "frontend") {
|
||||||
const bundleService = (await import("../services/bundle.js")).default;
|
const bundleService = (await import("../services/bundle.js")).default;
|
||||||
return await bundleService.getAndExecuteBundle(this.noteId);
|
return await bundleService.getAndExecuteBundle(this.noteId);
|
||||||
}
|
} else if (env === "backend") {
|
||||||
else if (env === "backend") {
|
await server.post(`script/run/${this.noteId}`);
|
||||||
const resp = await server.post(`script/run/${this.noteId}`);
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new Error(`Unrecognized env type ${env} for note ${this.noteId}`);
|
throw new Error(`Unrecognized env type ${env} for note ${this.noteId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1001,11 +991,9 @@ class FNote {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides note's date metadata.
|
* Provides note's date metadata.
|
||||||
*
|
|
||||||
* @returns {Promise<{dateCreated: string, utcDateCreated: string, dateModified: string, utcDateModified: string}>}
|
|
||||||
*/
|
*/
|
||||||
async getMetadata() {
|
async getMetadata() {
|
||||||
return await server.get(`notes/${this.noteId}/metadata`);
|
return await server.get<NoteMetaData>(`notes/${this.noteId}/metadata`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
const registeredClasses = new Set<string>();
|
const registeredClasses = new Set<string>();
|
||||||
|
|
||||||
function createClassForColor(color: string) {
|
function createClassForColor(color: string | null) {
|
||||||
if (!color?.trim()) {
|
if (!color?.trim()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
24
src/public/app/services/froca-interface.ts
Normal file
24
src/public/app/services/froca-interface.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import FAttachment from "../entities/fattachment.js";
|
||||||
|
import FAttribute from "../entities/fattribute.js";
|
||||||
|
import FBlob from "../entities/fblob.js";
|
||||||
|
import FBranch from "../entities/fbranch.js";
|
||||||
|
import FNote from "../entities/fnote.js";
|
||||||
|
|
||||||
|
export interface Froca {
|
||||||
|
notes: Record<string, FNote>;
|
||||||
|
branches: Record<string, FBranch>;
|
||||||
|
attributes: Record<string, FAttribute>;
|
||||||
|
attachments: Record<string, FAttachment>;
|
||||||
|
blobPromises: Record<string, Promise<void | FBlob> | null>;
|
||||||
|
|
||||||
|
getBlob(entityType: string, entityId: string): Promise<void | FBlob | null>;
|
||||||
|
getNote(noteId: string, silentNotFoundError?: boolean): Promise<FNote | null>;
|
||||||
|
getNoteFromCache(noteId: string): FNote;
|
||||||
|
getNotesFromCache(noteIds: string[], silentNotFoundError?: boolean): FNote[];
|
||||||
|
getNotes(noteIds: string[], silentNotFoundError?: boolean): Promise<FNote[]>;
|
||||||
|
|
||||||
|
getBranch(branchId: string, silentNotFoundError?: boolean): FBranch | undefined;
|
||||||
|
getBranches(branchIds: string[], silentNotFoundError?: boolean): FBranch[];
|
||||||
|
|
||||||
|
getAttachmentsForNote(noteId: string): Promise<FAttachment[]>;
|
||||||
|
}
|
@ -3,8 +3,22 @@ import FNote from "../entities/fnote.js";
|
|||||||
import FAttribute from "../entities/fattribute.js";
|
import FAttribute from "../entities/fattribute.js";
|
||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import FBlob from "../entities/fblob.js";
|
import FBlob, { FBlobRow } from "../entities/fblob.js";
|
||||||
import FAttachment from "../entities/fattachment.js";
|
import FAttachment, { FAttachmentRow } from "../entities/fattachment.js";
|
||||||
|
import { Froca } from "./froca-interface.js";
|
||||||
|
|
||||||
|
|
||||||
|
interface SubtreeResponse {
|
||||||
|
notes: FNoteRow[];
|
||||||
|
branches: FBranchRow[];
|
||||||
|
attributes: FAttributeRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchNoteResponse {
|
||||||
|
searchResultNoteIds: string[];
|
||||||
|
highlightedTokens: string[];
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Froca (FROntend CAche) keeps a read only cache of note tree structure in frontend's memory.
|
* Froca (FROntend CAche) keeps a read only cache of note tree structure in frontend's memory.
|
||||||
@ -16,48 +30,47 @@ import FAttachment from "../entities/fattachment.js";
|
|||||||
*
|
*
|
||||||
* Backend has a similar cache called Becca
|
* Backend has a similar cache called Becca
|
||||||
*/
|
*/
|
||||||
class Froca {
|
class FrocaImpl implements Froca {
|
||||||
|
private initializedPromise: Promise<void>;
|
||||||
|
|
||||||
|
notes!: Record<string, FNote>;
|
||||||
|
branches!: Record<string, FBranch>;
|
||||||
|
attributes!: Record<string, FAttribute>;
|
||||||
|
attachments!: Record<string, FAttachment>;
|
||||||
|
blobPromises!: Record<string, Promise<void | FBlob> | null>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initializedPromise = this.loadInitialTree();
|
this.initializedPromise = this.loadInitialTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadInitialTree() {
|
async loadInitialTree() {
|
||||||
const resp = await server.get('tree');
|
const resp = await server.get<SubtreeResponse>('tree');
|
||||||
|
|
||||||
// clear the cache only directly before adding new content which is important for e.g., switching to protected session
|
// clear the cache only directly before adding new content which is important for e.g., switching to protected session
|
||||||
|
|
||||||
/** @type {Object.<string, FNote>} */
|
|
||||||
this.notes = {};
|
this.notes = {};
|
||||||
|
|
||||||
/** @type {Object.<string, FBranch>} */
|
|
||||||
this.branches = {};
|
this.branches = {};
|
||||||
|
|
||||||
/** @type {Object.<string, FAttribute>} */
|
|
||||||
this.attributes = {};
|
this.attributes = {};
|
||||||
|
|
||||||
/** @type {Object.<string, FAttachment>} */
|
|
||||||
this.attachments = {};
|
this.attachments = {};
|
||||||
|
|
||||||
/** @type {Object.<string, Promise<FBlob>>} */
|
|
||||||
this.blobPromises = {};
|
this.blobPromises = {};
|
||||||
|
|
||||||
this.addResp(resp);
|
this.addResp(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadSubTree(subTreeNoteId) {
|
async loadSubTree(subTreeNoteId: string) {
|
||||||
const resp = await server.get(`tree?subTreeNoteId=${subTreeNoteId}`);
|
const resp = await server.get<SubtreeResponse>(`tree?subTreeNoteId=${subTreeNoteId}`);
|
||||||
|
|
||||||
this.addResp(resp);
|
this.addResp(resp);
|
||||||
|
|
||||||
return this.notes[subTreeNoteId];
|
return this.notes[subTreeNoteId];
|
||||||
}
|
}
|
||||||
|
|
||||||
addResp(resp) {
|
addResp(resp: SubtreeResponse) {
|
||||||
const noteRows = resp.notes;
|
const noteRows = resp.notes;
|
||||||
const branchRows = resp.branches;
|
const branchRows = resp.branches;
|
||||||
const attributeRows = resp.attributes;
|
const attributeRows = resp.attributes;
|
||||||
|
|
||||||
const noteIdsToSort = new Set();
|
const noteIdsToSort = new Set<string>();
|
||||||
|
|
||||||
for (const noteRow of noteRows) {
|
for (const noteRow of noteRows) {
|
||||||
const {noteId} = noteRow;
|
const {noteId} = noteRow;
|
||||||
@ -160,28 +173,28 @@ class Froca {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async reloadNotes(noteIds) {
|
async reloadNotes(noteIds: string[]) {
|
||||||
if (noteIds.length === 0) {
|
if (noteIds.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
noteIds = Array.from(new Set(noteIds)); // make noteIds unique
|
noteIds = Array.from(new Set(noteIds)); // make noteIds unique
|
||||||
|
|
||||||
const resp = await server.post('tree/load', { noteIds });
|
const resp = await server.post<SubtreeResponse>('tree/load', { noteIds });
|
||||||
|
|
||||||
this.addResp(resp);
|
this.addResp(resp);
|
||||||
|
|
||||||
appContext.triggerEvent('notesReloaded', {noteIds});
|
appContext.triggerEvent('notesReloaded', {noteIds});
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadSearchNote(noteId) {
|
async loadSearchNote(noteId: string) {
|
||||||
const note = await this.getNote(noteId);
|
const note = await this.getNote(noteId);
|
||||||
|
|
||||||
if (!note || note.type !== 'search') {
|
if (!note || note.type !== 'search') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {searchResultNoteIds, highlightedTokens, error} = await server.get(`search-note/${note.noteId}`);
|
const {searchResultNoteIds, highlightedTokens, error} = await server.get<SearchNoteResponse>(`search-note/${note.noteId}`);
|
||||||
|
|
||||||
if (!Array.isArray(searchResultNoteIds)) {
|
if (!Array.isArray(searchResultNoteIds)) {
|
||||||
throw new Error(`Search note '${note.noteId}' failed: ${searchResultNoteIds}`);
|
throw new Error(`Search note '${note.noteId}' failed: ${searchResultNoteIds}`);
|
||||||
@ -217,8 +230,7 @@ class Froca {
|
|||||||
return {error};
|
return {error};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FNote[]} */
|
getNotesFromCache(noteIds: string[], silentNotFoundError = false): FNote[] {
|
||||||
getNotesFromCache(noteIds, silentNotFoundError = false) {
|
|
||||||
return noteIds.map(noteId => {
|
return noteIds.map(noteId => {
|
||||||
if (!this.notes[noteId] && !silentNotFoundError) {
|
if (!this.notes[noteId] && !silentNotFoundError) {
|
||||||
console.trace(`Can't find note '${noteId}'`);
|
console.trace(`Can't find note '${noteId}'`);
|
||||||
@ -228,11 +240,10 @@ class Froca {
|
|||||||
else {
|
else {
|
||||||
return this.notes[noteId];
|
return this.notes[noteId];
|
||||||
}
|
}
|
||||||
}).filter(note => !!note);
|
}).filter(note => !!note) as FNote[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FNote[]>} */
|
async getNotes(noteIds: string[], silentNotFoundError = false): Promise<FNote[]> {
|
||||||
async getNotes(noteIds, silentNotFoundError = false) {
|
|
||||||
noteIds = Array.from(new Set(noteIds)); // make unique
|
noteIds = Array.from(new Set(noteIds)); // make unique
|
||||||
const missingNoteIds = noteIds.filter(noteId => !this.notes[noteId]);
|
const missingNoteIds = noteIds.filter(noteId => !this.notes[noteId]);
|
||||||
|
|
||||||
@ -246,18 +257,16 @@ class Froca {
|
|||||||
} else {
|
} else {
|
||||||
return this.notes[noteId];
|
return this.notes[noteId];
|
||||||
}
|
}
|
||||||
}).filter(note => !!note);
|
}).filter(note => !!note) as FNote[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<boolean>} */
|
async noteExists(noteId: string): Promise<boolean> {
|
||||||
async noteExists(noteId) {
|
|
||||||
const notes = await this.getNotes([noteId], true);
|
const notes = await this.getNotes([noteId], true);
|
||||||
|
|
||||||
return notes.length === 1;
|
return notes.length === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FNote>} */
|
async getNote(noteId: string, silentNotFoundError = false): Promise<FNote | null> {
|
||||||
async getNote(noteId, silentNotFoundError = false) {
|
|
||||||
if (noteId === 'none') {
|
if (noteId === 'none') {
|
||||||
console.trace(`No 'none' note.`);
|
console.trace(`No 'none' note.`);
|
||||||
return null;
|
return null;
|
||||||
@ -270,8 +279,7 @@ class Froca {
|
|||||||
return (await this.getNotes([noteId], silentNotFoundError))[0];
|
return (await this.getNotes([noteId], silentNotFoundError))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FNote|null} */
|
getNoteFromCache(noteId: string) {
|
||||||
getNoteFromCache(noteId) {
|
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
throw new Error("Empty noteId");
|
throw new Error("Empty noteId");
|
||||||
}
|
}
|
||||||
@ -279,15 +287,13 @@ class Froca {
|
|||||||
return this.notes[noteId];
|
return this.notes[noteId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FBranch[]} */
|
getBranches(branchIds: string[], silentNotFoundError = false): FBranch[] {
|
||||||
getBranches(branchIds, silentNotFoundError = false) {
|
|
||||||
return branchIds
|
return branchIds
|
||||||
.map(branchId => this.getBranch(branchId, silentNotFoundError))
|
.map(branchId => this.getBranch(branchId, silentNotFoundError))
|
||||||
.filter(b => !!b);
|
.filter(b => !!b) as FBranch[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FBranch} */
|
getBranch(branchId: string, silentNotFoundError = false) {
|
||||||
getBranch(branchId, silentNotFoundError = false) {
|
|
||||||
if (!(branchId in this.branches)) {
|
if (!(branchId in this.branches)) {
|
||||||
if (!silentNotFoundError) {
|
if (!silentNotFoundError) {
|
||||||
logError(`Not existing branch '${branchId}'`);
|
logError(`Not existing branch '${branchId}'`);
|
||||||
@ -298,7 +304,7 @@ class Froca {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBranchId(parentNoteId, childNoteId) {
|
async getBranchId(parentNoteId: string, childNoteId: string) {
|
||||||
if (childNoteId === 'root') {
|
if (childNoteId === 'root') {
|
||||||
return 'none_root';
|
return 'none_root';
|
||||||
}
|
}
|
||||||
@ -314,8 +320,7 @@ class Froca {
|
|||||||
return child.parentToBranch[parentNoteId];
|
return child.parentToBranch[parentNoteId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FAttachment>} */
|
async getAttachment(attachmentId: string, silentNotFoundError = false) {
|
||||||
async getAttachment(attachmentId, silentNotFoundError = false) {
|
|
||||||
const attachment = this.attachments[attachmentId];
|
const attachment = this.attachments[attachmentId];
|
||||||
if (attachment) {
|
if (attachment) {
|
||||||
return attachment;
|
return attachment;
|
||||||
@ -324,9 +329,8 @@ class Froca {
|
|||||||
// load all attachments for the given note even if one is requested, don't load one by one
|
// load all attachments for the given note even if one is requested, don't load one by one
|
||||||
let attachmentRows;
|
let attachmentRows;
|
||||||
try {
|
try {
|
||||||
attachmentRows = await server.getWithSilentNotFound(`attachments/${attachmentId}/all`);
|
attachmentRows = await server.getWithSilentNotFound<FAttachmentRow[]>(`attachments/${attachmentId}/all`);
|
||||||
}
|
} catch (e: any) {
|
||||||
catch (e) {
|
|
||||||
if (silentNotFoundError) {
|
if (silentNotFoundError) {
|
||||||
logInfo(`Attachment '${attachmentId}' not found, but silentNotFoundError is enabled: ` + e.message);
|
logInfo(`Attachment '${attachmentId}' not found, but silentNotFoundError is enabled: ` + e.message);
|
||||||
return null;
|
return null;
|
||||||
@ -344,14 +348,12 @@ class Froca {
|
|||||||
return this.attachments[attachmentId];
|
return this.attachments[attachmentId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FAttachment[]>} */
|
async getAttachmentsForNote(noteId: string) {
|
||||||
async getAttachmentsForNote(noteId) {
|
const attachmentRows = await server.get<FAttachmentRow[]>(`notes/${noteId}/attachments`);
|
||||||
const attachmentRows = await server.get(`notes/${noteId}/attachments`);
|
|
||||||
return this.processAttachmentRows(attachmentRows);
|
return this.processAttachmentRows(attachmentRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {FAttachment[]} */
|
processAttachmentRows(attachmentRows: FAttachmentRow[]): FAttachment[] {
|
||||||
processAttachmentRows(attachmentRows) {
|
|
||||||
return attachmentRows.map(attachmentRow => {
|
return attachmentRows.map(attachmentRow => {
|
||||||
let attachment;
|
let attachment;
|
||||||
|
|
||||||
@ -367,22 +369,21 @@ class Froca {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<FBlob>} */
|
async getBlob(entityType: string, entityId: string) {
|
||||||
async getBlob(entityType, entityId) {
|
|
||||||
// I'm not sure why we're not using blobIds directly, it would save us this composite key ...
|
// I'm not sure why we're not using blobIds directly, it would save us this composite key ...
|
||||||
// perhaps one benefit is that we're always requesting the latest blob, not relying on perhaps faulty/slow
|
// perhaps one benefit is that we're always requesting the latest blob, not relying on perhaps faulty/slow
|
||||||
// websocket update?
|
// websocket update?
|
||||||
const key = `${entityType}-${entityId}`;
|
const key = `${entityType}-${entityId}`;
|
||||||
|
|
||||||
if (!this.blobPromises[key]) {
|
if (!this.blobPromises[key]) {
|
||||||
this.blobPromises[key] = server.get(`${entityType}/${entityId}/blob`)
|
this.blobPromises[key] = server.get<FBlobRow>(`${entityType}/${entityId}/blob`)
|
||||||
.then(row => new FBlob(row))
|
.then(row => new FBlob(row))
|
||||||
.catch(e => console.error(`Cannot get blob for ${entityType} '${entityId}'`, e));
|
.catch(e => console.error(`Cannot get blob for ${entityType} '${entityId}'`, e));
|
||||||
|
|
||||||
// we don't want to keep large payloads forever in memory, so we clean that up quite quickly
|
// we don't want to keep large payloads forever in memory, so we clean that up quite quickly
|
||||||
// this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components)
|
// this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components)
|
||||||
// if the blob is updated within the cache lifetime, it should be invalidated by froca_updater
|
// if the blob is updated within the cache lifetime, it should be invalidated by froca_updater
|
||||||
this.blobPromises[key].then(
|
this.blobPromises[key]?.then(
|
||||||
() => setTimeout(() => this.blobPromises[key] = null, 1000)
|
() => setTimeout(() => this.blobPromises[key] = null, 1000)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -391,6 +392,6 @@ class Froca {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const froca = new Froca();
|
const froca = new FrocaImpl();
|
||||||
|
|
||||||
export default froca;
|
export default froca;
|
@ -1,3 +1,5 @@
|
|||||||
|
import FAttribute from "../entities/fattribute.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The purpose of this class is to cache the list of attributes for notes.
|
* The purpose of this class is to cache the list of attributes for notes.
|
||||||
*
|
*
|
||||||
@ -6,8 +8,9 @@
|
|||||||
* as loading the tree which uses attributes heavily.
|
* as loading the tree which uses attributes heavily.
|
||||||
*/
|
*/
|
||||||
class NoteAttributeCache {
|
class NoteAttributeCache {
|
||||||
|
attributes: Record<string, FAttribute[]>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
/** @property {Object.<string, BAttribute[]>} */
|
|
||||||
this.attributes = {};
|
this.attributes = {};
|
||||||
}
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user