Using Nitride - Front Matter
Being able to associated metadata for a page is a very useful thing. It can be used to group pages into categories, control how it is styled, or to simply provide internal notes. To do that, we use something called a YAML front matter to describe the details.
--- title: Name of the Page summary: Summary of page image: /path/to/image.png --- This is the front page.
This is a major functionality of the Markdown + YAML pages that I use in my technical, fantasy, and even blog posts. I use the categories and tags heavily on this page, not to mention giving a summary details for links. It can also be used to provide [[OpenGraph]] schemas.
Series
Defining a Model
Because C# is a static language, we want to take advantage of a schema so we can have type-safe. Historically, I've called this `PageModel` because I wrap that in a `TemplateModel` when I get to the styling.
// In //src/dotnet/Models/PageModel.cs namespace Generator.Models; public class PageModel { /// <summary> /// Gets or sets the optional list of categories associated with the page. /// </summary> public List<string>? Categories { get; set; } /// <summary> /// Gets or sets the URL of the image associated with the page. /// </summary> public string? Image { get; set; } /// <summary> /// Gets or sets the summary for the page. /// </summary> public string? Summary { get; set; } /// <summary> /// Gets or sets the title of the page. /// </summary> public string? Title { get; set; } }
Naturally, this can and will get a lot more complicated. Because we are using YAML, we can have nested objects, tags, and references that are appropriate for our page. In the above example, we are defining an optional title, summary, an image, and a list of categories.
It may come to a surprise to you, but this will eventually become a component in the page `Entity` class that is passed through the pipelines.
Including the YAML module.
Like the others [[MfGames.Nitride]] modules, we have to add it as a NuGet reference and tell the system to use it.
cd src/dotnet dotnet add package MfGames.Nitride.Yaml
Adding it to the system is just a matter of modifying `Program.cs` to include it.
// In //src/dotnet/Program.cs var builder = new NitrideBuilder(args) .UseIO(rootDirectory) .UseMarkdown() .UseHtml() .UseYaml() .UseModule<WebsiteModule>();
Parsing Front Matter
Parsing the front matter uses an operation, `MfGames.Nitride.Yaml.ParseYamlHeader` to parse the text content, pull off the front matter, wrap it in the model call, and then replace `ITextContent` with the page without the YAML header.
In effect, this:
--- title: Name of the Page summary: Summary of page image: /path/to/image.png --- This is the front page.
... becomes “This is the front page.” in the text content with a `PageModel` component.
Adding the operation is relatively simple but this operation uses a generic parameter to identify the model to parse the YAML as.
// In //src/dotnet/Pipelines/Inputs/PagesModel.cs public PagesPipeline( ILogger<PagesPipeline> logger, ReadFiles readFiles, IdentifyMarkdownFromPath identifyMarkdownFromPath, MoveToIndexPath moveToIndexPath, ParseYamlHeader<PageModel> parseYamlHeader) { _logger = logger; _identifyMarkdownFromPath = identifyMarkdownFromPath; _moveToIndexPath = moveToIndexPath; _parseYamlHeader = parseYamlHeader;
And, like the others, it's just a matter of adding the operation into the pipeline.
// In //src/dotnet/Pipelines/Inputs/PagesModel.cs var list = _readFiles .Run(cancellationToken) .Run(_identifyMarkdownFromPath) .Run(_parseYamlHeader) .Run(_moveToIndexPath) .ToList();
Once we run it, we get the following output:
[01:44:05 INF] <PagesPipeline> Entity: Path /contact/index.md, Components ["Zio.UPath","Generator.Models.PageModel","MfGames.Nitride.Contents.ITextContent","MfGames.Nitride.Markdown.IsMarkdown"] [01:44:05 INF] <PagesPipeline> Entity: Path /index.md, Components ["Zio.UPath","Generator.Models.PageModel","MfGames.Nitride.Contents.ITextContent","MfGames.Nitride.Markdown.IsMarkdown"]
When we look at the HTML output, you'll notice it has a simplified version.
$ cat build/typewriter/html/index.html <h1>Typewriter Press</h1>
Retrieving the component is code just requires the `Get
var page = entity.Get<PageModel>(); var list = entities .SelectEntity<PageModel>(OnlyRunOnEntitiesWithPageModel) .ToList(); public Entity OnlyRunOnEntitiesWithPageModel(Entity entity, PageModel page) { return entity.Set(somePageSpecificVariable); }
Gallium Methods
In the above case, the `SelectEntity` call will skip calling the lambda for entities that don't have a `PageModel`, but will still return it into the list. This is the “systems” part of the ECS system. The following will strip out the entities that don't have the appropriate models.
.SelectEntity<PageModel>((entity, page) => entity, includeEntitiesWithoutComponents: false)
So far, Gallium is set up to allow up to four components. I haven't had a need to do more, but those are easy to add. This makes it useful when doing some model processing on files that have a future date.
.WhereEntity<PageModel, UPage, Instant>(FilterOutFuturePages)
The `SelectEntity` is rather powerful because it takes an `Entity` and returns one. It can be the same entity, or it can be one that has zero or more components added or changed inside it. I found this really useful when I'm parsing out categories or I'm trying to build up a list of some specialized functionality that the styling will need.
public Entity OnSelectEntity( Entity entity, PageModel page, UPath path, ITextContent textContent) { return entity .Set(nextPreviousModel) .SetAll(parentPage, parentUrl) .Set(modifiedPageModel); }
What's Next
We have the minimum number of components and systems needed to setup styling. Next time, I'll use [[Handlebars]] to style the page and put a little chrome around the output.
Metadata
Categories:
Tags:
Footer
Below are various useful links within this site and to related sites (not all have been converted over to Gemini).
Source