mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	#126, added skeleton of note relations, copied from similar concept of labels
This commit is contained in:
		
							
								
								
									
										222
									
								
								src/public/javascripts/dialogs/relations.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								src/public/javascripts/dialogs/relations.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
			
		||||
import noteDetailService from '../services/note_detail.js';
 | 
			
		||||
import server from '../services/server.js';
 | 
			
		||||
import infoService from "../services/info.js";
 | 
			
		||||
 | 
			
		||||
const $dialog = $("#relations-dialog");
 | 
			
		||||
const $saveRelationsButton = $("#save-relations-button");
 | 
			
		||||
const $relationsBody = $('#relations-table tbody');
 | 
			
		||||
 | 
			
		||||
const relationsModel = new RelationsModel();
 | 
			
		||||
let relationNames = [];
 | 
			
		||||
 | 
			
		||||
function RelationsModel() {
 | 
			
		||||
    const self = this;
 | 
			
		||||
 | 
			
		||||
    this.relations = ko.observableArray();
 | 
			
		||||
 | 
			
		||||
    this.updateRelationPositions = function() {
 | 
			
		||||
        let position = 0;
 | 
			
		||||
 | 
			
		||||
        // we need to update positions by searching in the DOM, because order of the
 | 
			
		||||
        // relations in the viewmodel (self.relations()) stays the same
 | 
			
		||||
        $relationsBody.find('input[name="position"]').each(function() {
 | 
			
		||||
            const relation = self.getTargetRelation(this);
 | 
			
		||||
 | 
			
		||||
            relation().position = position++;
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.loadRelations = async function() {
 | 
			
		||||
        const noteId = noteDetailService.getCurrentNoteId();
 | 
			
		||||
 | 
			
		||||
        const relations = await server.get('notes/' + noteId + '/relations');
 | 
			
		||||
 | 
			
		||||
        self.relations(relations.map(ko.observable));
 | 
			
		||||
 | 
			
		||||
        addLastEmptyRow();
 | 
			
		||||
 | 
			
		||||
        relationNames = await server.get('relations/names');
 | 
			
		||||
 | 
			
		||||
        // relation might not be rendered immediatelly so could not focus
 | 
			
		||||
        setTimeout(() => $(".relation-name:last").focus(), 100);
 | 
			
		||||
 | 
			
		||||
        $relationsBody.sortable({
 | 
			
		||||
            handle: '.handle',
 | 
			
		||||
            containment: $relationsBody,
 | 
			
		||||
            update: this.updateRelationPositions
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.deleteRelation = function(data, event) {
 | 
			
		||||
        const relation = self.getTargetRelation(event.target);
 | 
			
		||||
        const relationData = relation();
 | 
			
		||||
 | 
			
		||||
        if (relationData) {
 | 
			
		||||
            relationData.isDeleted = 1;
 | 
			
		||||
 | 
			
		||||
            relation(relationData);
 | 
			
		||||
 | 
			
		||||
            addLastEmptyRow();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function isValid() {
 | 
			
		||||
        for (let relations = self.relations(), i = 0; i < relations.length; i++) {
 | 
			
		||||
            if (self.isEmptyName(i)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.save = async function() {
 | 
			
		||||
        // we need to defocus from input (in case of enter-triggered save) because value is updated
 | 
			
		||||
        // on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
 | 
			
		||||
        // stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
 | 
			
		||||
        $saveRelationsButton.focus();
 | 
			
		||||
 | 
			
		||||
        if (!isValid()) {
 | 
			
		||||
            alert("Please fix all validation errors and try saving again.");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.updateRelationPositions();
 | 
			
		||||
 | 
			
		||||
        const noteId = noteDetailService.getCurrentNoteId();
 | 
			
		||||
 | 
			
		||||
        const relationsToSave = self.relations()
 | 
			
		||||
            .map(relation => relation())
 | 
			
		||||
            .filter(relation => relation.relationId !== "" || relation.name !== "");
 | 
			
		||||
 | 
			
		||||
        const relations = await server.put('notes/' + noteId + '/relations', relationsToSave);
 | 
			
		||||
 | 
			
		||||
        self.relations(relations.map(ko.observable));
 | 
			
		||||
 | 
			
		||||
        addLastEmptyRow();
 | 
			
		||||
 | 
			
		||||
        infoService.showMessage("Relations have been saved.");
 | 
			
		||||
 | 
			
		||||
        noteDetailService.loadRelationList();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function addLastEmptyRow() {
 | 
			
		||||
        const relations = self.relations().filter(attr => attr().isDeleted === 0);
 | 
			
		||||
        const last = relations.length === 0 ? null : relations[relations.length - 1]();
 | 
			
		||||
 | 
			
		||||
        if (!last || last.name.trim() !== "" || last.value !== "") {
 | 
			
		||||
            self.relations.push(ko.observable({
 | 
			
		||||
                relationId: '',
 | 
			
		||||
                name: '',
 | 
			
		||||
                value: '',
 | 
			
		||||
                isDeleted: 0,
 | 
			
		||||
                position: 0
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.relationChanged = function (data, event) {
 | 
			
		||||
        addLastEmptyRow();
 | 
			
		||||
 | 
			
		||||
        const relation = self.getTargetRelation(event.target);
 | 
			
		||||
 | 
			
		||||
        relation.valueHasMutated();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.isNotUnique = function(index) {
 | 
			
		||||
        const cur = self.relations()[index]();
 | 
			
		||||
 | 
			
		||||
        if (cur.name.trim() === "") {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (let relations = self.relations(), i = 0; i < relations.length; i++) {
 | 
			
		||||
            const relation = relations[i]();
 | 
			
		||||
 | 
			
		||||
            if (index !== i && cur.name === relation.name) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.isEmptyName = function(index) {
 | 
			
		||||
        const cur = self.relations()[index]();
 | 
			
		||||
 | 
			
		||||
        return cur.name.trim() === "" && (cur.relationId !== "" || cur.value !== "");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.getTargetRelation = function(target) {
 | 
			
		||||
        const context = ko.contextFor(target);
 | 
			
		||||
        const index = context.$index();
 | 
			
		||||
 | 
			
		||||
        return self.relations()[index];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function showDialog() {
 | 
			
		||||
    glob.activeDialog = $dialog;
 | 
			
		||||
 | 
			
		||||
    await relationsModel.loadRelations();
 | 
			
		||||
 | 
			
		||||
    $dialog.dialog({
 | 
			
		||||
        modal: true,
 | 
			
		||||
        width: 800,
 | 
			
		||||
        height: 500
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ko.applyBindings(relationsModel, document.getElementById('relations-dialog'));
 | 
			
		||||
 | 
			
		||||
$(document).on('focus', '.relation-name', function (e) {
 | 
			
		||||
    if (!$(this).hasClass("ui-autocomplete-input")) {
 | 
			
		||||
        $(this).autocomplete({
 | 
			
		||||
            // shouldn't be required and autocomplete should just accept array of strings, but that fails
 | 
			
		||||
            // because we have overriden filter() function in autocomplete.js
 | 
			
		||||
            source: relationNames.map(relation => {
 | 
			
		||||
                return {
 | 
			
		||||
                    relation: relation,
 | 
			
		||||
                    value: relation
 | 
			
		||||
                }
 | 
			
		||||
            }),
 | 
			
		||||
            minLength: 0
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $(this).autocomplete("search", $(this).val());
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$(document).on('focus', '.relation-value', async function (e) {
 | 
			
		||||
    if (!$(this).hasClass("ui-autocomplete-input")) {
 | 
			
		||||
        const relationName = $(this).parent().parent().find('.relation-name').val();
 | 
			
		||||
 | 
			
		||||
        if (relationName.trim() === "") {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const relationValues = await server.get('relations/values/' + encodeURIComponent(relationName));
 | 
			
		||||
 | 
			
		||||
        if (relationValues.length === 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $(this).autocomplete({
 | 
			
		||||
            // shouldn't be required and autocomplete should just accept array of strings, but that fails
 | 
			
		||||
            // because we have overriden filter() function in autocomplete.js
 | 
			
		||||
            source: relationValues.map(relation => {
 | 
			
		||||
                return {
 | 
			
		||||
                    label: relation,
 | 
			
		||||
                    value: relation
 | 
			
		||||
                }
 | 
			
		||||
            }),
 | 
			
		||||
            minLength: 0
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $(this).autocomplete("search", $(this).val());
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    showDialog
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user