Backend support for code highlighting

This commit is contained in:
Raphaël Thériault 2020-01-15 01:50:54 -05:00
parent 768082f180
commit 2fbd134743
10 changed files with 133 additions and 8 deletions

View File

View File

@ -0,0 +1,2 @@
ALTER TABLE texts
DROP COLUMN highlight;

View File

@ -0,0 +1,2 @@
ALTER TABLE texts
ADD highlight BOOLEAN NOT NULL DEFAULT false;

47
resources/highlight.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ title }}</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/styles/{{ theme }}.min.css"
/>
<style>
html,
body {
overflow: hidden;
}
html,
body,
pre {
margin: 0;
padding: 0;
}
code {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
max-width: 100%;
max-height: 100%;
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo,
monospace;
}
</style>
</head>
<body>
<pre><code>{{ contents }}</code></pre>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/highlight.min.js"
integrity="sha256-1zu+3BnLYV9LdiY85uXMzii3bdrkelyp37e0ZyTAQh0="
crossorigin="anonymous"
></script>
{{ languages }}
<script>
hljs.initHighlightingOnLoad();
</script>
</body>
</html>

View File

@ -231,7 +231,7 @@ for (const group in inputs) {
fetch(url, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ contents }),
body: JSON.stringify({ contents, highlight: true }),
})
.then((response) => {
status = response.status;

View File

@ -65,6 +65,8 @@ pub mod texts {
pub contents: String,
/// Creation date and time as a UNIX timestamp
pub created: i32,
/// Whether to enable code highlighting or not for that text
pub highlight: bool,
}
/// A new entry to the `texts` table
@ -75,5 +77,7 @@ pub mod texts {
pub id: i32,
/// Text contents
pub contents: &'a str,
/// Whether to enable code highlighting or not for that text
pub highlight: bool,
}
}

View File

@ -175,11 +175,17 @@ pub mod texts {
find!(texts, Text);
/// REPLACE a text entry
pub fn replace(r_id: i32, r_contents: &str, pool: Data<Pool>) -> QueryResult<Text> {
pub fn replace(
r_id: i32,
r_contents: &str,
r_highlight: bool,
pool: Data<Pool>,
) -> QueryResult<Text> {
let conn: &SqliteConnection = &pool.get().unwrap();
let new_text = NewText {
id: r_id,
contents: r_contents,
highlight: r_highlight,
};
diesel::replace_into(table)
.values(&new_text)

View File

@ -116,6 +116,13 @@ fn timestamp_to_last_modified(timestamp: i32) -> String {
datetime.format("%a, %d %b %Y %H:%M:%S GMT").to_string()
}
/// Escapes text to be inserted in a HTML element
fn escape_html(text: &str) -> String {
text.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
}
/// GET multiple entries
macro_rules! select {
($m:ident) => {
@ -196,6 +203,9 @@ lazy_static! {
};
}
static HIGHLIGHT_CONTENTS: &str = include_str!("../resources/highlight.html");
const HIGHLIGHT_LANGUAGE: &str = r#"<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/languages/{{ language }}.min.js"></script>"#;
/// Index page letting users upload via a UI
pub async fn index(
request: HttpRequest,
@ -406,6 +416,7 @@ pub mod links {
}
pub mod texts {
use crate::routes::escape_html;
use crate::{
queries::{self, SelectQuery},
routes::{
@ -413,6 +424,10 @@ pub mod texts {
},
Pool,
};
use crate::{
routes::{HIGHLIGHT_CONTENTS, HIGHLIGHT_LANGUAGE},
setup::Config,
};
use actix_identity::Identity;
use actix_web::{web, Error, HttpRequest, HttpResponse};
@ -420,14 +435,38 @@ pub mod texts {
/// GET a text entry and display it
pub async fn get(
config: web::Data<Config>,
path: web::Path<String>,
pool: web::Data<Pool>,
) -> Result<HttpResponse, Error> {
let id = parse_id(&path)?;
match web::block(move || queries::texts::find(id, pool)).await {
Ok(text) => Ok(HttpResponse::Ok()
.header("Last-Modified", timestamp_to_last_modified(text.created))
.body(text.contents)),
Ok(text) => {
let last_modified = timestamp_to_last_modified(text.created);
if text.highlight {
let languages: Vec<String> = config
.highlight
.languages
.iter()
.map(|l| HIGHLIGHT_LANGUAGE.replace("{{ language }}", l))
.collect();
let languages = languages.join("\n");
let contents = HIGHLIGHT_CONTENTS
.replace("{{ title }}", &path)
.replace("{{ theme }}", &config.highlight.theme)
.replace("{{ contents }}", &escape_html(&text.contents))
.replace("{{ languages }}", &languages);
Ok(HttpResponse::Ok()
.header("Last-Modified", last_modified)
.header("Content-Type", "text/html")
.body(contents))
} else {
Ok(HttpResponse::Ok()
.header("Last-Modified", last_modified)
.body(text.contents))
}
}
Err(e) => match_find_error(e),
}
}
@ -436,6 +475,7 @@ pub mod texts {
#[derive(Deserialize)]
pub struct PutText {
pub contents: String,
pub highlight: bool,
}
/// PUT a new text entry
@ -451,7 +491,8 @@ pub mod texts {
let id = parse_id(&path)?;
match_replace_result(
web::block(move || queries::texts::replace(id, &body.contents, pool)).await,
web::block(move || queries::texts::replace(id, &body.contents, body.highlight, pool))
.await,
)
}

View File

@ -19,6 +19,7 @@ table! {
id -> Integer,
contents -> Text,
created -> Integer,
highlight -> Bool,
}
}

View File

@ -88,6 +88,17 @@ pub struct Config {
pub files_dir: PathBuf,
/// Maximum allowed file size
pub max_filesize: usize,
/// Highlight.js configuration
pub highlight: HighlightConfig,
}
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(not(feature = "dev"), serde(default))]
pub struct HighlightConfig {
/// Theme to use
pub theme: String,
/// Additional languages to include
pub languages: Vec<String>,
}
#[cfg(not(feature = "dev"))]
@ -104,12 +115,22 @@ impl Default for Config {
let files_dir = get_data_dir().join("files");
let max_filesize = 10_000_000;
Config {
Self {
port,
database_url,
pool_size,
files_dir,
max_filesize,
highlight: HighlightConfig::default(),
}
}
}
impl Default for HighlightConfig {
fn default() -> Self {
Self {
theme: "github".to_owned(),
languages: vec!["rust".to_owned()],
}
}
}
@ -192,12 +213,13 @@ impl Config {
};
let max_filesize = parse_env!("MAX_FILESIZE");
Config {
Self {
port,
database_url,
pool_size,
files_dir,
max_filesize,
highlight: HighlightConfig::default(),
}
}
}