Migrate to crafter-build project.cpp and restructure views

- Replace project.json + run.sh with a C++ project.cpp build descriptor
  (crafter-build), targeting wasm32-wasip1 with the Crafter.Graphics dep
- Add Catcrafts-Views implementation; drop Catcrafts-Component
- Add catcrafts-head.js (sets <title>/<link> tags at runtime)
- Update Blog/Root/main modules, README, and favicon

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-07-18 23:27:41 +02:00
commit 13a12c2a2c
15 changed files with 227 additions and 126 deletions

View file

@ -1,8 +1,8 @@
/*
catcrafts.net
Copyright (C) 2025 Catcrafts
Copyright (C) 2026 Catcrafts
The source code of this website is made available for viewing purposes only.
The source code of this website is made available for viewing purposes only.
No permission is granted to copy, modify, distribute, or create derivative works.
*/
@ -10,22 +10,24 @@ export module Catcrafts:Blog_impl;
import :Blog;
import :Root;
import :Views;
import Crafter.Graphics;
import std;
using namespace Crafter::CppDOMBindings;
using namespace Crafter;
export namespace Catcrafts {
std::vector<HtmlElementView> blogButtons;
// Persistent storage for the per-post card click handlers. HtmlElementPtr
// unregisters its listeners on destruction, so the elements have to
// outlive Render(); clear() at the top of RenderBlog() detaches the
// previous render's handlers before we attach the new ones.
std::vector<Dom::HtmlElementPtr> blogButtons;
void RenderBlog() {
blogButtons.clear();
std::string html = "";
for(const BlogPost& post : posts) {
std::cout << "test" << std::endl;
// For preview, we'll limit the content to first 200 characters
std::string previewContent = post.content;
if(previewContent.length() > 200) {
// Find the last space before 200 characters to avoid cutting words
std::size_t lastSpace = previewContent.find_last_of(' ', 200);
if(lastSpace != std::string::npos) {
previewContent = previewContent.substr(0, lastSpace) + "...";
@ -47,24 +49,21 @@ export namespace Catcrafts {
</div>
</div>)", post.slug, post.name, post.date, previewContent);
}
main.SetInnerHTML(std::format(R"(<div class="blog-posts">{}</div>)", html));
MainContent().SetInnerHTML(std::format(R"(<div class="blog-posts">{}</div>)", html));
for(const BlogPost& post : posts) {
// Add click listener to the entire post card
HtmlElementView& cardView = blogButtons.emplace_back(std::format("blog-post-{}", post.slug));
cardView.AddClickListener([slug = post.slug](Crafter::MouseEvent e) {
PushState("{}", "", std::format("/blog/{}", slug));
Dom::HtmlElementPtr& cardView = blogButtons.emplace_back(std::format("blog-post-{}", post.slug));
cardView.AddClickListener([slug = post.slug](Dom::MouseEvent) {
Router::PushState("{}", "", std::format("/blog/{}", slug));
RenderRoot(std::format("/blog/{}", slug));
});
}
}
void RenderBlogPost(const std::string_view slug) {
std::cout << "render "<< std::endl;
for(const BlogPost& post : posts) {
if(post.slug == slug) {
main.SetInnerHTML(std::format(R"(
MainContent().SetInnerHTML(std::format(R"(
<div class="blog-post-page">
<div class="post-header">
<h1 class="post-title">{}</h1>
@ -78,6 +77,6 @@ export namespace Catcrafts {
}
}
main.SetInnerHTML("<h1>Post Not Found</h1><p>The requested blog post could not be found.</p>");
MainContent().SetInnerHTML("<h1>Post Not Found</h1><p>The requested blog post could not be found.</p>");
}
}
}