mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01: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