SPA
This commit is contained in:
parent
c51dd48a73
commit
937b9fb48f
9 changed files with 180 additions and 90 deletions
92
README.md
92
README.md
|
|
@ -1,78 +1,54 @@
|
||||||
# catcrafts.net
|
# catcrafts.net
|
||||||
|
|
||||||
A modern, responsive website built with C++ using Crafter.CppDOM and compiled to WebAssembly.
|
This is the source code for catcrafts.net, a website built entirely in C++ using the Crafter.CppDOM library.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Modern, clean design with gradient colors and smooth animations
|
- **Responsive Design**: Works on all device sizes
|
||||||
- Fully responsive layout that works on mobile and desktop
|
- **Modern UI**: Clean and contemporary design with smooth animations
|
||||||
- Blog functionality with multiple posts
|
- **Blog System**:
|
||||||
- Smooth navigation and transitions
|
- Blog posts displayed with previews on the main blog page
|
||||||
- Semantic HTML structure
|
- Individual blog post pages with full content
|
||||||
- Custom CSS with modern styling techniques
|
- Navigation between posts
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
catcrafts.net/
|
project.json - Build configuration
|
||||||
├── interfaces/ # Interface definitions
|
interfaces/ - Interface definitions
|
||||||
│ ├── Catcrafts.cppm
|
implementations/ - Implementation files
|
||||||
│ ├── Catcrafts-Views.cppm
|
styles/styles.css - Styling
|
||||||
│ ├── Catcrafts-Component.cppm
|
run.sh - Run script
|
||||||
│ └── Catcrafts-Blog.cppm
|
README.md - This file
|
||||||
├── implementations/ # Implementation files
|
LICENSE - License information
|
||||||
│ ├── Catcrafts-Blog.cpp
|
|
||||||
│ └── main.cpp
|
|
||||||
├── styles.css # Modern CSS styling
|
|
||||||
├── project.json # Build configuration
|
|
||||||
├── run.sh # Development server script
|
|
||||||
└── build.sh # Build script
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Blog Implementation Details
|
||||||
|
|
||||||
|
### Blog Previews
|
||||||
|
On the main `/blog` page, each blog post is displayed with:
|
||||||
|
- Title and date
|
||||||
|
- Preview of the content (first 200 characters)
|
||||||
|
- "Read Full Post" button to view the complete article
|
||||||
|
|
||||||
|
### Individual Blog Pages
|
||||||
|
Each blog post has its own URL:
|
||||||
|
- `/blog/0` - First blog post
|
||||||
|
- `/blog/1` - Second blog post
|
||||||
|
- And so on...
|
||||||
|
|
||||||
|
When visiting these URLs, users see the complete blog post content.
|
||||||
|
|
||||||
## Building and Running
|
## Building and Running
|
||||||
|
|
||||||
### Prerequisites
|
To build and run the project:
|
||||||
- C++ compiler with WebAssembly support
|
|
||||||
- Caddy server for serving files
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
1. Make the scripts executable:
|
|
||||||
```bash
|
|
||||||
chmod +x run.sh build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Run the development server:
|
|
||||||
```bash
|
```bash
|
||||||
./run.sh
|
./run.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The website will be available at `http://localhost:8085`
|
This will compile the project and serve it locally.
|
||||||
|
|
||||||
### Building Manually
|
## Contributing
|
||||||
|
|
||||||
```bash
|
This project is open-source and made available for viewing purposes only. No permission is granted to copy, modify, distribute, or create derivative works.
|
||||||
./build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Technologies Used
|
|
||||||
|
|
||||||
- **C++**: Core application logic
|
|
||||||
- **Crafter.CppDOM**: DOM manipulation library
|
|
||||||
- **WebAssembly**: Runtime environment
|
|
||||||
- **CSS3**: Modern styling with animations and gradients
|
|
||||||
- **HTML5**: Semantic markup
|
|
||||||
|
|
||||||
## Customization
|
|
||||||
|
|
||||||
To add new blog posts:
|
|
||||||
1. Edit `interfaces/Catcrafts-Blog.cppm`
|
|
||||||
2. Add new entries to the `posts` vector
|
|
||||||
|
|
||||||
To modify styling:
|
|
||||||
1. Edit `styles.css`
|
|
||||||
2. Adjust colors, fonts, and layout as needed
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
||||||
|
|
@ -8,23 +8,73 @@ No permission is granted to copy, modify, distribute, or create derivative works
|
||||||
|
|
||||||
export module Catcrafts:Blog_impl;
|
export module Catcrafts:Blog_impl;
|
||||||
import :Blog;
|
import :Blog;
|
||||||
|
import :Root;
|
||||||
|
import :Views;
|
||||||
import std;
|
import std;
|
||||||
|
|
||||||
namespace Catcrafts {
|
using namespace Crafter::CppDOMBindings;
|
||||||
std::string RenderBlog() {
|
|
||||||
|
export namespace Catcrafts {
|
||||||
|
std::vector<HtmlElementView>* blogButtons = new std::vector<HtmlElementView>();
|
||||||
|
|
||||||
|
void RenderBlog() {
|
||||||
|
delete blogButtons;
|
||||||
|
blogButtons = new std::vector<HtmlElementView>();
|
||||||
std::string html = "";
|
std::string html = "";
|
||||||
for(const BlogPost& post : *posts) {
|
for(const BlogPost& post : *posts) {
|
||||||
|
// 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) + "...";
|
||||||
|
} else {
|
||||||
|
previewContent = previewContent.substr(0, 200) + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
html += std::format(R"(
|
html += std::format(R"(
|
||||||
<div class="post fade-in">
|
<div class="post fade-in">
|
||||||
<div class="post-header">
|
<div class="post-header">
|
||||||
<h2 class="post-title">{}</h2>
|
<h2 class="post-title"><a>{}</a></h2>
|
||||||
<span class="post-date">{}</span>
|
<span class="post-date">{}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="post-content">
|
<div class="post-content">
|
||||||
{}
|
{}
|
||||||
</div>
|
</div>
|
||||||
</div>)", post.name, post.date, post.content);
|
<div class="post-footer">
|
||||||
|
<a id="blog-button-{}" class="btn">Read Full Post</a>
|
||||||
|
</div>
|
||||||
|
</div>)", post.name, post.date, previewContent, post.slug);
|
||||||
}
|
}
|
||||||
return html;
|
main->SetInnerHTML(html);
|
||||||
|
|
||||||
|
for(const BlogPost& post : *posts) {
|
||||||
|
HtmlElementView& view = blogButtons->emplace_back(std::format("blog-button-{}", post.slug));
|
||||||
|
view.AddClickListener([slug = post.slug](Crafter::MouseEvent e) {
|
||||||
|
PushState("{}", "", std::format("/blog/{}", slug));
|
||||||
|
RenderRoot(std::format("/blog/{}", slug));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderBlogPost(const std::string_view slug) {
|
||||||
|
for(const BlogPost& post : *posts) {
|
||||||
|
if(post.slug == slug) {
|
||||||
|
main->SetInnerHTML(std::format(R"(
|
||||||
|
<div class="post fade-in">
|
||||||
|
<div class="post-header">
|
||||||
|
<h1 class="post-title">{}</h1>
|
||||||
|
<span class="post-date">{}</span>
|
||||||
|
</div>
|
||||||
|
<div class="post-content">
|
||||||
|
{}
|
||||||
|
</div>
|
||||||
|
</div>)", post.name, post.date, post.content));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main->SetInnerHTML("<h1>Post Not Found</h1><p>The requested blog post could not be found.</p>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
38
implementations/Catcrafts-Root.cpp
Normal file
38
implementations/Catcrafts-Root.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
catcrafts.net
|
||||||
|
Copyright (C) 2025 Catcrafts
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export module Catcrafts:Root_impl;
|
||||||
|
import :Root;
|
||||||
|
import :Views;
|
||||||
|
import :Blog;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
namespace Catcrafts {
|
||||||
|
void RenderRoot(const std::string_view route) {
|
||||||
|
std::string currentRoute = std::string(route);
|
||||||
|
|
||||||
|
if(currentRoute == "/blog" || currentRoute == "/") {
|
||||||
|
RenderBlog();
|
||||||
|
} else if(currentRoute.rfind("/blog/", 0) == 0) {
|
||||||
|
// Handle individual blog post routes like /blog/0, /blog/1, etc.
|
||||||
|
std::size_t pos = currentRoute.find_last_of('/');
|
||||||
|
std::cout << pos << std::endl;
|
||||||
|
std::cout << std::string::npos << std::endl;
|
||||||
|
std::cout << (pos != std::string::npos) << std::endl;
|
||||||
|
if(pos != std::string::npos) {
|
||||||
|
std::string postSlug = currentRoute.substr(pos + 1);
|
||||||
|
std::cout << postSlug << std::endl;
|
||||||
|
RenderBlogPost(postSlug);
|
||||||
|
} else {
|
||||||
|
main->SetInnerHTML("<h1>Post Not Found</h1><p>The requested blog post could not be found.</p>");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RenderBlog(); //default route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,27 +15,6 @@ using namespace Crafter::CppDOMBindings;
|
||||||
|
|
||||||
HtmlElementView* blogButton;
|
HtmlElementView* blogButton;
|
||||||
|
|
||||||
void RenderRoot(const std::string_view route) {
|
|
||||||
std::string pageContent;
|
|
||||||
if(route == "/blog") {
|
|
||||||
pageContent = RenderBlog();
|
|
||||||
} else {
|
|
||||||
pageContent = RenderBlog(); //default route
|
|
||||||
}
|
|
||||||
// Set body content
|
|
||||||
main->SetInnerHTML(pageContent);
|
|
||||||
|
|
||||||
// Update active nav link
|
|
||||||
// auto navLinks = document->GetElementsByClassName("nav-container")[0]->GetElementsByTagName("a");
|
|
||||||
// for(auto link : navLinks) {
|
|
||||||
// if(link->GetAttribute("id") == "blog-nav-button") {
|
|
||||||
// link->SetAttribute("class", "active");
|
|
||||||
// } else {
|
|
||||||
// link->SetAttribute("class", "");
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
AddPopStateListener([]() {
|
AddPopStateListener([]() {
|
||||||
RenderRoot(GetPathNameString());
|
RenderRoot(GetPathNameString());
|
||||||
|
|
@ -44,7 +23,7 @@ int main() {
|
||||||
blogButton = new HtmlElementView("blog-nav-button");
|
blogButton = new HtmlElementView("blog-nav-button");
|
||||||
blogButton->AddClickListener([](Crafter::MouseEvent e) {
|
blogButton->AddClickListener([](Crafter::MouseEvent e) {
|
||||||
PushState("{}", "", "/blog");
|
PushState("{}", "", "/blog");
|
||||||
RenderRoot("blog");
|
RenderRoot("/blog");
|
||||||
});
|
});
|
||||||
|
|
||||||
RenderRoot("/");
|
RenderRoot("/");
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,18 @@ using namespace Crafter;
|
||||||
export namespace Catcrafts {
|
export namespace Catcrafts {
|
||||||
struct BlogPost {
|
struct BlogPost {
|
||||||
std::string name;
|
std::string name;
|
||||||
|
std::string slug;
|
||||||
std::string date;
|
std::string date;
|
||||||
std::string content;
|
std::string content;
|
||||||
};
|
};
|
||||||
std::vector<BlogPost>* posts = new std::vector<BlogPost>{
|
std::vector<BlogPost>* posts = new std::vector<BlogPost>{
|
||||||
{
|
{
|
||||||
"Hello World!",
|
"Hello World!",
|
||||||
|
"hello-world",
|
||||||
"2025-11-12",
|
"2025-11-12",
|
||||||
R"(Welcome to catcrafts.net!<br><br> This blog will mostly be dedicated to random tidbits i come across while working on my Crafter series of libraries, This website is fully written in C++ using the Crafter.CppDOM library.)"
|
R"(Welcome to catcrafts.net!<br><br> This blog will mostly be dedicated to random tidbits i come across while working on my Crafter series of libraries, This website is fully written in C++ using the Crafter.CppDOM library.)"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
std::string RenderBlog();
|
void RenderBlog();
|
||||||
|
void RenderBlogPost(const std::string_view slug);
|
||||||
}
|
}
|
||||||
16
interfaces/Catcrafts-Root.cppm
Normal file
16
interfaces/Catcrafts-Root.cppm
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
catcrafts.net
|
||||||
|
Copyright (C) 2025 Catcrafts
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export module Catcrafts:Root;
|
||||||
|
import Crafter.CppDOM;
|
||||||
|
import std;
|
||||||
|
using namespace Crafter;
|
||||||
|
|
||||||
|
export namespace Catcrafts {
|
||||||
|
void RenderRoot(const std::string_view route);
|
||||||
|
}
|
||||||
|
|
@ -9,3 +9,4 @@ No permission is granted to copy, modify, distribute, or create derivative works
|
||||||
export module Catcrafts;
|
export module Catcrafts;
|
||||||
export import :Views;
|
export import :Views;
|
||||||
export import :Blog;
|
export import :Blog;
|
||||||
|
export import :Root;
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "executable",
|
"name": "executable",
|
||||||
"interfaces": ["interfaces/Catcrafts", "interfaces/Catcrafts-Views", "interfaces/Catcrafts-Blog"],
|
"interfaces": ["interfaces/Catcrafts", "interfaces/Catcrafts-Views", "interfaces/Catcrafts-Blog", "interfaces/Catcrafts-Root"],
|
||||||
"implementations": ["implementations/main", "implementations/Catcrafts-Blog"],
|
"implementations": ["implementations/main", "implementations/Catcrafts-Blog", "implementations/Catcrafts-Root"],
|
||||||
"target": "wasm32-wasi",
|
"target": "wasm32-wasi",
|
||||||
"additional_files": ["styles/style.css"],
|
"additional_files": ["styles/styles.css"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.CppDOM.git",
|
"path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.CppDOM.git",
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,33 @@ main {
|
||||||
.post-content a:hover {
|
.post-content a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: var(--secondary-color);
|
color: var(--secondary-color);
|
||||||
|
}.post-content {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: #333;
|
||||||
|
animation: fadeIn 0.6s ease-out 0.2s forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content p {
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-footer {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-content ul {
|
.post-content ul {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue