Prod: полный аудит и стабилизация — исправлены критические баги, расширено покрытие тестами, подготовлен бандл
- FileStore: защита от выхода за пределы хранилища (isInsideVault), валидация имён (запрет скрытых, зарезервированных, управляющих символов), проверка destDir, лимит watcher, dailyNote валидация, openVault проверка isDir - DocumentManager: лимит 10МБ, кламп автосейва 0.5-120с, защита от OOM, бинарный детект, проверка путей - VaultIndex: исправлен парсинг тегов (заголовки, инлайн-код), case-insensitive поиск, дедупликация рёбер, защита от traversal, лимит глубины, проверка vaultExists - MdParser: блокировка всех on* событий, javascript:/data:, esc кавычек, MIME fallback, normalizeLang расширен (c/cpp/py/js и кириллица), wiki глобальный поиск, image лимит 3МБ, защита clipboard - GitManager: commitAll с GIT_INDEX_ADD_DEFAULT и поддержкой пустых коммитов/удалений, fileGitStatus с cleanPath и проверкой префикса, repoExists/hasCommits с cleanPath, initRepo валидация - SettingsStore: encodeKey/encodeRel для QSettings Ini, flat хранение viewMode, sync после записи, кламп ширин панелей, миграция старых ключей - SyncManager: счётчик busy, обработка httpStatus при network error, валидация токена/провайдера/URL, GitHub Enterprise URL, таймаут - VaultManager: валидация путей (корень, файл vs папка, права), проверка вложенности - WindowController: per-window FileStore/DocumentManager/VaultIndex, cleanPath, закрытие окна через QQuickWindow::closing, обработка ошибок компонента - QML: TableEditor пустая строка, WelcomeWizard QUrl парсинг, FileTree фильтр, GraphView лимит 600 узлов, RightPanel debounce rebuild, Theme singleton, MonthCalendar и др. - Тесты: добавлены flora_settings_test, flora_sync_test, flora_extended_test, flora_integration_test — покрытие всех модулей, 20/20 зелёных - Сборка: обновлён Flora.app (4.6М бинарь -> 173М бандл, ad-hoc подпись, проверен offscreen запуск, версия 0.0.1 + cmark 0.29 + libgit2 1.9) - .gitignore: добавлены Testing/, coverage, gcda/gcov, __pycache__, .tmp, test-vault и др.
This commit is contained in:
+82
-44
@@ -1,19 +1,24 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Flora
|
||||
|
||||
// Главное окно: меню вверху, три панели (дерево / редактор / правая).
|
||||
// Мульти-хранилища: каждое окно привязано к своему хранилищу (windowVault).
|
||||
ApplicationWindow {
|
||||
id: window
|
||||
width: 1280
|
||||
height: 800
|
||||
minimumWidth: 900
|
||||
minimumHeight: 600
|
||||
visible: true
|
||||
title: "Flora"
|
||||
color: "white"
|
||||
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"
|
||||
|
||||
// Хранилище этого окна (задаётся WindowController); у основного окна
|
||||
// может быть пустым — тогда берём основное хранилище из настроек.
|
||||
property string windowVault: ""
|
||||
|
||||
readonly property string activeVault: {
|
||||
@@ -27,10 +32,8 @@ ApplicationWindow {
|
||||
firstRun: vaultManager.vaults.length === 0
|
||||
onFinished: {
|
||||
if (fileStore.ready) {
|
||||
// Добавлено новое хранилище при уже открытом — новое окно.
|
||||
windowController.openWindow(welcomeWizard.vaultPath)
|
||||
} else {
|
||||
// Первый запуск: применяем хранилище к этому окну.
|
||||
fileStore.openVault(welcomeWizard.vaultPath)
|
||||
root_status("Хранилище открыто")
|
||||
}
|
||||
@@ -43,7 +46,7 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
function root_status(msg, ok) {
|
||||
console.log(msg)
|
||||
// статус можно показать в UI, пока просто игнорируем
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -52,34 +55,14 @@ ApplicationWindow {
|
||||
} else if (vaultManager.currentVault.length > 0) {
|
||||
fileStore.openVault(vaultManager.currentVault)
|
||||
}
|
||||
// Если хранилищ нет — мастер откроется автоматически.
|
||||
if (vaultManager.vaults.length === 0) {
|
||||
welcomeWizard.open()
|
||||
} else {
|
||||
// Автооткрытие первого .md файла для тестирования
|
||||
autoOpenTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoOpenTimer
|
||||
interval: 200
|
||||
onTriggered: {
|
||||
if (!fileStore.ready) return
|
||||
for (let i = 0; i < fileStore.rowCount(); i++) {
|
||||
if (!fileStore.itemIsDir(i)) {
|
||||
const p = fileStore.itemPath(i)
|
||||
if (p.endsWith(".md")) {
|
||||
documentManager.openDocument(p)
|
||||
console.log("AUTO-OPENED:", p, "len:", documentManager.contentAt(documentManager.activeIndex).length)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menuBar: MenuBar {
|
||||
// Переопределяем цвета под тему (Basic стиль не красит сам)
|
||||
background: Rectangle { color: Theme.bgAlt; border.color: Theme.divider }
|
||||
Menu {
|
||||
title: qsTr("&Файл")
|
||||
Action { text: qsTr("Добавить хранилище…"); onTriggered: window.openWizard() }
|
||||
@@ -98,13 +81,35 @@ ApplicationWindow {
|
||||
MenuSeparator { }
|
||||
Action {
|
||||
text: qsTr("Вернуться к предыдущей версии…"); enabled: false
|
||||
// Диалог истории с diff-режимом — Фаза 9.
|
||||
}
|
||||
}
|
||||
Menu {
|
||||
title: qsTr("&Вид")
|
||||
Action { text: qsTr("Светлая тема"); checkable: true; checked: true }
|
||||
Action { text: qsTr("Тёмная тема"); checkable: true }
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Menu {
|
||||
title: qsTr("&Справка")
|
||||
@@ -121,28 +126,31 @@ ApplicationWindow {
|
||||
standardButtons: Dialog.Ok
|
||||
modal: true
|
||||
anchors.centerIn: parent
|
||||
background: Rectangle { color: Theme.surface; border.color: Theme.border; radius: 8 }
|
||||
Label {
|
||||
color: Theme.text
|
||||
text: "Flora\nМинималистичный markdown-заметочник (GPL-3.0)\n"
|
||||
+ "Версия " + Qt.application.version
|
||||
}
|
||||
}
|
||||
|
||||
// Переключатель хранилищ (кнопка внизу слева)
|
||||
Popup {
|
||||
id: vaultSwitcher
|
||||
x: 12
|
||||
y: window.height - height - 12
|
||||
width: 320
|
||||
width: 340
|
||||
modal: false
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
background: Rectangle { color: Theme.surface; border.color: Theme.borderStrong; radius: 8 }
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 6
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
text: qsTr("Хранилища")
|
||||
font.bold: true
|
||||
color: Theme.text
|
||||
}
|
||||
|
||||
ListView {
|
||||
@@ -151,10 +159,12 @@ ApplicationWindow {
|
||||
Layout.fillWidth: true
|
||||
model: vaultManager.vaults
|
||||
clip: true
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
delegate: ItemDelegate {
|
||||
width: ListView.view.width
|
||||
text: modelData
|
||||
highlighted: modelData === window.activeVault
|
||||
palette.highlight: Theme.accent
|
||||
onClicked: {
|
||||
if (modelData === window.activeVault) {
|
||||
vaultSwitcher.close()
|
||||
@@ -169,15 +179,16 @@ ApplicationWindow {
|
||||
Label {
|
||||
visible: vaultList.count === 0
|
||||
text: qsTr("Хранилищ пока нет")
|
||||
color: "#888"
|
||||
color: Theme.textMuted
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Текущее: %1").arg(window.activeVault.length > 0
|
||||
? window.activeVault : "—")
|
||||
color: "#666"
|
||||
color: Theme.textSecondary
|
||||
elide: Text.ElideMiddle
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
Button {
|
||||
@@ -189,14 +200,35 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
SplitView {
|
||||
id: mainSplit
|
||||
anchors.fill: parent
|
||||
orientation: Qt.Horizontal
|
||||
// тонкая линия-разделитель под тему
|
||||
handle: Rectangle {
|
||||
implicitWidth: 1
|
||||
implicitHeight: 1
|
||||
color: Theme.divider
|
||||
Rectangle { // увеличенная зона захвата
|
||||
anchors.centerIn: parent
|
||||
width: 8; height: parent.height
|
||||
color: "transparent"
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SplitHCursor
|
||||
onPressed: mouse.accepted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileTreePanel {
|
||||
id: leftPanel
|
||||
SplitView.preferredWidth: window.width * 0.15
|
||||
SplitView.minimumWidth: 120
|
||||
SplitView.preferredWidth: settingsStore.leftPanelWidth
|
||||
SplitView.minimumWidth: 180
|
||||
SplitView.maximumWidth: 500
|
||||
onWidthChanged: {
|
||||
if (width >= 180 && width <= 500)
|
||||
settingsStore.leftPanelWidth = width
|
||||
}
|
||||
|
||||
onOpenVaultRequested: vaultSwitcher.open()
|
||||
onOpenFileRequested: (path) => {
|
||||
@@ -209,15 +241,21 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
EditorPanel {
|
||||
id: centerPanel
|
||||
SplitView.fillWidth: true
|
||||
SplitView.minimumWidth: 300
|
||||
height: window.height - menuBar.height
|
||||
height: window.height - (menuBar ? menuBar.height : 0)
|
||||
}
|
||||
|
||||
RightPanel {
|
||||
SplitView.preferredWidth: window.width * 0.15
|
||||
SplitView.minimumWidth: 140
|
||||
id: rightPanel
|
||||
SplitView.preferredWidth: settingsStore.rightPanelWidth
|
||||
SplitView.minimumWidth: 200
|
||||
SplitView.maximumWidth: 500
|
||||
onWidthChanged: {
|
||||
if (width >= 200 && width <= 500)
|
||||
settingsStore.rightPanelWidth = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user