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 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 {
|
||||
anchors.fill: parent
|
||||
onClicked: outline.currentIndex = del.index
|
||||
|
|
@ -230,22 +281,33 @@ ListView {
|
|||
}
|
||||
}
|
||||
|
||||
// Body text with left rule
|
||||
RowLayout {
|
||||
// Body content (text and code blocks)
|
||||
ColumnLayout {
|
||||
visible: del.sectionBody !== ""
|
||||
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 {
|
||||
width: 2
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 2
|
||||
color: theme.midColor
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: del.sectionBody
|
||||
text: modelData.content
|
||||
color: theme.placeholderTextColor
|
||||
font.pointSize: outline.baseFontSize + 1
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue