implemented automatic summary generation, and added timestamps in page list

This commit is contained in:
2026-01-07 20:55:32 +01:00
parent 0422c3042a
commit dcae9feac7
8 changed files with 125 additions and 37 deletions

View File

@@ -1,11 +0,0 @@
# The Random Collection
Welcome to this automatically generated book! This is the **Summary** page, acting as the entry point for our small website.
### Table of Contents
1. [The Mascot: Ferris](./01-ferris.md) - A look at the Rust mascot.
2. [The Dark Elixir](./02-coffee.md) - A brief history of coffee.
3. [To the Stars](./03-space.md) - Facts about the Voyager 1 probe.
---
*Generated for the Axum Markdown Server test.*

View File

@@ -8,10 +8,12 @@ use axum::{
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use pulldown_cmark::{Options, Parser as MarkdownParser, html}; use pulldown_cmark::{Options, Parser as MarkdownParser, html};
use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
use std::{io::Cursor, path::PathBuf}; use std::{io::Cursor, path::PathBuf};
use syntect::{highlighting::ThemeSet, parsing::SyntaxSet}; use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
use tera::{Context, Tera}; use tera::{Context, Tera};
use tokio::io::{AsyncBufReadExt, BufReader};
mod codeblocks; mod codeblocks;
use codeblocks::*; use codeblocks::*;
@@ -20,6 +22,8 @@ lazy_static! {
pub static ref TEMPLATES: Tera = { pub static ref TEMPLATES: Tera = {
let mut tera = Tera::default(); let mut tera = Tera::default();
tera.add_raw_templates(vec![ tera.add_raw_templates(vec![
("_base.html", include_str!("../templates/_base.html")),
("home.html", include_str!("../templates/home.html")),
("page.html", include_str!("../templates/page.html")), ("page.html", include_str!("../templates/page.html")),
("style.css", include_str!("../templates/style.css")), ("style.css", include_str!("../templates/style.css")),
]) ])
@@ -100,7 +104,54 @@ async fn main() -> anyhow::Result<()> {
} }
async fn render_summary(State(state): State<Arc<AppState>>) -> impl IntoResponse { async fn render_summary(State(state): State<Arc<AppState>>) -> impl IntoResponse {
render_md_file("SUMMARY.md", state).await let mut context = Context::new();
context.insert("title", "Pages");
#[derive(Deserialize, Serialize)]
struct Page {
filename: String,
title: String,
datetime: String,
}
let mut pages: Vec<Page> = Vec::new();
if let Ok(mut entries) = tokio::fs::read_dir(&state.docs_dir).await {
while let Ok(Some(entry)) = entries.next_entry().await {
let path = entry.path();
let filename = entry.file_name();
let filename = filename.to_str().unwrap_or("<filename>");
let title = if let Ok(file) = tokio::fs::File::open(&path).await {
let mut reader = BufReader::new(file);
let mut line = String::new();
match reader.read_line(&mut line).await {
Ok(_) => line.trim_start_matches('#').trim().to_string(),
Err(_) => filename.to_string(),
}
} else {
filename.to_string()
};
let datetime = filename
.split_once('@')
.and_then(|(_, ts_with_ext)| ts_with_ext.split('.').next())
.map(|dt| dt.to_string())
.unwrap_or_else(|| "Invalid Date".to_string());
pages.push(Page {
filename: filename.to_string(),
title,
datetime,
});
}
}
context.insert("files", &pages);
match TEMPLATES.render("home.html", &context) {
Ok(rendered) => Html(rendered),
Err(e) => Html(format!("<h1>Template Error</h1><pre>{}</pre>", e)),
}
} }
async fn render_page( async fn render_page(
@@ -112,7 +163,14 @@ async fn render_page(
} else { } else {
format!("{}.md", page) format!("{}.md", page)
}; };
render_md_file(&filename, state).await
let file_path = state.docs_dir.join(&filename);
let content = match tokio::fs::read_to_string(&file_path).await {
Ok(c) => c,
Err(_) => return Html("<h1>404</h1><p>Page not found</p>".to_string()),
};
render_md_file(&content, &filename, state).await
} }
async fn serve_css() -> impl IntoResponse { async fn serve_css() -> impl IntoResponse {
@@ -125,14 +183,7 @@ async fn serve_css() -> impl IntoResponse {
} }
} }
async fn render_md_file(filename: &str, state: Arc<AppState>) -> Html<String> { async fn render_md_file(content: &String, filename: &str, state: Arc<AppState>) -> Html<String> {
let file_path = state.docs_dir.join(filename);
let content = match tokio::fs::read_to_string(&file_path).await {
Ok(c) => c,
Err(_) => return Html("<h1>404</h1><p>Page not found</p>".to_string()),
};
let mut options = Options::empty(); let mut options = Options::empty();
options.insert(Options::ENABLE_TABLES); options.insert(Options::ENABLE_TABLES);
options.insert(Options::ENABLE_FOOTNOTES); options.insert(Options::ENABLE_FOOTNOTES);

19
templates/_base.html Normal file
View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<meta charset="UTF-8">
<title>{{ title }}</title>
<link rel="stylesheet" href="/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
{% endblock head %}
</head>
<body>
<nav>
<a href="/">Home</a>
</nav>
<div id="content">{% block content %}{% endblock content %}</div>
</body>
</html>

41
templates/home.html Normal file
View File

@@ -0,0 +1,41 @@
{% extends "_base.html" %}
{% block title %}{{ title }}{% endblock title %}
{% block content %}
<h1>{{ title }}</h1>
<ol>
{% for file in files %}
<li>
<a href="./{{ file.filename }}">{{ file.title }}</a> -
<span class="local-date" data-timestamp="{{ file.datetime }}">
{{ file.datetime }}
</span>
</li>
{% endfor %}
</ol>
<hr />
<script>
document.addEventListener("DOMContentLoaded", function() {
const dateElements = document.querySelectorAll('.local-date');
dateElements.forEach(el => {
const rawValue = el.getAttribute('data-timestamp');
const unixTimestamp = parseInt(rawValue);
if (!isNaN(unixTimestamp)) {
const date = new Date(unixTimestamp * 1000);
el.textContent = date.toLocaleString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
});
});
</script>
{% endblock content %}

View File

@@ -1,21 +1,10 @@
<!DOCTYPE html> {% extends "_base.html" %}
<html lang="en"> {% block title %}{{ title }}{% endblock title %}
<head> {% block content %}
<meta charset="UTF-8">
<title>{{ title }}</title>
<link rel="stylesheet" href="/style.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<article> <article>
{{ content | safe }} {{ content | safe }}
</article> </article>
<nav>
<a href="/">Home (Summary)</a>
</nav>
<script> <script>
document.querySelectorAll('pre[data-code]').forEach((block) => { document.querySelectorAll('pre[data-code]').forEach((block) => {
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
@@ -51,5 +40,4 @@
}); });
}); });
</script> </script>
</body> {% endblock content %}
</html>