mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 04:51:31 +08:00 
			
		
		
		
	removed attributes dialog
This commit is contained in:
		
							parent
							
								
									2e24111c2b
								
							
						
					
					
						commit
						a0b3bc858d
					
				| @ -1,314 +0,0 @@ | ||||
| import server from '../services/server.js'; | ||||
| import toastService from "../services/toast.js"; | ||||
| import treeService from "../services/tree.js"; | ||||
| import attributeAutocompleteService from "../services/attribute_autocomplete.js"; | ||||
| import utils from "../services/utils.js"; | ||||
| import linkService from "../services/link.js"; | ||||
| import libraryLoader from "../services/library_loader.js"; | ||||
| import noteAutocompleteService from "../services/note_autocomplete.js"; | ||||
| import appContext from "../services/app_context.js"; | ||||
| 
 | ||||
| const $dialog = $("#attributes-dialog"); | ||||
| const $saveAttributesButton = $("#save-attributes-button"); | ||||
| const $ownedAttributesBody = $('#owned-attributes-table tbody'); | ||||
| 
 | ||||
| function AttributesModel() { | ||||
|     const self = this; | ||||
| 
 | ||||
|     this.ownedAttributes = ko.observableArray(); | ||||
|     this.inheritedAttributes = ko.observableArray(); | ||||
| 
 | ||||
|     this.availableTypes = [ | ||||
|         { text: "Label", value: "label" }, | ||||
|         { text: "Label definition", value: "label-definition" }, | ||||
|         { text: "Relation", value: "relation" }, | ||||
|         { text: "Relation definition", value: "relation-definition" } | ||||
|     ]; | ||||
| 
 | ||||
|     this.availableLabelTypes = [ | ||||
|         { text: "Text", value: "text" }, | ||||
|         { text: "Number", value: "number" }, | ||||
|         { text: "Boolean", value: "boolean" }, | ||||
|         { text: "Date", value: "date" }, | ||||
|         { text: "URL", value: "url"} | ||||
|     ]; | ||||
| 
 | ||||
|     this.multiplicityTypes = [ | ||||
|         { text: "Single value", value: "singlevalue" }, | ||||
|         { text: "Multi value", value: "multivalue" } | ||||
|     ]; | ||||
| 
 | ||||
|     this.typeChanged = function(data, event) { | ||||
|         self.getTargetAttribute(event.target).valueHasMutated(); | ||||
|     }; | ||||
| 
 | ||||
|     this.labelTypeChanged = function(data, event) { | ||||
|         self.getTargetAttribute(event.target).valueHasMutated(); | ||||
|     }; | ||||
| 
 | ||||
|     this.updateAttributePositions = function() { | ||||
|         let position = 10; | ||||
| 
 | ||||
|         // we need to update positions by searching in the DOM, because order of the
 | ||||
|         // attributes in the viewmodel (self.ownedAttributes()) stays the same
 | ||||
|         $ownedAttributesBody.find('input[name="position"]').each(function() { | ||||
|             const attribute = self.getTargetAttribute(this); | ||||
| 
 | ||||
|             attribute().position = position; | ||||
|             position += 10; | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     async function showAttributes(noteId, attributes) { | ||||
|         const ownedAttributes = attributes.filter(attr => attr.noteId === noteId); | ||||
| 
 | ||||
|         for (const attr of ownedAttributes) { | ||||
|             attr.labelValue = attr.type === 'label' ? attr.value : ''; | ||||
|             attr.relationValue = attr.type === 'relation' ? (await treeService.getNoteTitle(attr.value)) : ''; | ||||
|             attr.selectedPath = attr.type === 'relation' ? attr.value : ''; | ||||
|             attr.labelDefinition = (attr.type === 'label-definition' && attr.value) ? attr.value : { | ||||
|                 labelType: "text", | ||||
|                 multiplicityType: "singlevalue", | ||||
|                 isPromoted: true, | ||||
|                 numberPrecision: 0 | ||||
|             }; | ||||
| 
 | ||||
|             attr.relationDefinition = (attr.type === 'relation-definition' && attr.value) ? attr.value : { | ||||
|                 multiplicityType: "singlevalue", | ||||
|                 inverseRelation: "", | ||||
|                 isPromoted: true | ||||
|             }; | ||||
| 
 | ||||
|             delete attr.value; | ||||
|         } | ||||
| 
 | ||||
|         self.ownedAttributes(ownedAttributes.map(ko.observable)); | ||||
| 
 | ||||
|         addLastEmptyRow(); | ||||
| 
 | ||||
|         const inheritedAttributes = attributes.filter(attr => attr.noteId !== noteId); | ||||
| 
 | ||||
|         self.inheritedAttributes(inheritedAttributes); | ||||
|     } | ||||
| 
 | ||||
|     this.loadAttributes = async function() { | ||||
|         const noteId = appContext.tabManager.getActiveTabNoteId(); | ||||
| 
 | ||||
|         const attributes = await server.get('notes/' + noteId + '/attributes'); | ||||
| 
 | ||||
|         await showAttributes(noteId, attributes); | ||||
| 
 | ||||
|         // attribute might not be rendered immediatelly so could not focus
 | ||||
|         setTimeout(() => $(".attribute-type-select:last").trigger('focus'), 1000); | ||||
|     }; | ||||
| 
 | ||||
|     this.deleteAttribute = function(data, event) { | ||||
|         const attribute = self.getTargetAttribute(event.target); | ||||
|         const attributeData = attribute(); | ||||
| 
 | ||||
|         if (attributeData) { | ||||
|             attributeData.isDeleted = true; | ||||
| 
 | ||||
|             attribute(attributeData); | ||||
| 
 | ||||
|             addLastEmptyRow(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     function isValid() { | ||||
|         for (let attributes = self.ownedAttributes(), i = 0; i < attributes.length; i++) { | ||||
|             if (self.isEmptyName(i) || self.isEmptyRelationTarget(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.trigger('focus'); | ||||
| 
 | ||||
|         if (!isValid()) { | ||||
|             alert("Please fix all validation errors and try saving again."); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         self.updateAttributePositions(); | ||||
| 
 | ||||
|         const noteId = appContext.tabManager.getActiveTabNoteId(); | ||||
| 
 | ||||
|         const attributesToSave = self.ownedAttributes() | ||||
|             .map(attribute => attribute()) | ||||
|             .filter(attribute => attribute.attributeId !== "" || attribute.name !== ""); | ||||
| 
 | ||||
|         for (const attr of attributesToSave) { | ||||
|             if (attr.type === 'label') { | ||||
|                 attr.value = attr.labelValue; | ||||
|             } | ||||
|             else if (attr.type === 'relation') { | ||||
|                 attr.value = treeService.getNoteIdFromNotePath(attr.selectedPath); | ||||
|             } | ||||
|             else if (attr.type === 'label-definition') { | ||||
|                 attr.value = JSON.stringify(attr.labelDefinition); | ||||
|             } | ||||
|             else if (attr.type === 'relation-definition') { | ||||
|                 attr.value = JSON.stringify(attr.relationDefinition); | ||||
|             } | ||||
| 
 | ||||
|             delete attr.labelValue; | ||||
|             delete attr.relationValue; | ||||
|             delete attr.labelDefinition; | ||||
|             delete attr.relationDefinition; | ||||
|         } | ||||
| 
 | ||||
|         const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave); | ||||
| 
 | ||||
|         await showAttributes(noteId, attributes); | ||||
| 
 | ||||
|         toastService.showMessage("Attributes have been saved."); | ||||
|     }; | ||||
| 
 | ||||
|     function addLastEmptyRow() { | ||||
|         const attributes = self.ownedAttributes().filter(attr => !attr().isDeleted); | ||||
|         const last = attributes.length === 0 ? null : attributes[attributes.length - 1](); | ||||
| 
 | ||||
|         if (!last || last.name.trim() !== "") { | ||||
|             self.ownedAttributes.push(ko.observable({ | ||||
|                 attributeId: '', | ||||
|                 type: 'label', | ||||
|                 name: '', | ||||
|                 labelValue: '', | ||||
|                 relationValue: '', | ||||
|                 isInheritable: false, | ||||
|                 isDeleted: false, | ||||
|                 position: 0, | ||||
|                 labelDefinition: { | ||||
|                     labelType: "text", | ||||
|                     multiplicityType: "singlevalue", | ||||
|                     isPromoted: true, | ||||
|                     numberPrecision: 0 | ||||
|                 }, | ||||
|                 relationDefinition: { | ||||
|                     multiplicityType: "singlevalue", | ||||
|                     inverseRelation: "", | ||||
|                     isPromoted: true | ||||
|                 } | ||||
|             })); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     this.attributeChanged = function (data, event) { | ||||
|         addLastEmptyRow(); | ||||
| 
 | ||||
|         const attribute = self.getTargetAttribute(event.target); | ||||
| 
 | ||||
|         attribute.valueHasMutated(); | ||||
|     }; | ||||
| 
 | ||||
|     this.isEmptyName = function(index) { | ||||
|         const cur = self.ownedAttributes()[index](); | ||||
| 
 | ||||
|         if (cur.name.trim() || cur.isDeleted) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (cur.attributeId) { | ||||
|             // name is empty and attribute already exists so this is NO-GO
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if (cur.type === 'relation-definition' || cur.type === 'label-definition') { | ||||
|             // for definitions there's no possible empty value so we always require name
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if (cur.type === 'label' && cur.labelValue) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if (cur.type === 'relation' && cur.relationValue) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     }; | ||||
| 
 | ||||
|     this.isEmptyRelationTarget = function(index) { | ||||
|         const cur = self.ownedAttributes()[index](); | ||||
| 
 | ||||
|         return cur.type === "relation" && !cur.isDeleted && cur.name && !cur.relationValue; | ||||
|     }; | ||||
| 
 | ||||
|     this.getTargetAttribute = function(target) { | ||||
|         const context = ko.contextFor(target); | ||||
|         const index = context.$index(); | ||||
| 
 | ||||
|         return self.ownedAttributes()[index]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| let attributesModel; | ||||
| 
 | ||||
| function initKoPlugins() { | ||||
|     ko.bindingHandlers.noteLink = { | ||||
|         init: async function (element, valueAccessor, allBindings, viewModel, bindingContext) { | ||||
|             const noteId = ko.unwrap(valueAccessor()); | ||||
| 
 | ||||
|             if (noteId) { | ||||
|                 const link = await linkService.createNoteLink(noteId); | ||||
| 
 | ||||
|                 $(element).append(link); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     ko.bindingHandlers.noteAutocomplete = { | ||||
|         init: function (element, valueAccessor, allBindings, viewModel, bindingContext) { | ||||
|             noteAutocompleteService.initNoteAutocomplete($(element)); | ||||
| 
 | ||||
|             $(element).setSelectedNotePath(bindingContext.$data.selectedPath); | ||||
| 
 | ||||
|             $(element).on('autocomplete:selected', function (event, suggestion, dataset) { | ||||
|                 bindingContext.$data.selectedPath = $(element).val().trim() ? suggestion.path : ''; | ||||
|             }); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| export async function showDialog() { | ||||
|     await libraryLoader.requireLibrary(libraryLoader.KNOCKOUT); | ||||
| 
 | ||||
|     // lazily apply bindings on first use
 | ||||
|     if (!attributesModel) { | ||||
|         attributesModel = new AttributesModel(); | ||||
| 
 | ||||
|         initKoPlugins(); | ||||
| 
 | ||||
|         ko.applyBindings(attributesModel, $dialog[0]); | ||||
|     } | ||||
| 
 | ||||
|     await attributesModel.loadAttributes(); | ||||
| 
 | ||||
|     utils.openDialog($dialog); | ||||
| } | ||||
| 
 | ||||
| $dialog.on('focus', '.attribute-name', function (e) { | ||||
|     attributeAutocompleteService.initAttributeNameAutocomplete({ | ||||
|         $el: $(this), | ||||
|         attributeType: () => { | ||||
|             const attribute = attributesModel.getTargetAttribute(this); | ||||
|             return (attribute().type === 'relation' || attribute().type === 'relation-definition') ? 'relation' : 'label'; | ||||
|         }, | ||||
|         open: true | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| $dialog.on('focus', '.label-value', function (e) { | ||||
|     attributeAutocompleteService.initLabelValueAutocomplete({ | ||||
|         $el: $(this), | ||||
|         open: true | ||||
|     }) | ||||
| }); | ||||
| @ -12,10 +12,6 @@ export default class DialogCommandExecutor extends Component { | ||||
|         import("../dialogs/recent_changes.js").then(d => d.showDialog()); | ||||
|     } | ||||
| 
 | ||||
|     showAttributesCommand() { | ||||
|         import("../dialogs/attributes.js").then(d => d.showDialog()); | ||||
|     } | ||||
| 
 | ||||
|     showNoteInfoCommand() { | ||||
|         import("../dialogs/note_info.js").then(d => d.showDialog()); | ||||
|     } | ||||
|  | ||||
| @ -45,8 +45,6 @@ const LINK_MAP = { | ||||
| 
 | ||||
| const PRINT_THIS = {js: ["libraries/printThis.js"]}; | ||||
| 
 | ||||
| const KNOCKOUT = {js: ["libraries/knockout.min.js"]}; | ||||
| 
 | ||||
| const CALENDAR_WIDGET = {css: ["stylesheets/calendar.css"]}; | ||||
| 
 | ||||
| async function requireLibrary(library) { | ||||
| @ -96,6 +94,5 @@ export default { | ||||
|     RELATION_MAP, | ||||
|     LINK_MAP, | ||||
|     PRINT_THIS, | ||||
|     KNOCKOUT, | ||||
|     CALENDAR_WIDGET | ||||
| } | ||||
| @ -80,7 +80,6 @@ const TPL = ` | ||||
|             </span> | ||||
|         </div> | ||||
|         <a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a> | ||||
|         <a data-trigger-command="showAttributes" class="dropdown-item show-attributes-button"><kbd data-command="showAttributes"></kbd> Attributes</a> | ||||
|         <a data-trigger-command="showLinkMap" class="dropdown-item show-link-map-button"><kbd data-command="showLinkMap"></kbd> Link map</a> | ||||
|         <a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a> | ||||
|         <a class="dropdown-item import-files-button">Import files</a> | ||||
|  | ||||
| @ -230,10 +230,10 @@ const DEFAULT_KEYBOARD_ACTIONS = [ | ||||
|     { | ||||
|         separator: "Dialogs" | ||||
|     }, | ||||
|     { | ||||
|     { // FIXME
 | ||||
|         actionName: "showAttributes", | ||||
|         defaultShortcuts: ["Alt+A"], | ||||
|         description: "Shows Attributes dialog", | ||||
|         description: "Shows Attributes", | ||||
|         scope: "window" | ||||
|     }, | ||||
|     { | ||||
|  | ||||
| @ -138,7 +138,6 @@ | ||||
|                                     <li><kbd data-command="toggleZenMode"></kbd> - Zen mode - display only note editor, everything else is hidden</li> | ||||
|                                     <li><kbd data-command="searchNotes"></kbd> - toggle search form in tree pane</li> | ||||
|                                     <li><kbd data-command="findInText"></kbd> - in page search</li> | ||||
|                                     <li><kbd data-command="showAttributes"></kbd> - show note attributes dialog</li> | ||||
|                                 </ul> | ||||
|                             </p> | ||||
|                         </div> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam