-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathNavigationV2File.cs
More file actions
212 lines (184 loc) · 7 KB
/
NavigationV2File.cs
File metadata and controls
212 lines (184 loc) · 7 KB
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Elastic.Documentation.Configuration.Toc;
public interface INavV2Item { }
public record LabelNavV2Item(
string Label,
bool Expanded,
IReadOnlyList<INavV2Item> Children
) : INavV2Item;
public record TocNavV2Item(
Uri Source,
IReadOnlyList<INavV2Item> Children
) : INavV2Item;
/// <summary>
/// Represents a single-page crosslink (when <see cref="Page"/> is non-null) or
/// a placeholder disabled link (when <see cref="Page"/> is null and only <see cref="Title"/> is set).
/// </summary>
public record PageNavV2Item(Uri? Page, string? Title) : INavV2Item;
/// <summary>
/// A top-level section that owns an independent sidebar tree and (optionally) a tab in the
/// secondary nav bar. When <see cref="Isolated"/> is <c>true</c> the section does not appear
/// in the top bar and renders with a back-arrow instead.
/// </summary>
public record SectionNavV2Item(
string Label,
string Url,
bool Isolated,
IReadOnlyList<INavV2Item> Children
) : INavV2Item;
/// <summary>
/// A nav island nested inside a section. When a user navigates into pages belonging to
/// this island's toc, the sidebar shows only the island's tree with a back arrow to the
/// parent section. The island does not appear in the secondary nav bar.
/// </summary>
public record IslandNavV2Item(
string Label,
Uri Source
) : INavV2Item;
/// <summary>
/// A folder node — has a title and children, with an optional <c>page:</c> URI.
/// When <see cref="Page"/> is set, the header is a real clickable link; otherwise it renders
/// as a disabled placeholder (cursor-not-allowed).
/// </summary>
public record GroupNavV2Item(
string Title,
Uri? Page,
IReadOnlyList<INavV2Item> Children
) : INavV2Item;
public class NavigationV2File
{
[YamlMember(Alias = "nav")]
public IReadOnlyList<INavV2Item> Nav { get; set; } = [];
public static NavigationV2File Deserialize(string yaml) =>
ConfigurationFileProvider.NavV2Deserializer.Deserialize<NavigationV2File>(yaml);
}
public class NavV2FileYamlConverter : IYamlTypeConverter
{
public bool Accepts(Type type) => type == typeof(NavigationV2File) || type == typeof(INavV2Item);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
if (type == typeof(NavigationV2File))
return ReadFile(parser, rootDeserializer);
return ReadItem(parser, rootDeserializer);
}
private static NavigationV2File ReadFile(IParser parser, ObjectDeserializer rootDeserializer)
{
if (!parser.TryConsume<MappingStart>(out _))
return new NavigationV2File();
var file = new NavigationV2File();
while (!parser.TryConsume<MappingEnd>(out _))
{
var key = parser.Consume<Scalar>();
if (key.Value == "nav")
file.Nav = ReadItemList(parser, rootDeserializer);
else
parser.SkipThisAndNestedEvents();
}
return file;
}
private static IReadOnlyList<INavV2Item> ReadItemList(IParser parser, ObjectDeserializer rootDeserializer)
{
var items = new List<INavV2Item>();
if (!parser.TryConsume<SequenceStart>(out _))
return items;
while (!parser.TryConsume<SequenceEnd>(out _))
{
var item = ReadItem(parser, rootDeserializer);
if (item is not null)
items.Add(item);
}
return items;
}
private static INavV2Item? ReadItem(IParser parser, ObjectDeserializer rootDeserializer)
{
if (!parser.TryConsume<MappingStart>(out _))
return null;
var dict = new Dictionary<string, object?>();
while (!parser.TryConsume<MappingEnd>(out _))
{
var key = parser.Consume<Scalar>();
if (key.Value == "children")
dict["children"] = ReadItemList(parser, rootDeserializer);
else if (parser.Accept<Scalar>(out var val))
{
dict[key.Value] = val.Value;
_ = parser.MoveNext();
}
else
parser.SkipThisAndNestedEvents();
}
if (dict.TryGetValue("section", out var sectionVal) && sectionVal is string sectionStr)
{
var sectionUrl = dict.TryGetValue("url", out var suVal) && suVal is string suStr ? suStr : "/";
var isolated = dict.TryGetValue("isolated", out var isoVal)
&& isoVal is string isoStr
&& bool.TryParse(isoStr, out var isoBool)
&& isoBool;
var sectionChildren = dict.TryGetValue("children", out var sch) && sch is IReadOnlyList<INavV2Item> sChildList
? sChildList
: [];
return new SectionNavV2Item(sectionStr, sectionUrl, isolated, sectionChildren);
}
if (dict.TryGetValue("island", out var islandVal) && islandVal is string islandStr)
{
if (dict.TryGetValue("toc", out var itVal) && itVal is string itStr)
{
var itUriString = itStr.Contains("://") ? itStr : $"docs-content://{itStr}";
if (Uri.TryCreate(itUriString, UriKind.Absolute, out var itSource))
return new IslandNavV2Item(islandStr, itSource);
}
return null;
}
if (dict.TryGetValue("label", out var labelVal) && labelVal is string labelStr)
{
var expanded = dict.TryGetValue("expanded", out var expVal)
&& expVal is string expStr
&& bool.TryParse(expStr, out var expBool)
&& expBool;
var labelChildren = dict.TryGetValue("children", out var lch) && lch is IReadOnlyList<INavV2Item> lChildList
? lChildList
: [];
return new LabelNavV2Item(labelStr, expanded, labelChildren);
}
if (dict.TryGetValue("toc", out var tocVal) && tocVal is string tocStr)
{
var uriString = tocStr.Contains("://") ? tocStr : $"docs-content://{tocStr}";
if (!Uri.TryCreate(uriString, UriKind.Absolute, out var source))
throw new InvalidOperationException($"Invalid TOC source: '{tocStr}'");
var tocChildren = dict.TryGetValue("children", out var tch) && tch is IReadOnlyList<INavV2Item> tChildList
? tChildList
: [];
return new TocNavV2Item(source, tocChildren);
}
if (dict.TryGetValue("group", out var groupVal) && groupVal is string groupStr)
{
var groupChildren = dict.TryGetValue("children", out var gch) && gch is IReadOnlyList<INavV2Item> gChildList
? gChildList
: [];
Uri? groupPage = null;
if (dict.TryGetValue("page", out var gpVal) && gpVal is string gpStr)
{
var gpUri = gpStr.Contains("://") ? gpStr : $"docs-content://{gpStr}";
_ = Uri.TryCreate(gpUri, UriKind.Absolute, out groupPage);
}
return new GroupNavV2Item(groupStr, groupPage, groupChildren);
}
if (dict.TryGetValue("page", out var pageVal) && pageVal is string pageStr)
{
var uriString = pageStr.Contains("://") ? pageStr : $"docs-content://{pageStr}";
_ = Uri.TryCreate(uriString, UriKind.Absolute, out var pageUri);
var title = dict.TryGetValue("title", out var t) && t is string ts ? ts : null;
return new PageNavV2Item(pageUri, title);
}
if (dict.TryGetValue("title", out var titleVal) && titleVal is string titleStr)
return new PageNavV2Item(null, titleStr);
return null;
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) =>
serializer.Invoke(value, type);
}