integrate hljs into backend to provide code highlighting

1. Use hljs and wasm_bindgen to parse code
2. Allow the final user to choose what language to include by generating
   inline_js in build.rs for #[wasm_bindgen] (a thin wrapper around the
   raw hljs to load whatever is needed by config.json statically rather
   than dynamically)
3. Integrate the monokai color scheme into our default theme
master
Peter Cai 3 years ago
parent 26bb98b11e
commit f519152465
No known key found for this signature in database
GPG Key ID: 71F5FB4E4F3FD54F

@ -7,6 +7,12 @@ fn main() {
// Load theme name from config.json and output code to load the theme via include_dir!
let config: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string("./config.json").unwrap()).unwrap();
generate_theme_loader(&config);
generate_hljs_loader(&config);
}
fn generate_theme_loader(config: &serde_json::Value) {
let theme_name = match config.get("theme") {
Some(name) => name,
None => panic!("Please define `theme` in `config.json`")
@ -16,4 +22,33 @@ fn main() {
let mut out_file = std::fs::File::create(out_path.join("load_theme.rs")).unwrap();
out_file.write_all(theme_load_code.as_bytes()).unwrap();
out_file.sync_data().unwrap();
}
fn generate_hljs_loader(config: &serde_json::Value) {
println!("cargo:rerun-if-changed=src/hljs_tpl.rs");
let highlight_lang = match config.get("hljs") {
Some(val) => val,
None => panic!("Please specify what language for hljs to support in `config.json` with `hljs`")
};
if !highlight_lang.is_array() {
panic!("`hljs` is not an array");
}
let highlight_lang = highlight_lang.as_array().unwrap().into_iter().map(|lang| {
let lang = lang.as_str().unwrap();
// Require only the needed language definition files
format!("hljs.registerLanguage('{}', require('highlight.js/lib/languages/{}'));\n", lang, lang)
}).collect::<String>();
let js_code = format!(
"const hljs = require(\\\"highlight.js/lib/highlight.js\\\");\n{}module.exports = hljs;",
highlight_lang);
let rs_code = std::fs::read_to_string("./src/hljs_tpl.rs").unwrap();
let rs_code = format!("#[wasm_bindgen(inline_js = \"{}\")]\n{}", js_code, rs_code);
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
let mut out_file = std::fs::File::create(out_path.join("load_hljs.rs")).unwrap();
out_file.write_all(rs_code.as_bytes()).unwrap();
out_file.sync_data().unwrap();
}

6
package-lock.json generated

@ -1982,6 +1982,12 @@
"minimalistic-assert": "^1.0.1"
}
},
"highlight.js": {
"version": "9.18.1",
"resolved": "https://registry.npm.taobao.org/highlight.js/download/highlight.js-9.18.1.tgz",
"integrity": "sha1-7SGqAB/mJSuxCj121HVzxlOf4Tw=",
"dev": true
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/hmac-drbg/download/hmac-drbg-1.0.1.tgz",

@ -18,6 +18,7 @@
"homepage": "https://github.com/PeterCxy/paprika#readme",
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "^1.2.0",
"highlight.js": "^9.18.1",
"webpack": "^4.42.1"
}
}

@ -120,7 +120,7 @@ impl Post {
// library updates. Updaing this value invalidates all
// existing cache and they will be recompiled when someone
// visits.
const CACHE_VERSION: &'static str = "0009";
const CACHE_VERSION: &'static str = "0015";
// The prefix path used for caching remote images
pub const IMG_CACHE_PREFIX: &'static str = "/imgcache/";
@ -267,6 +267,13 @@ impl PostContentCache {
last_idx = idx + 3 + inserted_id.len();
}
// Transform all <pre><code> to <pre><code class="hljs">
// For syntax highlighting
// Note that though the Markdown engine may also insert classes,
// because we insert our class before that class, ours will
// always take precedence
*html = html.replace("<pre><code", "<pre><code class=\"hljs\" ");
}
// Only renders the content and spits out a cache object
@ -278,10 +285,35 @@ impl PostContentCache {
// because we need to asynchronously transform the events
// which could not be done through mapping on iterators
let mut parser: Vec<Event> = Parser::new_ext(&post.content, Options::all()).collect();
let mut in_code_block = false;
let mut code_block_lang = "";
for ev in parser.iter_mut() {
match ev {
Event::Start(Tag::CodeBlock(block)) => {
in_code_block = true;
match block {
CodeBlockKind::Fenced(lang) => code_block_lang = lang,
CodeBlockKind::Indented => code_block_lang = ""
}
},
Event::End(Tag::CodeBlock(_)) => {
in_code_block = false;
code_block_lang = "";
},
Event::Start(tag) | Event::End(tag) => {
Self::transform_tag(tag).await;
},
Event::Text(text) => {
if in_code_block {
let highlighted = if code_block_lang != "" {
crate::hljs::highlight(code_block_lang, text)
} else {
crate::hljs::highlight_auto(text)
};
*ev = Event::Html(
highlighted.into());
}
}
_ => ()
};

@ -0,0 +1,17 @@
// Simple bindings for Highlight.js
// We don't have something equivalent in Rust
// and I don't really want to run these on client
use js_sys::Reflect;
use wasm_bindgen::prelude::*;
include!(concat!(env!("OUT_DIR"), "/load_hljs.rs"));
pub fn highlight_auto(code: &str) -> String {
Reflect::get(&hljs_highlight_auto(code), &"value".into())
.unwrap().as_string().unwrap()
}
pub fn highlight(lang: &str, code: &str) -> String {
Reflect::get(&hljs_highlight(lang, code), &"value".into())
.unwrap().as_string().unwrap()
}

@ -0,0 +1,7 @@
// Will be loaded by build.rs and add #[wasm_bindgen(inline_js = "...")] here
extern "C" {
#[wasm_bindgen(js_name = "highlightAuto")]
fn hljs_highlight_auto(code: &str) -> JsValue;
#[wasm_bindgen(js_name = "highlight")]
fn hljs_highlight(lang: &str, code: &str) -> JsValue;
}

@ -13,6 +13,7 @@ extern crate serde_json;
mod utils;
mod router;
mod store;
mod hljs;
mod blog;
mod sn;
mod render;

@ -2,6 +2,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ #if title }}{{ title }} - {{ blog.title }}{{ else }}{{ blog.title }}{{ /if }}</title>
<link rel="stylesheet" href="/static/monokai-sublime.css?ver={{ build_num }}" />
<link rel="stylesheet" href="/static/style.css?ver={{ build_num }}" />
<link rel="alternate" type="application/rss+xml" title="RSS Feed for {{ blog.title }}" href="/feed.xml" />
</head>

@ -0,0 +1 @@
../../../node_modules/highlight.js/styles/monokai-sublime.css
Loading…
Cancel
Save