mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	
		
			
	
	
		
			222 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			222 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 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 | ||
|  | }; |