Tailwind for post_list

This commit is contained in:
Michal Vanko 2024-02-27 22:53:08 +01:00
parent 0cb8e84666
commit 8201312c26
12 changed files with 273 additions and 67 deletions

View File

@ -0,0 +1,9 @@
layout {
pane size=1 borderless=true {
plugin location="zellij:tab-bar"
}
pane
pane size=2 borderless=true {
plugin location="zellij:status-bar"
}
}

View File

@ -0,0 +1,25 @@
layout {
pane size=1 borderless=true {
plugin location="zellij:tab-bar"
}
pane split_direction="vertical" focus=true {
pane edit="src/main.rs"
pane split_direction="horizontal" size=60 {
just { args "server_dev"; }
just { args "test"; }
}
}
pane_template name="just" {
command "just"
start_suspended true
}
floating_panes {
pane {
command "just"
args "tailwind"
}
}
pane size=2 borderless=true {
plugin location="zellij:status-bar"
}
}

View File

@ -17,5 +17,6 @@ serde = "1.0.195"
serde_json = "1.0.111" serde_json = "1.0.111"
tokio = { version = "1.35.1", features = ["full"] } tokio = { version = "1.35.1", features = ["full"] }
tower-http = { version = "0.5.0", features = ["trace", "fs"] } tower-http = { version = "0.5.0", features = ["trace", "fs"] }
tower-livereload = "0.9.2"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }

View File

@ -11,6 +11,10 @@ server_dev:
dev: dev:
(just server_dev; just tailwind) | parallel (just server_dev; just tailwind) | parallel
# Run dev server in watch mode
test:
cargo test
# Run server in production mode # Run server in production mode
prod: prod:
cargo run --release cargo run --release

View File

@ -4,26 +4,20 @@ pub struct Link {
} }
pub struct HeaderProps { pub struct HeaderProps {
pub links: Vec<Link>, pub back_link: Option<Link>,
} }
impl Default for HeaderProps { impl Default for HeaderProps {
fn default() -> Self { fn default() -> Self {
Self { back_link: None }
}
}
impl HeaderProps {
pub fn with_back_link(link: Link) -> Self {
Self { Self {
links: vec![ back_link: Some(link),
Link { ..Default::default()
href: "/".to_string(),
label: "Introduction".to_string(),
},
Link {
href: "/blog".to_string(),
label: "Blog".to_string(),
},
Link {
href: "/portfolio".to_string(),
label: "Portfolio".to_string(),
},
],
} }
} }
} }

View File

@ -1,5 +1,6 @@
use axum; use axum;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use tower_livereload::LiveReloadLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod components; mod components;
@ -25,6 +26,10 @@ async fn main() {
// build our application with a single route // build our application with a single route
let app = router::get_router().nest_service("/styles", ServeDir::new("styles")); let app = router::get_router().nest_service("/styles", ServeDir::new("styles"));
#[cfg(debug_assertions)]
let app = app.layer(LiveReloadLayer::new());
// run our app with hyper, listening globally on port 3080 // run our app with hyper, listening globally on port 3080
let port = std::option_env!("PORT").unwrap_or("3080"); let port = std::option_env!("PORT").unwrap_or("3080");
let addr = format!("0.0.0.0:{}", port); let addr = format!("0.0.0.0:{}", port);

View File

@ -4,7 +4,7 @@ use axum::{extract::Path, http::StatusCode};
use crate::{ use crate::{
components::{ components::{
site_footer::{render_site_footer, SiteFooter}, site_footer::{render_site_footer, SiteFooter},
site_header::HeaderProps, site_header::{HeaderProps, Link},
}, },
post_list::get_post_list, post_list::get_post_list,
post_parser::ParseResult, post_parser::ParseResult,
@ -50,12 +50,21 @@ pub async fn render_post_list(tag: Option<Path<String>>) -> Result<PostListTempl
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// TODO if we have a tag we want to go back to all posts, otherwise we don't
let header_props = match tag {
Some(_) => HeaderProps::with_back_link(Link {
href: "/blog".to_string(),
label: "All posts".to_string(),
}),
None => HeaderProps::default(),
};
Ok(PostListTemplate { Ok(PostListTemplate {
title: "Posts".to_owned(), title: "Posts".to_owned(),
posts, posts,
tag, tag,
site_footer, site_footer,
header_props: HeaderProps::default(), header_props,
}) })
} }

View File

@ -544,19 +544,188 @@ video {
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
} }
.m-3 {
margin: 0.75rem;
}
.m-1 {
margin: 0.25rem;
}
.my-3 {
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
.my-5 {
margin-top: 1.25rem;
margin-bottom: 1.25rem;
}
.my-8 {
margin-top: 2rem;
margin-bottom: 2rem;
}
.my-10 {
margin-top: 2.5rem;
margin-bottom: 2.5rem;
}
.my-12 {
margin-top: 3rem;
margin-bottom: 3rem;
}
.mx-4 {
margin-left: 1rem;
margin-right: 1rem;
}
.mb-3 {
margin-bottom: 0.75rem;
}
.block { .block {
display: block; display: block;
} }
.inline {
display: inline;
}
.flex {
display: flex;
}
.h-0 {
height: 0px;
}
.min-h-full { .min-h-full {
min-height: 100%; min-height: 100%;
} }
.flex-grow {
flex-grow: 1;
}
.content-end {
align-content: flex-end;
}
.justify-end {
justify-content: flex-end;
}
.border-blue-200 {
--tw-border-opacity: 1;
border-color: rgb(191 219 254 / var(--tw-border-opacity));
}
.bg-blue-50 { .bg-blue-50 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(239 246 255 / var(--tw-bg-opacity)); background-color: rgb(239 246 255 / var(--tw-bg-opacity));
} }
.bg-blue-800 {
--tw-bg-opacity: 1;
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
}
.p-3 {
padding: 0.75rem;
}
.p-2 {
padding: 0.5rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.pr-2 {
padding-right: 0.5rem;
}
.pr-3 {
padding-right: 0.75rem;
}
.text-right {
text-align: right;
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.text-base {
font-size: 1rem;
line-height: 1.5rem;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.font-medium {
font-weight: 500;
}
.italic {
font-style: italic;
}
.text-gray-600 {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.text-blue-700 {
--tw-text-opacity: 1;
color: rgb(29 78 216 / var(--tw-text-opacity));
}
.text-blue-800 {
--tw-text-opacity: 1;
color: rgb(30 64 175 / var(--tw-text-opacity));
}
.text-gray-800 {
--tw-text-opacity: 1;
color: rgb(31 41 55 / var(--tw-text-opacity));
}
.drop-shadow-md {
--tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06));
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 {
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);
} }
.after\:content-\[\'\2c \'\]::after {
--tw-content: ',';
content: var(--tw-content);
}

View File

@ -33,7 +33,7 @@
<link rel="icon" type="image/svg+xml" href="/m-logo.svg" /> <link rel="icon" type="image/svg+xml" href="/m-logo.svg" />
<link rel="icon" type="image/png" href="/m-logo-192.png" /> <link rel="icon" type="image/png" href="/m-logo-192.png" />
</head> </head>
<body> <body class="bg-blue-50">
{% include "site_header.html" %} {% include "site_header.html" %}
{% block content %} Placeholder {% endblock %} {% block content %} Placeholder {% endblock %}
{# footer, should be not dependant on the each individual handler but it should have it's own handler #} {# footer, should be not dependant on the each individual handler but it should have it's own handler #}

View File

@ -11,7 +11,7 @@
<ul> <ul>
{% for post in posts %} {% for post in posts %}
<li> <li class="my-12">
{% include "post_preview_card.html" %} {# {% include "post_preview_card.html" %} {#
<ArticlePreviewCard {article} {segment} /> #} {# <ArticlePreviewCard {article} {segment} /> #} {#
<ArticleFooter {article} {segment} /> #} <ArticleFooter {article} {segment} /> #}

View File

@ -1,27 +1,29 @@
<article> <article>
<header> <header class="px-4 mb-3">
<h2> <h2 class="text-2xl">
<a rel="prefetch" href="/blog/{{post.slug}}">{{post.metadata.title}}</a> <a rel="prefetch" href="/blog/{{post.slug}}">{{post.metadata.title}}</a>
</h2> </h2>
<section class="created-at m-1 text-right text-sm text-gray-600">
<span>Published on</span>
<time datetime="{post.metadata.date}"> {{post.metadata.date}} </time>
</section>
</header> </header>
{# article preview, maybe implement as a filter? #}
</article>
<footer> <p class="px-5 text-gray-800"> TODO: article preview, maybe implement as a filter?,
<div class="article-tags"> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Ald </p>
<hr class="my-3 mx-4 h-0 border-blue-200 bg-blue-800 text-blue-800" />
<footer class="px-4">
<section class="article-tags text-base">
{% if post.metadata.tags.len() > 0 %} {% if post.metadata.tags.len() > 0 %}
<span class="lighten">Tags:</span> <span class="text-gray-600">Tags:</span>
<ul> <ul class="inline">
{% for tag in post.metadata.tags %} {% for tag in post.metadata.tags %}
<li class="{tagsListLiClass}"> <li class="inline italic text-blue-700 {% if !loop.last %} after:content-[','] {% endif %}">
<a href="/blog/tags/{{tag}}">{{tag}}</a> <a href="/blog/tags/{{tag}}">{{tag}}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
</div> </div>
<div class="created-at">
<span>Published on</span>
<time datetime="{post.metadata.date}"> {{post.metadata.date}} </time>
</div>
</footer> </footer>
</article>

View File

@ -1,31 +1,19 @@
<header class="min-h-full bg-blue-50"> <header class="min-h-full bg-blue-50">
<nav class=""> <nav class="flex">
<section> {% match header_props.back_link %}
<ul class=""> {% when Some with (link) %}
{% for link in header_props.links %}
<li>
<a <a
rel="prefetch" class="p-3 text-lg font-medium drop-shadow-md"
class=""
href="{{link.href}}" href="{{link.href}}"
> >
{{link.label}} {{link.label}}
</a> </a>
</li> {% when None %}
{% endfor %} {% endmatch %}
</ul> <aside class="flex logo-section flex-grow justify-end content-end">
<a class="logo p-3 text-base" href=".">
<aside class="logo-section "> @michalvankodev
<a class="logo" href=".">
<img
class=""
src="/m-logo.svg"
alt="m logo"
width="44px"
height="44px"
/>
</a> </a>
</aside> </aside>
</section>
</nav> </nav>
</header> </header>