2024-07-18 21:35:17 +03:00
|
|
|
import eventService from "./events.js";
|
|
|
|
|
import scriptService from "./script.js";
|
|
|
|
|
import treeService from "./tree.js";
|
|
|
|
|
import noteService from "./notes.js";
|
|
|
|
|
import becca from "../becca/becca.js";
|
|
|
|
|
import BAttribute from "../becca/entities/battribute.js";
|
|
|
|
|
import hiddenSubtreeService from "./hidden_subtree.js";
|
|
|
|
|
import oneTimeTimer from "./one_time_timer.js";
|
2025-01-13 23:18:10 +02:00
|
|
|
import type BNote from "../becca/entities/bnote.js";
|
|
|
|
|
import type AbstractBeccaEntity from "../becca/entities/abstract_becca_entity.js";
|
2025-01-09 18:36:24 +02:00
|
|
|
import type { DefinitionObject } from "./promoted_attribute_definition_interface.js";
|
2024-04-04 23:04:54 +03:00
|
|
|
|
|
|
|
|
type Handler = (definition: DefinitionObject, note: BNote, targetNote: BNote) => void;
|
|
|
|
|
|
|
|
|
|
function runAttachedRelations(note: BNote, relationName: string, originEntity: AbstractBeccaEntity<any>) {
|
2022-04-16 15:10:16 +02:00
|
|
|
if (!note) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-05 23:41:11 +02:00
|
|
|
// the same script note can get here with multiple ways, but execute only once
|
2020-10-12 22:30:30 +02:00
|
|
|
const notesToRun = new Set(
|
2025-01-09 18:07:02 +02:00
|
|
|
note
|
|
|
|
|
.getRelations(relationName)
|
|
|
|
|
.map((relation) => relation.getTargetNote())
|
|
|
|
|
.filter((note) => !!note) as BNote[]
|
2020-10-12 22:30:30 +02:00
|
|
|
);
|
2018-08-01 09:26:02 +02:00
|
|
|
|
2020-10-12 22:30:30 +02:00
|
|
|
for (const noteToRun of notesToRun) {
|
|
|
|
|
scriptService.executeNoteNoException(noteToRun, { originEntity });
|
2018-08-01 09:26:02 +02:00
|
|
|
}
|
2018-08-09 20:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
eventService.subscribe(eventService.NOTE_TITLE_CHANGED, (note) => {
|
|
|
|
|
runAttachedRelations(note, "runOnNoteTitleChange", note);
|
2018-08-01 09:26:02 +02:00
|
|
|
|
|
|
|
|
if (!note.isRoot()) {
|
2021-04-16 23:00:08 +02:00
|
|
|
const noteFromCache = becca.notes[note.noteId];
|
2018-08-01 09:26:02 +02:00
|
|
|
|
2021-01-30 22:25:40 +01:00
|
|
|
if (!noteFromCache) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-23 21:00:59 +01:00
|
|
|
for (const parentNote of noteFromCache.parents) {
|
|
|
|
|
if (parentNote.hasLabel("sorted")) {
|
2021-09-03 22:33:40 +02:00
|
|
|
treeService.sortNotesIfNeeded(parentNote.noteId);
|
2018-08-01 09:26:02 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-09 20:08:00 +02:00
|
|
|
});
|
|
|
|
|
|
2024-04-04 22:47:58 +03:00
|
|
|
eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED], ({ entityName, entity }) => {
|
2025-01-09 18:07:02 +02:00
|
|
|
if (entityName === "attributes") {
|
|
|
|
|
runAttachedRelations(entity.getNote(), "runOnAttributeChange", entity);
|
2021-11-23 23:09:29 +01:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (entity.type === "label" && ["sorted", "sortDirection", "sortFoldersFirst", "sortNatural", "sortLocale"].includes(entity.name)) {
|
2021-11-23 23:09:29 +01:00
|
|
|
handleSortedAttribute(entity);
|
2025-01-09 18:07:02 +02:00
|
|
|
} else if (entity.type === "label") {
|
2022-12-14 23:44:26 +01:00
|
|
|
handleMaybeSortingLabel(entity);
|
2021-11-23 23:09:29 +01:00
|
|
|
}
|
2025-01-09 18:07:02 +02:00
|
|
|
} else if (entityName === "notes") {
|
2022-06-05 14:58:19 +02:00
|
|
|
// ENTITY_DELETED won't trigger anything since all branches/attributes are already deleted at this point
|
2025-01-09 18:07:02 +02:00
|
|
|
runAttachedRelations(entity, "runOnNoteChange", entity);
|
2018-08-15 22:06:49 +02:00
|
|
|
}
|
|
|
|
|
});
|
2018-08-09 20:08:00 +02:00
|
|
|
|
2024-04-04 22:47:58 +03:00
|
|
|
eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => {
|
2025-01-09 18:07:02 +02:00
|
|
|
if (entityName === "branches") {
|
2023-05-03 23:42:44 +02:00
|
|
|
const parentNote = becca.getNote(entity.parentNoteId);
|
|
|
|
|
|
|
|
|
|
if (parentNote?.hasLabel("sorted")) {
|
|
|
|
|
treeService.sortNotesIfNeeded(parentNote.noteId);
|
|
|
|
|
}
|
2023-11-02 23:26:32 +01:00
|
|
|
|
|
|
|
|
const childNote = becca.getNote(entity.noteId);
|
|
|
|
|
|
|
|
|
|
if (childNote) {
|
2025-01-09 18:07:02 +02:00
|
|
|
runAttachedRelations(childNote, "runOnBranchChange", entity);
|
2023-11-02 23:26:32 +01:00
|
|
|
}
|
2022-12-18 23:53:47 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-04-04 22:47:58 +03:00
|
|
|
eventService.subscribe(eventService.NOTE_CONTENT_CHANGE, ({ entity }) => {
|
2025-01-09 18:07:02 +02:00
|
|
|
runAttachedRelations(entity, "runOnNoteContentChange", entity);
|
2023-05-05 15:40:44 +02:00
|
|
|
});
|
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => {
|
2025-01-09 18:07:02 +02:00
|
|
|
if (entityName === "attributes") {
|
|
|
|
|
runAttachedRelations(entity.getNote(), "runOnAttributeCreation", entity);
|
2019-09-08 16:57:41 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (entity.type === "relation" && entity.name === "template") {
|
2021-05-02 11:23:58 +02:00
|
|
|
const note = becca.getNote(entity.noteId);
|
2024-04-04 23:04:54 +03:00
|
|
|
if (!note) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-09-08 16:57:41 +02:00
|
|
|
|
2021-05-02 11:23:58 +02:00
|
|
|
const templateNote = becca.getNote(entity.value);
|
2019-09-08 16:57:41 +02:00
|
|
|
|
2020-11-19 14:29:26 +01:00
|
|
|
if (!templateNote) {
|
2019-09-08 16:57:41 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-06 21:34:03 +01:00
|
|
|
const content = note.getContent();
|
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (
|
2025-03-08 04:28:24 +02:00
|
|
|
["text", "code", "mermaid", "canvas", "relationMap", "mindMap", "geoMap"].includes(note.type) &&
|
2025-01-09 18:07:02 +02:00
|
|
|
typeof content === "string" &&
|
2021-03-06 21:34:03 +01:00
|
|
|
// if the note has already content we're not going to overwrite it with template's one
|
2025-01-09 18:07:02 +02:00
|
|
|
(!content || content.trim().length === 0) &&
|
|
|
|
|
templateNote.hasStringContent()
|
|
|
|
|
) {
|
2020-11-19 14:29:26 +01:00
|
|
|
const templateNoteContent = templateNote.getContent();
|
2020-08-29 23:08:53 +02:00
|
|
|
|
2020-11-19 14:29:26 +01:00
|
|
|
if (templateNoteContent) {
|
|
|
|
|
note.setContent(templateNoteContent);
|
|
|
|
|
}
|
2020-12-07 09:35:39 +01:00
|
|
|
|
|
|
|
|
note.type = templateNote.type;
|
|
|
|
|
note.mime = templateNote.mime;
|
|
|
|
|
note.save();
|
2020-08-29 23:08:53 +02:00
|
|
|
}
|
2020-11-19 14:29:26 +01:00
|
|
|
|
2021-03-06 21:34:03 +01:00
|
|
|
// we'll copy the children notes only if there's none so far
|
|
|
|
|
// this protects against e.g. multiple assignment of template relation resulting in having multiple copies of the subtree
|
|
|
|
|
if (note.getChildNotes().length === 0 && !note.isDescendantOfNote(templateNote.noteId)) {
|
|
|
|
|
noteService.duplicateSubtreeWithoutRoot(templateNote.noteId, note.noteId);
|
|
|
|
|
}
|
2025-01-09 18:07:02 +02:00
|
|
|
} else if (entity.type === "label" && ["sorted", "sortDirection", "sortFoldersFirst", "sortNatural", "sortLocale"].includes(entity.name)) {
|
2021-11-23 23:09:29 +01:00
|
|
|
handleSortedAttribute(entity);
|
2025-01-09 18:07:02 +02:00
|
|
|
} else if (entity.type === "label") {
|
2022-12-14 23:44:26 +01:00
|
|
|
handleMaybeSortingLabel(entity);
|
|
|
|
|
}
|
2025-01-09 18:07:02 +02:00
|
|
|
} else if (entityName === "branches") {
|
|
|
|
|
runAttachedRelations(entity.getNote(), "runOnBranchCreation", entity);
|
2022-12-25 13:46:31 +01:00
|
|
|
|
|
|
|
|
if (entity.parentNote?.hasLabel("sorted")) {
|
|
|
|
|
treeService.sortNotesIfNeeded(entity.parentNoteId);
|
|
|
|
|
}
|
2025-01-09 18:07:02 +02:00
|
|
|
} else if (entityName === "notes") {
|
|
|
|
|
runAttachedRelations(entity, "runOnNoteCreation", entity);
|
2018-08-15 22:06:49 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
eventService.subscribe(eventService.CHILD_NOTE_CREATED, ({ parentNote, childNote }) => {
|
2025-01-09 18:07:02 +02:00
|
|
|
runAttachedRelations(parentNote, "runOnChildNoteCreation", childNote);
|
2018-11-12 23:34:22 +01:00
|
|
|
});
|
|
|
|
|
|
2024-04-04 23:04:54 +03:00
|
|
|
function processInverseRelations(entityName: string, entity: BAttribute, handler: Handler) {
|
2025-01-09 18:07:02 +02:00
|
|
|
if (entityName === "attributes" && entity.type === "relation") {
|
2020-06-20 12:31:38 +02:00
|
|
|
const note = entity.getNote();
|
2022-12-21 15:19:05 +01:00
|
|
|
const relDefinitions = note.getLabels(`relation:${entity.name}`);
|
2018-11-12 23:34:22 +01:00
|
|
|
|
2020-12-21 20:55:01 +01:00
|
|
|
for (const relDefinition of relDefinitions) {
|
|
|
|
|
const definition = relDefinition.getDefinition();
|
2018-11-12 23:34:22 +01:00
|
|
|
|
2018-11-19 12:07:33 +01:00
|
|
|
if (definition.inverseRelation && definition.inverseRelation.trim()) {
|
2020-06-20 12:31:38 +02:00
|
|
|
const targetNote = entity.getTargetNote();
|
2018-11-12 23:34:22 +01:00
|
|
|
|
2024-04-04 23:04:54 +03:00
|
|
|
if (targetNote) {
|
|
|
|
|
handler(definition, note, targetNote);
|
|
|
|
|
}
|
2018-11-12 23:34:22 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-04 23:04:54 +03:00
|
|
|
function handleSortedAttribute(entity: BAttribute) {
|
2021-11-23 23:09:29 +01:00
|
|
|
treeService.sortNotesIfNeeded(entity.noteId);
|
|
|
|
|
|
|
|
|
|
if (entity.isInheritable) {
|
|
|
|
|
const note = becca.notes[entity.noteId];
|
|
|
|
|
|
|
|
|
|
if (note) {
|
|
|
|
|
for (const noteId of note.getSubtreeNoteIds()) {
|
|
|
|
|
treeService.sortNotesIfNeeded(noteId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-04 23:04:54 +03:00
|
|
|
function handleMaybeSortingLabel(entity: BAttribute) {
|
2022-12-14 23:44:26 +01:00
|
|
|
// check if this label is used for sorting, if yes force re-sort
|
|
|
|
|
const note = becca.notes[entity.noteId];
|
|
|
|
|
|
|
|
|
|
// this will not work on deleted notes, but in that case we don't really need to re-sort
|
|
|
|
|
if (note) {
|
|
|
|
|
for (const parentNote of note.getParentNotes()) {
|
|
|
|
|
const sorted = parentNote.getLabelValue("sorted");
|
2023-09-25 23:25:00 +02:00
|
|
|
if (sorted === null) {
|
|
|
|
|
// checking specifically for null since that means the label doesn't exist
|
|
|
|
|
// empty valued "sorted" is still valid
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-12-14 23:44:26 +01:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (
|
|
|
|
|
sorted.includes(entity.name) || // hacky check if this label is used in the sort
|
|
|
|
|
entity.name === "top" ||
|
|
|
|
|
entity.name === "bottom"
|
|
|
|
|
) {
|
2022-12-14 23:44:26 +01:00
|
|
|
treeService.sortNotesIfNeeded(parentNote.noteId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => {
|
|
|
|
|
processInverseRelations(entityName, entity, (definition, note, targetNote) => {
|
2021-08-07 21:21:30 +02:00
|
|
|
// we need to make sure that also target's inverse attribute exists and if not, then create it
|
2018-11-19 12:07:33 +01:00
|
|
|
// inverse attribute has to target our note as well
|
2025-01-09 18:07:02 +02:00
|
|
|
const hasInverseAttribute = targetNote.getRelations(definition.inverseRelation).some((attr) => attr.value === note.noteId);
|
2018-11-19 00:06:04 +01:00
|
|
|
|
2018-11-19 12:07:33 +01:00
|
|
|
if (!hasInverseAttribute) {
|
2023-01-03 13:52:37 +01:00
|
|
|
new BAttribute({
|
2018-11-12 23:34:22 +01:00
|
|
|
noteId: targetNote.noteId,
|
2025-01-09 18:07:02 +02:00
|
|
|
type: "relation",
|
2024-04-04 23:04:54 +03:00
|
|
|
name: definition.inverseRelation || "",
|
2018-11-12 23:34:22 +01:00
|
|
|
value: note.noteId,
|
|
|
|
|
isInheritable: entity.isInheritable
|
|
|
|
|
}).save();
|
2021-10-27 22:36:33 +02:00
|
|
|
|
|
|
|
|
// becca will not be updated before we'll check from the other side which would create infinite relation creation (#2269)
|
|
|
|
|
targetNote.invalidateThisCache();
|
2018-11-12 23:34:22 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) => {
|
2024-04-04 23:04:54 +03:00
|
|
|
processInverseRelations(entityName, entity, (definition: DefinitionObject, note: BNote, targetNote: BNote) => {
|
2023-05-05 23:41:11 +02:00
|
|
|
// if one inverse attribute is deleted, then the other should be deleted as well
|
2020-06-20 12:31:38 +02:00
|
|
|
const relations = targetNote.getOwnedRelations(definition.inverseRelation);
|
2018-11-12 23:34:22 +01:00
|
|
|
|
|
|
|
|
for (const relation of relations) {
|
2018-11-19 00:06:04 +01:00
|
|
|
if (relation.value === note.noteId) {
|
2021-05-02 20:32:50 +02:00
|
|
|
relation.markAsDeleted();
|
2018-11-19 00:06:04 +01:00
|
|
|
}
|
2018-11-12 23:34:22 +01:00
|
|
|
}
|
|
|
|
|
});
|
2022-06-05 14:58:19 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (entityName === "branches") {
|
|
|
|
|
runAttachedRelations(entity.getNote(), "runOnBranchDeletion", entity);
|
2022-06-05 14:58:19 +02:00
|
|
|
}
|
2023-01-13 11:53:25 +01:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (entityName === "notes" && entity.noteId.startsWith("_")) {
|
2023-01-13 11:53:25 +01:00
|
|
|
// "named" note has been deleted, we will probably need to rebuild the hidden subtree
|
|
|
|
|
// scheduling so that bulk deletes won't trigger so many checks
|
2025-01-09 18:07:02 +02:00
|
|
|
oneTimeTimer.scheduleExecution("hidden-subtree-check", 1000, () => hiddenSubtreeService.checkHiddenSubtree());
|
2023-01-13 11:53:25 +01:00
|
|
|
}
|
2020-06-20 12:31:38 +02:00
|
|
|
});
|
2022-06-05 14:58:19 +02:00
|
|
|
|
2024-07-18 21:42:44 +03:00
|
|
|
export default {
|
2022-06-05 14:58:19 +02:00
|
|
|
runAttachedRelations
|
|
|
|
|
};
|