blog: use RegExp from js_sys to handle headings and code blocks
This commit is contained in:
parent
e1033ff0b3
commit
4bf676419d
82
src/blog.rs
82
src/blog.rs
|
@ -6,9 +6,12 @@
|
||||||
// unnecessary from KV.
|
// unnecessary from KV.
|
||||||
use crate::store;
|
use crate::store;
|
||||||
use crate::utils::*;
|
use crate::utils::*;
|
||||||
|
use js_sys::{JsString, RegExp};
|
||||||
use pulldown_cmark::*;
|
use pulldown_cmark::*;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use wasm_bindgen::closure::Closure;
|
||||||
|
|
||||||
// A list of the UUIDs of all published blog posts
|
// A list of the UUIDs of all published blog posts
|
||||||
// This should be SORTED with the newest posts at lower indices (closer to 0)
|
// This should be SORTED with the newest posts at lower indices (closer to 0)
|
||||||
|
@ -227,62 +230,33 @@ impl PostContentCache {
|
||||||
// Do some HTML-level transformations to the compiled result
|
// Do some HTML-level transformations to the compiled result
|
||||||
// Because the Markdown parser doesn't always allow us to do
|
// Because the Markdown parser doesn't always allow us to do
|
||||||
// everything, like adding `id` attributes to tags
|
// everything, like adding `id` attributes to tags
|
||||||
fn transform_html(html: &mut String) {
|
fn transform_html(html: String) -> String {
|
||||||
// Generate IDs for all headings in article
|
let js_html: JsString = html.into();
|
||||||
// This allows navigation via the hash part of the URL
|
|
||||||
let mut last_idx: usize = 0;
|
// Add `id="xxx"` to all headings for anchoring
|
||||||
loop {
|
// Replacing is done in a Closure in order to generate
|
||||||
let len = html.len();
|
// the proper anchor string for each heading
|
||||||
|
// This matches only a single line, which is good because
|
||||||
let idx = match (&html[last_idx..len]).find("<h") {
|
// we only want it to match a single heading tag
|
||||||
Some(i) => i + last_idx,
|
// If it matched multiple lines, then it may match the
|
||||||
None => break,
|
// ending tag of another heading.
|
||||||
};
|
let regex_heading = RegExp::new(r"<h(\d)>([^<]*)<\/h\1>", "ig");
|
||||||
|
let closure = Closure::wrap(Box::new(|_m: String, p1: String, p2: String| {
|
||||||
if idx >= len - 4 {
|
let anchor = filter_non_ascii_alphanumeric(
|
||||||
break;
|
&p2.to_lowercase()).replace(" ", "-");
|
||||||
}
|
format!("<h{} id=\"{}\">{}</h{}>", p1, anchor, p2, p1)
|
||||||
|
}) as Box<dyn Fn(String, String, String) -> String>);
|
||||||
last_idx = idx + 3;
|
let js_html = js_html.replace_by_pattern_with_function(®ex_heading, closure.as_ref().unchecked_ref());
|
||||||
|
|
||||||
if &html[idx + 3..idx + 4] != ">" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we have found a <h*> tag
|
|
||||||
let htype = &html[idx + 1..idx + 3];
|
|
||||||
|
|
||||||
// Find the closing tag for this one
|
|
||||||
// Since it's generated by the Markdown engine,
|
|
||||||
// we can assume it's correct HTML
|
|
||||||
let end_idx = match (&html[idx + 3..len]).find(&format!("</{}>", htype)) {
|
|
||||||
Some(i) => i + idx + 3,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
if end_idx >= len - 4 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let heading = &html[idx + 4..end_idx];
|
|
||||||
// We also assume there should be no other HTML tags in the heading
|
|
||||||
// This should be fine for me but I don't know about others
|
|
||||||
// However it's really tedious to do anything better...
|
|
||||||
let heading_anchor = filter_non_ascii_alphanumeric(
|
|
||||||
&heading.to_lowercase()).replace(" ", "-");
|
|
||||||
let inserted_id = format!(" id=\"{}\"", heading_anchor);
|
|
||||||
|
|
||||||
html.insert_str(idx + 3, &inserted_id);
|
|
||||||
|
|
||||||
last_idx = idx + 3 + inserted_id.len();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform all <pre><code> to <pre><code class="hljs">
|
// Transform all <pre><code> to <pre><code class="hljs">
|
||||||
// For syntax highlighting
|
// For syntax highlighting
|
||||||
// Note that though the Markdown engine may also insert classes,
|
// We don't match the end tag because it may span multiple lines
|
||||||
// because we insert our class before that class, ours will
|
// trying to match the end tag could result in accidentally matching
|
||||||
// always take precedence
|
// the end tag of another code block.
|
||||||
*html = html.replace("<pre><code", "<pre><code class=\"hljs\" ");
|
let regex_code = RegExp::new("<pre><code class=\"language-([^\"]*)\">", "ig");
|
||||||
|
let js_html = js_html.replace_by_pattern(®ex_code, "<pre><code class=\"hljs\">");
|
||||||
|
|
||||||
|
js_html.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only renders the content and spits out a cache object
|
// Only renders the content and spits out a cache object
|
||||||
|
@ -329,7 +303,7 @@ impl PostContentCache {
|
||||||
}
|
}
|
||||||
let mut html_output = String::new();
|
let mut html_output = String::new();
|
||||||
html::push_html(&mut html_output, parser.into_iter());
|
html::push_html(&mut html_output, parser.into_iter());
|
||||||
Self::transform_html(&mut html_output);
|
html_output = Self::transform_html(html_output);
|
||||||
PostContentCache {
|
PostContentCache {
|
||||||
uuid: post.uuid.clone(),
|
uuid: post.uuid.clone(),
|
||||||
version: CACHE_VERSION.to_owned(),
|
version: CACHE_VERSION.to_owned(),
|
||||||
|
|
Loading…
Reference in New Issue