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
This commit is contained in:
parent
26bb98b11e
commit
f519152465
9 changed files with 102 additions and 1 deletions
35
build.rs
35
build.rs
|
@ -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
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"
|
||||
}
|
||||
}
|
||||
|
|
34
src/blog.rs
34
src/blog.rs
|
@ -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());
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
};
|
||||
|
|
17
src/hljs.rs
Normal file
17
src/hljs.rs
Normal file
|
@ -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()
|
||||
}
|
7
src/hljs_tpl.rs
Normal file
7
src/hljs_tpl.rs
Normal file
|
@ -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>
|
1
theme/default/static/monokai-sublime.css
Symbolic link
1
theme/default/static/monokai-sublime.css
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../node_modules/highlight.js/styles/monokai-sublime.css
|
Loading…
Add table
Reference in a new issue