2026-09-01 21:07:55 +03:00
|
|
|
|
import QtQuick
|
|
|
|
|
|
import QtQuick.Controls
|
2026-09-02 00:38:01 +03:00
|
|
|
|
import QtQuick.Layouts
|
2026-09-01 21:07:55 +03:00
|
|
|
|
import Flora
|
|
|
|
|
|
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
id: root
|
|
|
|
|
|
|
|
|
|
|
|
property string code: ""
|
|
|
|
|
|
property string language: "text"
|
|
|
|
|
|
property string codeId: ""
|
|
|
|
|
|
|
|
|
|
|
|
signal copyRequested(string codeId)
|
|
|
|
|
|
|
|
|
|
|
|
// Telegram-like bubble
|
|
|
|
|
|
color: "#2e4d6a"
|
|
|
|
|
|
radius: 12
|
|
|
|
|
|
// adaptive width like Telegram message
|
|
|
|
|
|
width: parent ? Math.min(parent.width * 0.88, implicitWidth) : implicitWidth
|
|
|
|
|
|
implicitWidth: 400
|
|
|
|
|
|
implicitHeight: column.implicitHeight + 20
|
|
|
|
|
|
|
|
|
|
|
|
// For syntax highlighting, we use a TextArea with RichText
|
|
|
|
|
|
// The highlighted code is set from outside via highlightedText
|
|
|
|
|
|
property string highlightedText: ""
|
|
|
|
|
|
|
|
|
|
|
|
Column {
|
|
|
|
|
|
id: column
|
2026-09-01 21:13:50 +03:00
|
|
|
|
width: parent.width - 20
|
|
|
|
|
|
anchors.top: parent.top
|
|
|
|
|
|
anchors.left: parent.left
|
|
|
|
|
|
anchors.right: parent.right
|
2026-09-01 21:07:55 +03:00
|
|
|
|
anchors.margins: 10
|
|
|
|
|
|
spacing: 6
|
|
|
|
|
|
|
2026-09-02 00:38:01 +03:00
|
|
|
|
// Header with language and copy button — строго справа
|
|
|
|
|
|
RowLayout {
|
2026-09-01 21:07:55 +03:00
|
|
|
|
width: parent.width
|
|
|
|
|
|
height: 24
|
|
|
|
|
|
spacing: 8
|
|
|
|
|
|
|
|
|
|
|
|
Label {
|
|
|
|
|
|
text: root.language.toUpperCase()
|
|
|
|
|
|
color: "#8a9ba8"
|
|
|
|
|
|
font.pixelSize: 10
|
|
|
|
|
|
font.bold: true
|
|
|
|
|
|
font.letterSpacing: 0.4
|
2026-09-02 00:38:01 +03:00
|
|
|
|
Layout.alignment: Qt.AlignVCenter
|
2026-09-01 21:07:55 +03:00
|
|
|
|
visible: root.language !== "text" && root.language !== ""
|
|
|
|
|
|
}
|
2026-09-02 00:38:01 +03:00
|
|
|
|
Item {
|
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
height: 1
|
|
|
|
|
|
}
|
2026-09-01 21:07:55 +03:00
|
|
|
|
Label {
|
|
|
|
|
|
id: languageLabel
|
|
|
|
|
|
text: root.language
|
|
|
|
|
|
visible: false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Copy button - Telegram style, hover/active feedback
|
|
|
|
|
|
Rectangle {
|
|
|
|
|
|
id: copyButton
|
2026-09-02 00:38:01 +03:00
|
|
|
|
Layout.preferredWidth: copyLabel.implicitWidth + 16
|
|
|
|
|
|
Layout.preferredHeight: 22
|
|
|
|
|
|
implicitWidth: copyLabel.implicitWidth + 16
|
|
|
|
|
|
implicitHeight: 22
|
2026-09-01 21:07:55 +03:00
|
|
|
|
radius: 11
|
|
|
|
|
|
color: copyHover.hovered ? (copyTap.pressed ? Qt.rgba(1,1,1,0.30) : Qt.rgba(1,1,1,0.22)) : Qt.rgba(1,1,1,0.12)
|
|
|
|
|
|
border.color: copyHover.hovered ? Qt.rgba(1,1,1,0.28) : Qt.rgba(1,1,1,0.18)
|
|
|
|
|
|
border.width: 1
|
|
|
|
|
|
|
|
|
|
|
|
property bool copied: false
|
|
|
|
|
|
Timer { id: copyResetTimer; interval: 1500; onTriggered: copyButton.copied = false }
|
|
|
|
|
|
|
|
|
|
|
|
Label {
|
|
|
|
|
|
id: copyLabel
|
|
|
|
|
|
anchors.centerIn: parent
|
2026-09-01 21:56:29 +03:00
|
|
|
|
text: copyButton.copied ? "✓" : "📋"
|
2026-09-01 21:07:55 +03:00
|
|
|
|
color: copyButton.copied ? "#6ab2f6" : "#ffffff"
|
2026-09-01 21:56:29 +03:00
|
|
|
|
font.pixelSize: 13
|
2026-09-01 21:07:55 +03:00
|
|
|
|
font.family: "-apple-system, sans-serif"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HoverHandler { id: copyHover }
|
|
|
|
|
|
TapHandler {
|
|
|
|
|
|
id: copyTap
|
|
|
|
|
|
onTapped: {
|
|
|
|
|
|
root.copyRequested(root.codeId)
|
|
|
|
|
|
copyButton.copied = true
|
|
|
|
|
|
copyResetTimer.restart()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// For mouse cursor
|
|
|
|
|
|
HoverHandler {
|
|
|
|
|
|
cursorShape: Qt.PointingHandCursor
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-01 21:56:29 +03:00
|
|
|
|
// Code area - как в редакторе, но без скролла — весь текст виден целиком
|
2026-09-01 21:07:55 +03:00
|
|
|
|
Rectangle {
|
|
|
|
|
|
width: parent.width
|
|
|
|
|
|
color: Qt.rgba(0,0,0,0.22)
|
|
|
|
|
|
radius: 8
|
2026-09-02 00:01:03 +03:00
|
|
|
|
implicitHeight: codeText.implicitHeight + 20
|
2026-09-01 21:07:55 +03:00
|
|
|
|
|
2026-09-01 21:56:29 +03:00
|
|
|
|
TextEdit {
|
|
|
|
|
|
id: codeText
|
2026-09-01 21:07:55 +03:00
|
|
|
|
anchors.fill: parent
|
2026-09-01 21:56:29 +03:00
|
|
|
|
anchors.margins: 10
|
2026-09-02 00:18:02 +03:00
|
|
|
|
// Сохраняем форматирование как ввёл пользователь: оборачиваем в <pre> с pre-wrap
|
|
|
|
|
|
text: {
|
|
|
|
|
|
let src = root.highlightedText
|
|
|
|
|
|
if (!src || src.length === 0) {
|
|
|
|
|
|
src = root.code.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\"/g, """)
|
|
|
|
|
|
}
|
|
|
|
|
|
return "<pre style=\"margin:0;white-space:pre-wrap;word-wrap:break-word;tab-size:4;\">" + src + "</pre>"
|
|
|
|
|
|
}
|
2026-09-01 21:56:29 +03:00
|
|
|
|
textFormat: TextEdit.RichText
|
|
|
|
|
|
wrapMode: TextEdit.Wrap
|
|
|
|
|
|
readOnly: true
|
|
|
|
|
|
selectByMouse: true
|
|
|
|
|
|
color: "#ffffff"
|
|
|
|
|
|
selectionColor: "#3a6ea5"
|
|
|
|
|
|
selectedTextColor: "#ffffff"
|
|
|
|
|
|
font.family: "JetBrains Mono, Fira Code, Menlo, Consolas, monospace"
|
|
|
|
|
|
font.pixelSize: 13
|
2026-09-01 21:07:55 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For bracket matching highlight in editor, we could add a similar indicator
|
|
|
|
|
|
// But for preview, this is just display
|
|
|
|
|
|
}
|