@@ -131,7 +131,7 @@ namespace Docnet | |||
private void GenerateSearchDataIndex() | |||
{ | |||
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", | |||
new JArray( | |||
collectedSearchEntries.Select(e=>new JObject( | |||
@@ -164,7 +164,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); | |||
searchSimpleElement.GenerateOutput(this, activePath, this.PathSpecification); | |||
activePath.Pop(); | |||
} | |||
@@ -256,6 +256,25 @@ namespace Docnet | |||
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 | |||
{ | |||
get | |||
@@ -60,10 +60,12 @@ | |||
<Compile Include="NavigatedPath.cs" /> | |||
<Compile Include="NavigationElement.cs" /> | |||
<Compile Include="NavigationLevel.cs" /> | |||
<Compile Include="PathSpecification.cs" /> | |||
<Compile Include="Program.cs" /> | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
<Compile Include="SearchIndexEntry.cs" /> | |||
<Compile Include="SimpleNavigationElement.cs" /> | |||
<Compile Include="INavigationElementExtensions.cs" /> | |||
<Compile Include="Utils.cs" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -70,11 +70,14 @@ namespace Docnet | |||
Console.WriteLine("Errors occurred, can't continue!"); | |||
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"); | |||
return null; | |||
} | |||
return config; | |||
} | |||
@@ -95,7 +98,7 @@ 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.Pages.GenerateOutput(_loadedConfig, new NavigatedPath(), _loadedConfig.PathSpecification); | |||
Console.WriteLine("Generating search index"); | |||
_loadedConfig.GenerateSearchData(); | |||
Console.WriteLine("Done!"); | |||
@@ -35,31 +35,39 @@ 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> | |||
void GenerateOutput(Config activeConfig, NavigatedPath activePath); | |||
/// <param name="pathSpecification">The path specification.</param> | |||
void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification); | |||
/// <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> | |||
/// <returns></returns> | |||
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot); | |||
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification); | |||
/// <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> | |||
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> | |||
/// Gets a value indicating whether this element is the __index element | |||
/// </summary> | |||
bool IsIndexElement { get; set; } | |||
bool IsAutoGenerated { get; set; } | |||
string Name { get; set; } | |||
object Value { get; set; } | |||
string TargetURL { get; } | |||
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> | |||
{ | |||
/// <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> | |||
/// <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> | |||
public string CreateBreadCrumbsHTML(string relativePathToRoot) | |||
public string CreateBreadCrumbsHTML(string relativePathToRoot, PathSpecification pathSpecification) | |||
{ | |||
var fragments = new List<string>(); | |||
// we enumerate a stack, which enumerates from top to bottom, so we have to reverse things first. | |||
foreach(var element in this.Reverse()) | |||
{ | |||
var targetURL = element.TargetURL; | |||
var targetURL = element.GetTargetURL(pathSpecification); | |||
if(string.IsNullOrWhiteSpace(targetURL)) | |||
{ | |||
fragments.Add(string.Format("<li>{0}</li>", element.Name)); | |||
@@ -73,11 +74,12 @@ namespace Docnet | |||
/// <summary> | |||
/// Creates the ToC HTML for the element reached by the elements in this path. All containers in this path are expanded, all elements inside these containers which | |||
/// aren't, are not expanded. | |||
/// 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> | |||
/// <returns></returns> | |||
public string CreateToCHTML(string relativePathToRoot) | |||
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. | |||
var rootContainer = this.Reverse().FirstOrDefault() as NavigationLevel; | |||
@@ -86,7 +88,7 @@ namespace Docnet | |||
// no root container, no TOC | |||
return string.Empty; | |||
} | |||
return rootContainer.GenerateToCFragment(this, relativePathToRoot); | |||
return rootContainer.GenerateToCFragment(this, relativePathToRoot, pathSpecification); | |||
} | |||
} | |||
} |
@@ -36,30 +36,39 @@ 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> | |||
/// <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> | |||
/// 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> | |||
/// <returns></returns> | |||
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot); | |||
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification); | |||
/// <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> | |||
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 | |||
public abstract string TargetURL { get; } | |||
/// <summary> | |||
/// Gets / sets a value indicating whether this element is the __index element | |||
/// </summary> | |||
public abstract bool IsIndexElement { get; set; } | |||
public bool IsAutoGenerated { get; set; } | |||
public string Name { get; set; } | |||
/// <summary> | |||
/// Gets or sets the value of this element, which can either be a string or a NavigationLevel | |||
@@ -71,26 +71,27 @@ namespace Docnet | |||
{ | |||
path = Path.Combine(_rootDirectory, path); | |||
} | |||
toAdd = CreateGeneratedLevel(path); | |||
toAdd.Name = nameToUse; | |||
} | |||
else | |||
{ | |||
toAdd = new SimpleNavigationElement | |||
{ | |||
Name = nameToUse, | |||
Value = childValue, | |||
IsIndexElement = isIndexElement | |||
}; | |||
{ | |||
Name = nameToUse, | |||
Value = childValue, | |||
IsIndexElement = isIndexElement | |||
}; | |||
} | |||
} | |||
else | |||
{ | |||
var subLevel = new NavigationLevel(_rootDirectory) | |||
{ | |||
Name = child.Key, | |||
IsRoot = false | |||
}; | |||
{ | |||
Name = child.Key, | |||
IsRoot = false | |||
}; | |||
subLevel.Load((JObject)child.Value); | |||
toAdd = subLevel; | |||
} | |||
@@ -105,12 +106,13 @@ namespace Docnet | |||
/// </summary> | |||
/// <param name="collectedEntries">The collected entries.</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); | |||
foreach (var element in this.Value) | |||
{ | |||
element.CollectSearchIndexEntries(collectedEntries, activePath); | |||
element.CollectSearchIndexEntries(collectedEntries, activePath, pathSpecification); | |||
} | |||
activePath.Pop(); | |||
} | |||
@@ -121,14 +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> | |||
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); | |||
int i = 0; | |||
while (i < this.Value.Count) | |||
{ | |||
var element = this.Value[i]; | |||
element.GenerateOutput(activeConfig, activePath); | |||
element.GenerateOutput(activeConfig, activePath, pathSpecification); | |||
i++; | |||
} | |||
activePath.Pop(); | |||
@@ -140,8 +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> | |||
/// <returns></returns> | |||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot) | |||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) | |||
{ | |||
var fragments = new List<string>(); | |||
if (!this.IsRoot) | |||
@@ -163,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.IndexElement; | |||
var indexElement = this.GetIndexElement(pathSpecification); | |||
if (indexElement == null) | |||
{ | |||
fragments.Add(string.Format("{0}{1}</span></li>", elementStartTag, this.Name)); | |||
@@ -172,18 +176,18 @@ namespace Docnet | |||
{ | |||
if (this.IsRoot) | |||
{ | |||
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot)); | |||
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification)); | |||
} | |||
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. | |||
foreach (var element in this.Value) | |||
{ | |||
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot)); | |||
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification)); | |||
} | |||
fragments.Add("</ul>"); | |||
} | |||
@@ -191,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, HttpUtility.UrlPathEncode(this.TargetURL), this.Name)); | |||
relativePathToRoot, this.GetFinalTargetUrl(pathSpecification), this.Name)); | |||
} | |||
if (!this.IsRoot) | |||
{ | |||
@@ -204,9 +208,10 @@ namespace Docnet | |||
private NavigationLevel CreateGeneratedLevel(string path) | |||
{ | |||
var root = new NavigationLevel(_rootDirectory) | |||
{ | |||
ParentContainer = this | |||
}; | |||
{ | |||
ParentContainer = this, | |||
IsAutoGenerated = true | |||
}; | |||
foreach (var mdFile in Directory.GetFiles(path, "*.md", SearchOption.TopDirectoryOnly)) | |||
{ | |||
@@ -217,11 +222,12 @@ namespace Docnet | |||
} | |||
var item = new SimpleNavigationElement | |||
{ | |||
Name = name, | |||
Value = Utils.MakeRelativePath(mdFile, _rootDirectory), | |||
ParentContainer = root | |||
}; | |||
{ | |||
Name = name, | |||
Value = Path.Combine(Utils.MakeRelativePath(_rootDirectory, path), Path.GetFileName(mdFile)), | |||
ParentContainer = root, | |||
IsAutoGenerated = true | |||
}; | |||
root.Value.Add(item); | |||
} | |||
@@ -234,6 +240,7 @@ namespace Docnet | |||
root.Value.Add(subDirectoryNavigationElement); | |||
} | |||
return root; | |||
} | |||
@@ -270,49 +277,106 @@ namespace Docnet | |||
return title; | |||
} | |||
#region Properties | |||
public override string TargetURL | |||
public override string GetTargetURL(PathSpecification pathSpecification) | |||
{ | |||
get | |||
var defaultElement = this.GetIndexElement(pathSpecification); | |||
if (defaultElement == null) | |||
{ | |||
var defaultElement = this.IndexElement; | |||
if (defaultElement == null) | |||
{ | |||
return string.Empty; | |||
} | |||
return defaultElement.TargetURL ?? string.Empty; | |||
return string.Empty; | |||
} | |||
} | |||
return defaultElement.GetTargetURL(pathSpecification) ?? string.Empty; | |||
} | |||
public SimpleNavigationElement IndexElement | |||
public SimpleNavigationElement GetIndexElement(PathSpecification pathSpecification) | |||
{ | |||
get | |||
var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement; | |||
if (toReturn == null) | |||
{ | |||
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) | |||
{ | |||
// no index element, add an artificial one. | |||
var path = string.Empty; | |||
if (this.ParentContainer != null) | |||
{ | |||
path = Path.GetDirectoryName(this.ParentContainer.TargetURL); | |||
path = Path.GetDirectoryName(this.ParentContainer.GetTargetURL(pathSpecification)); | |||
} | |||
var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", ""); | |||
if (string.IsNullOrWhiteSpace(nameToUse)) | |||
{ | |||
return null; | |||
} | |||
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> | |||
/// Gets / sets a value indicating whether this element is the __index element | |||
/// </summary> | |||
@@ -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 | |||
// by using the '*' as shown below: | |||
// [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")] |
@@ -49,7 +49,9 @@ 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> | |||
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(!this.IsIndexElement) | |||
@@ -58,7 +60,7 @@ namespace Docnet | |||
} | |||
_relativeH2LinksOnPage.Clear(); | |||
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 content = string.Empty; | |||
this.MarkdownFromFile = string.Empty; | |||
@@ -87,7 +89,8 @@ namespace Docnet | |||
{ | |||
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); | |||
content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeH2LinksOnPage, activeConfig.ConvertLocalLinks); | |||
@@ -109,8 +112,8 @@ namespace Docnet | |||
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)); | |||
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)); | |||
// 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 | |||
@@ -129,14 +132,15 @@ namespace Docnet | |||
/// </summary> | |||
/// <param name="collectedEntries">The collected entries.</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); | |||
// simply convert ourselves into an entry if we're not an index | |||
if(!this.IsIndexElement) | |||
{ | |||
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); | |||
} | |||
activePath.Pop(); | |||
@@ -148,8 +152,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> | |||
/// <returns></returns> | |||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot) | |||
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) | |||
{ | |||
// index elements are rendered in the parent container. | |||
if(this.IsIndexElement) | |||
@@ -157,7 +162,7 @@ namespace Docnet | |||
return string.Empty; | |||
} | |||
return PerformGenerateToCFragment(navigatedPath, relativePathToRoot); | |||
return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification); | |||
} | |||
@@ -167,8 +172,9 @@ 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> | |||
/// <returns></returns> | |||
public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot) | |||
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. | |||
bool isCurrent = navigatedPath.Contains(this); | |||
@@ -184,7 +190,7 @@ namespace Docnet | |||
string.IsNullOrWhiteSpace(liClass) ? string.Empty : string.Format(" class=\"{0}\"", liClass), | |||
string.IsNullOrWhiteSpace(aClass) ? string.Empty : string.Format(" class=\"{0}\"", aClass), | |||
relativePathToRoot, | |||
HttpUtility.UrlPathEncode(this.TargetURL), | |||
this.GetFinalTargetUrl(pathSpecification), | |||
this.Name)); | |||
if(isCurrent && _relativeH2LinksOnPage.Any()) | |||
{ | |||
@@ -203,26 +209,41 @@ namespace Docnet | |||
return string.Join(Environment.NewLine, fragments.ToArray()); | |||
} | |||
#region Properties | |||
public override string TargetURL | |||
/// <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) | |||
{ | |||
get | |||
if (_targetURLForHTML == null) | |||
{ | |||
if(_targetURLForHTML==null) | |||
_targetURLForHTML = (this.Value ?? string.Empty); | |||
var toReplace = ".md"; | |||
var replacement = ".htm"; | |||
if (pathSpecification == PathSpecification.RelativeAsFolder) | |||
{ | |||
_targetURLForHTML = (this.Value ?? string.Empty); | |||
if(_targetURLForHTML.ToLowerInvariant().EndsWith(".md")) | |||
if (!IsIndexElement && !_targetURLForHTML.EndsWith("index.md", StringComparison.InvariantCultureIgnoreCase)) | |||
{ | |||
_targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length-3) + ".htm"; | |||
replacement = "/index.htm"; | |||
} | |||
_targetURLForHTML = _targetURLForHTML.Replace("\\", "/"); | |||
} | |||
return _targetURLForHTML; | |||
if (_targetURLForHTML.EndsWith(toReplace, StringComparison.InvariantCultureIgnoreCase)) | |||
{ | |||
_targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length - toReplace.Length) + replacement; | |||
} | |||
_targetURLForHTML = _targetURLForHTML.Replace("\\", "/"); | |||
} | |||
} | |||
return _targetURLForHTML; | |||
} | |||
#region Properties | |||
/// <summary> | |||
/// Gets / sets a value indicating whether this element is the __index element | |||
/// </summary> | |||
@@ -14,6 +14,7 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Text; | |||
@@ -279,8 +280,10 @@ namespace MarkdownDeep | |||
} | |||
// 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) | |||
{ | |||
var info = new ImageInfo() { url = url, titled_image=TitledImage }; | |||
@@ -314,30 +317,51 @@ namespace MarkdownDeep | |||
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; | |||
} | |||
@@ -386,9 +410,11 @@ namespace MarkdownDeep | |||
} | |||
// Try to determine width and height | |||
var url = tag.attributes["src"]; | |||
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["height"] = height.ToString(); | |||
} | |||