@@ -1047,10 +1047,26 @@ div.figure p { | |||||
} | } | ||||
.menu-vertical li.tocentry { | .menu-vertical li.tocentry { | ||||
padding-left: 15px; | |||||
padding-left: 20px; | |||||
padding-top: 7px; | padding-top: 7px; | ||||
} | } | ||||
.menu-vertical li.tocentry.current > ul.currentrelative { | |||||
padding-left: 7px; | |||||
padding-bottom: 5px; | |||||
margin-top: 0; | |||||
} | |||||
.menu-vertical li.tocentry > ul.currentrelative { | |||||
padding-left: 7px; | |||||
padding-bottom: 5px; | |||||
margin-top: -3px; | |||||
} | |||||
.menu-vertical li.tocentry > ul.currentrelative > li.tocentry { | |||||
padding-top: 3px !important; | |||||
} | |||||
.menu-vertical li.tocrootentry { | .menu-vertical li.tocrootentry { | ||||
padding-left: 15px; | padding-left: 15px; | ||||
} | } | ||||
@@ -1047,10 +1047,26 @@ div.figure p { | |||||
} | } | ||||
.menu-vertical li.tocentry { | .menu-vertical li.tocentry { | ||||
padding-left: 15px; | |||||
padding-left: 20px; | |||||
padding-top: 7px; | padding-top: 7px; | ||||
} | } | ||||
.menu-vertical li.tocentry.current > ul.currentrelative { | |||||
padding-left: 7px; | |||||
padding-bottom: 5px; | |||||
margin-top: 0; | |||||
} | |||||
.menu-vertical li.tocentry > ul.currentrelative { | |||||
padding-left: 7px; | |||||
padding-bottom: 5px; | |||||
margin-top: -3px; | |||||
} | |||||
.menu-vertical li.tocentry > ul.currentrelative > li.tocentry { | |||||
padding-top: 3px !important; | |||||
} | |||||
.menu-vertical li.tocrootentry { | .menu-vertical li.tocrootentry { | ||||
padding-left: 15px; | padding-left: 15px; | ||||
} | } | ||||
@@ -1047,10 +1047,26 @@ div.figure p { | |||||
} | } | ||||
.menu-vertical li.tocentry { | .menu-vertical li.tocentry { | ||||
padding-left: 15px; | |||||
padding-left: 20px; | |||||
padding-top: 7px; | padding-top: 7px; | ||||
} | } | ||||
.menu-vertical li.tocentry.current > ul.currentrelative { | |||||
padding-left: 7px; | |||||
padding-bottom: 5px; | |||||
margin-top: 0; | |||||
} | |||||
.menu-vertical li.tocentry > ul.currentrelative { | |||||
padding-left: 7px; | |||||
padding-bottom: 5px; | |||||
margin-top: -3px; | |||||
} | |||||
.menu-vertical li.tocentry > ul.currentrelative > li.tocentry { | |||||
padding-top: 3px !important; | |||||
} | |||||
.menu-vertical li.tocrootentry { | .menu-vertical li.tocrootentry { | ||||
padding-left: 15px; | padding-left: 15px; | ||||
} | } | ||||
@@ -72,10 +72,11 @@ namespace Docnet | |||||
/// Generates the search data, which is the json file called 'search_index.json' with search data of all pages as well as the docnet_search.htm file in the output. | /// Generates the search data, which is the json file called 'search_index.json' with search data of all pages as well as the docnet_search.htm file in the output. | ||||
/// The search index is written to the root of the output folder. | /// The search index is written to the root of the output folder. | ||||
/// </summary> | /// </summary> | ||||
internal void GenerateSearchData() | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
internal void GenerateSearchData(NavigationContext navigationContext) | |||||
{ | { | ||||
GenerateSearchPage(); | |||||
GenerateSearchDataIndex(); | |||||
GenerateSearchPage(navigationContext); | |||||
GenerateSearchDataIndex(navigationContext); | |||||
} | } | ||||
internal void CopyThemeToDestination() | internal void CopyThemeToDestination() | ||||
@@ -128,10 +129,10 @@ namespace Docnet | |||||
/// <summary> | /// <summary> | ||||
/// Generates the index of the search data. this is a json file with per page which has markdown a couple of data elements. | /// Generates the index of the search data. this is a json file with per page which has markdown a couple of data elements. | ||||
/// </summary> | /// </summary> | ||||
private void GenerateSearchDataIndex() | |||||
private void GenerateSearchDataIndex(NavigationContext navigationContext) | |||||
{ | { | ||||
var collectedSearchEntries = new List<SearchIndexEntry>(); | var collectedSearchEntries = new List<SearchIndexEntry>(); | ||||
this.Pages.CollectSearchIndexEntries(collectedSearchEntries, new NavigatedPath(), this.PathSpecification); | |||||
this.Pages.CollectSearchIndexEntries(collectedSearchEntries, new NavigatedPath(), navigationContext); | |||||
JObject searchIndex = new JObject(new JProperty("docs", | JObject searchIndex = new JObject(new JProperty("docs", | ||||
new JArray( | new JArray( | ||||
collectedSearchEntries.Select(e=>new JObject( | collectedSearchEntries.Select(e=>new JObject( | ||||
@@ -145,7 +146,7 @@ namespace Docnet | |||||
} | } | ||||
private void GenerateSearchPage() | |||||
private void GenerateSearchPage(NavigationContext navigationContext) | |||||
{ | { | ||||
var activePath = new NavigatedPath(); | var activePath = new NavigatedPath(); | ||||
activePath.Push(this.Pages); | activePath.Push(this.Pages); | ||||
@@ -164,7 +165,7 @@ namespace Docnet | |||||
searchSimpleElement.ExtraScriptProducerFunc = e=> @" | searchSimpleElement.ExtraScriptProducerFunc = e=> @" | ||||
<script>var base_url = '.';</script> | <script>var base_url = '.';</script> | ||||
<script data-main=""js/search.js"" src=""js/require.js""></script>"; | <script data-main=""js/search.js"" src=""js/require.js""></script>"; | ||||
searchSimpleElement.GenerateOutput(this, activePath, this.PathSpecification); | |||||
searchSimpleElement.GenerateOutput(this, activePath, navigationContext); | |||||
activePath.Pop(); | activePath.Pop(); | ||||
} | } | ||||
@@ -216,6 +217,14 @@ namespace Docnet | |||||
} | } | ||||
} | } | ||||
public int MaxLevelInToC | |||||
{ | |||||
get | |||||
{ | |||||
return _configData.MaxLevelInToC ?? 2; | |||||
} | |||||
} | |||||
public string ThemeName | public string ThemeName | ||||
{ | { | ||||
get | get | ||||
@@ -58,6 +58,7 @@ | |||||
<Compile Include="Engine.cs" /> | <Compile Include="Engine.cs" /> | ||||
<Compile Include="INavigationElement.cs" /> | <Compile Include="INavigationElement.cs" /> | ||||
<Compile Include="NavigatedPath.cs" /> | <Compile Include="NavigatedPath.cs" /> | ||||
<Compile Include="NavigationContext.cs" /> | |||||
<Compile Include="NavigationElement.cs" /> | <Compile Include="NavigationElement.cs" /> | ||||
<Compile Include="NavigationLevel.cs" /> | <Compile Include="NavigationLevel.cs" /> | ||||
<Compile Include="PathSpecification.cs" /> | <Compile Include="PathSpecification.cs" /> | ||||
@@ -44,7 +44,14 @@ namespace Docnet | |||||
{ | { | ||||
return 1; | return 1; | ||||
} | } | ||||
GeneratePages(); | |||||
var navigationContext = new NavigationContext | |||||
{ | |||||
MaxLevel = _loadedConfig.MaxLevelInToC, | |||||
PathSpecification = _loadedConfig.PathSpecification | |||||
}; | |||||
GeneratePages(navigationContext); | |||||
return 0; | return 0; | ||||
} | } | ||||
@@ -86,7 +93,7 @@ namespace Docnet | |||||
/// Generates the pages from the md files in the source, using the page template loaded and the loaded config. | /// Generates the pages from the md files in the source, using the page template loaded and the loaded config. | ||||
/// </summary> | /// </summary> | ||||
/// <returns>true if everything went ok, false otherwise</returns> | /// <returns>true if everything went ok, false otherwise</returns> | ||||
private void GeneratePages() | |||||
private void GeneratePages(NavigationContext navigationContext) | |||||
{ | { | ||||
if(_input.ClearDestinationFolder) | if(_input.ClearDestinationFolder) | ||||
{ | { | ||||
@@ -98,9 +105,9 @@ namespace Docnet | |||||
Console.WriteLine("Copying source folders to copy."); | Console.WriteLine("Copying source folders to copy."); | ||||
_loadedConfig.CopySourceFoldersToCopy(); | _loadedConfig.CopySourceFoldersToCopy(); | ||||
Console.WriteLine("Generating pages in '{0}'", _loadedConfig.Destination); | Console.WriteLine("Generating pages in '{0}'", _loadedConfig.Destination); | ||||
_loadedConfig.Pages.GenerateOutput(_loadedConfig, new NavigatedPath(), _loadedConfig.PathSpecification); | |||||
_loadedConfig.Pages.GenerateOutput(_loadedConfig, new NavigatedPath(), navigationContext); | |||||
Console.WriteLine("Generating search index"); | Console.WriteLine("Generating search index"); | ||||
_loadedConfig.GenerateSearchData(); | |||||
_loadedConfig.GenerateSearchData(navigationContext); | |||||
Console.WriteLine("Done!"); | Console.WriteLine("Done!"); | ||||
} | } | ||||
} | } |
@@ -35,24 +35,24 @@ namespace Docnet | |||||
/// </summary> | /// </summary> | ||||
/// <param name="activeConfig">The active configuration to use for the output.</param> | /// <param name="activeConfig">The active configuration to use for the output.</param> | ||||
/// <param name="activePath">The active path navigated through the ToC to reach this element.</param> | /// <param name="activePath">The active path navigated through the ToC to reach this element.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification); | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
void GenerateOutput(Config activeConfig, NavigatedPath activePath, NavigationContext navigationContext); | |||||
/// <summary> | /// <summary> | ||||
/// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu. | /// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | /// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | ||||
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification); | |||||
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, NavigationContext navigationContext); | |||||
/// <summary> | /// <summary> | ||||
/// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element. | /// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="collectedEntries">The collected entries.</param> | /// <param name="collectedEntries">The collected entries.</param> | ||||
/// <param name="activePath">The active path currently navigated.</param> | /// <param name="activePath">The active path currently navigated.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification); | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, NavigationContext navigationContext); | |||||
/// <summary> | /// <summary> | ||||
/// Gets the target URL with respect to the <see cref="PathSpecification"/>. | /// Gets the target URL with respect to the <see cref="PathSpecification"/>. | ||||
@@ -77,9 +77,9 @@ namespace Docnet | |||||
/// aren't, are not expanded. | /// aren't, are not expanded. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public string CreateToCHTML(string relativePathToRoot, PathSpecification pathSpecification) | |||||
public string CreateToCHTML(string relativePathToRoot, NavigationContext navigationContext) | |||||
{ | { | ||||
// the root container is the bottom element of this path. We use that container to build the root and navigate any node open along the navigated path. | // the root container is the bottom element of this path. We use that container to build the root and navigate any node open along the navigated path. | ||||
var rootContainer = this.Reverse().FirstOrDefault() as NavigationLevel; | var rootContainer = this.Reverse().FirstOrDefault() as NavigationLevel; | ||||
@@ -88,7 +88,7 @@ namespace Docnet | |||||
// no root container, no TOC | // no root container, no TOC | ||||
return string.Empty; | return string.Empty; | ||||
} | } | ||||
return rootContainer.GenerateToCFragment(this, relativePathToRoot, pathSpecification); | |||||
return rootContainer.GenerateToCFragment(this, relativePathToRoot, navigationContext); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -0,0 +1,21 @@ | |||||
namespace Docnet | |||||
{ | |||||
public class NavigationContext | |||||
{ | |||||
public NavigationContext() | |||||
{ | |||||
MaxLevel = 2; | |||||
} | |||||
public NavigationContext(PathSpecification pathSpecification, int maxLevel) | |||||
: this() | |||||
{ | |||||
PathSpecification = pathSpecification; | |||||
MaxLevel = maxLevel; | |||||
} | |||||
public int MaxLevel { get; set; } | |||||
public PathSpecification PathSpecification { get; set; } | |||||
} | |||||
} |
@@ -36,23 +36,23 @@ namespace Docnet | |||||
/// </summary> | /// </summary> | ||||
/// <param name="activeConfig">The active configuration to use for the output.</param> | /// <param name="activeConfig">The active configuration to use for the output.</param> | ||||
/// <param name="activePath">The active path navigated through the ToC to reach this element.</param> | /// <param name="activePath">The active path navigated through the ToC to reach this element.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public abstract void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification); | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
public abstract void GenerateOutput(Config activeConfig, NavigatedPath activePath, NavigationContext navigationContext); | |||||
/// <summary> | /// <summary> | ||||
/// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu. | /// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | /// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | ||||
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification); | |||||
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, NavigationContext navigationContext); | |||||
/// <summary> | /// <summary> | ||||
/// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element. | /// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="collectedEntries">The collected entries.</param> | /// <param name="collectedEntries">The collected entries.</param> | ||||
/// <param name="activePath">The active path currently navigated.</param> | /// <param name="activePath">The active path currently navigated.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public abstract void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification); | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
public abstract void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, NavigationContext navigationContext); | |||||
/// <summary> | /// <summary> | ||||
/// Gets the target URL with respect to the <see cref="PathSpecification"/>. | /// Gets the target URL with respect to the <see cref="PathSpecification"/>. | ||||
@@ -106,13 +106,13 @@ namespace Docnet | |||||
/// </summary> | /// </summary> | ||||
/// <param name="collectedEntries">The collected entries.</param> | /// <param name="collectedEntries">The collected entries.</param> | ||||
/// <param name="activePath">The active path currently navigated.</param> | /// <param name="activePath">The active path currently navigated.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification) | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, NavigationContext navigationContext) | |||||
{ | { | ||||
activePath.Push(this); | activePath.Push(this); | ||||
foreach (var element in this.Value) | foreach (var element in this.Value) | ||||
{ | { | ||||
element.CollectSearchIndexEntries(collectedEntries, activePath, pathSpecification); | |||||
element.CollectSearchIndexEntries(collectedEntries, activePath, navigationContext); | |||||
} | } | ||||
activePath.Pop(); | activePath.Pop(); | ||||
} | } | ||||
@@ -123,15 +123,15 @@ namespace Docnet | |||||
/// </summary> | /// </summary> | ||||
/// <param name="activeConfig">The active configuration to use for the output.</param> | /// <param name="activeConfig">The active configuration to use for the output.</param> | ||||
/// <param name="activePath">The active path navigated through the ToC to reach this element.</param> | /// <param name="activePath">The active path navigated through the ToC to reach this element.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification) | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, NavigationContext navigationContext) | |||||
{ | { | ||||
activePath.Push(this); | activePath.Push(this); | ||||
int i = 0; | int i = 0; | ||||
while (i < this.Value.Count) | while (i < this.Value.Count) | ||||
{ | { | ||||
var element = this.Value[i]; | var element = this.Value[i]; | ||||
element.GenerateOutput(activeConfig, activePath, pathSpecification); | |||||
element.GenerateOutput(activeConfig, activePath, navigationContext); | |||||
i++; | i++; | ||||
} | } | ||||
activePath.Pop(); | activePath.Pop(); | ||||
@@ -143,9 +143,9 @@ namespace Docnet | |||||
/// </summary> | /// </summary> | ||||
/// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | /// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | ||||
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) | |||||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, NavigationContext navigationContext) | |||||
{ | { | ||||
var fragments = new List<string>(); | var fragments = new List<string>(); | ||||
if (!this.IsRoot) | if (!this.IsRoot) | ||||
@@ -167,7 +167,7 @@ namespace Docnet | |||||
// first render the level header, which is the index element, if present or a label. The root always has an __index element otherwise we'd have stopped at load. | // first render the level header, which is the index element, if present or a label. The root always has an __index element otherwise we'd have stopped at load. | ||||
var elementStartTag = "<li><span class=\"navigationgroup\"><i class=\"fa fa-caret-down\"></i> "; | var elementStartTag = "<li><span class=\"navigationgroup\"><i class=\"fa fa-caret-down\"></i> "; | ||||
var indexElement = this.GetIndexElement(pathSpecification); | |||||
var indexElement = this.GetIndexElement(navigationContext.PathSpecification); | |||||
if (indexElement == null) | if (indexElement == null) | ||||
{ | { | ||||
fragments.Add(string.Format("{0}{1}</span></li>", elementStartTag, this.Name)); | fragments.Add(string.Format("{0}{1}</span></li>", elementStartTag, this.Name)); | ||||
@@ -176,18 +176,18 @@ namespace Docnet | |||||
{ | { | ||||
if (this.IsRoot) | if (this.IsRoot) | ||||
{ | { | ||||
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification)); | |||||
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, navigationContext)); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
fragments.Add(string.Format("{0}<a href=\"{1}{2}\">{3}</a></span></li>", | fragments.Add(string.Format("{0}<a href=\"{1}{2}\">{3}</a></span></li>", | ||||
elementStartTag, relativePathToRoot, indexElement.GetFinalTargetUrl(pathSpecification), this.Name)); | |||||
elementStartTag, relativePathToRoot, indexElement.GetFinalTargetUrl(navigationContext.PathSpecification), this.Name)); | |||||
} | } | ||||
} | } | ||||
// then the elements in the container. Index elements are skipped here. | // then the elements in the container. Index elements are skipped here. | ||||
foreach (var element in this.Value) | foreach (var element in this.Value) | ||||
{ | { | ||||
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification)); | |||||
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, navigationContext)); | |||||
} | } | ||||
fragments.Add("</ul>"); | fragments.Add("</ul>"); | ||||
} | } | ||||
@@ -195,7 +195,7 @@ namespace Docnet | |||||
{ | { | ||||
// just a link | // just a link | ||||
fragments.Add(string.Format("<span class=\"navigationgroup\"><i class=\"fa fa-caret-right\"></i> <a href=\"{0}{1}\">{2}</a></span>", | fragments.Add(string.Format("<span class=\"navigationgroup\"><i class=\"fa fa-caret-right\"></i> <a href=\"{0}{1}\">{2}</a></span>", | ||||
relativePathToRoot, this.GetFinalTargetUrl(pathSpecification), this.Name)); | |||||
relativePathToRoot, this.GetFinalTargetUrl(navigationContext.PathSpecification), this.Name)); | |||||
} | } | ||||
if (!this.IsRoot) | if (!this.IsRoot) | ||||
{ | { | ||||
@@ -27,6 +27,7 @@ using System.Linq; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Web; | using System.Web; | ||||
using MarkdownDeep; | |||||
namespace Docnet | namespace Docnet | ||||
{ | { | ||||
@@ -34,13 +35,13 @@ namespace Docnet | |||||
{ | { | ||||
#region Members | #region Members | ||||
private string _targetURLForHTML; | private string _targetURLForHTML; | ||||
private List<Tuple<string, string>> _relativeH2LinksOnPage; // first element in Tuple is anchor name, second is name for ToC. | |||||
private readonly List<Heading> _relativeLinksOnPage; | |||||
#endregion | #endregion | ||||
public SimpleNavigationElement() | public SimpleNavigationElement() | ||||
{ | { | ||||
_relativeH2LinksOnPage = new List<Tuple<string, string>>(); | |||||
_relativeLinksOnPage = new List<Heading>(); | |||||
} | } | ||||
@@ -49,56 +50,57 @@ namespace Docnet | |||||
/// </summary> | /// </summary> | ||||
/// <param name="activeConfig">The active configuration to use for the output.</param> | /// <param name="activeConfig">The active configuration to use for the output.</param> | ||||
/// <param name="activePath">The active path navigated through the ToC to reach this element.</param> | /// <param name="activePath">The active path navigated through the ToC to reach this element.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
/// <exception cref="FileNotFoundException"></exception> | |||||
/// <exception cref="System.IO.FileNotFoundException"></exception> | /// <exception cref="System.IO.FileNotFoundException"></exception> | ||||
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification) | |||||
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, NavigationContext navigationContext) | |||||
{ | { | ||||
// if we're the __index element, we're not pushing ourselves on the path, as we're representing the container we're in, which is already on the path. | // if we're the __index element, we're not pushing ourselves on the path, as we're representing the container we're in, which is already on the path. | ||||
if(!this.IsIndexElement) | |||||
if (!this.IsIndexElement) | |||||
{ | { | ||||
activePath.Push(this); | activePath.Push(this); | ||||
} | } | ||||
_relativeH2LinksOnPage.Clear(); | |||||
_relativeLinksOnPage.Clear(); | |||||
var sourceFile = Utils.MakeAbsolutePath(activeConfig.Source, this.Value); | var sourceFile = Utils.MakeAbsolutePath(activeConfig.Source, this.Value); | ||||
var destinationFile = Utils.MakeAbsolutePath(activeConfig.Destination, this.GetTargetURL(pathSpecification)); | |||||
var destinationFile = Utils.MakeAbsolutePath(activeConfig.Destination, this.GetTargetURL(navigationContext.PathSpecification)); | |||||
var sb = new StringBuilder(activeConfig.PageTemplateContents.Length + 2048); | var sb = new StringBuilder(activeConfig.PageTemplateContents.Length + 2048); | ||||
var content = string.Empty; | var content = string.Empty; | ||||
this.MarkdownFromFile = string.Empty; | this.MarkdownFromFile = string.Empty; | ||||
var relativePathToRoot = Utils.MakeRelativePathForUri(Path.GetDirectoryName(destinationFile), activeConfig.Destination); | var relativePathToRoot = Utils.MakeRelativePathForUri(Path.GetDirectoryName(destinationFile), activeConfig.Destination); | ||||
if(File.Exists(sourceFile)) | |||||
if (File.Exists(sourceFile)) | |||||
{ | { | ||||
this.MarkdownFromFile = File.ReadAllText(sourceFile, Encoding.UTF8); | this.MarkdownFromFile = File.ReadAllText(sourceFile, Encoding.UTF8); | ||||
// Check if the content contains @@include tag | // Check if the content contains @@include tag | ||||
content = Utils.IncludeProcessor(this.MarkdownFromFile, Utils.MakeAbsolutePath(activeConfig.Source, activeConfig.IncludeFolder)); | content = Utils.IncludeProcessor(this.MarkdownFromFile, Utils.MakeAbsolutePath(activeConfig.Source, activeConfig.IncludeFolder)); | ||||
content = Utils.ConvertMarkdownToHtml(content, Path.GetDirectoryName(destinationFile), activeConfig.Destination, sourceFile, _relativeH2LinksOnPage, activeConfig.ConvertLocalLinks); | |||||
content = Utils.ConvertMarkdownToHtml(content, Path.GetDirectoryName(destinationFile), activeConfig.Destination, sourceFile, _relativeLinksOnPage, activeConfig.ConvertLocalLinks); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
// if we're not the index element, the file is missing and potentially it's an error in the config page. | // if we're not the index element, the file is missing and potentially it's an error in the config page. | ||||
// Otherwise we can simply assume we are a missing index page and we'll generate default markdown so the user has something to look at. | // Otherwise we can simply assume we are a missing index page and we'll generate default markdown so the user has something to look at. | ||||
if(this.IsIndexElement) | |||||
if (this.IsIndexElement) | |||||
{ | { | ||||
// replace with default markdown snippet. This is the name of our container and links to the elements in that container as we are the index page that's not | // replace with default markdown snippet. This is the name of our container and links to the elements in that container as we are the index page that's not | ||||
// specified / existend. | // specified / existend. | ||||
var defaultMarkdown = new StringBuilder(); | var defaultMarkdown = new StringBuilder(); | ||||
defaultMarkdown.AppendFormat("# {0}{1}{1}", this.ParentContainer.Name, Environment.NewLine); | defaultMarkdown.AppendFormat("# {0}{1}{1}", this.ParentContainer.Name, Environment.NewLine); | ||||
defaultMarkdown.AppendFormat("Please select one of the topics in this section:{0}{0}", Environment.NewLine); | defaultMarkdown.AppendFormat("Please select one of the topics in this section:{0}{0}", Environment.NewLine); | ||||
foreach(var sibling in this.ParentContainer.Value) | |||||
foreach (var sibling in this.ParentContainer.Value) | |||||
{ | { | ||||
if(sibling == this) | |||||
if (sibling == this) | |||||
{ | { | ||||
continue; | continue; | ||||
} | } | ||||
defaultMarkdown.AppendFormat("* [{0}]({1}{2}){3}", sibling.Name, relativePathToRoot, | |||||
sibling.GetFinalTargetUrl(pathSpecification), Environment.NewLine); | |||||
defaultMarkdown.AppendFormat("* [{0}]({1}{2}){3}", sibling.Name, relativePathToRoot, | |||||
sibling.GetFinalTargetUrl(navigationContext.PathSpecification), Environment.NewLine); | |||||
} | } | ||||
defaultMarkdown.Append(Environment.NewLine); | defaultMarkdown.Append(Environment.NewLine); | ||||
content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeH2LinksOnPage, activeConfig.ConvertLocalLinks); | |||||
content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeLinksOnPage, activeConfig.ConvertLocalLinks); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
// target not found. See if there's a content producer func to produce html for us. If not, we can only conclude an error in the config file. | // target not found. See if there's a content producer func to produce html for us. If not, we can only conclude an error in the config file. | ||||
if(this.ContentProducerFunc == null) | |||||
if (this.ContentProducerFunc == null) | |||||
{ | { | ||||
throw new FileNotFoundException(string.Format("The specified markdown file '{0}' couldn't be found. Aborting", sourceFile)); | throw new FileNotFoundException(string.Format("The specified markdown file '{0}' couldn't be found. Aborting", sourceFile)); | ||||
} | } | ||||
@@ -110,17 +112,17 @@ namespace Docnet | |||||
sb.Replace("{{Footer}}", activeConfig.Footer); | sb.Replace("{{Footer}}", activeConfig.Footer); | ||||
sb.Replace("{{TopicTitle}}", this.Name); | sb.Replace("{{TopicTitle}}", this.Name); | ||||
sb.Replace("{{Path}}", relativePathToRoot); | sb.Replace("{{Path}}", relativePathToRoot); | ||||
sb.Replace("{{RelativeSourceFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, sourceFile).TrimEnd('/')); | |||||
sb.Replace("{{RelativeTargetFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, destinationFile).TrimEnd('/')); | |||||
sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot, pathSpecification)); | |||||
sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, pathSpecification)); | |||||
sb.Replace("{{RelativeSourceFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, sourceFile).TrimEnd('/')); | |||||
sb.Replace("{{RelativeTargetFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, destinationFile).TrimEnd('/')); | |||||
sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot, navigationContext.PathSpecification)); | |||||
sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, navigationContext)); | |||||
sb.Replace("{{ExtraScript}}", (this.ExtraScriptProducerFunc == null) ? string.Empty : this.ExtraScriptProducerFunc(this)); | sb.Replace("{{ExtraScript}}", (this.ExtraScriptProducerFunc == null) ? string.Empty : this.ExtraScriptProducerFunc(this)); | ||||
// the last action has to be replacing the content marker, so markers in the content which we have in the template as well aren't replaced | // the last action has to be replacing the content marker, so markers in the content which we have in the template as well aren't replaced | ||||
sb.Replace("{{Content}}", content); | sb.Replace("{{Content}}", content); | ||||
Utils.CreateFoldersIfRequired(destinationFile); | Utils.CreateFoldersIfRequired(destinationFile); | ||||
File.WriteAllText(destinationFile, sb.ToString()); | File.WriteAllText(destinationFile, sb.ToString()); | ||||
if(!this.IsIndexElement) | |||||
if (!this.IsIndexElement) | |||||
{ | { | ||||
activePath.Pop(); | activePath.Pop(); | ||||
} | } | ||||
@@ -132,15 +134,15 @@ namespace Docnet | |||||
/// </summary> | /// </summary> | ||||
/// <param name="collectedEntries">The collected entries.</param> | /// <param name="collectedEntries">The collected entries.</param> | ||||
/// <param name="activePath">The active path currently navigated.</param> | /// <param name="activePath">The active path currently navigated.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification) | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, NavigationContext navigationContext) | |||||
{ | { | ||||
activePath.Push(this); | activePath.Push(this); | ||||
// simply convert ourselves into an entry if we're not an index | // simply convert ourselves into an entry if we're not an index | ||||
if(!this.IsIndexElement) | |||||
if (!this.IsIndexElement) | |||||
{ | { | ||||
var toAdd = new SearchIndexEntry(); | var toAdd = new SearchIndexEntry(); | ||||
toAdd.Fill(this.MarkdownFromFile, this.GetTargetURL(pathSpecification), this.Name, activePath); | |||||
toAdd.Fill(this.MarkdownFromFile, this.GetTargetURL(navigationContext.PathSpecification), this.Name, activePath); | |||||
collectedEntries.Add(toAdd); | collectedEntries.Add(toAdd); | ||||
} | } | ||||
activePath.Pop(); | activePath.Pop(); | ||||
@@ -152,17 +154,17 @@ namespace Docnet | |||||
/// </summary> | /// </summary> | ||||
/// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | /// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | ||||
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) | |||||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, NavigationContext navigationContext) | |||||
{ | { | ||||
// index elements are rendered in the parent container. | // index elements are rendered in the parent container. | ||||
if(this.IsIndexElement) | |||||
if (this.IsIndexElement) | |||||
{ | { | ||||
return string.Empty; | return string.Empty; | ||||
} | } | ||||
return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification); | |||||
return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, navigationContext); | |||||
} | } | ||||
@@ -172,16 +174,16 @@ namespace Docnet | |||||
/// </summary> | /// </summary> | ||||
/// <param name="navigatedPath">The navigated path.</param> | /// <param name="navigatedPath">The navigated path.</param> | ||||
/// <param name="relativePathToRoot">The relative path to root.</param> | /// <param name="relativePathToRoot">The relative path to root.</param> | ||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <param name="navigationContext">The navigation context.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) | |||||
public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, NavigationContext navigationContext) | |||||
{ | { | ||||
// we can't navigate deeper from here. If we are the element being navigated to, we are the current and will have to emit any additional relative URLs too. | // we can't navigate deeper from here. If we are the element being navigated to, we are the current and will have to emit any additional relative URLs too. | ||||
bool isCurrent = navigatedPath.Contains(this); | bool isCurrent = navigatedPath.Contains(this); | ||||
var fragments = new List<string>(); | var fragments = new List<string>(); | ||||
var liClass = "tocentry"; | var liClass = "tocentry"; | ||||
var aClass = string.Empty; | var aClass = string.Empty; | ||||
if(isCurrent) | |||||
if (isCurrent) | |||||
{ | { | ||||
liClass = "tocentry current"; | liClass = "tocentry current"; | ||||
aClass = "current"; | aClass = "current"; | ||||
@@ -190,16 +192,22 @@ namespace Docnet | |||||
string.IsNullOrWhiteSpace(liClass) ? string.Empty : string.Format(" class=\"{0}\"", liClass), | string.IsNullOrWhiteSpace(liClass) ? string.Empty : string.Format(" class=\"{0}\"", liClass), | ||||
string.IsNullOrWhiteSpace(aClass) ? string.Empty : string.Format(" class=\"{0}\"", aClass), | string.IsNullOrWhiteSpace(aClass) ? string.Empty : string.Format(" class=\"{0}\"", aClass), | ||||
relativePathToRoot, | relativePathToRoot, | ||||
this.GetFinalTargetUrl(pathSpecification), | |||||
this.GetFinalTargetUrl(navigationContext.PathSpecification), | |||||
this.Name)); | this.Name)); | ||||
if(isCurrent && _relativeH2LinksOnPage.Any()) | |||||
if (isCurrent && _relativeLinksOnPage.SelectMany(x => x.Children).Any(x => x.Level > 1)) | |||||
{ | { | ||||
// generate relative links | // generate relative links | ||||
fragments.Add(string.Format("<ul class=\"{0}\">", this.ParentContainer.IsRoot ? "currentrelativeroot" : "currentrelative")); | fragments.Add(string.Format("<ul class=\"{0}\">", this.ParentContainer.IsRoot ? "currentrelativeroot" : "currentrelative")); | ||||
foreach(var p in _relativeH2LinksOnPage) | |||||
foreach (var heading in _relativeLinksOnPage) | |||||
{ | { | ||||
fragments.Add(string.Format("<li class=\"tocentry\"><a href=\"#{0}\">{1}</a></li>", p.Item1, p.Item2)); | |||||
var content = GenerateToCFragmentForHeading(heading, navigationContext); | |||||
if (!string.IsNullOrWhiteSpace(content)) | |||||
{ | |||||
fragments.Add(content); | |||||
} | |||||
} | } | ||||
fragments.Add("</ul>"); | fragments.Add("</ul>"); | ||||
} | } | ||||
else | else | ||||
@@ -243,6 +251,48 @@ namespace Docnet | |||||
return _targetURLForHTML; | return _targetURLForHTML; | ||||
} | } | ||||
private string GenerateToCFragmentForHeading(Heading heading, NavigationContext navigationContext) | |||||
{ | |||||
var stringBuilder = new StringBuilder(); | |||||
// Skip heading 1 and larger than allowed | |||||
var isHeading1 = heading.Level <= 1; | |||||
if (!isHeading1 && heading.Level <= navigationContext.MaxLevel) | |||||
{ | |||||
stringBuilder.AppendLine(string.Format("<li class=\"tocentry\"><a href=\"#{0}\">{1}</a></li>", heading.Id, heading.Name)); | |||||
} | |||||
var childContentBuilder = new StringBuilder(); | |||||
foreach (var child in heading.Children) | |||||
{ | |||||
var childContent = GenerateToCFragmentForHeading(child, navigationContext); | |||||
if (!string.IsNullOrWhiteSpace(childContent)) | |||||
{ | |||||
childContentBuilder.AppendLine(childContent); | |||||
} | |||||
} | |||||
if (childContentBuilder.Length > 0) | |||||
{ | |||||
if (!isHeading1) | |||||
{ | |||||
stringBuilder.AppendLine("<li class=\"tocentry\">"); | |||||
stringBuilder.AppendLine(string.Format("<ul class=\"{0}\">", this.ParentContainer.IsRoot ? "currentrelativeroot" : "currentrelative")); | |||||
} | |||||
stringBuilder.AppendLine(childContentBuilder.ToString()); | |||||
if (!isHeading1) | |||||
{ | |||||
stringBuilder.AppendLine("</ul>"); | |||||
stringBuilder.AppendLine("</li>"); | |||||
} | |||||
} | |||||
return stringBuilder.ToString(); | |||||
} | |||||
#region Properties | #region Properties | ||||
/// <summary> | /// <summary> | ||||
/// Gets / sets a value indicating whether this element is the __index element | /// Gets / sets a value indicating whether this element is the __index element | ||||
@@ -27,6 +27,7 @@ using System.Linq; | |||||
using System.Text; | using System.Text; | ||||
using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using MarkdownDeep; | |||||
namespace Docnet | namespace Docnet | ||||
{ | { | ||||
@@ -50,7 +51,7 @@ namespace Docnet | |||||
/// <param name="convertLocalLinks">if set to <c>true</c>, convert local links to md files to target files.</param> | /// <param name="convertLocalLinks">if set to <c>true</c>, convert local links to md files to target files.</param> | ||||
/// <returns></returns> | /// <returns></returns> | ||||
public static string ConvertMarkdownToHtml(string toConvert, string destinationDocumentPath, string siteRoot, string sourceDocumentFilename, | public static string ConvertMarkdownToHtml(string toConvert, string destinationDocumentPath, string siteRoot, string sourceDocumentFilename, | ||||
List<Tuple<string, string>> createdAnchorCollector, bool convertLocalLinks) | |||||
List<Heading> createdAnchorCollector, bool convertLocalLinks) | |||||
{ | { | ||||
var parser = new MarkdownDeep.Markdown | var parser = new MarkdownDeep.Markdown | ||||
{ | { | ||||
@@ -67,7 +68,9 @@ namespace Docnet | |||||
}; | }; | ||||
var toReturn = parser.Transform(toConvert); | var toReturn = parser.Transform(toConvert); | ||||
createdAnchorCollector.AddRange(parser.CreatedH2IdCollector); | |||||
createdAnchorCollector.AddRange(parser.Headings.ConvertToHierarchy()); | |||||
return toReturn; | return toReturn; | ||||
} | } | ||||
@@ -193,14 +193,19 @@ namespace MarkdownDeep | |||||
{ | { | ||||
b.Append("<" + BlockType.ToString() + ">"); | b.Append("<" + BlockType.ToString() + ">"); | ||||
} | } | ||||
if(m.DocNetMode && BlockType == BlockType.h2 && !string.IsNullOrWhiteSpace(id)) | |||||
if(m.DocNetMode && !string.IsNullOrWhiteSpace(id)) | |||||
{ | { | ||||
// collect h2 id + text in collector | |||||
var h2ContentSb = new StringBuilder(); | |||||
m.SpanFormatter.Format(h2ContentSb, Buf, ContentStart, ContentLen); | |||||
var h2ContentAsString = h2ContentSb.ToString(); | |||||
b.Append(h2ContentAsString); | |||||
m.CreatedH2IdCollector.Add(new Tuple<string, string>(id, h2ContentAsString)); | |||||
// collect id + text in collector | |||||
var headerContentStringBuilder = new StringBuilder(); | |||||
m.SpanFormatter.Format(headerContentStringBuilder, Buf, ContentStart, ContentLen); | |||||
var headerContentAsString = headerContentStringBuilder.ToString(); | |||||
b.Append(headerContentAsString); | |||||
m.Headings.Add(new Heading | |||||
{ | |||||
Level = (int)BlockType, | |||||
Id = id, | |||||
Name = headerContentAsString | |||||
}); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -0,0 +1,59 @@ | |||||
using System.Collections.Generic; | |||||
namespace MarkdownDeep | |||||
{ | |||||
public static class Extensions | |||||
{ | |||||
public static List<Heading> ConvertToHierarchy(this List<Heading> headings) | |||||
{ | |||||
var hierarchy = new List<Heading>(); | |||||
for (var i = 0; i < headings.Count; i++) | |||||
{ | |||||
if (i > 0) | |||||
{ | |||||
var previousHeading = headings[i - 1]; | |||||
var currentHeading = headings[i]; | |||||
SetParentForHeading(previousHeading, currentHeading); | |||||
var parent = currentHeading.Parent; | |||||
if (parent == null) | |||||
{ | |||||
hierarchy.Add(currentHeading); | |||||
} | |||||
else | |||||
{ | |||||
parent.Children.Add(currentHeading); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
hierarchy.Add(headings[i]); | |||||
} | |||||
} | |||||
return hierarchy; | |||||
} | |||||
private static void SetParentForHeading(Heading previousHeading, Heading headingToAdd) | |||||
{ | |||||
if (previousHeading.Level == headingToAdd.Level) | |||||
{ | |||||
headingToAdd.Parent = previousHeading.Parent; | |||||
} | |||||
else if (previousHeading.Level < headingToAdd.Level) | |||||
{ | |||||
headingToAdd.Parent = previousHeading; | |||||
} | |||||
else if (previousHeading.Level > headingToAdd.Level) | |||||
{ | |||||
var previousHeadingParent = previousHeading.Parent; | |||||
if (previousHeadingParent != null) | |||||
{ | |||||
SetParentForHeading(previousHeadingParent, headingToAdd); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,43 @@ | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace MarkdownDeep | |||||
{ | |||||
public class Heading | |||||
{ | |||||
public Heading() | |||||
{ | |||||
Children = new List<Heading>(); | |||||
} | |||||
public Heading Parent { get; set; } | |||||
public List<Heading> Children { get; private set; } | |||||
public int Level { get; set; } | |||||
public string Id { get; set; } | |||||
public string Name { get; set; } | |||||
public override string ToString() | |||||
{ | |||||
var stringBuilder = new StringBuilder(); | |||||
for (var i = 0; i < Level; i++) | |||||
{ | |||||
stringBuilder.Append("#"); | |||||
} | |||||
stringBuilder.AppendLine($"{Id} - {Name}"); | |||||
foreach (var child in Children) | |||||
{ | |||||
stringBuilder.AppendLine(child.ToString()); | |||||
} | |||||
var value = stringBuilder.ToString(); | |||||
return value; | |||||
} | |||||
} | |||||
} |
@@ -58,7 +58,9 @@ namespace MarkdownDeep | |||||
m_Footnotes = new Dictionary<string, Block>(); | m_Footnotes = new Dictionary<string, Block>(); | ||||
m_UsedFootnotes = new List<Block>(); | m_UsedFootnotes = new List<Block>(); | ||||
m_UsedHeaderIDs = new Dictionary<string, bool>(); | m_UsedHeaderIDs = new Dictionary<string, bool>(); | ||||
this.CreatedH2IdCollector = new List<Tuple<string, string>>(); | |||||
this.Headings = new List<Heading>(); | |||||
_tabIdCounter = 0; | _tabIdCounter = 0; | ||||
} | } | ||||
@@ -977,13 +979,11 @@ namespace MarkdownDeep | |||||
set; | set; | ||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// Collector for the created id's for H2 headers. First element in Tuple is id name, second is name for ToC (the text for H2). Id's are generated | |||||
/// Collector for the created id's for headers. First element in Tuple is id name, second is name for ToC (the text for header). Id's are generated | |||||
/// by the parser and use pandoc algorithm, as AutoHeadingId's is switched on. Only in use if DocNetMode is set to true | /// by the parser and use pandoc algorithm, as AutoHeadingId's is switched on. Only in use if DocNetMode is set to true | ||||
/// </summary> | /// </summary> | ||||
public List<Tuple<string, string>> CreatedH2IdCollector { get; private set; } | |||||
public List<Heading> Headings { get; private set; } | |||||
// Set the html class for the footnotes div | // Set the html class for the footnotes div | ||||
// (defaults to "footnotes") | // (defaults to "footnotes") | ||||
@@ -77,7 +77,9 @@ | |||||
<ItemGroup> | <ItemGroup> | ||||
<Compile Include="Abbreviation.cs" /> | <Compile Include="Abbreviation.cs" /> | ||||
<Compile Include="BlockProcessor.cs" /> | <Compile Include="BlockProcessor.cs" /> | ||||
<Compile Include="Extensions.cs" /> | |||||
<Compile Include="FootnoteReference.cs" /> | <Compile Include="FootnoteReference.cs" /> | ||||
<Compile Include="Heading.cs" /> | |||||
<Compile Include="HtmlTag.cs" /> | <Compile Include="HtmlTag.cs" /> | ||||
<Compile Include="LinkDefinition.cs" /> | <Compile Include="LinkDefinition.cs" /> | ||||
<Compile Include="LinkInfo.cs" /> | <Compile Include="LinkInfo.cs" /> | ||||
@@ -0,0 +1,65 @@ | |||||
using System.Collections.Generic; | |||||
using MarkdownDeep; | |||||
using NUnit.Framework; | |||||
namespace MarkdownDeepTests | |||||
{ | |||||
[TestFixture] | |||||
public class ExtensionsTests | |||||
{ | |||||
[TestCase] | |||||
public void ConvertsHeadingsHierarchy() | |||||
{ | |||||
var headings = new List<Heading>(); | |||||
headings.Add(new Heading { Level = 1, Name = "1" }); | |||||
headings.Add(new Heading { Level = 2, Name = "1.1" }); | |||||
headings.Add(new Heading { Level = 3, Name = "1.1.1" }); | |||||
headings.Add(new Heading { Level = 2, Name = "1.2" }); | |||||
headings.Add(new Heading { Level = 4, Name = "1.2.1.1" }); | |||||
headings.Add(new Heading { Level = 2, Name = "1.3" }); | |||||
headings.Add(new Heading { Level = 1, Name = "2" }); | |||||
headings.Add(new Heading { Level = 3, Name = "2.1.1" }); | |||||
headings.Add(new Heading { Level = 2, Name = "2.2" }); | |||||
var hierarchy = headings.ConvertToHierarchy(); | |||||
Assert.AreEqual(2, hierarchy.Count); | |||||
var heading1 = hierarchy[0]; | |||||
Assert.AreEqual("1", heading1.Name); | |||||
Assert.AreEqual(3, heading1.Children.Count); | |||||
var heading1_1 = heading1.Children[0]; | |||||
Assert.AreEqual("1.1", heading1_1.Name); | |||||
Assert.AreEqual(1, heading1_1.Children.Count); | |||||
var heading1_1_1 = heading1_1.Children[0]; | |||||
Assert.AreEqual("1.1.1", heading1_1_1.Name); | |||||
Assert.AreEqual(0, heading1_1_1.Children.Count); | |||||
var heading1_2 = heading1.Children[1]; | |||||
Assert.AreEqual("1.2", heading1_2.Name); | |||||
Assert.AreEqual(1, heading1_2.Children.Count); | |||||
var heading1_2_1_1 = heading1_2.Children[0]; | |||||
Assert.AreEqual("1.2.1.1", heading1_2_1_1.Name); | |||||
Assert.AreEqual(0, heading1_2_1_1.Children.Count); | |||||
var heading1_3 = heading1.Children[2]; | |||||
Assert.AreEqual("1.3", heading1_3.Name); | |||||
Assert.AreEqual(0, heading1_3.Children.Count); | |||||
var heading2 = hierarchy[1]; | |||||
Assert.AreEqual("2", heading2.Name); | |||||
Assert.AreEqual(2, heading2.Children.Count); | |||||
var heading2_1_1 = heading2.Children[0]; | |||||
Assert.AreEqual("2.1.1", heading2_1_1.Name); | |||||
Assert.AreEqual(0, heading2_1_1.Children.Count); | |||||
var heading2_2 = heading2.Children[1]; | |||||
Assert.AreEqual("2.2", heading2_2.Name); | |||||
Assert.AreEqual(0, heading2_2.Children.Count); | |||||
} | |||||
} | |||||
} |
@@ -75,6 +75,7 @@ | |||||
<Compile Include="AutoLinkTests.cs" /> | <Compile Include="AutoLinkTests.cs" /> | ||||
<Compile Include="AutoHeaderIDTests.cs" /> | <Compile Include="AutoHeaderIDTests.cs" /> | ||||
<Compile Include="DocNetMode.cs" /> | <Compile Include="DocNetMode.cs" /> | ||||
<Compile Include="ExtensionsTests.cs" /> | |||||
<Compile Include="GithubMode.cs" /> | <Compile Include="GithubMode.cs" /> | ||||
<Compile Include="LocalLinkTests.cs" /> | <Compile Include="LocalLinkTests.cs" /> | ||||
<Compile Include="TableSpecTests.cs" /> | <Compile Include="TableSpecTests.cs" /> | ||||