182 lines
5.7 KiB
QML
182 lines
5.7 KiB
QML
|
|
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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|