mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	work in progress on attributes UI - unification of labels and relations now mostly works
This commit is contained in:
		
							
								
								
									
										1
									
								
								db/migrations/0110__add_isInheritable_to_attributes.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								db/migrations/0110__add_isInheritable_to_attributes.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | ALTER TABLE attributes ADD isInheritable int DEFAULT 0 NULL; | ||||||
| @@ -8,7 +8,7 @@ const sql = require('../services/sql'); | |||||||
| class Attribute extends Entity { | class Attribute extends Entity { | ||||||
|     static get tableName() { return "attributes"; } |     static get tableName() { return "attributes"; } | ||||||
|     static get primaryKeyName() { return "attributeId"; } |     static get primaryKeyName() { return "attributeId"; } | ||||||
|     static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "dateModified", "dateCreated"]; } |     static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "isInheritable", "dateModified", "dateCreated"]; } | ||||||
|  |  | ||||||
|     async getNote() { |     async getNote() { | ||||||
|         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); |         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); | ||||||
| @@ -26,6 +26,10 @@ class Attribute extends Entity { | |||||||
|             this.position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [this.noteId]); |             this.position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [this.noteId]); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (!this.isInheritable) { | ||||||
|  |             this.isInheritable = false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (!this.isDeleted) { |         if (!this.isDeleted) { | ||||||
|             this.isDeleted = false; |             this.isDeleted = false; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -19,6 +19,10 @@ function AttributesModel() { | |||||||
|         { text: "Relation", value: "relation" } |         { text: "Relation", value: "relation" } | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|  |     this.typeChanged = function(data, event) { | ||||||
|  |         self.getTargetAttribute(event.target).valueHasMutated(); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     this.updateAttributePositions = function() { |     this.updateAttributePositions = function() { | ||||||
|         let position = 0; |         let position = 0; | ||||||
|  |  | ||||||
| @@ -36,6 +40,13 @@ function AttributesModel() { | |||||||
|  |  | ||||||
|         const attributes = await server.get('notes/' + noteId + '/attributes'); |         const attributes = await server.get('notes/' + noteId + '/attributes'); | ||||||
|  |  | ||||||
|  |         for (const attr of attributes) { | ||||||
|  |             attr.labelValue = attr.type === 'label' ? attr.value : ''; | ||||||
|  |             attr.relationValue = attr.type === 'relation' ? attr.value : ''; | ||||||
|  |  | ||||||
|  |             delete attr.value; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         self.attributes(attributes.map(ko.observable)); |         self.attributes(attributes.map(ko.observable)); | ||||||
|  |  | ||||||
|         addLastEmptyRow(); |         addLastEmptyRow(); | ||||||
| @@ -107,12 +118,14 @@ function AttributesModel() { | |||||||
|         const attributes = self.attributes().filter(attr => attr().isDeleted === 0); |         const attributes = self.attributes().filter(attr => attr().isDeleted === 0); | ||||||
|         const last = attributes.length === 0 ? null : attributes[attributes.length - 1](); |         const last = attributes.length === 0 ? null : attributes[attributes.length - 1](); | ||||||
|  |  | ||||||
|         if (!last || last.name.trim() !== "" || last.value !== "") { |         if (!last || last.name.trim() !== "") { | ||||||
|             self.attributes.push(ko.observable({ |             self.attributes.push(ko.observable({ | ||||||
|                 attributeId: '', |                 attributeId: '', | ||||||
|                 type: 'label', |                 type: 'label', | ||||||
|                 name: '', |                 name: '', | ||||||
|                 value: '', |                 labelValue: '', | ||||||
|  |                 relationValue: '', | ||||||
|  |                 isInheritable: false, | ||||||
|                 isDeleted: 0, |                 isDeleted: 0, | ||||||
|                 position: 0 |                 position: 0 | ||||||
|             })); |             })); | ||||||
| @@ -148,7 +161,7 @@ function AttributesModel() { | |||||||
|     this.isEmptyName = function(index) { |     this.isEmptyName = function(index) { | ||||||
|         const cur = self.attributes()[index](); |         const cur = self.attributes()[index](); | ||||||
|  |  | ||||||
|         return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== ""); |         return cur.name.trim() === "" && (cur.attributeId !== "" || cur.labelValue !== "" || cur.relationValue); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     this.getTargetAttribute = function(target) { |     this.getTargetAttribute = function(target) { | ||||||
| @@ -204,7 +217,7 @@ $dialog.on('focus', '.attribute-name', function (e) { | |||||||
|     $(this).autocomplete("search", $(this).val()); |     $(this).autocomplete("search", $(this).val()); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| $dialog.on('focus', '.attribute-value', async function (e) { | $dialog.on('focus', '.label-value', async function (e) { | ||||||
|     if (!$(this).hasClass("ui-autocomplete-input")) { |     if (!$(this).hasClass("ui-autocomplete-input")) { | ||||||
|         const attributeName = $(this).parent().parent().find('.attribute-name').val(); |         const attributeName = $(this).parent().parent().find('.attribute-name').val(); | ||||||
|  |  | ||||||
| @@ -234,6 +247,49 @@ $dialog.on('focus', '.attribute-value', async function (e) { | |||||||
|     $(this).autocomplete("search", $(this).val()); |     $(this).autocomplete("search", $(this).val()); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | async function initNoteAutocomplete($el) { | ||||||
|  |     if (!$el.hasClass("ui-autocomplete-input")) { | ||||||
|  |         await $el.autocomplete({ | ||||||
|  |             source: async function (request, response) { | ||||||
|  |                 const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | ||||||
|  |  | ||||||
|  |                 if (result.length > 0) { | ||||||
|  |                     response(result.map(row => { | ||||||
|  |                         return { | ||||||
|  |                             label: row.label, | ||||||
|  |                             value: row.label + ' (' + row.value + ')' | ||||||
|  |                         } | ||||||
|  |                     })); | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     response([{ | ||||||
|  |                         label: "No results", | ||||||
|  |                         value: "No results" | ||||||
|  |                     }]); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             minLength: 0, | ||||||
|  |             select: function (event, ui) { | ||||||
|  |                 if (ui.item.value === 'No results') { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | $dialog.on('focus', '.relation-target-note-id', async function () { | ||||||
|  |     await initNoteAutocomplete($(this)); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | $dialog.on('click', '.relations-show-recent-notes', async function () { | ||||||
|  |     const $autocomplete = $(this).parent().find('.relation-target-note-id'); | ||||||
|  |  | ||||||
|  |     await initNoteAutocomplete($autocomplete); | ||||||
|  |  | ||||||
|  |     $autocomplete.autocomplete("search", ""); | ||||||
|  | }); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     showDialog |     showDialog | ||||||
| }; | }; | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| const build = require('./build'); | const build = require('./build'); | ||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 109; | const APP_DB_VERSION = 110; | ||||||
| const SYNC_VERSION = 1; | const SYNC_VERSION = 1; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ async function load() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function findNotes(query) { | function findNotes(query) { | ||||||
|     if (!noteTitles || query.length <= 2) { |     if (!noteTitles || !query.length) { | ||||||
|         return []; |         return []; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -567,13 +567,15 @@ | |||||||
|             <tr> |             <tr> | ||||||
|               <th></th> |               <th></th> | ||||||
|               <th>ID</th> |               <th>ID</th> | ||||||
|  |               <th>Type</th> | ||||||
|               <th>Name</th> |               <th>Name</th> | ||||||
|               <th>Value</th> |               <th>Value</th> | ||||||
|  |               <th>Inheritable</th> | ||||||
|               <th></th> |               <th></th> | ||||||
|             </tr> |             </tr> | ||||||
|             </thead> |             </thead> | ||||||
|             <tbody data-bind="foreach: attributes"> |             <tbody data-bind="foreach: attributes"> | ||||||
|             <tr data-bind="if: isDeleted == 0"> |             <tr data-bind="if: isDeleted == 0" class="attribute-row"> | ||||||
|               <td class="handle"> |               <td class="handle"> | ||||||
|                 <span class="glyphicon glyphicon-resize-vertical"></span> |                 <span class="glyphicon glyphicon-resize-vertical"></span> | ||||||
|                 <input type="hidden" name="position" data-bind="value: position"/> |                 <input type="hidden" name="position" data-bind="value: position"/> | ||||||
| @@ -581,7 +583,7 @@ | |||||||
|               <!-- ID column has specific width because if it's empty its size can be deformed when dragging --> |               <!-- ID column has specific width because if it's empty its size can be deformed when dragging --> | ||||||
|               <td data-bind="text: attributeId" style="min-width: 10em; font-size: smaller;"></td> |               <td data-bind="text: attributeId" style="min-width: 10em; font-size: smaller;"></td> | ||||||
|               <td> |               <td> | ||||||
|                   <select data-bind="options: $root.availableTypes, optionsText: 'text', optionsValue: 'value', value: type"></select> |                   <select data-bind="options: $parent.availableTypes, optionsText: 'text', optionsValue: 'value', value: type, event: { change: $parent.typeChanged }"></select> | ||||||
|               </td> |               </td> | ||||||
|               <td> |               <td> | ||||||
|                 <!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event --> |                 <!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event --> | ||||||
| @@ -590,7 +592,19 @@ | |||||||
|                 <div style="color: red" data-bind="if: $parent.isEmptyName($index())">Attribute name can't be empty.</div> |                 <div style="color: red" data-bind="if: $parent.isEmptyName($index())">Attribute name can't be empty.</div> | ||||||
|               </td> |               </td> | ||||||
|               <td> |               <td> | ||||||
|                 <input type="text" class="attribute-value form-control" data-bind="value: value, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }" style="width: 300px"/> |                 <input type="text" class="label-value form-control" data-bind="visible: type == 'label', value: labelValue, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }" style="width: 300px"/> | ||||||
|  |  | ||||||
|  |                 <div class="relation-value input-group" data-bind="visible: type == 'relation'"> | ||||||
|  |                   <input class="form-control relation-target-note-id" | ||||||
|  |                          placeholder="search for note by its name" | ||||||
|  |                          data-bind="value: relationValue, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }" | ||||||
|  |                          style="width: 300px;"> | ||||||
|  |  | ||||||
|  |                   <span class="input-group-addon relations-show-recent-notes" title="Show recent notes" style="background: url('/images/icons/clock-16.png') no-repeat center; cursor: pointer;"></span> | ||||||
|  |                 </div> | ||||||
|  |               </td> | ||||||
|  |               <td title="Inheritable relations are automatically inherited to the child notes"> | ||||||
|  |                 <input type="checkbox" value="1" data-bind="checked: isInheritable" /> | ||||||
|               </td> |               </td> | ||||||
|               <td title="Delete" style="padding: 13px; cursor: pointer;"> |               <td title="Delete" style="padding: 13px; cursor: pointer;"> | ||||||
|                 <span class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteAttribute"></span> |                 <span class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteAttribute"></span> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user