mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-11-04 07:01:31 +08:00 
			
		
		
		
	added attributes sorting
This commit is contained in:
		
							parent
							
								
									e011b9ae63
								
							
						
					
					
						commit
						c76e4faf5d
					
				
							
								
								
									
										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
 | 
				
			||||||
@ -35,13 +35,14 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res,
 | 
				
			|||||||
                attr.attributeId = utils.newAttributeId();
 | 
					                attr.attributeId = utils.newAttributeId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                await sql.insert("attributes", {
 | 
					                await sql.insert("attributes", {
 | 
				
			||||||
                   attributeId: attr.attributeId,
 | 
					                    attributeId: attr.attributeId,
 | 
				
			||||||
                   noteId: noteId,
 | 
					                    noteId: noteId,
 | 
				
			||||||
                   name: attr.name,
 | 
					                    name: attr.name,
 | 
				
			||||||
                   value: attr.value,
 | 
					                    value: attr.value,
 | 
				
			||||||
                   dateCreated: now,
 | 
					                    position: attr.position,
 | 
				
			||||||
                   dateModified: now,
 | 
					                    dateCreated: now,
 | 
				
			||||||
                   isDeleted: false
 | 
					                    dateModified: now,
 | 
				
			||||||
 | 
					                    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>
 | 
				
			||||||
@ -390,8 +391,13 @@
 | 
				
			|||||||
            </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">
 | 
				
			||||||
              <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 }"/>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user