📄
src/Storage/NoteFrontmatter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using Markdig; using Markdig.Extensions.Yaml; using Markdig.Syntax; using YamlDotNet.Serialization; using YamlDotNet.Serialization.Converters; namespace NotesApp.Storage; // Mirrors the frontmatter handling in the companion CLI's New-Note cmdlet: Markdig's // UseYamlFrontMatter extension locates the --- delimited YAML block, and YamlDotNet // (de)serializes it as a loose Dictionary<string, object> so arbitrary frontmatter schemas // round-trip without a fixed model. public static class NoteFrontmatter { private static readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder() .UseYamlFrontMatter() .Build(); private static readonly IDeserializer Deserializer = new DeserializerBuilder().Build(); private static readonly ISerializer Serializer = new SerializerBuilder() .WithTypeConverter(new DateOnlyConverter(formats: "yyyy-MM-dd")) .Build(); // The note's body with any leading YAML frontmatter block (delimiters included) removed, // or the content unchanged if it has none. public static string StripBlock(string content) { var block = FindBlock(content); if (block is null) return content; var start = Math.Min(block.Span.End + 1, content.Length); return content[start..].TrimStart('\r', '\n'); } public static Dictionary<string, object>? Extract(string content) { var block = FindBlock(content); if (block is null) return null; return Deserializer.Deserialize<Dictionary<string, object>>(block.Lines.ToString()); } // Builds a fresh frontmatter block (fences included) from a field dictionary, stamping // today's date the same way the CLI's New-Note does - even an otherwise-empty dictionary // still gets a dated block. public static string BuildBlock(Dictionary<string, object> fields) { fields["date"] = DateOnly.FromDateTime(DateTime.Now); var yaml = Serializer.Serialize(fields).TrimEnd(); return $"---\n{yaml}\n---\n\n\n"; } private static YamlFrontMatterBlock? FindBlock(string content) => Markdown.Parse(content, Pipeline).Descendants<YamlFrontMatterBlock>().FirstOrDefault(); }