Фаза 7: правая панель — VaultIndex (теги/[[вики]]/md-ссылки, граф), MonthCalendar (заметки дня), RightPanel (ссылки+теги, чипы, попап файлов), GraphView (Canvas, drag, клик-открытие); dailyNotePath; тест vaultindex (16/16); self-contained Flora.app
This commit is contained in:
+197
-14
@@ -2,36 +2,219 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
// Правая панель: календарь + ссылки/теги + граф связей.
|
||||
// Фаза 0 — каркас; контент наполняется в Фазах 7 и 8.
|
||||
// Правая панель: календарь (заметки дня), «Ссылки и теги» активного файла,
|
||||
// граф связей (файлы + теги, рёбра по ссылкам).
|
||||
Item {
|
||||
id: root
|
||||
|
||||
function activePath() {
|
||||
return documentManager.pathAt(documentManager.activeIndex)
|
||||
}
|
||||
|
||||
function refreshLinksTags() {
|
||||
const p = root.activePath()
|
||||
linksModel.clear()
|
||||
if (p.length > 0) {
|
||||
const links = vaultIndex.linksOfFile(p)
|
||||
for (let i = 0; i < links.length; ++i) {
|
||||
linksModel.append({ text: links[i].text, path: links[i].path })
|
||||
}
|
||||
}
|
||||
tagsModel.clear()
|
||||
const tags = p.length > 0 ? vaultIndex.tagsOfFile(p) : []
|
||||
for (let i = 0; i < tags.length; ++i)
|
||||
tagsModel.append({ tag: tags[i] })
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (fileStore.ready)
|
||||
vaultIndex.rebuild(fileStore.vault)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: fileStore
|
||||
function onVaultChanged() {
|
||||
if (fileStore.ready)
|
||||
vaultIndex.rebuild(fileStore.vault)
|
||||
}
|
||||
function onReadyChanged() {
|
||||
if (fileStore.ready)
|
||||
vaultIndex.rebuild(fileStore.vault)
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: documentManager
|
||||
function onActiveIndexChanged() { root.refreshLinksTags() }
|
||||
function onOpenTabsChanged() { root.refreshLinksTags() }
|
||||
function onContentChanged(i) {
|
||||
if (i === documentManager.activeIndex) root.refreshLinksTags()
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: vaultIndex
|
||||
function onIndexChanged() { root.refreshLinksTags() }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// ---------- Календарь ----------
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 218
|
||||
color: "#fafafa"
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
Label {
|
||||
text: qsTr("Календарь")
|
||||
font.bold: true
|
||||
color: "#555"
|
||||
padding: 6
|
||||
}
|
||||
MonthCalendar {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
onDaySelected: (ds) => {
|
||||
const path = fileStore.dailyNotePath(ds)
|
||||
if (path.length > 0)
|
||||
documentManager.openDocument(path)
|
||||
}
|
||||
} }
|
||||
}
|
||||
|
||||
// ---------- Ссылки и теги активного файла ----------
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: "#fafafa"
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("Календарь\n(Фаза 7)")
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: "#bbb"
|
||||
color: "#f4f4f4"
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
spacing: 4
|
||||
|
||||
Label {
|
||||
text: qsTr("Ссылки и теги")
|
||||
font.bold: true
|
||||
color: "#555"
|
||||
}
|
||||
Label {
|
||||
text: root.activePath().length > 0
|
||||
? qsTr("Файл: %1").arg(root.baseName(activePath()))
|
||||
: qsTr("Нет активного файла")
|
||||
elide: Text.ElideMiddle
|
||||
color: "#888"
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
Label { text: qsTr("Ссылки"); color: "#666"; font.pixelSize: 11 }
|
||||
ListView {
|
||||
id: linksList
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(120, linksModel.count * 22)
|
||||
model: ListModel { id: linksModel }
|
||||
clip: true
|
||||
delegate: ItemDelegate {
|
||||
width: ListView.view.width
|
||||
height: 22
|
||||
text: model.text
|
||||
font.pixelSize: 12
|
||||
onClicked: documentManager.openDocument(model.path)
|
||||
}
|
||||
}
|
||||
Label {
|
||||
visible: linksModel.count === 0
|
||||
text: qsTr("— нет ссылок —")
|
||||
color: "#bbb"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
Label { text: qsTr("Теги"); color: "#666"; font.pixelSize: 11 }
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
readonly property var m: ListModel { id: tagsModel }
|
||||
Repeater {
|
||||
model: tagsModel
|
||||
delegate: TagChip {
|
||||
text: "#" + model.tag
|
||||
onClicked: {
|
||||
tagFilesModel.clear()
|
||||
const files = vaultIndex.filesWithTag(model.tag)
|
||||
for (let i = 0; i < files.length; ++i)
|
||||
tagFilesModel.append({ path: files[i], name: baseName(files[i]) })
|
||||
tagFilesTitle.text = qsTr("Файлы с тегом #%1 (%2)").arg(model.tag).arg(files.length)
|
||||
tagFilesPopup.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Граф связей ----------
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: "#f5f5f5"
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("Граф связей\n(Фаза 7)")
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: "#bbb"
|
||||
color: "#eef1f5"
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: qsTr("Граф связей")
|
||||
font.bold: true
|
||||
color: "#555"
|
||||
padding: 6
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
Button {
|
||||
text: qsTr("Обновить")
|
||||
font.pixelSize: 11
|
||||
implicitHeight: 22
|
||||
onClicked: vaultIndex.rebuild(fileStore.vault)
|
||||
}
|
||||
}
|
||||
GraphView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
nodes: vaultIndex.nodes
|
||||
edges: vaultIndex.edges
|
||||
onFileClicked: (path) => documentManager.openDocument(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function baseName(p) {
|
||||
const idx = p.lastIndexOf("/")
|
||||
return idx >= 0 ? p.substring(idx + 1) : p
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: tagFilesPopup
|
||||
modal: true
|
||||
anchors.centerIn: parent
|
||||
width: 300
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
Label { id: tagFilesTitle; font.bold: true }
|
||||
ListView {
|
||||
Layout.preferredHeight: Math.min(220, contentHeight)
|
||||
Layout.fillWidth: true
|
||||
model: ListModel { id: tagFilesModel }
|
||||
clip: true
|
||||
delegate: ItemDelegate {
|
||||
width: ListView.view.width
|
||||
text: model.name
|
||||
onClicked: {
|
||||
documentManager.openDocument(model.path)
|
||||
tagFilesPopup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user