post parsing with different comrak
This commit is contained in:
parent
4fd4373ed0
commit
49b92088b7
@ -10,8 +10,8 @@ askama = { version = "0.12", features = ["with-axum", "mime", "mime_guess"] }
|
|||||||
askama_axum = "0.4.0"
|
askama_axum = "0.4.0"
|
||||||
axum = "0.7.3"
|
axum = "0.7.3"
|
||||||
chrono = { version = "0.4.31", features = ["serde"] }
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
|
comrak = { version = "0.21", features = ["shortcodes"] }
|
||||||
gray_matter = "0.2.6"
|
gray_matter = "0.2.6"
|
||||||
markdown = "1.0.0-alpha.16"
|
|
||||||
rss = "2.0.7"
|
rss = "2.0.7"
|
||||||
serde = "1.0.195"
|
serde = "1.0.195"
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.111"
|
||||||
|
@ -26,7 +26,9 @@ async fn main() {
|
|||||||
.init();
|
.init();
|
||||||
|
|
||||||
// build our application with a single route
|
// build our application with a single route
|
||||||
let app = router::get_router().nest_service("/styles", ServeDir::new("styles"));
|
let app = router::get_router()
|
||||||
|
.nest_service("/styles", ServeDir::new("styles"))
|
||||||
|
.nest_service("/images", ServeDir::new("../static/images"));
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let app = app.layer(LiveReloadLayer::new());
|
let app = app.layer(LiveReloadLayer::new());
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::filters;
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::{extract::Path, http::StatusCode};
|
use axum::{extract::Path, http::StatusCode};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@ -28,6 +29,7 @@ pub struct PostMetadata {
|
|||||||
pub struct PostTemplate {
|
pub struct PostTemplate {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
|
pub date: DateTime<Utc>,
|
||||||
pub site_footer: SiteFooter,
|
pub site_footer: SiteFooter,
|
||||||
pub header_props: HeaderProps,
|
pub header_props: HeaderProps,
|
||||||
}
|
}
|
||||||
@ -42,6 +44,7 @@ pub async fn render_post(Path(post_id): Path<String>) -> Result<PostTemplate, St
|
|||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
Ok(PostTemplate {
|
Ok(PostTemplate {
|
||||||
title: parsed.metadata.title,
|
title: parsed.metadata.title,
|
||||||
|
date: parsed.metadata.date,
|
||||||
body: parsed.body,
|
body: parsed.body,
|
||||||
site_footer,
|
site_footer,
|
||||||
header_props: HeaderProps::default(),
|
header_props: HeaderProps::default(),
|
||||||
|
@ -2,8 +2,12 @@ use std::path::Path;
|
|||||||
|
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use comrak::{
|
||||||
|
format_html,
|
||||||
|
nodes::{AstNode, NodeValue},
|
||||||
|
parse_document, Arena, Options,
|
||||||
|
};
|
||||||
use gray_matter::{engine::YAML, Matter};
|
use gray_matter::{engine::YAML, Matter};
|
||||||
use markdown::{to_html_with_options, CompileOptions, Constructs, Options, ParseOptions};
|
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Deserializer};
|
use serde::{de::DeserializeOwned, Deserialize, Deserializer};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
@ -35,26 +39,6 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>(
|
|||||||
// TODO Proper reasoning for an error
|
// TODO Proper reasoning for an error
|
||||||
.map_err(|_| StatusCode::NOT_FOUND)?;
|
.map_err(|_| StatusCode::NOT_FOUND)?;
|
||||||
|
|
||||||
let markdown_options = Options {
|
|
||||||
parse: ParseOptions {
|
|
||||||
constructs: Constructs {
|
|
||||||
frontmatter: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
compile: CompileOptions {
|
|
||||||
allow_dangerous_html: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let body = to_html_with_options(&file_contents, &markdown_options).map_err(|reason| {
|
|
||||||
tracing::error!(reason);
|
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let matter = Matter::<YAML>::new();
|
let matter = Matter::<YAML>::new();
|
||||||
let metadata = matter
|
let metadata = matter
|
||||||
.parse_with_struct::<Metadata>(&file_contents)
|
.parse_with_struct::<Metadata>(&file_contents)
|
||||||
@ -63,6 +47,8 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>(
|
|||||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
return StatusCode::INTERNAL_SERVER_ERROR;
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let body = parse_html(&metadata.content);
|
||||||
|
|
||||||
let filename = Path::new(path)
|
let filename = Path::new(path)
|
||||||
.file_stem()
|
.file_stem()
|
||||||
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?
|
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
@ -76,3 +62,51 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>(
|
|||||||
slug: filename,
|
slug: filename,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_html(markdown: &str) -> String {
|
||||||
|
let mut options = Options::default();
|
||||||
|
options.parse.smart = true;
|
||||||
|
options.parse.relaxed_autolinks = true;
|
||||||
|
options.extension.strikethrough = true;
|
||||||
|
// options.extension.tagfilter = true;
|
||||||
|
options.extension.table = true;
|
||||||
|
options.extension.autolink = true;
|
||||||
|
options.extension.tasklist = true;
|
||||||
|
options.extension.superscript = true;
|
||||||
|
options.extension.header_ids = Some("".to_string());
|
||||||
|
options.extension.footnotes = false;
|
||||||
|
options.extension.description_lists = false;
|
||||||
|
options.extension.multiline_block_quotes = false;
|
||||||
|
options.extension.shortcodes = true;
|
||||||
|
options.render.hardbreaks = true;
|
||||||
|
options.render.github_pre_lang = true;
|
||||||
|
options.render.full_info_string = true;
|
||||||
|
options.render.unsafe_ = true;
|
||||||
|
|
||||||
|
// The returned nodes are created in the supplied Arena, and are bound by its lifetime.
|
||||||
|
let arena = Arena::new();
|
||||||
|
|
||||||
|
let root = parse_document(&arena, markdown, &options);
|
||||||
|
|
||||||
|
fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &F)
|
||||||
|
where
|
||||||
|
F: Fn(&'a AstNode<'a>),
|
||||||
|
{
|
||||||
|
f(node);
|
||||||
|
for c in node.children() {
|
||||||
|
iter_nodes(c, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iter_nodes(root, &|node| match &mut node.data.borrow_mut().value {
|
||||||
|
&mut NodeValue::Text(ref mut _text) => {
|
||||||
|
// let orig = std::mem::replace(text, String::new());
|
||||||
|
// *text = orig.replace("my", "your");
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut html = vec![];
|
||||||
|
format_html(root, &options, &mut html).unwrap();
|
||||||
|
return String::from_utf8(html).unwrap();
|
||||||
|
}
|
||||||
|
@ -1,3 +1,20 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
.article-body {
|
||||||
|
h1 {
|
||||||
|
@apply px-4 text-2xl text-blue-900 my-2;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
@apply px-4 text-xl text-blue-900 my-2;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
@apply px-4 my-2;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
@apply p-4 my-1 overflow-auto text-sm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -444,6 +444,63 @@ video {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-body {
|
||||||
|
h1 {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(30 58 138 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(30 58 138 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*, ::before, ::after {
|
*, ::before, ::after {
|
||||||
--tw-border-spacing-x: 0;
|
--tw-border-spacing-x: 0;
|
||||||
--tw-border-spacing-y: 0;
|
--tw-border-spacing-y: 0;
|
||||||
@ -544,64 +601,30 @@ video {
|
|||||||
--tw-backdrop-sepia: ;
|
--tw-backdrop-sepia: ;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-3 {
|
|
||||||
margin: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-1 {
|
.m-1 {
|
||||||
margin: 0.25rem;
|
margin: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-3 {
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-5 {
|
|
||||||
margin-top: 1.25rem;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-8 {
|
|
||||||
margin-top: 2rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-10 {
|
|
||||||
margin-top: 2.5rem;
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-12 {
|
|
||||||
margin-top: 3rem;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-4 {
|
.mx-4 {
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-2 {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-2 {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-3 {
|
|
||||||
margin-left: 0.75rem;
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-6 {
|
.mx-6 {
|
||||||
margin-left: 1.5rem;
|
margin-left: 1.5rem;
|
||||||
margin-right: 1.5rem;
|
margin-right: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.my-12 {
|
||||||
|
margin-top: 3rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-3 {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mb-3 {
|
.mb-3 {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
@ -661,15 +684,6 @@ video {
|
|||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-2 {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-2 {
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-4 {
|
.px-4 {
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
@ -680,38 +694,10 @@ video {
|
|||||||
padding-right: 1.25rem;
|
padding-right: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pr-2 {
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pr-3 {
|
|
||||||
padding-right: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-right {
|
.text-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-lg {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
line-height: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-base {
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-2xl {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
line-height: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-sm {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-3xl {
|
.text-3xl {
|
||||||
font-size: 1.875rem;
|
font-size: 1.875rem;
|
||||||
line-height: 2.25rem;
|
line-height: 2.25rem;
|
||||||
@ -722,6 +708,25 @@ video {
|
|||||||
line-height: 2.5rem;
|
line-height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-base {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-lg {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-extrabold {
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
.font-medium {
|
.font-medium {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@ -730,23 +735,10 @@ video {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-bold {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-extrabold {
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.italic {
|
.italic {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-gray-600 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(75 85 99 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-blue-700 {
|
.text-blue-700 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(29 78 216 / var(--tw-text-opacity));
|
color: rgb(29 78 216 / var(--tw-text-opacity));
|
||||||
@ -757,21 +749,21 @@ video {
|
|||||||
color: rgb(30 64 175 / var(--tw-text-opacity));
|
color: rgb(30 64 175 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-gray-800 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(31 41 55 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-blue-950 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(23 37 84 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-blue-900 {
|
.text-blue-900 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(30 58 138 / var(--tw-text-opacity));
|
color: rgb(30 58 138 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-gray-600 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(75 85 99 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray-800 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(31 41 55 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.drop-shadow-md {
|
.drop-shadow-md {
|
||||||
--tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06));
|
--tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06));
|
||||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||||
|
@ -3,9 +3,19 @@
|
|||||||
{% block title %}{{title}}{% endblock %}
|
{% block title %}{{title}}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{title}}</h1>
|
<article>
|
||||||
|
<header class="px-4">
|
||||||
|
<h1 class="text-3xl text-blue-900 mb-3">{{title}}</h1>
|
||||||
|
<section class="created-at m-1 text-right text-sm text-gray-600">
|
||||||
|
<span>Published on</span>
|
||||||
|
<time datetime="{date}"> {{date|pretty_date}} </time>
|
||||||
|
</section>
|
||||||
|
</header>
|
||||||
|
|
||||||
<article>{{body|escape("none")}}</article>
|
<section class="article-body">
|
||||||
|
{{body|escape("none")}}
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
{# footer #}
|
{# footer #}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user