Render org src blocks as styled code blocks
- Parse body text into segments (text vs code) - Src blocks get: monospace font, alternate background, rounded corners - Language label shown above code (python, bash, etc.) - Regular text keeps left rule indicator Handles #+BEGIN_SRC lang ... #+END_SRC blocks.
This commit is contained in:
parent
9e6dc589bd
commit
17906c06f9
|
|
@ -57,6 +57,57 @@ ListView {
|
||||||
readonly property int sectionLevel: model.level ?? 1
|
readonly property int sectionLevel: model.level ?? 1
|
||||||
readonly property int indent: (sectionLevel - 1) * outline.indentWidth
|
readonly property int indent: (sectionLevel - 1) * outline.indentWidth
|
||||||
|
|
||||||
|
// Parse body into text and code block segments
|
||||||
|
readonly property var bodyParts: {
|
||||||
|
let parts = [];
|
||||||
|
let body = sectionBody;
|
||||||
|
if (!body) return parts;
|
||||||
|
|
||||||
|
let lines = body.split('\n');
|
||||||
|
let currentText = [];
|
||||||
|
let inCode = false;
|
||||||
|
let codeLang = "";
|
||||||
|
let codeLines = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
let line = lines[i];
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
if (!inCode && trimmed.toUpperCase().startsWith("#+BEGIN_SRC")) {
|
||||||
|
// Flush pending text
|
||||||
|
if (currentText.length > 0) {
|
||||||
|
let text = currentText.join('\n').trim();
|
||||||
|
if (text) parts.push({type: "text", content: text});
|
||||||
|
currentText = [];
|
||||||
|
}
|
||||||
|
// Start code block
|
||||||
|
inCode = true;
|
||||||
|
codeLang = trimmed.substring(11).trim().split(/\s+/)[0] || "";
|
||||||
|
codeLines = [];
|
||||||
|
} else if (inCode && trimmed.toUpperCase() === "#+END_SRC") {
|
||||||
|
// End code block
|
||||||
|
parts.push({type: "code", language: codeLang, content: codeLines.join('\n')});
|
||||||
|
inCode = false;
|
||||||
|
codeLang = "";
|
||||||
|
codeLines = [];
|
||||||
|
} else if (inCode) {
|
||||||
|
codeLines.push(line);
|
||||||
|
} else {
|
||||||
|
currentText.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush remaining
|
||||||
|
if (inCode && codeLines.length > 0) {
|
||||||
|
parts.push({type: "code", language: codeLang, content: codeLines.join('\n')});
|
||||||
|
} else if (currentText.length > 0) {
|
||||||
|
let text = currentText.join('\n').trim();
|
||||||
|
if (text) parts.push({type: "text", content: text});
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: outline.currentIndex = del.index
|
onClicked: outline.currentIndex = del.index
|
||||||
|
|
@ -230,22 +281,33 @@ ListView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body text with left rule
|
// Body content (text and code blocks)
|
||||||
RowLayout {
|
ColumnLayout {
|
||||||
visible: del.sectionBody !== ""
|
visible: del.sectionBody !== ""
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 8
|
spacing: 4
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: del.bodyParts
|
||||||
|
|
||||||
|
delegate: Loader {
|
||||||
|
required property var modelData
|
||||||
|
Layout.fillWidth: true
|
||||||
|
sourceComponent: modelData.type === "code" ? codeBlockComponent : textBlockComponent
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: textBlockComponent
|
||||||
|
RowLayout {
|
||||||
|
spacing: 8
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 2
|
width: 2
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.leftMargin: 2
|
Layout.leftMargin: 2
|
||||||
color: theme.midColor
|
color: theme.midColor
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: del.sectionBody
|
text: modelData.content
|
||||||
color: theme.placeholderTextColor
|
color: theme.placeholderTextColor
|
||||||
font.pointSize: outline.baseFontSize + 1
|
font.pointSize: outline.baseFontSize + 1
|
||||||
lineHeight: 1.3
|
lineHeight: 1.3
|
||||||
|
|
@ -254,6 +316,46 @@ ListView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: codeBlockComponent
|
||||||
|
Rectangle {
|
||||||
|
color: theme.alternateBaseColor
|
||||||
|
radius: 4
|
||||||
|
implicitHeight: codeColumn.implicitHeight + 12
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: codeColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
// Language label
|
||||||
|
Label {
|
||||||
|
visible: (modelData.language ?? "") !== ""
|
||||||
|
text: modelData.language
|
||||||
|
color: theme.linkColor
|
||||||
|
font.pointSize: outline.baseFontSize - 2
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code content
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: modelData.content
|
||||||
|
color: theme.textColor
|
||||||
|
font.family: "monospace"
|
||||||
|
font.pointSize: outline.baseFontSize
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty state
|
// Empty state
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue