diff --git a/db/migrations/0074__add_position_to_attribute.sql b/db/migrations/0074__add_position_to_attribute.sql new file mode 100644 index 000000000..350a8aa31 --- /dev/null +++ b/db/migrations/0074__add_position_to_attribute.sql @@ -0,0 +1 @@ +ALTER TABLE attributes ADD COLUMN position INT NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/src/public/javascripts/dialogs/attributes.js b/src/public/javascripts/dialogs/attributes.js index 585b4c8cf..837e96e8d 100644 --- a/src/public/javascripts/dialogs/attributes.js +++ b/src/public/javascripts/dialogs/attributes.js @@ -1,8 +1,10 @@ "use strict"; const attributesDialog = (function() { - const dialogEl = $("#attributes-dialog"); - const saveAttributesButton = $("#save-attributes-button"); + const $dialog = $("#attributes-dialog"); + const $saveAttributesButton = $("#save-attributes-button"); + const $attributesBody = $('#attributes-table tbody'); + const attributesModel = new AttributesModel(); let attributeNames = []; @@ -24,10 +26,24 @@ const attributesDialog = (function() { // attribute might not be rendered immediatelly so could not focus 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) { - const attr = self.getTargetAttribute(event); + const attr = self.getTargetAttribute(event.target); const attrData = attr(); 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 // 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.focus(); + $saveAttributesButton.focus(); if (!isValid()) { alert("Please fix all validation errors and try saving again."); @@ -86,7 +102,8 @@ const attributesDialog = (function() { attributeId: '', name: '', value: '', - isDeleted: 0 + isDeleted: 0, + position: 0 })); } } @@ -94,7 +111,7 @@ const attributesDialog = (function() { this.attributeChanged = function (data, event) { addLastEmptyRow(); - const attr = self.getTargetAttribute(event); + const attr = self.getTargetAttribute(event.target); attr.valueHasMutated(); }; @@ -123,8 +140,8 @@ const attributesDialog = (function() { return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== ""); }; - this.getTargetAttribute = function(event) { - const context = ko.contextFor(event.target); + this.getTargetAttribute = function(target) { + const context = ko.contextFor(target); const index = context.$index(); return self.attributes()[index]; @@ -132,11 +149,11 @@ const attributesDialog = (function() { } async function showDialog() { - glob.activeDialog = dialogEl; + glob.activeDialog = $dialog; await attributesModel.loadAttributes(); - dialogEl.dialog({ + $dialog.dialog({ modal: true, width: 800, height: 500 diff --git a/src/routes/api/attributes.js b/src/routes/api/attributes.js index 950d21e1b..dfaae158a 100644 --- a/src/routes/api/attributes.js +++ b/src/routes/api/attributes.js @@ -12,7 +12,7 @@ const attributes = require('../../services/attributes'); router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { 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) => { @@ -23,8 +23,8 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, await sql.doInTransaction(async () => { for (const attr of attributes) { if (attr.attributeId) { - await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ? WHERE attributeId = ?", - [attr.name, attr.value, now, attr.isDeleted, attr.attributeId]); + await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ?, position = ? WHERE attributeId = ?", + [attr.name, attr.value, now, attr.isDeleted, attr.position, attr.attributeId]); } else { // 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(); await sql.insert("attributes", { - attributeId: attr.attributeId, - noteId: noteId, - name: attr.name, - value: attr.value, - dateCreated: now, - dateModified: now, - isDeleted: false + attributeId: attr.attributeId, + noteId: noteId, + name: attr.name, + value: attr.value, + position: attr.position, + dateCreated: now, + 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) => { diff --git a/src/services/app_info.js b/src/services/app_info.js index 1b862a4bf..01050340c 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -3,7 +3,7 @@ const build = require('./build'); const packageJson = require('../../package'); -const APP_DB_VERSION = 73; +const APP_DB_VERSION = 74; module.exports = { app_version: packageJson.version, diff --git a/src/views/index.ejs b/src/views/index.ejs index 0341904e5..570ad1c77 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -383,6 +383,7 @@ + @@ -390,8 +391,13 @@ - - + + + +
ID Name Value
+ + +