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"
|
||||
axum = "0.7.3"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
comrak = { version = "0.21", features = ["shortcodes"] }
|
||||
gray_matter = "0.2.6"
|
||||
markdown = "1.0.0-alpha.16"
|
||||
rss = "2.0.7"
|
||||
serde = "1.0.195"
|
||||
serde_json = "1.0.111"
|
||||
|
@ -26,7 +26,9 @@ async fn main() {
|
||||
.init();
|
||||
|
||||
// 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)]
|
||||
let app = app.layer(LiveReloadLayer::new());
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::filters;
|
||||
use askama::Template;
|
||||
use axum::{extract::Path, http::StatusCode};
|
||||
use chrono::{DateTime, Utc};
|
||||
@ -28,6 +29,7 @@ pub struct PostMetadata {
|
||||
pub struct PostTemplate {
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
pub date: DateTime<Utc>,
|
||||
pub site_footer: SiteFooter,
|
||||
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)?;
|
||||
Ok(PostTemplate {
|
||||
title: parsed.metadata.title,
|
||||
date: parsed.metadata.date,
|
||||
body: parsed.body,
|
||||
site_footer,
|
||||
header_props: HeaderProps::default(),
|
||||
|
@ -2,8 +2,12 @@ use std::path::Path;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use chrono::{DateTime, Utc};
|
||||
use comrak::{
|
||||
format_html,
|
||||
nodes::{AstNode, NodeValue},
|
||||
parse_document, Arena, Options,
|
||||
};
|
||||
use gray_matter::{engine::YAML, Matter};
|
||||
use markdown::{to_html_with_options, CompileOptions, Constructs, Options, ParseOptions};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Deserializer};
|
||||
use tokio::fs;
|
||||
|
||||
@ -35,26 +39,6 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>(
|
||||
// TODO Proper reasoning for an error
|
||||
.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 metadata = matter
|
||||
.parse_with_struct::<Metadata>(&file_contents)
|
||||
@ -63,6 +47,8 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>(
|
||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
||||
})?;
|
||||
|
||||
let body = parse_html(&metadata.content);
|
||||
|
||||
let filename = Path::new(path)
|
||||
.file_stem()
|
||||
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
@ -76,3 +62,51 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>(
|
||||
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 components;
|
||||
@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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
@ -544,64 +601,30 @@ video {
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
.m-3 {
|
||||
margin: 0.75rem;
|
||||
}
|
||||
|
||||
.m-1 {
|
||||
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 {
|
||||
margin-left: 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 {
|
||||
margin-left: 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 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
@ -661,15 +684,6 @@ video {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.p-2 {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.px-4 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
@ -680,38 +694,10 @@ video {
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.pr-2 {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.pr-3 {
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
|
||||
.text-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 {
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
@ -722,6 +708,25 @@ video {
|
||||
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-weight: 500;
|
||||
}
|
||||
@ -730,23 +735,10 @@ video {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-extrabold {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.text-gray-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(75 85 99 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-blue-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(29 78 216 / var(--tw-text-opacity));
|
||||
@ -757,21 +749,21 @@ video {
|
||||
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 {
|
||||
--tw-text-opacity: 1;
|
||||
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 {
|
||||
--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);
|
||||
|
@ -3,9 +3,19 @@
|
||||
{% block title %}{{title}}{% endblock %}
|
||||
|
||||
{% 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 #}
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user