import server from "./server.js"; import utils from "./utils.js"; import messagingService from "./messaging.js"; import treeUtils from "./tree_utils.js"; import noteAutocompleteService from "./note_autocomplete.js"; import linkService from "./link.js"; class Attributes { /** * @param {TabContext} ctx */ constructor(ctx) { this.ctx = ctx; this.$attributeList = ctx.$tabContent.find(".attribute-list"); this.$attributeListInner = ctx.$tabContent.find(".attribute-list-inner"); this.$promotedAttributesContainer = ctx.$tabContent.find(".note-detail-promoted-attributes"); this.$savedIndicator = ctx.$tabContent.find(".saved-indicator"); this.attributePromise = null; } invalidateAttributes() { this.attributePromise = null; } reloadAttributes() { this.attributePromise = server.get(`notes/${this.ctx.note.noteId}/attributes`); } async refreshAttributes() { this.reloadAttributes(); await this.showAttributes(); } async getAttributes() { if (!this.attributePromise) { this.reloadAttributes(); } return await this.attributePromise; } async showAttributes() { this.$promotedAttributesContainer.empty(); this.$attributeList.hide(); this.$attributeListInner.empty(); const note = this.ctx.note; 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 = $("
"); 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); } else if (note.type !== 'relation-map') { // display only "own" notes const ownedAttributes = attributes.filter(attr => attr.noteId === note.noteId); if (ownedAttributes.length > 0) { for (const attribute of ownedAttributes) { if (attribute.type === 'label') { this.$attributeListInner.append(utils.formatLabel(attribute) + " "); } else if (attribute.type === 'relation') { if (attribute.value) { this.$attributeListInner.append('@' + attribute.name + "="); this.$attributeListInner.append(await linkService.createNoteLink(attribute.value)); this.$attributeListInner.append(" "); } else { messagingService.logError(`Relation ${attribute.attributeId} has empty target`); } } else if (attribute.type === 'label-definition' || attribute.type === 'relation-definition') { this.$attributeListInner.append(attribute.name + " definition "); } else { messagingService.logError("Unknown attr type: " + attribute.type); } } this.$attributeList.show(); } } return attributes; } async createPromotedAttributeRow(definitionAttr, valueAttr) { const definition = definitionAttr.value; const $tr = $("