Skip to content

Commit

Permalink
5: Add markdown rendering to eportfolio (#16)
Browse files Browse the repository at this point in the history
* added markdown dependency and created markdown render operation

* move file path of markdown operation to context

* refactor course routes and context and add course list page

* styling to courses list and more in context

* tailwindstyles and under development markers

* simplify error handling into function
  • Loading branch information
CJGutz authored Sep 19, 2024
1 parent d279454 commit e161e85
Show file tree
Hide file tree
Showing 14 changed files with 1,405 additions and 182 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
/target
package.json
pnpm-lock.yaml
**/*/node_modules
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions eportfolio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
markdown = "1.0.0-alpha.20"
unchained = { package = "unchained_web", path = "../unchained" }
120 changes: 69 additions & 51 deletions eportfolio/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::collections::HashMap;

pub mod render_markdown;

use render_markdown::render_md;
use unchained::{
error::Error,
router::{HTTPVerb::*, ResponseContent, Route},
router::{HTTPVerb::*, Request, Response, ResponseContent, Route},
server::Server,
templates::{
context::{ctx_map, ctx_str, ctx_vec, ContextTree, Primitive},
Expand Down Expand Up @@ -30,6 +33,13 @@ fn handle_error(e: &Error) -> String {
}
}

fn load_tmpl_and_handle_error(path: &str, context: Option<HashMap<String, ContextTree>>) -> String {
match load_template(path, context, &RenderOptions::empty()) {
Ok(template) => template.to_string(),
Err(e) => handle_error(&e),
}
}

fn create_skill(
id: &str,
name: &str,
Expand Down Expand Up @@ -74,15 +84,23 @@ fn create_experience(
])
}

fn create_course(course_id: &str, title: &str, image_path: &str) -> ContextTree {
ctx_map([
("course_id", ctx_str(course_id)),
("title", ctx_str(title)),
("image", ctx_str(image_path)),
])
}

fn folder_access(path: &str) -> Route {
Route::new(GET, path, ResponseContent::FolderAccess)
}

fn main() {
let mut context_landing = HashMap::new();
let mut context_base = HashMap::new();

let current_year: isize = current_year().try_into().unwrap();
context_landing.insert(
context_base.insert(
"current_year".to_string(),
ContextTree::Leaf(Primitive::Num(current_year)),
);
Expand All @@ -94,12 +112,14 @@ fn main() {
("label", ctx_str("Experience")),
]),
ctx_map([("href", ctx_str("/skills")), ("label", ctx_str("Skills"))]),
ctx_map([("href", ctx_str("/courses")), ("label", ctx_str("Courses"))]),
]);
context_landing.insert("page_links".to_string(), page_links.clone());
context_base.insert("page_links".to_string(), page_links.clone());

let mut context_skills = context_landing.clone();
let mut context_experience = context_landing.clone();
let context_404 = context_landing.clone();
let mut context_landing = context_base.clone();
let mut context_skills = context_base.clone();
let mut context_experience = context_base.clone();
let mut context_courses = context_base.clone();

context_landing.insert(
"carl_images".to_string(),
Expand Down Expand Up @@ -139,53 +159,52 @@ fn main() {
create_experience("tihlde-index", "Programmer with TIHLDE Index", "Worked as a Back-end developer for index.", "tihlde.jpg", "Aug 2021", "Jun 2022", "https://tihlde.org", "https://github.com/tihlde/lepton", vec!["Django", "Docker"]),
]));

let start = std::time::Instant::now();
let landing = load_template(
"templates/landing.html",
Some(context_landing),
&RenderOptions::empty(),
);
let skills = load_template(
"templates/skills.html",
Some(context_skills),
&RenderOptions::empty(),
);
let experience = load_template(
"templates/experience.html",
Some(context_experience),
&RenderOptions::empty(),
);
let page_404 = load_template(
"templates/404.html",
Some(context_404),
&RenderOptions::empty(),
context_courses.insert(
"course_pages".to_string(),
ctx_vec(vec![
create_course("CS4515", "3D Computer Graphics and Animation", ""),
create_course("CS4505", "Software Architecture", ""),
create_course("DSAIT4005", "Machine and Deep Learning", ""),
create_course("CS4510", "Formal Reasoning about Software", ""),
]),
);

let start = std::time::Instant::now();
let landing =
load_tmpl_and_handle_error("templates/landing.html", Some(context_landing.clone()));
let skills = load_tmpl_and_handle_error("templates/skills.html", Some(context_skills));
let experience =
load_tmpl_and_handle_error("templates/experience.html", Some(context_experience));
let courses = load_tmpl_and_handle_error("templates/course-list.html", Some(context_courses));
let page_404 = load_tmpl_and_handle_error("templates/404.html", Some(context_landing));
let duration = start.elapsed();
println!("Finished rendering after {} s", duration.as_secs_f64());

let routes = vec![
Route::new(GET, "/", ResponseContent::Str(landing)),
Route::new(GET, "/skills", ResponseContent::Str(skills)),
Route::new(GET, "/experience", ResponseContent::Str(experience)),
Route::new(GET, "/courses", ResponseContent::Str(courses)),
Route::new(
GET,
"/",
ResponseContent::Str(match &landing {
Ok(template) => template.to_string(),
Err(e) => handle_error(e),
}),
),
Route::new(
GET,
"/skills",
ResponseContent::Str(match &skills {
Ok(template) => template.to_string(),
Err(e) => handle_error(e),
}),
),
Route::new(
GET,
"/experience",
ResponseContent::Str(match &experience {
Ok(template) => template.to_string(),
Err(e) => handle_error(e),
"courses/:courseid",
ResponseContent::FromRequest({
let page_404 = page_404.clone();
Box::new(move |req: Request| {
let md = if let Some(courseid) = req.path_params.get("courseid") {
let mut ctx = context_base.clone();
let path = format!("templates/markdown/courses/{}.md", courseid);
ctx.insert("course_md_path".to_string(), ctx_str(&path));
render_md("templates/course-detail.html", Some(ctx)).ok()
} else {
None
};
let is_some = md.is_some();
Response::new(
Some(md.unwrap_or(page_404.clone())),
if is_some { 200 } else { 404 },
)
})
}),
),
folder_access("/images/*"),
Expand All @@ -197,10 +216,9 @@ fn main() {
Route::new(
GET,
"/*",
ResponseContent::Str(match &page_404 {
Ok(template) => template.to_string(),
Err(e) => handle_error(e),
}),
ResponseContent::FromRequest(Box::new(move |_req: Request| {
Response::new(Some(page_404.clone()), 404)
})),
),
];
let mut server = Server::new(routes);
Expand Down
36 changes: 36 additions & 0 deletions eportfolio/src/render_markdown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use std::collections::HashMap;

use markdown::Options;
use unchained::{
error::WebResult,
templates::{
context::{ContextMap, ContextTree::*, Primitive::*},
operations::{unwrap_n_params, TemplateOperation},
render::{load_template, RenderOptions},
},
};

pub fn render_md(path: &str, context: Option<ContextMap>) -> WebResult<String> {
let closure = {
|call, ctx, opts| {
let file_path = unwrap_n_params::<1>(&call.parameters)?[0];
let path_in_context = ctx.get(file_path);
let file_path = if let Some(Leaf(Str(valid_path))) = path_in_context {
valid_path
} else {
file_path
};
let file_content = load_template(file_path, Some(ctx.clone()), opts)?;
let md = markdown::to_html_with_options(&file_content, &Options::gfm());
Ok(md.unwrap_or_default())
}
} as TemplateOperation;

load_template(
path,
context.clone(),
&RenderOptions {
custom_operations: HashMap::from([("md", closure)]),
},
)
}
4 changes: 3 additions & 1 deletion eportfolio/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ module.exports = {
},
},
},
plugins: [],
plugins: [
require('@tailwindcss/typography'),
],
}
2 changes: 1 addition & 1 deletion eportfolio/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
</div>
</nav>
</header>
<main class="container relative">
<main class="grow container relative">
{* slot default *}
</main>
<footer class="p-4 z-10 md:flex md:items-center md:justify-around md:p-6 bg-off-white shadow-[0px_0px_10px_2px_rgba(0,0,0,0.3)]">
Expand Down
9 changes: 9 additions & 0 deletions eportfolio/templates/course-detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{* component templates/base.html {
<div class="flex flex-col gap-5 items-start px-6 lg:px-20 py-6">
<a href="/courses" class="border-b border-black mvn-button">🠐 List of courses</a>
<div class="grid w-full">
<div class="prose prose-sm lg:prose-lg m-auto">
{* md course_md_path *}
</div>
</div></div>
} *}
22 changes: 22 additions & 0 deletions eportfolio/templates/course-list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{* comment {
Takes in a list of page information called "pages". Each has a course id (also file name), title, and possible image path.
} *}
{* component templates/base.html {
<div class="w-full my-10">
<div class="m-10">
<h1 class="text-3xl lg:text-5xl font-bold lg:w-2/3">Courses</h1>
<p class="text-gray-700">Following is a list of pages with information about some of the courses I have taken.</p>
</div>
<div class="grid lg:grid-cols-3 grid-cols-1 gap-5 p-4 place-items-stretch">
{* for page in course_pages {
<a href="/courses/{* get page.course_id *}" class="focus:ring-2 focus:rounded-lg focus:ring-black focus:outline-none">
<div class="border-2 border-gray-500 rounded-lg p-4 bg-zinc-50 mvn-button shadow-gray-800 shadow-md h-full">
<p class="text-sm ">{* get page.course_id *}</p>
<h3 class="text-xl">{* get page.title *}</h3>
</div>
</a>
} *}
</div>
</div>
} *}

Loading

0 comments on commit e161e85

Please sign in to comment.