Browse Source

Merge branch 'GeertvanHorrik-pr/max-level-in-toc'

pull/70/head
Frans Bouma 8 years ago
parent
commit
e4dc8786d4
20 changed files with 406 additions and 92 deletions
  1. +17
    -1
      Themes/Default/Destination/css/theme.css
  2. +17
    -1
      Themes/LLBLGenPro/Destination/css/theme.css
  3. +17
    -1
      Themes/Light/Destination/css/theme.css
  4. +16
    -7
      src/DocNet/Config.cs
  5. +1
    -0
      src/DocNet/Docnet.csproj
  6. +11
    -4
      src/DocNet/Engine.cs
  7. +6
    -6
      src/DocNet/INavigationElement.cs
  8. +3
    -3
      src/DocNet/NavigatedPath.cs
  9. +21
    -0
      src/DocNet/NavigationContext.cs
  10. +6
    -6
      src/DocNet/NavigationElement.cs
  11. +13
    -13
      src/DocNet/NavigationLevel.cs
  12. +86
    -36
      src/DocNet/SimpleNavigationElement.cs
  13. +5
    -2
      src/DocNet/Utils.cs
  14. +12
    -7
      src/MarkdownDeep/Block.cs
  15. +59
    -0
      src/MarkdownDeep/Extensions.cs
  16. +43
    -0
      src/MarkdownDeep/Heading.cs
  17. +5
    -5
      src/MarkdownDeep/MardownDeep.cs
  18. +2
    -0
      src/MarkdownDeep/MarkdownDeep.csproj
  19. +65
    -0
      src/MarkdownDeepTests/ExtensionsTests.cs
  20. +1
    -0
      src/MarkdownDeepTests/MarkdownDeepTests.csproj

+ 17
- 1
Themes/Default/Destination/css/theme.css View File

@@ -1047,10 +1047,26 @@ div.figure p {
}

.menu-vertical li.tocentry {
padding-left: 15px;
padding-left: 20px;
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 {
padding-left: 15px;
}


+ 17
- 1
Themes/LLBLGenPro/Destination/css/theme.css View File

@@ -1047,10 +1047,26 @@ div.figure p {
}

.menu-vertical li.tocentry {
padding-left: 15px;
padding-left: 20px;
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 {
padding-left: 15px;
}


+ 17
- 1
Themes/Light/Destination/css/theme.css View File

@@ -1047,10 +1047,26 @@ div.figure p {
}

.menu-vertical li.tocentry {
padding-left: 15px;
padding-left: 20px;
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 {
padding-left: 15px;
}


+ 16
- 7
src/DocNet/Config.cs View File

@@ -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.
/// The search index is written to the root of the output folder.
/// </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()
@@ -128,10 +129,10 @@ namespace Docnet
/// <summary>
/// Generates the index of the search data. this is a json file with per page which has markdown a couple of data elements.
/// </summary>
private void GenerateSearchDataIndex()
private void GenerateSearchDataIndex(NavigationContext navigationContext)
{
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",
new JArray(
collectedSearchEntries.Select(e=>new JObject(
@@ -145,7 +146,7 @@ namespace Docnet
}


private void GenerateSearchPage()
private void GenerateSearchPage(NavigationContext navigationContext)
{
var activePath = new NavigatedPath();
activePath.Push(this.Pages);
@@ -164,7 +165,7 @@ namespace Docnet
searchSimpleElement.ExtraScriptProducerFunc = e=> @"
<script>var base_url = '.';</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();
}

@@ -216,6 +217,14 @@ namespace Docnet
}
}

public int MaxLevelInToC
{
get
{
return _configData.MaxLevelInToC ?? 2;
}
}

public string ThemeName
{
get


+ 1
- 0
src/DocNet/Docnet.csproj View File

@@ -58,6 +58,7 @@
<Compile Include="Engine.cs" />
<Compile Include="INavigationElement.cs" />
<Compile Include="NavigatedPath.cs" />
<Compile Include="NavigationContext.cs" />
<Compile Include="NavigationElement.cs" />
<Compile Include="NavigationLevel.cs" />
<Compile Include="PathSpecification.cs" />


+ 11
- 4
src/DocNet/Engine.cs View File

@@ -44,7 +44,14 @@ namespace Docnet
{
return 1;
}
GeneratePages();

var navigationContext = new NavigationContext
{
MaxLevel = _loadedConfig.MaxLevelInToC,
PathSpecification = _loadedConfig.PathSpecification
};

GeneratePages(navigationContext);
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.
/// </summary>
/// <returns>true if everything went ok, false otherwise</returns>
private void GeneratePages()
private void GeneratePages(NavigationContext navigationContext)
{
if(_input.ClearDestinationFolder)
{
@@ -98,9 +105,9 @@ namespace Docnet
Console.WriteLine("Copying source folders to copy.");
_loadedConfig.CopySourceFoldersToCopy();
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");
_loadedConfig.GenerateSearchData();
_loadedConfig.GenerateSearchData(navigationContext);
Console.WriteLine("Done!");
}
}

+ 6
- 6
src/DocNet/INavigationElement.cs View File

@@ -35,24 +35,24 @@ namespace Docnet
/// </summary>
/// <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="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>
/// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu.
/// </summary>
/// <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="pathSpecification">The path specification.</param>
/// <param name="navigationContext">The navigation context.</param>
/// <returns></returns>
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification);
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, NavigationContext navigationContext);
/// <summary>
/// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element.
/// </summary>
/// <param name="collectedEntries">The collected entries.</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>
/// Gets the target URL with respect to the <see cref="PathSpecification"/>.


+ 3
- 3
src/DocNet/NavigatedPath.cs View File

@@ -77,9 +77,9 @@ namespace Docnet
/// aren't, are not expanded.
/// </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="pathSpecification">The path specification.</param>
/// <param name="navigationContext">The navigation context.</param>
/// <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.
var rootContainer = this.Reverse().FirstOrDefault() as NavigationLevel;
@@ -88,7 +88,7 @@ namespace Docnet
// no root container, no TOC
return string.Empty;
}
return rootContainer.GenerateToCFragment(this, relativePathToRoot, pathSpecification);
return rootContainer.GenerateToCFragment(this, relativePathToRoot, navigationContext);
}
}
}

+ 21
- 0
src/DocNet/NavigationContext.cs View File

@@ -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; }
}
}

+ 6
- 6
src/DocNet/NavigationElement.cs View File

@@ -36,23 +36,23 @@ namespace Docnet
/// </summary>
/// <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="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>
/// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu.
/// </summary>
/// <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="pathSpecification">The path specification.</param>
/// <param name="navigationContext">The navigation context.</param>
/// <returns></returns>
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification);
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, NavigationContext navigationContext);
/// <summary>
/// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element.
/// </summary>
/// <param name="collectedEntries">The collected entries.</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>
/// Gets the target URL with respect to the <see cref="PathSpecification"/>.


+ 13
- 13
src/DocNet/NavigationLevel.cs View File

@@ -106,13 +106,13 @@ namespace Docnet
/// </summary>
/// <param name="collectedEntries">The collected entries.</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);
foreach (var element in this.Value)
{
element.CollectSearchIndexEntries(collectedEntries, activePath, pathSpecification);
element.CollectSearchIndexEntries(collectedEntries, activePath, navigationContext);
}
activePath.Pop();
}
@@ -123,15 +123,15 @@ namespace Docnet
/// </summary>
/// <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="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);
int i = 0;
while (i < this.Value.Count)
{
var element = this.Value[i];
element.GenerateOutput(activeConfig, activePath, pathSpecification);
element.GenerateOutput(activeConfig, activePath, navigationContext);
i++;
}
activePath.Pop();
@@ -143,9 +143,9 @@ namespace Docnet
/// </summary>
/// <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="pathSpecification">The path specification.</param>
/// <param name="navigationContext">The navigation context.</param>
/// <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>();
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.
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)
{
fragments.Add(string.Format("{0}{1}</span></li>", elementStartTag, this.Name));
@@ -176,18 +176,18 @@ namespace Docnet
{
if (this.IsRoot)
{
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification));
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, navigationContext));
}
else
{
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.
foreach (var element in this.Value)
{
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification));
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, navigationContext));
}
fragments.Add("</ul>");
}
@@ -195,7 +195,7 @@ namespace Docnet
{
// just a link
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)
{


+ 86
- 36
src/DocNet/SimpleNavigationElement.cs View File

@@ -27,6 +27,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using MarkdownDeep;

namespace Docnet
{
@@ -34,13 +35,13 @@ namespace Docnet
{
#region Members
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


public SimpleNavigationElement()
{
_relativeH2LinksOnPage = new List<Tuple<string, string>>();
_relativeLinksOnPage = new List<Heading>();
}


@@ -49,56 +50,57 @@ namespace Docnet
/// </summary>
/// <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="pathSpecification">The path specification.</param>
/// <param name="navigationContext">The navigation context.</param>
/// <exception cref="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(!this.IsIndexElement)
if (!this.IsIndexElement)
{
activePath.Push(this);
}
_relativeH2LinksOnPage.Clear();
_relativeLinksOnPage.Clear();
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 content = string.Empty;
this.MarkdownFromFile = string.Empty;
var relativePathToRoot = Utils.MakeRelativePathForUri(Path.GetDirectoryName(destinationFile), activeConfig.Destination);
if(File.Exists(sourceFile))
if (File.Exists(sourceFile))
{
this.MarkdownFromFile = File.ReadAllText(sourceFile, Encoding.UTF8);
// Check if the content contains @@include tag
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
{
// 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.
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
// specified / existend.
var defaultMarkdown = new StringBuilder();
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);
foreach(var sibling in this.ParentContainer.Value)
foreach (var sibling in this.ParentContainer.Value)
{
if(sibling == this)
if (sibling == this)
{
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);
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
{
// 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));
}
@@ -110,17 +112,17 @@ namespace Docnet
sb.Replace("{{Footer}}", activeConfig.Footer);
sb.Replace("{{TopicTitle}}", this.Name);
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));

// 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);
Utils.CreateFoldersIfRequired(destinationFile);
File.WriteAllText(destinationFile, sb.ToString());
if(!this.IsIndexElement)
if (!this.IsIndexElement)
{
activePath.Pop();
}
@@ -132,15 +134,15 @@ namespace Docnet
/// </summary>
/// <param name="collectedEntries">The collected entries.</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);
// simply convert ourselves into an entry if we're not an index
if(!this.IsIndexElement)
if (!this.IsIndexElement)
{
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);
}
activePath.Pop();
@@ -152,17 +154,17 @@ namespace Docnet
/// </summary>
/// <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="pathSpecification">The path specification.</param>
/// <param name="navigationContext">The navigation context.</param>
/// <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.
if(this.IsIndexElement)
if (this.IsIndexElement)
{
return string.Empty;
}

return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification);
return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, navigationContext);
}


@@ -172,16 +174,16 @@ namespace Docnet
/// </summary>
/// <param name="navigatedPath">The navigated path.</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>
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.
bool isCurrent = navigatedPath.Contains(this);
var fragments = new List<string>();
var liClass = "tocentry";
var aClass = string.Empty;
if(isCurrent)
if (isCurrent)
{
liClass = "tocentry current";
aClass = "current";
@@ -190,16 +192,22 @@ namespace Docnet
string.IsNullOrWhiteSpace(liClass) ? string.Empty : string.Format(" class=\"{0}\"", liClass),
string.IsNullOrWhiteSpace(aClass) ? string.Empty : string.Format(" class=\"{0}\"", aClass),
relativePathToRoot,
this.GetFinalTargetUrl(pathSpecification),
this.GetFinalTargetUrl(navigationContext.PathSpecification),
this.Name));
if(isCurrent && _relativeH2LinksOnPage.Any())
if (isCurrent && _relativeLinksOnPage.SelectMany(x => x.Children).Any(x => x.Level > 1))
{
// generate relative links
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>");
}
else
@@ -243,6 +251,48 @@ namespace Docnet
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
/// <summary>
/// Gets / sets a value indicating whether this element is the __index element


+ 5
- 2
src/DocNet/Utils.cs View File

@@ -27,6 +27,7 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using MarkdownDeep;

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>
/// <returns></returns>
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
{
@@ -67,7 +68,9 @@ namespace Docnet
};

var toReturn = parser.Transform(toConvert);
createdAnchorCollector.AddRange(parser.CreatedH2IdCollector);

createdAnchorCollector.AddRange(parser.Headings.ConvertToHierarchy());

return toReturn;
}



+ 12
- 7
src/MarkdownDeep/Block.cs View File

@@ -193,14 +193,19 @@ namespace MarkdownDeep
{
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
{


+ 59
- 0
src/MarkdownDeep/Extensions.cs View File

@@ -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);
}
}
}
}
}

+ 43
- 0
src/MarkdownDeep/Heading.cs View File

@@ -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;
}
}
}

+ 5
- 5
src/MarkdownDeep/MardownDeep.cs View File

@@ -58,7 +58,9 @@ namespace MarkdownDeep
m_Footnotes = new Dictionary<string, Block>();
m_UsedFootnotes = new List<Block>();
m_UsedHeaderIDs = new Dictionary<string, bool>();
this.CreatedH2IdCollector = new List<Tuple<string, string>>();

this.Headings = new List<Heading>();

_tabIdCounter = 0;
}

@@ -977,13 +979,11 @@ namespace MarkdownDeep
set;
}


/// <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
/// </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
// (defaults to "footnotes")


+ 2
- 0
src/MarkdownDeep/MarkdownDeep.csproj View File

@@ -77,7 +77,9 @@
<ItemGroup>
<Compile Include="Abbreviation.cs" />
<Compile Include="BlockProcessor.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="FootnoteReference.cs" />
<Compile Include="Heading.cs" />
<Compile Include="HtmlTag.cs" />
<Compile Include="LinkDefinition.cs" />
<Compile Include="LinkInfo.cs" />


+ 65
- 0
src/MarkdownDeepTests/ExtensionsTests.cs View File

@@ -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);
}
}
}

+ 1
- 0
src/MarkdownDeepTests/MarkdownDeepTests.csproj View File

@@ -75,6 +75,7 @@
<Compile Include="AutoLinkTests.cs" />
<Compile Include="AutoHeaderIDTests.cs" />
<Compile Include="DocNetMode.cs" />
<Compile Include="ExtensionsTests.cs" />
<Compile Include="GithubMode.cs" />
<Compile Include="LocalLinkTests.cs" />
<Compile Include="TableSpecTests.cs" />


Loading…
Cancel
Save