diff --git a/src/Blend.Optimizely/TagHelpers/AnchorTagHelper.cs b/src/Blend.Optimizely/TagHelpers/AnchorTagHelper.cs new file mode 100644 index 0000000..68f7178 --- /dev/null +++ b/src/Blend.Optimizely/TagHelpers/AnchorTagHelper.cs @@ -0,0 +1,89 @@ +using Blend.Optimizely; +using EPiServer.ServiceLocation; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Blend.Optimizely.TagHelpers +{ + [HtmlTargetElement("a", Attributes = "content-link")] + public class AnchorTagHelper : TagHelper + { + private Injected LinkResolver { get; } + + /// + /// Some kind of link that can be resolved via Optimizely. This includes: + /// IResolvable for special cases + /// IContent objects + /// Url objects + /// LinkItem objects + /// ContentReference objects + /// + [HtmlAttributeName("content-link")] + public object ContentLink { get; set; } + + /// + /// How should the anchor tag be handle when there is no valid href or the condition is false. + /// Options are: + /// None, ConvertLinkToSpan, SuppressOutput, KeepInnerContent + /// + [HtmlAttributeName("link-options")] + public LinkOptions LinkOptions { get; set; } + + [HtmlAttributeName("fallback-option")] + public AnchorFallbackOptions FallbackOption { get; set; } + + /// + /// Only output an anchor tag with href when condition is true and a valid link exists. Default is true + /// + [HtmlAttributeName("condition")] + public bool Condition { get; set; } = true; + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context is null || output is null) + return; + + var resolvedLink = LinkResolver.Service.TryResolveLink(ContentLink, LinkOptions); + if (resolvedLink is not null && resolvedLink.Href.HasValue() && Condition) + { + output.Attributes.SetAttribute("href", resolvedLink.Href); + + if (resolvedLink.Target.HasValue() && !context.AllAttributes.TryGetAttribute("target", out _)) + output.Attributes.SetAttribute("target", resolvedLink.Target); + } + else if (FallbackOption is AnchorFallbackOptions.ConvertLinkToSpan) + { + output.TagName = "span"; + } + else if (FallbackOption is AnchorFallbackOptions.SuppressOutput) + { + output.SuppressOutput(); + return; + } + else if (FallbackOption is AnchorFallbackOptions.KeepInnerContent) + { + output.TagName = string.Empty; + return; + } + } + } + + public enum AnchorFallbackOptions + { + None = 0, + + /// + /// If url is blank, convert the wrapping a tag to a span + /// + ConvertLinkToSpan = 1, + + /// + /// If url is blank, suppress output + /// + SuppressOutput = 2, + + /// + /// If url is blank, remove wrapping tag + /// + KeepInnerContent = 3 + } +} \ No newline at end of file diff --git a/src/Blend.Optimizely/TagHelpers/ConditionalWrapperHelper.cs b/src/Blend.Optimizely/TagHelpers/ConditionalWrapperHelper.cs new file mode 100644 index 0000000..59eccc7 --- /dev/null +++ b/src/Blend.Optimizely/TagHelpers/ConditionalWrapperHelper.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Razor.TagHelpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blend.Optimizely.TagHelpers +{ + [HtmlTargetElement("*", Attributes = "wrap-if")] + [HtmlTargetElement("*", Attributes = "suppress-if")] + public class ConditionalWrapperHelper : TagHelper + { + [HtmlAttributeName("wrap-if")] + public bool WrapIfCondition { get; set; } + + [HtmlAttributeName("suppress-if")] + public bool SuppressIfCondition { get; set; } + + + [HtmlAttributeName("link-options")] + public LinkOptions LinkOptions { get; set; } + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (SuppressIfCondition) + { + output.SuppressOutput(); + } + if (!WrapIfCondition) + { + output.TagName = null; + } + } + } +} diff --git a/src/Blend.Optimizely/TagHelpers/IImageFile.cs b/src/Blend.Optimizely/TagHelpers/IImageFile.cs new file mode 100644 index 0000000..6b5b817 --- /dev/null +++ b/src/Blend.Optimizely/TagHelpers/IImageFile.cs @@ -0,0 +1,16 @@ +using EPiServer.Core; +using System.Collections.Generic; + +namespace Blend.Optimizely.TagHelpers +{ + internal interface IImageFile :IContent + { + public int Width { get; set; } + public int Height { get; set; } + + public string GetAltText(); + + public IEnumerable ImageOptions(); + + } +} diff --git a/src/Blend.Optimizely/TagHelpers/ImageHelper.cs b/src/Blend.Optimizely/TagHelpers/ImageHelper.cs new file mode 100644 index 0000000..4f17e94 --- /dev/null +++ b/src/Blend.Optimizely/TagHelpers/ImageHelper.cs @@ -0,0 +1,68 @@ +using EPiServer; +using EPiServer.Core; +using EPiServer.ServiceLocation; +using Microsoft.AspNetCore.Razor.TagHelpers; +using System.Collections.Generic; +using System.Linq; + +namespace Blend.Optimizely.TagHelpers +{ + [HtmlTargetElement("Image", Attributes = "src", TagStructure = TagStructure.WithoutEndTag)] + public class ImageHelper : TagHelper + { + public ContentReference? src { get; set; } + + [HtmlAttributeName("eager-loading")] + public bool EagerLoading { get; set; } + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (!src.HasValue() && output != null) + { + output.SuppressOutput(); + return; + } + + var imgUrl = src.ResolveUrl(); + if (!imgUrl.HasValue()) + { + output.SuppressOutput(); + return; + } + + var imageVariations = new List(); + + var loader = ServiceLocator.Current.GetInstance(); + if (loader.TryGet(src, out var image)) + { + output.Attributes.SetAttribute("alt", image.GetAltText()); + + if (image.Width > 0) + output.Attributes.SetAttribute("width", image.Width); + + if (image.Height > 0) + output.Attributes.SetAttribute("height", image.Height); + + imageVariations = image.ImageOptions().Where(x => x != image.Width).Select(x => + { + var url = new UrlBuilder(imgUrl); + url.QueryCollection.Add("width", x.ToString()); + return (string)url + $" {x}w"; + }).ToList(); + + if (imageVariations.HasValue()) + { + imageVariations.Add($"{imgUrl} {image.Width}w"); + output.Attributes.SetAttribute("srcset", string.Join($", ", imageVariations)); + output.Attributes.SetAttribute("sizes", "100vw"); + } + } + + output.TagName = "img"; + output.TagMode = TagMode.SelfClosing; + output.Attributes.SetAttribute("src", imgUrl); + + if (!EagerLoading) + output.Attributes.SetAttribute("loading", "lazy"); + } + } +} \ No newline at end of file diff --git a/src/Blend.Optimizely/TagHelpers/SourceHelper.cs b/src/Blend.Optimizely/TagHelpers/SourceHelper.cs new file mode 100644 index 0000000..7af1565 --- /dev/null +++ b/src/Blend.Optimizely/TagHelpers/SourceHelper.cs @@ -0,0 +1,61 @@ +using EPiServer; +using EPiServer.Core; +using Microsoft.AspNetCore.Razor.TagHelpers; +using System.Linq; + +namespace Blend.Optimizely.TagHelpers +{ + + [HtmlTargetElement("Source", Attributes = "srcset")] + public class SourceHelper : TagHelper + { + public ContentReference? src { get; set; } + + public bool EagerLoading { get; set; } + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (!src.HasValue() && output != null) + { + output.SuppressOutput(); + return; + } + + var image = src.Get(); // NOTE: This is likely to be a breaking change - Switching from `ImageFile` to `IImageFile` + var imgUrl = src.ResolveUrl(); + + if (image == null || !imgUrl.HasValue()) + { + output.SuppressOutput(); + return; + } + + output.TagName = "img"; + output.TagMode = TagMode.SelfClosing; + output.Attributes.SetAttribute("src", imgUrl); + output.Attributes.SetAttribute("alt", image.GetAltText()); // NOTE: This is likely to be a breaking change - Switching from `ContentReference` to `IImageFile` + + if (image.Width > 0) + output.Attributes.SetAttribute("width", image.Width); + + if (image.Height > 0) + output.Attributes.SetAttribute("height", image.Height); + + var imageVariations = image.ImageOptions().Where(x => x != image.Width).Select(x => + { + var url = new UrlBuilder(imgUrl); + url.QueryCollection.Add("width", x.ToString()); + return (string)url + $" {x}w"; + }).ToList(); + + if (imageVariations.HasValue()) + { + imageVariations.Add($"{imgUrl} {image.Width}w"); + output.Attributes.SetAttribute("srcset", string.Join($", ", imageVariations)); + output.Attributes.SetAttribute("sizes", "100vw"); + } + + if (!EagerLoading) + output.Attributes.SetAttribute("loading", "lazy"); + } + } +} \ No newline at end of file