support videos
parent
0437d2b2e5
commit
d496efe835
|
@ -17,6 +17,15 @@ version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android-tzdata"
|
name = "android-tzdata"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -32,6 +41,16 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ansi-to-html"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d73c455ae09fa2223a75114789f30ad605e9e297f79537953523366c05995f5f"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.15"
|
version = "0.6.15"
|
||||||
|
@ -172,6 +191,7 @@ dependencies = [
|
||||||
name = "blog-thing"
|
name = "blog-thing"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ansi-to-html",
|
||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -592,6 +612,35 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
|
@ -715,6 +764,26 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.40.0"
|
version = "1.40.0"
|
||||||
|
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ansi-to-html = "0.2.1"
|
||||||
axum = "0.7.7"
|
axum = "0.7.7"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.5.18", features = ["derive", "env"] }
|
clap = { version = "4.5.18", features = ["derive", "env"] }
|
||||||
|
|
188
src/main.rs
188
src/main.rs
|
@ -5,8 +5,9 @@ use axum::{
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, path::PathBuf, sync::Arc};
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use markdown::ParseOptions;
|
||||||
|
use std::{collections::HashMap, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct BlogState {
|
struct BlogState {
|
||||||
|
@ -77,25 +78,182 @@ struct Post {
|
||||||
embed_template_name: Option<String>,
|
embed_template_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait ToHtml {
|
||||||
|
fn is_directive(&self) -> bool;
|
||||||
|
fn to_html(&self) -> String;
|
||||||
|
fn template(&self) -> Option<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToHtml for markdown::mdast::Node {
|
||||||
|
fn to_html(&self) -> String {
|
||||||
|
let mut s = String::new();
|
||||||
|
|
||||||
|
if self.is_directive() {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self {
|
||||||
|
markdown::mdast::Node::Root(root) => {
|
||||||
|
for child in &root.children {
|
||||||
|
s += &child.to_html();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::Blockquote(block_quote) => {
|
||||||
|
s += "<blockquote>";
|
||||||
|
for child in &block_quote.children {
|
||||||
|
s += &child.to_html();
|
||||||
|
}
|
||||||
|
s += "</blockquote>";
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::FootnoteDefinition(_) => {}
|
||||||
|
markdown::mdast::Node::MdxJsxFlowElement(_) => {}
|
||||||
|
markdown::mdast::Node::List(list) => {
|
||||||
|
s += "<ul>";
|
||||||
|
for child in &list.children {
|
||||||
|
s += &child.to_html();
|
||||||
|
}
|
||||||
|
s += "</ul>";
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::MdxjsEsm(_) => {}
|
||||||
|
markdown::mdast::Node::Toml(_) => {}
|
||||||
|
markdown::mdast::Node::Yaml(_) => {}
|
||||||
|
markdown::mdast::Node::Break(_) => {}
|
||||||
|
markdown::mdast::Node::InlineCode(inline_code) => {
|
||||||
|
s += &format!(
|
||||||
|
"<code>{}</code>",
|
||||||
|
&ansi_to_html::convert(&inline_code.value).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::InlineMath(_) => {}
|
||||||
|
markdown::mdast::Node::Delete(_) => {}
|
||||||
|
markdown::mdast::Node::Emphasis(emphasis) => {
|
||||||
|
s += "<em>";
|
||||||
|
for child in &emphasis.children {
|
||||||
|
s += &child.to_html();
|
||||||
|
}
|
||||||
|
s += "</em>";
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::MdxTextExpression(_) => {}
|
||||||
|
markdown::mdast::Node::FootnoteReference(_) => {}
|
||||||
|
markdown::mdast::Node::Html(_) => {}
|
||||||
|
markdown::mdast::Node::Image(image) => {
|
||||||
|
if image.url.ends_with(".mp4") {
|
||||||
|
s += &format!("<video controls src={}>", image.url);
|
||||||
|
} else {
|
||||||
|
s += &format!("<img src={}>", image.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::ImageReference(_) => {}
|
||||||
|
markdown::mdast::Node::MdxJsxTextElement(_) => {}
|
||||||
|
markdown::mdast::Node::Link(link) => {
|
||||||
|
s += &format!("<a href={}>", link.url);
|
||||||
|
for child in &link.children {
|
||||||
|
s += &child.to_html();
|
||||||
|
}
|
||||||
|
s += "</a>";
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::LinkReference(_) => {}
|
||||||
|
markdown::mdast::Node::Strong(strong) => {
|
||||||
|
s += "<b>";
|
||||||
|
for child in &strong.children {
|
||||||
|
s += &child.to_html();
|
||||||
|
}
|
||||||
|
s += "</b>";
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::Text(text) => s += &ansi_to_html::convert(&text.value).unwrap(),
|
||||||
|
markdown::mdast::Node::Code(code) => {
|
||||||
|
s += &format!(
|
||||||
|
"<pre><code>{}</code></pre>",
|
||||||
|
&ansi_to_html::convert(&code.value).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::Math(_) => {}
|
||||||
|
markdown::mdast::Node::MdxFlowExpression(_) => {}
|
||||||
|
markdown::mdast::Node::Heading(heading) => {
|
||||||
|
s += &format!("<h{}>", heading.depth);
|
||||||
|
for child in &heading.children {
|
||||||
|
s += &child.to_html();
|
||||||
|
}
|
||||||
|
s += &format!("</h{}>", heading.depth);
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::Table(_) => {}
|
||||||
|
markdown::mdast::Node::ThematicBreak(_) => {
|
||||||
|
s += "<hr>";
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::TableRow(_) => {}
|
||||||
|
markdown::mdast::Node::TableCell(_) => {}
|
||||||
|
markdown::mdast::Node::ListItem(item) => {
|
||||||
|
s += "<li>";
|
||||||
|
for child in &item.children {
|
||||||
|
s += &child.to_html();
|
||||||
|
}
|
||||||
|
s += "</li>";
|
||||||
|
}
|
||||||
|
markdown::mdast::Node::Definition(_) => {}
|
||||||
|
markdown::mdast::Node::Paragraph(paragraph) => {
|
||||||
|
s += "<p>";
|
||||||
|
for child in ¶graph.children {
|
||||||
|
s += &child.to_html();
|
||||||
|
}
|
||||||
|
s += "</p>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_directive(&self) -> bool {
|
||||||
|
self.children()
|
||||||
|
.and_then(|children| children.first())
|
||||||
|
.map(|first| match first {
|
||||||
|
markdown::mdast::Node::Text(text) => {
|
||||||
|
if text.value.starts_with("%{") && text.value.ends_with("}") {
|
||||||
|
text.value.chars().filter(|x| *x == '}').count() == 1
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}).is_some_and(|x| x)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn template(&self) -> Option<String> {
|
||||||
|
self.children()
|
||||||
|
.and_then(|children| children.first())
|
||||||
|
.and_then(|first| match first {
|
||||||
|
markdown::mdast::Node::Paragraph(_) => first.template(),
|
||||||
|
markdown::mdast::Node::Text(text) => {
|
||||||
|
if text.value.starts_with("%{") {
|
||||||
|
text.value[2..].split('}').next().map(String::from)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Post {
|
impl Post {
|
||||||
fn to_html(&self, templates: &HashMap<String, Cached<Template>>) -> String {
|
fn to_html(&self, templates: &HashMap<String, Cached<Template>>) -> String {
|
||||||
let mut s = String::with_capacity(self.content.len());
|
let mut s = String::with_capacity(self.content.len());
|
||||||
if let Some(template) = self
|
|
||||||
.embed_template_name
|
let ast = markdown::to_mdast(&self.content, &ParseOptions::default()).unwrap();
|
||||||
.as_ref()
|
let template_name = ast.template();
|
||||||
.and_then(|name| templates.get(name))
|
|
||||||
{
|
if let Some(template) = template_name.and_then(|name| templates.get(&name)) {
|
||||||
if template.data.index > 0 {
|
if template.data.index > 0 {
|
||||||
s += &template.data.content[0..template.data.index];
|
s += &template.data.content[0..template.data.index];
|
||||||
}
|
}
|
||||||
|
|
||||||
s += &markdown::to_html(&self.content);
|
s += &ast.to_html();
|
||||||
|
|
||||||
if template.data.index > 0 {
|
if template.data.index > 0 {
|
||||||
s += &template.data.content[template.data.index + 3..]
|
s += &template.data.content[template.data.index + 3..]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s += &markdown::to_html(&self.content);
|
s += &ast.to_html();
|
||||||
}
|
}
|
||||||
|
|
||||||
s
|
s
|
||||||
|
@ -105,7 +263,8 @@ impl Post {
|
||||||
impl From<String> for Post {
|
impl From<String> for Post {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
let template_name = if value.starts_with("%{") {
|
let template_name = if value.starts_with("%{") {
|
||||||
value[2..].split('}').next().map(Into::into)
|
// value[2..].split('}').next().map(Into::into)
|
||||||
|
None
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -138,7 +297,10 @@ async fn main() {
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
|
|
||||||
for dir in std::fs::read_dir(&blog_state.config.blog_dir).unwrap().flatten() {
|
for dir in std::fs::read_dir(&blog_state.config.blog_dir)
|
||||||
|
.unwrap()
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
if dir.file_type().unwrap().is_file() {
|
if dir.file_type().unwrap().is_file() {
|
||||||
let file_name = dir.file_name().into_string().unwrap();
|
let file_name = dir.file_name().into_string().unwrap();
|
||||||
let Some((file_name, extension)) = file_name.split_once('.') else {
|
let Some((file_name, extension)) = file_name.split_once('.') else {
|
||||||
|
@ -169,7 +331,9 @@ async fn main() {
|
||||||
.route("/:post", get(blog_post))
|
.route("/:post", get(blog_post))
|
||||||
.route("/images/:image", get(image))
|
.route("/images/:image", get(image))
|
||||||
.with_state(Arc::new(blog_state));
|
.with_state(Arc::new(blog_state));
|
||||||
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await.unwrap();
|
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -199,7 +363,7 @@ async fn blog_post(
|
||||||
|
|
||||||
async fn image(State(state): AppState, Path(image_path): Path<String>) -> impl IntoResponse {
|
async fn image(State(state): AppState, Path(image_path): Path<String>) -> impl IntoResponse {
|
||||||
match std::fs::read(format!("{}/images/{image_path}", state.config.blog_dir)) {
|
match std::fs::read(format!("{}/images/{image_path}", state.config.blog_dir)) {
|
||||||
Ok(contents) => Ok(([(header::CONTENT_TYPE, "image/png")], contents)),
|
Ok(contents) => Ok(([(header::CONTENT_TYPE, "image/png;video/mp4")], contents)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{e:#?}");
|
eprintln!("{e:#?}");
|
||||||
Err(StatusCode::NOT_FOUND)
|
Err(StatusCode::NOT_FOUND)
|
||||||
|
|
Loading…
Reference in New Issue