mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	added attributes sorting
This commit is contained in:
		
							
								
								
									
										1
									
								
								db/migrations/0074__add_position_to_attribute.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								db/migrations/0074__add_position_to_attribute.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | ALTER TABLE attributes ADD COLUMN position INT NOT NULL DEFAULT 0; | ||||||
| @@ -1,8 +1,10 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const attributesDialog = (function() { | const attributesDialog = (function() { | ||||||
|     const dialogEl = $("#attributes-dialog"); |     const $dialog = $("#attributes-dialog"); | ||||||
|     const saveAttributesButton = $("#save-attributes-button"); |     const $saveAttributesButton = $("#save-attributes-button"); | ||||||
|  |     const $attributesBody = $('#attributes-table tbody'); | ||||||
|  |  | ||||||
|     const attributesModel = new AttributesModel(); |     const attributesModel = new AttributesModel(); | ||||||
|     let attributeNames = []; |     let attributeNames = []; | ||||||
|  |  | ||||||
| @@ -24,10 +26,24 @@ const attributesDialog = (function() { | |||||||
|  |  | ||||||
|             // attribute might not be rendered immediatelly so could not focus |             // attribute might not be rendered immediatelly so could not focus | ||||||
|             setTimeout(() => $(".attribute-name:last").focus(), 100); |             setTimeout(() => $(".attribute-name:last").focus(), 100); | ||||||
|  |  | ||||||
|  |             $attributesBody.sortable({ | ||||||
|  |                 handle: '.handle', | ||||||
|  |                 containment: $attributesBody, | ||||||
|  |                 update: function() { | ||||||
|  |                     let position = 0; | ||||||
|  |  | ||||||
|  |                     $attributesBody.find('input[name="position"]').each(function() { | ||||||
|  |                         const attr = self.getTargetAttribute(this); | ||||||
|  |  | ||||||
|  |                         attr().position = position++; | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         this.deleteAttribute = function(data, event) { |         this.deleteAttribute = function(data, event) { | ||||||
|             const attr = self.getTargetAttribute(event); |             const attr = self.getTargetAttribute(event.target); | ||||||
|             const attrData = attr(); |             const attrData = attr(); | ||||||
|  |  | ||||||
|             if (attrData) { |             if (attrData) { | ||||||
| @@ -53,7 +69,7 @@ const attributesDialog = (function() { | |||||||
|             // we need to defocus from input (in case of enter-triggered save) because value is updated |             // 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 |             // 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. |             // stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel. | ||||||
|             saveAttributesButton.focus(); |             $saveAttributesButton.focus(); | ||||||
|  |  | ||||||
|             if (!isValid()) { |             if (!isValid()) { | ||||||
|                 alert("Please fix all validation errors and try saving again."); |                 alert("Please fix all validation errors and try saving again."); | ||||||
| @@ -86,7 +102,8 @@ const attributesDialog = (function() { | |||||||
|                     attributeId: '', |                     attributeId: '', | ||||||
|                     name: '', |                     name: '', | ||||||
|                     value: '', |                     value: '', | ||||||
|                     isDeleted: 0 |                     isDeleted: 0, | ||||||
|  |                     position: 0 | ||||||
|                 })); |                 })); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -94,7 +111,7 @@ const attributesDialog = (function() { | |||||||
|         this.attributeChanged = function (data, event) { |         this.attributeChanged = function (data, event) { | ||||||
|             addLastEmptyRow(); |             addLastEmptyRow(); | ||||||
|  |  | ||||||
|             const attr = self.getTargetAttribute(event); |             const attr = self.getTargetAttribute(event.target); | ||||||
|  |  | ||||||
|             attr.valueHasMutated(); |             attr.valueHasMutated(); | ||||||
|         }; |         }; | ||||||
| @@ -123,8 +140,8 @@ const attributesDialog = (function() { | |||||||
|             return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== ""); |             return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== ""); | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         this.getTargetAttribute = function(event) { |         this.getTargetAttribute = function(target) { | ||||||
|             const context = ko.contextFor(event.target); |             const context = ko.contextFor(target); | ||||||
|             const index = context.$index(); |             const index = context.$index(); | ||||||
|  |  | ||||||
|             return self.attributes()[index]; |             return self.attributes()[index]; | ||||||
| @@ -132,11 +149,11 @@ const attributesDialog = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function showDialog() { |     async function showDialog() { | ||||||
|         glob.activeDialog = dialogEl; |         glob.activeDialog = $dialog; | ||||||
|  |  | ||||||
|         await attributesModel.loadAttributes(); |         await attributesModel.loadAttributes(); | ||||||
|  |  | ||||||
|         dialogEl.dialog({ |         $dialog.dialog({ | ||||||
|             modal: true, |             modal: true, | ||||||
|             width: 800, |             width: 800, | ||||||
|             height: 500 |             height: 500 | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ const attributes = require('../../services/attributes'); | |||||||
| router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     const noteId = req.params.noteId; |     const noteId = req.params.noteId; | ||||||
|  |  | ||||||
|     res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY dateCreated", [noteId])); |     res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId])); | ||||||
| })); | })); | ||||||
|  |  | ||||||
| router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
| @@ -23,8 +23,8 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, | |||||||
|     await sql.doInTransaction(async () => { |     await sql.doInTransaction(async () => { | ||||||
|         for (const attr of attributes) { |         for (const attr of attributes) { | ||||||
|             if (attr.attributeId) { |             if (attr.attributeId) { | ||||||
|                 await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ? WHERE attributeId = ?", |                 await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ?, position = ? WHERE attributeId = ?", | ||||||
|                     [attr.name, attr.value, now, attr.isDeleted, attr.attributeId]); |                     [attr.name, attr.value, now, attr.isDeleted, attr.position, attr.attributeId]); | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|                 // if it was "created" and then immediatelly deleted, we just don't create it at all |                 // if it was "created" and then immediatelly deleted, we just don't create it at all | ||||||
| @@ -39,6 +39,7 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, | |||||||
|                     noteId: noteId, |                     noteId: noteId, | ||||||
|                     name: attr.name, |                     name: attr.name, | ||||||
|                     value: attr.value, |                     value: attr.value, | ||||||
|  |                     position: attr.position, | ||||||
|                     dateCreated: now, |                     dateCreated: now, | ||||||
|                     dateModified: now, |                     dateModified: now, | ||||||
|                     isDeleted: false |                     isDeleted: false | ||||||
| @@ -49,7 +50,7 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, | |||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY dateCreated", [noteId])); |     res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId])); | ||||||
| })); | })); | ||||||
|  |  | ||||||
| router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| const build = require('./build'); | const build = require('./build'); | ||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 73; | const APP_DB_VERSION = 74; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     app_version: packageJson.version, |     app_version: packageJson.version, | ||||||
|   | |||||||
| @@ -383,6 +383,7 @@ | |||||||
|         <table id="attributes-table" class="table"> |         <table id="attributes-table" class="table"> | ||||||
|           <thead> |           <thead> | ||||||
|             <tr> |             <tr> | ||||||
|  |               <th></th> | ||||||
|               <th>ID</th> |               <th>ID</th> | ||||||
|               <th>Name</th> |               <th>Name</th> | ||||||
|               <th>Value</th> |               <th>Value</th> | ||||||
| @@ -391,7 +392,12 @@ | |||||||
|           </thead> |           </thead> | ||||||
|           <tbody data-bind="foreach: attributes"> |           <tbody data-bind="foreach: attributes"> | ||||||
|             <tr data-bind="if: isDeleted == 0"> |             <tr data-bind="if: isDeleted == 0"> | ||||||
|               <td data-bind="text: attributeId"></td> |               <td class="handle"> | ||||||
|  |                 <span class="glyphicon glyphicon-resize-vertical"></span> | ||||||
|  |                 <input type="hidden" name="position" data-bind="value: position"/> | ||||||
|  |               </td> | ||||||
|  |               <!-- ID column has specific width because if it's empty its size can be deformed when dragging --> | ||||||
|  |               <td data-bind="text: attributeId" style="width: 150px;"></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 --> | ||||||
|                 <input type="text" class="attribute-name" data-bind="value: name, valueUpdate: 'blur',  event: { blur: $parent.attributeChanged }"/> |                 <input type="text" class="attribute-name" data-bind="value: name, valueUpdate: 'blur',  event: { blur: $parent.attributeChanged }"/> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user