Skip to content

Commit

Permalink
πŸ“ Add initial docs for content collections
Browse files Browse the repository at this point in the history
  • Loading branch information
koeeenig committed Feb 11, 2024
1 parent 3da3dcb commit 13a804c
Show file tree
Hide file tree
Showing 14 changed files with 2,883 additions and 84 deletions.
11 changes: 4 additions & 7 deletions src/BlazeKit.Website.Islands/Components/Counter.razor
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,10 @@
</div>
<div>
<p class="font-bold">Conditional CSS Classes</p>
<div class="w-56 h-5 bg-green-300
@(
new CssBuilder()
.AddIf("bg-green-500",counter.Value % 2 == 0)
.AddIf("bg-red-500",counter.Value % 2 != 0)
.Apply()
)"
<div class="w-56 h-5 bg-green-300 @(new CssBuilder()
.AddIf("bg-green-500",counter.Value % 2 == 0)
.AddIf("bg-red-500",counter.Value % 2 != 0)
.Apply())"
></div>
</div>
<article>
Expand Down
2 changes: 1 addition & 1 deletion src/BlazeKit.Website/Components/DocsSitemap.razor
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<div class="flex flex-col">
@foreach (var heading in Schema.Headings)
{
<a class="no-underline text-xs p-1 text-gray-400 hover:text-gray-500 active:bg-sky-200 @(Indentation(heading))" href="@Route(heading)">@heading.Title</a>
<a class="no-underline text-xs p-1 text-gray-400 hover:text-gray-500 active:bg-sky-200 @(Indentation(heading))" href="@Route(heading)">@heading.Title.Replace("&lt;","<").Replace("&gt;",">")</a>
}
</div>
</div>
Expand Down
21 changes: 11 additions & 10 deletions src/BlazeKit.Website/Content/Docs/BlazeKit-CLI.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
---
title: CLI - Docs - BlazeKit
title: Command-Line Interface
category: Getting Started
draft: false
slug: blazekit-cli
---
# BlazeKit CLI
## Introduction
BlazeKit provides a CLI tool to create and build projects.
## Install
# Command-Line Interface
The CLI is designed to make getting started with the BlazeKit as easy as possible. It simplifies the process of adding third-party integrations like TailwindCSS, making it a no-brainer even for developers new to the framework.
Furthermore, the CLI simplifies app deployment by providing support for various hosting vendors such as Vercel or Azure. This means you can focus more on building your application and less on the intricacies of deployment.
In summary, the CLI is a powerful tool that accelerates development, simplifies integration and deployment, and ultimately enhances your productivity when building with BlazeKit.
# Install
```sh
# Install the BlazeKit CLI
dotnet tool install --global BlazeKit.CLI
```
## Update
# Update
The easiest way to update the BlazeKit CLI is to simply uninstall and reinstall it.
```sh
# Update the BlazeKit CLI
dotnet tool uninstall -g BlazeKit.CLI
dotnet tool install -g BlazeKit.CLI
```
## Create a Project
# Create a Project
```sh
bkit new NextUnicorn
```
Expand All @@ -32,7 +33,7 @@ bkit run dev
```
And here it is, your first BalzeKit project πŸŽ‰

## Build
# Build
Building the is handled by the CLI as well.
```sh
bkit build
Expand All @@ -43,8 +44,8 @@ bkit build -o ./artifacts/static
```
This will build the app into the `./artifacts/static` directory

## Integrations
### TailwindCSS
# Integrations
## TailwindCSS
```sh
bkit add tailwindcss
```
Expand Down
226 changes: 220 additions & 6 deletions src/BlazeKit.Website/Content/Docs/Content-Collections.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,230 @@
---
title: Content Collections - Docs - BlazeKit
title: Content Collections
category: Core Concepts
draft: false
slug: content-collections
---
# Content Collections
In BlazeKit, Content Collections are a core concept that allows you to manage and organize your content in a structured way. Similar to the [Astro](https://astro.build/) framework, a Content Collection in BlazeKit is any top-level directory inside the reserved `/Content` project directory. For example, `/Content/Blog` and `/Content/Authors` could be considered as Content Collections.
# What are Content Collections

## What are Content Collections
A content collection is any top-level directory inside the reserved `/Content` project directory, such as `/Content/Blog` and `/Content/Authors`.
Content Collections provide a way to group related content together. This could be blog posts, author profiles, product descriptions, or any other type of content your project requires. Each item within a Content Collection is a file, and the data within these files can be queried and used throughout your application.

## Defining Collections
This feature allows for a more organized project structure and makes it easier to manage content, especially in large projects. It also enables dynamic routing based on the content files, which can greatly simplify the creation of pages for individual content items.

## Querying Collections
In the following sections, we will discuss how to define, query, and generate routes from Content Collections in BlazeKit.
# Defining Collections
To define a Content Collection, create a new top-level directory inside the `/Content` directory in your project. Usually, the name of the directory represents the name of the Content Collection. For example, to create a Content Collection for blog posts, you would create a `/Content/Blog` directory.

## Generating Routes from Content
Inside this directory, each file represents an item in the collection. The file is a Markdown (.md) file. The frontmatter of the file can contain metadata about the content item.
Here's an example of a Content Collection class for a collection of documents:
```md
---
title: My First Blog Post
date: 2022-01-01
author: John Doe
---

This is my first blog post.
```
## Frontmatter
Each Content Collection can have an associated [Frontmatter](https://frontmatter.codes/) Schema. This schema defines the structure of the metadata in the frontmatter of the content files in the collection. The schema is represented by a class that implements the `ISchema` interface from the `BlazeKit.Static.ContentCollections` namespace.
Here's an example of a schema class for a blog post:
```csharp
using BlazeKit.Static.ContentCollections;
using YamlDotNet.Serialization;

public class BlogPostSchema : ISchema
{
[YamlMember(Alias = "title")]
public string Title { get; set; }
[YamlMember(Alias = "author")]
public string Author { get; set; }
[YamlMember(Alias = "date")]
public DateTime Date { get; set; }
[YamlMember(Alias = "draft")]
public bool IsDraft { get; set; }
// The content of the markdown file
public bool Content { get; set; }
}
```
In this example, the `BlogPostSchema` class defines the structure of the metadata in the frontmatter of the blog post files. Each property in the class corresponds to a field in the frontmatter. When you query the Blog collection, you can access the metadata of the blog posts through the properties of the `BlogPostSchema` class. For example, `post.Title` would give you the title of the blog post.

Remember to replace the property names and types to match the fields in your frontmatter. The property names should be the same as the field names in the frontmatter, and the property types should be compatible with the field values.

## IContentCollection
The `IContentCollection` interface in BlazeKit is a crucial part of the Content Collection system. It serves as a contract for all Content Collections, ensuring they provide certain functionalities that BlazeKit relies on to handle and serve content.

Implementing the IContentCollection interface makes a Content Collection available to BlazeKit. This means BlazeKit can access, query, and generate routes for the content items in the collection.

Here's the basic structure of the IContentCollection interface:
```csharp
public interface IContentCollection
{
string Name { get; }
IEnumerable<ISchema> Items { get; }
string Route(ISchema item);
}
```
The IContentCollection interface includes:
- Name: A string property that represents the name of the Content Collection.
- Items: An enumerable of `ISchema` objects that represents the content items in the collection.
- `Route(ISchema item)`: A method that takes an ISchema object and returns a string. This method defines the route for each item in the collection.
- When you create a Content Collection class, you need to implement the `IContentCollection` interface and provide implementations for the Name, Items, and `Route(ISchema item)` members.

For example, here's how you might implement the IContentCollection interface for a Blog Content Collection:
```csharp
public sealed class BlogCollection : IContentCollection
{
public string Name => "Blog";
public IEnumerable<ISchema> Items { get; private set; }

public DocsCollection(IEnumerable<ISchema> items)
{
Items = items;
}

public string Route(ISchema item)
{
return $"/Blog/{(item as BlogPostSchema).Slug}";
}
}
```
## ContentCollectionEnvelope
The `ContentCollectionEnvelope` class is an abstract class that simplifies the process of implementing the `IContentCollection` interface. It provides a basic structure and common functionalities for a Content Collection, which you can then extend and customize to suit your needs.

Here's a brief overview of how to use ContentCollectionEnvelope:
```csharp
public sealed class BlogCollection : ContentCollectionEnvelope
{
public const string CollectionName = "Blog";
public DocsCollection() : base(
CollectionName,
Path.Combine("Content", CollectionName),
md => {
var schema = md.GetFrontMatter<BlogPostSchema>();
// Do additional stuff with the markdown content such a extracting headers for e.g. sitemaps
// ...
return schema;
},
schema => (schema as BlogPostSchema).IsDraft == false // Filter out draft items
)
{ }

public override string Route(ISchema item)
{
// Define the route for each item in the collection
return $"/Blog/{(item as BlogPostSchema).Slug}";
}
}
```
In this example, the `BlogCollection` class inherits from `ContentCollectionEnvelope` and provides implementations for the required methods.
The ContentCollectionEnvelope constructor takes three arguments:

1. The name of the collection.
2. The path to the directory that contains the content files.
3. A function that parses the frontmatter and the content of the markdown files.
4. A function that filters the items in the collection.

The `Route` method, which is abstract in `ContentCollectionEnvelope` and must be overridden, defines the route for each item in the collection.

By using `ContentCollectionEnvelope`, you can create a Content Collection with less boilerplate code and a clear, consistent structure. This makes it easier to manage and extend your Content Collections as your project grows.

## StaticServiceCollection
The `StaticServiceCollection` is a specialized service collection designed for static site generation. It's an implementation of the `IStaticServiceCollection` interface and is used to register and manage services that are required during the static site generation process.

**Purpose**

The main purpose of `StaticServiceCollection` is to provide a centralized location for registering and accessing services that are used in static site generation. This includes all Content Collections, which need to be added as KeyedSingleton services. A KeyedSingleton service is a service that is registered with a key and behaves like a singleton service

In the context of `StaticServiceCollection`, each Content Collection is registered as a KeyedSingleton service. The key for each service is the name of the Content Collection, which allows the service to be retrieved based on the collection name.

```csharp
/// <summary>
/// A static service collection that is used to build a static site
/// </summary>
public sealed class StaticServiceCollection : IStaticServiceCollection
{
private readonly Func<IServiceCollection> services;

/// <summary>
/// A static service collection that is used to build a static site
/// </summary>
public StaticServiceCollection() :this(
new ServiceCollection()
)
{ }

/// <summary>
/// A static service collection that is used to build a static site
/// </summary>
public StaticServiceCollection(IServiceCollection serviceCollection)
{
this.services = () => {
serviceCollection.AddKeyedSingleton(BlogCollection.CollectionName, new BlogCollection());
// ...
return serviceCollection;
};
}
public IServiceCollection Services() => services();
}
```

By using `StaticServiceCollection`, you can manage all the services required for static site generation in a consistent and organized way. This makes it easier to add, remove, and access services as your project grows.

# Query and Render Content from Collections
In BlazeKit, you can access and render content from a specific item in a Content Collection by querying the collection and filtering the items based on your criteria.
1. Access the Content Collection
You can access the `BlogCollection` in your `Page.razor` by injecting it:
```csharp
[Inject(Key = DocsCollection.CollectionName)]
public DocsCollection Collection {get;set;}
```
1. Query the Content Collection
To access a specific item in the `BlogCollection`, you can query the collection and filter the items based on e.g. the Slug:
```csharp
this.docItem =
collection.Items.Cast<BlogPostSchema>().FirstOrDefault(
item => item.Slug.Equals(Slug, StringComparison.InvariantCultureIgnoreCase)
);
```
In this code, `FirstOrDefault` is a LINQ method that returns the first item that matches the provided condition, or null if no items match the condition.
1. Render the Content
Finally, you can render the content of the item in your Razor file:
```razor
@((MarkupString)docItem.Content)
```

The full `Page.razor` might look like this:
```razor
@using BlazeKit.Web.Components
@using BlazeKit.Web
@inherits PageComponentBase<PageDataBase>
@code {
[Inject(Key = BlogCollection.CollectionName)]
public BlogCollection Collection {get;set;}
private BlogPostSchema? blogPostItem;
protected override Task<PageDataBase> ServerLoadAsync(Uri route, Response response)
{
this.blogPostItem =
Collection.Items.Cast<BlogPostSchema>().FirstOrDefault(
item => item.Slug.Equals(Slug, StringComparison.InvariantCultureIgnoreCase)
);
// We handle a page not found here.
// This is unlikly to happen since SSG will only have valid slug's.
if (this.blogPostItem is null)
{
throw new ApplicationException($"The Slug '{Slug}' does not exist in {Collection.CollectionName}");
}
return Task.FromResult(default(PageDataBase));
}
}
<PageTitle>@blogPostItem.Title - MyBlog</PageTitle>
<p>@blogPostItem.Author published on @blogPostItem.Date.ToOrdinalWords()</p>
@((MarkupString)blogPostItem.Content)
```
17 changes: 0 additions & 17 deletions src/BlazeKit.Website/Content/Docs/DocsCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,6 @@
using System.Text.RegularExpressions;
using YamlDotNet.Serialization;
namespace BlazeKit.Website;
public sealed class DocsSchema : ISchema
{
[YamlMember(Alias = "title")]
public required string Title { get; set; }
[YamlMember(Alias = "category")]
public required string Category { get; set; } = "";
[YamlMember(Alias = "slug")]
public required string Slug { get; set; }
[YamlMember(Alias = "draft")]
public required bool IsDraft { get; set; }
public required string Content { get;set; }

public IList<Heading> Headings { get; set; } = new List<Heading>();

public record Heading(int Level, string Title, string Id);
}

public sealed class DocsCollection : ContentCollectionEnvelope
{
public const string CollectionName = "Docs";
Expand Down
20 changes: 20 additions & 0 deletions src/BlazeKit.Website/Content/Docs/DocsSchema.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using BlazeKit.Static.ContentCollections;
using YamlDotNet.Serialization;

namespace BlazeKit.Website;
public sealed class DocsSchema : ISchema
{
[YamlMember(Alias = "title")]
public required string Title { get; set; }
[YamlMember(Alias = "category")]
public required string Category { get; set; } = "";
[YamlMember(Alias = "slug")]
public required string Slug { get; set; }
[YamlMember(Alias = "draft")]
public required bool IsDraft { get; set; }
public required string Content { get;set; }

public IList<Heading> Headings { get; set; } = new List<Heading>();

public record Heading(int Level, string Title, string Id);
}
9 changes: 4 additions & 5 deletions src/BlazeKit.Website/Content/Docs/Installation.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
---
title: Installation - Docs - BlazeKit
title: Installation
category: Getting Started
draft: false
slug: installation
---
# Installation
## Requirements
# Requirements
Before creating your first BlazeKit project, you should ensure that your local machine has:
- .NET 8 - you can find the download [here](https://dotnet.microsoft.com/en-us/download){target="_blank"}

That's it πŸ‘

## Create an app using the CLI
# Create an app using the CLI
After you have installed the required dependencies, the easiest way to get a project up an running is by using the BlazeKit CLI.
```shell
# Install the BlazeKit CLI
Expand All @@ -30,7 +29,7 @@ bkit run dev
```
And here it is, your first BalzeKit project πŸŽ‰

## Updating the BlazeKit CLI
# Updating the BlazeKit CLI
The easiest way to update the BlazeKit CLI is to simply uninstall and reinstall it.
```shell
dotnet tool uninstall -g BlazeKit.CLI
Expand Down
2 changes: 1 addition & 1 deletion src/BlazeKit.Website/Content/Docs/Introduction.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Introduction - Docs - BlazeKit
title: Introduction
category: Getting Started
draft: false
slug: introduction
Expand Down
Loading

0 comments on commit 13a804c

Please sign in to comment.