feat: full Markdown support in service card comments (mistune)

This commit is contained in:
2026-04-28 11:57:44 +00:00
parent b951f6c68e
commit fa88f7f4e4
3 changed files with 39 additions and 24 deletions
+7 -24
View File
@@ -16,6 +16,7 @@ from typing import Optional
import docker import docker
import requests import requests
import mistune
from fastapi import Depends, FastAPI, File, Form, HTTPException, Query, Request, UploadFile, status from fastapi import Depends, FastAPI, File, Form, HTTPException, Query, Request, UploadFile, status
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
@@ -299,34 +300,16 @@ def normalize_web_target(url: str) -> str:
return f"http://{raw}" return f"http://{raw}"
_md = mistune.create_markdown(
escape=True,
plugins=["strikethrough", "table", "task_lists"],
)
def format_service_comment(raw_text: str) -> Markup: def format_service_comment(raw_text: str) -> Markup:
raw = (raw_text or "").replace("\r\n", "\n").replace("\r", "\n").strip() raw = (raw_text or "").replace("\r\n", "\n").replace("\r", "\n").strip()
if not raw: if not raw:
return Markup("") return Markup("")
escaped = str(escape(raw)) return Markup(_md(raw))
# Support pasted/plain markdown-like bold fragments.
escaped = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", escaped, flags=re.DOTALL)
# Allow a small safe subset of pasted HTML tags.
replacements = {
"&lt;b&gt;": "<b>",
"&lt;/b&gt;": "</b>",
"&lt;strong&gt;": "<strong>",
"&lt;/strong&gt;": "</strong>",
"&lt;i&gt;": "<i>",
"&lt;/i&gt;": "</i>",
"&lt;em&gt;": "<em>",
"&lt;/em&gt;": "</em>",
"&lt;u&gt;": "<u>",
"&lt;/u&gt;": "</u>",
"&lt;br&gt;": "<br>",
"&lt;br/&gt;": "<br>",
"&lt;br /&gt;": "<br>",
}
for src, dst in replacements.items():
escaped = escaped.replace(src, dst)
escaped = escaped.replace("\n", "<br>")
return Markup(escaped)
def parse_rdp_target(target: str) -> dict: def parse_rdp_target(target: str) -> dict:
raw = (target or "").strip() raw = (target or "").strip()
+1
View File
@@ -7,3 +7,4 @@ jinja2==3.1.6
passlib[argon2]==1.7.4 passlib[argon2]==1.7.4
docker==7.1.0 docker==7.1.0
itsdangerous==2.2.0 itsdangerous==2.2.0
mistune==3.1.3
+31
View File
@@ -781,3 +781,34 @@ button {
color: rgba(240, 248, 255, 0.95); color: rgba(240, 248, 255, 0.95);
text-shadow: 0 2px 10px rgba(9, 44, 72, 0.45); text-shadow: 0 2px 10px rgba(9, 44, 72, 0.45);
} }
/* Markdown inside service card comments */
.tile-comment p { margin: 0 0 0.4em; }
.tile-comment p:last-child { margin-bottom: 0; }
.tile-comment ul, .tile-comment ol { margin: 0.3em 0 0.4em 1.2em; padding: 0; }
.tile-comment li { margin-bottom: 0.15em; }
.tile-comment h1,.tile-comment h2,.tile-comment h3,
.tile-comment h4,.tile-comment h5,.tile-comment h6 {
font-size: 0.85em; font-weight: 700; margin: 0.4em 0 0.2em;
}
.tile-comment code {
font-family: monospace; font-size: 0.88em;
background: rgba(0,0,0,.06); border-radius: 3px; padding: 0.1em 0.3em;
}
.tile-comment pre {
background: rgba(0,0,0,.06); border-radius: 4px;
padding: 0.4em 0.6em; overflow-x: auto; font-size: 0.82em;
}
.tile-comment pre code { background: none; padding: 0; }
.tile-comment blockquote {
border-left: 3px solid #c7d9e8; margin: 0.3em 0 0.3em 0;
padding-left: 0.6em; color: #5a7a90;
}
.tile-comment a { color: #1668a6; text-decoration: underline; }
.tile-comment table { border-collapse: collapse; font-size: 0.82em; margin: 0.3em 0; }
.tile-comment th, .tile-comment td {
border: 1px solid #c7d9e8; padding: 0.2em 0.5em;
}
.tile-comment th { background: #eaf2fb; font-weight: 700; }
.tile-comment del { text-decoration: line-through; color: #7a9aaf; }
.tile-comment input[type=checkbox] { margin-right: 0.3em; }