accomodate plugins system (prism, mermaid)
This commit is contained in:
parent
1265305bac
commit
a8e8476c9c
9 changed files with 541 additions and 215 deletions
35
src/formatter.rs
Normal file
35
src/formatter.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use handlebars::{Handlebars, JsonRender};
|
||||
|
||||
pub fn new<'r>() -> Handlebars<'r> {
|
||||
let mut handlebars = Handlebars::new();
|
||||
handlebars.register_helper("format_url", Box::new(format_helper));
|
||||
|
||||
handlebars
|
||||
}
|
||||
|
||||
fn format_helper(
|
||||
h: &handlebars::Helper,
|
||||
_: &Handlebars,
|
||||
_: &handlebars::Context,
|
||||
_: &mut handlebars::RenderContext,
|
||||
out: &mut dyn handlebars::Output,
|
||||
) -> Result<(), handlebars::RenderError> {
|
||||
let prefix_val = h.param(0).ok_or(handlebars::RenderError::new(
|
||||
"Param 0 is required for format helper.",
|
||||
))?;
|
||||
|
||||
let uri_val = h.param(1).ok_or(handlebars::RenderError::new(
|
||||
"Param 1 is required for format helper.",
|
||||
))?;
|
||||
|
||||
let prefix = prefix_val.value().render();
|
||||
let uri = uri_val.value().render();
|
||||
|
||||
let rendered = match uri.starts_with("/") {
|
||||
true => format!("{}{}", prefix, uri),
|
||||
false => uri,
|
||||
};
|
||||
|
||||
out.write(rendered.as_ref())?;
|
||||
Ok(())
|
||||
}
|
12
src/lib.rs
12
src/lib.rs
|
@ -12,18 +12,18 @@ mod api_generated;
|
|||
use crate::api_generated::api::{finish_entry_buffer, get_root_as_entry, Entry, EntryArgs};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! load_static_resources {
|
||||
( $( $x:expr ),* ) => {
|
||||
macro_rules! load_static_resources(
|
||||
{ $($key:expr => $value:expr),+ } => {
|
||||
{
|
||||
let mut resources: HashMap<&str, &[u8]> = HashMap::new();
|
||||
let mut resources: HashMap<&'static str, &'static [u8]> = HashMap::new();
|
||||
$(
|
||||
resources.insert($x, include_bytes!($x));
|
||||
resources.insert($key, include_bytes!($value));
|
||||
)*
|
||||
|
||||
resources
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
pub fn compaction_filter_expired_entries(
|
||||
_: u32,
|
||||
|
|
158
src/main.rs
158
src/main.rs
|
@ -5,23 +5,27 @@
|
|||
extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate structopt_derive;
|
||||
extern crate chrono;
|
||||
extern crate flatbuffers;
|
||||
extern crate handlebars;
|
||||
extern crate nanoid;
|
||||
extern crate num_cpus;
|
||||
extern crate regex;
|
||||
extern crate speculate;
|
||||
extern crate structopt;
|
||||
extern crate chrono;
|
||||
extern crate regex;
|
||||
|
||||
mod formatter;
|
||||
|
||||
#[macro_use]
|
||||
mod lib;
|
||||
use lib::{compaction_filter_expired_entries, get_entry_data, get_extension, new_entry};
|
||||
|
||||
mod plugins;
|
||||
use plugins::plugin::{Plugin, PluginManager};
|
||||
|
||||
mod api_generated;
|
||||
use api_generated::api::get_root_as_entry;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::BTreeMap;
|
||||
use std::io;
|
||||
use std::io::Cursor;
|
||||
|
@ -32,15 +36,15 @@ use rocket::http::{ContentType, Status};
|
|||
use rocket::response::{Redirect, Response};
|
||||
use rocket::{Data, State};
|
||||
|
||||
use humantime::parse_duration;
|
||||
use handlebars::Handlebars;
|
||||
use rocksdb::{Options, DB};
|
||||
use structopt::StructOpt;
|
||||
use chrono::NaiveDateTime;
|
||||
use speculate::speculate;
|
||||
use serde_json::json;
|
||||
use handlebars::Handlebars;
|
||||
use humantime::parse_duration;
|
||||
use nanoid::nanoid;
|
||||
use regex::Regex;
|
||||
use rocksdb::{Options, DB};
|
||||
use serde_json::json;
|
||||
use speculate::speculate;
|
||||
use structopt::StructOpt;
|
||||
|
||||
speculate! {
|
||||
use super::rocket;
|
||||
|
@ -247,10 +251,7 @@ struct PastebinConfig {
|
|||
)]
|
||||
tls_key: Option<String>,
|
||||
|
||||
#[structopt(
|
||||
long = "uri",
|
||||
help = "Override default URI",
|
||||
)]
|
||||
#[structopt(long = "uri", help = "Override default URI")]
|
||||
uri: Option<String>,
|
||||
|
||||
#[structopt(
|
||||
|
@ -267,11 +268,7 @@ struct PastebinConfig {
|
|||
)]
|
||||
slug_charset: String,
|
||||
|
||||
#[structopt(
|
||||
long = "slug-len",
|
||||
help = "Length of URL slug",
|
||||
default_value = "21"
|
||||
)]
|
||||
#[structopt(long = "slug-len", help = "Length of URL slug", default_value = "21")]
|
||||
slug_len: usize,
|
||||
|
||||
#[structopt(
|
||||
|
@ -280,6 +277,13 @@ struct PastebinConfig {
|
|||
default_value = "5 minutes, 10 minutes, 1 hour, 1 day, 1 week, 1 month, 1 year, Never"
|
||||
)]
|
||||
ui_expiry_times: Vec<String>,
|
||||
|
||||
#[structopt(
|
||||
long = "plugins",
|
||||
help = "Enable additional functionalities (ie. prism, mermaid)",
|
||||
default_value = "prism"
|
||||
)]
|
||||
plugins: Vec<String>,
|
||||
}
|
||||
|
||||
fn get_url(cfg: &PastebinConfig) -> String {
|
||||
|
@ -307,6 +311,7 @@ fn get_url(cfg: &PastebinConfig) -> String {
|
|||
}
|
||||
|
||||
fn get_error_response<'r>(
|
||||
handlebars: &Handlebars<'r>,
|
||||
uri_prefix: String,
|
||||
html: String,
|
||||
status: Status,
|
||||
|
@ -317,9 +322,7 @@ fn get_error_response<'r>(
|
|||
"uri_prefix": uri_prefix,
|
||||
});
|
||||
|
||||
let content = Handlebars::new()
|
||||
.render_template(html.as_str(), &map)
|
||||
.unwrap();
|
||||
let content = handlebars.render_template(html.as_str(), &map).unwrap();
|
||||
|
||||
Response::build()
|
||||
.status(status)
|
||||
|
@ -367,12 +370,14 @@ fn get<'r>(
|
|||
id: String,
|
||||
lang: Option<String>,
|
||||
state: State<'r, DB>,
|
||||
resources: State<'r, HashMap<&str, &[u8]>>,
|
||||
handlebars: State<'r, Handlebars>,
|
||||
plugin_manager: State<PluginManager>,
|
||||
ui_expiry_times: State<'r, BTreeMap<String, u64>>,
|
||||
ui_expiry_default: State<'r, String>,
|
||||
cfg: State<PastebinConfig>,
|
||||
) -> Response<'r> {
|
||||
let html = String::from_utf8_lossy(resources.get("../static/index.html").unwrap()).to_string();
|
||||
let resources = plugin_manager.static_resources();
|
||||
let html = String::from_utf8_lossy(resources.get("/static/index.html").unwrap()).to_string();
|
||||
|
||||
// handle missing entry
|
||||
let root = match get_entry_data(&id, &state) {
|
||||
|
@ -386,12 +391,13 @@ fn get<'r>(
|
|||
let map = json!({
|
||||
"version": VERSION,
|
||||
"is_error": "true",
|
||||
"uri_prefix": cfg.uri_prefix
|
||||
"uri_prefix": cfg.uri_prefix,
|
||||
"js_imports": plugin_manager.js_imports(),
|
||||
"css_imports": plugin_manager.css_imports(),
|
||||
"js_init": plugin_manager.js_init(),
|
||||
});
|
||||
|
||||
let content = Handlebars::new()
|
||||
.render_template(html.as_str(), &map)
|
||||
.unwrap();
|
||||
let content = handlebars.render_template(html.as_str(), &map).unwrap();
|
||||
|
||||
return Response::build()
|
||||
.status(err_kind)
|
||||
|
@ -415,6 +421,9 @@ fn get<'r>(
|
|||
"uri_prefix": cfg.uri_prefix,
|
||||
"ui_expiry_times": ui_expiry_times.inner(),
|
||||
"ui_expiry_default": ui_expiry_default.inner(),
|
||||
"js_imports": plugin_manager.js_imports(),
|
||||
"css_imports": plugin_manager.css_imports(),
|
||||
"js_init": plugin_manager.js_init(),
|
||||
});
|
||||
|
||||
if entry.burn() {
|
||||
|
@ -423,7 +432,8 @@ fn get<'r>(
|
|||
map["is_burned"] = json!("true");
|
||||
map["glyph"] = json!("fa fa-fire");
|
||||
} else if entry.expiry_timestamp() != 0 {
|
||||
let time = NaiveDateTime::from_timestamp(entry.expiry_timestamp() as i64, 0).format("%Y-%m-%d %H:%M:%S");
|
||||
let time = NaiveDateTime::from_timestamp(entry.expiry_timestamp() as i64, 0)
|
||||
.format("%Y-%m-%d %H:%M:%S");
|
||||
map["msg"] = json!(format!("This paste will expire on {}.", time));
|
||||
map["level"] = json!("info");
|
||||
map["glyph"] = json!("far fa-clock");
|
||||
|
@ -433,9 +443,7 @@ fn get<'r>(
|
|||
map["is_encrypted"] = json!("true");
|
||||
}
|
||||
|
||||
let content = Handlebars::new()
|
||||
.render_template(html.as_str(), &map)
|
||||
.unwrap();
|
||||
let content = handlebars.render_template(html.as_str(), &map).unwrap();
|
||||
|
||||
Response::build()
|
||||
.status(Status::Ok)
|
||||
|
@ -447,8 +455,9 @@ fn get<'r>(
|
|||
#[get("/new?<id>&<level>&<msg>&<glyph>&<url>")]
|
||||
fn get_new<'r>(
|
||||
state: State<'r, DB>,
|
||||
handlebars: State<Handlebars>,
|
||||
cfg: State<PastebinConfig>,
|
||||
resources: State<'r, HashMap<&str, &[u8]>>,
|
||||
plugin_manager: State<PluginManager>,
|
||||
ui_expiry_times: State<'r, BTreeMap<String, u64>>,
|
||||
ui_expiry_default: State<'r, String>,
|
||||
id: Option<String>,
|
||||
|
@ -457,7 +466,8 @@ fn get_new<'r>(
|
|||
msg: Option<String>,
|
||||
url: Option<String>,
|
||||
) -> Response<'r> {
|
||||
let html = String::from_utf8_lossy(resources.get("../static/index.html").unwrap()).to_string();
|
||||
let resources = plugin_manager.static_resources();
|
||||
let html = String::from_utf8_lossy(resources.get("/static/index.html").unwrap()).to_string();
|
||||
let msg = msg.unwrap_or_else(|| String::from(""));
|
||||
let level = level.unwrap_or_else(|| String::from("secondary"));
|
||||
let glyph = glyph.unwrap_or_else(|| String::from(""));
|
||||
|
@ -474,6 +484,9 @@ fn get_new<'r>(
|
|||
"uri_prefix": cfg.uri_prefix,
|
||||
"ui_expiry_times": ui_expiry_times.inner(),
|
||||
"ui_expiry_default": ui_expiry_default.inner(),
|
||||
"js_imports": plugin_manager.js_imports(),
|
||||
"css_imports": plugin_manager.css_imports(),
|
||||
"js_init": plugin_manager.js_init(),
|
||||
});
|
||||
|
||||
if let Some(id) = id {
|
||||
|
@ -484,14 +497,10 @@ fn get_new<'r>(
|
|||
map["is_encrypted"] = json!("true");
|
||||
}
|
||||
|
||||
map["pastebin_code"] = json!(
|
||||
std::str::from_utf8(entry.data().unwrap()).unwrap()
|
||||
);
|
||||
map["pastebin_code"] = json!(std::str::from_utf8(entry.data().unwrap()).unwrap());
|
||||
}
|
||||
|
||||
let content = Handlebars::new()
|
||||
.render_template(html.as_str(), &map)
|
||||
.unwrap();
|
||||
let content = handlebars.render_template(html.as_str(), &map).unwrap();
|
||||
|
||||
Response::build()
|
||||
.status(Status::Ok)
|
||||
|
@ -538,18 +547,26 @@ fn get_binary(id: String, state: State<DB>) -> Response {
|
|||
#[get("/static/<resource>")]
|
||||
fn get_static<'r>(
|
||||
resource: String,
|
||||
resources: State<'r, HashMap<&str, &[u8]>>,
|
||||
handlebars: State<Handlebars>,
|
||||
plugin_manager: State<PluginManager>,
|
||||
cfg: State<PastebinConfig>,
|
||||
) -> Response<'r> {
|
||||
let pth = format!("../static/{}", resource);
|
||||
let resources = plugin_manager.static_resources();
|
||||
let pth = format!("/static/{}", resource);
|
||||
let ext = get_extension(resource.as_str()).replace(".", "");
|
||||
|
||||
let content = match resources.get(pth.as_str()) {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
let html =
|
||||
String::from_utf8_lossy(resources.get("../static/index.html").unwrap()).to_string();
|
||||
return get_error_response(cfg.uri_prefix.clone(), html, Status::NotFound);
|
||||
String::from_utf8_lossy(resources.get("/static/index.html").unwrap()).to_string();
|
||||
|
||||
return get_error_response(
|
||||
handlebars.inner(),
|
||||
cfg.uri_prefix.clone(),
|
||||
html,
|
||||
Status::NotFound,
|
||||
);
|
||||
}
|
||||
};
|
||||
let content_type = ContentType::from_extension(ext.as_str()).unwrap();
|
||||
|
@ -563,7 +580,12 @@ fn get_static<'r>(
|
|||
|
||||
#[get("/")]
|
||||
fn index(cfg: State<PastebinConfig>) -> Redirect {
|
||||
let url = String::from(Path::new(cfg.uri_prefix.as_str()).join("new").to_str().unwrap());
|
||||
let url = String::from(
|
||||
Path::new(cfg.uri_prefix.as_str())
|
||||
.join("new")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
Redirect::to(url)
|
||||
}
|
||||
|
@ -610,7 +632,7 @@ fn rocket(pastebin_config: PastebinConfig) -> rocket::Rocket {
|
|||
let mut alphabet: Vec<char> = vec![];
|
||||
|
||||
// match all printable ASCII characters
|
||||
for i in 0x20 .. 0x7e as u8 {
|
||||
for i in 0x20..0x7e as u8 {
|
||||
let c = i as char;
|
||||
|
||||
if re.is_match(c.encode_utf8(&mut tmp)) {
|
||||
|
@ -629,17 +651,26 @@ fn rocket(pastebin_config: PastebinConfig) -> rocket::Rocket {
|
|||
if sub_elem.trim().to_lowercase() == "never" {
|
||||
all.insert(sub_elem.trim().to_string(), 0);
|
||||
} else {
|
||||
all.insert(sub_elem.trim().to_string(), parse_duration(sub_elem).unwrap().as_secs());
|
||||
all.insert(
|
||||
sub_elem.trim().to_string(),
|
||||
parse_duration(sub_elem).unwrap().as_secs(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
all
|
||||
};
|
||||
|
||||
let ui_expiry_default: String = ui_expiry_times
|
||||
.iter()
|
||||
.filter_map(|(key, &val)| if val == pastebin_config.ttl { Some(key.clone()) } else { None })
|
||||
.filter_map(|(key, &val)| {
|
||||
if val == pastebin_config.ttl {
|
||||
Some(key.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if ui_expiry_default.is_empty() {
|
||||
|
@ -654,26 +685,35 @@ fn rocket(pastebin_config: PastebinConfig) -> rocket::Rocket {
|
|||
panic!("selected slug alphabet is empty, please check if slug_charset is a valid regex");
|
||||
}
|
||||
|
||||
let plugins: Vec<Box<dyn Plugin>> = pastebin_config
|
||||
.plugins
|
||||
.iter()
|
||||
.map(|t| match t.as_str() {
|
||||
"prism" => Box::new(plugins::prism::new()),
|
||||
"mermaid" => Box::new(plugins::mermaid::new()),
|
||||
_ => panic!("unknown plugin provided"),
|
||||
})
|
||||
.map(|x| x as Box<dyn plugins::plugin::Plugin>)
|
||||
.collect();
|
||||
|
||||
let plugin_manager = plugins::new(plugins);
|
||||
let uri_prefix = pastebin_config.uri_prefix.clone();
|
||||
let resources = load_static_resources!(
|
||||
"../static/index.html",
|
||||
"../static/custom.js",
|
||||
"../static/custom.css",
|
||||
"../static/prism.js",
|
||||
"../static/prism.css",
|
||||
"../static/favicon.ico"
|
||||
);
|
||||
|
||||
// run rocket
|
||||
rocket::custom(rocket_config)
|
||||
.manage(pastebin_config)
|
||||
.manage(db)
|
||||
.manage(resources)
|
||||
.manage(formatter::new())
|
||||
.manage(plugin_manager)
|
||||
.manage(alphabet)
|
||||
.manage(ui_expiry_times)
|
||||
.manage(ui_expiry_default)
|
||||
.mount(
|
||||
if uri_prefix == "" { "/" } else { uri_prefix.as_str() },
|
||||
if uri_prefix == "" {
|
||||
"/"
|
||||
} else {
|
||||
uri_prefix.as_str()
|
||||
},
|
||||
routes![index, create, remove, get, get_new, get_raw, get_binary, get_static],
|
||||
)
|
||||
}
|
||||
|
|
37
src/plugins.rs
Normal file
37
src/plugins.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
pub mod mermaid;
|
||||
pub mod plugin;
|
||||
pub mod prism;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn new<'r>(plugins: Vec<Box<dyn plugin::Plugin<'r>>>) -> plugin::PluginManager<'r> {
|
||||
let base_static_resources = load_static_resources!(
|
||||
"/static/index.html" => "../static/index.html",
|
||||
"/static/custom.js" => "../static/custom.js",
|
||||
"/static/custom.css" => "../static/custom.css",
|
||||
"/static/favicon.ico" => "../static/favicon.ico"
|
||||
);
|
||||
|
||||
let base_css_imports = vec![
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css",
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css",
|
||||
"/static/custom.css",
|
||||
];
|
||||
|
||||
let base_js_imports = vec![
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js",
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js",
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js",
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js",
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js",
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-notify/0.2.0/js/bootstrap-notify.min.js",
|
||||
"/static/custom.js",
|
||||
];
|
||||
|
||||
plugin::PluginManager::build()
|
||||
.plugins(plugins)
|
||||
.static_resources(base_static_resources)
|
||||
.css_imports(base_css_imports)
|
||||
.js_imports(base_js_imports)
|
||||
.finalize()
|
||||
}
|
12
src/plugins/mermaid.rs
Normal file
12
src/plugins/mermaid.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::plugins::plugin::PastebinPlugin;
|
||||
|
||||
pub fn new<'r>() -> PastebinPlugin<'r> {
|
||||
PastebinPlugin {
|
||||
css_imports: vec![],
|
||||
js_imports: vec!["https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.8.2/mermaid.min.js"],
|
||||
js_init: Some("mermaid.init(undefined, '.language-mermaid');"),
|
||||
static_resources: HashMap::new(),
|
||||
}
|
||||
}
|
181
src/plugins/plugin.rs
Normal file
181
src/plugins/plugin.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
pub trait Plugin<'r>: Sync + Send {
|
||||
fn css_imports(&self) -> &Vec<&'r str>;
|
||||
fn js_imports(&self) -> &Vec<&'r str>;
|
||||
fn js_init(&self) -> Option<&'r str>;
|
||||
fn static_resources(&self) -> &HashMap<&'r str, &'r [u8]>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PastebinPlugin<'r> {
|
||||
pub css_imports: Vec<&'r str>,
|
||||
pub js_imports: Vec<&'r str>,
|
||||
pub js_init: Option<&'r str>,
|
||||
pub static_resources: HashMap<&'r str, &'r [u8]>,
|
||||
}
|
||||
|
||||
impl<'r> Plugin<'r> for PastebinPlugin<'r> {
|
||||
fn css_imports(&self) -> &Vec<&'r str> {
|
||||
&self.css_imports
|
||||
}
|
||||
|
||||
fn js_imports(&self) -> &Vec<&'r str> {
|
||||
&self.js_imports
|
||||
}
|
||||
|
||||
fn js_init(&self) -> Option<&'r str> {
|
||||
self.js_init
|
||||
}
|
||||
|
||||
fn static_resources(&self) -> &HashMap<&'r str, &'r [u8]> {
|
||||
&self.static_resources
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PluginManagerBuilder<'r> {
|
||||
manager: PluginManager<'r>,
|
||||
}
|
||||
|
||||
impl<'r> PluginManagerBuilder<'r> {
|
||||
pub fn plugins(&mut self, plugins: Vec<Box<dyn Plugin<'r>>>) -> &mut PluginManagerBuilder<'r> {
|
||||
self.manager.set_plugins(plugins);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn css_imports(&mut self, css_imports: Vec<&'r str>) -> &mut PluginManagerBuilder<'r> {
|
||||
self.manager.set_css_imports(css_imports);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn js_imports(&mut self, js_imports: Vec<&'r str>) -> &mut PluginManagerBuilder<'r> {
|
||||
self.manager.set_js_imports(js_imports);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn static_resources(
|
||||
&mut self,
|
||||
static_resources: HashMap<&'r str, &'r [u8]>,
|
||||
) -> &mut PluginManagerBuilder<'r> {
|
||||
self.manager.set_static_resources(static_resources);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self) -> PluginManager<'r> {
|
||||
self.manager.build_css_imports();
|
||||
self.manager.build_js_imports();
|
||||
self.manager.build_js_init();
|
||||
self.manager.build_static_resources();
|
||||
|
||||
std::mem::replace(&mut self.manager, PluginManager::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PluginManager<'r> {
|
||||
// plugins are used to build up the static members of the struct, for instance:
|
||||
// * js_imports (ie. "{{uri_prefix}}/static/prism.js")
|
||||
// * static_resources (files under static/ directory - compiled with the binary)
|
||||
plugins: Vec<Box<dyn Plugin<'r>>>,
|
||||
|
||||
css_imports: Vec<&'r str>,
|
||||
js_imports: Vec<&'r str>,
|
||||
js_init: Vec<&'r str>,
|
||||
static_resources: HashMap<&'r str, &'r [u8]>,
|
||||
}
|
||||
|
||||
impl<'r> PluginManager<'r> {
|
||||
pub fn new() -> PluginManager<'r> {
|
||||
PluginManager {
|
||||
plugins: vec![],
|
||||
css_imports: vec![],
|
||||
js_imports: vec![],
|
||||
js_init: vec![],
|
||||
static_resources: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build() -> PluginManagerBuilder<'r> {
|
||||
PluginManagerBuilder {
|
||||
manager: PluginManager::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_plugins(&mut self, plugins: Vec<Box<dyn Plugin<'r>>>) {
|
||||
self.plugins = plugins;
|
||||
}
|
||||
|
||||
pub fn set_css_imports(&mut self, css_imports: Vec<&'r str>) {
|
||||
self.css_imports = css_imports;
|
||||
}
|
||||
|
||||
pub fn css_imports(&self) -> Vec<&'r str> {
|
||||
self.css_imports.clone()
|
||||
}
|
||||
|
||||
pub fn set_js_imports(&mut self, js_imports: Vec<&'r str>) {
|
||||
self.js_imports = js_imports;
|
||||
}
|
||||
|
||||
pub fn js_imports(&self) -> Vec<&'r str> {
|
||||
self.js_imports.clone()
|
||||
}
|
||||
|
||||
pub fn set_js_init(&mut self, js_init: Vec<&'r str>) {
|
||||
self.js_init = js_init;
|
||||
}
|
||||
|
||||
pub fn js_init(&self) -> Vec<&'r str> {
|
||||
self.js_init.clone()
|
||||
}
|
||||
|
||||
pub fn set_static_resources(&mut self, static_resources: HashMap<&'r str, &'r [u8]>) {
|
||||
self.static_resources = static_resources;
|
||||
}
|
||||
|
||||
pub fn static_resources(&self) -> HashMap<&'r str, &'r [u8]> {
|
||||
self.static_resources.clone()
|
||||
}
|
||||
|
||||
fn build_css_imports(&mut self) {
|
||||
self.set_css_imports(
|
||||
self.plugins
|
||||
.iter()
|
||||
.flat_map(|p| p.css_imports().into_iter())
|
||||
.chain((&self.css_imports).into_iter())
|
||||
.map(|&val| val)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_js_imports(&mut self) {
|
||||
self.set_js_imports(
|
||||
self.plugins
|
||||
.iter()
|
||||
.flat_map(|p| p.js_imports().into_iter())
|
||||
.chain((&self.js_imports).into_iter())
|
||||
.map(|&val| val)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_js_init(&mut self) {
|
||||
self.set_js_init(
|
||||
self.plugins
|
||||
.iter()
|
||||
.flat_map(|p| p.js_init().into_iter())
|
||||
.chain((&self.js_init).into_iter().map(|&val| val))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_static_resources(&mut self) {
|
||||
self.set_static_resources(
|
||||
self.plugins
|
||||
.iter()
|
||||
.flat_map(|p| p.static_resources().into_iter())
|
||||
.chain((&self.static_resources).into_iter())
|
||||
.map(|(&key, &val)| (key, val))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
18
src/plugins/prism.rs
Normal file
18
src/plugins/prism.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::plugins::plugin::PastebinPlugin;
|
||||
|
||||
pub fn new<'r>() -> PastebinPlugin<'r> {
|
||||
PastebinPlugin {
|
||||
css_imports: vec!["/static/prism.css"],
|
||||
js_imports: vec!["/static/prism.js"],
|
||||
js_init: Some(
|
||||
"var holder = $('#pastebin-code-block:first').get(0); \
|
||||
if (holder) { Prism.highlightElement(holder); }",
|
||||
),
|
||||
static_resources: load_static_resources! {
|
||||
"/static/prism.js" => "../../static/prism.js",
|
||||
"/static/prism.css" =>"../../static/prism.css"
|
||||
},
|
||||
}
|
||||
}
|
237
static/custom.js
237
static/custom.js
|
@ -1,161 +1,162 @@
|
|||
$(document).ready(function() {
|
||||
function replaceUrlParam(url, param, value) {
|
||||
if (value == null) {
|
||||
value = '';
|
||||
}
|
||||
function replaceUrlParam(url, param, value) {
|
||||
if (value == null) {
|
||||
value = '';
|
||||
}
|
||||
|
||||
var pattern = new RegExp('\\b('+param+'=).*?(&|#|$)');
|
||||
if (url.search(pattern)>=0) {
|
||||
return url.replace(pattern,'$1' + value + '$2');
|
||||
}
|
||||
var pattern = new RegExp('\\b('+param+'=).*?(&|#|$)');
|
||||
if (url.search(pattern)>=0) {
|
||||
return url.replace(pattern,'$1' + value + '$2');
|
||||
}
|
||||
|
||||
url = url.replace(/[?#]$/,'');
|
||||
return url + (url.indexOf('?')>0 ? '&' : '?') + param + '=' + value;
|
||||
}
|
||||
url = url.replace(/[?#]$/,'');
|
||||
return url + (url.indexOf('?')>0 ? '&' : '?') + param + '=' + value;
|
||||
}
|
||||
|
||||
function resetLanguageSelector() {
|
||||
var url = new URL(document.location);
|
||||
var params = url.searchParams;
|
||||
var lang = params.get("lang");
|
||||
function resetLanguageSelector() {
|
||||
var url = new URL(document.location);
|
||||
var params = url.searchParams;
|
||||
var lang = params.get("lang");
|
||||
|
||||
if (lang != null) {
|
||||
$("#language-selector").val(lang);
|
||||
} else {
|
||||
if($("#pastebin-code-block").length) {
|
||||
$("#language-selector").val(
|
||||
$("#pastebin-code-block").prop("class").trim().split('-')[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lang != null) {
|
||||
$("#language-selector").val(lang);
|
||||
} else {
|
||||
if($("#pastebin-code-block").length) {
|
||||
$("#language-selector").val(
|
||||
$("#pastebin-code-block").prop("class").trim().split('-')[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultExpiryTime() {
|
||||
var expiry = $("#expiry-dropdown-btn").text().split("Expires: ")[1];
|
||||
return $("#expiry-dropdown a:contains('"+ expiry +"')").attr('href');
|
||||
}
|
||||
|
||||
function checkPasswordModal() {
|
||||
if ($("#password-modal").length) {
|
||||
$('#password-modal').modal('toggle');
|
||||
}
|
||||
}
|
||||
function checkPasswordModal() {
|
||||
if ($("#password-modal").length) {
|
||||
$('#password-modal').modal('toggle');
|
||||
}
|
||||
}
|
||||
|
||||
resetLanguageSelector();
|
||||
checkPasswordModal();
|
||||
resetLanguageSelector();
|
||||
checkPasswordModal();
|
||||
init_plugins();
|
||||
|
||||
var state = {
|
||||
expiry: getDefaultExpiryTime(),
|
||||
burn: 0,
|
||||
};
|
||||
var state = {
|
||||
expiry: getDefaultExpiryTime(),
|
||||
burn: 0,
|
||||
};
|
||||
|
||||
$("#language-selector").change(function() {
|
||||
if ($("#pastebin-code-block").length) {
|
||||
$('#pastebin-code-block').attr('class', 'language-' + $("#language-selector").val());
|
||||
Prism.highlightElement($('#pastebin-code-block')[0]);
|
||||
}
|
||||
});
|
||||
$("#language-selector").change(function() {
|
||||
if ($("#pastebin-code-block").length) {
|
||||
$('#pastebin-code-block').attr('class', 'language-' + $("#language-selector").val());
|
||||
init_plugins();
|
||||
}
|
||||
});
|
||||
|
||||
$("#remove-btn").on("click", function(event) {
|
||||
event.preventDefault();
|
||||
$("#remove-btn").on("click", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
url: window.location.pathname,
|
||||
type: 'DELETE',
|
||||
success: function(result) {
|
||||
uri = uri_prefix + "/new";
|
||||
uri = replaceUrlParam(uri, 'level', "info");
|
||||
uri = replaceUrlParam(uri, 'glyph', "fas fa-info-circle");
|
||||
uri = replaceUrlParam(uri, 'msg', "The paste has been successfully removed.");
|
||||
window.location.href = encodeURI(uri);
|
||||
}
|
||||
});
|
||||
});
|
||||
$.ajax({
|
||||
url: window.location.pathname,
|
||||
type: 'DELETE',
|
||||
success: function(result) {
|
||||
uri = uri_prefix + "/new";
|
||||
uri = replaceUrlParam(uri, 'level', "info");
|
||||
uri = replaceUrlParam(uri, 'glyph', "fas fa-info-circle");
|
||||
uri = replaceUrlParam(uri, 'msg', "The paste has been successfully removed.");
|
||||
window.location.href = encodeURI(uri);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#copy-btn").on("click", function(event) {
|
||||
event.preventDefault();
|
||||
$("#copy-btn").on("click", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
$(".toolbar-item button").get(0).click();
|
||||
$(".toolbar-item button").get(0).click();
|
||||
|
||||
var $this = $(this);
|
||||
$this.text("Copied!");
|
||||
$this.attr("disabled", "disabled");
|
||||
var $this = $(this);
|
||||
$this.text("Copied!");
|
||||
$this.attr("disabled", "disabled");
|
||||
|
||||
setTimeout(function() {
|
||||
$this.text("Copy");
|
||||
$this.removeAttr("disabled");
|
||||
}, 800);
|
||||
setTimeout(function() {
|
||||
$this.text("Copy");
|
||||
$this.removeAttr("disabled");
|
||||
}, 800);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
$("#send-btn").on("click", function(event) {
|
||||
event.preventDefault();
|
||||
$("#send-btn").on("click", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
uri = uri_prefix == "" ? "/" : uri_prefix;
|
||||
uri = replaceUrlParam(uri, 'lang', $("#language-selector").val());
|
||||
uri = replaceUrlParam(uri, 'ttl', state.expiry);
|
||||
uri = replaceUrlParam(uri, 'burn', state.burn);
|
||||
uri = uri_prefix == "" ? "/" : uri_prefix;
|
||||
uri = replaceUrlParam(uri, 'lang', $("#language-selector").val());
|
||||
uri = replaceUrlParam(uri, 'ttl', state.expiry);
|
||||
uri = replaceUrlParam(uri, 'burn', state.burn);
|
||||
|
||||
var data = $("#content-textarea").val();
|
||||
var pass = $("#pastebin-password").val();
|
||||
var data = $("#content-textarea").val();
|
||||
var pass = $("#pastebin-password").val();
|
||||
|
||||
if ($("#pastebin-password").val().length > 0) {
|
||||
data = CryptoJS.AES.encrypt(data, pass).toString();
|
||||
uri = replaceUrlParam(uri, 'encrypted', true);
|
||||
}
|
||||
if ($("#pastebin-password").val().length > 0) {
|
||||
data = CryptoJS.AES.encrypt(data, pass).toString();
|
||||
uri = replaceUrlParam(uri, 'encrypted', true);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: uri,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
success: function(result) {
|
||||
uri = uri_prefix + "/new";
|
||||
uri = replaceUrlParam(uri, 'level', "success");
|
||||
uri = replaceUrlParam(uri, 'glyph', "fas fa-check");
|
||||
uri = replaceUrlParam(uri, 'msg', "The paste has been successfully created:");
|
||||
uri = replaceUrlParam(uri, 'url', result);
|
||||
$.ajax({
|
||||
url: uri,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
success: function(result) {
|
||||
uri = uri_prefix + "/new";
|
||||
uri = replaceUrlParam(uri, 'level', "success");
|
||||
uri = replaceUrlParam(uri, 'glyph', "fas fa-check");
|
||||
uri = replaceUrlParam(uri, 'msg', "The paste has been successfully created:");
|
||||
uri = replaceUrlParam(uri, 'url', result);
|
||||
|
||||
window.location.href = encodeURI(uri);
|
||||
}
|
||||
});
|
||||
});
|
||||
window.location.href = encodeURI(uri);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#expiry-dropdown a').click(function(event){
|
||||
event.preventDefault();
|
||||
$('#expiry-dropdown a').click(function(event){
|
||||
event.preventDefault();
|
||||
|
||||
state.expiry = $(this).attr("href");
|
||||
$('#expiry-dropdown-btn').text("Expires: " + this.innerHTML);
|
||||
});
|
||||
state.expiry = $(this).attr("href");
|
||||
$('#expiry-dropdown-btn').text("Expires: " + this.innerHTML);
|
||||
});
|
||||
|
||||
$('#burn-dropdown a').click(function(event){
|
||||
event.preventDefault();
|
||||
$('#burn-dropdown a').click(function(event){
|
||||
event.preventDefault();
|
||||
|
||||
state.burn = $(this).attr("href");
|
||||
$('#burn-dropdown-btn').text("Burn: " + this.innerHTML);
|
||||
});
|
||||
state.burn = $(this).attr("href");
|
||||
$('#burn-dropdown-btn').text("Burn: " + this.innerHTML);
|
||||
});
|
||||
|
||||
$('#decrypt-btn').click(function(event) {
|
||||
var pass = $("#modal-password").val();
|
||||
$('#decrypt-btn').click(function(event) {
|
||||
var pass = $("#modal-password").val();
|
||||
var data = "";
|
||||
|
||||
if ($("#pastebin-code-block").length) {
|
||||
data = $("#pastebin-code-block").text();
|
||||
data = $("#pastebin-code-block").text();
|
||||
} else {
|
||||
data = $("#content-textarea").text();
|
||||
data = $("#content-textarea").text();
|
||||
}
|
||||
|
||||
var decrypted = CryptoJS.AES.decrypt(data, pass).toString(CryptoJS.enc.Utf8);
|
||||
if (decrypted.length == 0) {
|
||||
$("#modal-alert").removeClass("collapse");
|
||||
} else {
|
||||
var decrypted = CryptoJS.AES.decrypt(data, pass).toString(CryptoJS.enc.Utf8);
|
||||
if (decrypted.length == 0) {
|
||||
$("#modal-alert").removeClass("collapse");
|
||||
} else {
|
||||
if ($("#pastebin-code-block").length) {
|
||||
$("#pastebin-code-block").text(decrypted);
|
||||
Prism.highlightElement($('#pastebin-code-block')[0]);
|
||||
$("#pastebin-code-block").text(decrypted);
|
||||
init_plugins();
|
||||
} else {
|
||||
$("#content-textarea").text(decrypted);
|
||||
$("#content-textarea").text(decrypted);
|
||||
}
|
||||
|
||||
$("#modal-close-btn").click();
|
||||
$("#modal-alert").alert('close');
|
||||
}
|
||||
});
|
||||
$("#modal-close-btn").click();
|
||||
$("#modal-alert").alert('close');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,11 +9,10 @@
|
|||
<title>Pastebin</title>
|
||||
|
||||
<link rel="icon" href="/static/favicon.ico">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" crossorigin="anonymous">
|
||||
|
||||
<link href="{{uri_prefix}}/static/prism.css" rel="stylesheet" />
|
||||
<link href="{{uri_prefix}}/static/custom.css" rel="stylesheet" />
|
||||
{{#each css_imports as |url|}}
|
||||
<link href="{{format_url ../uri_prefix url}}" rel="stylesheet" />
|
||||
{{/each}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -27,16 +26,16 @@
|
|||
<li class="nav-item active">
|
||||
<a class="nav-link" href="{{uri_prefix}}/new">New <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
{{#if is_created}}
|
||||
{{#if (not is_burned)}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" id="remove-btn">Remove</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
{{#if is_created}}
|
||||
{{#if (not is_burned)}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" id="remove-btn">Remove</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{uri_prefix}}/new?id={{pastebin_id}}" id="clone-btn">Clone</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if is_editable}}
|
||||
<li class="nav-item dropdown">
|
||||
|
@ -242,8 +241,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
{{#if msg}}
|
||||
{{ else }}
|
||||
{{#if msg}}
|
||||
<div class="mt-3 alert alert-{{level}} alert-dismissible fade show" role="alert">
|
||||
{{#if glyph}}<i class="{{glyph}}"></i>{{/if}}
|
||||
{{#if url}}{{msg}} <a href="{{url}}">{{url}}</a>{{else}}{{msg}}{{/if}}
|
||||
|
@ -251,16 +250,16 @@
|
|||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if is_editable}}
|
||||
{{#if is_editable}}
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" id="content-textarea" rows="25">{{pastebin_code}}</textarea>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if is_created or is_clone}}
|
||||
<pre><code id="pastebin-code-block" class="language-{{pastebin_language}}">{{pastebin_code}}</code></pre>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if is_created or is_clone}}
|
||||
<pre><code id="pastebin-code-block" class="language-{{pastebin_language}}">{{pastebin_code}}</code></pre>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</main>
|
||||
|
||||
|
@ -268,7 +267,7 @@
|
|||
<span class="text-muted ml-4"><a href="https://github.com/mkaczanowski/pastebin"><i class="fab fa-github"></i></a> - pastebin v{{version}}</span>
|
||||
</footer>
|
||||
|
||||
{{#if is_encrypted}}
|
||||
{{#if is_encrypted}}
|
||||
<div class="modal fade" id="password-modal" tabindex="-1" role="dialog" aria-labelledby="password-modal-label" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
|
@ -299,16 +298,19 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<script>var uri_prefix="{{uri_prefix}}"</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-notify/0.2.0/js/bootstrap-notify.min.js"></script>
|
||||
<script src="{{uri_prefix}}/static/prism.js"></script>
|
||||
<script src="{{uri_prefix}}/static/custom.js"></script>
|
||||
<script>
|
||||
var uri_prefix="{{uri_prefix}}";
|
||||
|
||||
function init_plugins() {
|
||||
{{#each js_init as |fn|}}{{fn}}
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
</script>
|
||||
{{#each js_imports as |url|}}
|
||||
<script src="{{format_url ../uri_prefix url}}"></script>
|
||||
{{/each}}
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue