Compare commits

..

4 commits

Author SHA1 Message Date
Chris Cochrun
ee252aec13 fixing up the api to work better for nix and move the table format 2025-06-23 15:13:42 -05:00
Chris Cochrun
774328aa5b changing search from json to javascript 2025-06-23 15:13:22 -05:00
Chris Cochrun
845817d0e9 making the site much closer to the original 2025-06-23 15:13:00 -05:00
Chris Cochrun
23156940c0 adding the elastilunr search js 2025-06-23 15:12:28 -05:00
19 changed files with 427 additions and 149 deletions

117
Cargo.lock generated
View file

@ -335,6 +335,56 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.98" version = "1.0.98"
@ -639,6 +689,46 @@ dependencies = [
"stacker", "stacker",
] ]
[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.103",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]] [[package]]
name = "color-eyre" name = "color-eyre"
version = "0.6.5" version = "0.6.5"
@ -666,6 +756,12 @@ dependencies = [
"tracing-error", "tracing-error",
] ]
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.5.0" version = "2.5.0"
@ -1624,6 +1720,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"
@ -2030,6 +2132,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.73" version = "0.10.73"
@ -3094,7 +3202,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tfcapi" name = "tfcsite"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-cors", "actix-cors",
@ -3103,6 +3211,7 @@ dependencies = [
"actix-rt", "actix-rt",
"actix-web", "actix-web",
"async-std", "async-std",
"clap",
"color-eyre", "color-eyre",
"env_logger", "env_logger",
"futures", "futures",
@ -3479,6 +3588,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.17.0" version = "1.17.0"

View file

@ -1,5 +1,5 @@
[package] [package]
name = "tfcapi" name = "tfcsite"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
@ -32,6 +32,7 @@ tracing-actix-web = "0.7.14"
color-eyre = "0.6.3" color-eyre = "0.6.3"
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
sqlx = { version = "0.8.2", features = ["sqlite"] } sqlx = { version = "0.8.2", features = ["sqlite"] }
clap = { version = "4.5.40", features = ["derive"] }
[profile.dev] [profile.dev]
opt-level = 0 opt-level = 0

View file

@ -1,5 +1,7 @@
# The base URL of the site; the only required configuration variable. # The base URL of the site; the only required configuration variable.
base_url = "https://tfcconnection.org" # base_url = "https://tfcconnection.org"
# used when developing
base_url = "/"
# The site title and description; used in feeds by default. # The site title and description; used in feeds by default.
title = "TFC Connection" title = "TFC Connection"
@ -123,7 +125,7 @@ include_content = true
# become too big to load on the site. Defaults to not being set. # become too big to load on the site. Defaults to not being set.
truncate_content_length = 100 truncate_content_length = 100
index_format = "elasticlunr_json" index_format = "elasticlunr_javascript"
# Optional translation object for the default language # Optional translation object for the default language
# Example: # Example:
@ -160,6 +162,7 @@ taxonomies = [
# #
[extra] [extra]
enable_search = true enable_search = true
enable_sidebar = false
[extra.navbar] [extra.navbar]

View file

@ -22,10 +22,10 @@ Fill out now!
</span> </span>
</div> </div>
{{ spacing(height="h-4") }} {{ spacing(height="h-8") }}
> If you need to fill out a camp health form, please do so [here](/camp-health-form). > If you need to fill out a camp health form, please do so [here](/camp-health-form).
{{ spacing(height="h-4") }}
> If you need to pay for your camp form, pay for registration ($100) [here](https://secure.myvanco.com/L-Z772/campaign/C-13JPJ) or pay for the full price ($200) [here](https://secure.myvanco.com/L-Z772/campaign/C-13JQE). > If you need to pay for your camp form, pay for registration ($100) [here](https://secure.myvanco.com/L-Z772/campaign/C-13JPJ) or pay for the full price ($200) [here](https://secure.myvanco.com/L-Z772/campaign/C-13JQE).
{{ spacing(height="h-8") }} {{ spacing(height="h-8") }}

View file

@ -66,15 +66,13 @@
# cp -r ${blowfish} themes/blowfish # cp -r ${blowfish} themes/blowfish
# ls themes/blowfish # ls themes/blowfish
# ''; # '';
# buildPhase = '' buildPhase = ''
# NODE_ENV=production ./themes/blowfish/node_modules/tailwindcss/lib/cli.js -c ./themes/blowfish/tailwind.config.js -i ./themes/blowfish/assets/css/main.css -o ./assets/css/compiled/main.css --jit && hugo --gc --minify ${pkgs.tailwindcss_4}/bin/tailwindcss -i static/css/base.css -o static/css/main.css
# ${pkgs.hugo}/bin/hugo --minify ${pkgs.zola}/bin/zola build
# ''; '';
# installPhase = '' installPhase = ''
# ls -l cp -r public $out/
# cp -r public $out/ '';
# ls -l $out
# '';
buildInputs = bi; buildInputs = bi;
nativeBuildInputs = nbi; nativeBuildInputs = nbi;
}; };

View file

@ -3,7 +3,7 @@ default:
build: build:
tailwindcss -i static/css/base.css -o static/css/main.css && zola build tailwindcss -i static/css/base.css -o static/css/main.css && zola build
serve: serve:
zola serve zola serve -p 4242
uglify: uglify:
uglifyjs ./src/js/main.js --compress --mangle -o ./static/js/main.js && uglifyjs ./src/js/page.js --compress --mangle -o ./static/js/page.js && uglifyjs ./src/js/search.js --compress --mangle -o ./static/js/search.js && uglifyjs ./src/js/lang.js --compress --mangle -o ./static/js/lang.js uglifyjs ./src/js/main.js --compress --mangle -o ./static/js/main.js && uglifyjs ./src/js/page.js --compress --mangle -o ./static/js/page.js && uglifyjs ./src/js/search.js --compress --mangle -o ./static/js/search.js && uglifyjs ./src/js/lang.js --compress --mangle -o ./static/js/lang.js
dev: dev:
@ -14,3 +14,5 @@ clean:
cargo clean cargo clean
test: test:
RUST_LOG=debug cargo test --benches --tests --all-features -- --nocapture RUST_LOG=debug cargo test --benches --tests --all-features -- --nocapture
run:
tailwindcss -i static/css/base.css -o static/css/main.css --watch & zola build && cargo run -- -d

View file

@ -6,8 +6,8 @@ use color_eyre::eyre::{Context, Result};
use futures::FutureExt; use futures::FutureExt;
use lettre::{message::SinglePart, Message}; use lettre::{message::SinglePart, Message};
use maud::{html, Markup, DOCTYPE}; use maud::{html, Markup, DOCTYPE};
use reqwest::Client; use reqwest::{Client, Response};
use tracing::{error, info}; use tracing::{debug, error, info};
#[derive(Debug, MultipartForm)] #[derive(Debug, MultipartForm)]
struct CampForm { struct CampForm {
@ -39,27 +39,30 @@ struct CampForm {
health_form: Text<String>, health_form: Text<String>,
} }
impl From<&CampForm> for HashMap<i32, String> { impl From<&CampForm> for HashMap<&str, String> {
fn from(form: &CampForm) -> Self { fn from(form: &CampForm) -> Self {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(63, format!("{} {}", form.first_name.0, form.last_name.0));
map.insert( map.insert(
64, "Full_Name",
format!("{} {}", form.first_name.0, form.last_name.0),
);
map.insert(
"Parent_Name",
format!("{} {}", form.parent_first_name.0, form.parent_last_name.0), format!("{} {}", form.parent_first_name.0, form.parent_last_name.0),
); );
map.insert(65, form.parent_phone.0.clone()); map.insert("Parent_Phone", form.parent_phone.0.clone());
map.insert(66, form.parent_email.0.clone().clone()); map.insert("Parent_Email", form.parent_email.0.clone().clone());
map.insert(67, form.birthdate.0.clone()); map.insert("Birth_Date", form.birthdate.0.clone());
map.insert(69, form.gender.0.clone()); map.insert("Gender", form.gender.0.clone());
map.insert(70, form.street.0.clone()); map.insert("Street_Address", form.street.0.clone());
map.insert(71, form.city.0.clone()); map.insert("City", form.city.0.clone());
map.insert(72, form.state.0.clone()); map.insert("State", form.state.0.clone());
map.insert(73, form.zip.0.clone().to_string()); map.insert("Zipcode", form.zip.0.clone().to_string());
map.insert(74, form.grade.0.clone()); map.insert("Grade", form.grade.0.clone());
map.insert(75, form.week.0.clone()); map.insert("Week_Chosen", form.week.0.clone());
map.insert(76, form.shirt.0.clone()); map.insert("Shirt_Size", form.shirt.0.clone());
map.insert(77, form.registration.0.clone()); map.insert("Registration", form.registration.0.clone());
map.insert(115, form.health_form.0.clone()); map.insert("Health_Form", form.health_form.0.clone());
map map
} }
} }
@ -292,19 +295,21 @@ pub async fn camp_form(MultipartForm(form): MultipartForm<CampForm>) -> HttpResp
} }
} }
async fn store_camp_form(map: HashMap<i32, String>) -> Result<()> { async fn store_camp_form(map: HashMap<&str, String>) -> Result<Response> {
let request = Client::new(); let request = Client::new();
let mut json = HashMap::new(); let mut json = HashMap::new();
json.insert("data", map); let mut fields = HashMap::new();
request fields.insert("fields", map);
.post("https://staff.tfcconnection.org/apps/tables/api/1/tables/5/rows") json.insert("records", vec![fields]);
.basic_auth("chris", Some("2VHeGxeC^Zf9KqFK^G@Pt!zu2q^6@b")) let response = request
.header("OCS-APIRequest", "true") .post("https://table.tfcconnection.org/api/docs/e8SFoTHpmJuFQsiMhRTXCi/tables/Camp_Data/records")
.bearer_auth("b8189d1b315548aa610db2fd3a43177a967cd41f")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.json(&json) .json(&json)
.send() .send()
.await?; .await?;
Ok(()) debug!(?response);
Ok(response)
} }
#[cfg(test)] #[cfg(test)]
@ -329,7 +334,7 @@ mod test {
grade: Text(String::from("junior")), grade: Text(String::from("junior")),
shirt: Text(String::from("medium")), shirt: Text(String::from("medium")),
allergies: Text(String::from("Cool beans")), allergies: Text(String::from("Cool beans")),
week: Text(String::from("1")), week: Text(String::from("week1")),
registration: Text(String::from("later")), registration: Text(String::from("later")),
health_form: Text(String::from("I guess")), health_form: Text(String::from("I guess")),
} }
@ -342,7 +347,7 @@ mod test {
let map = HashMap::from(&form); let map = HashMap::from(&form);
let res = store_camp_form(map).await; let res = store_camp_form(map).await;
match res { match res {
Ok(_) => assert!(true), Ok(r) => assert!(r.status().is_success(), "Failed to store: {:?}", r),
Err(e) => assert!(false, "Failed storing test: {e}"), Err(e) => assert!(false, "Failed storing test: {e}"),
} }
} }

View file

@ -12,7 +12,7 @@ use lettre::{
Message, Message,
}; };
use maud::{html, Markup, DOCTYPE}; use maud::{html, Markup, DOCTYPE};
use reqwest::Client; use reqwest::{Client, Response};
use tracing::{error, info}; use tracing::{error, info};
use crate::email::send_email; use crate::email::send_email;
@ -75,40 +75,46 @@ struct HealthForm {
registration: Text<String>, registration: Text<String>,
} }
impl From<&HealthForm> for HashMap<i32, String> { impl From<&HealthForm> for HashMap<&str, String> {
fn from(form: &HealthForm) -> Self { fn from(form: &HealthForm) -> Self {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(37, format!("{} {}", form.first_name.0, form.last_name.0));
map.insert( map.insert(
38, "Student_Name",
format!("{} {}", form.first_name.0, form.last_name.0),
);
map.insert(
"Parent_Name",
format!("{} {}", form.parent_first_name.0, form.parent_last_name.0), format!("{} {}", form.parent_first_name.0, form.parent_last_name.0),
); );
map.insert(39, form.birthdate.0.clone()); map.insert("Birthdate", form.birthdate.0.clone());
map.insert(40, form.street.0.clone()); map.insert("Street", form.street.0.clone());
map.insert(41, form.city.0.clone()); map.insert("City", form.city.0.clone());
map.insert(42, form.state.0.clone()); map.insert("State", form.state.0.clone());
map.insert(43, form.zip.0.clone()); map.insert("Zipcode", form.zip.0.clone());
map.insert(44, form.parent_cellphone.0.clone()); map.insert("Parent_Phone", form.parent_cellphone.0.clone());
map.insert(45, form.homephone.0.clone()); map.insert("Homephone", form.homephone.0.clone());
map.insert(46, format!("{} {}", form.contact.0, form.contact_phone.0));
map.insert(47, form.doctorname.0.clone());
map.insert(48, form.doctorcity.0.clone());
map.insert(49, form.doctorphone.0.clone());
map.insert(50, form.medical.0.clone());
map.insert(51, form.insurance.0.clone());
map.insert(52, form.policy_number.0.clone());
map.insert(54, form.agreement.0.clone());
map.insert( map.insert(
55, "Emergency_Contact",
format!("{} {}", form.contact.0, form.contact_phone.0),
);
map.insert("Doctor", form.doctorname.0.clone());
map.insert("Doctor_City", form.doctorcity.0.clone());
map.insert("Doctor_Phone", form.doctorphone.0.clone());
map.insert("Medical_Coverage", form.medical.0.clone());
map.insert("Insurance_Name", form.insurance.0.clone());
map.insert("Policy_Number", form.policy_number.0.clone());
map.insert("Agreement", form.agreement.0.clone());
map.insert(
"Allergies",
format!("{} \n {}", form.allergies.0, form.allergies_other.0), format!("{} \n {}", form.allergies.0, form.allergies_other.0),
); );
map.insert(56, form.specific_allergies.0.clone()); map.insert("Specific_Allergies", form.specific_allergies.0.clone());
map.insert(57, form.treatment.0.clone()); map.insert("Allergic_Treatments", form.treatment.0.clone());
map.insert(58, form.conditions.0.clone()); map.insert("Conditions", form.conditions.0.clone());
map.insert(59, form.tetanus.0.clone()); map.insert("Tetanus_Shot_Date", form.tetanus.0.clone());
map.insert(60, form.medication.0.clone()); map.insert("Medication_Schedule", form.medication.0.clone());
map.insert(61, form.notes.0.clone()); map.insert("Other_Notes", form.notes.0.clone());
map.insert(62, form.swimming.0.clone()); map.insert("Swimming_Ability", form.swimming.0.clone());
map map
} }
} }
@ -431,22 +437,22 @@ pub async fn health_form(MultipartForm(mut form): MultipartForm<HealthForm>) ->
// HttpResponse::Ok().body("hi") // HttpResponse::Ok().body("hi")
} }
async fn store_form(map: HashMap<i32, String>) -> Result<()> { async fn store_form(map: HashMap<&str, String>) -> Result<Response> {
let client = Client::new(); let client = Client::new();
let mut json = HashMap::new(); let mut json = HashMap::new();
json.insert("data", map); let mut fields = HashMap::new();
fields.insert("fields", map);
json.insert("records", vec![fields]);
let res = client let res = client
.post("https://staff.tfcconnection.org/ocs/v2.php/apps/tables/api/2/tables/4/rows") .post("https://table.tfcconnection.org/api/docs/e8SFoTHpmJuFQsiMhRTXCi/tables/Health_Data/records")
.basic_auth("chris", Some("2VHeGxeC^Zf9KqFK^G@Pt!zu2q^6@b")) .bearer_auth("b8189d1b315548aa610db2fd3a43177a967cd41f")
.header("OCS-APIRequest", "true")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.json(&json) .json(&json)
.send() .send()
.await?; .await?;
if res.status().is_success() { if res.status().is_success() {
let res = res.text().await.unwrap(); Ok(res)
Ok(())
} else { } else {
Err(eyre!( Err(eyre!(
"Problem in storing data: {:?}", "Problem in storing data: {:?}",
@ -454,3 +460,69 @@ async fn store_form(map: HashMap<i32, String>) -> Result<()> {
)) ))
} }
} }
#[cfg(test)]
mod test {
use super::*;
use actix_web::test;
fn form() -> HealthForm {
HealthForm {
first_name: Text("Frodo".into()),
last_name: Text("Braggins".into()),
parent_first_name: Text("Bilbo".into()),
parent_last_name: Text("Braggins".into()),
birthdate: Text(String::from("1845-09-12")),
street: Text(String::from("1234 Bag End")),
city: Text(String::from("The Shire")),
state: Text(String::from("Hobbiton")),
zip: Text(String::from("88888")),
allergies: Text(String::from("Cool beans")),
registration: Text(String::from("later")),
parent_cellphone: Text(String::from("later")),
homephone: Text("test".into()),
contact: Text("test".into()),
contact_phone: Text("test".into()),
doctorname: Text("test".into()),
doctorcity: Text("test".into()),
doctorphone: Text("test".into()),
medical: Text("test".into()),
insurance: Text("test".into()),
policy_number: Text("test".into()),
allergies_other: Text("test".into()),
specific_allergies: Text("test".into()),
treatment: Text("test".into()),
conditions: Text("test".into()),
tetanus: Text("test".into()),
swimming: Text("test".into()),
medication: Text("test".into()),
notes: Text("test".into()),
agreement: Text("test".into()),
file: None,
}
}
#[test]
async fn test_nc_post() {
let form = form();
assert!(!form.first_name.is_empty());
let map = HashMap::from(&form);
let res = store_form(map).await;
match res {
Ok(r) => assert!(r.status().is_success(), "Failed to store: {:?}", r),
Err(e) => assert!(false, "Failed storing test: {e}"),
}
}
#[test]
async fn test_email() {
let mut form = form();
assert!(!form.first_name.is_empty());
match form.send_email() {
Ok(m) => {
assert!(crate::email::send_email(m).await.is_ok())
}
Err(e) => assert!(false, "Failed emailing test: {e}"),
}
}
}

View file

@ -7,7 +7,7 @@ use actix_web::body::MessageBody;
use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::{web, App, Error, HttpServer}; use actix_web::{web, App, Error, HttpServer};
use api::camp_form::camp_form; use api::camp_form::camp_form;
use api::contact::{self, contact_form}; use api::contact::contact_form;
use api::health_form::health_form; use api::health_form::health_form;
use api::local_trip_form::local_form; use api::local_trip_form::local_form;
use api::mt_form::mt_form; use api::mt_form::mt_form;
@ -15,6 +15,7 @@ use api::{
mt_church_form::mt_church_form, mt_parent_form::mt_parent_form, mt_church_form::mt_church_form, mt_parent_form::mt_parent_form,
mt_teacher_form::mt_teacher_form, mt_teacher_form::mt_teacher_form,
}; };
use clap::Parser;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::Result; use color_eyre::Result;
use sqlx::{Connection, SqliteConnection}; use sqlx::{Connection, SqliteConnection};
@ -45,45 +46,64 @@ impl RootSpanBuilder for DomainRootSpanBuilder {
fn on_request_end<B: MessageBody>(_span: Span, _outcome: &Result<ServiceResponse<B>, Error>) {} fn on_request_end<B: MessageBody>(_span: Span, _outcome: &Result<ServiceResponse<B>, Error>) {}
} }
#[derive(Debug, Parser)]
#[command(name = "tfcsite", version, about)]
struct Cli {
#[arg(short, long)]
dev: bool,
}
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
let site;
let temp;
let logs;
if Cli::parse().dev {
site = "./public";
logs = "/tmp/tfcsite";
temp = "/tmp/tfcsite";
} else {
site = "../public";
logs = "/storage/logs/tfcsite";
temp = "/tmp/tfcsite";
}
std::fs::create_dir_all(&logs)?;
std::fs::create_dir_all(&temp)?;
let timer = let timer =
tracing_subscriber::fmt::time::ChronoLocal::new("%Y-%m-%d_%I:%M:%S%.6f %P".to_owned()); tracing_subscriber::fmt::time::ChronoLocal::new("%Y-%m-%d_%I:%M:%S%.6f %P".to_owned());
let logfile = RollingFileAppender::builder() let logfile = RollingFileAppender::builder()
.rotation(Rotation::DAILY) .rotation(Rotation::DAILY)
.filename_prefix("api") .filename_prefix("api")
.filename_suffix("log") .filename_suffix("log")
.build(if DEV_MODE { .build(&logs)
"/tmp/tfcsite"
} else {
"/storage/logs/tfcsite"
})
.expect("Shouldn't"); .expect("Shouldn't");
let filter = EnvFilter::builder() let file_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into()) .with_default_directive(LevelFilter::WARN.into())
.parse_lossy("tfcapi=debug"); .parse_lossy("tfcsite=debug");
let logfile_layer = tracing_subscriber::fmt::layer() let logfile_layer = tracing_subscriber::fmt::layer()
.with_writer(logfile) .with_writer(logfile)
.with_line_number(true) .with_line_number(true)
.with_level(true) .with_level(true)
.with_target(true) .with_target(true)
.with_ansi(false) .with_ansi(false)
.with_timer(timer.clone()); .with_timer(timer.clone())
.with_filter(file_filter);
let stdout_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.parse_lossy("tfcsite=debug");
let stdout_layer = tracing_subscriber::fmt::layer() let stdout_layer = tracing_subscriber::fmt::layer()
.pretty() .pretty()
.with_line_number(true) .with_line_number(true)
.with_target(true) .with_target(true)
.with_timer(timer) .with_timer(timer)
.with_filter(filter); .with_filter(stdout_filter);
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.parse_lossy("tfcapi=debug");
let subscriber = tracing_subscriber::registry()
.with(logfile_layer.with_filter(filter).and_then(stdout_layer));
let _ = tracing::subscriber::set_global_default(subscriber).wrap_err("Tracing broked");
std::fs::create_dir_all("/tmp/tfcsite")?; let subscriber = tracing_subscriber::registry().with(logfile_layer.and_then(stdout_layer));
let _ = tracing::subscriber::set_global_default(subscriber).wrap_err("Tracing broked");
info!("starting HTTP server at http://localhost:4242"); info!("starting HTTP server at http://localhost:4242");
@ -96,11 +116,7 @@ async fn main() -> std::io::Result<()> {
App::new() App::new()
.app_data(data.clone()) .app_data(data.clone())
.wrap(TracingLogger::<DomainRootSpanBuilder>::new()) .wrap(TracingLogger::<DomainRootSpanBuilder>::new())
.app_data(TempFileConfig::default().directory(if DEV_MODE { .app_data(TempFileConfig::default().directory(&temp))
"/tmp/tfcsite"
} else {
"/storage/logs/tfcsite"
}))
.service(mt_form) .service(mt_form)
.service(health_form) .service(health_form)
.service(mt_parent_form) .service(mt_parent_form)
@ -109,7 +125,7 @@ async fn main() -> std::io::Result<()> {
.service(local_form) .service(local_form)
.service(camp_form) .service(camp_form)
.service(contact_form) .service(contact_form)
.service(Files::new("/", "./public").index_file("index.html")) .service(Files::new("/", &site).index_file("index.html"))
}) })
.bind(("localhost", 4242))? .bind(("localhost", 4242))?
.workers(4) .workers(4)

View file

@ -27,6 +27,17 @@
li::marker { li::marker {
color: var(--color-gray-600); color: var(--color-gray-600);
} }
blockquote {
list-style-type: disc;
border-left-width: .25rem;
border-left-color: var(--color-blue-600);
font-style: italic;
font-weight: 500;
padding-left: 1em;
margin-top: 1.6em;
margin-bottom: 1.6em;
}
img { img {
@apply py-0; @apply py-0;

View file

@ -7,6 +7,7 @@
'Noto Color Emoji'; 'Noto Color Emoji';
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace; monospace;
--color-blue-200: oklch(88.2% 0.059 254.128);
--color-blue-400: oklch(70.7% 0.165 254.624); --color-blue-400: oklch(70.7% 0.165 254.624);
--color-blue-500: oklch(62.3% 0.214 259.815); --color-blue-500: oklch(62.3% 0.214 259.815);
--color-blue-600: oklch(54.6% 0.245 262.881); --color-blue-600: oklch(54.6% 0.245 262.881);
@ -351,9 +352,6 @@
.mt-16 { .mt-16 {
margin-top: calc(var(--spacing) * 16); margin-top: calc(var(--spacing) * 16);
} }
.mt-20 {
margin-top: calc(var(--spacing) * 20);
}
.mt-40 { .mt-40 {
margin-top: calc(var(--spacing) * 40); margin-top: calc(var(--spacing) * 40);
} }
@ -390,6 +388,9 @@
.ml-4 { .ml-4 {
margin-left: calc(var(--spacing) * 4); margin-left: calc(var(--spacing) * 4);
} }
.ml-\[-0\.2em\] {
margin-left: -0.2em;
}
.ml-auto { .ml-auto {
margin-left: auto; margin-left: auto;
} }
@ -457,9 +458,6 @@
.h-24 { .h-24 {
height: calc(var(--spacing) * 24); height: calc(var(--spacing) * 24);
} }
.h-36 {
height: calc(var(--spacing) * 36);
}
.h-40 { .h-40 {
height: calc(var(--spacing) * 40); height: calc(var(--spacing) * 40);
} }
@ -767,9 +765,6 @@
.bg-indigo-500 { .bg-indigo-500 {
background-color: var(--color-indigo-500); background-color: var(--color-indigo-500);
} }
.bg-transparent {
background-color: transparent;
}
.bg-white { .bg-white {
background-color: var(--color-white); background-color: var(--color-white);
} }
@ -947,9 +942,6 @@
.text-gray-900 { .text-gray-900 {
color: var(--color-gray-900); color: var(--color-gray-900);
} }
.text-indigo-500 {
color: var(--color-indigo-500);
}
.text-white { .text-white {
color: var(--color-white); color: var(--color-white);
} }
@ -1020,9 +1012,6 @@
--tw-blur: blur(8px); --tw-blur: blur(8px);
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
} }
.filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.transition { .transition {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter; transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
@ -1198,13 +1187,6 @@
} }
} }
} }
.hover\:bg-gray-100 {
&:hover {
@media (hover: hover) {
background-color: var(--color-gray-100);
}
}
}
.hover\:bg-gray-200 { .hover\:bg-gray-200 {
&:hover { &:hover {
@media (hover: hover) { @media (hover: hover) {
@ -1240,6 +1222,14 @@
} }
} }
} }
.hover\:text-3xl {
&:hover {
@media (hover: hover) {
font-size: var(--text-3xl);
line-height: var(--tw-leading, var(--text-3xl--line-height));
}
}
}
.hover\:text-blue-500 { .hover\:text-blue-500 {
&:hover { &:hover {
@media (hover: hover) { @media (hover: hover) {
@ -1261,13 +1251,6 @@
} }
} }
} }
.hover\:text-indigo-400 {
&:hover {
@media (hover: hover) {
color: var(--color-indigo-400);
}
}
}
.hover\:text-white { .hover\:text-white {
&:hover { &:hover {
@media (hover: hover) { @media (hover: hover) {
@ -1628,6 +1611,11 @@
background-color: var(--color-gray-900); background-color: var(--color-gray-900);
} }
} }
.dark\:text-blue-200 {
@media (prefers-color-scheme: dark) {
color: var(--color-blue-200);
}
}
.dark\:text-gray-200 { .dark\:text-gray-200 {
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
color: var(--color-gray-200); color: var(--color-gray-200);
@ -1716,6 +1704,16 @@
li::marker { li::marker {
color: var(--color-gray-600); color: var(--color-gray-600);
} }
blockquote {
list-style-type: disc;
border-left-width: .25rem;
border-left-color: var(--color-blue-600);
font-style: italic;
font-weight: 500;
padding-left: 1em;
margin-top: 1.6em;
margin-bottom: 1.6em;
}
img { img {
padding-block: calc(var(--spacing) * 0); padding-block: calc(var(--spacing) * 0);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);

10
static/js/elasticlunr.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,55 @@
function toggleSearchModal(){var e=document.getElementById("search-modal");e.classList.toggle("opacity-0"),e.classList.toggle("pointer-events-none"),document.body.classList.toggle("search-active"),[...document.body.classList].includes("search-active")&&(document.getElementById("search-input").value="",document.getElementById("search-input").focus())}function formatResultItem(e){return console.log(e),htmlToElement(`<li class="flex hover:bg-gray-200 dark:hover:bg-gray-600 text-black dark:text-gray-200 p-2 rounded-lg border border-black dark:border-gray-200 bg-gray-200 dark:bg-gray-500 rounded-lg hover:shadow-xl mb-2"> function toggleSearchModal() {
var e = document.getElementById("search-modal");
e.classList.toggle("opacity-0"), e.classList.toggle("pointer-events-none"), document.body.classList.toggle("search-active"), [...document.body.classList].includes("search-active") && (document.getElementById("search-input").value = "", document.getElementById("search-input").focus())
}
function formatResultItem(e) {
return console.log(e), htmlToElement(`<li class="flex basis-full hover:bg-gray-200 dark:hover:bg-gray-600 text-black dark:text-blue-200 p-2 rounded-lg border border-black dark:border-gray-200 bg-gray-200 dark:bg-gray-700 rounded-lg hover:shadow-xl mb-2 ml-[-0.2em]">
<a href="${e.doc.path}"> <a href="${e.doc.path}">
<span class="text-xl text-bold">${e.doc.title}</span> <span class="dark:text-white text-xl text-bold">${e.doc.title}</span>
<span class="text-lg">${e.doc.description}</span> <br/>
<span class="dark:text-blue-200 text-lg">${e.doc.description}</span>
</a> </a>
</li>`)}function htmlToElement(e){var t=document.createElement("template");return e=e.trim(),t.innerHTML=e,t.content.firstChild}document.addEventListener("DOMContentLoaded",function(){document.getElementById("search").addEventListener("click",function(e){e.preventDefault(),toggleSearchModal()});document.querySelector(".modal-overlay").addEventListener("click",toggleSearchModal);for(var e=document.querySelectorAll(".modal-close"),o=0;o<e.length;o++)e[o].addEventListener("click",toggleSearchModal);document.onkeydown=function(e){let t=!1,n=!1;"key"in(e=e||window.event)?(t="Escape"===e.key||"Esc"===e.key,n="k"===e.key&&!0===e.metaKey):(n=75===e.keyCode&&e.metaKey,t=27===e.keyCode),n&&e.preventDefault(),(t&&document.body.classList.contains("search-active")||n)&&toggleSearchModal()};let a=elasticlunr.Index.load(window.searchIndex),l={bool:"AND",fields:{title:{boost:2},body:{boost:1}}},r,c,d=document.getElementById("search-input");document.getElementById("search-results");d.addEventListener("keyup",function(e){if([...document.body.classList].includes("search-active")&&3<d.value.trim().length&&(r=d.value.trim(),c=a.search(r,l),Array.isArray(c))&&0<c.length){var t=document.getElementById("results-list");t.replaceChildren();for(o=0;o<c.length;o++){var n=formatResultItem(c[o]);t.appendChild(n)}}})}); </li>`)
}
function htmlToElement(e) {
var t = document.createElement("template");
return e = e.trim(), t.innerHTML = e, t.content.firstChild
}
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("search").addEventListener("click", function(e) {
e.preventDefault(), toggleSearchModal()
});
document.querySelector(".modal-overlay").addEventListener("click", toggleSearchModal);
for (var e = document.querySelectorAll(".modal-close"), o = 0; o < e.length; o++) e[o].addEventListener("click", toggleSearchModal);
document.onkeydown = function(e) {
let t = !1,
n = !1;
"key" in (e = e || window.event) ? (t = "Escape" === e.key || "Esc" === e.key, n = "k" === e.key && !0 === e.metaKey) : (n = 75 === e.keyCode && e.metaKey, t = 27 === e.keyCode), n && e.preventDefault(), (t && document.body.classList.contains("search-active") || n) && toggleSearchModal()
};
let a = elasticlunr.Index.load(window.searchIndex),
l = {
bool: "AND",
fields: {
title: {
boost: 2
},
body: {
boost: 1
}
}
},
r, c, d = document.getElementById("search-input");
document.getElementById("search-results");
d.addEventListener("keyup", function(e) {
if ([...document.body.classList].includes("search-active") && 3 < d.value.trim().length && (r = d.value.trim(), c = a.search(r, l), Array.isArray(c)) && 0 < c.length) {
var t = document.getElementById("results-list");
t.replaceChildren();
for (o = 0; o < c.length; o++) {
var n = formatResultItem(c[o]);
t.appendChild(n)
}
}
})
});

View file

@ -36,7 +36,7 @@
{% endblock title %} {% endblock title %}
</title> </title>
</head> </head>
<body class="dark:bg-gray-700 flex flex-col h-screen justify-between"> <body class="dark:bg-gray-800 flex flex-col h-screen justify-between">
<!----------------------------------------------------------> <!---------------------------------------------------------->
<!------------------------- NAVBAR -------------------------> <!------------------------- NAVBAR ------------------------->
<!----------------------------------------------------------> <!---------------------------------------------------------->
@ -113,7 +113,7 @@
<div id="search-modal" class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex z-10"> <div id="search-modal" class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex z-10">
<div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50"></div> <div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50"></div>
<div class="modal-container text-gray-800 bg-gray-200 dark:bg-gray-800 dark:text-gray-400 w-11/12 md:max-w-md mx-auto rounded-lg shadow-lg z-50 mt-40 sm:mt-32 h-36 border border-2 border-gray-800 dark:border-gray-400"> <div class="modal-container text-gray-800 bg-gray-200 dark:bg-gray-800 dark:text-gray-400 w-11/12 md:max-w-md mx-auto rounded-lg shadow-lg z-50 mt-40 sm:mt-32 h-48 border border-2 border-gray-800 dark:border-gray-400">
<div class="modal-close absolute top-0 right-0 cursor-pointer flex flex-col items-center mt-4 mr-4 text-white text-sm z-50"> <div class="modal-close absolute top-0 right-0 cursor-pointer flex flex-col items-center mt-4 mr-4 text-white text-sm z-50">
<svg class="fill-current text-white" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"> <svg class="fill-current text-white" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
@ -141,8 +141,7 @@
<ul id="results-list" class="flex flex-col justify-center max-h-2xl mt-2 overflow-scroll"></ul> <ul id="results-list" class="flex flex-col justify-center max-h-2xl mt-2 overflow-scroll"></ul>
<!--Footer--> <!--Footer-->
<div class="flex justify-end pt-2"> <div class="flex justify-end pt-2">
<button class="px-4 bg-transparent p-3 rounded-lg text-indigo-500 hover:bg-gray-100 hover:text-indigo-400 mr-2">Action</button> <button class="px-4 bg-indigo-500 p-3 rounded-lg text-white hover:bg-indigo-400">Search</button>
<button class="modal-close px-4 bg-indigo-500 p-3 rounded-lg text-white hover:bg-indigo-400">Close</button>
</div> </div>
</div> </div>
@ -230,12 +229,12 @@
<!----------------------------- Mobile menu -----------------------------> <!----------------------------- Mobile menu ----------------------------->
<div id="mobile-menu" class="sm:hidden fixed z-10 overflow-hidden"> <div id="mobile-menu" class="sm:hidden fixed z-10 overflow-hidden">
<div class="nav-links flex flex-col space-y-4 items-center w-screen bg-gray-200 dark:bg-gray-800 transition-all ease-out duration-500 h-0"> <div class="nav-links flex flex-col space-y-4 items-center w-screen bg-gray-200 dark:bg-gray-800 transition-all ease-out duration-300 h-0">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" --> <!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
{% for item in config.extra.navbar.items %} {% for item in config.extra.navbar.items %}
{% if lang == item.lang %} {% if lang == item.lang %}
{% for link in item.links %} {% for link in item.links %}
<a href="{{ link.url }}" class="text-gray-800 dark:text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">{{ link.name }}</a> <a href="{{ link.url }}" class="text-gray-800 dark:text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-3xl hover:text-3xl font-medium">{{ link.name }}</a>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -244,7 +243,7 @@
{% for item in config.extra.sidebar.items %} {% for item in config.extra.sidebar.items %}
{% if lang == item.lang %} {% if lang == item.lang %}
{% for link in item.links %} {% for link in item.links %}
<a href="{{ link.url }}" class="text-gray-800 dark:text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">{{ link.name }}</a> <a href="{{ link.url }}" class="text-gray-800 dark:text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-3xl font-medium">{{ link.name }}</a>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -255,7 +254,7 @@
<!-----------------------------------------------------------> <!----------------------------------------------------------->
<!------------------------- CONTENT -------------------------> <!------------------------- CONTENT ------------------------->
<!-----------------------------------------------------------> <!----------------------------------------------------------->
<main class="max-w-7xl mx-auto text-black dark:text-gray-200 w-full mb-auto"> <main class="max-w-7xl mx-auto text-white bg-gray-200 dark:bg-gray-800 w-full mb-auto">
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}
</main> </main>
@ -308,7 +307,7 @@
<!------------------------- SCRIPTS -------------------------> <!------------------------- SCRIPTS ------------------------->
<script defer src="/js/main.js"></script> <script defer src="/js/main.js"></script>
{% if config.extra.enable_search %} {% if config.extra.enable_search %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/elasticlunr/0.9.6/elasticlunr.min.js"></script> <script src="/js/elasticlunr.min.js"></script>
<script src="/search_index.{{lang}}.js"></script> <script src="/search_index.{{lang}}.js"></script>
<script defer src="/js/search.js"></script> <script defer src="/js/search.js"></script>
{% endif %} {% endif %}
@ -322,5 +321,7 @@
{% block extra_js %} {% block extra_js %}
{% endblock extra_js %} {% endblock extra_js %}
<script src="https://unpkg.com/htmx.org@1.9.12" integrity="sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2" crossorigin="anonymous"></script>
</body> </body>
</html> </html>

View file

@ -1,8 +1,7 @@
<a <a
class="rounded-lg outline-2 text-white bg-blue-600 outline-blue-600 px-4 py-2 text-neutral !no-underline hover:!bg-blue-500 dark:bg-blue-800 dark:hover:!bg-blue-700" class="rounded-lg text-white bg-blue-600 px-4 py-2 text-neutral !no-underline hover:!bg-blue-500 dark:bg-blue-800 dark:hover:!bg-blue-700"
{% if href %}href="{{ href }}"{% endif %} {% if href %}href="{{ href }}"{% endif %}
{% if target %}target="{{ target }}"{% endif %} {% if target %}target="{{ target }}"{% endif %}
role="button" role="button">
>
<i>{{ body }}</i> <i>{{ body }}</i>
</a> </a>

View file

@ -121,7 +121,6 @@
<span class='inline-block text-[#f39] text-sm align-sub'>* required</span> <span class='inline-block text-[#f39] text-sm align-sub'>* required</span>
</label> </label>
<input type="email" id="parent-email" name="parent-email" <input type="email" id="parent-email" name="parent-email"
pattern="^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$"
class="basis-full peer form-input class="basis-full peer form-input
{{ formClasses }} {{ formClasses }}
invalid:text-[#F39] invalid:text-[#F39]

View file

@ -57,7 +57,6 @@
<label for="email" class="basis-full">Your Email <span class='inline-block text-[#f39] text-sm align-sub'>* required</span></label> <label for="email" class="basis-full">Your Email <span class='inline-block text-[#f39] text-sm align-sub'>* required</span></label>
<input type="email" id="email" name="email" <input type="email" id="email" name="email"
pattern="^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$"
placeholder="bob@frank.xyz" placeholder="bob@frank.xyz"
class="basis-full form-input {{ formClasses }}"> class="basis-full form-input {{ formClasses }}">

View file

@ -1,4 +1,4 @@
<div class="mt-20"> <div class="mt-10">
<h1>Disciplemaking</h1> <h1>Disciplemaking</h1>
<div class="mb-8">TFCs core value is to follow the example of Jesus whose strategy of disciplemaking allowed Him to minister to all levels of spiritual interest at all times. These are those levels and where we work to reach teens at those levels. Everything we do is built around a desire to impact lives at each of these levels and help teenagers move forward through them.</div> <div class="mb-8">TFCs core value is to follow the example of Jesus whose strategy of disciplemaking allowed Him to minister to all levels of spiritual interest at all times. These are those levels and where we work to reach teens at those levels. Everything we do is built around a desire to impact lives at each of these levels and help teenagers move forward through them.</div>

View file

@ -25,7 +25,7 @@ homepage = "https://tchartron.com"
# Use snake_casing to be consistent with the rest of Zola # Use snake_casing to be consistent with the rest of Zola
[extra] [extra]
enable_search = true enable_search = true
enable_sidebar = true enable_sidebar = false
enable_adsense = true enable_adsense = true
enable_multilingue = true enable_multilingue = true
adsense_link = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=myclientid" adsense_link = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=myclientid"