From 4305da1d0c4c8cf3a51979659a4cc55244658175 Mon Sep 17 00:00:00 2001 From: Michal Vanko Date: Wed, 11 Sep 2024 13:15:00 +0200 Subject: [PATCH] Code highlighting --- _posts/blog/dev-2019-08-09-ide-to copy.md | 14 +++- axum_server/Cargo.toml | 1 + axum_server/src/main.rs | 3 - axum_server/src/post_utils/post_parser.rs | 52 ++++++++++++--- axum_server/styles/input.css | 10 ++- axum_server/styles/output.css | 81 +++++++++++++++++++++-- 6 files changed, 138 insertions(+), 23 deletions(-) diff --git a/_posts/blog/dev-2019-08-09-ide-to copy.md b/_posts/blog/dev-2019-08-09-ide-to copy.md index fd9ca33..55f45a6 100644 --- a/_posts/blog/dev-2019-08-09-ide-to copy.md +++ b/_posts/blog/dev-2019-08-09-ide-to copy.md @@ -27,6 +27,16 @@ similis: caput te prodere disceditis: quinque: an et in, accipis divitior talia? Per deducit ademi, _sub_ qvem orbatura Pindo te manus verbaque **tuorum nati** vivere, an me detectique est. Decoram erat mediaque auras. +`sh ./oneliner.sh` + +This is a paragraph with a `reference to code` inside. +This is a paragraph with a `reference to code` inside. +This is a paragraph with a `reference to code` inside. +This is a paragraph with a `reference to code` inside. +This is a paragraph with a `reference to code` inside. +This is a paragraph with a `reference to code` inside. +This is a paragraph with a `reference to code` inside. + > Leti ensis mihi torquere fiducia me sunt nec prima caeli quaeras et coma > tinctis sibi; tua fidem aethera. Animosque ferret vultus puellari poteris > florilegae ignes crevisse ad pulvere recenti, luce male; neque nec! @@ -51,7 +61,7 @@ praemia pariter exaestuat fecerat. Haemonio quem: _in_ sibi spectans parmam, tetenderat filia ait quo calcitrat at vides, cui iuvenem rerum erat. Eminus flammas iamque. -```typescript +```ts var brouterVisualRecycle = netmaskExbibyteMac + download(twitter_serp_yobibyte, backlinkDirectBandwidth, hot) @@ -73,7 +83,7 @@ Erat Iunonis pennis lugubris, vixque nec quo tua lacrimarum nubila nobiscum. Ferrum inhaeret ille; operi in Theseus contingere fateri, mirabatur, consequar ullis, exuit fatemur humani iustis! -``` +```js var mpeg_reader_modifier = jfs; if (318464 >= association_thunderbolt_bar) { copyrightMemoryWep.skinHeaderEmoticon = diff --git a/axum_server/Cargo.toml b/axum_server/Cargo.toml index 5fa2a7c..e6905c5 100644 --- a/axum_server/Cargo.toml +++ b/axum_server/Cargo.toml @@ -23,6 +23,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } image = "0.25.2" anyhow = "1.0.86" rayon = "1.10.0" +syntect = "5.2.0" [build] rustflags = ["-Z", "threads=8"] diff --git a/axum_server/src/main.rs b/axum_server/src/main.rs index cd61a27..06cc051 100644 --- a/axum_server/src/main.rs +++ b/axum_server/src/main.rs @@ -48,9 +48,6 @@ async fn main() { axum::serve(listener, app).await.unwrap(); } -// TODO Display blog posts -// TODO Markdown notes -// TODO code snippets highlighting // TODO responsive design // TODO Colors // TODO go live pipeline diff --git a/axum_server/src/post_utils/post_parser.rs b/axum_server/src/post_utils/post_parser.rs index 71f0a45..9db7f6c 100644 --- a/axum_server/src/post_utils/post_parser.rs +++ b/axum_server/src/post_utils/post_parser.rs @@ -4,8 +4,9 @@ use axum::http::StatusCode; use chrono::{DateTime, Utc}; use gray_matter::{engine::YAML, Matter}; use image::image_dimensions; -use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd}; +use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd}; use serde::{de::DeserializeOwned, Deserialize, Deserializer}; +use syntect::{highlighting::ThemeSet, html::highlighted_html_for_string, parsing::SyntaxSet}; use tokio::fs; use tracing::debug; @@ -68,6 +69,11 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>( }) } +enum TextKind { + Text, + Code(String), +} + pub fn parse_html(markdown: &str, generate_images: bool) -> String { let mut options = Options::empty(); options.insert(Options::ENABLE_TABLES); @@ -77,6 +83,11 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String { options.insert(Options::ENABLE_SMART_PUNCTUATION); options.insert(Options::ENABLE_HEADING_ATTRIBUTES); + let mut text_kind = TextKind::Text; + let syntax_set = SyntaxSet::load_defaults_newlines(); + let theme_set = ThemeSet::load_defaults(); + let theme = theme_set.themes.get("InspiredGitHub").unwrap(); + let parser = Parser::new_ext(markdown, options).map(|event| match event { /* Parsing images considers `alt` attribute as inner `Text` event @@ -123,16 +134,6 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String { alt = title, src = dest_url, )); - // let picture_markup = format!( - // r#" - // {alt}"#, - // alt = title, - // src = dest_url, - // ); - debug!( "Image link_type: {:?} url: {} title: {} id: {}", link_type, dest_url, title, id @@ -147,8 +148,37 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String { .into(), ) } + Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(lang))) => { + text_kind = TextKind::Code(lang.to_string()); + Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(lang))) + } + Event::Text(text) => match &text_kind { + TextKind::Code(lang) => { + // TODO Check https://github.com/trishume/syntect/pull/535 for typescript support + let lang = if ["ts".to_string(), "typescript".to_string()].contains(lang) { + "javascript" + } else { + lang + }; + let syntax_reference = syntax_set + .find_syntax_by_token(lang) + .unwrap_or(syntax_set.find_syntax_plain_text()); + syntax_set.syntaxes().iter().for_each(|sr| { + debug!("{}", sr.name); + }); + let highlighted = + highlighted_html_for_string(&text, &syntax_set, syntax_reference, theme) + .unwrap(); + Event::Html(highlighted.into()) + } + _ => Event::Text(text), + }, Event::Start(_) => event, Event::End(TagEnd::Image) => Event::Html("".into()), + Event::End(TagEnd::CodeBlock) => { + text_kind = TextKind::Text; + Event::End(TagEnd::CodeBlock) + } _ => event, }); diff --git a/axum_server/styles/input.css b/axum_server/styles/input.css index 3b053dd..928a4ce 100644 --- a/axum_server/styles/input.css +++ b/axum_server/styles/input.css @@ -35,7 +35,7 @@ a { } table { - @apply text-sm mx-auto max-w-image table-auto border-collapse border-spacing-12 border border-gray-200 rounded md:text-base md:my-4 lg:text-xl lg:my-8; + @apply text-sm mx-auto my-4 max-w-image table-auto border-collapse border-spacing-12 border border-gray-200 rounded md:text-base lg:text-xl lg:my-8; } thead { @@ -62,6 +62,14 @@ a { @apply my-2 md:my-4 text-slate-600 max-w-note; } } + + :not(pre) code { + @apply text-pink-950 rounded border border-blue-300 px-1 py-0.5 bg-blue-100 text-sm md:text-base lg:text-xl; + } + + pre code pre { + @apply mx-2 rounded lg:mx-auto lg:text-lg shadow-sm lg:max-w-note; + } } .video-embed { diff --git a/axum_server/styles/output.css b/axum_server/styles/output.css index ed217f1..e7d924d 100644 --- a/axum_server/styles/output.css +++ b/axum_server/styles/output.css @@ -1228,6 +1228,10 @@ a { margin-left: auto; margin-right: auto; } + table { + margin-top: 1rem; + margin-bottom: 1rem; + } table { max-width: 70rem; } @@ -1256,12 +1260,6 @@ a { font-size: 0.875rem; line-height: 1.25rem; } - @media (min-width: 768px) { - table { - margin-top: 1rem; - margin-bottom: 1rem; - } - } @media (min-width: 768px) { table { font-size: 1rem; @@ -1371,6 +1369,77 @@ a { } } } + :not(pre) code { + border-radius: 0.25rem; + } + :not(pre) code { + border-width: 1px; + } + :not(pre) code { + --tw-border-opacity: 1; + border-color: rgb(147 197 253 / var(--tw-border-opacity)); + } + :not(pre) code { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); + } + :not(pre) code { + padding-left: 0.25rem; + padding-right: 0.25rem; + } + :not(pre) code { + padding-top: 0.125rem; + padding-bottom: 0.125rem; + } + :not(pre) code { + font-size: 0.875rem; + line-height: 1.25rem; + } + :not(pre) code { + --tw-text-opacity: 1; + color: rgb(80 7 36 / var(--tw-text-opacity)); + } + @media (min-width: 768px) { + :not(pre) code { + font-size: 1rem; + line-height: 1.5rem; + } + } + @media (min-width: 1024px) { + :not(pre) code { + font-size: 1.25rem; + line-height: 1.75rem; + } + } + pre code pre { + margin-left: 0.5rem; + margin-right: 0.5rem; + } + pre code pre { + border-radius: 0.25rem; + } + pre code pre { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + } + @media (min-width: 1024px) { + pre code pre { + margin-left: auto; + margin-right: auto; + } + } + @media (min-width: 1024px) { + pre code pre { + max-width: 60rem; + } + } + @media (min-width: 1024px) { + pre code pre { + font-size: 1.125rem; + line-height: 1.75rem; + } + } } .video-embed {