diff --git a/ui/qml/OutlineView.qml b/ui/qml/OutlineView.qml
index 6eb7b61..fd0ea88 100644
--- a/ui/qml/OutlineView.qml
+++ b/ui/qml/OutlineView.qml
@@ -41,6 +41,45 @@ ListView {
return Math.max(outline.baseFontSize - 1, outline.baseFontSize + 5 - level)
}
+ // Escape HTML special chars for safe StyledText rendering.
+ function escHtml(s) {
+ return String(s)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ }
+
+ // Render inline org markup (currently links) to StyledText. Org links are
+ // [[target]] or [[target][description]]; everything else is escaped. Line
+ // breaks are converted to
so StyledText preserves them.
+ function renderInline(text) {
+ let re = /\[\[([^\]\[]+?)\](?:\[([^\]]*?)\])?\]/g
+ let out = ""
+ let last = 0
+ let m
+ function escBlock(s) { return escHtml(s).replace(/\n/g, "
") }
+ while ((m = re.exec(text)) !== null) {
+ out += escBlock(text.slice(last, m.index))
+ let target = m[1]
+ let desc = (m[2] !== undefined && m[2] !== "") ? m[2] : m[1]
+ let href = escHtml(target).replace(/"/g, """)
+ out += '' + escHtml(desc) + ''
+ last = re.lastIndex
+ }
+ out += escBlock(text.slice(last))
+ return out
+ }
+
+ // Open a link target activated from body text. External URL schemes open
+ // in the system handler; internal org links (id:, #custom, *headline) are
+ // left for a future navigation pass.
+ function openLink(target) {
+ if (/^([a-zA-Z][a-zA-Z0-9+.-]*):\/\//.test(target)
+ || /^(mailto:|file:)/.test(target)) {
+ Qt.openUrlExternally(target)
+ }
+ }
+
delegate: Rectangle {
id: del
width: outline.width
@@ -427,12 +466,19 @@ ListView {
}
Label {
Layout.fillWidth: true
- text: modelData.content
+ text: outline.renderInline(modelData.content)
color: theme.placeholderTextColor
+ linkColor: theme.linkColor
font.pointSize: outline.baseFontSize + 1
lineHeight: 1.3
wrapMode: Text.WordWrap
- textFormat: Text.PlainText
+ textFormat: Text.StyledText
+ onLinkActivated: (link) => outline.openLink(link)
+
+ HoverHandler {
+ enabled: parent.hoveredLink !== ""
+ cursorShape: Qt.PointingHandCursor
+ }
}
}
}
@@ -497,13 +543,20 @@ ListView {
// Item text; checked items are dimmed and struck through.
Label {
Layout.fillWidth: true
- text: modelData.text ?? ""
+ text: outline.renderInline(modelData.text ?? "")
color: (modelData.checkbox === "checked")
? theme.placeholderTextColor : theme.textColor
+ linkColor: theme.linkColor
font.pointSize: outline.baseFontSize + 1
font.strikeout: modelData.checkbox === "checked"
wrapMode: Text.WordWrap
- textFormat: Text.PlainText
+ textFormat: Text.StyledText
+ onLinkActivated: (link) => outline.openLink(link)
+
+ HoverHandler {
+ enabled: parent.hoveredLink !== ""
+ cursorShape: Qt.PointingHandCursor
+ }
}
}
}