Compare commits

..

No commits in common. "2979e212858cab14b9468d0ec27cdbbbce9942ae" and "49f830cd4477b2a59e7a028098be47c67d17ce18" have entirely different histories.

27 changed files with 67 additions and 309 deletions

View File

@ -28,7 +28,7 @@ This week I've attended a [Rusty game jam #2](https://itch.io/jam/rusty-jam-2).
![Egg fetcher game preview](/images/uploads/screenshot-from-2022-06-26-22-37-16.png "Egg fetcher game preview")
[You can check the result built with WASM here.](/showcase/egg-fetcher)
[You can check the result built with WASM here.](/showcase/egg-fetcher/)
## What's up with the weeklys

View File

@ -30,7 +30,7 @@ test_watch:
cargo watch -x test
# Run server in production mode
prod $TARGET="PROD" $RUST_LOG="info":
prod $TARGET="PROD":
cargo run --release
# Wait for port to listen to connections
@ -52,7 +52,6 @@ clean:
# SSG
ssg:
- wget --no-convert-links -r -p -E -P dist --no-host-directories 127.0.0.1:{{port}}
find generated_images/ -name "*_og*" -exec cp --parents {} dist/ \;
# Preview server
preview:

View File

@ -1,2 +0,0 @@
User-agent: *
Disallow: /admin

View File

@ -4,19 +4,13 @@ use tracing::debug;
use crate::{
blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
post_utils::{post_listing::get_post_list, post_parser::ParseResult},
post_utils::post_listing::get_post_list,
};
pub async fn get_popular_tags(segment: Option<String>) -> Result<Vec<String>, StatusCode> {
pub async fn get_popular_blog_tags() -> Result<Vec<String>, StatusCode> {
const TAGS_LENGTH: usize = 7;
let mut post_list = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH).await?;
post_list.retain(|post| post.metadata.published);
if let Some(segment) = segment {
post_list.retain(|post| post.metadata.segments.contains(&segment));
}
let post_list = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH).await?;
let tags_sum = post_list
.into_iter()
.flat_map(|post| post.metadata.tags)
@ -33,32 +27,12 @@ pub async fn get_popular_tags(segment: Option<String>) -> Result<Vec<String>, St
debug!("Tag: {}, Count: {}", tag, count);
}
let popular_tags = sorted_tags_by_count
let popular_blog_tags = sorted_tags_by_count
.into_iter()
.map(|tag_count| tag_count.0)
.filter(|tag| tag != "dev")
.take(TAGS_LENGTH)
.collect::<Vec<String>>();
Ok(popular_tags)
}
pub fn get_posts_by_tag(
post_list: Vec<ParseResult<BlogPostMetadata>>,
tag: &Option<String>,
) -> Vec<ParseResult<BlogPostMetadata>> {
match tag {
Some(tag) => post_list
.into_iter()
.filter(|post| {
post.metadata
.tags
.iter()
.map(|post_tag| post_tag.to_lowercase())
.collect::<String>()
.contains(&tag.to_lowercase())
})
.collect(),
None => post_list,
}
Ok(popular_blog_tags)
}

View File

@ -49,5 +49,5 @@ pub async fn render_rss_feed() -> Result<impl IntoResponse, StatusCode> {
.build();
let response = feed_builder.to_string();
Ok(([(header::CONTENT_TYPE, "application/xml")], response))
return Ok(([(header::CONTENT_TYPE, "application/xml")], response));
}

View File

@ -1,5 +1,4 @@
use askama_axum::IntoResponse;
use axum::{self, extract::OriginalUri, http::StatusCode};
use axum::{self};
use tower_http::services::ServeDir;
use tower_livereload::LiveReloadLayer;
use tracing::info;
@ -34,14 +33,9 @@ async fn main() {
.nest_service("/styles", ServeDir::new("styles"))
.nest_service("/images", ServeDir::new("static/images"))
.nest_service("/fonts", ServeDir::new("static/fonts"))
.nest_service("/files", ServeDir::new("static/files"))
.nest_service("/generated_images", ServeDir::new("generated_images"))
.nest_service("/egg-fetcher", ServeDir::new("static/egg-fetcher"))
.nest_service("/svg", ServeDir::new("static/svg"))
.nest_service("/config.yml", ServeDir::new("static/resources/config.yml")) // Decap CMS config
.nest_service("/robots.txt", ServeDir::new("robots.txt"));
let app = app.fallback(handler_404);
.nest_service("/config.yml", ServeDir::new("static/resources/config.yml")); // Decap CMS config
#[cfg(debug_assertions)]
let app = app.layer(LiveReloadLayer::new());
@ -54,11 +48,6 @@ async fn main() {
axum::serve(listener, app).await.unwrap();
}
async fn handler_404(OriginalUri(original_uri): OriginalUri) -> impl IntoResponse {
info!("{original_uri} not found");
(StatusCode::NOT_FOUND, "nothing to see here")
}
// TODO Socials
// - fotos
// background gradient color
@ -67,8 +56,3 @@ async fn handler_404(OriginalUri(original_uri): OriginalUri) -> impl IntoRespons
// TODO after release
// OG tags
// - projects page
// TODO broken links
// showcase/eggfetcher
// broadcasts/
// manifest.json
//

View File

@ -1,6 +1,6 @@
use askama::Template;
use axum::{
extract::{OriginalUri, Path},
extract::{OriginalUri, Path, RawQuery},
http::StatusCode,
};
use tokio::try_join;
@ -9,7 +9,7 @@ use tracing::debug;
use crate::{
blog_posts::{
blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
tag_list::{get_popular_tags, get_posts_by_tag},
tag_list::get_popular_blog_tags,
},
components::site_header::{HeaderProps, Link},
filters,
@ -21,11 +21,10 @@ use crate::{
#[template(path = "blog_post_list.html")]
pub struct PostListTemplate {
pub title: String,
pub og_title: String,
pub segment: String,
pub posts: Vec<ParseResult<BlogPostMetadata>>,
pub tag: Option<String>,
pub header_props: HeaderProps,
pub tags: Vec<String>,
pub blog_tags: Vec<String>,
pub featured_projects: Vec<ParseResult<ProjectMetadata>>,
pub current_url: String,
}
@ -38,17 +37,30 @@ pub async fn render_blog_post_list(
let tag = tag.map(|Path(tag)| tag);
let (blog_tags, featured_projects, mut post_list) = try_join!(
get_popular_tags(Some("blog".to_string())),
get_popular_blog_tags(),
get_featured_projects(),
get_post_list::<BlogPostMetadata>(BLOG_POST_PATH)
)?;
post_list.sort_by_key(|post| post.metadata.date);
post_list.retain(|post| post.metadata.published);
post_list.retain(|post| post.metadata.segments.contains(&"blog".to_string()));
post_list.reverse();
let posts = get_posts_by_tag(post_list, &tag);
let posts = match &tag {
Some(tag) => post_list
.into_iter()
.filter(|post| {
post.metadata
.tags
.iter()
.map(|post_tag| post_tag.to_lowercase())
.collect::<String>()
.contains(&tag.to_lowercase())
})
.collect(),
None => post_list,
};
let header_props = match tag {
Some(_) => HeaderProps::with_back_link(Link {
href: "/blog".to_string(),
@ -59,19 +71,18 @@ pub async fn render_blog_post_list(
debug!("uri:{:?}", original_uri);
let (title, og_title) = if let Some(tag) = &tag {
(format!("#{tag}"), format!("{tag} blog posts"))
let title = if let Some(tag) = &tag {
format!("{tag} blog posts")
} else {
("Blog posts".to_string(), "Blog posts".to_string())
"Blog posts".to_string()
};
Ok(PostListTemplate {
title,
og_title,
segment: "blog".to_string(),
posts,
tag,
header_props,
tags: blog_tags,
blog_tags,
featured_projects,
current_url: original_uri.to_string(),
})

View File

@ -1,5 +1,4 @@
use askama::Template;
use axum::extract::OriginalUri;
use axum::{extract::Path, http::StatusCode};
use chrono::{DateTime, Utc};
@ -18,38 +17,15 @@ pub struct BlogPostTemplate {
pub body: String,
pub date: DateTime<Utc>,
pub tags: Vec<String>,
pub segment: String,
pub header_props: HeaderProps,
pub slug: String,
pub thumbnail: Option<String>,
}
pub async fn render_blog_post(
Path(post_id): Path<String>,
OriginalUri(original_uri): OriginalUri,
) -> Result<BlogPostTemplate, StatusCode> {
pub async fn render_blog_post(Path(post_id): Path<String>) -> Result<BlogPostTemplate, StatusCode> {
let path = format!("{}/{}.md", BLOG_POST_PATH, post_id);
let parse_post = parse_post::<BlogPostMetadata>(&path, true);
let parsed = parse_post.await?;
let segment = if original_uri.to_string().starts_with("/blog") {
"blog"
} else if original_uri.to_string().starts_with("/broadcasts") {
"broadcasts"
} else {
"blog"
};
let header_props = match segment {
"blog" => HeaderProps::with_back_link(Link {
href: "/blog".to_string(),
label: "All posts".to_string(),
}),
"broadcasts" => HeaderProps::with_back_link(Link {
href: "/broadcasts".to_string(),
label: "All broadcasts".to_string(),
}),
_ => HeaderProps::default(),
};
Ok(BlogPostTemplate {
title: parsed.metadata.title,
@ -57,8 +33,10 @@ pub async fn render_blog_post(
tags: parsed.metadata.tags,
body: parsed.body,
slug: parsed.slug,
segment: segment.to_string(),
thumbnail: parsed.metadata.thumbnail,
header_props,
header_props: HeaderProps::with_back_link(Link {
href: "/blog".to_string(),
label: "All posts".to_string(),
}),
})
}

View File

@ -1,66 +0,0 @@
use axum::{
extract::{OriginalUri, Path},
http::StatusCode,
};
use tokio::try_join;
use tracing::debug;
use crate::{
blog_posts::{
blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
tag_list::{get_popular_tags, get_posts_by_tag},
},
components::site_header::{HeaderProps, Link},
post_utils::post_listing::get_post_list,
projects::featured_projects::get_featured_projects,
};
use super::blog_post_list::PostListTemplate;
pub async fn render_broadcast_post_list(
tag: Option<Path<String>>,
OriginalUri(original_uri): OriginalUri,
) -> Result<PostListTemplate, StatusCode> {
// I will forget what happens here in a week. But essentially it's pattern matching and shadowing
let tag = tag.map(|Path(tag)| tag);
let (popular_tags, featured_projects, mut post_list) = try_join!(
get_popular_tags(Some("broadcasts".to_string())),
get_featured_projects(),
get_post_list::<BlogPostMetadata>(BLOG_POST_PATH)
)?;
post_list.sort_by_key(|post| post.metadata.date);
post_list.retain(|post| post.metadata.published);
post_list.retain(|post| post.metadata.segments.contains(&"broadcasts".to_string()));
post_list.reverse();
let posts = get_posts_by_tag(post_list, &tag);
let header_props = match tag {
Some(_) => HeaderProps::with_back_link(Link {
href: "/broadcasts".to_string(),
label: "All broadcasts".to_string(),
}),
None => HeaderProps::default(),
};
debug!("uri:{:?}", original_uri);
let title = if let Some(tag) = &tag {
format!("#{tag} broadcasts")
} else {
"Broadcasts".to_string()
};
Ok(PostListTemplate {
title: title.clone(),
og_title: title,
segment: "broadcasts".to_string(),
posts,
header_props,
tags: popular_tags,
featured_projects,
current_url: original_uri.to_string(),
})
}

View File

@ -5,7 +5,7 @@ use tokio::try_join;
use crate::{
blog_posts::{
blog_post_model::BlogPostMetadata, featured_blog_posts::get_featured_blog_posts,
tag_list::get_popular_tags,
tag_list::get_popular_blog_tags,
},
components::site_header::HeaderProps,
filters,
@ -24,7 +24,7 @@ pub struct IndexTemplate {
pub async fn render_index() -> Result<IndexTemplate, StatusCode> {
let (blog_tags, featured_blog_posts, featured_projects) = try_join!(
get_popular_tags(Some("blog".to_string())),
get_popular_blog_tags(),
get_featured_blog_posts(),
get_featured_projects()
)?;

View File

@ -1,8 +1,6 @@
pub mod admin;
pub mod blog_post_list;
pub mod blog_post_page;
pub mod broadcast_list;
pub mod contact;
pub mod index;
pub mod project_list;
pub mod showcase;

View File

@ -1,16 +0,0 @@
use askama::Template;
use axum::http::StatusCode;
use crate::components::site_header::HeaderProps;
#[derive(Template)]
#[template(path = "egg_fetcher_page.html")]
pub struct EggFetcherShowcaseTemplate {
header_props: HeaderProps,
}
pub async fn render_egg_fetcher() -> Result<EggFetcherShowcaseTemplate, StatusCode> {
Ok(EggFetcherShowcaseTemplate {
header_props: HeaderProps::default(),
})
}

View File

@ -1 +0,0 @@
pub mod egg_fetcher;

View File

@ -1,63 +0,0 @@
use std::{path::Path, sync::Arc};
use anyhow::Context;
use image::ImageReader;
use tracing::debug;
use super::{
image_generator::generate_images,
picture_markup_generator::{get_export_formats, get_generated_file_name, get_image_path},
};
pub fn generate_image_with_src(
orig_img_path: &str,
width: u32,
height: u32,
suffix: &str,
generate_image: bool,
) -> Result<String, anyhow::Error> {
let path_to_generated = get_generated_file_name(orig_img_path);
let file_stem = path_to_generated.file_stem().unwrap().to_str().unwrap();
let path_to_generated = path_to_generated.with_file_name(format!("{file_stem}{suffix}"));
let disk_img_path =
Path::new("static/").join(orig_img_path.strip_prefix("/").unwrap_or(orig_img_path));
let resolutions = [(width, height, 1.)];
let exported_formats = get_export_formats(orig_img_path);
let exported_format = *exported_formats.first().unwrap();
let path_to_generated_arc = Arc::new(path_to_generated);
let path_to_generated_clone = Arc::clone(&path_to_generated_arc);
if generate_image {
rayon::spawn(move || {
let orig_img = ImageReader::open(&disk_img_path)
.with_context(|| format!("Failed to read instrs from {:?}", &disk_img_path))
.unwrap()
.decode()
.unwrap();
let path_to_generated = path_to_generated_clone.as_ref();
let result = generate_images(
&orig_img,
path_to_generated,
&resolutions,
&[exported_format],
)
.with_context(|| "Failed to generate images".to_string());
if let Err(e) = result {
tracing::error!("Error: {}", e);
}
});
}
let path_to_generated = Arc::clone(&path_to_generated_arc);
let image_path = get_image_path(
&path_to_generated,
resolutions.first().expect("Should this error ever happen?"),
&exported_format,
);
Ok(image_path)
}

View File

@ -23,6 +23,5 @@ It should be used from the templates as well
pub mod export_format;
pub mod image_generator;
pub mod image_src_generator;
pub mod picture_markup_generator;
pub mod resolutions;

View File

@ -119,7 +119,7 @@ pub fn generate_picture_markup(
Ok(result)
}
pub fn get_image_path(path: &Path, resolution: &(u32, u32, f32), format: &ExportFormat) -> String {
fn get_image_path(path: &Path, resolution: &(u32, u32, f32), format: &ExportFormat) -> String {
let path_name = path.to_str().expect("Image has to have a valid path");
let (width, height, _) = resolution;
let extension = format.get_extension();
@ -197,7 +197,7 @@ fn strip_prefixes(path: &Path) -> &Path {
parent_path
}
pub fn get_generated_file_name(orig_img_path: &str) -> PathBuf {
fn get_generated_file_name(orig_img_path: &str) -> PathBuf {
let path = Path::new(&orig_img_path);
// let parent = path
// .parent()
@ -247,7 +247,7 @@ fn generate_srcset(path: &Path, format: &ExportFormat, resolutions: &[(u32, u32,
.join(", ")
}
pub fn get_export_formats(orig_img_path: &str) -> Vec<ExportFormat> {
fn get_export_formats(orig_img_path: &str) -> Vec<ExportFormat> {
let path = Path::new(&orig_img_path)
.extension()
.and_then(|ext| ext.to_str());

View File

@ -2,9 +2,8 @@ use crate::{
feed::render_rss_feed,
pages::{
admin::render_admin, blog_post_list::render_blog_post_list,
blog_post_page::render_blog_post, broadcast_list::render_broadcast_post_list,
contact::render_contact, index::render_index, project_list::render_projects_list,
showcase::egg_fetcher::render_egg_fetcher,
blog_post_page::render_blog_post, contact::render_contact, index::render_index,
project_list::render_projects_list,
},
};
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
@ -17,12 +16,8 @@ pub fn get_router() -> Router {
.route("/blog", get(render_blog_post_list))
.route("/blog/tags/:tag", get(render_blog_post_list))
.route("/blog/:post_id", get(render_blog_post))
.route("/broadcasts", get(render_broadcast_post_list))
.route("/broadcasts/tags/:tag", get(render_broadcast_post_list))
.route("/broadcasts/:post_id", get(render_blog_post))
.route("/contact", get(render_contact))
.route("/showcase", get(render_projects_list))
.route("/showcase/:project_slug", get(render_egg_fetcher))
.route("/admin", get(render_admin))
.route("/feed.xml", get(render_rss_feed))
.layer(

View File

@ -2125,11 +2125,6 @@ article a:visited {
.xl\:grid-cols-\[1fr_2fr\] {
grid-template-columns: 1fr 2fr;
}
.xl\:gap-x-32 {
-moz-column-gap: 8rem;
column-gap: 8rem;
}
}
@media print {

View File

@ -26,6 +26,8 @@
<!-- Tailwind output file -->
<link rel="stylesheet" href="/styles/output.css" />
<link rel="manifest" href="/manifest.json" />
<link rel="icon" type="image/svg+xml" href="/images/m-logo.svg" />
<link rel="icon" type="image/png" href="/images/m-logo-192.png" />
</head>

View File

@ -3,11 +3,10 @@
{% block og_meta %}
<meta property="og:title" content="{{title}}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://michalvanko.dev/{{segment}}/{{slug}}" />
<meta property="og:url" content="https://michalvanko.dev/blog/{{slug}}" />
{% match thumbnail %}
{% when Some with (img) %}
{% let src = crate::picture_generator::image_src_generator::generate_image_with_src(img, 1200, 630, "_og", true).unwrap_or("thumbnail not found".to_string())|safe %}
<meta property="og:image" content="https://michalvanko.dev{{src}}" />
<meta property="og:image" content="https://michalvanko.dev{{img}}" />
{% when None %}
<meta property="og:image" content="https://michalvanko.dev/images/m-logo.svg" />
{% endmatch %}

View File

@ -2,7 +2,7 @@
{% extends "base.html" %}
{% block og_meta %}
<meta property="og:title" content="{{og_title}} @michalvankodev" />
<meta property="og:title" content="{{title}} @michalvankodev" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://michalvanko.dev{{current_url}}" />
<meta property="og:image" content="https://michalvanko.dev/images/m-logo.svg" />
@ -10,20 +10,24 @@
{% block content %}
<section id="blog-container" class="lg:grid lg:grid-cols-[2fr_1fr] lg:grid-rows-[min-content_1fr] xl:gap-x-32 max-w-maxindex mx-auto">
<section id="blog-container" class="lg:grid lg:grid-cols-[2fr_1fr] lg:grid-rows-[min-content_1fr] lg:gap-x-32 max-w-maxindex mx-auto">
<section id="blog-list" class="lg:row-span-2">
{% if posts.len() == 0 %}
<p class="no-posts">You've found void in the space.</p>
{% else %}
<h1 class="m-5 text-4xl text-blue-950 font-extrabold md:text-6xl">
{{title}}
{% if let Some(t) = tag %}
#{{t}}
{% else %}
Blog posts
{% endif %}
</h1>
<section id="blog-tags">
<ul class="mx-5">
{% for tag in tags %}
{% for tag in blog_tags %}
<li class="inline-block mx-0.5 p-0.5 md:text-xl">
<a href="/{{segment}}/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
<a href="/blog/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
</li>
{% endfor %}
</ul>

View File

@ -11,7 +11,7 @@
</aside>
<header>
<h3 class="text-lg font-bold mb-1 md:text-3xl">
<a rel="prefetch" href="/{{segment}}/{{post.slug}}" class="text-blue-950 visited:text-purple-700 no-underline">{{post.metadata.title}}</a>
<a rel="prefetch" href="/blog/{{post.slug}}" class="text-blue-950 visited:text-purple-700 no-underline">{{post.metadata.title}}</a>
</h3>
</header>
<section class="text-base leading-5 text-slate-800 md:text-xl text-justify">{{post.body|description_filter|safe}}</section>
@ -19,7 +19,7 @@
<ul class="inline-block">
{% for tag in post.metadata.tags %}
<li class="inline-block">
<a href="/{{segment}}/tags/{{tag}}" class="text-pink-950 no-underline">#{{tag|capitalize}}</a>
<a href="/blog/tags/{{tag}}" class="text-pink-950 no-underline">#{{tag|capitalize}}</a>
</li>
{% endfor %}
</ul>

View File

@ -1,16 +0,0 @@
{% extends "base.html" %}
{% block title %}Egg fetcher showcase{% endblock %}
{% block og_meta %}
<meta property="og:title" content="Egg fetcher showcase" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://michalvanko.dev/showcase/egg-fetcher" />
<meta property="og:image" content="https://michalvanko.dev/images/m-logo.svg" />
{% endblock %}
{% block content %}
<article class="article-body">
{% include "showcase/egg-fetcher.html" %}
</article>
{% endblock %}

View File

@ -41,13 +41,12 @@
</section>
<section id="blog" class="lg:col-span-2 lg:row-start-2 xl:col-auto xl:row-start-auto xl:row-span-2">
{% let segment = "blog".to_string() %}
<h2 class="text-blue-950 font-bold text-2xl md:text-4xl m-5"><a href="/blog" class="text-blue-950 no-underline">Blog</a></h2>
<section id="blog-tags">
<ul class="mx-5">
{% for tag in blog_tags %}
<li class="inline-block mx-0.5 p-0.5 md:text-xl">
<a href="/{{segment}}/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
<a href="/blog/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
</li>
{% endfor %}
</ul>

View File

@ -3,7 +3,7 @@
<ul class="inline">
{% for tag in tags %}
<li class="inline italic text-blue-700 md:text-lg">
<a href="/{{segment}}/tags/{{tag}}">#{{tag}}</a>
<a href="/blog/tags/{{tag}}">#{{tag}}</a>
</li>
{% endfor %}
</ul>

View File

@ -1,15 +0,0 @@
<h1>Egg-fetcher</h1>
<p>As mentioned in <a href="/blog/2022-06-26-our-attempt-at-rusty-game-jam-weekly-25-2022">Weekly #25-2022</a>, I've attended the <a href="https://itch.io/jam/rusty-jam-2">Rusty game jam #2</a> where we had a week to <strong>create a game with Rust</strong>.</p>
<p>I've teamed up with <a href="https://github.com/silen-z/">@silen-z</a>. We haven't been able to finish the game. We didn't even make the mechanics we were thinking of. But I'd like to show <strong>the incomplete version of Egg-fetcher</strong> anyway. As we built it with Rust and <a href="https://bevyengine.org/">bevy engine</a> we were able to <a href="https://github.com/septum/rusty_jam_bevy_template">reuse a template</a> that had a configured WASM build. Therefore is very easy to just present the game in the browser.</p>
<iframe title="Egg fetcher game" src="/egg-fetcher/index.html" width="800" height="600"></iframe>
<p><strong>The only functional controls are arrows</strong>. We have built a collision system where the chickens should move out of the way of the player and his pet. The player is not able to move through the fences and so on. We wanted to <strong>create a puzzle</strong> where you would have to play fetch with your pet dog to control the chickens and simultaneously control player movement.</p>
<p>This was only my 3rd attempt at a Rust codebase and therefore I got pretty much always stuck at some problem with borrow-checker or lifetimes.
I learned many things and I'd like to continue with Rust and use it more in my side-projects.</p>
<style>
iframe {
height: 720px;
width: 1280px !important;
max-width: 1280px !important;
}
</style>

View File

@ -25,17 +25,17 @@
rel="license noopener noreferrer"
style="display: inline-block"
>CC BY-NC-ND 4.0<img
src="/images/creative-commons/cc.svg"
src="images/creative-commons/cc.svg"
alt="cc"
class="inline-block h-6 mx-0.5"
height="24"
width="24" /><img
src="/images/creative-commons/by.svg"
src="images/creative-commons/by.svg"
alt="by"
class="inline-block h-6 mx-0.5"
height="24"
width="24" /><img
src="/images/creative-commons/nc.svg"
src="images/creative-commons/nc.svg"
alt="nc"
class="inline-block h-6 mx-0.5"
height="24"