admin ui featured blogs
This commit is contained in:
@ -13,6 +13,11 @@ svgstore:
|
||||
server_dev:
|
||||
cargo watch -x run
|
||||
|
||||
# CMS server for local dev
|
||||
# TODO #directory-swap
|
||||
decap_server:
|
||||
cd .. && npx decap-server
|
||||
|
||||
# Run dev server in watch mode
|
||||
dev:
|
||||
(just server_dev; just tailwind) | parallel
|
||||
|
13
axum_server/src/featured_posts.rs
Normal file
13
axum_server/src/featured_posts.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::{pages::post::PostMetadata, post_list::get_post_list, post_parser::ParseResult};
|
||||
use axum::http::StatusCode;
|
||||
|
||||
pub async fn get_featured_posts() -> Result<Vec<ParseResult<PostMetadata>>, StatusCode> {
|
||||
let post_list = get_post_list::<PostMetadata>().await?;
|
||||
|
||||
let featured_posts = post_list
|
||||
.into_iter()
|
||||
.filter(|post| post.metadata.segments.contains(&"featured".to_string()))
|
||||
.collect();
|
||||
|
||||
Ok(featured_posts)
|
||||
}
|
@ -1,7 +1,20 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use tracing::debug;
|
||||
|
||||
// This filter does not have extra arguments
|
||||
pub fn pretty_date(date_time: &DateTime<Utc>) -> ::askama::Result<String> {
|
||||
let formatted = format!("{}", date_time.format("%e %B %Y"));
|
||||
Ok(formatted)
|
||||
}
|
||||
|
||||
// This filter does not have extra arguments
|
||||
pub fn description_filter(body: &String) -> ::askama::Result<String> {
|
||||
let description = body
|
||||
.lines()
|
||||
.filter(|line| line.starts_with("<p"))
|
||||
.take(3)
|
||||
.collect::<Vec<&str>>()
|
||||
.join("\n");
|
||||
debug!(description);
|
||||
Ok(description)
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
use axum;
|
||||
use axum::{self};
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_livereload::LiveReloadLayer;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
mod components;
|
||||
mod featured_posts;
|
||||
mod feed;
|
||||
mod filters;
|
||||
mod pages;
|
||||
@ -30,7 +31,11 @@ async fn main() {
|
||||
let app = router::get_router()
|
||||
.nest_service("/styles", ServeDir::new("styles"))
|
||||
.nest_service("/images", ServeDir::new("../static/images"))
|
||||
.nest_service("/svg", ServeDir::new("../static/svg"));
|
||||
.nest_service("/svg", ServeDir::new("../static/svg"))
|
||||
.nest_service(
|
||||
"/config.yml",
|
||||
ServeDir::new("../static/resources/config.yml"),
|
||||
);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let app = app.layer(LiveReloadLayer::new());
|
||||
|
9
axum_server/src/pages/admin.rs
Normal file
9
axum_server/src/pages/admin.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use askama::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "admin.html")]
|
||||
pub struct AdminPageTemplate {}
|
||||
|
||||
pub async fn render_admin() -> AdminPageTemplate {
|
||||
AdminPageTemplate {}
|
||||
}
|
@ -1,25 +1,32 @@
|
||||
use askama::Template;
|
||||
use axum::http::StatusCode;
|
||||
|
||||
use crate::filters;
|
||||
use crate::{
|
||||
components::{
|
||||
site_footer::{render_site_footer, SiteFooter},
|
||||
site_header::HeaderProps,
|
||||
},
|
||||
featured_posts::get_featured_posts,
|
||||
post_parser::ParseResult,
|
||||
tag_list::get_popular_blog_tags,
|
||||
};
|
||||
|
||||
use super::post::PostMetadata;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
pub struct IndexTemplate {
|
||||
site_footer: SiteFooter,
|
||||
header_props: HeaderProps,
|
||||
blog_tags: Vec<String>,
|
||||
featured_posts: Vec<ParseResult<PostMetadata>>,
|
||||
}
|
||||
|
||||
pub async fn render_index() -> Result<IndexTemplate, StatusCode> {
|
||||
let site_footer = tokio::spawn(render_site_footer());
|
||||
let blog_tags = tokio::spawn(get_popular_blog_tags());
|
||||
let featured_posts = tokio::spawn(get_featured_posts());
|
||||
|
||||
let blog_tags = blog_tags
|
||||
.await
|
||||
@ -29,9 +36,14 @@ pub async fn render_index() -> Result<IndexTemplate, StatusCode> {
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let featured_posts = featured_posts
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)??;
|
||||
|
||||
Ok(IndexTemplate {
|
||||
site_footer,
|
||||
header_props: HeaderProps::default(),
|
||||
blog_tags,
|
||||
featured_posts,
|
||||
})
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod admin;
|
||||
pub mod contact;
|
||||
pub mod index;
|
||||
pub mod post;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
feed::render_rss_feed,
|
||||
pages::{
|
||||
contact::render_contact, index::render_index, post::render_post,
|
||||
admin::render_admin, contact::render_contact, index::render_index, post::render_post,
|
||||
post_list::render_post_list,
|
||||
},
|
||||
};
|
||||
@ -16,6 +16,7 @@ pub fn get_router() -> Router {
|
||||
.route("/blog/tags/:tag", get(render_post_list))
|
||||
.route("/blog/:post_id", get(render_post))
|
||||
.route("/contact", get(render_contact))
|
||||
.route("/admin", get(render_admin))
|
||||
.route("/feed.xml", get(render_rss_feed))
|
||||
.layer(
|
||||
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
||||
|
@ -554,6 +554,10 @@ video {
|
||||
--tw-contain-style: ;
|
||||
}
|
||||
|
||||
.row-span-3 {
|
||||
grid-row: span 3 / span 3;
|
||||
}
|
||||
|
||||
.m-1 {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
@ -596,16 +600,6 @@ video {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
.my-0 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.my-0\.5 {
|
||||
margin-top: 0.125rem;
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.my-12 {
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 3rem;
|
||||
@ -653,6 +647,10 @@ video {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@ -685,6 +683,14 @@ video {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.grid-flow-col {
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
|
||||
.grid-rows-3 {
|
||||
grid-template-rows: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
@ -713,6 +719,10 @@ video {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
15
axum_server/templates/admin.html
Normal file
15
axum_server/templates/admin.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<link rel="icon" type="image/svg+xml" href="/m-logo.svg" />
|
||||
<link rel="icon" type="image/png" href="/m-logo-192.png" />
|
||||
<title>Content Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Include the script that builds the page and powers Decap CMS -->
|
||||
<script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
|
||||
</body>
|
||||
</html>
|
15
axum_server/templates/components/post_preview.html
Normal file
15
axum_server/templates/components/post_preview.html
Normal file
@ -0,0 +1,15 @@
|
||||
<article class="grid grid-rows-3 grid-flow-col gap-4">
|
||||
<aside class="row-span-3">
|
||||
<svg aria-hidden="true" class="h-12 w-12 fill-blue-950">
|
||||
<use xlink:href="/svg/icons-sprite.svg#mail" />
|
||||
</svg>
|
||||
</aside>
|
||||
<header>
|
||||
<h3 class="text-lg font-medium mb-1">{{post.metadata.title}}</h3>
|
||||
</header>
|
||||
<section class="text-sm leading-5 text-gray-800">{{post.body|description_filter|safe}}</section>
|
||||
<footer>
|
||||
Footrik
|
||||
</footer>
|
||||
</article>
|
||||
|
@ -51,7 +51,11 @@
|
||||
<hr class="border-blue-950 m-5">
|
||||
|
||||
<ul class="mx-5">
|
||||
|
||||
{% for post in featured_posts %}
|
||||
<li>
|
||||
{% include "components/post_preview.html" %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
|
Reference in New Issue
Block a user