import QtQuick import QtQuick.Controls import QtQuick.Layouts import Flora // Свойства заметки (Notion-поля): источник правды — frontmatter в самом .md, // база обновляется следом. Грязную вкладку правим только после сохранения. Dialog { id: root property string filePath: "" title: qsTr("Свойства заметки") modal: true width: 520 height: 560 anchors.centerIn: parent closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside standardButtons: Dialog.NoButton background: Rectangle { color: Theme.surface; border.color: Theme.borderStrong; radius: 10 } footer: RowLayout { spacing: 8 Item { Layout.fillWidth: true } Button { text: qsTr("Отмена") onClicked: root.reject() } Button { text: qsTr("Сохранить") highlighted: true onClicked: root.saveProps() } } onOpened: { statusLabel.text = "" newKeyField.text = "" newValueField.text = "" propsModel.clear() const props = vaultDatabase.fileProperties(root.filePath) const keys = Object.keys(props).sort() for (let i = 0; i < keys.length; ++i) propsModel.append({ key: keys[i], value: props[keys[i]] }) } function saveProps() { const map = {} for (let i = 0; i < propsModel.count; ++i) { const row = propsModel.get(i) map[row.key] = row.value } const err = vaultDatabase.saveFileProperties(root.filePath, map) if (err.length > 0) { statusLabel.text = err return } // Обновить открытые вкладки с этим файлом (только чистые) let dirty = false for (let t = 0; t < documentManager.openTabs.length; ++t) { if (documentManager.pathAt(t) === root.filePath) { if (documentManager.isDirty(t)) { dirty = true } else { documentManager.reloadDocument(t) } } } vaultIndex.updateFile(root.filePath) vaultDatabase.syncFile(root.filePath) if (dirty) { statusLabel.text = qsTr("Свойства записаны, но вкладка изменена — сохраните её (Ctrl+S) и переоткройте.") return } root.accept() } function addProp() { const k = newKeyField.text.trim() const v = newValueField.text if (k.length === 0) { statusLabel.text = qsTr("Укажите имя свойства.") return } for (let i = 0; i < propsModel.count; ++i) { if (propsModel.get(i).key === k) { statusLabel.text = qsTr("Такое свойство уже есть — правьте его в списке.") return } } propsModel.append({ key: k, value: v }) newKeyField.text = "" newValueField.text = "" statusLabel.text = "" } ListModel { id: propsModel } ColumnLayout { anchors.fill: parent spacing: 10 Label { Layout.fillWidth: true color: Theme.textSecondary font.pixelSize: 11 text: root.filePath elide: Text.ElideMiddle } ListView { id: propsView Layout.fillWidth: true Layout.fillHeight: true model: propsModel clip: true spacing: 6 delegate: RowLayout { width: propsView.width spacing: 8 Label { text: model.key color: Theme.text font.bold: true Layout.preferredWidth: 140 elide: Text.ElideRight } TextField { Layout.fillWidth: true text: model.value placeholderText: qsTr("значение") onTextChanged: propsModel.setProperty(index, "value", text) } Button { text: "✕" onClicked: propsModel.remove(index) } } Label { visible: propsModel.count === 0 anchors.centerIn: parent text: qsTr("Свойств нет — добавьте первое ниже") color: Theme.textFaint } } RowLayout { Layout.fillWidth: true spacing: 8 TextField { id: newKeyField Layout.preferredWidth: 150 placeholderText: qsTr("имя") onAccepted: root.addProp() } TextField { id: newValueField Layout.fillWidth: true placeholderText: qsTr("значение") onAccepted: root.addProp() } Button { text: qsTr("Добавить") onClicked: root.addProp() } } Label { text: qsTr("Имя: буквы, цифры, _ и - (до 64 символов).") color: Theme.textFaint font.pixelSize: 11 } Label { id: statusLabel Layout.fillWidth: true wrapMode: Text.Wrap color: "#e5484d" font.pixelSize: 11 } } }