9a49829611
- CodeBlock.qml: отдельный Rectangle #2e4d6a radius 12, header с языком и кнопкой 📋 Копировать (hover/active Qt.rgba, Timer 1.5с), код в rgba(0,0,0,0.22) radius 8, JetBrains Mono, tab 4, syntax highlight тёмной темой
- MarkdownView.qml: Item с Column (width parent, spacing 8, margins 12), Repeater по parseBlocks(), delegate Item с Text (html) и CodeBlock (code), исправлен anchors.fill → width/anchors.top для корректного implicitHeight, убраны дубли visible
- MdParser: parseBlocks() сохраняет codeBlocks/counter перед render(htmlPart) чтобы не терять codeId, QRegularExpression для ```lang, highlightedCode() с forceDark
- EditorPanel: viewArea/livePreview теперь MarkdownView (markdown property), liveDebounce/markdown, убран лишний \}
- qmllint чист, ctest 21/21, offscreen/cocoa RUNNING OK
98 lines
3.2 KiB
QML
98 lines
3.2 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import Flora
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property string markdown: ""
|
|
property string vaultRoot: ""
|
|
property string fileDir: ""
|
|
implicitHeight: column.implicitHeight + 24
|
|
|
|
signal linkActivated(string href)
|
|
|
|
// Для обновления
|
|
function update() {
|
|
// Триггерим пересборку блоков
|
|
blocksModel.clear()
|
|
const blocks = mdParser.parseBlocks(markdown, vaultRoot, fileDir)
|
|
// console.log("MarkdownView update", markdown.length, blocks.length)
|
|
for (let i = 0; i < blocks.length; i++) {
|
|
blocksModel.append(blocks[i])
|
|
}
|
|
// Позиция скролла в начало
|
|
// contentItem.contentY = 0
|
|
}
|
|
|
|
onMarkdownChanged: update()
|
|
onVaultRootChanged: update()
|
|
onFileDirChanged: update()
|
|
|
|
Component.onCompleted: update()
|
|
|
|
ListModel { id: blocksModel }
|
|
|
|
Column {
|
|
id: column
|
|
width: parent.width
|
|
spacing: 8
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.margins: 12
|
|
|
|
Repeater {
|
|
model: blocksModel
|
|
delegate: Item {
|
|
required property int index
|
|
required property string type
|
|
required property string html
|
|
required property string code
|
|
required property string language
|
|
required property string codeId
|
|
required property string highlighted
|
|
width: parent.width
|
|
height: childrenRect.height
|
|
// HTML block
|
|
Text {
|
|
visible: type === "html" && html
|
|
width: parent.width
|
|
text: html || ""
|
|
textFormat: Text.RichText
|
|
wrapMode: Text.Wrap
|
|
color: Theme.text
|
|
font.family: "Menlo, Consolas, 'DejaVu Sans Mono', monospace"
|
|
font.pixelSize: 14
|
|
onLinkActivated: (href) => root.linkActivated(href)
|
|
}
|
|
// Code block - separate component
|
|
CodeBlock {
|
|
visible: type === "code"
|
|
width: parent.width
|
|
code: code || ""
|
|
language: language || "text"
|
|
codeId: codeId || ""
|
|
highlightedText: highlighted || ""
|
|
onCopyRequested: (id) => {
|
|
const txt = mdParser.getCodeBlock(id)
|
|
if (txt.length > 0) {
|
|
mdParser.copyToClipboard(txt)
|
|
root.linkActivated("flora://copycode/" + id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
width: parent.width
|
|
text: markdown.trim().length === 0 ? qsTr("Пустой файл — начните писать в режиме редактирования") : ""
|
|
color: Theme.textFaint
|
|
wrapMode: Text.Wrap
|
|
font.italic: true
|
|
visible: markdown.trim().length === 0 && blocksModel.count === 0
|
|
}
|
|
}
|
|
}
|