From 7ffb20780506987d67303ec31d7a09a0fffa7812 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Sun, 16 Aug 2026 00:11:27 +0200
Subject: [PATCH 1/2] feat: add support for github alert-syntax
---
compendium/compendium/page/docs/docs.css | 35 +++++++++
compendium/docs.py | 71 ++++++++++++++++++-
.../docs/de/compendium/docs/authoring.md | 28 ++++++++
.../docs/en/compendium/docs/authoring.md | 27 +++++++
compendium/tests/test_docs.py | 47 ++++++++++++
5 files changed, 207 insertions(+), 1 deletion(-)
diff --git a/compendium/compendium/page/docs/docs.css b/compendium/compendium/page/docs/docs.css
index e6cdc7a..1283a9d 100644
--- a/compendium/compendium/page/docs/docs.css
+++ b/compendium/compendium/page/docs/docs.css
@@ -326,6 +326,41 @@
margin-bottom: 0;
}
+.docs-reading-pane .docs-alert-title {
+ display: flex;
+ align-items: center;
+ gap: 0.4em;
+ margin: 0 0 0.4em;
+ font-weight: 600;
+ line-height: 1.2;
+ color: var(--docs-alert-color, var(--text-color));
+}
+
+.docs-reading-pane blockquote.docs-alert-note {
+ --docs-alert-color: var(--alert-text-info);
+ border-left-color: var(--docs-alert-color);
+}
+
+.docs-reading-pane blockquote.docs-alert-tip {
+ --docs-alert-color: var(--alert-text-success);
+ border-left-color: var(--docs-alert-color);
+}
+
+.docs-reading-pane blockquote.docs-alert-important {
+ --docs-alert-color: var(--purple-500);
+ border-left-color: var(--docs-alert-color);
+}
+
+.docs-reading-pane blockquote.docs-alert-warning {
+ --docs-alert-color: var(--alert-text-warning);
+ border-left-color: var(--docs-alert-color);
+}
+
+.docs-reading-pane blockquote.docs-alert-caution {
+ --docs-alert-color: var(--alert-text-danger);
+ border-left-color: var(--docs-alert-color);
+}
+
.docs-state {
display: flex;
align-items: center;
diff --git a/compendium/docs.py b/compendium/docs.py
index 5ac69db..a675b79 100644
--- a/compendium/docs.py
+++ b/compendium/docs.py
@@ -23,6 +23,14 @@
DEFAULT_ROLE = "Desk User"
ALLOWED_FRONTMATTER_KEYS = ("title", "order", "roles")
IMAGE_SRC_PATTERN = re.compile(r'(]+src=["\'])([^"\']+)(["\'])', re.IGNORECASE)
+GITHUB_ALERT_MARKER = re.compile(r"^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*", re.IGNORECASE)
+GITHUB_ALERTS = {
+ "NOTE": ("Note", "info"),
+ "TIP": ("Tip", "light-bulb"),
+ "IMPORTANT": ("Important", "megaphone"),
+ "WARNING": ("Warning", "alert"),
+ "CAUTION": ("Caution", "stop"),
+}
@frappe.whitelist()
@@ -796,10 +804,71 @@ def sort_tree_nodes(nodes):
def render_page_content(body, page_path="", locale=None):
html = frappe.utils.md_to_html(body or "")
- content = sanitize_html(str(html), linkify=True)
+ content = apply_github_alerts(str(html))
+ content = sanitize_html(content, linkify=True)
return rewrite_asset_urls(content, page_path, locale)
+def apply_github_alerts(html):
+ """Turn GitHub alert markers into a titled, typed blockquote."""
+ from bs4 import BeautifulSoup, NavigableString
+
+ if not html or "[!" not in html:
+ return html
+
+ soup = BeautifulSoup(html, "html.parser")
+ for blockquote in soup.find_all("blockquote"):
+ paragraph = next((child for child in blockquote.children if child.name), None)
+ if not paragraph or paragraph.name != "p":
+ continue
+
+ text_node = next(
+ (
+ child
+ for child in paragraph.children
+ if isinstance(child, NavigableString) and str(child).strip()
+ ),
+ None,
+ )
+ if text_node is None:
+ continue
+
+ leading = str(text_node).lstrip()
+ match = GITHUB_ALERT_MARKER.match(leading)
+ if not match:
+ continue
+
+ alert_type = match.group(1).upper()
+ remainder = leading[match.end() :].lstrip()
+ blockquote["class"] = [
+ *(blockquote.get("class") or []),
+ "docs-alert",
+ f"docs-alert-{alert_type.lower()}",
+ ]
+ title = build_alert_title(soup, alert_type)
+
+ if remainder:
+ text_node.replace_with(remainder)
+ paragraph.insert_before(title)
+ else:
+ text_node.extract()
+ if paragraph.get_text(strip=True) or paragraph.find(True):
+ paragraph.insert_before(title)
+ else:
+ paragraph.replace_with(title)
+
+ return str(soup)
+
+
+def build_alert_title(soup, alert_type):
+ label, icon = GITHUB_ALERTS[alert_type]
+ title = soup.new_tag("p", attrs={"class": "docs-alert-title"})
+ icon_tag = soup.new_tag("i", attrs={"class": f"octicon octicon-{icon}"})
+ title.append(icon_tag)
+ title.append(label)
+ return title
+
+
def rewrite_asset_urls(html, page_path, locale=None):
def replace(match):
prefix, src, suffix = match.groups()
diff --git a/compendium/docs/de/compendium/docs/authoring.md b/compendium/docs/de/compendium/docs/authoring.md
index 0f17c63..03f7b3d 100644
--- a/compendium/docs/de/compendium/docs/authoring.md
+++ b/compendium/docs/de/compendium/docs/authoring.md
@@ -110,6 +110,34 @@ def hello():
print("Hello")
```
+## Alerts
+
+Die GitHub-Alert-Syntax macht aus einem Blockzitat einen Hinweis. Die Zeile
+`[!TYPE]` wird durch ein Symbol und eine fette Überschrift ersetzt. Fünf Typen
+stehen zur Verfügung:
+
+> [!NOTE]
+> Zusatzinformation, die auch beim Überfliegen nützlich ist.
+
+> [!TIP]
+> Ein Rat, der eine Aufgabe leichter macht.
+
+> [!IMPORTANT]
+> Information, die der Leser braucht, um sein Ziel zu erreichen.
+
+> [!WARNING]
+> Dringende Information. Handeln Sie, um ein Problem zu vermeiden.
+
+> [!CAUTION]
+> Ein Risiko oder eine negative Folge einer Handlung.
+
+Schreiben Sie sie als Blockzitat, dessen erste Zeile der Typmarker ist:
+
+```md
+> [!NOTE]
+> Zusatzinformation, die auch beim Überfliegen nützlich ist.
+```
+
## Beispielseite
```md
diff --git a/compendium/docs/en/compendium/docs/authoring.md b/compendium/docs/en/compendium/docs/authoring.md
index 8a799ea..57d605f 100644
--- a/compendium/docs/en/compendium/docs/authoring.md
+++ b/compendium/docs/en/compendium/docs/authoring.md
@@ -106,6 +106,33 @@ def hello():
print("Hello")
```
+## Alerts
+
+GitHub alert syntax turns a blockquote into a callout. The `[!TYPE]` line
+becomes an icon and a bold heading. Five types are available:
+
+> [!NOTE]
+> Extra information that is useful even when skimming.
+
+> [!TIP]
+> Advice that makes a task easier.
+
+> [!IMPORTANT]
+> Information the reader needs to reach their goal.
+
+> [!WARNING]
+> Urgent information. Act on it to avoid a problem.
+
+> [!CAUTION]
+> A risk or a negative outcome of an action.
+
+Write them as a blockquote whose first line is the type marker:
+
+```md
+> [!NOTE]
+> Extra information that is useful even when skimming.
+```
+
## Example page
```md
diff --git a/compendium/tests/test_docs.py b/compendium/tests/test_docs.py
index 9e6e668..65e5ae4 100644
--- a/compendium/tests/test_docs.py
+++ b/compendium/tests/test_docs.py
@@ -156,6 +156,53 @@ def test_mermaid_fence_keeps_language_class(self):
self.assertIn("language-mermaid", html)
self.assertIn("flowchart LR", html)
+ def test_github_alert_replaces_marker_with_title(self):
+ html = render_page_content("> [!NOTE]\n> Useful information")
+ self.assertIn("docs-alert-note", html)
+ self.assertIn("octicon-info", html)
+ self.assertIn("Note", html)
+ self.assertIn("Useful information", html)
+ self.assertNotIn("[!NOTE]", html)
+
+ def test_github_alert_types(self):
+ cases = {
+ "TIP": ("docs-alert-tip", "octicon-light-bulb", "Tip"),
+ "IMPORTANT": ("docs-alert-important", "octicon-megaphone", "Important"),
+ "WARNING": ("docs-alert-warning", "octicon-alert", "Warning"),
+ "CAUTION": ("docs-alert-caution", "octicon-stop", "Caution"),
+ }
+ for marker, (css_class, icon, heading) in cases.items():
+ html = render_page_content(f"> [!{marker}]\n> Body")
+ self.assertIn(css_class, html)
+ self.assertIn(icon, html)
+ self.assertIn(heading, html)
+ self.assertIn("Body", html)
+ self.assertNotIn(f"[!{marker}]", html)
+
+ def test_github_alert_with_following_list(self):
+ html = render_page_content("> [!NOTE]\n>\n> - first\n> - second")
+ self.assertIn("docs-alert-note", html)
+ self.assertIn("octicon-info", html)
+ self.assertNotIn("[!NOTE]", html)
+ self.assertIn("