# Conflicts: # src/DocNet/INavigationElement.cs # src/DocNet/NavigatedPath.cs # src/DocNet/NavigationElement.cs # src/DocNet/NavigationLevel.cs # src/DocNet/SimpleNavigationElement.cstags/v0.16
@@ -131,7 +131,7 @@ namespace Docnet | |||||
private void GenerateSearchDataIndex() | private void GenerateSearchDataIndex() | ||||
{ | { | ||||
var collectedSearchEntries = new List<SearchIndexEntry>(); | var collectedSearchEntries = new List<SearchIndexEntry>(); | ||||
this.Pages.CollectSearchIndexEntries(collectedSearchEntries, new NavigatedPath()); | |||||
this.Pages.CollectSearchIndexEntries(collectedSearchEntries, new NavigatedPath(), this.PathSpecification); | |||||
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( | ||||
@@ -164,7 +164,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); | |||||
searchSimpleElement.GenerateOutput(this, activePath, this.PathSpecification); | |||||
activePath.Pop(); | activePath.Pop(); | ||||
} | } | ||||
@@ -264,6 +264,25 @@ namespace Docnet | |||||
get { return _templateContents ?? string.Empty; } | get { return _templateContents ?? string.Empty; } | ||||
} | } | ||||
public PathSpecification PathSpecification | |||||
{ | |||||
get | |||||
{ | |||||
var pathSpecification = PathSpecification.Full; | |||||
var pathSpecificationAsString = (string)_configData.PathSpecification; | |||||
if (!string.IsNullOrWhiteSpace(pathSpecificationAsString)) | |||||
{ | |||||
if (!Enum.TryParse(pathSpecificationAsString, true, out pathSpecification)) | |||||
{ | |||||
pathSpecification = PathSpecification.Full; | |||||
} | |||||
} | |||||
return pathSpecification; | |||||
} | |||||
} | |||||
public NavigationLevel Pages | public NavigationLevel Pages | ||||
{ | { | ||||
get | get | ||||
@@ -271,7 +290,7 @@ namespace Docnet | |||||
if(_pages == null) | if(_pages == null) | ||||
{ | { | ||||
JObject rawPages = _configData.Pages; | JObject rawPages = _configData.Pages; | ||||
_pages = new NavigationLevel() {Name = "Home", IsRoot = true}; | |||||
_pages = new NavigationLevel(Source) {Name = "Home", IsRoot = true}; | |||||
_pages.Load(rawPages); | _pages.Load(rawPages); | ||||
} | } | ||||
return _pages; | return _pages; | ||||
@@ -60,10 +60,12 @@ | |||||
<Compile Include="NavigatedPath.cs" /> | <Compile Include="NavigatedPath.cs" /> | ||||
<Compile Include="NavigationElement.cs" /> | <Compile Include="NavigationElement.cs" /> | ||||
<Compile Include="NavigationLevel.cs" /> | <Compile Include="NavigationLevel.cs" /> | ||||
<Compile Include="PathSpecification.cs" /> | |||||
<Compile Include="Program.cs" /> | <Compile Include="Program.cs" /> | ||||
<Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
<Compile Include="SearchIndexEntry.cs" /> | <Compile Include="SearchIndexEntry.cs" /> | ||||
<Compile Include="SimpleNavigationElement.cs" /> | <Compile Include="SimpleNavigationElement.cs" /> | ||||
<Compile Include="INavigationElementExtensions.cs" /> | |||||
<Compile Include="Utils.cs" /> | <Compile Include="Utils.cs" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -70,11 +70,14 @@ namespace Docnet | |||||
Console.WriteLine("Errors occurred, can't continue!"); | Console.WriteLine("Errors occurred, can't continue!"); | ||||
return null; | return null; | ||||
} | } | ||||
if(config.Pages.IndexElement == null) | |||||
var indexElement = config.Pages.GetIndexElement(config.PathSpecification); | |||||
if(indexElement == null) | |||||
{ | { | ||||
Console.WriteLine("[ERROR] Root __index not found. The root navigationlevel is required to have an __index element"); | Console.WriteLine("[ERROR] Root __index not found. The root navigationlevel is required to have an __index element"); | ||||
return null; | return null; | ||||
} | } | ||||
return config; | return config; | ||||
} | } | ||||
@@ -95,7 +98,7 @@ 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.Pages.GenerateOutput(_loadedConfig, new NavigatedPath(), _loadedConfig.PathSpecification); | |||||
Console.WriteLine("Generating search index"); | Console.WriteLine("Generating search index"); | ||||
_loadedConfig.GenerateSearchData(); | _loadedConfig.GenerateSearchData(); | ||||
Console.WriteLine("Done!"); | Console.WriteLine("Done!"); | ||||
@@ -35,32 +35,39 @@ 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> | ||||
void GenerateOutput(Config activeConfig, NavigatedPath activePath); | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification); | |||||
/// <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="maxLevel">The maximum level.</param> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel); | |||||
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification); | |||||
/// <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> | ||||
void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath); | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification); | |||||
/// <summary> | |||||
/// Gets the target URL with respect to the <see cref="PathSpecification"/>. | |||||
/// </summary> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | |||||
string GetTargetURL(PathSpecification pathSpecification); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a value indicating whether this element is the __index element | /// Gets a value indicating whether this element is the __index element | ||||
/// </summary> | /// </summary> | ||||
bool IsIndexElement { get; set; } | bool IsIndexElement { get; set; } | ||||
bool IsAutoGenerated { get; set; } | |||||
string Name { get; set; } | string Name { get; set; } | ||||
object Value { get; set; } | object Value { get; set; } | ||||
string TargetURL { get; } | |||||
NavigationLevel ParentContainer { get; set; } | NavigationLevel ParentContainer { get; set; } | ||||
} | } | ||||
} | } |
@@ -0,0 +1,35 @@ | |||||
using System; | |||||
using System.Web; | |||||
namespace Docnet | |||||
{ | |||||
public static class INavigationElementExtensions | |||||
{ | |||||
private const string IndexHtmFileName = "index.htm"; | |||||
/// <summary> | |||||
/// Gets the final URL by encoding the path and by removing the filename if it equals <c>index.htm</c>. | |||||
/// </summary> | |||||
/// <param name="navigationElement">The navigation element.</param> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | |||||
public static string GetFinalTargetUrl(this INavigationElement navigationElement, PathSpecification pathSpecification) | |||||
{ | |||||
var targetUrl = navigationElement.GetTargetURL(pathSpecification); | |||||
var link = HttpUtility.UrlPathEncode(targetUrl); | |||||
// Disabled for now as discussed in #65 (https://github.com/FransBouma/DocNet/pull/65), but | |||||
// is required for #44 | |||||
//if (pathSpecification == PathSpecification.RelativeAsFolder) | |||||
//{ | |||||
// if (link.Length > IndexHtmFileName.Length && | |||||
// link.EndsWith(IndexHtmFileName, StringComparison.InvariantCultureIgnoreCase)) | |||||
// { | |||||
// link = link.Substring(0, link.Length - IndexHtmFileName.Length); | |||||
// } | |||||
//} | |||||
return link; | |||||
} | |||||
} | |||||
} |
@@ -35,17 +35,18 @@ namespace Docnet | |||||
public class NavigatedPath : Stack<INavigationElement> | public class NavigatedPath : Stack<INavigationElement> | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// Creates the bread crumbs HTML of the elements in this path, delimited by '/' characters. | |||||
/// Creates the bread crumbs HTML of the elements in this path, delimited by '/' characters. | |||||
/// </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> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public string CreateBreadCrumbsHTML(string relativePathToRoot) | |||||
public string CreateBreadCrumbsHTML(string relativePathToRoot, PathSpecification pathSpecification) | |||||
{ | { | ||||
var fragments = new List<string>(); | var fragments = new List<string>(); | ||||
// we enumerate a stack, which enumerates from top to bottom, so we have to reverse things first. | // we enumerate a stack, which enumerates from top to bottom, so we have to reverse things first. | ||||
foreach(var element in this.Reverse()) | foreach(var element in this.Reverse()) | ||||
{ | { | ||||
var targetURL = element.TargetURL; | |||||
var targetURL = element.GetTargetURL(pathSpecification); | |||||
if(string.IsNullOrWhiteSpace(targetURL)) | if(string.IsNullOrWhiteSpace(targetURL)) | ||||
{ | { | ||||
fragments.Add(string.Format("<li>{0}</li>", element.Name)); | fragments.Add(string.Format("<li>{0}</li>", element.Name)); | ||||
@@ -76,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="maxLevel">The maximum level.</param> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public string CreateToCHTML(string relativePathToRoot, int maxLevel) | |||||
public string CreateToCHTML(string relativePathToRoot, PathSpecification pathSpecification) | |||||
{ | { | ||||
// 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; | ||||
@@ -87,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, maxLevel); | |||||
return rootContainer.GenerateToCFragment(this, relativePathToRoot, pathSpecification); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -36,31 +36,39 @@ 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> | ||||
/// <returns>true if everything went well, false otherwise</returns> | |||||
public abstract void GenerateOutput(Config activeConfig, NavigatedPath activePath); | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public abstract void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification); | |||||
/// <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="maxLevel">The maximum level.</param> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel); | |||||
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification); | |||||
/// <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> | ||||
public abstract void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath); | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public abstract void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification); | |||||
/// <summary> | |||||
/// Gets the target URL with respect to the <see cref="PathSpecification"/>. | |||||
/// </summary> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | |||||
public abstract string GetTargetURL(PathSpecification pathSpecification); | |||||
#region Properties | #region Properties | ||||
public abstract string TargetURL { get; } | |||||
/// <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 | ||||
/// </summary> | /// </summary> | ||||
public abstract bool IsIndexElement { get; set; } | public abstract bool IsIndexElement { get; set; } | ||||
public bool IsAutoGenerated { get; set; } | |||||
public string Name { get; set; } | public string Name { get; set; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets or sets the value of this element, which can either be a string or a NavigationLevel | /// Gets or sets the value of this element, which can either be a string or a NavigationLevel | ||||
@@ -33,30 +33,65 @@ namespace Docnet | |||||
{ | { | ||||
public class NavigationLevel : NavigationElement<List<INavigationElement>> | public class NavigationLevel : NavigationElement<List<INavigationElement>> | ||||
{ | { | ||||
public NavigationLevel() : base() | |||||
#region Members | |||||
private readonly string _rootDirectory; | |||||
#endregion | |||||
public NavigationLevel(string rootDirectory) | |||||
: base() | |||||
{ | { | ||||
this._rootDirectory = rootDirectory; | |||||
this.Value = new List<INavigationElement>(); | this.Value = new List<INavigationElement>(); | ||||
} | } | ||||
public void Load(JObject dataFromFile) | public void Load(JObject dataFromFile) | ||||
{ | { | ||||
foreach(KeyValuePair<string, JToken> child in dataFromFile) | |||||
foreach (KeyValuePair<string, JToken> child in dataFromFile) | |||||
{ | { | ||||
INavigationElement toAdd; | INavigationElement toAdd; | ||||
if(child.Value.Type == JTokenType.String) | |||||
if (child.Value.Type == JTokenType.String) | |||||
{ | { | ||||
var nameToUse = child.Key; | var nameToUse = child.Key; | ||||
var isIndexElement = child.Key == "__index"; | var isIndexElement = child.Key == "__index"; | ||||
if(isIndexElement) | |||||
if (isIndexElement) | |||||
{ | { | ||||
nameToUse = this.Name; | nameToUse = this.Name; | ||||
} | } | ||||
toAdd = new SimpleNavigationElement() { Name = nameToUse, Value = child.Value.ToObject<string>(), IsIndexElement = isIndexElement}; | |||||
var childValue = child.Value.ToObject<string>(); | |||||
if (childValue.EndsWith("**")) | |||||
{ | |||||
var path = childValue.Replace("**", string.Empty) | |||||
.Replace('\\', Path.DirectorySeparatorChar) | |||||
.Replace('/', Path.DirectorySeparatorChar); | |||||
if (!Path.IsPathRooted(path)) | |||||
{ | |||||
path = Path.Combine(_rootDirectory, path); | |||||
} | |||||
toAdd = CreateGeneratedLevel(path); | |||||
toAdd.Name = nameToUse; | |||||
} | |||||
else | |||||
{ | |||||
toAdd = new SimpleNavigationElement | |||||
{ | |||||
Name = nameToUse, | |||||
Value = childValue, | |||||
IsIndexElement = isIndexElement | |||||
}; | |||||
} | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
var subLevel = new NavigationLevel() { Name = child.Key, IsRoot = false}; | |||||
var subLevel = new NavigationLevel(_rootDirectory) | |||||
{ | |||||
Name = child.Key, | |||||
IsRoot = false | |||||
}; | |||||
subLevel.Load((JObject)child.Value); | subLevel.Load((JObject)child.Value); | ||||
toAdd = subLevel; | toAdd = subLevel; | ||||
} | } | ||||
@@ -71,12 +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> | ||||
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath) | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification) | |||||
{ | { | ||||
activePath.Push(this); | activePath.Push(this); | ||||
foreach(var element in this.Value) | |||||
foreach (var element in this.Value) | |||||
{ | { | ||||
element.CollectSearchIndexEntries(collectedEntries, activePath); | |||||
element.CollectSearchIndexEntries(collectedEntries, activePath, pathSpecification); | |||||
} | } | ||||
activePath.Pop(); | activePath.Pop(); | ||||
} | } | ||||
@@ -87,14 +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> | ||||
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath) | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification) | |||||
{ | { | ||||
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); | |||||
element.GenerateOutput(activeConfig, activePath, pathSpecification); | |||||
i++; | i++; | ||||
} | } | ||||
activePath.Pop(); | activePath.Pop(); | ||||
@@ -106,20 +143,20 @@ 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="maxLevel">The maximum level.</param> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel) | |||||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) | |||||
{ | { | ||||
var fragments = new List<string>(); | var fragments = new List<string>(); | ||||
if(!this.IsRoot) | |||||
if (!this.IsRoot) | |||||
{ | { | ||||
fragments.Add("<li class=\"tocentry\">"); | fragments.Add("<li class=\"tocentry\">"); | ||||
} | } | ||||
if(navigatedPath.Contains(this)) | |||||
if (navigatedPath.Contains(this)) | |||||
{ | { | ||||
// we're expanded. If we're not root and on the top of the navigated path stack, our index page is the page we're currently generating the ToC for, so | // we're expanded. If we're not root and on the top of the navigated path stack, our index page is the page we're currently generating the ToC for, so | ||||
// we have to mark the entry as 'current' | // we have to mark the entry as 'current' | ||||
if(navigatedPath.Peek() == this && !this.IsRoot) | |||||
if (navigatedPath.Peek() == this && !this.IsRoot) | |||||
{ | { | ||||
fragments.Add("<ul class=\"current\">"); | fragments.Add("<ul class=\"current\">"); | ||||
} | } | ||||
@@ -130,37 +167,37 @@ 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.IndexElement; | |||||
if(indexElement == null) | |||||
var indexElement = this.GetIndexElement(pathSpecification); | |||||
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)); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if(this.IsRoot) | |||||
if (this.IsRoot) | |||||
{ | { | ||||
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel)); | |||||
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification)); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
fragments.Add(string.Format("{0}<a href=\"{1}{2}\">{3}</a></span></li>", elementStartTag, relativePathToRoot, HttpUtility.UrlPathEncode(indexElement.TargetURL), | |||||
this.Name)); | |||||
fragments.Add(string.Format("{0}<a href=\"{1}{2}\">{3}</a></span></li>", | |||||
elementStartTag, relativePathToRoot, indexElement.GetFinalTargetUrl(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, maxLevel)); | |||||
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification)); | |||||
} | } | ||||
fragments.Add("</ul>"); | fragments.Add("</ul>"); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// 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>", | |||||
relativePathToRoot, HttpUtility.UrlPathEncode(this.TargetURL), this.Name)); | |||||
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)); | |||||
} | } | ||||
if(!this.IsRoot) | |||||
if (!this.IsRoot) | |||||
{ | { | ||||
fragments.Add("</li>"); | fragments.Add("</li>"); | ||||
} | } | ||||
@@ -168,48 +205,178 @@ namespace Docnet | |||||
} | } | ||||
#region Properties | |||||
public override string TargetURL | |||||
private NavigationLevel CreateGeneratedLevel(string path) | |||||
{ | { | ||||
get | |||||
var root = new NavigationLevel(_rootDirectory) | |||||
{ | |||||
ParentContainer = this, | |||||
IsAutoGenerated = true | |||||
}; | |||||
foreach (var mdFile in Directory.GetFiles(path, "*.md", SearchOption.TopDirectoryOnly)) | |||||
{ | { | ||||
var defaultElement = this.IndexElement; | |||||
if(defaultElement == null) | |||||
var name = FindTitleInMdFile(mdFile); | |||||
if (string.IsNullOrWhiteSpace(name)) | |||||
{ | { | ||||
return string.Empty; | |||||
continue; | |||||
} | } | ||||
return defaultElement.TargetURL ?? string.Empty; | |||||
var item = new SimpleNavigationElement | |||||
{ | |||||
Name = name, | |||||
Value = Path.Combine(Utils.MakeRelativePath(_rootDirectory, path), Path.GetFileName(mdFile)), | |||||
ParentContainer = root, | |||||
IsAutoGenerated = true | |||||
}; | |||||
root.Value.Add(item); | |||||
} | |||||
foreach (var directory in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly)) | |||||
{ | |||||
var subDirectoryNavigationElement = CreateGeneratedLevel(directory); | |||||
subDirectoryNavigationElement.Name = new DirectoryInfo(directory).Name; | |||||
subDirectoryNavigationElement.ParentContainer = root; | |||||
root.Value.Add(subDirectoryNavigationElement); | |||||
} | } | ||||
return root; | |||||
} | } | ||||
public SimpleNavigationElement IndexElement | |||||
private string FindTitleInMdFile(string path) | |||||
{ | { | ||||
get | |||||
var title = string.Empty; | |||||
using (var fileStream = File.OpenRead(path)) | |||||
{ | { | ||||
var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement; | |||||
if(toReturn == null) | |||||
using (var streamReader = new StreamReader(fileStream)) | |||||
{ | { | ||||
// no index element, add an artificial one. | |||||
var path = string.Empty; | |||||
if(this.ParentContainer != null) | |||||
var line = string.Empty; | |||||
while (string.IsNullOrWhiteSpace(line)) | |||||
{ | { | ||||
path = Path.GetDirectoryName(this.ParentContainer.TargetURL); | |||||
line = streamReader.ReadLine(); | |||||
if (!string.IsNullOrWhiteSpace(line)) | |||||
{ | |||||
line = line.Trim(); | |||||
while (line.StartsWith("#")) | |||||
{ | |||||
line = line.Substring(1).Trim(); | |||||
} | |||||
if (!string.IsNullOrWhiteSpace(line)) | |||||
{ | |||||
title = line; | |||||
break; | |||||
} | |||||
} | |||||
} | } | ||||
var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", ""); | |||||
if(string.IsNullOrWhiteSpace(nameToUse)) | |||||
} | |||||
} | |||||
return title; | |||||
} | |||||
public override string GetTargetURL(PathSpecification pathSpecification) | |||||
{ | |||||
var defaultElement = this.GetIndexElement(pathSpecification); | |||||
if (defaultElement == null) | |||||
{ | |||||
return string.Empty; | |||||
} | |||||
return defaultElement.GetTargetURL(pathSpecification) ?? string.Empty; | |||||
} | |||||
public SimpleNavigationElement GetIndexElement(PathSpecification pathSpecification) | |||||
{ | |||||
var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement; | |||||
if (toReturn == null) | |||||
{ | |||||
// no index element, add an artificial one. | |||||
var path = string.Empty; | |||||
// Don't check parents when using relative paths since we need to walk the tree manually | |||||
if (pathSpecification == PathSpecification.Full) | |||||
{ | |||||
if (this.ParentContainer != null) | |||||
{ | { | ||||
return null; | |||||
path = Path.GetDirectoryName(this.ParentContainer.GetTargetURL(pathSpecification)); | |||||
} | } | ||||
toReturn = new SimpleNavigationElement() {ParentContainer = this, Value = string.Format("{0}{1}.md", path, nameToUse), Name = this.Name, IsIndexElement = true}; | |||||
this.Value.Add(toReturn); | |||||
} | } | ||||
return toReturn; | |||||
var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", ""); | |||||
if (string.IsNullOrWhiteSpace(nameToUse)) | |||||
{ | |||||
return null; | |||||
} | |||||
var value = string.Format("{0}{1}.md", path, nameToUse); | |||||
switch (pathSpecification) | |||||
{ | |||||
case PathSpecification.Full: | |||||
// Default is correct | |||||
break; | |||||
case PathSpecification.Relative: | |||||
case PathSpecification.RelativeAsFolder: | |||||
if (!IsRoot) | |||||
{ | |||||
string preferredPath = null; | |||||
// We're making a big assumption here, but we can get the first page and assume it's | |||||
// in the right folder. | |||||
// Find first (simple) child and use 1 level up. A SimpleNavigationElement mostly represents a folder | |||||
// thus is excellent to be used as a folder name | |||||
var firstSimpleChildPage = (SimpleNavigationElement) this.Value.FirstOrDefault(x => x is SimpleNavigationElement && !ReferenceEquals(this, x)); | |||||
if (firstSimpleChildPage != null) | |||||
{ | |||||
preferredPath = Path.GetDirectoryName(firstSimpleChildPage.Value); | |||||
} | |||||
else | |||||
{ | |||||
// This is representing an empty folder. Search for first child navigation that has real childs, | |||||
// then retrieve the path by going levels up. | |||||
var firstChildNavigationLevel = (NavigationLevel)this.Value.FirstOrDefault(x => x is NavigationLevel && ((NavigationLevel)x).Value.Any() && !ReferenceEquals(this, x)); | |||||
if (firstChildNavigationLevel != null) | |||||
{ | |||||
var targetUrl = firstChildNavigationLevel.Value.First().GetTargetURL(pathSpecification); | |||||
// 3 times since we need 2 parents up | |||||
preferredPath = Path.GetDirectoryName(targetUrl); | |||||
preferredPath = Path.GetDirectoryName(preferredPath); | |||||
preferredPath = Path.GetDirectoryName(preferredPath); | |||||
} | |||||
} | |||||
if (!string.IsNullOrWhiteSpace(preferredPath)) | |||||
{ | |||||
value = Path.Combine(preferredPath, "index.md"); | |||||
} | |||||
} | |||||
break; | |||||
default: | |||||
throw new ArgumentOutOfRangeException(nameof(pathSpecification), pathSpecification, null); | |||||
} | |||||
toReturn = new SimpleNavigationElement | |||||
{ | |||||
ParentContainer = this, | |||||
Value = value, | |||||
Name = this.Name, | |||||
IsIndexElement = true | |||||
}; | |||||
this.Value.Add(toReturn); | |||||
} | } | ||||
} | |||||
return toReturn; | |||||
} | |||||
#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 | ||||
/// </summary> | /// </summary> | ||||
@@ -217,7 +384,8 @@ namespace Docnet | |||||
{ | { | ||||
// never an index | // never an index | ||||
get { return false; } | get { return false; } | ||||
set { | |||||
set | |||||
{ | |||||
// nop; | // nop; | ||||
} | } | ||||
} | } | ||||
@@ -0,0 +1,11 @@ | |||||
namespace Docnet | |||||
{ | |||||
public enum PathSpecification | |||||
{ | |||||
Full, | |||||
Relative, | |||||
RelativeAsFolder | |||||
} | |||||
} |
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices; | |||||
// You can specify all the values or you can default the Build and Revision Numbers | // You can specify all the values or you can default the Build and Revision Numbers | ||||
// by using the '*' as shown below: | // by using the '*' as shown below: | ||||
// [assembly: AssemblyVersion("1.0.*")] | // [assembly: AssemblyVersion("1.0.*")] | ||||
[assembly: AssemblyVersion("0.15.0.0")] | |||||
[assembly: AssemblyFileVersion("0.15.0")] | |||||
[assembly: AssemblyVersion("0.16.0.0")] | |||||
[assembly: AssemblyFileVersion("0.16.0")] |
@@ -27,7 +27,6 @@ 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 | ||||
{ | { | ||||
@@ -35,14 +34,13 @@ namespace Docnet | |||||
{ | { | ||||
#region Members | #region Members | ||||
private string _targetURLForHTML; | private string _targetURLForHTML; | ||||
private readonly List<Heading> _relativeLinksOnPage; // first element in Tuple is anchor name, second is name for ToC. | |||||
private List<Tuple<string, string>> _relativeH2LinksOnPage; // first element in Tuple is anchor name, second is name for ToC. | |||||
#endregion | #endregion | ||||
public SimpleNavigationElement() | public SimpleNavigationElement() | ||||
{ | { | ||||
_relativeLinksOnPage = new List<Heading>(); | |||||
_relativeH2LinksOnPage = new List<Tuple<string, string>>(); | |||||
} | } | ||||
@@ -51,53 +49,56 @@ 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> | ||||
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath) | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <exception cref="System.IO.FileNotFoundException"></exception> | |||||
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification) | |||||
{ | { | ||||
// 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); | ||||
} | } | ||||
_relativeLinksOnPage.Clear(); | |||||
_relativeH2LinksOnPage.Clear(); | |||||
var sourceFile = Utils.MakeAbsolutePath(activeConfig.Source, this.Value); | var sourceFile = Utils.MakeAbsolutePath(activeConfig.Source, this.Value); | ||||
var destinationFile = Utils.MakeAbsolutePath(activeConfig.Destination, this.TargetURL); | |||||
var destinationFile = Utils.MakeAbsolutePath(activeConfig.Destination, this.GetTargetURL(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, _relativeLinksOnPage, activeConfig.ConvertLocalLinks); | |||||
content = Utils.ConvertMarkdownToHtml(content, Path.GetDirectoryName(destinationFile), activeConfig.Destination, sourceFile, _relativeH2LinksOnPage, 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, HttpUtility.UrlPathEncode(sibling.TargetURL), Environment.NewLine); | |||||
defaultMarkdown.AppendFormat("* [{0}]({1}{2}){3}", sibling.Name, relativePathToRoot, | |||||
sibling.GetFinalTargetUrl(pathSpecification), Environment.NewLine); | |||||
} | } | ||||
defaultMarkdown.Append(Environment.NewLine); | defaultMarkdown.Append(Environment.NewLine); | ||||
content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeLinksOnPage, activeConfig.ConvertLocalLinks); | |||||
content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeH2LinksOnPage, 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)); | ||||
} | } | ||||
@@ -109,17 +110,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)); | |||||
sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, activeConfig.MaxLevelInToC)); | |||||
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("{{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(); | ||||
} | } | ||||
@@ -131,14 +132,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> | ||||
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath) | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification) | |||||
{ | { | ||||
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.TargetURL, this.Name, activePath); | |||||
toAdd.Fill(this.MarkdownFromFile, this.GetTargetURL(pathSpecification), this.Name, activePath); | |||||
collectedEntries.Add(toAdd); | collectedEntries.Add(toAdd); | ||||
} | } | ||||
activePath.Pop(); | activePath.Pop(); | ||||
@@ -150,17 +152,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="maxLevel">The maximum level.</param> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel) | |||||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) | |||||
{ | { | ||||
// 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, maxLevel, null); | |||||
return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification); | |||||
} | } | ||||
@@ -170,17 +172,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="maxLevel">The maximum level.</param> | |||||
/// <param name="parentHeading">The parent heading.</param> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel) | |||||
public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) | |||||
{ | { | ||||
// 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"; | ||||
@@ -189,71 +190,60 @@ 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, | ||||
HttpUtility.UrlPathEncode(this.TargetURL), | |||||
this.GetFinalTargetUrl(pathSpecification), | |||||
this.Name)); | this.Name)); | ||||
if (isCurrent) | |||||
if(isCurrent && _relativeH2LinksOnPage.Any()) | |||||
{ | { | ||||
var content = PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel, null); | |||||
if (!string.IsNullOrWhiteSpace(content)) | |||||
// generate relative links | |||||
fragments.Add(string.Format("<ul class=\"{0}\">", this.ParentContainer.IsRoot ? "currentrelativeroot" : "currentrelative")); | |||||
foreach(var p in _relativeH2LinksOnPage) | |||||
{ | { | ||||
fragments.Add(content); | |||||
fragments.Add(string.Format("<li class=\"tocentry\"><a href=\"#{0}\">{1}</a></li>", p.Item1, p.Item2)); | |||||
} | } | ||||
fragments.Add("</ul>"); | |||||
} | |||||
else | |||||
{ | |||||
fragments.Add("</li>"); | |||||
} | } | ||||
fragments.Add("</li>"); | |||||
return string.Join(Environment.NewLine, fragments.ToArray()); | return string.Join(Environment.NewLine, fragments.ToArray()); | ||||
} | } | ||||
private string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel, Heading parentHeading) | |||||
/// <summary> | |||||
/// Gets the target URL with respect to the <see cref="T:Docnet.PathSpecification" />. | |||||
/// </summary> | |||||
/// <param name="pathSpecification">The path specification.</param> | |||||
/// <returns></returns> | |||||
/// <exception cref="System.NotImplementedException"></exception> | |||||
public override string GetTargetURL(PathSpecification pathSpecification) | |||||
{ | { | ||||
var fragments = new List<string>(); | |||||
var headings = (parentHeading != null) ? parentHeading.Children : _relativeLinksOnPage; | |||||
var includedHeadings = headings.Where(x => x.Level > 1 && x.Level <= maxLevel).ToList(); | |||||
if (includedHeadings.Count > 0) | |||||
if (_targetURLForHTML == null) | |||||
{ | { | ||||
fragments.Add(string.Format("<ul class=\"{0}\">", this.ParentContainer.IsRoot ? "currentrelativeroot" : "currentrelative")); | |||||
_targetURLForHTML = (this.Value ?? string.Empty); | |||||
// generate relative links | |||||
foreach (var heading in includedHeadings) | |||||
{ | |||||
fragments.Add(string.Format("<li class=\"tocentry\"><a href=\"#{0}\">{1}</a></li>", heading.Id, heading.Name)); | |||||
var toReplace = ".md"; | |||||
var replacement = ".htm"; | |||||
var headingContent = PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel, heading); | |||||
if (!string.IsNullOrWhiteSpace(headingContent)) | |||||
if (pathSpecification == PathSpecification.RelativeAsFolder) | |||||
{ | |||||
if (!IsIndexElement && !_targetURLForHTML.EndsWith("index.md", StringComparison.InvariantCultureIgnoreCase)) | |||||
{ | { | ||||
fragments.Add(headingContent); | |||||
replacement = "/index.htm"; | |||||
} | } | ||||
} | } | ||||
fragments.Add("</ul>"); | |||||
} | |||||
return string.Join(Environment.NewLine, fragments.ToArray()); | |||||
} | |||||
#region Properties | |||||
public override string TargetURL | |||||
{ | |||||
get | |||||
{ | |||||
if (_targetURLForHTML == null) | |||||
if (_targetURLForHTML.EndsWith(toReplace, StringComparison.InvariantCultureIgnoreCase)) | |||||
{ | { | ||||
_targetURLForHTML = (this.Value ?? string.Empty); | |||||
if (_targetURLForHTML.ToLowerInvariant().EndsWith(".md")) | |||||
{ | |||||
_targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length - 3) + ".htm"; | |||||
} | |||||
_targetURLForHTML = _targetURLForHTML.Replace("\\", "/"); | |||||
_targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length - toReplace.Length) + replacement; | |||||
} | } | ||||
return _targetURLForHTML; | |||||
_targetURLForHTML = _targetURLForHTML.Replace("\\", "/"); | |||||
} | } | ||||
} | |||||
return _targetURLForHTML; | |||||
} | |||||
#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 | ||||
/// </summary> | /// </summary> | ||||
@@ -14,6 +14,7 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.IO; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Text; | using System.Text; | ||||
@@ -281,8 +282,10 @@ namespace MarkdownDeep | |||||
} | } | ||||
// Override to supply the size of an image | // Override to supply the size of an image | ||||
public virtual bool OnGetImageSize(string url, bool TitledImage, out int width, out int height) | |||||
public virtual bool OnGetImageSize(string url, bool TitledImage, out int width, out int height, out string finalUrl) | |||||
{ | { | ||||
finalUrl = url; | |||||
if (GetImageSizeFunc != null) | if (GetImageSizeFunc != null) | ||||
{ | { | ||||
var info = new ImageInfo() { url = url, titled_image=TitledImage }; | var info = new ImageInfo() { url = url, titled_image=TitledImage }; | ||||
@@ -316,30 +319,51 @@ namespace MarkdownDeep | |||||
url=url.Substring(1); | url=url.Substring(1); | ||||
} | } | ||||
str=str + "\\" + url.Replace("/", "\\"); | |||||
var success = false; | |||||
// Because PathSpecification.RelativeAsFolder creates an additional layer of directories, | |||||
// this trial & error code was implemented to ensure that images could be found | |||||
var count = 0; | |||||
while (count < 2) | |||||
{ | |||||
//Create an image object from the uploaded file | |||||
try | |||||
{ | |||||
var fileName = str + "\\"; | |||||
var currentUrl = url; | |||||
// | |||||
for (int i = 0; i < count; i++) | |||||
{ | |||||
currentUrl = "../" + currentUrl; | |||||
} | |||||
//Create an image object from the uploaded file | |||||
try | |||||
{ | |||||
var img = System.Drawing.Image.FromFile(str); | |||||
width=img.Width; | |||||
height=img.Height; | |||||
fileName += currentUrl.Replace("/", "\\"); | |||||
if (File.Exists(fileName)) | |||||
{ | |||||
var img = System.Drawing.Image.FromFile(fileName); | |||||
width = img.Width; | |||||
height = img.Height; | |||||
finalUrl = currentUrl; | |||||
if (MaxImageWidth != 0 && width > MaxImageWidth) | |||||
{ | |||||
height = (int)((double)height * (double)MaxImageWidth / (double)width); | |||||
width = MaxImageWidth; | |||||
} | |||||
if (MaxImageWidth != 0 && width>MaxImageWidth) | |||||
success = true; | |||||
break; | |||||
} | |||||
} | |||||
catch (Exception) | |||||
{ | { | ||||
height=(int)((double)height * (double)MaxImageWidth / (double)width); | |||||
width=MaxImageWidth; | |||||
} | } | ||||
return true; | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
return false; | |||||
count++; | |||||
} | } | ||||
return success; | |||||
} | } | ||||
@@ -388,9 +412,11 @@ namespace MarkdownDeep | |||||
} | } | ||||
// Try to determine width and height | // Try to determine width and height | ||||
var url = tag.attributes["src"]; | |||||
int width, height; | int width, height; | ||||
if (OnGetImageSize(tag.attributes["src"], TitledImage, out width, out height)) | |||||
if (OnGetImageSize(url, TitledImage, out width, out height, out url)) | |||||
{ | { | ||||
tag.attributes["src"] = url; | |||||
tag.attributes["width"] = width.ToString(); | tag.attributes["width"] = width.ToString(); | ||||
tag.attributes["height"] = height.ToString(); | tag.attributes["height"] = height.ToString(); | ||||
} | } | ||||