mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			205 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 
 | |
| const attributesDialog = (function() {
 | |
|     const dialogEl = $("#attributes-dialog");
 | |
|     const saveAttributesButton = $("#save-attributes-button");
 | |
|     const attributesModel = new AttributesModel();
 | |
|     let attributeNames = [];
 | |
| 
 | |
|     function AttributesModel() {
 | |
|         const self = this;
 | |
| 
 | |
|         this.attributes = ko.observableArray();
 | |
| 
 | |
|         this.loadAttributes = async function() {
 | |
|             const noteId = noteEditor.getCurrentNoteId();
 | |
| 
 | |
|             const attributes = await server.get('notes/' + noteId + '/attributes');
 | |
| 
 | |
|             self.attributes(attributes.map(ko.observable));
 | |
| 
 | |
|             addLastEmptyRow();
 | |
| 
 | |
|             attributeNames = await server.get('attributes/names');
 | |
| 
 | |
|             // attribute might not be rendered immediatelly so could not focus
 | |
|             setTimeout(() => $(".attribute-name:last").focus(), 100);
 | |
|         };
 | |
| 
 | |
|         this.deleteAttribute = function(data, event) {
 | |
|             const attr = self.getTargetAttribute(event);
 | |
|             const attrData = attr();
 | |
| 
 | |
|             if (attrData) {
 | |
|                 attrData.isDeleted = 1;
 | |
| 
 | |
|                 attr(attrData);
 | |
| 
 | |
|                 addLastEmptyRow();
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         function isValid() {
 | |
|             for (let attrs = self.attributes(), i = 0; i < attrs.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.
 | |
|             saveAttributesButton.focus();
 | |
| 
 | |
|             if (!isValid()) {
 | |
|                 alert("Please fix all validation errors and try saving again.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const noteId = noteEditor.getCurrentNoteId();
 | |
| 
 | |
|             const attributesToSave = self.attributes()
 | |
|                 .map(attr => attr())
 | |
|                 .filter(attr => attr.attributeId !== "" || attr.name !== "");
 | |
| 
 | |
|             const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);
 | |
| 
 | |
|             self.attributes(attributes.map(ko.observable));
 | |
| 
 | |
|             addLastEmptyRow();
 | |
| 
 | |
|             showMessage("Attributes have been saved.");
 | |
| 
 | |
|             noteEditor.loadAttributeList();
 | |
|         };
 | |
| 
 | |
|         function addLastEmptyRow() {
 | |
|             const attrs = self.attributes().filter(attr => attr().isDeleted === 0);
 | |
|             const last = attrs.length === 0 ? null : attrs[attrs.length - 1]();
 | |
| 
 | |
|             if (!last || last.name.trim() !== "" || last.value !== "") {
 | |
|                 self.attributes.push(ko.observable({
 | |
|                     attributeId: '',
 | |
|                     name: '',
 | |
|                     value: '',
 | |
|                     isDeleted: 0
 | |
|                 }));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         this.attributeChanged = function (data, event) {
 | |
|             addLastEmptyRow();
 | |
| 
 | |
|             const attr = self.getTargetAttribute(event);
 | |
| 
 | |
|             attr.valueHasMutated();
 | |
|         };
 | |
| 
 | |
|         this.isNotUnique = function(index) {
 | |
|             const cur = self.attributes()[index]();
 | |
| 
 | |
|             if (cur.name.trim() === "") {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) {
 | |
|                 const attr = attrs[i]();
 | |
| 
 | |
|                 if (index !== i && cur.name === attr.name) {
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return false;
 | |
|         };
 | |
| 
 | |
|         this.isEmptyName = function(index) {
 | |
|             const cur = self.attributes()[index]();
 | |
| 
 | |
|             return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== "");
 | |
|         };
 | |
| 
 | |
|         this.getTargetAttribute = function(event) {
 | |
|             const context = ko.contextFor(event.target);
 | |
|             const index = context.$index();
 | |
| 
 | |
|             return self.attributes()[index];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     async function showDialog() {
 | |
|         glob.activeDialog = dialogEl;
 | |
| 
 | |
|         await attributesModel.loadAttributes();
 | |
| 
 | |
|         dialogEl.dialog({
 | |
|             modal: true,
 | |
|             width: 800,
 | |
|             height: 500
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     $(document).bind('keydown', 'alt+a', e => {
 | |
|         showDialog();
 | |
| 
 | |
|         e.preventDefault();
 | |
|     });
 | |
| 
 | |
|     ko.applyBindings(attributesModel, document.getElementById('attributes-dialog'));
 | |
| 
 | |
|     $(document).on('focus', '.attribute-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 init.js
 | |
|                 source: attributeNames.map(attr => {
 | |
|                     return {
 | |
|                         label: attr,
 | |
|                         value: attr
 | |
|                     }
 | |
|                 }),
 | |
|                 minLength: 0
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         $(this).autocomplete("search", $(this).val());
 | |
|     });
 | |
| 
 | |
|     $(document).on('focus', '.attribute-value', async function (e) {
 | |
|         if (!$(this).hasClass("ui-autocomplete-input")) {
 | |
|             const attributeName = $(this).parent().parent().find('.attribute-name').val();
 | |
| 
 | |
|             if (attributeName.trim() === "") {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const attributeValues = await server.get('attributes/values/' + encodeURIComponent(attributeName));
 | |
| 
 | |
|             if (attributeValues.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 init.js
 | |
|                 source: attributeValues.map(attr => {
 | |
|                     return {
 | |
|                         label: attr,
 | |
|                         value: attr
 | |
|                     }
 | |
|                 }),
 | |
|                 minLength: 0
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         $(this).autocomplete("search", $(this).val());
 | |
|     });
 | |
| 
 | |
|     return {
 | |
|         showDialog
 | |
|     };
 | |
| })(); |