implemented automatic summary generation, and added timestamps in page list
This commit is contained in:
@@ -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.*
|
||||
71
src/main.rs
71
src/main.rs
@@ -8,10 +8,12 @@ use axum::{
|
||||
use clap::{Parser, Subcommand};
|
||||
use lazy_static::lazy_static;
|
||||
use pulldown_cmark::{Options, Parser as MarkdownParser, html};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use std::{io::Cursor, path::PathBuf};
|
||||
use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
|
||||
use tera::{Context, Tera};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
|
||||
mod codeblocks;
|
||||
use codeblocks::*;
|
||||
@@ -20,6 +22,8 @@ lazy_static! {
|
||||
pub static ref TEMPLATES: Tera = {
|
||||
let mut tera = Tera::default();
|
||||
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")),
|
||||
("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 {
|
||||
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(
|
||||
@@ -112,7 +163,14 @@ async fn render_page(
|
||||
} else {
|
||||
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 {
|
||||
@@ -125,14 +183,7 @@ async fn serve_css() -> impl IntoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
async fn render_md_file(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()),
|
||||
};
|
||||
|
||||
async fn render_md_file(content: &String, filename: &str, state: Arc<AppState>) -> Html<String> {
|
||||
let mut options = Options::empty();
|
||||
options.insert(Options::ENABLE_TABLES);
|
||||
options.insert(Options::ENABLE_FOOTNOTES);
|
||||
|
||||
19
templates/_base.html
Normal file
19
templates/_base.html
Normal 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
41
templates/home.html
Normal 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 %}
|
||||
@@ -1,21 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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>
|
||||
{% extends "_base.html" %}
|
||||
{% block title %}{{ title }}{% endblock title %}
|
||||
{% block content %}
|
||||
<article>
|
||||
{{ content | safe }}
|
||||
</article>
|
||||
|
||||
<nav>
|
||||
<a href="/">Home (Summary)</a>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('pre[data-code]').forEach((block) => {
|
||||
const wrapper = document.createElement('div');
|
||||
@@ -51,5 +40,4 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock content %}
|
||||
|
||||
Reference in New Issue
Block a user