diff --git a/CHANGELOG.md b/CHANGELOG.md index 88d8d9ee2..7092a0a65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ Features in Experimental are subject to change and removal without being conside This document is formatted according to the principles of [Keep A CHANGELOG](http://keepachangelog.com). +## [2.8.0] - 2016-06-29 + +### Added + +* Hyperlink Feature #1: Automatic Hyperlink Generation for Scenario Titles ([320](https://github.com/picklesdoc/pickles/issues/320)) (by [@ocsurfnut](https://github.com/ocsurfnut)). + ## [2.7.0] - 2016-06-14 ### Added diff --git a/build.bat b/build.bat index ee47b56e3..13be015ae 100644 --- a/build.bat +++ b/build.bat @@ -1,5 +1,5 @@ @echo off -set "picklesVersion=2.7.0" +set "picklesVersion=2.8.0" cls diff --git a/src/Pickles/Pickles.BaseDhtmlFiles/Index.html b/src/Pickles/Pickles.BaseDhtmlFiles/Index.html index ef8794ff9..fefb5c624 100644 --- a/src/Pickles/Pickles.BaseDhtmlFiles/Index.html +++ b/src/Pickles/Pickles.BaseDhtmlFiles/Index.html @@ -1,4 +1,4 @@ - + @@ -14,7 +14,7 @@ - #### EMBED EXPERIMENTALS #### + @@ -173,7 +173,7 @@

Background:

-
+
@@ -195,6 +195,9 @@

+ Get Link for Scenario +

@@ -254,6 +257,25 @@

@NotTested Scenario Summary by Root Namespace

+ ", mathScript); } else { - this.WriteTextFile(folder, "Index.html", "#### EMBED EXPERIMENTALS ####", ""); + this.WriteTextFile(folder, "Index.html", "", ""); } this.WriteTextFile(folder, "pickledFeatures.js"); @@ -65,6 +65,7 @@ public void WriteTo(string folder) this.EnsureFolder(imagesFolder); this.WriteImage(imagesFolder, "glyphicons-halflings-white.png"); this.WriteImage(imagesFolder, "glyphicons-halflings.png"); + this.WriteImage(imagesFolder, "link.png"); string scriptsFolder = this.FileSystem.Path.Combine(folder, "js"); this.EnsureFolder(scriptsFolder); diff --git a/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlScenarioFormatter.cs b/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlScenarioFormatter.cs index b6dd41c9c..df1094740 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlScenarioFormatter.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlScenarioFormatter.cs @@ -50,6 +50,7 @@ public XElement Format(Scenario scenario, int id) var header = new XElement( this.xmlns + "div", new XAttribute("class", "scenario-heading"), + string.IsNullOrEmpty(scenario.Slug) ? null : new XAttribute("id", scenario.Slug), new XElement(this.xmlns + "h2", scenario.Name)); var tags = RetrieveTags(scenario); @@ -72,7 +73,26 @@ public XElement Format(Scenario scenario, int id) new XAttribute("class", "steps"), new XElement( this.xmlns + "ul", - scenario.Steps.Select(step => this.htmlStepFormatter.Format(step))))); + scenario.Steps.Select(step => this.htmlStepFormatter.Format(step)))), + this.FormatLinkButton(scenario)); + } + + private XElement FormatLinkButton(Scenario scenarioOutline) + { + if (string.IsNullOrEmpty(scenarioOutline.Slug)) + { + return null; + } + + return new XElement( + this.xmlns + "a", + new XAttribute("class", "scenario-link"), + new XAttribute("href", $"javascript:showImageLink('{scenarioOutline.Slug}')"), + new XAttribute("title", "Copy scenario link to clipboard."), + new XElement( + this.xmlns + "i", + new XAttribute("class", "icon-link"), + " ")); } internal static XNode[] CreateTagElements(string[] tags, XNamespace xNamespace) diff --git a/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlScenarioOutlineFormatter.cs b/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlScenarioOutlineFormatter.cs index 901e9d5fe..8ec8e9058 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlScenarioOutlineFormatter.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlScenarioOutlineFormatter.cs @@ -61,7 +61,9 @@ private XElement FormatHeading(ScenarioOutline scenarioOutline) var result = new XElement( this.xmlns + "div", new XAttribute("class", "scenario-heading"), + string.IsNullOrEmpty(scenarioOutline.Slug) ? null : new XAttribute("id", scenarioOutline.Slug), new XElement(this.xmlns + "h2", scenarioOutline.Name)); + var tags = RetrieveTags(scenarioOutline); if (tags.Length > 0) { @@ -91,6 +93,24 @@ private XElement FormatSteps(ScenarioOutline scenarioOutline) step => this.htmlStepFormatter.Format(step)))); } + private XElement FormatLinkButton(ScenarioOutline scenarioOutline) + { + if (string.IsNullOrEmpty(scenarioOutline.Slug)) + { + return null; + } + + return new XElement( + this.xmlns + "a", + new XAttribute("class", "scenario-link"), + new XAttribute("href", $"javascript:showImageLink('{scenarioOutline.Slug}')"), + new XAttribute("title", "Copy scenario link to clipboard."), + new XElement( + this.xmlns + "i", + new XAttribute("class", "icon-link"), + " ")); + } + private XElement FormatExamples(ScenarioOutline scenarioOutline) { var exampleDiv = new XElement(this.xmlns + "div"); @@ -117,6 +137,7 @@ public XElement Format(ScenarioOutline scenarioOutline, int id) this.htmlImageResultFormatter.Format(scenarioOutline), this.FormatHeading(scenarioOutline), this.FormatSteps(scenarioOutline), + this.FormatLinkButton(scenarioOutline), (scenarioOutline.Examples == null || !scenarioOutline.Examples.Any()) ? null : this.FormatExamples(scenarioOutline)); diff --git a/src/Pickles/Pickles/DocumentationBuilders/JSON/IJsonFeatureElement.cs b/src/Pickles/Pickles/DocumentationBuilders/JSON/IJsonFeatureElement.cs index 70c47ec91..8330e587d 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/JSON/IJsonFeatureElement.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/JSON/IJsonFeatureElement.cs @@ -28,6 +28,8 @@ public interface IJsonFeatureElement string Name { get; set; } + string Slug { get; set; } + string Description { get; set; } List Steps { get; set; } diff --git a/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonScenario.cs b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonScenario.cs index 997893b9f..6c2f92037 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonScenario.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonScenario.cs @@ -35,6 +35,8 @@ public JsonScenario() public string Name { get; set; } + public string Slug { get; set; } + public string Description { get; set; } public List Steps { get; set; } diff --git a/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonScenarioOutline.cs b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonScenarioOutline.cs index 43c62a6f5..cdf018318 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonScenarioOutline.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonScenarioOutline.cs @@ -37,6 +37,8 @@ public JsonScenarioOutline() public string Name { get; set; } + public string Slug { get; set; } + public string Description { get; set; } public List Steps { get; set; } diff --git a/src/Pickles/Pickles/DocumentationBuilders/JSON/Mapper/ScenarioOutlineToJsonScenarioOutlineMapper.cs b/src/Pickles/Pickles/DocumentationBuilders/JSON/Mapper/ScenarioOutlineToJsonScenarioOutlineMapper.cs index fff685b98..118e667ec 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/JSON/Mapper/ScenarioOutlineToJsonScenarioOutlineMapper.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/JSON/Mapper/ScenarioOutlineToJsonScenarioOutlineMapper.cs @@ -50,6 +50,7 @@ public JsonScenarioOutline Map(ScenarioOutline scenarioOutline) Steps = (scenarioOutline.Steps ?? new List()).Select(this.stepMapper.Map).ToList(), Tags = (scenarioOutline.Tags ?? new List()).ToList(), Name = scenarioOutline.Name, + Slug = scenarioOutline.Slug, Description = scenarioOutline.Description, Result = this.resultMapper.Map(scenarioOutline.Result), }; diff --git a/src/Pickles/Pickles/DocumentationBuilders/JSON/Mapper/ScenarioToJsonScenarioMapper.cs b/src/Pickles/Pickles/DocumentationBuilders/JSON/Mapper/ScenarioToJsonScenarioMapper.cs index 57d996c5f..a52a7b8cd 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/JSON/Mapper/ScenarioToJsonScenarioMapper.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/JSON/Mapper/ScenarioToJsonScenarioMapper.cs @@ -49,6 +49,7 @@ public JsonScenario Map(Scenario scenario) Steps = (scenario.Steps ?? new List()).Select(this.stepMapper.Map).ToList(), Tags = (scenario.Tags ?? new List()).ToList(), Name = scenario.Name, + Slug = scenario.Slug, Description = scenario.Description, Result = this.resultMapper.Map(scenario.Result), }; diff --git a/src/Pickles/Pickles/Extensions/StringExtensions.cs b/src/Pickles/Pickles/Extensions/StringExtensions.cs index 9734df3d1..68509524f 100644 --- a/src/Pickles/Pickles/Extensions/StringExtensions.cs +++ b/src/Pickles/Pickles/Extensions/StringExtensions.cs @@ -24,6 +24,8 @@ namespace PicklesDoc.Pickles.Extensions { + using System.Text.RegularExpressions; + public static class StringExtensions { public static string ExpandWikiWord(this string word) @@ -32,8 +34,8 @@ public static string ExpandWikiWord(this string word) char previous = char.MinValue; foreach (char current in word.Where(x => char.IsLetterOrDigit(x))) { - if (previous != char.MinValue && sb.Length > 1 && - ((char.IsUpper(current) || char.IsDigit(current)) && char.IsLower(previous))) + if (previous != char.MinValue && sb.Length > 1 + && ((char.IsUpper(current) || char.IsDigit(current)) && char.IsLower(previous))) { sb.Append(' '); } @@ -57,8 +59,32 @@ public static string ExpandWikiWord(this string word) public static string ComparisonNormalize(this string text) { return - text.Trim().ToLowerInvariant().Replace("\r", string.Empty).Replace("\n", Environment.NewLine).Replace( - "\t", " "); + text.Trim() + .ToLowerInvariant() + .Replace("\r", string.Empty) + .Replace("\n", Environment.NewLine) + .Replace("\t", " "); + } + + /// + /// Takes a string and returns a url-friendly version of the string (slug) with all special characters + /// and extra spaces stripped out, with words seperated by dashes. + /// + /// The string that will be used to create the slug. + /// A slug generated from the given string. + public static string ToSlug(this string text) + { + // remove any accent characters + var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(text); + var str = Encoding.ASCII.GetString(bytes); + + // modify string to slug format + str = str.ToLower(); + str = Regex.Replace(str, @"[^a-z0-9\s-]", ""); + str = Regex.Replace(str, @"\s+", " ").Trim(); + str = Regex.Replace(str, @"\s", "-"); + + return str; } } } diff --git a/src/Pickles/Pickles/ObjectModel/Mapper.cs b/src/Pickles/Pickles/ObjectModel/Mapper.cs index c87b893fe..3687feb28 100644 --- a/src/Pickles/Pickles/ObjectModel/Mapper.cs +++ b/src/Pickles/Pickles/ObjectModel/Mapper.cs @@ -25,6 +25,8 @@ namespace PicklesDoc.Pickles.ObjectModel { + using PicklesDoc.Pickles.Extensions; + public class Mapper { private readonly IConfiguration configuration; @@ -172,6 +174,7 @@ public Scenario MapToScenario(G.Scenario scenario) Description = scenario.Description ?? string.Empty, Location = this.MapToLocation(scenario.Location), Name = scenario.Name, + Slug = scenario.Name.ToSlug(), Steps = scenario.Steps.Select(this.MapToStep).ToList(), Tags = scenario.Tags.Select(this.MapToString).ToList() }; @@ -205,6 +208,7 @@ public ScenarioOutline MapToScenarioOutline(G.ScenarioOutline scenarioOutline) Examples = (scenarioOutline.Examples ?? new G.Examples[0]).Select(this.MapToExample).ToList(), Location = this.MapToLocation(scenarioOutline.Location), Name = scenarioOutline.Name, + Slug = scenarioOutline.Name.ToSlug(), Steps = scenarioOutline.Steps.Select(this.MapToStep).ToList(), Tags = scenarioOutline.Tags.Select(this.MapToString).ToList() }; diff --git a/src/Pickles/Pickles/Pickles.csproj b/src/Pickles/Pickles/Pickles.csproj index 220d4f32b..db9414817 100644 --- a/src/Pickles/Pickles/Pickles.csproj +++ b/src/Pickles/Pickles/Pickles.csproj @@ -349,6 +349,11 @@ Resources\Dhtml\js\knockout-3.4.0.js + + + Resources\Dhtml\img\link.png + +