feat: gist view

master
Aravinth Manivannan 2022-03-12 18:16:42 +05:30
parent 9fb203de32
commit 2323dbf82c
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
12 changed files with 628 additions and 38 deletions

123
Cargo.lock generated
View File

@ -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"

View File

@ -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 <realaravinth@batsense.net>"]
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"]

View File

@ -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<FileInfo>,
@ -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)

View File

@ -17,5 +17,6 @@
pub mod account;
pub mod auth;
pub mod gists;
pub mod render_html;
pub(crate) use crate::utils::get_random;

View File

@ -0,0 +1,171 @@
/*
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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!(
"<div style=\"background-color:#{:02x}{:02x}{:02x};\">\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!("<div title='click for more options' id=\"line-{num}\"class=\"line\"><details class='line_links'><summary class='line_top-link'><a href=\"#line-{num}\"<span class=\"line-number\">{num}</span></a>{line}</summary><a href=\"#line-{num}\"<span class=\"line-link\">Permanant link</span></a><a href=\"#line-{num}\"<span class=\"line-link\">Highlight</span></a></details></div>"
));
num += 1;
}
}
}
output.push_str("</div>");
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");
//}
}

View File

@ -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);
}

View File

@ -1,3 +1,4 @@
use actix_http::header;
/*
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
@ -70,6 +71,9 @@ async fn gists_new_route_works(data: Arc<Data>, 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<Data>, 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)

128
src/pages/gists/view.rs Normal file
View File

@ -0,0 +1,128 @@
/*
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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<Context>,
}
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<PostCommentPath>,
) -> PageResult<impl Responder, ViewGist> {
let username = id.identity();
let map_err = |e: ServiceError, g: Option<&GistInfo>| -> PageError<ViewGist> {
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))
}

View File

@ -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;
}

View File

@ -0,0 +1 @@
<div class="gist__filename">{{ payload_file.filename }}</div>

View File

@ -0,0 +1,3 @@
{% if "text" in payload_file.content.file %}
{{ payload_file.content.file.text }}
{% endif %}

View File

@ -0,0 +1,31 @@
{% extends 'gistbase' %}
{% block gist_main %}
{% include "error_comp" %}
<div class="gist__container>
{% if payload %}
<p class="gist__name">
<a href={{ gist_owner_link }}>{{ payload.owner }}</a>/<a href="">{{ payload.id | truncate(length=10) }}</a>
<span class="gist__visibility">{{ payload.visibility}}</span>
</p>
{% if payload.description %}
<p class="gist__description">{{ payload.description}}</p>
{% 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 %}
</div>
{% endblock %}