").addClass("input-group").append($input));
        const $actionCell = $("
");
        const $multiplicityCell = $(" | ")
            .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 => ({ 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);
                        }
                    }]);
                    $input.on('autocomplete:selected', e => this.promotedAttributeChanged(e))
                });
            }
            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 = $("")
                    .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($(" ")
                    .addClass("input-group-append")
                    .append($openButton));
            }
            else {
                ws.logError("Unknown labelType=" + definitionAttr.labelType);
            }
        }
        else if (valueAttr.type === 'relation') {
            if (valueAttr.value) {
                $input.val(await treeService.getNoteTitle(valueAttr.value));
            }
            // no need to wait for this
            noteAutocompleteService.initNoteAutocomplete($input);
            $input.on('autocomplete:selected', (event, suggestion, dataset) => {
                this.promotedAttributeChanged(event);
            });
            $input.setSelectedNotePath(valueAttr.value);
        }
        else {
            ws.logError("Unknown attribute type=" + valueAttr.type);
            return;
        }
        if (definition.multiplicity === "multivalue") {
            const addButton = $("")
                .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 = $("")
                .addClass("bx bx-trash pointer")
                .prop("title", "Remove this attribute")
                .on('click', async () => {
                    if (valueAttr.attributeId) {
                        await server.remove("notes/" + this.noteId + "/attributes/" + valueAttr.attributeId, this.componentId);
                    }
                    $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.getSelectedNotePath();
            value = selectedPath ? treeService.getNoteIdFromNotePath(selectedPath) : "";
        }
        else {
            value = $attr.val();
        }
        const result = await server.put(`notes/${this.noteId}/attribute`, {
            attributeId: $attr.prop("attribute-id"),
            type: $attr.prop("attribute-type"),
            name: $attr.prop("attribute-name"),
            value: value
        }, this.componentId);
        $attr.prop("attribute-id", result.attributeId);
    }
    entitiesReloadedEvent({loadResults}) {
        if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) {
            this.refresh();
        }
    }
}
  |