Files
Flora/qml/dialogs/FilePropertiesDialog.qml
grendervill fdf59f5b39 SQLite-база хранилища: свойства, ~sql-блоки, настройки хранилища
- Frontmatter: шапка --- ключ: значение (источник правды свойств)
- VaultSettings: .flora/settings.json (журнал, sqlEnabled, sqlMaxRows)
- Database: files/tags/file_tags/links/properties, только SELECT/WITH
  на read-only соединении (mode=ro + query_only + зонд), лимит строк
- MdParser: ~sql-блоки таблицей (parseBlocks + Renderer), шапка скрыта в просмотре
- Окно: per-window Database/VaultSettings, sync при сохранении, дебаунс reindex
- QML: VaultSettingsDialog, FilePropertiesDialog, меню, автообновление ~sql
- FileStore.journalDir, DocumentManager.reloadDocument
- Тест flora_db_test (23/23 зелёных)
2026-09-07 16:30:06 +03:00

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
}
}
}