aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-29 16:16:36 +0200
committerDanilo M. <danix@danix.xyz>2026-09-29 16:16:36 +0200
commit8215c7bcde3e1fcb3635059c408b4b97e42f2141 (patch)
treee994d2690e0d6ee06b28eb0976ec570d380341b1
parent0da34c765da9b9c831befdab659c9eb0c79c9079 (diff)
downloadquickshell-8215c7bcde3e1fcb3635059c408b4b97e42f2141.tar.gz
quickshell-8215c7bcde3e1fcb3635059c408b4b97e42f2141.zip
feat(notifications): render bodies as Markdown
Balloon and drawer bodies switch from RichText to Qt's MarkdownText (CommonMark plus GitHub extensions). Raw HTML still goes through Qt's HTML importer, so body-markup senders keep working. Markdown images fetch their destination the same way an <img> does, which the existing sanitize filter would miss since it only inspects tags. sanitize now escapes every "![" so the syntax renders as literal text, local or remote; parsing CommonMark destinations and reference definitions to allow local ones is not worth the grammar. Verified in the shell log: before the filter reached the running shell, a test body fetched https://example.org/a.png; after, a second one did not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--desktop/NotificationRow.qml2
-rw-r--r--notifications/NotificationBalloon.qml2
-rw-r--r--shared/Notify.qml9
3 files changed, 10 insertions, 3 deletions
diff --git a/desktop/NotificationRow.qml b/desktop/NotificationRow.qml
index 923cc9e..35c468b 100644
--- a/desktop/NotificationRow.qml
+++ b/desktop/NotificationRow.qml
@@ -93,7 +93,7 @@ Rectangle {
width: parent.width
visible: text !== ""
text: Notify.sanitize(row.notification.body)
- textFormat: Text.RichText
+ textFormat: Text.MarkdownText
wrapMode: Text.WordWrap
maximumLineCount: 2
elide: Text.ElideRight
diff --git a/notifications/NotificationBalloon.qml b/notifications/NotificationBalloon.qml
index 49ec0b4..b01a848 100644
--- a/notifications/NotificationBalloon.qml
+++ b/notifications/NotificationBalloon.qml
@@ -124,7 +124,7 @@ Rectangle {
width: parent.width
visible: text !== ""
text: Notify.sanitize(b.notification.body)
- textFormat: Text.RichText
+ textFormat: Text.MarkdownText
wrapMode: Text.WordWrap
maximumLineCount: 3
elide: Text.ElideRight
diff --git a/shared/Notify.qml b/shared/Notify.qml
index 1abe57d..cb1e2fe 100644
--- a/shared/Notify.qml
+++ b/shared/Notify.qml
@@ -75,6 +75,13 @@ Singleton {
// not just the first, so a decoy attribute cannot shadow a remote one;
// entities are decoded before the test, so an encoded scheme cannot slip
// past.
+ //
+ // The body renders as Markdown, so the same rule covers its image syntax:
+ // `![alt](url)` and `![alt][ref]` fetch just as an <img> does, and parsing
+ // their destinations would mean reimplementing CommonMark's link grammar.
+ // Escaping the bracket turns every one into literal text instead, local or
+ // not. Raw HTML inside Markdown goes through Qt's HTML importer, so the
+ // tag filter below still applies to it.
function sanitize(body) {
function decode(s) {
return s.replace(/&(?:#x([0-9a-f]+)|#(\d+)|(amp|colon|sol|tab|quot));/gi,
@@ -84,7 +91,7 @@ Singleton {
return { amp: "&", colon: ":", sol: "/", tab: "\t", quot: "\"" }[name.toLowerCase()];
});
}
- return (body || "").replace(/<img\b[^>]*>/gi, function (tag) {
+ return (body || "").replace(/!\[/g, "!\\[").replace(/<img\b[^>]*>/gi, function (tag) {
const re = /(?:^|\s)src\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
let m, found = false, local = true;
while ((m = re.exec(tag))) {