2021-08-20 21:42:06 +02:00
|
|
|
import LoadResults from "./load_results.js";
|
|
|
|
|
import froca from "./froca.js";
|
2021-08-24 22:59:51 +02:00
|
|
|
import utils from "./utils.js";
|
2021-08-20 21:42:06 +02:00
|
|
|
import options from "./options.js";
|
|
|
|
|
import noteAttributeCache from "./note_attribute_cache.js";
|
2024-07-25 20:42:56 +03:00
|
|
|
import FBranch, { FBranchRow } from "../entities/fbranch.js";
|
|
|
|
|
import FAttribute, { FAttributeRow } from "../entities/fattribute.js";
|
|
|
|
|
import FAttachment, { FAttachmentRow } from "../entities/fattachment.js";
|
|
|
|
|
import FNote, { FNoteRow } from "../entities/fnote.js";
|
2024-12-19 20:03:38 +02:00
|
|
|
import type { EntityChange } from "../server_types.js"
|
2021-08-20 21:42:06 +02:00
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
async function processEntityChanges(entityChanges: EntityChange[]) {
|
2021-08-20 21:42:06 +02:00
|
|
|
const loadResults = new LoadResults(entityChanges);
|
|
|
|
|
|
|
|
|
|
for (const ec of entityChanges) {
|
|
|
|
|
try {
|
|
|
|
|
if (ec.entityName === 'notes') {
|
|
|
|
|
processNoteChange(loadResults, ec);
|
|
|
|
|
} else if (ec.entityName === 'branches') {
|
2023-03-29 23:16:45 +02:00
|
|
|
await processBranchChange(loadResults, ec);
|
2021-08-20 21:42:06 +02:00
|
|
|
} else if (ec.entityName === 'attributes') {
|
|
|
|
|
processAttributeChange(loadResults, ec);
|
|
|
|
|
} else if (ec.entityName === 'note_reordering') {
|
|
|
|
|
processNoteReordering(loadResults, ec);
|
2023-06-04 23:01:40 +02:00
|
|
|
} else if (ec.entityName === 'revisions') {
|
|
|
|
|
loadResults.addRevision(ec.entityId, ec.noteId, ec.componentId);
|
2021-08-20 21:42:06 +02:00
|
|
|
} else if (ec.entityName === 'options') {
|
2024-07-25 20:42:56 +03:00
|
|
|
const attributeEntity = ec.entity as FAttributeRow;
|
|
|
|
|
if (attributeEntity.name === 'openNoteContexts') {
|
2021-08-20 21:42:06 +02:00
|
|
|
continue; // only noise
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
options.set(attributeEntity.name, attributeEntity.value);
|
2021-08-20 21:42:06 +02:00
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
loadResults.addOption(attributeEntity.name);
|
2023-04-01 23:55:04 +02:00
|
|
|
} else if (ec.entityName === 'attachments') {
|
2023-04-03 23:47:24 +02:00
|
|
|
processAttachment(loadResults, ec);
|
2023-11-13 23:53:14 +01:00
|
|
|
} else if (ec.entityName === 'blobs' || ec.entityName === 'etapi_tokens') {
|
2022-01-10 17:09:20 +01:00
|
|
|
// NOOP
|
|
|
|
|
}
|
2021-08-20 21:42:06 +02:00
|
|
|
else {
|
2023-05-03 10:23:20 +02:00
|
|
|
throw new Error(`Unknown entityName '${ec.entityName}'`);
|
2021-08-20 21:42:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-07-25 20:42:56 +03:00
|
|
|
catch (e: any) {
|
2021-08-20 21:42:06 +02:00
|
|
|
throw new Error(`Can't process entity ${JSON.stringify(ec)} with error ${e.message} ${e.stack}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-28 21:33:09 +02:00
|
|
|
// froca is supposed to contain all notes currently being visible to the users in the tree / otherwise being processed
|
|
|
|
|
// and their complete "ancestor relationship", so it's always possible to go up in the hierarchy towards the root.
|
|
|
|
|
// To this we count: standard parent-child relationships and template/inherit relations (attribute inheritance follows them).
|
2023-06-30 11:18:34 +02:00
|
|
|
// Here we watch for changes which might violate this principle - e.g., an introduction of a new "inherit" relation might
|
2023-06-28 21:33:09 +02:00
|
|
|
// mean we need to load the target of the relation (and then perhaps transitively the whole note path of this target).
|
2021-08-20 21:42:06 +02:00
|
|
|
const missingNoteIds = [];
|
|
|
|
|
|
|
|
|
|
for (const {entityName, entity} of entityChanges) {
|
|
|
|
|
if (!entity) { // if erased
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
if (entityName === 'branches' && !((entity as FBranchRow).parentNoteId in froca.notes)) {
|
|
|
|
|
missingNoteIds.push((entity as FBranchRow).parentNoteId);
|
2021-08-20 21:42:06 +02:00
|
|
|
}
|
2024-07-25 20:42:56 +03:00
|
|
|
else if (entityName === 'attributes') {
|
|
|
|
|
let attributeEntity = entity as FAttributeRow;
|
|
|
|
|
if (attributeEntity.type === 'relation'
|
|
|
|
|
&& (attributeEntity.name === 'template' || attributeEntity.name === 'inherit')
|
|
|
|
|
&& !(attributeEntity.value in froca.notes)) {
|
|
|
|
|
missingNoteIds.push(attributeEntity.value);
|
|
|
|
|
}
|
2021-08-20 21:42:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (missingNoteIds.length > 0) {
|
|
|
|
|
await froca.reloadNotes(missingNoteIds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!loadResults.isEmpty()) {
|
|
|
|
|
if (loadResults.hasAttributeRelatedChanges()) {
|
|
|
|
|
noteAttributeCache.invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
// TODO: Remove after porting the file
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
const appContext = (await import("../components/app_context.js")).default as any;
|
2021-08-20 21:42:06 +02:00
|
|
|
await appContext.triggerEvent('entitiesReloaded', {loadResults});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
function processNoteChange(loadResults: LoadResults, ec: EntityChange) {
|
2021-08-20 21:42:06 +02:00
|
|
|
const note = froca.notes[ec.entityId];
|
|
|
|
|
|
|
|
|
|
if (!note) {
|
2023-01-15 21:04:17 +01:00
|
|
|
// if this note has not been requested before then it's not part of froca's cached subset, and
|
2021-10-02 23:26:18 +02:00
|
|
|
// we're not interested in it
|
2021-08-20 21:42:06 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-09 20:16:39 +01:00
|
|
|
loadResults.addNote(ec.entityId, ec.componentId);
|
2021-08-24 22:37:00 +02:00
|
|
|
|
2021-08-24 22:59:51 +02:00
|
|
|
if (ec.isErased && ec.entityId in froca.notes) {
|
2023-05-03 10:23:20 +02:00
|
|
|
utils.reloadFrontendApp(`${ec.entityName} '${ec.entityId}' is erased, need to do complete reload.`);
|
2021-08-24 22:59:51 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-20 21:42:06 +02:00
|
|
|
if (ec.isErased || ec.entity?.isDeleted) {
|
|
|
|
|
delete froca.notes[ec.entityId];
|
|
|
|
|
}
|
2021-08-24 22:37:00 +02:00
|
|
|
else {
|
2024-07-25 20:42:56 +03:00
|
|
|
if (note.blobId !== (ec.entity as FNoteRow).blobId) {
|
2023-11-13 23:53:14 +01:00
|
|
|
for (const key of Object.keys(froca.blobPromises)) {
|
|
|
|
|
if (key.includes(note.noteId)) {
|
|
|
|
|
delete froca.blobPromises[key];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
if (ec.componentId) {
|
|
|
|
|
loadResults.addNoteContent(note.noteId, ec.componentId);
|
|
|
|
|
}
|
2023-11-13 23:53:14 +01:00
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
note.update(ec.entity as FNoteRow);
|
2021-08-24 22:37:00 +02:00
|
|
|
}
|
2021-08-20 21:42:06 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
async function processBranchChange(loadResults: LoadResults, ec: EntityChange) {
|
2021-08-24 22:59:51 +02:00
|
|
|
if (ec.isErased && ec.entityId in froca.branches) {
|
2023-05-03 10:23:20 +02:00
|
|
|
utils.reloadFrontendApp(`${ec.entityName} '${ec.entityId}' is erased, need to do complete reload.`);
|
2021-08-24 22:59:51 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-20 21:42:06 +02:00
|
|
|
let branch = froca.branches[ec.entityId];
|
|
|
|
|
|
|
|
|
|
if (ec.isErased || ec.entity?.isDeleted) {
|
|
|
|
|
if (branch) {
|
|
|
|
|
const childNote = froca.notes[branch.noteId];
|
|
|
|
|
const parentNote = froca.notes[branch.parentNoteId];
|
|
|
|
|
|
|
|
|
|
if (childNote) {
|
|
|
|
|
childNote.parents = childNote.parents.filter(parentNoteId => parentNoteId !== branch.parentNoteId);
|
|
|
|
|
delete childNote.parentToBranch[branch.parentNoteId];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parentNote) {
|
|
|
|
|
parentNote.children = parentNote.children.filter(childNoteId => childNoteId !== branch.noteId);
|
|
|
|
|
delete parentNote.childToBranch[branch.noteId];
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
if (ec.componentId) {
|
|
|
|
|
loadResults.addBranch(ec.entityId, ec.componentId);
|
|
|
|
|
}
|
2021-08-24 22:37:00 +02:00
|
|
|
|
2021-08-20 21:42:06 +02:00
|
|
|
delete froca.branches[ec.entityId];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
if (ec.componentId) {
|
|
|
|
|
loadResults.addBranch(ec.entityId, ec.componentId);
|
|
|
|
|
}
|
2021-08-24 22:37:00 +02:00
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
const branchEntity = ec.entity as FBranchRow;
|
|
|
|
|
const childNote = froca.notes[branchEntity.noteId];
|
|
|
|
|
let parentNote: FNote | null = froca.notes[branchEntity.parentNoteId];
|
2023-03-29 23:16:45 +02:00
|
|
|
|
2023-04-14 20:24:28 +02:00
|
|
|
if (childNote && !childNote.isRoot() && !parentNote) {
|
2023-03-29 23:16:45 +02:00
|
|
|
// a branch cannot exist without the parent
|
2023-05-05 23:41:11 +02:00
|
|
|
// a note loaded into froca has to also contain all its ancestors,
|
2023-06-30 11:18:34 +02:00
|
|
|
// this problem happened, e.g., in sharing where _share was hidden and thus not loaded
|
2023-03-29 23:16:45 +02:00
|
|
|
// sharing meant cloning into _share, which crashed because _share was not loaded
|
2024-07-25 20:42:56 +03:00
|
|
|
parentNote = await froca.getNote(branchEntity.parentNoteId);
|
2023-03-29 23:16:45 +02:00
|
|
|
}
|
2021-08-20 21:42:06 +02:00
|
|
|
|
|
|
|
|
if (branch) {
|
2024-07-25 20:42:56 +03:00
|
|
|
branch.update(ec.entity as FBranch);
|
2021-08-20 21:42:06 +02:00
|
|
|
}
|
|
|
|
|
else if (childNote || parentNote) {
|
2024-07-25 20:42:56 +03:00
|
|
|
froca.branches[ec.entityId] = branch = new FBranch(froca, branchEntity);
|
2021-08-20 21:42:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (childNote) {
|
|
|
|
|
childNote.addParent(branch.parentNoteId, branch.branchId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parentNote) {
|
|
|
|
|
parentNote.addChild(branch.noteId, branch.branchId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
function processNoteReordering(loadResults: LoadResults, ec: EntityChange) {
|
|
|
|
|
const parentNoteIdsToSort = new Set<string>();
|
2021-08-20 21:42:06 +02:00
|
|
|
|
|
|
|
|
for (const branchId in ec.positions) {
|
|
|
|
|
const branch = froca.branches[branchId];
|
|
|
|
|
|
|
|
|
|
if (branch) {
|
|
|
|
|
branch.notePosition = ec.positions[branchId];
|
|
|
|
|
|
|
|
|
|
parentNoteIdsToSort.add(branch.parentNoteId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const parentNoteId of parentNoteIdsToSort) {
|
|
|
|
|
const parentNote = froca.notes[parentNoteId];
|
|
|
|
|
|
|
|
|
|
if (parentNote) {
|
|
|
|
|
parentNote.sortChildren();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
if (ec.componentId) {
|
|
|
|
|
loadResults.addNoteReordering(ec.entityId, ec.componentId);
|
|
|
|
|
}
|
2021-08-20 21:42:06 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
function processAttributeChange(loadResults: LoadResults, ec: EntityChange) {
|
2021-08-20 21:42:06 +02:00
|
|
|
let attribute = froca.attributes[ec.entityId];
|
|
|
|
|
|
2021-08-24 22:59:51 +02:00
|
|
|
if (ec.isErased && ec.entityId in froca.attributes) {
|
2023-05-03 10:23:20 +02:00
|
|
|
utils.reloadFrontendApp(`${ec.entityName} '${ec.entityId}' is erased, need to do complete reload.`);
|
2021-08-24 22:59:51 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-20 21:42:06 +02:00
|
|
|
if (ec.isErased || ec.entity?.isDeleted) {
|
|
|
|
|
if (attribute) {
|
|
|
|
|
const sourceNote = froca.notes[attribute.noteId];
|
|
|
|
|
const targetNote = attribute.type === 'relation' && froca.notes[attribute.value];
|
|
|
|
|
|
|
|
|
|
if (sourceNote) {
|
|
|
|
|
sourceNote.attributes = sourceNote.attributes.filter(attributeId => attributeId !== attribute.attributeId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (targetNote) {
|
|
|
|
|
targetNote.targetRelations = targetNote.targetRelations.filter(attributeId => attributeId !== attribute.attributeId);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
if (ec.componentId) {
|
|
|
|
|
loadResults.addAttribute(ec.entityId, ec.componentId);
|
|
|
|
|
}
|
2021-08-24 22:37:00 +02:00
|
|
|
|
2021-08-20 21:42:06 +02:00
|
|
|
delete froca.attributes[ec.entityId];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
if (ec.componentId) {
|
|
|
|
|
loadResults.addAttribute(ec.entityId, ec.componentId);
|
|
|
|
|
}
|
2021-08-24 22:37:00 +02:00
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
const attributeEntity = ec.entity as FAttributeRow;
|
|
|
|
|
const sourceNote = froca.notes[attributeEntity.noteId];
|
|
|
|
|
const targetNote = attributeEntity.type === 'relation' && froca.notes[attributeEntity.value];
|
2021-08-20 21:42:06 +02:00
|
|
|
|
|
|
|
|
if (attribute) {
|
2024-07-25 20:42:56 +03:00
|
|
|
attribute.update(ec.entity as FAttributeRow);
|
2021-08-20 21:42:06 +02:00
|
|
|
} else if (sourceNote || targetNote) {
|
2024-07-25 20:42:56 +03:00
|
|
|
attribute = new FAttribute(froca, ec.entity as FAttributeRow);
|
2021-08-20 21:42:06 +02:00
|
|
|
|
|
|
|
|
froca.attributes[attribute.attributeId] = attribute;
|
|
|
|
|
|
|
|
|
|
if (sourceNote && !sourceNote.attributes.includes(attribute.attributeId)) {
|
|
|
|
|
sourceNote.attributes.push(attribute.attributeId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (targetNote && !targetNote.targetRelations.includes(attribute.attributeId)) {
|
|
|
|
|
targetNote.targetRelations.push(attribute.attributeId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
function processAttachment(loadResults: LoadResults, ec: EntityChange) {
|
2023-04-03 23:47:24 +02:00
|
|
|
if (ec.isErased && ec.entityId in froca.attachments) {
|
2023-05-03 10:23:20 +02:00
|
|
|
utils.reloadFrontendApp(`${ec.entityName} '${ec.entityId}' is erased, need to do complete reload.`);
|
2023-04-03 23:47:24 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const attachment = froca.attachments[ec.entityId];
|
2024-07-25 20:42:56 +03:00
|
|
|
const attachmentEntity = ec.entity as FAttachmentRow;
|
2023-04-03 23:47:24 +02:00
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
if (ec.isErased || (ec.entity as any)?.isDeleted) {
|
2023-04-03 23:47:24 +02:00
|
|
|
if (attachment) {
|
|
|
|
|
const note = attachment.getNote();
|
|
|
|
|
|
|
|
|
|
if (note && note.attachments) {
|
|
|
|
|
note.attachments = note.attachments.filter(att => att.attachmentId !== attachment.attachmentId);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
loadResults.addAttachmentRow(attachmentEntity);
|
2023-04-03 23:47:24 +02:00
|
|
|
|
|
|
|
|
delete froca.attachments[ec.entityId];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attachment) {
|
2024-07-25 20:42:56 +03:00
|
|
|
attachment.update(ec.entity as FAttachmentRow);
|
2023-04-03 23:47:24 +02:00
|
|
|
} else {
|
2024-07-25 20:42:56 +03:00
|
|
|
const attachmentRow = ec.entity as FAttachmentRow;
|
|
|
|
|
const note = froca.notes[attachmentRow.ownerId];
|
2023-04-03 23:47:24 +02:00
|
|
|
|
2023-05-29 00:19:54 +02:00
|
|
|
if (note?.attachments) {
|
2024-07-25 20:42:56 +03:00
|
|
|
note.attachments.push(new FAttachment(froca, attachmentRow));
|
2023-04-03 23:47:24 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 20:42:56 +03:00
|
|
|
loadResults.addAttachmentRow(attachmentEntity);
|
2023-04-03 23:47:24 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-20 21:42:06 +02:00
|
|
|
export default {
|
|
|
|
|
processEntityChanges
|
|
|
|
|
}
|