mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			183 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const eventService = require('./events');
 | 
						|
const scriptService = require('./script');
 | 
						|
const treeService = require('./tree');
 | 
						|
const noteService = require('./notes');
 | 
						|
const becca = require('../becca/becca');
 | 
						|
const Attribute = require('../becca/entities/attribute');
 | 
						|
 | 
						|
function runAttachedRelations(note, relationName, originEntity) {
 | 
						|
    if (!note) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    // same script note can get here with multiple ways, but execute only once
 | 
						|
    const notesToRun = new Set(
 | 
						|
        note.getRelations(relationName)
 | 
						|
            .map(relation => relation.getTargetNote())
 | 
						|
            .filter(note => !!note)
 | 
						|
    );
 | 
						|
 | 
						|
    for (const noteToRun of notesToRun) {
 | 
						|
        scriptService.executeNoteNoException(noteToRun, { originEntity });
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
eventService.subscribe(eventService.NOTE_TITLE_CHANGED, note => {
 | 
						|
    runAttachedRelations(note, 'runOnNoteTitleChange', note);
 | 
						|
 | 
						|
    if (!note.isRoot()) {
 | 
						|
        const noteFromCache = becca.notes[note.noteId];
 | 
						|
 | 
						|
        if (!noteFromCache) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        for (const parentNote of noteFromCache.parents) {
 | 
						|
            if (parentNote.hasLabel("sorted")) {
 | 
						|
                treeService.sortNotesIfNeeded(parentNote.noteId);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
});
 | 
						|
 | 
						|
eventService.subscribe([ eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED ], ({ entityName, entity }) => {
 | 
						|
    if (entityName === 'attributes') {
 | 
						|
        runAttachedRelations(entity.getNote(), 'runOnAttributeChange', entity);
 | 
						|
 | 
						|
        if (entity.type === 'label' && ['sorted', 'sortDirection', 'sortFoldersFirst'].includes(entity.name)) {
 | 
						|
            handleSortedAttribute(entity);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (entityName === 'notes') {
 | 
						|
        // ENTITY_DELETED won't trigger anything since all branches/attributes are already deleted at this point
 | 
						|
        runAttachedRelations(entity, 'runOnNoteChange', entity);
 | 
						|
    }
 | 
						|
});
 | 
						|
 | 
						|
eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => {
 | 
						|
    if (entityName === 'attributes') {
 | 
						|
        runAttachedRelations(entity.getNote(), 'runOnAttributeCreation', entity);
 | 
						|
 | 
						|
        if (entity.type === 'relation' && entity.name === 'template') {
 | 
						|
            const note = becca.getNote(entity.noteId);
 | 
						|
 | 
						|
            const templateNote = becca.getNote(entity.value);
 | 
						|
 | 
						|
            if (!templateNote) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            const content = note.getContent();
 | 
						|
 | 
						|
            if (["text", "code"].includes(note.type)
 | 
						|
                // if the note has already content we're not going to overwrite it with template's one
 | 
						|
                && (!content || content.trim().length === 0)
 | 
						|
                && templateNote.isStringNote()) {
 | 
						|
 | 
						|
                const templateNoteContent = templateNote.getContent();
 | 
						|
 | 
						|
                if (templateNoteContent) {
 | 
						|
                    note.setContent(templateNoteContent);
 | 
						|
                }
 | 
						|
 | 
						|
                note.type = templateNote.type;
 | 
						|
                note.mime = templateNote.mime;
 | 
						|
                note.save();
 | 
						|
            }
 | 
						|
 | 
						|
            // 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);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else if (entity.type === 'label' && entity.name === 'sorted') {
 | 
						|
            handleSortedAttribute(entity);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (entityName === 'branches') {
 | 
						|
        runAttachedRelations(entity.getNote(), 'runOnBranchCreation', entity);
 | 
						|
    }
 | 
						|
    else if (entityName === 'notes') {
 | 
						|
        runAttachedRelations(entity, 'runOnNoteCreation', entity);
 | 
						|
    }
 | 
						|
});
 | 
						|
 | 
						|
eventService.subscribe(eventService.CHILD_NOTE_CREATED, ({ parentNote, childNote }) => {
 | 
						|
    runAttachedRelations(parentNote, 'runOnChildNoteCreation', childNote);
 | 
						|
});
 | 
						|
 | 
						|
function processInverseRelations(entityName, entity, handler) {
 | 
						|
    if (entityName === 'attributes' && entity.type === 'relation') {
 | 
						|
        const note = entity.getNote();
 | 
						|
        const relDefinitions = note.getLabels('relation:' + entity.name);
 | 
						|
 | 
						|
        for (const relDefinition of relDefinitions) {
 | 
						|
            const definition = relDefinition.getDefinition();
 | 
						|
 | 
						|
            if (definition.inverseRelation && definition.inverseRelation.trim()) {
 | 
						|
                const targetNote = entity.getTargetNote();
 | 
						|
 | 
						|
                handler(definition, note, targetNote);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
function handleSortedAttribute(entity) {
 | 
						|
    treeService.sortNotesIfNeeded(entity.noteId);
 | 
						|
 | 
						|
    if (entity.isInheritable) {
 | 
						|
        const note = becca.notes[entity.noteId];
 | 
						|
 | 
						|
        if (note) {
 | 
						|
            for (const noteId of note.getSubtreeNoteIds()) {
 | 
						|
                treeService.sortNotesIfNeeded(noteId);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => {
 | 
						|
    processInverseRelations(entityName, entity, (definition, note, targetNote) => {
 | 
						|
        // we need to make sure that also target's inverse attribute exists and if not, then create it
 | 
						|
        // inverse attribute has to target our note as well
 | 
						|
        const hasInverseAttribute = (targetNote.getRelations(definition.inverseRelation))
 | 
						|
            .some(attr => attr.value === note.noteId);
 | 
						|
 | 
						|
        if (!hasInverseAttribute) {
 | 
						|
            new Attribute({
 | 
						|
                noteId: targetNote.noteId,
 | 
						|
                type: 'relation',
 | 
						|
                name: definition.inverseRelation,
 | 
						|
                value: note.noteId,
 | 
						|
                isInheritable: entity.isInheritable
 | 
						|
            }).save();
 | 
						|
 | 
						|
            // becca will not be updated before we'll check from the other side which would create infinite relation creation (#2269)
 | 
						|
            targetNote.invalidateThisCache();
 | 
						|
        }
 | 
						|
    });
 | 
						|
});
 | 
						|
 | 
						|
eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) => {
 | 
						|
    processInverseRelations(entityName, entity, (definition, note, targetNote) => {
 | 
						|
        // if one inverse attribute is deleted then the other should be deleted as well
 | 
						|
        const relations = targetNote.getOwnedRelations(definition.inverseRelation);
 | 
						|
 | 
						|
        for (const relation of relations) {
 | 
						|
            if (relation.value === note.noteId) {
 | 
						|
                relation.markAsDeleted();
 | 
						|
            }
 | 
						|
        }
 | 
						|
    });
 | 
						|
 | 
						|
    if (entityName === 'branches') {
 | 
						|
        runAttachedRelations(entity.getNote(), 'runOnBranchDeletion', entity);
 | 
						|
    }
 | 
						|
});
 | 
						|
 | 
						|
module.exports = {
 | 
						|
    runAttachedRelations
 | 
						|
};
 |