2018-03-26 01:41:29 +08:00
|
|
|
import noteDetailService from '../services/note_detail.js';
|
2018-03-26 01:02:39 +08:00
|
|
|
import utils from '../services/utils.js';
|
2018-03-26 02:49:20 +08:00
|
|
|
import server from '../services/server.js';
|
2018-03-26 09:29:35 +08:00
|
|
|
import infoService from "../services/info.js";
|
2018-02-10 21:37:14 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
const $dialog = $("#labels-dialog");
|
|
|
|
const $saveLabelsButton = $("#save-labels-button");
|
|
|
|
const $labelsBody = $('#labels-table tbody');
|
2018-01-11 13:01:16 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
const labelsModel = new LabelsModel();
|
|
|
|
let labelNames = [];
|
2018-01-11 13:01:16 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
function LabelsModel() {
|
|
|
|
const self = this;
|
2018-01-12 10:40:09 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
this.labels = ko.observableArray();
|
2018-01-12 10:40:09 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
this.loadLabels = async function() {
|
2018-03-26 01:41:29 +08:00
|
|
|
const noteId = noteDetailService.getCurrentNoteId();
|
2018-01-12 10:40:09 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
const labels = await server.get('notes/' + noteId + '/labels');
|
2018-01-11 13:01:16 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
self.labels(labels.map(ko.observable));
|
2018-02-05 08:27:27 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
addLastEmptyRow();
|
2018-02-05 08:27:27 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
labelNames = await server.get('labels/names');
|
2018-02-10 21:37:14 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
// label might not be rendered immediatelly so could not focus
|
|
|
|
setTimeout(() => $(".label-name:last").focus(), 100);
|
2018-02-10 21:37:14 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
$labelsBody.sortable({
|
|
|
|
handle: '.handle',
|
|
|
|
containment: $labelsBody,
|
|
|
|
update: function() {
|
|
|
|
let position = 0;
|
2018-02-10 21:37:14 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
// 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() {
|
2018-04-01 21:59:44 +08:00
|
|
|
const label = self.getTargetLabel(this);
|
2018-01-12 10:40:09 +08:00
|
|
|
|
2018-04-01 21:59:44 +08:00
|
|
|
label().position = position++;
|
2018-03-25 23:09:17 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
2018-02-07 12:09:19 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
this.deleteLabel = function(data, event) {
|
2018-04-01 21:59:44 +08:00
|
|
|
const label = self.getTargetLabel(event.target);
|
|
|
|
const labelData = label();
|
2018-02-07 12:09:19 +08:00
|
|
|
|
2018-04-01 21:59:44 +08:00
|
|
|
if (labelData) {
|
|
|
|
labelData.isDeleted = 1;
|
2018-02-07 12:09:19 +08:00
|
|
|
|
2018-04-01 21:59:44 +08:00
|
|
|
label(labelData);
|
2018-02-07 12:09:19 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
addLastEmptyRow();
|
|
|
|
}
|
|
|
|
};
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
function isValid() {
|
2018-04-01 21:59:44 +08:00
|
|
|
for (let labels = self.labels(), i = 0; i < labels.length; i++) {
|
2018-03-25 23:09:17 +08:00
|
|
|
if (self.isEmptyName(i)) {
|
|
|
|
return false;
|
|
|
|
}
|
2018-02-05 06:22:21 +08:00
|
|
|
}
|
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
return true;
|
|
|
|
}
|
2018-02-05 12:16:45 +08:00
|
|
|
|
2018-03-25 23:09:17 +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
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
if (!isValid()) {
|
|
|
|
alert("Please fix all validation errors and try saving again.");
|
|
|
|
return;
|
|
|
|
}
|
2018-01-12 10:40:09 +08:00
|
|
|
|
2018-03-26 01:41:29 +08:00
|
|
|
const noteId = noteDetailService.getCurrentNoteId();
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
const labelsToSave = self.labels()
|
2018-04-01 21:59:44 +08:00
|
|
|
.map(label => label())
|
|
|
|
.filter(label => label.labelId !== "" || label.name !== "");
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
const labels = await server.put('notes/' + noteId + '/labels', labelsToSave);
|
2018-01-12 10:40:09 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
self.labels(labels.map(ko.observable));
|
2018-01-12 10:40:09 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
addLastEmptyRow();
|
2018-02-05 09:23:30 +08:00
|
|
|
|
2018-03-26 09:29:35 +08:00
|
|
|
infoService.showMessage("Labels have been saved.");
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-26 01:41:29 +08:00
|
|
|
noteDetailService.loadLabelList();
|
2018-03-25 23:09:17 +08:00
|
|
|
};
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
function addLastEmptyRow() {
|
2018-04-01 21:59:44 +08:00
|
|
|
const labels = self.labels().filter(attr => attr().isDeleted === 0);
|
|
|
|
const last = labels.length === 0 ? null : labels[labels.length - 1]();
|
2018-03-25 23:09:17 +08:00
|
|
|
|
|
|
|
if (!last || last.name.trim() !== "" || last.value !== "") {
|
|
|
|
self.labels.push(ko.observable({
|
|
|
|
labelId: '',
|
|
|
|
name: '',
|
|
|
|
value: '',
|
|
|
|
isDeleted: 0,
|
|
|
|
position: 0
|
|
|
|
}));
|
2018-02-05 06:22:21 +08:00
|
|
|
}
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
this.labelChanged = function (data, event) {
|
|
|
|
addLastEmptyRow();
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-04-01 21:59:44 +08:00
|
|
|
const label = self.getTargetLabel(event.target);
|
2018-02-07 12:09:19 +08:00
|
|
|
|
2018-04-01 21:59:44 +08:00
|
|
|
label.valueHasMutated();
|
2018-03-25 23:09:17 +08:00
|
|
|
};
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
this.isNotUnique = function(index) {
|
|
|
|
const cur = self.labels()[index]();
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
if (cur.name.trim() === "") {
|
|
|
|
return false;
|
|
|
|
}
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-04-01 21:59:44 +08:00
|
|
|
for (let labels = self.labels(), i = 0; i < labels.length; i++) {
|
|
|
|
const label = labels[i]();
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-04-01 21:59:44 +08:00
|
|
|
if (index !== i && cur.name === label.name) {
|
2018-03-25 23:09:17 +08:00
|
|
|
return true;
|
2018-02-05 06:22:21 +08:00
|
|
|
}
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
return false;
|
|
|
|
};
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
this.isEmptyName = function(index) {
|
|
|
|
const cur = self.labels()[index]();
|
2018-02-05 06:22:21 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
return cur.name.trim() === "" && (cur.labelId !== "" || cur.value !== "");
|
|
|
|
};
|
2018-02-07 12:09:19 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
this.getTargetLabel = function(target) {
|
|
|
|
const context = ko.contextFor(target);
|
|
|
|
const index = context.$index();
|
2018-02-07 12:09:19 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
return self.labels()[index];
|
2018-01-11 13:01:16 +08:00
|
|
|
}
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
2018-01-11 13:01:16 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
async function showDialog() {
|
|
|
|
glob.activeDialog = $dialog;
|
2018-01-11 13:01:16 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
await labelsModel.loadLabels();
|
2018-02-05 08:43:11 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
$dialog.dialog({
|
|
|
|
modal: true,
|
|
|
|
width: 800,
|
|
|
|
height: 500
|
|
|
|
});
|
|
|
|
}
|
2018-01-11 13:01:16 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
ko.applyBindings(labelsModel, document.getElementById('labels-dialog'));
|
2018-02-05 08:43:11 +08:00
|
|
|
|
2018-03-25 23:09:17 +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
|
2018-03-27 10:29:14 +08:00
|
|
|
// because we have overriden filter() function in autocomplete.js
|
2018-04-01 21:59:44 +08:00
|
|
|
source: labelNames.map(label => {
|
2018-03-25 23:09:17 +08:00
|
|
|
return {
|
2018-04-01 21:59:44 +08:00
|
|
|
label: label,
|
|
|
|
value: label
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
|
|
|
}),
|
|
|
|
minLength: 0
|
|
|
|
});
|
|
|
|
}
|
2018-02-05 08:43:11 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
$(this).autocomplete("search", $(this).val());
|
|
|
|
});
|
2018-02-05 08:43:11 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
$(document).on('focus', '.label-value', async function (e) {
|
|
|
|
if (!$(this).hasClass("ui-autocomplete-input")) {
|
|
|
|
const labelName = $(this).parent().parent().find('.label-name').val();
|
2018-02-05 08:43:11 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
if (labelName.trim() === "") {
|
|
|
|
return;
|
|
|
|
}
|
2018-02-05 08:43:11 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
const labelValues = await server.get('labels/values/' + encodeURIComponent(labelName));
|
2018-02-05 08:43:11 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
if (labelValues.length === 0) {
|
|
|
|
return;
|
2018-02-05 08:43:11 +08:00
|
|
|
}
|
2018-02-05 08:27:27 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
$(this).autocomplete({
|
|
|
|
// shouldn't be required and autocomplete should just accept array of strings, but that fails
|
2018-03-27 10:29:14 +08:00
|
|
|
// because we have overriden filter() function in autocomplete.js
|
2018-04-01 21:59:44 +08:00
|
|
|
source: labelValues.map(label => {
|
2018-03-25 23:09:17 +08:00
|
|
|
return {
|
2018-04-01 21:59:44 +08:00
|
|
|
label: label,
|
|
|
|
value: label
|
2018-03-25 23:09:17 +08:00
|
|
|
}
|
|
|
|
}),
|
|
|
|
minLength: 0
|
|
|
|
});
|
|
|
|
}
|
2018-02-05 08:27:27 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
$(this).autocomplete("search", $(this).val());
|
|
|
|
});
|
2018-03-24 12:54:50 +08:00
|
|
|
|
2018-03-25 23:09:17 +08:00
|
|
|
export default {
|
|
|
|
showDialog
|
|
|
|
};
|