Skip to content

Commit 3d89f6e

Browse files
authored
Merge pull request #42 from dimacurrentai/markdown
Markdown
2 parents 04fb5d3 + 93f8794 commit 3d89f6e

File tree

7 files changed

+321
-21
lines changed

7 files changed

+321
-21
lines changed

lib/http.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use ammonia::clean;
12
use askama::Template;
23
use axum::response::{Html, IntoResponse, Response};
4+
use comrak::{markdown_to_html, ComrakOptions};
35
use hyper::{
46
header::{self, HeaderMap},
57
StatusCode,
@@ -11,6 +13,23 @@ pub struct DataHtmlTemplate<'a> {
1113
pub raw_json_as_string: &'a str,
1214
}
1315

16+
#[derive(Template)]
17+
#[template(path = "markdown.html", escape = "none")]
18+
pub struct MarkdownHtmlTemplate<'a> {
19+
pub rendered_markdown: &'a str,
20+
}
21+
22+
pub fn render_markdown(input: &str) -> String {
23+
let mut options = ComrakOptions::default();
24+
options.extension.table = true;
25+
options.extension.strikethrough = true;
26+
options.extension.tasklist = true;
27+
options.extension.footnotes = true;
28+
29+
let html_output = markdown_to_html(input, &options);
30+
clean(&html_output)
31+
}
32+
1433
pub async fn json_or_html(headers: HeaderMap, raw_json_as_string: &str) -> impl IntoResponse {
1534
if accept_header_contains_text_html(&headers) {
1635
let template = DataHtmlTemplate { raw_json_as_string };
@@ -28,6 +47,24 @@ pub async fn json_or_html(headers: HeaderMap, raw_json_as_string: &str) -> impl
2847
}
2948
}
3049

50+
pub async fn markdown_or_html(headers: HeaderMap, raw_markdown: &str) -> impl IntoResponse {
51+
if accept_header_contains_text_html(&headers) {
52+
let rendered = render_markdown(raw_markdown);
53+
let template = MarkdownHtmlTemplate { rendered_markdown: &rendered };
54+
match template.render() {
55+
Ok(html) => Html(html).into_response(),
56+
Err(_) => axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response(),
57+
}
58+
} else {
59+
Response::builder()
60+
.status(StatusCode::OK)
61+
.header("content-type", "text/markdown")
62+
.body(raw_markdown.to_string())
63+
.unwrap()
64+
.into_response()
65+
}
66+
}
67+
3168
pub fn accept_header_contains_text_html(headers: &HeaderMap) -> bool {
3269
headers
3370
.get_all(header::ACCEPT)

lib/templates/markdown.html

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Markdown</title>
8+
<link href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4v5QBAARLAaU/Dnq2AAAAAElFTkSuQmCC" rel="icon" type="image/x-icon" />
9+
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
10+
11+
<style>
12+
body {
13+
font-family: Arial, sans-serif;
14+
background-color: #282c34;
15+
color: white;
16+
padding: 20px;
17+
max-width: 800px;
18+
margin: 0 auto;
19+
line-height: 1.6;
20+
}
21+
22+
h1, h2, h3, h4, h5, h6 {
23+
color: #61dafb;
24+
margin-top: 24px;
25+
margin-bottom: 16px;
26+
}
27+
28+
h1 {
29+
font-size: 2em;
30+
border-bottom: 1px solid #444;
31+
padding-bottom: 0.3em;
32+
}
33+
34+
h2 {
35+
font-size: 1.5em;
36+
border-bottom: 1px solid #444;
37+
padding-bottom: 0.3em;
38+
}
39+
40+
a {
41+
color: #9cdcfe;
42+
text-decoration: none;
43+
}
44+
45+
a:hover {
46+
text-decoration: underline;
47+
}
48+
49+
code {
50+
font-family: 'Courier New', Courier, monospace;
51+
background-color: #1e1e1e;
52+
padding: 0.2em 0.4em;
53+
border-radius: 3px;
54+
font-size: 85%;
55+
}
56+
57+
pre {
58+
background-color: #1e1e1e;
59+
padding: 16px;
60+
border-radius: 5px;
61+
overflow-x: auto;
62+
margin: 16px 0;
63+
}
64+
65+
pre code {
66+
padding: 0;
67+
background-color: transparent;
68+
}
69+
70+
blockquote {
71+
padding: 0 1em;
72+
color: #aaa;
73+
border-left: 0.25em solid #444;
74+
}
75+
76+
table {
77+
border-collapse: collapse;
78+
width: 100%;
79+
margin: 16px 0;
80+
}
81+
82+
table th, table td {
83+
border: 1px solid #444;
84+
padding: 8px 12px;
85+
}
86+
87+
table th {
88+
background-color: #1e1e1e;
89+
}
90+
91+
img {
92+
max-width: 100%;
93+
}
94+
95+
ul, ol {
96+
padding-left: 2em;
97+
}
98+
99+
strong {
100+
color: #e5c07b;
101+
}
102+
103+
em {
104+
color: #c678dd;
105+
}
106+
107+
.mermaid {
108+
background-color: #1e1e1e;
109+
padding: 16px;
110+
border-radius: 5px;
111+
margin: 16px 0;
112+
text-align: center;
113+
}
114+
115+
.language-mermaid {
116+
display: none;
117+
}
118+
119+
.mermaid-container {
120+
background-color: #1e1e1e;
121+
padding: 16px;
122+
border-radius: 5px;
123+
margin: 16px 0;
124+
text-align: center;
125+
}
126+
</style>
127+
</head>
128+
129+
<body>
130+
<div class="markdown-content">
131+
{{ rendered_markdown|safe }}
132+
</div>
133+
134+
<script>
135+
document.addEventListener('DOMContentLoaded', function() {
136+
// Configure mermaid
137+
mermaid.initialize({
138+
theme: 'dark',
139+
securityLevel: 'loose',
140+
fontFamily: 'monospace',
141+
startOnLoad: false
142+
});
143+
144+
// Find all pre elements
145+
const preElements = document.querySelectorAll('pre');
146+
preElements.forEach((preElement, index) => {
147+
// Get the code element inside
148+
const codeElement = preElement.querySelector('code');
149+
if (!codeElement) return;
150+
151+
// Check if it's a mermaid block
152+
// First try the class
153+
const isMermaid = codeElement.classList.contains('language-mermaid');
154+
// Or check if the first line says mermaid
155+
const firstLine = codeElement.textContent.trim().split('\n')[0].trim();
156+
const startsWithMermaid = firstLine === 'mermaid' ||
157+
firstLine === 'graph' ||
158+
firstLine === 'sequenceDiagram' ||
159+
firstLine.startsWith('flowchart') ||
160+
firstLine === 'gantt' ||
161+
firstLine === 'classDiagram';
162+
163+
if (isMermaid || startsWithMermaid) {
164+
// Get the diagram code
165+
let diagramCode = codeElement.textContent;
166+
167+
// Create a div for the mermaid diagram
168+
const mermaidDiv = document.createElement('div');
169+
mermaidDiv.className = 'mermaid-container';
170+
mermaidDiv.id = 'mermaid-diagram-' + index;
171+
172+
// Replace the pre element with our mermaid container
173+
preElement.parentNode.replaceChild(mermaidDiv, preElement);
174+
175+
// Create a div with the mermaid class
176+
const mermaidContentDiv = document.createElement('div');
177+
mermaidContentDiv.className = 'mermaid';
178+
mermaidContentDiv.textContent = diagramCode;
179+
mermaidDiv.appendChild(mermaidContentDiv);
180+
181+
// Let mermaid process it
182+
try {
183+
window.setTimeout(() => {
184+
mermaid.init(undefined, mermaidContentDiv);
185+
}, 0);
186+
} catch (error) {
187+
console.error('Error processing diagram:', error);
188+
mermaidDiv.innerHTML = '<div class="error">Error processing diagram</div>';
189+
}
190+
}
191+
});
192+
});
193+
</script>
194+
</body>
195+
196+
</html>

step02_httpserver/code/Cargo.toml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ name = "httpserver"
33
edition = "2021"
44

55
[dependencies]
6-
askama = "0.12.1"
7-
axum = "0.7"
8-
hyper = { version = "1", features = ["server", "http1"] }
9-
tokio = { version = "1", features = ["full"] }
10-
tower = { version = "0.4", features = ["util"] }
11-
mime = "0.3"
6+
askama = "0.14.0"
7+
axum = "0.8.4"
8+
hyper = { version = "1.6.0", features = ["server", "http1"] }
9+
tokio = { version = "1.45.1", features = ["full"] }
10+
tower = { version = "0.5.2", features = ["util"] }
11+
mime = "0.3.17"
12+
comrak = "0.39.0"
13+
ammonia = "4.1.0"

step02_httpserver/code/src/demo.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Markdown
2+
3+
Hello, I am an example **Markdown** (`*.md`) file!
4+
5+
## Table Example
6+
7+
| Name | Language | Purpose |
8+
|-----------: | ------------------- | --------------------------- |
9+
| Rust | Systems Programming | Fast, memory-safe applications |
10+
| Python | Scripting | Rapid development, data science |
11+
| JavaScript | Web | Frontend/backend web development |
12+
| Go | Systems | Cloud-native services |
13+
14+
## Mermaid Diagram
15+
16+
```mermaid
17+
flowchart TD
18+
A[Start] --> B{Is it working?}
19+
B -->|Yes| C[Great!]
20+
B -->|No| D[Debug]
21+
D --> B
22+
C --> E[Continue]
23+
```
24+
25+
## Sequence Diagram
26+
27+
```mermaid
28+
sequenceDiagram
29+
participant Browser
30+
participant Server
31+
Browser->>Server: GET /markdown
32+
Server->>Browser: HTML Response
33+
Browser->>Browser: Render Markdown
34+
Browser->>Browser: Execute Mermaid JS
35+
Browser->>Browser: Display Diagrams
36+
```
37+
38+
## Class Diagram
39+
40+
```mermaid
41+
classDiagram
42+
class Animal {
43+
+name: string
44+
+eat(): void
45+
+sleep(): void
46+
}
47+
class Dog {
48+
+bark(): void
49+
}
50+
class Cat {
51+
+meow(): void
52+
}
53+
Animal <|-- Dog
54+
Animal <|-- Cat
55+
```

step02_httpserver/code/src/main.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use tokio::{
88
};
99

1010
const SAMPLE_JSON: &str = include_str!("sample.json");
11+
const SAMPLE_MARKDOWN: &str = include_str!("demo.md");
1112

1213
mod lib {
1314
pub mod http;
@@ -18,6 +19,10 @@ async fn json_handler(headers: HeaderMap) -> impl IntoResponse {
1819
http::json_or_html(headers, SAMPLE_JSON).await
1920
}
2021

22+
async fn markdown_handler(headers: HeaderMap) -> impl IntoResponse {
23+
http::markdown_or_html(headers, SAMPLE_MARKDOWN).await
24+
}
25+
2126
#[tokio::main]
2227
async fn main() {
2328
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
@@ -35,7 +40,8 @@ async fn main() {
3540
}
3641
}),
3742
)
38-
.route("/json", get(json_handler));
43+
.route("/json", get(json_handler))
44+
.route("/markdown", get(markdown_handler));
3945

4046
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
4147
let listener = TcpListener::bind(addr).await.unwrap();

step07_redb/code/Cargo.toml

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ name = "httpserver"
33
edition = "2021"
44

55
[dependencies]
6-
askama = "0.12.1"
7-
axum = "0.7"
8-
hyper = { version = "1", features = ["server", "http1"] }
9-
mime = "0.3"
10-
redb = "1.4"
11-
serde = { version = "1", features = ["derive"] }
12-
serde_json = "1"
13-
tokio = { version = "1", features = ["full"] }
14-
tower = { version = "0.4", features = ["util"] }
6+
askama = "0.14.0"
7+
axum = "0.8.4"
8+
hyper = { version = "1.6.0", features = ["server", "http1"] }
9+
mime = "0.3.17"
10+
redb = "2.6.0"
11+
serde = { version = "1.0.219", features = ["derive"] }
12+
serde_json = "1.0.140"
13+
tokio = { version = "1.45.1", features = ["full"] }
14+
tower = { version = "0.5.2", features = ["util"] }
15+
comrak = "0.39.0"
16+
ammonia = "4.1.0"

step10_websocket/code/Cargo.toml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ name = "httpserver"
33
edition = "2021"
44

55
[dependencies]
6-
askama = "0.12.1"
7-
axum = { version = "0.8.3", features = ["ws"] }
8-
chrono = "0.4"
9-
clap = { version = "4.0", features = ["derive"] }
10-
tokio = { version = "1", features = ["full"] }
6+
askama = "0.14.0"
7+
axum = { version = "0.8.4", features = ["ws"] }
8+
chrono = "0.4.41"
9+
clap = { version = "4.5.38", features = ["derive"] }
10+
tokio = { version = "1.45.1", features = ["full"] }
11+
comrak = "0.39.0"
12+
ammonia = "4.1.0"

0 commit comments

Comments
 (0)