Files

248 lines
9.2 KiB
QML
Raw Permalink Normal View History

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Flora
Item {
id: pane
property int docIndex: -1
property bool editMode: true
property bool canOpenBeside: false
property bool closable: false
signal openBeside()
signal closePane()
function insertText(t) { textDisplay.insert(textDisplay.cursorPosition, t) }
function refresh() {
const idx = pane.docIndex
if (idx >= 0) {
const c = documentManager.contentAt(idx)
if (textDisplay.text !== c && !textDisplay.activeFocus)
textDisplay.text = c
if (!pane.editMode) {
viewDisplay.text = mdParser.render(c, fileStore.vault, filePathDir(documentManager.pathAt(idx)))
} else {
viewDisplay.text = ""
}
} else {
textDisplay.text = ""
viewDisplay.text = ""
}
}
function filePathDir(p) {
const i = p.lastIndexOf("/")
return i >= 0 ? p.substring(0, i) : p
}
onDocIndexChanged: refresh()
onEditModeChanged: refresh()
Connections {
target: documentManager
function onContentChanged(idx) { if (idx === pane.docIndex && !textDisplay.activeFocus) refresh() }
function onActiveIndexChanged() { if (documentManager.activeIndex === pane.docIndex) refresh() }
}
Component.onCompleted: refresh()
function applyInline(pre, post) {
if (!pane.editMode) return
textDisplay.forceActiveFocus()
const s = textDisplay.selectionStart, e = textDisplay.selectionEnd
if (s !== e) {
const sel = textDisplay.selectedText
textDisplay.remove(s, e)
textDisplay.insert(s, pre + sel + post)
textDisplay.select(s, s + pre.length + sel.length + post.length)
} else {
const pos = textDisplay.cursorPosition
textDisplay.insert(pos, pre + post)
textDisplay.cursorPosition = pos + pre.length
}
documentManager.updateContent(pane.docIndex, textDisplay.text)
}
function applyHeading(level) {
if (!pane.editMode) return
textDisplay.forceActiveFocus()
const txt = textDisplay.text
const pos = textDisplay.cursorPosition
let start = txt.lastIndexOf("\n", pos - 1) + 1
let end = txt.indexOf("\n", pos)
if (end < 0) end = txt.length
let line = txt.substring(start, end)
line = line.replace(/^#{1,6}\s*/, "")
const newLine = "#".repeat(level) + " " + line
textDisplay.remove(start, end)
textDisplay.insert(start, newLine)
textDisplay.cursorPosition = start + newLine.length
documentManager.updateContent(pane.docIndex, textDisplay.text)
}
function applyLinePrefix(prefix) {
if (!pane.editMode) return
textDisplay.forceActiveFocus()
const txt = textDisplay.text
const s = textDisplay.selectionStart, e = textDisplay.selectionEnd
let start = txt.lastIndexOf("\n", s - 1) + 1
let finish = txt.indexOf("\n", e)
if (finish < 0) finish = txt.length
const block = txt.substring(start, finish)
const newBlock = block.split("\n").map(l => prefix + l).join("\n")
textDisplay.remove(start, finish)
textDisplay.insert(start, newBlock)
textDisplay.select(start, start + newBlock.length)
documentManager.updateContent(pane.docIndex, textDisplay.text)
}
function applyTaskToggle() { applyLinePrefix("- [ ] ") }
function applyCallout(kind) { applyLinePrefix("> [!" + kind + "] ") }
function applyFloatWrap() {
if (!pane.editMode) return
textDisplay.forceActiveFocus()
textDisplay.insert(textDisplay.cursorPosition, "![[image.png|float:left|width:40%]]")
documentManager.updateContent(pane.docIndex, textDisplay.text)
}
function applyColor(color, highlightMode) {
if (highlightMode) applyInline('<span style="background-color:' + color + '">', '</span>')
else applyInline('<span style="color:' + color + '">', '</span>')
}
function applySubSup(kind) {
if (kind === "sup") applyInline("<sup>", "</sup>")
else applyInline("<sub>", "</sub>")
}
// --- Текстовый редактор ---
ScrollView {
id: editScroll
anchors.fill: parent
anchors.bottomMargin: 36
visible: pane.editMode
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
TextArea {
id: textDisplay
placeholderText: qsTr("Начните писать…")
placeholderTextColor: Theme.textFaint
color: Theme.editorText
selectionColor: Theme.selectionBg
selectedTextColor: Theme.text
font.family: "Menlo, Consolas, 'DejaVu Sans Mono', monospace"
font.pixelSize: 14
wrapMode: TextArea.NoWrap
selectByMouse: true
background: Rectangle { color: Theme.editorBg }
onTextChanged: if (activeFocus && text !== documentManager.contentAt(pane.docIndex)) editDebounce.restart()
}
}
Timer { id: editDebounce; interval: 250; onTriggered: documentManager.updateContent(pane.docIndex, textDisplay.text) }
ScrollView {
id: viewScroll
anchors.fill: parent
anchors.bottomMargin: 36
visible: !pane.editMode
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
TextArea {
id: viewDisplay
readOnly: true
textFormat: Text.RichText
color: Theme.text
selectionColor: Theme.selectionBg
font.family: "Menlo, Consolas, 'DejaVu Sans Mono', monospace"
font.pixelSize: 14
wrapMode: TextArea.Wrap
background: Rectangle { color: Theme.editorBg }
onLinkActivated: (href) => {
if (href.startsWith("flora://copycode/")) {
if (mdParser.copyCodeBlock(href.substring(17))) { copyFeedback.visible = true; copyTimer.restart() }
return
}
if (href.startsWith("flora://task/")) {
const line = Number(href.substring(14))
if (line > 0) {
const lines = textDisplay.text.split("\n")
const ln = lines[line - 1]
if (ln !== undefined) {
if (/\[ \]/.test(ln)) lines[line - 1] = ln.replace("[ ]", "[x]")
else if (/\[x\]/.test(ln)) lines[line - 1] = ln.replace("[x]", "[ ]")
textDisplay.text = lines.join("\n")
documentManager.updateContent(pane.docIndex, textDisplay.text)
}
}
} else if (href.startsWith("flora://open/")) {
documentManager.openDocument(decodeURIComponent(href.substring(13)))
} else {
Qt.openUrlExternally(href)
}
}
}
// Тост копирования
Rectangle {
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 8
visible: copyFeedback.visible
color: "#333"
radius: 6
width: copyFeedback.implicitWidth + 16
height: copyFeedback.implicitHeight + 10
z: 11
Label { id: copyFeedback; anchors.centerIn: parent; visible: false; text: qsTr("Скопировано!"); color: "white"; font.pixelSize: 11 }
}
Timer { id: copyTimer; interval: 1500; onTriggered: copyFeedback.visible = false }
}
// --- Нижняя панель ---
Rectangle {
id: toolbar
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 36
z: 10
color: Theme.toolbarBg
border.color: Theme.divider
RowLayout {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
spacing: 4
Label {
visible: pane.docIndex >= 0
text: pane.docIndex >= 0 ? documentManager.titleAt(pane.docIndex) : ""
color: Theme.textMuted
font.pixelSize: 11
elide: Text.ElideMiddle
Layout.fillWidth: true
}
ToolButton {
text: "✎"
checkable: true
checked: pane.editMode
ToolTip.text: qsTr("Редактирование")
ToolTip.visible: hovered
onClicked: pane.editMode = true
}
ToolButton {
text: "👁"
checkable: true
checked: !pane.editMode
ToolTip.text: qsTr("Просмотр")
ToolTip.visible: hovered
onClicked: pane.editMode = false
}
ToolButton {
text: "\u2715"
visible: pane.closable
ToolTip.text: qsTr("Закрыть")
ToolTip.visible: hovered
onClicked: pane.closePane()
}
}
}
}