mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
widgetizing promoted attributes
This commit is contained in:
parent
657d01da95
commit
8b9c235465
@ -9,6 +9,7 @@ import server from "./server.js";
|
|||||||
import keyboardActionService from "./keyboard_actions.js";
|
import keyboardActionService from "./keyboard_actions.js";
|
||||||
import TabRowWidget from "./tab_row.js";
|
import TabRowWidget from "./tab_row.js";
|
||||||
import NoteTitleWidget from "../widgets/note_title.js";
|
import NoteTitleWidget from "../widgets/note_title.js";
|
||||||
|
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||||
|
|
||||||
class AppContext {
|
class AppContext {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -28,6 +29,9 @@ class AppContext {
|
|||||||
|
|
||||||
$("#global-menu-wrapper").after(contents);
|
$("#global-menu-wrapper").after(contents);
|
||||||
|
|
||||||
|
this.promotedAttributes = new PromotedAttributesWidget(this);
|
||||||
|
$("#center-pane").prepend(this.promotedAttributes.render());
|
||||||
|
|
||||||
this.noteTitleWidget = new NoteTitleWidget(this);
|
this.noteTitleWidget = new NoteTitleWidget(this);
|
||||||
$("#center-pane").prepend(this.noteTitleWidget.render());
|
$("#center-pane").prepend(this.noteTitleWidget.render());
|
||||||
|
|
||||||
@ -47,6 +51,7 @@ class AppContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.widgets.push(this.noteTitleWidget);
|
this.widgets.push(this.noteTitleWidget);
|
||||||
|
this.widgets.push(this.promotedAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger(name, data) {
|
trigger(name, data) {
|
||||||
|
@ -9,8 +9,6 @@ class Attributes {
|
|||||||
*/
|
*/
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.$promotedAttributesContainer = ctx.$tabContent.find(".note-detail-promoted-attributes");
|
|
||||||
this.$savedIndicator = ctx.$tabContent.find(".saved-indicator");
|
|
||||||
this.attributePromise = null;
|
this.attributePromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,8 +22,6 @@ class Attributes {
|
|||||||
|
|
||||||
async refreshAttributes() {
|
async refreshAttributes() {
|
||||||
this.reloadAttributes();
|
this.reloadAttributes();
|
||||||
|
|
||||||
await this.showAttributes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAttributes() {
|
async getAttributes() {
|
||||||
@ -36,237 +32,6 @@ class Attributes {
|
|||||||
return this.attributePromise;
|
return this.attributePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async showAttributes() {
|
|
||||||
this.$promotedAttributesContainer.empty();
|
|
||||||
|
|
||||||
const attributes = await this.getAttributes();
|
|
||||||
|
|
||||||
const promoted = attributes.filter(attr =>
|
|
||||||
(attr.type === 'label-definition' || attr.type === 'relation-definition')
|
|
||||||
&& !attr.name.startsWith("child:")
|
|
||||||
&& attr.value.isPromoted);
|
|
||||||
|
|
||||||
const hidePromotedAttributes = attributes.some(attr => attr.type === 'label' && attr.name === 'hidePromotedAttributes');
|
|
||||||
|
|
||||||
if (promoted.length > 0 && !hidePromotedAttributes) {
|
|
||||||
const $tbody = $("<tbody>");
|
|
||||||
|
|
||||||
for (const definitionAttr of promoted) {
|
|
||||||
const definitionType = definitionAttr.type;
|
|
||||||
const valueType = definitionType.substr(0, definitionType.length - 11);
|
|
||||||
|
|
||||||
let valueAttrs = attributes.filter(el => el.name === definitionAttr.name && el.type === valueType);
|
|
||||||
|
|
||||||
if (valueAttrs.length === 0) {
|
|
||||||
valueAttrs.push({
|
|
||||||
attributeId: "",
|
|
||||||
type: valueType,
|
|
||||||
name: definitionAttr.name,
|
|
||||||
value: ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (definitionAttr.value.multiplicityType === 'singlevalue') {
|
|
||||||
valueAttrs = valueAttrs.slice(0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const valueAttr of valueAttrs) {
|
|
||||||
const $tr = await this.createPromotedAttributeRow(definitionAttr, valueAttr);
|
|
||||||
|
|
||||||
$tbody.append($tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we replace the whole content in one step so there can't be any race conditions
|
|
||||||
// (previously we saw promoted attributes doubling)
|
|
||||||
this.$promotedAttributesContainer.empty().append($tbody);
|
|
||||||
}
|
|
||||||
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createPromotedAttributeRow(definitionAttr, valueAttr) {
|
|
||||||
const definition = definitionAttr.value;
|
|
||||||
const $tr = $("<tr>");
|
|
||||||
const $labelCell = $("<th>").append(valueAttr.name);
|
|
||||||
const $input = $("<input>")
|
|
||||||
.prop("tabindex", definitionAttr.position)
|
|
||||||
.prop("attribute-id", valueAttr.isOwned ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
|
||||||
.prop("attribute-type", valueAttr.type)
|
|
||||||
.prop("attribute-name", valueAttr.name)
|
|
||||||
.prop("value", valueAttr.value)
|
|
||||||
.addClass("form-control")
|
|
||||||
.addClass("promoted-attribute-input")
|
|
||||||
.on('change', event => this.promotedAttributeChanged(event));
|
|
||||||
|
|
||||||
const $inputCell = $("<td>").append($("<div>").addClass("input-group").append($input));
|
|
||||||
|
|
||||||
const $actionCell = $("<td>");
|
|
||||||
const $multiplicityCell = $("<td>")
|
|
||||||
.addClass("multiplicity")
|
|
||||||
.attr("nowrap", true);
|
|
||||||
|
|
||||||
$tr
|
|
||||||
.append($labelCell)
|
|
||||||
.append($inputCell)
|
|
||||||
.append($actionCell)
|
|
||||||
.append($multiplicityCell);
|
|
||||||
|
|
||||||
if (valueAttr.type === 'label') {
|
|
||||||
if (definition.labelType === 'text') {
|
|
||||||
$input.prop("type", "text");
|
|
||||||
|
|
||||||
// no need to await for this, can be done asynchronously
|
|
||||||
server.get('attributes/values/' + encodeURIComponent(valueAttr.name)).then(attributeValues => {
|
|
||||||
if (attributeValues.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
attributeValues = attributeValues.map(attribute => { return { value: attribute }; });
|
|
||||||
|
|
||||||
$input.autocomplete({
|
|
||||||
appendTo: document.querySelector('body'),
|
|
||||||
hint: false,
|
|
||||||
autoselect: false,
|
|
||||||
openOnFocus: true,
|
|
||||||
minLength: 0,
|
|
||||||
tabAutocomplete: false
|
|
||||||
}, [{
|
|
||||||
displayKey: 'value',
|
|
||||||
source: function (term, cb) {
|
|
||||||
term = term.toLowerCase();
|
|
||||||
|
|
||||||
const filtered = attributeValues.filter(attr => attr.value.toLowerCase().includes(term));
|
|
||||||
|
|
||||||
cb(filtered);
|
|
||||||
}
|
|
||||||
}]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (definition.labelType === 'number') {
|
|
||||||
$input.prop("type", "number");
|
|
||||||
|
|
||||||
let step = 1;
|
|
||||||
|
|
||||||
for (let i = 0; i < (definition.numberPrecision || 0) && i < 10; i++) {
|
|
||||||
step /= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
$input.prop("step", step);
|
|
||||||
}
|
|
||||||
else if (definition.labelType === 'boolean') {
|
|
||||||
$input.prop("type", "checkbox");
|
|
||||||
|
|
||||||
if (valueAttr.value === "true") {
|
|
||||||
$input.prop("checked", "checked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (definition.labelType === 'date') {
|
|
||||||
$input.prop("type", "date");
|
|
||||||
}
|
|
||||||
else if (definition.labelType === 'url') {
|
|
||||||
$input.prop("placeholder", "http://website...");
|
|
||||||
|
|
||||||
const $openButton = $("<span>")
|
|
||||||
.addClass("input-group-text open-external-link-button bx bx-trending-up")
|
|
||||||
.prop("title", "Open external link")
|
|
||||||
.on('click', () => window.open($input.val(), '_blank'));
|
|
||||||
|
|
||||||
$input.after($("<div>")
|
|
||||||
.addClass("input-group-append")
|
|
||||||
.append($openButton));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ws.logError("Unknown labelType=" + definitionAttr.labelType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (valueAttr.type === 'relation') {
|
|
||||||
if (valueAttr.value) {
|
|
||||||
$input.val(await treeUtils.getNoteTitle(valueAttr.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// no need to wait for this
|
|
||||||
noteAutocompleteService.initNoteAutocomplete($input);
|
|
||||||
|
|
||||||
$input.on('autocomplete:selected', (event, suggestion, dataset) => {
|
|
||||||
this.promotedAttributeChanged(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
$input.setSelectedPath(valueAttr.value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ws.logError("Unknown attribute type=" + valueAttr.type);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (definition.multiplicityType === "multivalue") {
|
|
||||||
const addButton = $("<span>")
|
|
||||||
.addClass("bx bx-plus pointer")
|
|
||||||
.prop("title", "Add new attribute")
|
|
||||||
.on('click', async () => {
|
|
||||||
const $new = await this.createPromotedAttributeRow(definitionAttr, {
|
|
||||||
attributeId: "",
|
|
||||||
type: valueAttr.type,
|
|
||||||
name: definitionAttr.name,
|
|
||||||
value: ""
|
|
||||||
});
|
|
||||||
|
|
||||||
$tr.after($new);
|
|
||||||
|
|
||||||
$new.find('input').trigger('focus');
|
|
||||||
});
|
|
||||||
|
|
||||||
const removeButton = $("<span>")
|
|
||||||
.addClass("bx bx-trash pointer")
|
|
||||||
.prop("title", "Remove this attribute")
|
|
||||||
.on('click', async () => {
|
|
||||||
if (valueAttr.attributeId) {
|
|
||||||
await server.remove("notes/" + this.ctx.note.noteId + "/attributes/" + valueAttr.attributeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tr.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
$multiplicityCell.append(addButton).append(" ").append(removeButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tr;
|
|
||||||
}
|
|
||||||
|
|
||||||
async promotedAttributeChanged(event) {
|
|
||||||
const $attr = $(event.target);
|
|
||||||
|
|
||||||
let value;
|
|
||||||
|
|
||||||
if ($attr.prop("type") === "checkbox") {
|
|
||||||
value = $attr.is(':checked') ? "true" : "false";
|
|
||||||
}
|
|
||||||
else if ($attr.prop("attribute-type") === "relation") {
|
|
||||||
const selectedPath = $attr.getSelectedPath();
|
|
||||||
|
|
||||||
value = selectedPath ? treeUtils.getNoteIdFromNotePath(selectedPath) : "";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
value = $attr.val();
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await server.put(`notes/${this.ctx.note.noteId}/attribute`, {
|
|
||||||
attributeId: $attr.prop("attribute-id"),
|
|
||||||
type: $attr.prop("attribute-type"),
|
|
||||||
name: $attr.prop("attribute-name"),
|
|
||||||
value: value
|
|
||||||
});
|
|
||||||
|
|
||||||
$attr.prop("attribute-id", result.attributeId);
|
|
||||||
|
|
||||||
// animate only if it's not being animated already, this is important especially for e.g. number inputs
|
|
||||||
// which can be changed many times in a second by clicking on higher/lower buttons.
|
|
||||||
if (this.$savedIndicator.queue().length === 0) {
|
|
||||||
this.$savedIndicator.fadeOut();
|
|
||||||
this.$savedIndicator.fadeIn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventReceived(name, data) {
|
eventReceived(name, data) {
|
||||||
if (!this.ctx.note) {
|
if (!this.ctx.note) {
|
||||||
return;
|
return;
|
||||||
|
269
src/public/javascripts/widgets/promoted_attributes.js
Normal file
269
src/public/javascripts/widgets/promoted_attributes.js
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
import server from "../services/server.js";
|
||||||
|
import ws from "../services/ws.js";
|
||||||
|
import treeUtils from "../services/tree_utils.js";
|
||||||
|
import noteAutocompleteService from "../services/note_autocomplete.js";
|
||||||
|
import TabAwareWidget from "./tab_aware_widget.js";
|
||||||
|
|
||||||
|
const TPL = `
|
||||||
|
<div class="promoted-attributes-wrapper">
|
||||||
|
<style>
|
||||||
|
.promoted-attributes-wrapper {
|
||||||
|
margin: auto;
|
||||||
|
/* setting the display to block since "table" doesn't support scrolling */
|
||||||
|
display: block;
|
||||||
|
/** flex-basis: content; - use once "content" is implemented by chrome */
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promoted-attributes td, .promoted-attributes th {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<table class="promoted-attributes"></table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default class PromotedAttributesWidget extends TabAwareWidget {
|
||||||
|
doRender() {
|
||||||
|
const $widget = $(TPL);
|
||||||
|
|
||||||
|
this.$container = $widget.find(".promoted-attributes");
|
||||||
|
|
||||||
|
return $widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
async activeTabChanged() {
|
||||||
|
this.$container.empty();
|
||||||
|
|
||||||
|
const attributes = await this.tabContext.attributes.getAttributes();
|
||||||
|
|
||||||
|
const promoted = attributes.filter(attr =>
|
||||||
|
(attr.type === 'label-definition' || attr.type === 'relation-definition')
|
||||||
|
&& !attr.name.startsWith("child:")
|
||||||
|
&& attr.value.isPromoted);
|
||||||
|
|
||||||
|
const hidePromotedAttributes = attributes.some(attr => attr.type === 'label' && attr.name === 'hidePromotedAttributes');
|
||||||
|
|
||||||
|
if (promoted.length > 0 && !hidePromotedAttributes) {
|
||||||
|
const $tbody = $("<tbody>");
|
||||||
|
|
||||||
|
for (const definitionAttr of promoted) {
|
||||||
|
const definitionType = definitionAttr.type;
|
||||||
|
const valueType = definitionType.substr(0, definitionType.length - 11);
|
||||||
|
|
||||||
|
let valueAttrs = attributes.filter(el => el.name === definitionAttr.name && el.type === valueType);
|
||||||
|
|
||||||
|
if (valueAttrs.length === 0) {
|
||||||
|
valueAttrs.push({
|
||||||
|
attributeId: "",
|
||||||
|
type: valueType,
|
||||||
|
name: definitionAttr.name,
|
||||||
|
value: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (definitionAttr.value.multiplicityType === 'singlevalue') {
|
||||||
|
valueAttrs = valueAttrs.slice(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const valueAttr of valueAttrs) {
|
||||||
|
const $tr = await this.createPromotedAttributeRow(definitionAttr, valueAttr);
|
||||||
|
|
||||||
|
$tbody.append($tr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we replace the whole content in one step so there can't be any race conditions
|
||||||
|
// (previously we saw promoted attributes doubling)
|
||||||
|
this.$container.empty().append($tbody);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPromotedAttributeRow(definitionAttr, valueAttr) {
|
||||||
|
const definition = definitionAttr.value;
|
||||||
|
const $tr = $("<tr>");
|
||||||
|
const $labelCell = $("<th>").append(valueAttr.name);
|
||||||
|
const $input = $("<input>")
|
||||||
|
.prop("tabindex", definitionAttr.position)
|
||||||
|
.prop("attribute-id", valueAttr.isOwned ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
||||||
|
.prop("attribute-type", valueAttr.type)
|
||||||
|
.prop("attribute-name", valueAttr.name)
|
||||||
|
.prop("value", valueAttr.value)
|
||||||
|
.addClass("form-control")
|
||||||
|
.addClass("promoted-attribute-input")
|
||||||
|
.on('change', event => this.promotedAttributeChanged(event));
|
||||||
|
|
||||||
|
const $inputCell = $("<td>").append($("<div>").addClass("input-group").append($input));
|
||||||
|
|
||||||
|
const $actionCell = $("<td>");
|
||||||
|
const $multiplicityCell = $("<td>")
|
||||||
|
.addClass("multiplicity")
|
||||||
|
.attr("nowrap", true);
|
||||||
|
|
||||||
|
$tr
|
||||||
|
.append($labelCell)
|
||||||
|
.append($inputCell)
|
||||||
|
.append($actionCell)
|
||||||
|
.append($multiplicityCell);
|
||||||
|
|
||||||
|
if (valueAttr.type === 'label') {
|
||||||
|
if (definition.labelType === 'text') {
|
||||||
|
$input.prop("type", "text");
|
||||||
|
|
||||||
|
// no need to await for this, can be done asynchronously
|
||||||
|
server.get('attributes/values/' + encodeURIComponent(valueAttr.name)).then(attributeValues => {
|
||||||
|
if (attributeValues.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeValues = attributeValues.map(attribute => { return { value: attribute }; });
|
||||||
|
|
||||||
|
$input.autocomplete({
|
||||||
|
appendTo: document.querySelector('body'),
|
||||||
|
hint: false,
|
||||||
|
autoselect: false,
|
||||||
|
openOnFocus: true,
|
||||||
|
minLength: 0,
|
||||||
|
tabAutocomplete: false
|
||||||
|
}, [{
|
||||||
|
displayKey: 'value',
|
||||||
|
source: function (term, cb) {
|
||||||
|
term = term.toLowerCase();
|
||||||
|
|
||||||
|
const filtered = attributeValues.filter(attr => attr.value.toLowerCase().includes(term));
|
||||||
|
|
||||||
|
cb(filtered);
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (definition.labelType === 'number') {
|
||||||
|
$input.prop("type", "number");
|
||||||
|
|
||||||
|
let step = 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < (definition.numberPrecision || 0) && i < 10; i++) {
|
||||||
|
step /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
$input.prop("step", step);
|
||||||
|
}
|
||||||
|
else if (definition.labelType === 'boolean') {
|
||||||
|
$input.prop("type", "checkbox");
|
||||||
|
|
||||||
|
if (valueAttr.value === "true") {
|
||||||
|
$input.prop("checked", "checked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (definition.labelType === 'date') {
|
||||||
|
$input.prop("type", "date");
|
||||||
|
}
|
||||||
|
else if (definition.labelType === 'url') {
|
||||||
|
$input.prop("placeholder", "http://website...");
|
||||||
|
|
||||||
|
const $openButton = $("<span>")
|
||||||
|
.addClass("input-group-text open-external-link-button bx bx-trending-up")
|
||||||
|
.prop("title", "Open external link")
|
||||||
|
.on('click', () => window.open($input.val(), '_blank'));
|
||||||
|
|
||||||
|
$input.after($("<div>")
|
||||||
|
.addClass("input-group-append")
|
||||||
|
.append($openButton));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ws.logError("Unknown labelType=" + definitionAttr.labelType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (valueAttr.type === 'relation') {
|
||||||
|
if (valueAttr.value) {
|
||||||
|
$input.val(await treeUtils.getNoteTitle(valueAttr.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need to wait for this
|
||||||
|
noteAutocompleteService.initNoteAutocomplete($input);
|
||||||
|
|
||||||
|
$input.on('autocomplete:selected', (event, suggestion, dataset) => {
|
||||||
|
this.promotedAttributeChanged(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.setSelectedPath(valueAttr.value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ws.logError("Unknown attribute type=" + valueAttr.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (definition.multiplicityType === "multivalue") {
|
||||||
|
const addButton = $("<span>")
|
||||||
|
.addClass("bx bx-plus pointer")
|
||||||
|
.prop("title", "Add new attribute")
|
||||||
|
.on('click', async () => {
|
||||||
|
const $new = await this.createPromotedAttributeRow(definitionAttr, {
|
||||||
|
attributeId: "",
|
||||||
|
type: valueAttr.type,
|
||||||
|
name: definitionAttr.name,
|
||||||
|
value: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
$tr.after($new);
|
||||||
|
|
||||||
|
$new.find('input').trigger('focus');
|
||||||
|
});
|
||||||
|
|
||||||
|
const removeButton = $("<span>")
|
||||||
|
.addClass("bx bx-trash pointer")
|
||||||
|
.prop("title", "Remove this attribute")
|
||||||
|
.on('click', async () => {
|
||||||
|
if (valueAttr.attributeId) {
|
||||||
|
await server.remove("notes/" + this.tabContext.note.noteId + "/attributes/" + valueAttr.attributeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tr.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
$multiplicityCell.append(addButton).append(" ").append(removeButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
async promotedAttributeChanged(event) {
|
||||||
|
const $attr = $(event.target);
|
||||||
|
|
||||||
|
let value;
|
||||||
|
|
||||||
|
if ($attr.prop("type") === "checkbox") {
|
||||||
|
value = $attr.is(':checked') ? "true" : "false";
|
||||||
|
}
|
||||||
|
else if ($attr.prop("attribute-type") === "relation") {
|
||||||
|
const selectedPath = $attr.getSelectedPath();
|
||||||
|
|
||||||
|
value = selectedPath ? treeUtils.getNoteIdFromNotePath(selectedPath) : "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = $attr.val();
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await server.put(`notes/${this.tabContext.note.noteId}/attribute`, {
|
||||||
|
attributeId: $attr.prop("attribute-id"),
|
||||||
|
type: $attr.prop("attribute-type"),
|
||||||
|
name: $attr.prop("attribute-name"),
|
||||||
|
value: value
|
||||||
|
});
|
||||||
|
|
||||||
|
$attr.prop("attribute-id", result.attributeId);
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
// animate only if it's not being animated already, this is important especially for e.g. number inputs
|
||||||
|
// which can be changed many times in a second by clicking on higher/lower buttons.
|
||||||
|
// if (this.$savedIndicator.queue().length === 0) {
|
||||||
|
// this.$savedIndicator.fadeOut();
|
||||||
|
// this.$savedIndicator.fadeIn();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
@ -478,21 +478,6 @@ button.icon-button {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-detail-promoted-attributes {
|
|
||||||
margin: auto;
|
|
||||||
/* setting the display to block since "table" doesn't support scrolling */
|
|
||||||
display: block;
|
|
||||||
/** flex-basis: content; - use once "content" is implemented by chrome */
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
max-height: 30%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-promoted-attributes td, .note-detail-promoted-attributes th {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-image {
|
.note-detail-image {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,6 @@
|
|||||||
<div id="note-tab-container">
|
<div id="note-tab-container">
|
||||||
<div class="note-tab-content note-tab-content-template">
|
<div class="note-tab-content note-tab-content-template">
|
||||||
<div class="note-detail-content">
|
<div class="note-detail-content">
|
||||||
<div class="note-detail-script-area"></div>
|
|
||||||
|
|
||||||
<table class="note-detail-promoted-attributes"></table>
|
|
||||||
|
|
||||||
<div class="note-detail-component-wrapper">
|
<div class="note-detail-component-wrapper">
|
||||||
<div class="note-detail-text note-detail-component">
|
<div class="note-detail-text note-detail-component">
|
||||||
<div class="note-detail-text-editor" tabindex="10000"></div>
|
<div class="note-detail-text-editor" tabindex="10000"></div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user