trilium/src/public/javascripts/dialogs/labels.js

227 lines
6.7 KiB
JavaScript
Raw Normal View History

"use strict";
const labelsDialog = (function() {
const $showDialogButton = $(".show-labels-button");
const $dialog = $("#labels-dialog");
const $saveLabelsButton = $("#save-labels-button");
const $labelsBody = $('#labels-table tbody');
2018-02-10 21:37:14 +08:00
const labelsModel = new LabelsModel();
let labelNames = [];
function LabelsModel() {
2018-01-12 10:40:09 +08:00
const self = this;
this.labels = ko.observableArray();
2018-01-12 10:40:09 +08:00
this.loadLabels = async function() {
2018-01-12 10:40:09 +08:00
const noteId = noteEditor.getCurrentNoteId();
const labels = await server.get('notes/' + noteId + '/labels');
2018-01-12 10:40:09 +08:00
self.labels(labels.map(ko.observable));
2018-02-05 06:22:21 +08:00
addLastEmptyRow();
labelNames = await server.get('labels/names');
// label might not be rendered immediatelly so could not focus
setTimeout(() => $(".label-name:last").focus(), 100);
2018-02-10 21:37:14 +08:00
$labelsBody.sortable({
2018-02-10 21:37:14 +08:00
handle: '.handle',
containment: $labelsBody,
2018-02-10 21:37:14 +08:00
update: function() {
let position = 0;
// we need to update positions by searching in the DOM, because order of the
// labels in the viewmodel (self.labels()) stays the same
$labelsBody.find('input[name="position"]').each(function() {
const attr = self.getTargetLabel(this);
2018-02-10 21:37:14 +08:00
attr().position = position++;
});
}
});
2018-01-12 10:40:09 +08:00
};
this.deleteLabel = function(data, event) {
const attr = self.getTargetLabel(event.target);
2018-02-07 12:09:19 +08:00
const attrData = attr();
if (attrData) {
attrData.isDeleted = 1;
attr(attrData);
addLastEmptyRow();
}
};
2018-02-05 06:22:21 +08:00
function isValid() {
for (let attrs = self.labels(), i = 0; i < attrs.length; i++) {
if (self.isEmptyName(i)) {
2018-02-05 06:22:21 +08:00
return false;
}
}
return true;
}
2018-01-12 10:40:09 +08:00
this.save = async 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.
$saveLabelsButton.focus();
2018-02-05 06:22:21 +08:00
if (!isValid()) {
alert("Please fix all validation errors and try saving again.");
return;
}
2018-01-12 10:40:09 +08:00
const noteId = noteEditor.getCurrentNoteId();
const labelsToSave = self.labels()
2018-02-05 06:22:21 +08:00
.map(attr => attr())
.filter(attr => attr.labelId !== "" || attr.name !== "");
2018-02-05 06:22:21 +08:00
const labels = await server.put('notes/' + noteId + '/labels', labelsToSave);
2018-02-05 06:22:21 +08:00
self.labels(labels.map(ko.observable));
2018-01-12 10:40:09 +08:00
2018-02-05 06:22:21 +08:00
addLastEmptyRow();
2018-01-12 10:40:09 +08:00
2018-03-25 11:37:55 +08:00
utils.showMessage("Labels have been saved.");
2018-02-05 09:23:30 +08:00
noteEditor.loadLabelList();
2018-01-12 10:40:09 +08:00
};
2018-02-05 06:22:21 +08:00
function addLastEmptyRow() {
const attrs = self.labels().filter(attr => attr().isDeleted === 0);
const last = attrs.length === 0 ? null : attrs[attrs.length - 1]();
2018-02-05 06:22:21 +08:00
if (!last || last.name.trim() !== "" || last.value !== "") {
self.labels.push(ko.observable({
labelId: '',
2018-02-05 06:22:21 +08:00
name: '',
2018-02-07 12:09:19 +08:00
value: '',
2018-02-10 21:37:14 +08:00
isDeleted: 0,
position: 0
2018-02-05 06:22:21 +08:00
}));
}
}
this.labelChanged = function (data, event) {
2018-02-05 06:22:21 +08:00
addLastEmptyRow();
const attr = self.getTargetLabel(event.target);
2018-02-07 12:09:19 +08:00
attr.valueHasMutated();
2018-02-05 06:22:21 +08:00
};
this.isNotUnique = function(index) {
const cur = self.labels()[index]();
2018-02-05 06:22:21 +08:00
if (cur.name.trim() === "") {
return false;
}
for (let attrs = self.labels(), i = 0; i < attrs.length; i++) {
2018-02-05 06:22:21 +08:00
const attr = attrs[i]();
if (index !== i && cur.name === attr.name) {
return true;
}
}
return false;
};
this.isEmptyName = function(index) {
const cur = self.labels()[index]();
2018-02-05 06:22:21 +08:00
return cur.name.trim() === "" && (cur.labelId !== "" || cur.value !== "");
2018-02-07 12:09:19 +08:00
};
this.getTargetLabel = function(target) {
2018-02-10 21:37:14 +08:00
const context = ko.contextFor(target);
2018-02-07 12:09:19 +08:00
const index = context.$index();
return self.labels()[index];
2018-02-05 06:22:21 +08:00
}
}
async function showDialog() {
2018-02-10 21:37:14 +08:00
glob.activeDialog = $dialog;
await labelsModel.loadLabels();
2018-02-10 21:37:14 +08:00
$dialog.dialog({
modal: true,
width: 800,
2018-02-04 01:44:22 +08:00
height: 500
});
}
$(document).bind('keydown', 'alt+a', e => {
showDialog();
e.preventDefault();
});
ko.applyBindings(labelsModel, document.getElementById('labels-dialog'));
2018-01-12 10:40:09 +08:00
$(document).on('focus', '.label-name', function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in init.js
source: labelNames.map(attr => {
return {
label: attr,
value: attr
}
}),
minLength: 0
});
}
$(this).autocomplete("search", $(this).val());
});
$(document).on('focus', '.label-value', async function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
const labelName = $(this).parent().parent().find('.label-name').val();
if (labelName.trim() === "") {
return;
}
const labelValues = await server.get('labels/values/' + encodeURIComponent(labelName));
if (labelValues.length === 0) {
return;
}
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in init.js
source: labelValues.map(attr => {
return {
label: attr,
value: attr
}
}),
minLength: 0
});
}
$(this).autocomplete("search", $(this).val());
});
$showDialogButton.click(showDialog);
return {
showDialog
};
})();