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
anchors in blog headings
ericwood.org
3 months ago
3d749f71
523fea6e
0/0
Waiting for spindle ...
+84
-6
4 changed files
expand all
collapse all
unified
split
Cargo.lock
Cargo.toml
src
blog.rs
views
blog
show
style.css
+17
Cargo.lock
···
1814
1814
]
1815
1815
1816
1816
[[package]]
1817
1817
+
name = "deunicode"
1818
1818
+
version = "1.6.2"
1819
1819
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1820
1820
+
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
1821
1821
+
1822
1822
+
[[package]]
1817
1823
name = "digest"
1818
1824
version = "0.10.7"
1819
1825
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3064
3070
"serde_html_form",
3065
3071
"serde_valid",
3066
3072
"serde_yaml",
3073
3073
+
"slug",
3067
3074
"sqlx",
3068
3075
"thiserror 2.0.17",
3069
3076
"tokio",
···
3820
3827
version = "0.4.11"
3821
3828
source = "registry+https://github.com/rust-lang/crates.io-index"
3822
3829
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
3830
3830
+
3831
3831
+
[[package]]
3832
3832
+
name = "slug"
3833
3833
+
version = "0.1.6"
3834
3834
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3835
3835
+
checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
3836
3836
+
dependencies = [
3837
3837
+
"deunicode",
3838
3838
+
"wasm-bindgen",
3839
3839
+
]
3823
3840
3824
3841
[[package]]
3825
3842
name = "smallvec"
+1
Cargo.toml
···
30
30
serde_yaml = "0.9.34"
31
31
comrak = "0.48.0"
32
32
arborium = { version = "2.3.2", features = ["all-languages"] }
33
33
+
slug = "0.1.6"
+42
-6
src/blog.rs
···
1
1
use std::{
2
2
collections::HashMap,
3
3
+
fmt::{self, Write},
3
4
fs::{self, File, read_to_string},
4
4
-
io::{BufRead, BufReader, Write},
5
5
+
io::{BufRead, BufReader, Write as IoWrite},
5
6
path::{Path, PathBuf},
6
7
};
7
8
8
9
use arborium::Highlighter;
9
9
-
use comrak::{adapters::SyntaxHighlighterAdapter, markdown_to_html_with_plugins, options};
10
10
+
use comrak::{
11
11
+
adapters::{HeadingAdapter, HeadingMeta, SyntaxHighlighterAdapter},
12
12
+
markdown_to_html_with_plugins,
13
13
+
nodes::Sourcepos,
14
14
+
options,
15
15
+
};
10
16
use minijinja::context;
11
17
12
18
use crate::{AppState, config::Config, date_time::DateTime, templates::render};
···
98
104
.collect();
99
105
100
106
for file_name in names {
101
101
-
let mut slug = file_name.replace("_", "-");
102
102
-
slug.replace_last(".md", "");
107
107
+
let mut slug = slug::slugify(&file_name);
108
108
+
slug.replace_last("-md", "");
103
109
let path = root_path.join(file_name).clone();
104
110
105
111
let maybe_post = extract_frontmatter(&path)?;
···
143
149
Ok(Some(post))
144
150
}
145
151
152
152
+
struct LinkedHeadingAdapter;
153
153
+
154
154
+
impl HeadingAdapter for LinkedHeadingAdapter {
155
155
+
fn enter(
156
156
+
&self,
157
157
+
output: &mut dyn Write,
158
158
+
heading: &HeadingMeta,
159
159
+
_sourcepos: Option<Sourcepos>,
160
160
+
) -> fmt::Result {
161
161
+
let id = slug::slugify(&heading.content);
162
162
+
163
163
+
write!(
164
164
+
output,
165
165
+
"<div class=\"blog__heading\" id=\"{}\">\n<h{}>",
166
166
+
id, heading.level
167
167
+
)
168
168
+
}
169
169
+
170
170
+
fn exit(&self, output: &mut dyn Write, heading: &HeadingMeta) -> fmt::Result {
171
171
+
let id = slug::slugify(&heading.content);
172
172
+
write!(
173
173
+
output,
174
174
+
"</h{}><a href=\"#{}\" aria-label=\"Permalink: {}\">#</a></div>",
175
175
+
heading.level, id, heading.content
176
176
+
)
177
177
+
}
178
178
+
}
179
179
+
146
180
pub fn render_post(path: &PathBuf) -> anyhow::Result<String> {
147
181
let md = read_to_string(path)?;
148
148
-
let adapter = SyntaxAdapter::new();
182
182
+
let syntax_adapter = SyntaxAdapter::new();
183
183
+
let heading_adapter = LinkedHeadingAdapter;
149
184
let mut plugins = options::Plugins::default();
150
150
-
plugins.render.codefence_syntax_highlighter = Some(&adapter);
185
185
+
plugins.render.codefence_syntax_highlighter = Some(&syntax_adapter);
186
186
+
plugins.render.heading_adapter = Some(&heading_adapter);
151
187
let body = markdown_to_html_with_plugins(
152
188
&md,
153
189
&comrak::Options {
+24
src/views/blog/show/style.css
···
13
13
display: block;
14
14
}
15
15
16
16
+
.blog__heading {
17
17
+
position: relative;
18
18
+
scroll-margin-top: 20px;
19
19
+
}
20
20
+
21
21
+
.blog__heading a {
22
22
+
position: absolute;
23
23
+
top: 50%;
24
24
+
left: 0;
25
25
+
transform: translateX(calc(-100% - 5px)) translateY(-50%);
26
26
+
color: black;
27
27
+
text-decoration: none;
28
28
+
opacity: 0;
29
29
+
transition: opacity 0.3s;
30
30
+
}
31
31
+
32
32
+
.blog__heading:hover a,
33
33
+
.blog__heading:hover a,
34
34
+
.blog__heading:hover a,
35
35
+
.blog__heading:hover a,
36
36
+
.blog__heading:hover a {
37
37
+
opacity: 0.75;
38
38
+
}
39
39
+