2026-08-17 00:05:20 +03:00
|
|
|
|
import QtQuick
|
|
|
|
|
|
import QtQuick.Controls
|
|
|
|
|
|
import QtQuick.Layouts
|
2026-09-01 19:25:07 +03:00
|
|
|
|
import Flora
|
2026-08-17 00:05:20 +03:00
|
|
|
|
|
|
|
|
|
|
ApplicationWindow {
|
|
|
|
|
|
id: window
|
|
|
|
|
|
width: 1280
|
|
|
|
|
|
height: 800
|
2026-09-01 19:25:07 +03:00
|
|
|
|
minimumWidth: 900
|
|
|
|
|
|
minimumHeight: 600
|
2026-08-17 00:05:20 +03:00
|
|
|
|
visible: true
|
|
|
|
|
|
title: "Flora"
|
2026-09-01 19:25:07 +03:00
|
|
|
|
color: Theme.bg
|
|
|
|
|
|
palette.window: Theme.bg
|
|
|
|
|
|
palette.windowText: Theme.text
|
|
|
|
|
|
palette.base: Theme.surface
|
|
|
|
|
|
palette.text: Theme.text
|
|
|
|
|
|
palette.highlight: Theme.accent
|
|
|
|
|
|
palette.highlightedText: "white"
|
2026-08-17 00:05:20 +03:00
|
|
|
|
|
2026-08-17 01:02:05 +03:00
|
|
|
|
property string windowVault: ""
|
|
|
|
|
|
|
|
|
|
|
|
readonly property string activeVault: {
|
|
|
|
|
|
if (fileStore.ready) return fileStore.vault
|
|
|
|
|
|
if (windowVault.length > 0) return windowVault
|
|
|
|
|
|
return vaultManager.currentVault
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 00:43:33 +03:00
|
|
|
|
WelcomeWizard {
|
|
|
|
|
|
id: welcomeWizard
|
|
|
|
|
|
firstRun: vaultManager.vaults.length === 0
|
2026-08-17 01:02:05 +03:00
|
|
|
|
onFinished: {
|
|
|
|
|
|
if (fileStore.ready) {
|
|
|
|
|
|
windowController.openWindow(welcomeWizard.vaultPath)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fileStore.openVault(welcomeWizard.vaultPath)
|
|
|
|
|
|
root_status("Хранилище открыто")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-17 00:43:33 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openWizard() {
|
|
|
|
|
|
welcomeWizard.firstRun = false
|
|
|
|
|
|
welcomeWizard.open()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 01:02:05 +03:00
|
|
|
|
function root_status(msg, ok) {
|
2026-09-01 19:25:07 +03:00
|
|
|
|
// статус можно показать в UI, пока просто игнорируем
|
2026-08-17 01:02:05 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Component.onCompleted: {
|
|
|
|
|
|
if (windowVault.length > 0) {
|
|
|
|
|
|
fileStore.openVault(windowVault)
|
|
|
|
|
|
} else if (vaultManager.currentVault.length > 0) {
|
|
|
|
|
|
fileStore.openVault(vaultManager.currentVault)
|
|
|
|
|
|
}
|
2026-08-17 17:29:23 +03:00
|
|
|
|
if (vaultManager.vaults.length === 0) {
|
2026-08-17 01:02:05 +03:00
|
|
|
|
welcomeWizard.open()
|
2026-08-17 17:29:23 +03:00
|
|
|
|
}
|
2026-08-17 01:02:05 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 00:05:20 +03:00
|
|
|
|
menuBar: MenuBar {
|
2026-09-01 19:25:07 +03:00
|
|
|
|
// Переопределяем цвета под тему (Basic стиль не красит сам)
|
|
|
|
|
|
background: Rectangle { color: Theme.bgAlt; border.color: Theme.divider }
|
2026-08-17 00:05:20 +03:00
|
|
|
|
Menu {
|
|
|
|
|
|
title: qsTr("&Файл")
|
2026-08-17 00:43:33 +03:00
|
|
|
|
Action { text: qsTr("Добавить хранилище…"); onTriggered: window.openWizard() }
|
2026-08-17 00:05:20 +03:00
|
|
|
|
Menu {
|
|
|
|
|
|
title: qsTr("Экспорт")
|
|
|
|
|
|
Action { text: qsTr("В HTML…"); enabled: false }
|
|
|
|
|
|
Action { text: qsTr("В PDF…"); enabled: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
MenuSeparator { }
|
|
|
|
|
|
Action { text: qsTr("Выход"); onTriggered: window.close() }
|
|
|
|
|
|
}
|
|
|
|
|
|
Menu {
|
|
|
|
|
|
title: qsTr("&Правка")
|
|
|
|
|
|
Action { text: qsTr("Отменить"); shortcut: "Ctrl+Z"; enabled: false }
|
|
|
|
|
|
Action { text: qsTr("Повторить"); shortcut: "Ctrl+Shift+Z"; enabled: false }
|
|
|
|
|
|
MenuSeparator { }
|
|
|
|
|
|
Action {
|
2026-09-01 19:40:55 +03:00
|
|
|
|
text: qsTr("История файла…")
|
|
|
|
|
|
shortcut: "Ctrl+H"
|
|
|
|
|
|
enabled: documentManager.activeIndex >= 0 && fileStore.ready
|
|
|
|
|
|
onTriggered: {
|
|
|
|
|
|
const p = documentManager.pathAt(documentManager.activeIndex)
|
|
|
|
|
|
if (p.length>0) {
|
|
|
|
|
|
historyDialog.vaultPath = fileStore.vault
|
|
|
|
|
|
historyDialog.filePath = p
|
|
|
|
|
|
historyDialog.open()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Action {
|
|
|
|
|
|
text: qsTr("Вернуться к предыдущей версии…")
|
|
|
|
|
|
enabled: documentManager.activeIndex >= 0 && fileStore.ready
|
|
|
|
|
|
onTriggered: {
|
|
|
|
|
|
const p = documentManager.pathAt(documentManager.activeIndex)
|
|
|
|
|
|
if (p.length>0) {
|
|
|
|
|
|
historyDialog.vaultPath = fileStore.vault
|
|
|
|
|
|
historyDialog.filePath = p
|
|
|
|
|
|
historyDialog.open()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-17 00:05:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Menu {
|
|
|
|
|
|
title: qsTr("&Вид")
|
2026-09-01 19:25:07 +03:00
|
|
|
|
ActionGroup { id: themeGroup }
|
|
|
|
|
|
Action {
|
|
|
|
|
|
text: qsTr("Светлая тема")
|
|
|
|
|
|
checkable: true
|
|
|
|
|
|
checked: settingsStore.theme !== "dark"
|
|
|
|
|
|
ActionGroup.group: themeGroup
|
|
|
|
|
|
onTriggered: settingsStore.theme = "light"
|
|
|
|
|
|
}
|
|
|
|
|
|
Action {
|
|
|
|
|
|
text: qsTr("Тёмная тема")
|
|
|
|
|
|
checkable: true
|
|
|
|
|
|
checked: settingsStore.theme === "dark"
|
|
|
|
|
|
ActionGroup.group: themeGroup
|
|
|
|
|
|
onTriggered: settingsStore.theme = "dark"
|
|
|
|
|
|
}
|
|
|
|
|
|
MenuSeparator { }
|
|
|
|
|
|
Action {
|
|
|
|
|
|
text: qsTr("Сбросить ширины панелей")
|
|
|
|
|
|
onTriggered: {
|
|
|
|
|
|
settingsStore.leftPanelWidth = 220
|
|
|
|
|
|
settingsStore.rightPanelWidth = 220
|
|
|
|
|
|
leftPanel.width = 220
|
|
|
|
|
|
rightPanel.width = 220
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-17 00:05:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
Menu {
|
|
|
|
|
|
title: qsTr("&Справка")
|
|
|
|
|
|
Action {
|
|
|
|
|
|
text: qsTr("О программе")
|
|
|
|
|
|
onTriggered: aboutDialog.open()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Dialog {
|
|
|
|
|
|
id: aboutDialog
|
|
|
|
|
|
title: qsTr("О программе")
|
|
|
|
|
|
standardButtons: Dialog.Ok
|
|
|
|
|
|
modal: true
|
|
|
|
|
|
anchors.centerIn: parent
|
2026-09-01 19:25:07 +03:00
|
|
|
|
background: Rectangle { color: Theme.surface; border.color: Theme.border; radius: 8 }
|
2026-08-17 00:05:20 +03:00
|
|
|
|
Label {
|
2026-09-01 19:25:07 +03:00
|
|
|
|
color: Theme.text
|
2026-08-17 00:05:20 +03:00
|
|
|
|
text: "Flora\nМинималистичный markdown-заметочник (GPL-3.0)\n"
|
|
|
|
|
|
+ "Версия " + Qt.application.version
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-01 19:40:55 +03:00
|
|
|
|
HistoryDialog {
|
|
|
|
|
|
id: historyDialog
|
|
|
|
|
|
onRestored: {
|
|
|
|
|
|
// после восстановления обновляем граф и статус
|
|
|
|
|
|
if (fileStore.ready) vaultIndex.rebuild(fileStore.vault)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 00:05:20 +03:00
|
|
|
|
Popup {
|
|
|
|
|
|
id: vaultSwitcher
|
|
|
|
|
|
x: 12
|
|
|
|
|
|
y: window.height - height - 12
|
2026-09-01 19:25:07 +03:00
|
|
|
|
width: 340
|
2026-08-17 00:05:20 +03:00
|
|
|
|
modal: false
|
|
|
|
|
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
2026-09-01 19:25:07 +03:00
|
|
|
|
background: Rectangle { color: Theme.surface; border.color: Theme.borderStrong; radius: 8 }
|
2026-08-17 00:43:33 +03:00
|
|
|
|
|
2026-08-17 00:05:20 +03:00
|
|
|
|
ColumnLayout {
|
|
|
|
|
|
width: parent.width
|
2026-09-01 19:25:07 +03:00
|
|
|
|
spacing: 8
|
2026-08-17 00:43:33 +03:00
|
|
|
|
|
|
|
|
|
|
Label {
|
|
|
|
|
|
text: qsTr("Хранилища")
|
|
|
|
|
|
font.bold: true
|
2026-09-01 19:25:07 +03:00
|
|
|
|
color: Theme.text
|
2026-08-17 00:43:33 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ListView {
|
|
|
|
|
|
id: vaultList
|
|
|
|
|
|
Layout.preferredHeight: Math.min(200, contentHeight)
|
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
model: vaultManager.vaults
|
|
|
|
|
|
clip: true
|
2026-09-01 19:25:07 +03:00
|
|
|
|
ScrollBar.vertical: ScrollBar {}
|
2026-08-17 00:43:33 +03:00
|
|
|
|
delegate: ItemDelegate {
|
|
|
|
|
|
width: ListView.view.width
|
|
|
|
|
|
text: modelData
|
2026-08-17 01:02:05 +03:00
|
|
|
|
highlighted: modelData === window.activeVault
|
2026-09-01 19:25:07 +03:00
|
|
|
|
palette.highlight: Theme.accent
|
2026-08-17 00:43:33 +03:00
|
|
|
|
onClicked: {
|
2026-08-17 01:02:05 +03:00
|
|
|
|
if (modelData === window.activeVault) {
|
|
|
|
|
|
vaultSwitcher.close()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
windowController.openWindow(modelData)
|
|
|
|
|
|
vaultSwitcher.close()
|
2026-08-17 00:43:33 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Label {
|
|
|
|
|
|
visible: vaultList.count === 0
|
|
|
|
|
|
text: qsTr("Хранилищ пока нет")
|
2026-09-01 19:25:07 +03:00
|
|
|
|
color: Theme.textMuted
|
2026-08-17 00:43:33 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Label {
|
2026-08-17 01:02:05 +03:00
|
|
|
|
text: qsTr("Текущее: %1").arg(window.activeVault.length > 0
|
|
|
|
|
|
? window.activeVault : "—")
|
2026-09-01 19:25:07 +03:00
|
|
|
|
color: Theme.textSecondary
|
2026-08-17 00:43:33 +03:00
|
|
|
|
elide: Text.ElideMiddle
|
|
|
|
|
|
Layout.fillWidth: true
|
2026-09-01 19:25:07 +03:00
|
|
|
|
font.pixelSize: 11
|
2026-08-17 00:43:33 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 00:05:20 +03:00
|
|
|
|
Button {
|
2026-08-17 00:43:33 +03:00
|
|
|
|
Layout.fillWidth: true
|
2026-08-17 00:05:20 +03:00
|
|
|
|
text: qsTr("Добавить новое хранилище…")
|
2026-08-17 00:43:33 +03:00
|
|
|
|
onClicked: { vaultSwitcher.close(); window.openWizard() }
|
2026-08-17 00:05:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SplitView {
|
2026-09-01 19:25:07 +03:00
|
|
|
|
id: mainSplit
|
2026-08-17 00:05:20 +03:00
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
orientation: Qt.Horizontal
|
2026-09-01 22:37:40 +03:00
|
|
|
|
// Увеличенная зона захвата — работает по всей высоте
|
2026-09-01 19:25:07 +03:00
|
|
|
|
handle: Rectangle {
|
2026-09-01 22:37:40 +03:00
|
|
|
|
implicitWidth: 12
|
|
|
|
|
|
color: "transparent"
|
2026-09-01 22:23:09 +03:00
|
|
|
|
Rectangle {
|
2026-09-01 19:25:07 +03:00
|
|
|
|
anchors.centerIn: parent
|
2026-09-01 22:23:09 +03:00
|
|
|
|
width: 1; height: parent.height
|
|
|
|
|
|
color: Theme.divider
|
|
|
|
|
|
}
|
|
|
|
|
|
Rectangle {
|
2026-09-01 22:37:40 +03:00
|
|
|
|
anchors.fill: parent
|
2026-09-01 19:25:07 +03:00
|
|
|
|
color: "transparent"
|
|
|
|
|
|
MouseArea {
|
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
cursorShape: Qt.SplitHCursor
|
2026-09-01 22:23:09 +03:00
|
|
|
|
hoverEnabled: true
|
2026-09-01 19:25:07 +03:00
|
|
|
|
onPressed: mouse.accepted = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-17 00:05:20 +03:00
|
|
|
|
|
|
|
|
|
|
FileTreePanel {
|
|
|
|
|
|
id: leftPanel
|
2026-09-01 19:25:07 +03:00
|
|
|
|
SplitView.preferredWidth: settingsStore.leftPanelWidth
|
|
|
|
|
|
SplitView.minimumWidth: 180
|
2026-08-17 00:05:20 +03:00
|
|
|
|
SplitView.maximumWidth: 500
|
2026-09-01 19:25:07 +03:00
|
|
|
|
onWidthChanged: {
|
|
|
|
|
|
if (width >= 180 && width <= 500)
|
|
|
|
|
|
settingsStore.leftPanelWidth = width
|
|
|
|
|
|
}
|
2026-08-17 01:02:05 +03:00
|
|
|
|
|
2026-08-17 00:05:20 +03:00
|
|
|
|
onOpenVaultRequested: vaultSwitcher.open()
|
2026-08-17 01:02:05 +03:00
|
|
|
|
onOpenFileRequested: (path) => {
|
2026-08-17 01:14:00 +03:00
|
|
|
|
if (documentManager.isTextForEditor(path)) {
|
|
|
|
|
|
documentManager.openDocument(path)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
leftPanel.showStatus(qsTr("Предпросмотр HTML/PDF/изображений появится в Фазе 6-10"), false)
|
|
|
|
|
|
}
|
2026-08-17 01:02:05 +03:00
|
|
|
|
}
|
2026-09-01 19:40:55 +03:00
|
|
|
|
onHistoryRequested: (path) => {
|
|
|
|
|
|
historyDialog.vaultPath = fileStore.vault
|
|
|
|
|
|
historyDialog.filePath = path
|
|
|
|
|
|
historyDialog.open()
|
|
|
|
|
|
}
|
2026-08-17 00:05:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorPanel {
|
2026-09-01 19:25:07 +03:00
|
|
|
|
id: centerPanel
|
2026-08-17 00:05:20 +03:00
|
|
|
|
SplitView.fillWidth: true
|
|
|
|
|
|
SplitView.minimumWidth: 300
|
2026-09-01 19:25:07 +03:00
|
|
|
|
height: window.height - (menuBar ? menuBar.height : 0)
|
2026-09-01 19:40:55 +03:00
|
|
|
|
onHistoryRequested: (path) => {
|
|
|
|
|
|
historyDialog.vaultPath = fileStore.vault
|
|
|
|
|
|
historyDialog.filePath = path
|
|
|
|
|
|
historyDialog.open()
|
|
|
|
|
|
}
|
2026-08-17 00:05:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RightPanel {
|
2026-09-01 19:25:07 +03:00
|
|
|
|
id: rightPanel
|
|
|
|
|
|
SplitView.preferredWidth: settingsStore.rightPanelWidth
|
|
|
|
|
|
SplitView.minimumWidth: 200
|
2026-08-17 00:05:20 +03:00
|
|
|
|
SplitView.maximumWidth: 500
|
2026-09-01 19:25:07 +03:00
|
|
|
|
onWidthChanged: {
|
|
|
|
|
|
if (width >= 200 && width <= 500)
|
|
|
|
|
|
settingsStore.rightPanelWidth = width
|
|
|
|
|
|
}
|
2026-08-17 00:05:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-01 19:25:07 +03:00
|
|
|
|
}
|