import QtQuick import QtQuick.Controls import QtQuick.Layouts // Правая панель: календарь (заметки дня), «Ссылки и теги» активного файла, // граф связей (файлы + теги, рёбра по ссылкам). 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: "#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: "#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() } } } } } }