tangled
alpha
login
or
join now
ericwood.org
/
photos-site
1
fork
atom
A little app to serve my photography from my personal website
1
fork
atom
overview
issues
pulls
pipelines
Scroll spy
ericwood.org
3 months ago
a25a4426
3b66bbc7
+69
-9
5 changed files
expand all
collapse all
unified
split
Cargo.lock
Cargo.toml
src
blog.rs
views
blog
show
script.js
template.jinja
+1
Cargo.lock
···
3066
3066
"init-tracing-opentelemetry",
3067
3067
"minijinja",
3068
3068
"minijinja-autoreload",
3069
3069
+
"regex",
3069
3070
"serde",
3070
3071
"serde_html_form",
3071
3072
"serde_valid",
+1
Cargo.toml
···
31
31
comrak = "0.48.0"
32
32
arborium = { version = "2.3.2", features = ["all-languages"] }
33
33
slug = "0.1.6"
34
34
+
regex = "1.12.2"
+26
-1
src/blog.rs
···
16
16
nodes::{AstNode, NodeValue, Sourcepos},
17
17
options, parse_document,
18
18
};
19
19
+
use regex::Regex;
19
20
use serde::Serialize;
20
21
21
22
use crate::{
···
175
176
176
177
write!(
177
178
output,
178
178
-
"<div class=\"blog__heading\" id=\"{}\">\n<h{}>",
179
179
+
"</section>\n<section id=\"{}\" class=\"blog__section\"><div class=\"blog__heading\">\n<h{}>",
179
180
id, heading.level
180
181
)
181
182
}
···
248
249
toc.into()
249
250
}
250
251
252
252
+
fn fix_sections(body: &str) -> String {
253
253
+
let re = Regex::new(r"<\/?section>").unwrap();
254
254
+
let mut result = body.to_string();
255
255
+
for (i, m) in re.find_iter(body).enumerate() {
256
256
+
if m.as_str() == "<section>" && i == 0 {
257
257
+
break;
258
258
+
}
259
259
+
if m.as_str() == "</section>" && i == 0 {
260
260
+
result = body.replacen("</section>", "", 1);
261
261
+
break;
262
262
+
}
263
263
+
}
264
264
+
265
265
+
let close = "</section>";
266
266
+
if let Some(i) = body.rfind("<section class=\"footnotes\"") {
267
267
+
result.insert_str(i - close.len(), close);
268
268
+
} else {
269
269
+
result += close;
270
270
+
}
271
271
+
272
272
+
result
273
273
+
}
274
274
+
251
275
pub fn render_post(path: &PathBuf) -> anyhow::Result<(String, Vec<Section>)> {
252
276
let md = read_to_string(path)?;
253
277
let syntax_adapter = SyntaxAdapter::new();
···
279
303
280
304
let mut body = String::new();
281
305
format_document_with_plugins(root, &options, &mut body, &plugins)?;
306
306
+
body = fix_sections(&body);
282
307
283
308
Ok((body, toc))
284
309
}
+26
src/views/blog/show/script.js
···
1
1
+
const initToc = () => {
2
2
+
const intersections = {};
3
3
+
4
4
+
document.querySelectorAll(".blog__section").forEach((el) => {
5
5
+
intersections[el.id] = document.querySelector(`.blog__toc a[href="#${el.id}"]`);
6
6
+
});
7
7
+
8
8
+
9
9
+
observer = new IntersectionObserver((entries) => {
10
10
+
entries.forEach((entry) => {
11
11
+
const id = entry.target.id;
12
12
+
intersections[id].classList.toggle("active", entry.isIntersecting);
13
13
+
});
14
14
+
},
15
15
+
{
16
16
+
rootMargin: "-50% 0px",
17
17
+
});
18
18
+
19
19
+
document.querySelectorAll(".blog__section").forEach((el) => {
20
20
+
observer.observe(el);
21
21
+
});
22
22
+
};
23
23
+
24
24
+
document.addEventListener("DOMContentLoaded", () => {
25
25
+
initToc();
26
26
+
});
+15
-8
src/views/blog/show/template.jinja
···
8
8
{% endif %}
9
9
{% endblock %}
10
10
{% block body %}
11
11
-
<div class="blog__container">
12
12
-
<h1 class="blog__title">{{ post.title }}</h1>
13
13
-
<p>
14
14
-
<time datetime="{{ post.published_at }}">{{ post.published_at }}</time>
15
15
-
</p>
16
16
-
{{ toc_html }}
17
17
-
<div class="blog__body">
18
18
-
{{ body }}
11
11
+
<div class="blog__layout">
12
12
+
<div class="blog__header">
13
13
+
<h1 class="blog__title">{{ post.title }}</h1>
14
14
+
<p>
15
15
+
<time datetime="{{ post.published_at }}">{{ post.published_at }}</time>
16
16
+
</p>
17
17
+
</div>
18
18
+
<div class="blog__container">
19
19
+
<div class="blog__body">
20
20
+
{{ body }}
21
21
+
</div>
22
22
+
<div class="blog__toc">
23
23
+
{{ toc_html }}
24
24
+
</div>
19
25
</div>
20
26
</div>
27
27
+
{{ inline_script("src/views/blog/show/script.js") }}
21
28
{% endblock %}