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
|
||||
* being a hidden child.
|
||||
*/
|
||||
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} */
|
||||
this.froca = froca;
|
||||
|
||||
this.update(row);
|
||||
}
|
||||
|
||||
update(row) {
|
||||
/** @type {string} */
|
||||
update(row: FAttachmentRow) {
|
||||
this.attachmentId = row.attachmentId;
|
||||
/** @type {string} */
|
||||
this.ownerId = row.ownerId;
|
||||
/** @type {string} */
|
||||
this.role = row.role;
|
||||
/** @type {string} */
|
||||
this.mime = row.mime;
|
||||
/** @type {string} */
|
||||
this.title = row.title;
|
||||
/** @type {string} */
|
||||
this.dateModified = row.dateModified;
|
||||
/** @type {string} */
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
/** @type {string} */
|
||||
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
|
||||
|
||||
/**
|
||||
* optionally added to the entity
|
||||
* @type {int}
|
||||
*/
|
||||
this.contentLength = row.contentLength;
|
||||
|
||||
this.froca.attachments[this.attachmentId] = this;
|
||||
}
|
||||
|
||||
/** @returns {FNote} */
|
||||
getNote() {
|
||||
return this.froca.notes[this.ownerId];
|
||||
}
|
||||
|
||||
/** @return {FBlob} */
|
||||
async getBlob() {
|
||||
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';
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* and relation (representing named relationship between source and target note)
|
||||
*/
|
||||
class FAttribute {
|
||||
constructor(froca, row) {
|
||||
/** @type {Froca} */
|
||||
private froca: Froca;
|
||||
attributeId!: string;
|
||||
noteId!: string;
|
||||
type!: AttributeType;
|
||||
name!: string;
|
||||
value!: string;
|
||||
position!: number;
|
||||
isInheritable!: boolean;
|
||||
|
||||
constructor(froca: Froca, row: FAttributeRow) {
|
||||
this.froca = froca;
|
||||
|
||||
this.update(row);
|
||||
}
|
||||
|
||||
update(row) {
|
||||
/** @type {string} */
|
||||
update(row: FAttributeRow) {
|
||||
this.attributeId = row.attributeId;
|
||||
/** @type {string} */
|
||||
this.noteId = row.noteId;
|
||||
/** @type {AttributeType} */
|
||||
this.type = row.type;
|
||||
/** @type {string} */
|
||||
this.name = row.name;
|
||||
/** @type {string} */
|
||||
this.value = row.value;
|
||||
/** @type {int} */
|
||||
this.position = row.position;
|
||||
/** @type {boolean} */
|
||||
this.isInheritable = !!row.isInheritable;
|
||||
}
|
||||
|
||||
/** @returns {FNote} */
|
||||
getNote() {
|
||||
return this.froca.notes[this.noteId];
|
||||
}
|
||||
|
||||
/** @returns {Promise<FNote>} */
|
||||
async getTargetNote() {
|
||||
const targetNoteId = this.targetNoteId;
|
||||
|
||||
@ -70,12 +81,12 @@ class FAttribute {
|
||||
return promotedAttributeDefinitionParser.parse(this.value);
|
||||
}
|
||||
|
||||
isDefinitionFor(attr) {
|
||||
isDefinitionFor(attr: FAttribute) {
|
||||
return this.type === 'label' && this.name === `${attr.type}:${attr.name}`;
|
||||
}
|
||||
|
||||
get dto() {
|
||||
const dto = Object.assign({}, this);
|
||||
get dto(): Omit<FAttribute, "froca"> {
|
||||
const dto: any = Object.assign({}, this);
|
||||
delete dto.froca;
|
||||
|
||||
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
|
||||
* parents.
|
||||
*/
|
||||
class FBranch {
|
||||
constructor(froca, row) {
|
||||
/** @type {Froca} */
|
||||
private froca: 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.update(row);
|
||||
}
|
||||
|
||||
update(row) {
|
||||
update(row: FBranchRow) {
|
||||
/**
|
||||
* primary key
|
||||
* @type {string}
|
||||
*/
|
||||
this.branchId = row.branchId;
|
||||
/** @type {string} */
|
||||
this.noteId = row.noteId;
|
||||
/** @type {string} */
|
||||
this.parentNoteId = row.parentNoteId;
|
||||
/** @type {int} */
|
||||
this.notePosition = row.notePosition;
|
||||
/** @type {string} */
|
||||
this.prefix = row.prefix;
|
||||
/** @type {boolean} */
|
||||
this.isExpanded = !!row.isExpanded;
|
||||
/** @type {boolean} */
|
||||
this.fromSearchNote = !!row.fromSearchNote;
|
||||
}
|
||||
|
||||
/** @returns {FNote} */
|
||||
async getNote() {
|
||||
return this.froca.getNote(this.noteId);
|
||||
}
|
||||
|
||||
/** @returns {FNote} */
|
||||
getNoteFromCache() {
|
||||
return this.froca.getNoteFromCache(this.noteId);
|
||||
}
|
||||
|
||||
/** @returns {FNote} */
|
||||
async getParentNote() {
|
||||
return this.froca.getNote(this.parentNoteId);
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if it's top level, meaning its parent is the root note */
|
||||
/** @returns true if it's top level, meaning its parent is the root note */
|
||||
isTopLevel() {
|
||||
return this.parentNoteId === 'root';
|
||||
}
|
||||
@ -54,8 +68,8 @@ class FBranch {
|
||||
return `FBranch(branchId=${this.branchId})`;
|
||||
}
|
||||
|
||||
get pojo() {
|
||||
const pojo = {...this};
|
||||
get pojo(): Omit<FBranch, "froca"> {
|
||||
const pojo = {...this} as any;
|
||||
delete pojo.froca;
|
||||
return pojo;
|
||||
}
|
@ -4,6 +4,9 @@ import ws from "../services/ws.js";
|
||||
import froca from "../services/froca.js";
|
||||
import protectedSessionHolder from "../services/protected_session_holder.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 RELATION = 'relation';
|
||||
@ -29,76 +32,91 @@ const NOTE_TYPE_ICONS = {
|
||||
* 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
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* @typedef {Object} NotePathRecord
|
||||
* @property {boolean} isArchived
|
||||
* @property {boolean} isInHoistedSubTree
|
||||
* @property {boolean} isSearch
|
||||
* @property {Array<string>} notePath
|
||||
* @property {boolean} isHidden
|
||||
*/
|
||||
interface NotePathRecord {
|
||||
isArchived: boolean;
|
||||
isInHoistedSubTree: boolean;
|
||||
isSearch: boolean;
|
||||
notePath: string[];
|
||||
isHidden: boolean;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
class FNote {
|
||||
|
||||
private froca: Froca;
|
||||
|
||||
noteId!: string;
|
||||
title!: string;
|
||||
isProtected!: boolean;
|
||||
type!: NoteType;
|
||||
/**
|
||||
* @param {Froca} froca
|
||||
* @param {Object.<string, Object>} row
|
||||
* content-type, e.g. "application/json"
|
||||
*/
|
||||
constructor(froca, row) {
|
||||
/** @type {Froca} */
|
||||
mime!: string;
|
||||
// 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;
|
||||
|
||||
/** @type {string[]} */
|
||||
this.attributes = [];
|
||||
|
||||
/** @type {string[]} */
|
||||
this.targetRelations = [];
|
||||
|
||||
/** @type {string[]} */
|
||||
this.parents = [];
|
||||
/** @type {string[]} */
|
||||
this.children = [];
|
||||
|
||||
/** @type {Object.<string, string>} */
|
||||
this.parentToBranch = {};
|
||||
|
||||
/** @type {Object.<string, string>} */
|
||||
this.childToBranch = {};
|
||||
|
||||
/** @type {FAttachment[]|null} */
|
||||
this.attachments = null; // lazy loaded
|
||||
|
||||
this.update(row);
|
||||
}
|
||||
|
||||
update(row) {
|
||||
/** @type {string} */
|
||||
update(row: FNoteRow) {
|
||||
this.noteId = row.noteId;
|
||||
/** @type {string} */
|
||||
this.title = row.title;
|
||||
/** @type {boolean} */
|
||||
this.isProtected = !!row.isProtected;
|
||||
/**
|
||||
* See {@see NoteType} for info on values.
|
||||
* @type {NoteType}
|
||||
*/
|
||||
this.type = row.type;
|
||||
/**
|
||||
* content-type, e.g. "application/json"
|
||||
* @type {string}
|
||||
*/
|
||||
|
||||
this.mime = row.mime;
|
||||
|
||||
// the main use case to keep this is to detect content change which should trigger refresh
|
||||
this.blobId = row.blobId;
|
||||
}
|
||||
|
||||
addParent(parentNoteId, branchId, sort = true) {
|
||||
addParent(parentNoteId: string, branchId: string, sort = true) {
|
||||
if (parentNoteId === 'none') {
|
||||
return;
|
||||
}
|
||||
@ -114,7 +132,7 @@ class FNote {
|
||||
}
|
||||
}
|
||||
|
||||
addChild(childNoteId, branchId, sort = true) {
|
||||
addChild(childNoteId: string, branchId: string, sort = true) {
|
||||
if (!(childNoteId in this.childToBranch)) {
|
||||
this.children.push(childNoteId);
|
||||
}
|
||||
@ -127,16 +145,18 @@ class FNote {
|
||||
}
|
||||
|
||||
sortChildren() {
|
||||
const branchIdPos = {};
|
||||
const branchIdPos: Record<string, number> = {};
|
||||
|
||||
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]]);
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
isJson() {
|
||||
return this.mime === "application/json";
|
||||
}
|
||||
@ -150,34 +170,32 @@ class FNote {
|
||||
async getJsonContent() {
|
||||
const content = await this.getContent();
|
||||
|
||||
if (typeof content !== "string") {
|
||||
console.log(`Unknown note content for '${this.noteId}'.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
}
|
||||
catch (e) {
|
||||
catch (e: any) {
|
||||
console.log(`Cannot parse content of note '${this.noteId}': `, e.message);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getParentBranchIds() {
|
||||
return Object.values(this.parentToBranch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
* @deprecated use getParentBranchIds() instead
|
||||
*/
|
||||
getBranchIds() {
|
||||
return this.getParentBranchIds();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {FBranch[]}
|
||||
*/
|
||||
getParentBranches() {
|
||||
const branchIds = Object.values(this.parentToBranch);
|
||||
|
||||
@ -185,19 +203,16 @@ class FNote {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {FBranch[]}
|
||||
* @deprecated use getParentBranches() instead
|
||||
*/
|
||||
getBranches() {
|
||||
return this.getParentBranches();
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
hasChildren() {
|
||||
return this.children.length > 0;
|
||||
}
|
||||
|
||||
/** @returns {FBranch[]} */
|
||||
getChildBranches() {
|
||||
// don't use Object.values() to guarantee order
|
||||
const branchIds = this.children.map(childNoteId => this.childToBranch[childNoteId]);
|
||||
@ -205,12 +220,10 @@ class FNote {
|
||||
return this.froca.getBranches(branchIds);
|
||||
}
|
||||
|
||||
/** @returns {string[]} */
|
||||
getParentNoteIds() {
|
||||
return this.parents;
|
||||
}
|
||||
|
||||
/** @returns {FNote[]} */
|
||||
getParentNotes() {
|
||||
return this.froca.getNotesFromCache(this.parents);
|
||||
}
|
||||
@ -239,17 +252,14 @@ class FNote {
|
||||
return this.hasAttribute('label', 'archived');
|
||||
}
|
||||
|
||||
/** @returns {string[]} */
|
||||
getChildNoteIds() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
/** @returns {Promise<FNote[]>} */
|
||||
async getChildNotes() {
|
||||
return await this.froca.getNotes(this.children);
|
||||
}
|
||||
|
||||
/** @returns {Promise<FAttachment[]>} */
|
||||
async getAttachments() {
|
||||
if (!this.attachments) {
|
||||
this.attachments = await this.froca.getAttachmentsForNote(this.noteId);
|
||||
@ -258,14 +268,12 @@ class FNote {
|
||||
return this.attachments;
|
||||
}
|
||||
|
||||
/** @returns {Promise<FAttachment[]>} */
|
||||
async getAttachmentsByRole(role) {
|
||||
async getAttachmentsByRole(role: string) {
|
||||
return (await this.getAttachments())
|
||||
.filter(attachment => attachment.role === role);
|
||||
}
|
||||
|
||||
/** @returns {Promise<FAttachment>} */
|
||||
async getAttachmentById(attachmentId) {
|
||||
async getAttachmentById(attachmentId: string) {
|
||||
const attachments = await this.getAttachments();
|
||||
|
||||
return attachments.find(att => att.attachmentId === attachmentId);
|
||||
@ -295,11 +303,11 @@ class FNote {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [type] - (optional) attribute type to filter
|
||||
* @param {string} [name] - (optional) attribute name to filter
|
||||
* @returns {FAttribute[]} all note's attributes, including inherited ones
|
||||
* @param [type] - attribute type to filter
|
||||
* @param [name] - attribute name to filter
|
||||
* @returns all note's attributes, including inherited ones
|
||||
*/
|
||||
getOwnedAttributes(type, name) {
|
||||
getOwnedAttributes(type?: AttributeType, name?: string) {
|
||||
const attrs = this.attributes
|
||||
.map(attributeId => this.froca.attributes[attributeId])
|
||||
.filter(Boolean); // filter out nulls;
|
||||
@ -308,20 +316,18 @@ class FNote {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [type] - (optional) attribute type to filter
|
||||
* @param {string} [name] - (optional) attribute name to filter
|
||||
* @returns {FAttribute[]} all note's attributes, including inherited ones
|
||||
* @param [type] - attribute type to filter
|
||||
* @param [name] - attribute name to filter
|
||||
* @returns all note's attributes, including inherited ones
|
||||
*/
|
||||
getAttributes(type, name) {
|
||||
getAttributes(type?: AttributeType, name?: string) {
|
||||
return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} path
|
||||
* @return {FAttribute[]}
|
||||
* @private
|
||||
*/
|
||||
__getCachedAttributes(path) {
|
||||
__getCachedAttributes(path: string[]): FAttribute[] {
|
||||
// notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
|
||||
// when template instance is a parent of template itself
|
||||
if (path.includes(this.noteId)) {
|
||||
@ -376,9 +382,9 @@ class FNote {
|
||||
/**
|
||||
* 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') {
|
||||
return [['root']];
|
||||
}
|
||||
@ -396,10 +402,6 @@ class FNote {
|
||||
return notePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [hoistedNoteId='root']
|
||||
* @return {Array<NotePathRecord>}
|
||||
*/
|
||||
getSortedNotePathRecords(hoistedNoteId = 'root') {
|
||||
const isHoistedRoot = hoistedNoteId === 'root';
|
||||
|
||||
@ -475,14 +477,10 @@ class FNote {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FAttribute[]} attributes
|
||||
* @param {AttributeType} type
|
||||
* @param {string} name
|
||||
* @return {FAttribute[]}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
__filterAttrs(attributes, type, name) {
|
||||
__filterAttrs(attributes: FAttribute[], type?: AttributeType, name?: string): FAttribute[] {
|
||||
this.__validateTypeName(type, name);
|
||||
|
||||
if (!type && !name) {
|
||||
@ -494,15 +492,17 @@ class FNote {
|
||||
} else if (name) {
|
||||
return attributes.filter(attr => attr.name === name);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
__getInheritableAttributes(path) {
|
||||
__getInheritableAttributes(path: string[]) {
|
||||
const attrs = this.__getCachedAttributes(path);
|
||||
|
||||
return attrs.filter(attr => attr.isInheritable);
|
||||
}
|
||||
|
||||
__validateTypeName(type, name) {
|
||||
__validateTypeName(type?: string, name?: string) {
|
||||
if (type && type !== 'label' && type !== 'relation') {
|
||||
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
|
||||
* @returns {FAttribute[]} all note's labels (attributes with type label), including inherited ones
|
||||
* @param [name] - label name to filter
|
||||
* @returns all note's labels (attributes with type label), including inherited ones
|
||||
*/
|
||||
getOwnedLabels(name) {
|
||||
getOwnedLabels(name: string) {
|
||||
return this.getOwnedAttributes(LABEL, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - label name to filter
|
||||
* @returns {FAttribute[]} all note's labels (attributes with type label), including inherited ones
|
||||
* @param [name] - label name to filter
|
||||
* @returns all note's labels (attributes with type label), including inherited ones
|
||||
*/
|
||||
getLabels(name) {
|
||||
getLabels(name: string) {
|
||||
return this.getAttributes(LABEL, name);
|
||||
}
|
||||
|
||||
@ -535,7 +535,7 @@ class FNote {
|
||||
const iconClassLabels = this.getLabels('iconClass');
|
||||
const workspaceIconClass = this.getWorkspaceIconClass();
|
||||
|
||||
if (iconClassLabels.length > 0) {
|
||||
if (iconClassLabels && iconClassLabels.length > 0) {
|
||||
return iconClassLabels[0].value;
|
||||
}
|
||||
else if (workspaceIconClass) {
|
||||
@ -578,7 +578,7 @@ class FNote {
|
||||
|
||||
if (!childBranches) {
|
||||
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
|
||||
@ -590,102 +590,104 @@ class FNote {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - relation name to filter
|
||||
* @returns {FAttribute[]} all note's relations (attributes with type relation), including inherited ones
|
||||
* @param [name] - relation name to filter
|
||||
* @returns all note's relations (attributes with type relation), including inherited ones
|
||||
*/
|
||||
getOwnedRelations(name) {
|
||||
getOwnedRelations(name: string) {
|
||||
return this.getOwnedAttributes(RELATION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - relation name to filter
|
||||
* @returns {FAttribute[]} all note's relations (attributes with type relation), including inherited ones
|
||||
* @param [name] - relation name to filter
|
||||
* @returns all note's relations (attributes with type relation), including inherited ones
|
||||
*/
|
||||
getRelations(name) {
|
||||
getRelations(name: string) {
|
||||
return this.getAttributes(RELATION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {boolean} true if note has an attribute with given type and name (including inherited)
|
||||
* @param type - attribute type (label, relation, etc.)
|
||||
* @param name - attribute name
|
||||
* @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();
|
||||
|
||||
return attributes.some(attr => attr.name === name && attr.type === type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {boolean} true if note has an attribute with given type and name (including inherited)
|
||||
* @param type - attribute type (label, relation, etc.)
|
||||
* @param name - attribute name
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
||||
* @param {string} 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.
|
||||
* @param type - attribute type (label, relation, etc.)
|
||||
* @param name - attribute name
|
||||
* @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();
|
||||
|
||||
return attributes.find(attr => attr.name === name && attr.type === type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
||||
* @param {string} 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.
|
||||
* @param type - attribute type (label, relation, etc.)
|
||||
* @param name - attribute name
|
||||
* @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();
|
||||
|
||||
return attributes.find(attr => attr.name === name && attr.type === type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {string} attribute value of the given type and name or null if no such attribute exists.
|
||||
* @param type - attribute type (label, relation, etc.)
|
||||
* @param name - attribute name
|
||||
* @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);
|
||||
|
||||
return attr ? attr.value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AttributeType} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {string} attribute value of the given type and name or null if no such attribute exists.
|
||||
* @param type - attribute type (label, relation, etc.)
|
||||
* @param name - attribute name
|
||||
* @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);
|
||||
|
||||
return attr ? attr.value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {boolean} true if label exists (excluding inherited)
|
||||
* @param name - label name
|
||||
* @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
|
||||
* @returns {boolean} true if label exists (including inherited)
|
||||
* @param name - label name
|
||||
* @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
|
||||
* @returns {boolean} true if label exists (including inherited) and does not have "false" value.
|
||||
* @param name - label name
|
||||
* @returns true if label exists (including inherited) and does not have "false" value.
|
||||
*/
|
||||
isLabelTruthy(name) {
|
||||
isLabelTruthy(name: string) {
|
||||
const label = this.getLabel(name);
|
||||
|
||||
if (!label) {
|
||||
@ -696,80 +698,79 @@ class FNote {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {boolean} true if relation exists (excluding inherited)
|
||||
* @param name - relation name
|
||||
* @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
|
||||
* @returns {boolean} true if relation exists (including inherited)
|
||||
* @param name - relation name
|
||||
* @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
|
||||
* @returns {FAttribute} label if it exists, null otherwise
|
||||
* @param name - label name
|
||||
* @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
|
||||
* @returns {FAttribute} label if it exists, null otherwise
|
||||
* @param name - label name
|
||||
* @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
|
||||
* @returns {FAttribute} relation if it exists, null otherwise
|
||||
* @param name - relation name
|
||||
* @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
|
||||
* @returns {FAttribute} relation if it exists, null otherwise
|
||||
* @param name - relation name
|
||||
* @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
|
||||
* @returns {string} label value if label exists, null otherwise
|
||||
* @param name - label name
|
||||
* @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
|
||||
* @returns {string} label value if label exists, null otherwise
|
||||
* @param name - label name
|
||||
* @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
|
||||
* @returns {string} relation value if relation exists, null otherwise
|
||||
* @param name - relation name
|
||||
* @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
|
||||
* @returns {string} relation value if relation exists, null otherwise
|
||||
* @param name - relation name
|
||||
* @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
|
||||
* @returns {Promise<FNote>|null} target note of the relation or null (if target is empty or note was not found)
|
||||
* @param name
|
||||
* @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);
|
||||
|
||||
return targets.length > 0 ? targets[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - relation name to filter
|
||||
* @returns {Promise<FNote[]>}
|
||||
* @param [name] - relation name to filter
|
||||
*/
|
||||
async getRelationTargets(name) {
|
||||
async getRelationTargets(name: string) {
|
||||
const relations = this.getRelations(name);
|
||||
const targets = [];
|
||||
|
||||
@ -780,9 +781,6 @@ class FNote {
|
||||
return targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {FNote[]}
|
||||
*/
|
||||
getNotesToInheritAttributesFrom() {
|
||||
const relations = [
|
||||
...this.getRelations('template'),
|
||||
@ -818,7 +816,7 @@ class FNote {
|
||||
return promotedAttrs;
|
||||
}
|
||||
|
||||
hasAncestor(ancestorNoteId, followTemplates = false, visitedNoteIds = null) {
|
||||
hasAncestor(ancestorNoteId: string, followTemplates = false, visitedNoteIds: Set<string> | null = null) {
|
||||
if (this.noteId === ancestorNoteId) {
|
||||
return true;
|
||||
}
|
||||
@ -860,8 +858,6 @@ class FNote {
|
||||
|
||||
/**
|
||||
* Get relations which target this note
|
||||
*
|
||||
* @returns {FAttribute[]}
|
||||
*/
|
||||
getTargetRelations() {
|
||||
return this.targetRelations
|
||||
@ -870,8 +866,6 @@ class FNote {
|
||||
|
||||
/**
|
||||
* Get relations which target this note
|
||||
*
|
||||
* @returns {Promise<FNote[]>}
|
||||
*/
|
||||
async getTargetRelationSourceNotes() {
|
||||
const targetRelations = this.getTargetRelations();
|
||||
@ -881,13 +875,11 @@ class FNote {
|
||||
|
||||
/**
|
||||
* @deprecated use getBlob() instead
|
||||
* @return {Promise<FBlob>}
|
||||
*/
|
||||
async getNoteComplement() {
|
||||
return this.getBlob();
|
||||
}
|
||||
|
||||
/** @return {Promise<FBlob>} */
|
||||
async getBlob() {
|
||||
return await this.froca.getBlob('notes', this.noteId);
|
||||
}
|
||||
@ -896,8 +888,8 @@ class FNote {
|
||||
return `Note(noteId=${this.noteId}, title=${this.title})`;
|
||||
}
|
||||
|
||||
get dto() {
|
||||
const dto = Object.assign({}, this);
|
||||
get dto(): Omit<FNote, "froca"> {
|
||||
const dto = Object.assign({}, this) as any;
|
||||
delete dto.froca;
|
||||
|
||||
return dto;
|
||||
@ -918,7 +910,7 @@ class FNote {
|
||||
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() {
|
||||
return (this.type === "code" || this.type === "file" || this.type === 'launcher')
|
||||
&& (this.mime.startsWith("application/javascript")
|
||||
@ -926,12 +918,12 @@ class FNote {
|
||||
|| this.mime === "text/javascript");
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if this note is HTML */
|
||||
/** @returns true if this note is HTML */
|
||||
isHtml() {
|
||||
return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
|
||||
}
|
||||
|
||||
/** @returns {string|null} JS script environment - either "frontend" or "backend" */
|
||||
/** @returns JS script environment - either "frontend" or "backend" */
|
||||
getScriptEnv() {
|
||||
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) {
|
||||
return "frontend";
|
||||
@ -958,11 +950,9 @@ class FNote {
|
||||
if (env === "frontend") {
|
||||
const bundleService = (await import("../services/bundle.js")).default;
|
||||
return await bundleService.getAndExecuteBundle(this.noteId);
|
||||
}
|
||||
else if (env === "backend") {
|
||||
const resp = await server.post(`script/run/${this.noteId}`);
|
||||
}
|
||||
else {
|
||||
} else if (env === "backend") {
|
||||
await server.post(`script/run/${this.noteId}`);
|
||||
} else {
|
||||
throw new Error(`Unrecognized env type ${env} for note ${this.noteId}`);
|
||||
}
|
||||
}
|
||||
@ -1001,11 +991,9 @@ class FNote {
|
||||
|
||||
/**
|
||||
* Provides note's date metadata.
|
||||
*
|
||||
* @returns {Promise<{dateCreated: string, utcDateCreated: string, dateModified: string, utcDateModified: string}>}
|
||||
*/
|
||||
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>();
|
||||
|
||||
function createClassForColor(color: string) {
|
||||
function createClassForColor(color: string | null) {
|
||||
if (!color?.trim()) {
|
||||
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 server from "./server.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import FBlob from "../entities/fblob.js";
|
||||
import FAttachment from "../entities/fattachment.js";
|
||||
import FBlob, { FBlobRow } from "../entities/fblob.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.
|
||||
@ -16,48 +30,47 @@ import FAttachment from "../entities/fattachment.js";
|
||||
*
|
||||
* 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() {
|
||||
this.initializedPromise = this.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
|
||||
|
||||
/** @type {Object.<string, FNote>} */
|
||||
this.notes = {};
|
||||
|
||||
/** @type {Object.<string, FBranch>} */
|
||||
this.branches = {};
|
||||
|
||||
/** @type {Object.<string, FAttribute>} */
|
||||
this.attributes = {};
|
||||
|
||||
/** @type {Object.<string, FAttachment>} */
|
||||
this.attachments = {};
|
||||
|
||||
/** @type {Object.<string, Promise<FBlob>>} */
|
||||
this.blobPromises = {};
|
||||
|
||||
this.addResp(resp);
|
||||
}
|
||||
|
||||
async loadSubTree(subTreeNoteId) {
|
||||
const resp = await server.get(`tree?subTreeNoteId=${subTreeNoteId}`);
|
||||
async loadSubTree(subTreeNoteId: string) {
|
||||
const resp = await server.get<SubtreeResponse>(`tree?subTreeNoteId=${subTreeNoteId}`);
|
||||
|
||||
this.addResp(resp);
|
||||
|
||||
return this.notes[subTreeNoteId];
|
||||
}
|
||||
|
||||
addResp(resp) {
|
||||
addResp(resp: SubtreeResponse) {
|
||||
const noteRows = resp.notes;
|
||||
const branchRows = resp.branches;
|
||||
const attributeRows = resp.attributes;
|
||||
|
||||
const noteIdsToSort = new Set();
|
||||
const noteIdsToSort = new Set<string>();
|
||||
|
||||
for (const noteRow of noteRows) {
|
||||
const {noteId} = noteRow;
|
||||
@ -160,28 +173,28 @@ class Froca {
|
||||
}
|
||||
}
|
||||
|
||||
async reloadNotes(noteIds) {
|
||||
async reloadNotes(noteIds: string[]) {
|
||||
if (noteIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
appContext.triggerEvent('notesReloaded', {noteIds});
|
||||
}
|
||||
|
||||
async loadSearchNote(noteId) {
|
||||
async loadSearchNote(noteId: string) {
|
||||
const note = await this.getNote(noteId);
|
||||
|
||||
if (!note || note.type !== 'search') {
|
||||
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)) {
|
||||
throw new Error(`Search note '${note.noteId}' failed: ${searchResultNoteIds}`);
|
||||
@ -217,8 +230,7 @@ class Froca {
|
||||
return {error};
|
||||
}
|
||||
|
||||
/** @returns {FNote[]} */
|
||||
getNotesFromCache(noteIds, silentNotFoundError = false) {
|
||||
getNotesFromCache(noteIds: string[], silentNotFoundError = false): FNote[] {
|
||||
return noteIds.map(noteId => {
|
||||
if (!this.notes[noteId] && !silentNotFoundError) {
|
||||
console.trace(`Can't find note '${noteId}'`);
|
||||
@ -228,11 +240,10 @@ class Froca {
|
||||
else {
|
||||
return this.notes[noteId];
|
||||
}
|
||||
}).filter(note => !!note);
|
||||
}).filter(note => !!note) as FNote[];
|
||||
}
|
||||
|
||||
/** @returns {Promise<FNote[]>} */
|
||||
async getNotes(noteIds, silentNotFoundError = false) {
|
||||
async getNotes(noteIds: string[], silentNotFoundError = false): Promise<FNote[]> {
|
||||
noteIds = Array.from(new Set(noteIds)); // make unique
|
||||
const missingNoteIds = noteIds.filter(noteId => !this.notes[noteId]);
|
||||
|
||||
@ -246,18 +257,16 @@ class Froca {
|
||||
} else {
|
||||
return this.notes[noteId];
|
||||
}
|
||||
}).filter(note => !!note);
|
||||
}).filter(note => !!note) as FNote[];
|
||||
}
|
||||
|
||||
/** @returns {Promise<boolean>} */
|
||||
async noteExists(noteId) {
|
||||
async noteExists(noteId: string): Promise<boolean> {
|
||||
const notes = await this.getNotes([noteId], true);
|
||||
|
||||
return notes.length === 1;
|
||||
}
|
||||
|
||||
/** @returns {Promise<FNote>} */
|
||||
async getNote(noteId, silentNotFoundError = false) {
|
||||
async getNote(noteId: string, silentNotFoundError = false): Promise<FNote | null> {
|
||||
if (noteId === 'none') {
|
||||
console.trace(`No 'none' note.`);
|
||||
return null;
|
||||
@ -270,8 +279,7 @@ class Froca {
|
||||
return (await this.getNotes([noteId], silentNotFoundError))[0];
|
||||
}
|
||||
|
||||
/** @returns {FNote|null} */
|
||||
getNoteFromCache(noteId) {
|
||||
getNoteFromCache(noteId: string) {
|
||||
if (!noteId) {
|
||||
throw new Error("Empty noteId");
|
||||
}
|
||||
@ -279,15 +287,13 @@ class Froca {
|
||||
return this.notes[noteId];
|
||||
}
|
||||
|
||||
/** @returns {FBranch[]} */
|
||||
getBranches(branchIds, silentNotFoundError = false) {
|
||||
getBranches(branchIds: string[], silentNotFoundError = false): FBranch[] {
|
||||
return branchIds
|
||||
.map(branchId => this.getBranch(branchId, silentNotFoundError))
|
||||
.filter(b => !!b);
|
||||
.filter(b => !!b) as FBranch[];
|
||||
}
|
||||
|
||||
/** @returns {FBranch} */
|
||||
getBranch(branchId, silentNotFoundError = false) {
|
||||
getBranch(branchId: string, silentNotFoundError = false) {
|
||||
if (!(branchId in this.branches)) {
|
||||
if (!silentNotFoundError) {
|
||||
logError(`Not existing branch '${branchId}'`);
|
||||
@ -298,7 +304,7 @@ class Froca {
|
||||
}
|
||||
}
|
||||
|
||||
async getBranchId(parentNoteId, childNoteId) {
|
||||
async getBranchId(parentNoteId: string, childNoteId: string) {
|
||||
if (childNoteId === 'root') {
|
||||
return 'none_root';
|
||||
}
|
||||
@ -314,8 +320,7 @@ class Froca {
|
||||
return child.parentToBranch[parentNoteId];
|
||||
}
|
||||
|
||||
/** @returns {Promise<FAttachment>} */
|
||||
async getAttachment(attachmentId, silentNotFoundError = false) {
|
||||
async getAttachment(attachmentId: string, silentNotFoundError = false) {
|
||||
const attachment = this.attachments[attachmentId];
|
||||
if (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
|
||||
let attachmentRows;
|
||||
try {
|
||||
attachmentRows = await server.getWithSilentNotFound(`attachments/${attachmentId}/all`);
|
||||
}
|
||||
catch (e) {
|
||||
attachmentRows = await server.getWithSilentNotFound<FAttachmentRow[]>(`attachments/${attachmentId}/all`);
|
||||
} catch (e: any) {
|
||||
if (silentNotFoundError) {
|
||||
logInfo(`Attachment '${attachmentId}' not found, but silentNotFoundError is enabled: ` + e.message);
|
||||
return null;
|
||||
@ -344,14 +348,12 @@ class Froca {
|
||||
return this.attachments[attachmentId];
|
||||
}
|
||||
|
||||
/** @returns {Promise<FAttachment[]>} */
|
||||
async getAttachmentsForNote(noteId) {
|
||||
const attachmentRows = await server.get(`notes/${noteId}/attachments`);
|
||||
async getAttachmentsForNote(noteId: string) {
|
||||
const attachmentRows = await server.get<FAttachmentRow[]>(`notes/${noteId}/attachments`);
|
||||
return this.processAttachmentRows(attachmentRows);
|
||||
}
|
||||
|
||||
/** @returns {FAttachment[]} */
|
||||
processAttachmentRows(attachmentRows) {
|
||||
processAttachmentRows(attachmentRows: FAttachmentRow[]): FAttachment[] {
|
||||
return attachmentRows.map(attachmentRow => {
|
||||
let attachment;
|
||||
|
||||
@ -367,22 +369,21 @@ class Froca {
|
||||
});
|
||||
}
|
||||
|
||||
/** @returns {Promise<FBlob>} */
|
||||
async getBlob(entityType, entityId) {
|
||||
async getBlob(entityType: string, entityId: string) {
|
||||
// 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
|
||||
// websocket update?
|
||||
const key = `${entityType}-${entityId}`;
|
||||
|
||||
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))
|
||||
.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
|
||||
// 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
|
||||
this.blobPromises[key].then(
|
||||
this.blobPromises[key]?.then(
|
||||
() => setTimeout(() => this.blobPromises[key] = null, 1000)
|
||||
);
|
||||
}
|
||||
@ -391,6 +392,6 @@ class Froca {
|
||||
}
|
||||
}
|
||||
|
||||
const froca = new Froca();
|
||||
const froca = new FrocaImpl();
|
||||
|
||||
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.
|
||||
*
|
||||
@ -6,8 +8,9 @@
|
||||
* as loading the tree which uses attributes heavily.
|
||||
*/
|
||||
class NoteAttributeCache {
|
||||
attributes: Record<string, FAttribute[]>;
|
||||
|
||||
constructor() {
|
||||
/** @property {Object.<string, BAttribute[]>} */
|
||||
this.attributes = {};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user