mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-30 04:01: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()); |         import("../dialogs/recent_changes.js").then(d => d.showDialog()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     showAttributesCommand() { |  | ||||||
|         import("../dialogs/attributes.js").then(d => d.showDialog()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     showNoteInfoCommand() { |     showNoteInfoCommand() { | ||||||
|         import("../dialogs/note_info.js").then(d => d.showDialog()); |         import("../dialogs/note_info.js").then(d => d.showDialog()); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -45,8 +45,6 @@ const LINK_MAP = { | |||||||
| 
 | 
 | ||||||
| const PRINT_THIS = {js: ["libraries/printThis.js"]}; | const PRINT_THIS = {js: ["libraries/printThis.js"]}; | ||||||
| 
 | 
 | ||||||
| const KNOCKOUT = {js: ["libraries/knockout.min.js"]}; |  | ||||||
| 
 |  | ||||||
| const CALENDAR_WIDGET = {css: ["stylesheets/calendar.css"]}; | const CALENDAR_WIDGET = {css: ["stylesheets/calendar.css"]}; | ||||||
| 
 | 
 | ||||||
| async function requireLibrary(library) { | async function requireLibrary(library) { | ||||||
| @ -96,6 +94,5 @@ export default { | |||||||
|     RELATION_MAP, |     RELATION_MAP, | ||||||
|     LINK_MAP, |     LINK_MAP, | ||||||
|     PRINT_THIS, |     PRINT_THIS, | ||||||
|     KNOCKOUT, |  | ||||||
|     CALENDAR_WIDGET |     CALENDAR_WIDGET | ||||||
| } | } | ||||||
| @ -80,7 +80,6 @@ const TPL = ` | |||||||
|             </span> |             </span> | ||||||
|         </div> |         </div> | ||||||
|         <a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a> |         <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="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 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> |         <a class="dropdown-item import-files-button">Import files</a> | ||||||
|  | |||||||
| @ -230,10 +230,10 @@ const DEFAULT_KEYBOARD_ACTIONS = [ | |||||||
|     { |     { | ||||||
|         separator: "Dialogs" |         separator: "Dialogs" | ||||||
|     }, |     }, | ||||||
|     { |     { // FIXME
 | ||||||
|         actionName: "showAttributes", |         actionName: "showAttributes", | ||||||
|         defaultShortcuts: ["Alt+A"], |         defaultShortcuts: ["Alt+A"], | ||||||
|         description: "Shows Attributes dialog", |         description: "Shows Attributes", | ||||||
|         scope: "window" |         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="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="searchNotes"></kbd> - toggle search form in tree pane</li> | ||||||
|                                     <li><kbd data-command="findInText"></kbd> - in page search</li> |                                     <li><kbd data-command="findInText"></kbd> - in page search</li> | ||||||
|                                     <li><kbd data-command="showAttributes"></kbd> - show note attributes dialog</li> |  | ||||||
|                                 </ul> |                                 </ul> | ||||||
|                             </p> |                             </p> | ||||||
|                         </div> |                         </div> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam