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 @@
+
+
@@ -254,6 +257,25 @@
+
", 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
+
+