Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
<ToolingPackagesVersion>1.1.1.4415</ToolingPackagesVersion>
<ToolingPackagesVersion>1.1.1.4520</ToolingPackagesVersion>
<AccessToNugetFeed>true</AccessToNugetFeed>
<RestoreSources>
https://api.nuget.org/v3/index.json;
Expand Down
14 changes: 8 additions & 6 deletions EssentialCSharp.Web.Tests/SiteMappingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace EssentialCSharp.Web.Tests;
public class SiteMappingTests
{
static SiteMapping HelloWorldSiteMapping => new(
key: "hello-world",
keys: ["hello-world"],
pagePath:
[
"Chapters",
Expand All @@ -15,14 +15,15 @@ public class SiteMappingTests
],
chapterNumber: 1,
pageNumber: 1,
orderOnPage: 1,
chapterTitle: "Introducing C#",
rawHeading: "Introduction",
anchorId: "hello-world",
indentLevel: 0
);

static SiteMapping CSyntaxFundamentalsSiteMapping => new(
key: "c-syntax-fundamentals",
keys: ["c-syntax-fundamentals"],
pagePath:
[
"Chapters",
Expand All @@ -32,6 +33,7 @@ public class SiteMappingTests
],
chapterNumber: 1,
pageNumber: 2,
orderOnPage: 1,
chapterTitle: "Introducing C#",
rawHeading: "C# Syntax Fundamentals",
anchorId: "c-syntax-fundamentals",
Expand All @@ -52,30 +54,30 @@ public void FindHelloWorldWithAnchorSlugReturnsCorrectSiteMap()
{
SiteMapping? foundSiteMap = GetSiteMap().Find("hello-world#hello-world");
Assert.NotNull(foundSiteMap);
Assert.Equal(HelloWorldSiteMapping, foundSiteMap);
Assert.Equivalent(HelloWorldSiteMapping, foundSiteMap);
}

[Fact]
public void FindCSyntaxFundamentalsWithSpacesReturnsCorrectSiteMap()
{
SiteMapping? foundSiteMap = GetSiteMap().Find("C# Syntax Fundamentals");
Assert.NotNull(foundSiteMap);
Assert.Equal(CSyntaxFundamentalsSiteMapping, foundSiteMap);
Assert.Equivalent(CSyntaxFundamentalsSiteMapping, foundSiteMap);
}

[Fact]
public void FindCSyntaxFundamentalsWithSpacesAndAnchorReturnsCorrectSiteMap()
{
SiteMapping? foundSiteMap = GetSiteMap().Find("C# Syntax Fundamentals#hello-world");
Assert.NotNull(foundSiteMap);
Assert.Equal(CSyntaxFundamentalsSiteMapping, foundSiteMap);
Assert.Equivalent(CSyntaxFundamentalsSiteMapping, foundSiteMap);
}

[Fact]
public void FindCSyntaxFundamentalsSanitizedWithAnchorReturnsCorrectSiteMap()
{
SiteMapping? foundSiteMap = GetSiteMap().Find("c-syntax-fundamentals#hello-world");
Assert.NotNull(foundSiteMap);
Assert.Equal(CSyntaxFundamentalsSiteMapping, foundSiteMap);
Assert.Equivalent(CSyntaxFundamentalsSiteMapping, foundSiteMap);
}
}
2 changes: 1 addition & 1 deletion EssentialCSharp.Web/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ private string FlipPage(int currentChapter, int currentPage, bool next)
return "";
}
}
return $"{siteMap.Key}#{siteMap.AnchorId}";
return $"{siteMap.Keys.First()}#{siteMap.AnchorId}";
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static class SiteMappingListExtensions
}
foreach (string? potentialMatch in key.GetPotentialMatches())
{
if (siteMappings.FirstOrDefault(x => x.Key == potentialMatch) is { } siteMap)
if (siteMappings.FirstOrDefault(x => x.Keys.Any(x => x == potentialMatch)) is { } siteMap)
{
return siteMap;
}
Expand Down
1 change: 1 addition & 0 deletions EssentialCSharp.Web/Services/ISiteMappingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
public interface ISiteMappingService
{
IList<SiteMapping> SiteMappings { get; }
IEnumerable<SiteMappingDto> GetTocData();
}
10 changes: 10 additions & 0 deletions EssentialCSharp.Web/Services/SiteMappingDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace EssentialCSharp.Web.Services;

public class SiteMappingDto
{
public required int Level { get; set; }
public required List<string> Keys { get; set; }
public required string Href { get; set; }
public required string Title { get; set; }
public required IEnumerable<SiteMappingDto> Items { get; set; }
}
38 changes: 38 additions & 0 deletions EssentialCSharp.Web/Services/SiteMappingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,42 @@ public SiteMappingService(IWebHostEnvironment webHostEnvironment)
List<SiteMapping>? siteMappings = System.Text.Json.JsonSerializer.Deserialize<List<SiteMapping>>(File.OpenRead(path)) ?? throw new InvalidOperationException("No table of contents found");
SiteMappings = siteMappings;
}

public IEnumerable<SiteMappingDto> GetTocData()
{
return SiteMappings.GroupBy(x => x.ChapterNumber).OrderBy(x => x.Key).Select(x =>
{
IEnumerable<SiteMapping> orderedX = x.OrderBy(i => i.PageNumber).ThenBy(i => i.OrderOnPage);
SiteMapping firstX = orderedX.First();
return new SiteMappingDto()
{
Level = 0,
Keys = [firstX.Keys.First()],
Href = $"{firstX.Keys.First()}#{firstX.AnchorId}",
Title = $"Chapter {x.Key}: {firstX.ChapterTitle}",
Items = GetItems(orderedX.Skip(1), 1)
};
}
);
}

private static IEnumerable<SiteMappingDto> GetItems(IEnumerable<SiteMapping> chapterItems, int indentLevel)
{
return chapterItems
// Examine all items up until we move up to a level higher than where we're starting,
// which would indicate that we've reached the end of the entries nested under `indentationLevel`
.TakeWhile(i => i.IndentLevel >= indentLevel)
// Of all the multi-level descendants we found, take only those at the current level that we're wanting to render.
.Where(i => i.IndentLevel == indentLevel)
.Select(i => new SiteMappingDto()
{
Level = indentLevel,
Keys = i.Keys,
Href = $"{i.Keys.First()}#{i.AnchorId}",
Title = i.RawHeading,
// Any children of this node will be /after/ this node,
// so skip any items that are /before/ the current node.
Items = GetItems(chapterItems.SkipWhile(q => i.Keys.First() != q.Keys.First()).Skip(1), indentLevel + 1)
});
}
}
39 changes: 7 additions & 32 deletions EssentialCSharp.Web/Views/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -266,32 +266,7 @@
@await RenderSectionAsync("Scripts", required: false);
<script>
@{
object GetItems(IEnumerable<SiteMapping> chapterItems, int indentLevel) => chapterItems
// Skip the chapter entry itself
.Skip(1)
// Examine all items up until we move up to a level higher than where we're starting,
// which would indicate that we've reached the end of the entries nested under `indentationLevel`
.TakeWhile(i => i.IndentLevel >= indentLevel)
// Of all the multi-level descendants we found, take only those at the current level that we're wanting to render.
.Where(i => i.IndentLevel == indentLevel)
.Select(i => new
{
Level = indentLevel,
Key = i.Key,
Href = $"{i.Key}#{i.AnchorId}",
Title = i.RawHeading,
// Any children of this node will be /after/ this node,
// so skip any items that are /before/ the current node.
Items = GetItems(chapterItems.SkipWhile(q => i.Key != q.Key), indentLevel + 1)
});
var tocData = _SiteMappings.SiteMappings.GroupBy(x => x.ChapterNumber).OrderBy(x => x.Key).Select(x => new
{
Level = 0,
Key = x.First().Key,
Href = $"{x.First().Key}#{x.First().AnchorId}",
Title = $"Chapter {x.Key}: {x.First().ChapterTitle}",
Items = GetItems(x, 1)
});
var tocData = _SiteMappings.GetTocData();
}
PREVIOUS_PAGE = @Json.Serialize(ViewBag.PreviousPage)
Expand All @@ -302,25 +277,25 @@
@* Recursive vue component template for rendering the table of contents. *@
<template id="toc-tree">
<li v-if="item.items.length">
<details :open="expandedTocs.has(item.key)"
<details :open="expandedTocs.has(item.keys)"
v-on:toggle="!$event.target.open ? expandedTocs.delete(item.key) : expandedTocs.add(item.key)">
<summary :class="{
'toc-content' : item.level==0,
'nested' : item.level>
0,
'current-section': currentPage.some(p => p.key == item.key),
'current-section': currentPage.some(p => JSON.stringify(p.keys) == JSON.stringify(item.keys)),
}" :href="item.href">{{item.title}}
</summary>
<ul>
<li :class="{
['indent-level-' + (item.level+1)]: true,
'current-li' : currentPage.some(p=>
p.key == item.key) && !currentPage.some(p => p.level > item.level),
JSON.stringify(p.keys) == JSON.stringify(item.keys)) && !currentPage.some(p => p.level > item.level),
}" >
<a class="section-link" :class="{
['indent-level-' + (item.level+1)]: true,
'current-section' : currentPage.some(p=>
p.key == item.key) && !currentPage.some(p => p.level > item.level),
JSON.stringify(p.keys) == JSON.stringify(item.keys)) && !currentPage.some(p => p.level > item.level),
}" :href="item.href"> Introduction
</a>
</li>
Expand All @@ -332,12 +307,12 @@
<li v-else :class="{
['indent-level-' + (item.level+1)]: true,
'current-li' : currentPage.some(p=>
p.key == item.key) && !currentPage.some(p => p.level > item.level),
JSON.stringify(p.keys) == JSON.stringify(item.keys)) && !currentPage.some(p => p.level > item.level),
}" >
<a class="section-link" :class="{
['indent-level-' + (item.level)]: true,
'current-section' : currentPage.some(p=>
p.key == item.key),
JSON.stringify(p.keys) == JSON.stringify(item.keys)),
}" :href="item.href"> {{item.title}}
</a>
</li>
Expand Down
18 changes: 10 additions & 8 deletions EssentialCSharp.Web/wwwroot/js/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,13 @@ const completedFeaturesList = [
function findCurrentPage(path, items) {
for (const item of items) {
const itemPath = [item, ...path];
if (
window.location.href.endsWith("/" + item.href) ||
window.location.href.endsWith("/" + item.key)
) {
return itemPath;
if (window.location.href.endsWith("/" + item.href)) {
return itemPath;
}
for (let key of item.keys) {
if (window.location.href.endsWith("/" + item.key)) {
return itemPath;
}
}

const recursivePath = findCurrentPage(itemPath, item.items);
Expand Down Expand Up @@ -199,7 +201,7 @@ const app = createApp({
const sectionTitle = ref(currentPage?.[0]?.title || "Essential C#");
const expandedTocs = reactive(new Set());
for (const item of currentPage) {
expandedTocs.add(item.key);
expandedTocs.add(item.keys);
}

// hide the sidebar when resizing to small screen
Expand Down Expand Up @@ -269,15 +271,15 @@ const app = createApp({
expandedTocs.clear();
// If a search query is removed, open the TOC for the current page.
for (const item of currentPage) {
expandedTocs.add(item.key);
expandedTocs.add(item.keys);
}
}
else {
expandedTocs.clear();
const query = normalizeString(newQuery);
tocData.forEach(item => {
if (filterItem(item, query)) {
expandedTocs.add(item.key);
expandedTocs.add(item.keys);
}
});
}
Expand Down
Loading