Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/Blend.Optimizely/TagHelpers/AnchorTagHelper.cs
Original file line number Diff line number Diff line change
@@ -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<LinkResolverService> LinkResolver { get; }

/// <summary>
/// Some kind of link that can be resolved via Optimizely. This includes:
/// IResolvable for special cases
/// IContent objects
/// Url objects
/// LinkItem objects
/// ContentReference objects
/// </summary>
[HtmlAttributeName("content-link")]
public object ContentLink { get; set; }

/// <summary>
/// How should the anchor tag be handle when there is no valid href or the condition is false.
/// Options are:
/// None, ConvertLinkToSpan, SuppressOutput, KeepInnerContent
/// </summary>
[HtmlAttributeName("link-options")]
public LinkOptions LinkOptions { get; set; }

[HtmlAttributeName("fallback-option")]
public AnchorFallbackOptions FallbackOption { get; set; }

/// <summary>
/// Only output an anchor tag with href when condition is true and a valid link exists. Default is true
/// </summary>
[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,

/// <summary>
/// If url is blank, convert the wrapping a tag to a span
/// </summary>
ConvertLinkToSpan = 1,

/// <summary>
/// If url is blank, suppress output
/// </summary>
SuppressOutput = 2,

/// <summary>
/// If url is blank, remove wrapping tag
/// </summary>
KeepInnerContent = 3
}
}
36 changes: 36 additions & 0 deletions src/Blend.Optimizely/TagHelpers/ConditionalWrapperHelper.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
16 changes: 16 additions & 0 deletions src/Blend.Optimizely/TagHelpers/IImageFile.cs
Original file line number Diff line number Diff line change
@@ -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<int> ImageOptions();

}
}
68 changes: 68 additions & 0 deletions src/Blend.Optimizely/TagHelpers/ImageHelper.cs
Original file line number Diff line number Diff line change
@@ -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<string>();

var loader = ServiceLocator.Current.GetInstance<IContentLoader>();
if (loader.TryGet<IImageFile>(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");
}
}
}
61 changes: 61 additions & 0 deletions src/Blend.Optimizely/TagHelpers/SourceHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using EPiServer;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nhawdge I don't know that this tag helper fits our use cases. We should chat about this and maybe include a FE dev.

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<IImageFile>(); // 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");
}
}
}