From 2323dbf82ca2c9a4dfc40b8cbc2e94dc33f356f6 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Sat, 12 Mar 2022 18:16:42 +0530 Subject: [PATCH] feat: gist view --- Cargo.lock | 123 ++++++++++++++++ Cargo.toml | 102 ++++++++----- src/data/api/v1/gists.rs | 30 ++++ src/data/api/v1/mod.rs | 1 + src/data/api/v1/render_html.rs | 171 ++++++++++++++++++++++ src/pages/gists/mod.rs | 5 +- src/pages/gists/tests.rs | 5 +- src/pages/gists/view.rs | 128 ++++++++++++++++ static/cache/css/main.css | 66 +++++++++ templates/pages/gists/view/_filename.html | 1 + templates/pages/gists/view/_text.html | 3 + templates/pages/gists/view/index.html | 31 ++++ 12 files changed, 628 insertions(+), 38 deletions(-) create mode 100644 src/data/api/v1/render_html.rs create mode 100644 src/pages/gists/view.rs create mode 100644 templates/pages/gists/view/_filename.html create mode 100644 templates/pages/gists/view/_text.html create mode 100644 templates/pages/gists/view/index.html diff --git a/Cargo.lock b/Cargo.lock index 377a186..91dc2a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,6 +409,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde 1.0.136", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -1089,6 +1098,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1162,11 +1180,13 @@ dependencies = [ "num_cpus", "num_enum", "pretty_env_logger", + "pulldown-cmark", "rand 0.8.5", "rust-embed", "serde 1.0.136", "serde_json", "sqlx", + "syntect", "tera", "tokio", "url", @@ -1438,6 +1458,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lexical-core" version = "0.7.6" @@ -1508,6 +1534,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -1766,6 +1801,28 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +[[package]] +name = "onig" +version = "6.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ddfe2c93bb389eea6e6d713306880c7f6dcc99a75b659ce145d962c861b225" +dependencies = [ + "bitflags", + "lazy_static", + "libc", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd3eee045c84695b53b20255bb7317063df090b68e18bfac0abb6c39cf7f33e" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "opaque-debug" version = "0.2.3" @@ -1985,6 +2042,20 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +[[package]] +name = "plist" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +dependencies = [ + "base64", + "indexmap", + "line-wrap", + "serde 1.0.136", + "time 0.3.7", + "xml-rs", +] + [[package]] name = "polyval" version = "0.5.3" @@ -2068,6 +2139,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6" +dependencies = [ + "bitflags", + "getopts", + "memchr", + "unicase", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -2304,6 +2387,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "same-file" version = "1.0.6" @@ -2771,6 +2860,28 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syntect" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b20815bbe80ee0be06e6957450a841185fcf690fe0178f14d77a05ce2caa031" +dependencies = [ + "bincode", + "bitflags", + "flate2", + "fnv", + "lazy_static", + "lazycell", + "onig", + "plist", + "regex-syntax", + "serde 1.0.136", + "serde_derive", + "serde_json", + "walkdir", + "yaml-rust", +] + [[package]] name = "tendril" version = "0.4.2" @@ -3107,6 +3218,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + [[package]] name = "unicode-xid" version = "0.2.2" @@ -3410,6 +3527,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + [[package]] name = "xml5ever" version = "0.16.2" diff --git a/Cargo.toml b/Cargo.toml index 0515fdd..48e0d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,37 +1,28 @@ [package] -name = "gitpad" -description = "Self-Hosted GitHub Gists" -version = "0.1.0" -edition = "2021" -homepage = "https://github.com/realaravinth/gitpad" -repository = "https://github.com/realaravinth/gitpad" -documentation = "https://github.con/realaravinth/gitpad" -readme = "https://github.com/realaravinth/gitpad/blob/master/README.md" -license = "AGPLv3 or later version" authors = ["realaravinth "] build = "build.rs" +description = "Self-Hosted GitHub Gists" +documentation = "https://github.con/realaravinth/gitpad" +edition = "2021" +homepage = "https://github.com/realaravinth/gitpad" +license = "AGPLv3 or later version" +name = "gitpad" +readme = "https://github.com/realaravinth/gitpad/blob/master/README.md" +repository = "https://github.com/realaravinth/gitpad" +version = "0.1.0" +[build-dependencies] +mime = "0.3.16" +serde_json = "1" -[workspace] -exclude = ["database/migrator"] -members = [ - ".", - "database/db-core", - "database/db-sqlx-postgres", - "database/db-sqlx-sqlite", -] +[build-dependencies.cache-buster] +git = "https://github.com/realaravinth/cache-buster" [dependencies] -actix-auth-middleware = { branch="v4", version = "0.2", git = "https://github.com/realaravinth/actix-auth-middleware", features = ["actix_identity_backend"] } -actix-web = "4.0.0-rc.3" actix-http = "3.0.0-rc.2" actix-identity = "0.4.0-beta.8" actix-rt = "2.6.0" -argon2-creds = { branch = "master", git = "https://github.com/realaravinth/argon2-creds"} -cache-buster = { git = "https://github.com/realaravinth/cache-buster" } +actix-web = "4.0.0-rc.3" config = "0.11" -db-core = {path = "./database/db-core"} -db-sqlx-postgres = {path = "./database/db-sqlx-postgres"} -db-sqlx-sqlite = {path = "./database/db-sqlx-sqlite"} derive_more = "0.99" futures = "0.3.21" git2 = "0.13.25" @@ -39,27 +30,66 @@ lazy_static = "1.4" log = "0.4" mime = "0.3.16" mime_guess = "2.0.3" -my-codegen = {package = "actix-web-codegen", git ="https://github.com/realaravinth/actix-web"} num_cpus = "1.13" num_enum = "0.5.6" pretty_env_logger = "0.4" +pulldown-cmark = "*" rand = "0.8.4" rust-embed = "6.0.0" -serde = { version = "1", features = ["derive"]} serde_json = "1" -sqlx = { version = "0.5.10", features = [ "runtime-actix-rustls", "uuid", "postgres", "time", "offline", "sqlite" ] } -tera = { version = "1.15.0", default-features = false} -tokio = { version = "1.16.1", features = ["fs"] } +syntect = "*" url = "2.2" urlencoding = "2.1.0" -validator = { version = "0.14.0", features = ["derive"] } + +[dependencies.actix-auth-middleware] +branch = "v4" +features = ["actix_identity_backend"] +git = "https://github.com/realaravinth/actix-auth-middleware" +version = "0.2" + +[dependencies.argon2-creds] +branch = "master" +git = "https://github.com/realaravinth/argon2-creds" + +[dependencies.cache-buster] +git = "https://github.com/realaravinth/cache-buster" + +[dependencies.db-core] +path = "./database/db-core" + +[dependencies.db-sqlx-postgres] +path = "./database/db-sqlx-postgres" + +[dependencies.db-sqlx-sqlite] +path = "./database/db-sqlx-sqlite" + +[dependencies.my-codegen] +git = "https://github.com/realaravinth/actix-web" +package = "actix-web-codegen" + +[dependencies.serde] +features = ["derive"] +version = "1" + +[dependencies.sqlx] +features = ["runtime-actix-rustls", "uuid", "postgres", "time", "offline", "sqlite"] +version = "0.5.10" + +[dependencies.tera] +default-features = false +version = "1.15.0" + +[dependencies.tokio] +features = ["fs"] +version = "1.16.1" + +[dependencies.validator] +features = ["derive"] +version = "0.14.0" [dev-dependencies] actix-rt = "2" - -[build-dependencies] -serde_json = "1" -cache-buster = { git = "https://github.com/realaravinth/cache-buster" } -#cache-buster = { version = "0.2.0", git = "https://github.com/realaravinth/cache-buster" } -mime = "0.3.16" +[workspace] +exclude = ["database/migrator"] +members = [".", "database/db-core", "database/db-sqlx-postgres", "database/db-sqlx-sqlite"] diff --git a/src/data/api/v1/gists.rs b/src/data/api/v1/gists.rs index 7afeaae..7ca4ca4 100644 --- a/src/data/api/v1/gists.rs +++ b/src/data/api/v1/gists.rs @@ -26,6 +26,8 @@ use crate::errors::*; use crate::utils::*; use crate::*; +use super::render_html::{GenerateHTML, SourcegraphQuery}; + /// A FileMode represents the kind of tree entries used by git. It /// resembles regular file systems modes, although FileModes are /// considerably simpler (there are not so many), and there are some, @@ -101,6 +103,32 @@ pub struct FileInfo { pub content: FileType, } +impl GenerateHTML for FileInfo { + fn generate(&mut self) { + fn highlight(code: &mut String, filepath: &str) { + let q = SourcegraphQuery { code, filepath }; + *code = q.syntax_highlight(); + } + + fn extract(f: &mut FileInfo) { + match f.content { + FileType::File(ref mut c) => match &mut *c { + ContentType::Binary(_) => (), + ContentType::Text(ref mut code) => highlight(&mut *code, &f.filename), + }, + + FileType::Dir(ref mut files) => { + for file in files.iter_mut() { + extract(file) + } + } + } + } + + extract(self); + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct GistInfo { pub files: Vec, @@ -109,6 +137,7 @@ pub struct GistInfo { pub created: i64, pub updated: i64, pub visibility: GistVisibility, + pub id: String, } #[derive(Serialize, PartialEq, Clone, Debug, Deserialize)] @@ -397,6 +426,7 @@ impl Data { visibility: gist_info.visibility, description: gist_info.description, owner: gist_info.owner, + id: gist_info.public_id, }; Ok(resp) diff --git a/src/data/api/v1/mod.rs b/src/data/api/v1/mod.rs index 083382e..4c883ae 100644 --- a/src/data/api/v1/mod.rs +++ b/src/data/api/v1/mod.rs @@ -17,5 +17,6 @@ pub mod account; pub mod auth; pub mod gists; +pub mod render_html; pub(crate) use crate::utils::get_random; diff --git a/src/data/api/v1/render_html.rs b/src/data/api/v1/render_html.rs new file mode 100644 index 0000000..6d53dfa --- /dev/null +++ b/src/data/api/v1/render_html.rs @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use std::path::Path; + +use syntect::highlighting::{Color, ThemeSet}; +use syntect::html::highlighted_html_for_string; +use syntect::parsing::{SyntaxReference, SyntaxSet}; + +use crate::errors::*; + +pub trait GenerateHTML { + fn generate(&mut self); +} + +#[allow(dead_code)] +pub const STYLE: &str = " + "; + +thread_local! { + pub(crate) static SYNTAX_SET: SyntaxSet = SyntaxSet::load_defaults_newlines(); +} + +pub struct SourcegraphQuery<'a> { + pub filepath: &'a str, + pub code: &'a str, +} + +impl<'a> SourcegraphQuery<'a> { + pub fn syntax_highlight(&self) -> String { + // let ss = SYNTAX_SET; + let ts = ThemeSet::load_defaults(); + + let theme = &ts.themes["InspiredGitHub"]; + let c = theme.settings.background.unwrap_or(Color::WHITE); + let mut num = 1; + let mut output = format!( + "
\n", + c.r, c.g, c.b + ); + + // highlighted_html_for_string(&q.code, syntax_set, syntax_def, theme), + let html = SYNTAX_SET.with(|ss| { + let language = self.determine_language(ss).unwrap(); + highlighted_html_for_string(self.code, ss, language, theme) + }); + let total_lines = html.lines().count(); + for (line_num, line) in html.lines().enumerate() { + if !line.trim().is_empty() { + if line_num == 0 || line_num == total_lines - 1 { + output.push_str(line); + } else { + output.push_str(&format!("" + )); + num += 1; + } + } + } + output.push_str("
"); + output + } + + // adopted from + // https://github.com/sourcegraph/sourcegraph/blob/9fe138ae75fd64dce06b621572b252a9c9c8da70/docker-images/syntax-highlighter/crates/sg-syntax/src/lib.rs#L81 + // with minimum modifications. Crate was MIT licensed at the time(2022-03-12 11:11) + fn determine_language<'b>( + &self, + syntax_set: &'b SyntaxSet, + ) -> ServiceResult<&'b SyntaxReference> { + if self.filepath.is_empty() { + // Legacy codepath, kept for backwards-compatability with old clients. + match syntax_set.find_syntax_by_first_line(self.code) { + Some(v) => { + return Ok(v); + } + None => unimplemented!(), //Err(json!({"error": "invalid extension"})), + }; + } + + // Split the input path ("foo/myfile.go") into file name + // ("myfile.go") and extension ("go"). + let path = Path::new(&self.filepath); + let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); + let extension = path.extension().and_then(|x| x.to_str()).unwrap_or(""); + + // Override syntect's language detection for conflicting file extensions because + // it's impossible to express this logic in a syntax definition. + struct Override { + extension: &'static str, + prefix_langs: Vec<(&'static str, &'static str)>, + default: &'static str, + } + let overrides = vec![Override { + extension: "cls", + prefix_langs: vec![("%", "TeX"), ("\\", "TeX")], + default: "Apex", + }]; + + if let Some(Override { + prefix_langs, + default, + .. + }) = overrides.iter().find(|o| o.extension == extension) + { + let name = match prefix_langs + .iter() + .find(|(prefix, _)| self.code.starts_with(prefix)) + { + Some((_, lang)) => lang, + None => default, + }; + return Ok(syntax_set + .find_syntax_by_name(name) + .unwrap_or_else(|| syntax_set.find_syntax_plain_text())); + } + + Ok(syntax_set + // First try to find a syntax whose "extension" matches our file + // name. This is done due to some syntaxes matching an "extension" + // that is actually a whole file name (e.g. "Dockerfile" or "CMakeLists.txt") + // see https://github.com/trishume/syntect/pull/170 + .find_syntax_by_extension(file_name) + .or_else(|| syntax_set.find_syntax_by_extension(extension)) + .or_else(|| syntax_set.find_syntax_by_first_line(self.code)) + .unwrap_or_else(|| syntax_set.find_syntax_plain_text())) + } +} + +#[cfg(test)] +mod tests { + use super::SourcegraphQuery; + + use syntect::parsing::SyntaxSet; + + #[test] + fn cls_tex() { + let syntax_set = SyntaxSet::load_defaults_newlines(); + let query = SourcegraphQuery { + filepath: "foo.cls", + code: "%", + }; + let result = query.determine_language(&syntax_set); + assert_eq!(result.unwrap().name, "TeX"); + let _result = query.syntax_highlight(); + } + + //#[test] + //fn cls_apex() { + // let syntax_set = SyntaxSet::load_defaults_newlines(); + // let query = SourcegraphQuery { + // filepath: "foo.cls".to_string(), + // code: "/**".to_string(), + // extension: String::new(), + // }; + // let result = determine_language(&query, &syntax_set); + // assert_eq!(result.unwrap().name, "Apex"); + //} +} diff --git a/src/pages/gists/mod.rs b/src/pages/gists/mod.rs index f5e5dbb..5e05008 100644 --- a/src/pages/gists/mod.rs +++ b/src/pages/gists/mod.rs @@ -24,18 +24,21 @@ pub use super::{ pub mod new; #[cfg(test)] mod tests; +pub mod view; pub const GIST_BASE: TemplateFile = TemplateFile::new("gistbase", "pages/gists/base.html"); pub const GIST_EXPLORE: TemplateFile = TemplateFile::new("gist_explore", "pages/gists/explore.html"); pub fn register_templates(t: &mut tera::Tera) { - for template in [GIST_BASE, GIST_EXPLORE, new::NEW_GIST].iter() { + for template in [GIST_BASE, GIST_EXPLORE].iter() { template.register(t).expect(template.name); } new::register_templates(t); + view::register_templates(t); } pub fn services(cfg: &mut web::ServiceConfig) { new::services(cfg); + view::services(cfg); } diff --git a/src/pages/gists/tests.rs b/src/pages/gists/tests.rs index cbda55f..352ea11 100644 --- a/src/pages/gists/tests.rs +++ b/src/pages/gists/tests.rs @@ -1,3 +1,4 @@ +use actix_http::header; /* * Copyright (C) 2022 Aravinth Manivannan * @@ -70,6 +71,9 @@ async fn gists_new_route_works(data: Arc, db: BoxDB) { ) .await; assert_eq!(resp.status(), StatusCode::FOUND); + let gist_id = resp.headers().get(header::LOCATION).unwrap(); + let resp = get_request!(&app, gist_id.to_str().unwrap(), cookies.clone()); + assert_eq!(resp.status(), StatusCode::OK); // add new file during gist creation let payload = serde_json::json!({ @@ -88,7 +92,6 @@ async fn gists_new_route_works(data: Arc, db: BoxDB) { ) .await; assert_eq!(resp.status(), StatusCode::OK); - let empty_gist = test::call_service( &app, post_request!(&serde_json::Value::default(), PAGES.gist.new, FORM) diff --git a/src/pages/gists/view.rs b/src/pages/gists/view.rs new file mode 100644 index 0000000..34677eb --- /dev/null +++ b/src/pages/gists/view.rs @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use std::cell::RefCell; + +use actix_identity::Identity; +use actix_web::http::header::ContentType; +use tera::Context; + +use db_core::prelude::*; + +use crate::data::api::v1::gists::{GistID, GistInfo}; +use crate::data::api::v1::render_html::GenerateHTML; +use crate::errors::*; +use crate::pages::routes::GistProfilePathComponent; +use crate::pages::routes::PostCommentPath; +use crate::settings::Settings; +use crate::AppData; + +pub use super::*; + +pub const VIEW_GIST: TemplateFile = TemplateFile::new("viewgist", "pages/gists/view/index.html"); +pub const GIST_TEXTFILE: TemplateFile = + TemplateFile::new("gist_textfile", "pages/gists/view/_text.html"); +pub const GIST_FILENAME: TemplateFile = + TemplateFile::new("gist_filename", "pages/gists/view/_filename.html"); + +pub fn register_templates(t: &mut tera::Tera) { + for template in [VIEW_GIST, GIST_FILENAME, GIST_TEXTFILE].iter() { + template.register(t).expect(template.name); + } +} + +pub fn services(cfg: &mut web::ServiceConfig) { + cfg.service(view_preview); +} + +pub struct ViewGist { + ctx: RefCell, +} + +impl CtxError for ViewGist { + fn with_error(&self, e: &ReadableError) -> String { + self.ctx.borrow_mut().insert(ERROR_KEY, e); + self.render() + } +} + +impl ViewGist { + pub fn new(username: Option<&str>, gist: Option<&GistInfo>, settings: &Settings) -> Self { + let mut ctx = auth_ctx(username, settings); + ctx.insert("visibility_private", GistVisibility::Private.to_str()); + ctx.insert("visibility_unlisted", GistVisibility::Unlisted.to_str()); + ctx.insert("visibility_public", GistVisibility::Public.to_str()); + + if let Some(gist) = gist { + ctx.insert(PAYLOAD_KEY, gist); + ctx.insert( + "gist_owner_link", + &PAGES.gist.get_profile_route(GistProfilePathComponent { + username: &gist.owner, + }), + ); + } + + let ctx = RefCell::new(ctx); + Self { ctx } + } + + pub fn render(&self) -> String { + TEMPLATES + .render(VIEW_GIST.name, &self.ctx.borrow()) + .unwrap() + } + + pub fn page(username: Option<&str>, gist: Option<&GistInfo>, s: &Settings) -> String { + let p = Self::new(username, gist, s); + p.render() + } +} +#[my_codegen::get(path = "PAGES.gist.view_gist", wrap = "super::get_auth_middleware()")] +async fn view_preview( + data: AppData, + db: crate::DB, + id: Identity, + path: web::Path, +) -> PageResult { + let username = id.identity(); + + let map_err = |e: ServiceError, g: Option<&GistInfo>| -> PageError { + PageError::new(ViewGist::new(username.as_deref(), g, &data.settings), e) + }; + + let gist = db.get_gist(&path.gist).await.map_err(|e| { + let err: ServiceError = e.into(); + map_err(err, None) + })?; + + if let Some(username) = &username { + if gist.visibility == GistVisibility::Private && username != &gist.owner { + return Err(map_err(ServiceError::GistNotFound, None)); + } + } + + let mut gist = data + .gist_preview(db.as_ref(), &mut GistID::ID(&path.gist)) + .await + .map_err(|e| map_err(e, None))?; + + gist.files.iter_mut().for_each(|file| file.generate()); + + let page = ViewGist::page(username.as_deref(), Some(&gist), &data.settings); + let html = ContentType::html(); + Ok(HttpResponse::Ok().content_type(html).body(page)) +} diff --git a/static/cache/css/main.css b/static/cache/css/main.css index 7d68123..6c6d9eb 100644 --- a/static/cache/css/main.css +++ b/static/cache/css/main.css @@ -263,3 +263,69 @@ footer { .auth__demo-user__cred { font-family: monospace, monospace; } + +.gist__container { +} + +pre { + font-size: 13px; + font-family: Consolas, \"Liberation Mono\", Menlo, Courier, monospace; + margin: 10px 0; + border: 1px solid var(--color-border-default, #ddd); + border-radius: 6px; + box-sizing: border-box; + padding: 10px 0; + +} + +.line { + display: block; + padding: 0px; + cursor: pointer; +} + +.line-number { + color: #24292f; + margin: 0; + margin-right: 20px; + min-width: 35px; + padding: 0; + width: 1%; + padding-right: 10px; + padding-left: 10px; + display: inline-block; + text-align: right; +} + +details, +summary { + display: "inline"; + margin: 0; + padding: 0; +} + +summary::marker { + content: ""; + margin: 0; + padding: 0 5px; +} + +.line-link { + display: block; +} + + +pre { + box-sizing: border-box; + padding: 10px 0; +} + +.line { + /* margin: -5px 5px; */ +} + +.line-number { + /* min-width: 50px; */ + min-width: 35px; +} + diff --git a/templates/pages/gists/view/_filename.html b/templates/pages/gists/view/_filename.html new file mode 100644 index 0000000..fe02cbb --- /dev/null +++ b/templates/pages/gists/view/_filename.html @@ -0,0 +1 @@ +
{{ payload_file.filename }}
diff --git a/templates/pages/gists/view/_text.html b/templates/pages/gists/view/_text.html new file mode 100644 index 0000000..a80a1dc --- /dev/null +++ b/templates/pages/gists/view/_text.html @@ -0,0 +1,3 @@ +{% if "text" in payload_file.content.file %} + {{ payload_file.content.file.text }} +{% endif %} diff --git a/templates/pages/gists/view/index.html b/templates/pages/gists/view/index.html new file mode 100644 index 0000000..932a852 --- /dev/null +++ b/templates/pages/gists/view/index.html @@ -0,0 +1,31 @@ +{% extends 'gistbase' %} +{% block gist_main %} + {% include "error_comp" %} +
+ {{ payload.owner }}/{{ payload.id | truncate(length=10) }} + {{ payload.visibility}} +

+ + {% if payload.description %} +

{{ payload.description}}

+ {% endif %} + {% for payload_file in payload.files %} + {% include "gist_filename" %} + {% if "file" in payload_file.content %} + {% include "gist_textfile" %} + {% endif %} + + {% if "dir" in payload_file.content %} + {% for payload_file in payload_file.content.dir %} + {% include "gist_filename" %} + {% include "gist_textfile" %} + {% endfor %} + {% endif %} + + {% endfor %} + {% endif %} +
+{% endblock %}