diff --git a/migrations/.gitkeep b/migrations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/migrations/2020-01-15-052544_add_highlight/down.sql b/migrations/2020-01-15-052544_add_highlight/down.sql new file mode 100644 index 0000000..240553b --- /dev/null +++ b/migrations/2020-01-15-052544_add_highlight/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE texts +DROP COLUMN highlight; diff --git a/migrations/2020-01-15-052544_add_highlight/up.sql b/migrations/2020-01-15-052544_add_highlight/up.sql new file mode 100644 index 0000000..bbfb5a2 --- /dev/null +++ b/migrations/2020-01-15-052544_add_highlight/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE texts +ADD highlight BOOLEAN NOT NULL DEFAULT false; diff --git a/resources/highlight.html b/resources/highlight.html new file mode 100644 index 0000000..ca30fab --- /dev/null +++ b/resources/highlight.html @@ -0,0 +1,47 @@ + + + + + + {{ title }} + + + + +
{{ contents }}
+ + {{ languages }} + + + diff --git a/resources/script.js b/resources/script.js index 664a9ab..c6c5907 100644 --- a/resources/script.js +++ b/resources/script.js @@ -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; diff --git a/src/models.rs b/src/models.rs index 4b41386..19afe6d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -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, } } diff --git a/src/queries.rs b/src/queries.rs index bd05257..e56c9e5 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -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) -> QueryResult { + pub fn replace( + r_id: i32, + r_contents: &str, + r_highlight: bool, + pool: Data, + ) -> QueryResult { 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) diff --git a/src/routes.rs b/src/routes.rs index 362bd21..2578990 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -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("&", "&") + .replace("<", "<") + .replace(">", ">") +} + /// 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#""#; + /// 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, path: web::Path, pool: web::Data, ) -> Result { 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 = 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, ) } diff --git a/src/schema.rs b/src/schema.rs index 43cb46e..63ccef8 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -19,6 +19,7 @@ table! { id -> Integer, contents -> Text, created -> Integer, + highlight -> Bool, } } diff --git a/src/setup.rs b/src/setup.rs index f3ab217..95fe2c6 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -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, } #[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(), } } }