Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSearchIndex.cs
More file actions
Latest commit
110 lines (97 loc) · 3.97 KB
/
Copy pathSearchIndex.cs
File metadata and controls
110 lines (97 loc) · 3.97 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
usingSystem.Text.RegularExpressions;
namespaceShellDocs.Core;
/* An in-memory search index built from the navigation graph. Each entry
represents one searchable thing — a page, or a heading within a page.
Page entries carry a trimmed plain-text body so client-side match can
find hits that aren't in the title/description surface. */
publicsealedclassSearchIndex
{
publicIReadOnlyList<SearchEntry>Entries{get;}
publicSearchIndex(IReadOnlyList<SearchEntry>entries)=>Entries=entries;
publicstaticSearchIndexFromGraph(NavigationGraphgraph)
{
varentries=newList<SearchEntry>();
Walk(graph.Root,section:null,entries);
returnnewSearchIndex(entries);
}
privatestaticvoidWalk(NavigationNodenode,string?section,List<SearchEntry>acc)
{
if(node.Kind==NodeKind.Page&&!string.IsNullOrEmpty(node.Url))
{
acc.Add(newSearchEntry(
Url:node.Url,
Title:node.Title,
Description:node.Description,
Section:section,
Kind:SearchEntryKind.Page,
Body:ExtractBodyFromFile(node.Path)));
// Prefer headings already extracted at render time; otherwise pull
// them from the source markdown ourselves so the index isn't blank
// at startup (headings normally populate only when a page renders).
varheadings=node.Headings.Count>0
?node.Headings
:ExtractHeadingsFromFile(node.Path);
foreach(varhinheadings.Where(h =>h.Level==2||h.Level==3))
{
acc.Add(newSearchEntry(
Url:node.Url+"#"+h.Id,
Title:h.Text,
Description:null,
Section:node.Title,
Kind:SearchEntryKind.Heading));
}
}
varnextSection=node.Kind==NodeKind.Section&&!string.IsNullOrEmpty(node.Title)
?node.Title
:section;
foreach(varchildinnode.Children)
{
Walk(child,nextSection,acc);
}
}
privatestaticreadonlyRegexFencedBlock=new(@"^```[\s\S]*?^```",RegexOptions.Multiline|RegexOptions.Compiled);
privatestaticreadonlyRegexHeadingLine=new(@"^(#{2,3})\s+(.+?)\s*$",RegexOptions.Multiline|RegexOptions.Compiled);
privatestaticIReadOnlyList<Heading>ExtractHeadingsFromFile(string?path)
{
if(string.IsNullOrEmpty(path)||!File.Exists(path))returnArray.Empty<Heading>();
vartext=File.ReadAllText(path);
// Strip fenced code blocks so # inside code doesn't parse as a heading.
text=FencedBlock.Replace(text,"");
varlist=newList<Heading>();
foreach(MatchminHeadingLine.Matches(text))
{
varlevel=m.Groups[1].Value.Length;
varraw=m.Groups[2].Value.Trim();
list.Add(newHeading(level,raw,Slugify(raw)));
}
returnlist;
}
privatestaticstring?ExtractBodyFromFile(string?path)
{
if(string.IsNullOrEmpty(path)||!File.Exists(path))returnnull;
returnMarkdownPlainText.Extract(File.ReadAllText(path));
}
privatestaticstringSlugify(stringtext)
{
varlowered=text.ToLowerInvariant();
varsb=newSystem.Text.StringBuilder(lowered.Length);
varlastDash=false;
foreach(varchinlowered)
{
if(char.IsLetterOrDigit(ch)){sb.Append(ch);lastDash=false;}
elseif((ch==' '||ch=='-'||ch=='_')&&!lastDash)
{
sb.Append('-');lastDash=true;
}
}
returnsb.ToString().Trim('-');
}
}
publicrecordSearchEntry(
stringUrl,
stringTitle,
string?Description,
string?Section,
SearchEntryKindKind,
string?Body=null);
publicenumSearchEntryKind{Page,Heading}