Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
2a8f19c
Enable SrcSet support with three different input formats and test cas…
Krishanthaudayakumara Jul 9, 2025
2d719cd
Remove unnecessary white spaces remained in changes
Krishanthaudayakumara Jul 9, 2025
0937e2f
Add changes to match with content SDK
Krishanthaudayakumara Jul 14, 2025
defd382
Alternative approach for parameter preservation in srcSet functionality
Krishanthaudayakumara Jul 14, 2025
6fc8c4f
Enhanced parameter support - Added legacy width and maxWidth paramet…
Krishanthaudayakumara Jul 14, 2025
eaa8bc5
Replace var keywords with explicit type declarations
Krishanthaudayakumara Jul 14, 2025
e58c52c
feat: Keep URL parameter preservation for srcset and add comprehensiv…
Krishanthaudayakumara Jul 16, 2025
454e049
Remove unnessasary GetMediaLink method overloading
Krishanthaudayakumara Jul 16, 2025
50c37ba
Updated - Enable SrcSet support with Content SDK approach compatibili…
Krishanthaudayakumara Jul 16, 2025
43a09f8
Add comment explanation of GetMediaLinkForSrcSet preservation and usi…
Krishanthaudayakumara Jul 16, 2025
10c272a
refactor the code to eliminate the unnecessary dictionary copying by …
Krishanthaudayakumara Jul 17, 2025
21d8af3
extract the duplicated URL prefix replacement logic into a separate m…
Krishanthaudayakumara Jul 17, 2025
87fabe5
reorder the methods below the properties in ImageTagHelper.cs - SDK.R…
Krishanthaudayakumara Jul 17, 2025
8b1bff5
updated ParseSrcSet in - ImageTagHelper.cs code to use the [srcSetVal…
Krishanthaudayakumara Jul 17, 2025
3e0cb92
Remove unnessasary comments in ImageTagHelper and refactor ParseSrcSe…
Krishanthaudayakumara Jul 17, 2025
99aa164
Remove unnessasary comments in ImageTagHelper and refactor ParseSrcSe…
Krishanthaudayakumara Jul 17, 2025
ac23fcb
Throw exception in invalid JSON passing in ParseJsonSrcSet, updated t…
Krishanthaudayakumara Jul 17, 2025
f87a037
Add clarification comment for the use of Dictionary<string, object>[]
Krishanthaudayakumara Jul 17, 2025
edd2bfd
optimize this by using LINQ's Where() - in GenerateSrcSetAttribute
Krishanthaudayakumara Jul 17, 2025
efe5b45
Remove Unnecessary comment
Krishanthaudayakumara Jul 17, 2025
ad66f66
Add readable approach in priority checking in GetWidthDescriptor method
Krishanthaudayakumara Jul 17, 2025
6e62708
Remove unnecessary comment
Krishanthaudayakumara Jul 17, 2025
6ccd0bd
refactor the GetWidthDescriptor method to use a helper method and the…
Krishanthaudayakumara Jul 17, 2025
2d3978f
refactor: improve ImageTagHelper test assertions with precise string …
Krishanthaudayakumara Jul 17, 2025
fec9351
remove the intermediate variable and casting in ParseJsonSrcSet
Krishanthaudayakumara Jul 18, 2025
2b3c82d
Remove some unnecessary comments used in ImageTagHelper
Krishanthaudayakumara Jul 18, 2025
b0887ee
refactor: rename MergeParameters method parameters for clarity
Krishanthaudayakumara Jul 21, 2025
fe0a9fe
refactor: extract duplicate parameter processing logic into reusable …
Krishanthaudayakumara Jul 21, 2025
d40864e
refactor: extract URL parameter parsing into separate ParseUrlParams …
Krishanthaudayakumara Jul 21, 2025
b3407d6
refactor: add using System.Text.Json to remove fully qualified name -…
Krishanthaudayakumara Jul 21, 2025
1343dc7
refactor: move complete URL parsing logic into ParseUrlParams method
Krishanthaudayakumara Jul 22, 2025
03938a6
refactor: move mergedParams declaration to ParseUrlParams call for cl…
Krishanthaudayakumara Jul 22, 2025
09ad819
Intergration tests for GeneratesProperSrcSetAttribute and SrcSetAttri…
Krishanthaudayakumara Jul 24, 2025
f6c22c0
Added FourthImage and enhanced integration tests to assert srcset URL…
Krishanthaudayakumara Jul 25, 2025
041c70e
update the testcase ImgTagHelper_SrcSetAttributeContainsCorrectUrls t…
Krishanthaudayakumara Jul 25, 2025
5c4bed2
Complete check of attribute contents in GenerateProperSrcSetAttribute
Krishanthaudayakumara Jul 25, 2025
e535930
Remove ImgTagHelper_SrcSetAttributeContainsCorrectUrlsAndSizes and me…
Krishanthaudayakumara Jul 25, 2025
104dea2
Merge pull request #13 from Krishanthaudayakumara/feat/srcset-support…
Krishanthaudayakumara Jul 25, 2025
7560201
Refactor AddParametersToResult to use RouteValueDictionary for object…
Krishanthaudayakumara Jul 29, 2025
c317da6
Updated ParseUrlParams to use QueryHelpers.ParseQuery for extracting …
Krishanthaudayakumara Jul 29, 2025
64bdd84
Refactor GetWidthDescriptor to use TryGetValue instead of ContainsKey…
Krishanthaudayakumara Jul 29, 2025
6161068
Remove JSON string support for SrcSet in ImageTagHelper and update tests
Krishanthaudayakumara Jul 29, 2025
0c22634
Merge pull request #14 from Krishanthaudayakumara/feat/srcset-support…
Krishanthaudayakumara Jul 30, 2025
ceef4be
Remove blank line to fix the warning - ComponentWithImage.cs
Krishanthaudayakumara Jul 30, 2025
618f8c8
Refactor GetWidthDescriptor to use HtmlHelper.AnonymousObjectToHtmlAt…
Krishanthaudayakumara Jul 31, 2025
7141336
Refactor: Update ParseUrlParams to use Uri type and leverage Uri prop…
Krishanthaudayakumara Jul 31, 2025
1816434
Refactor: Add using for Microsoft.Extensions.Primitives and use Strin…
Krishanthaudayakumara Jul 31, 2025
b15b4c1
Simplify ParseUrlParams to handle relative URIs without exceptions
Krishanthaudayakumara Aug 1, 2025
b3e5a58
refactor: ParseUrlParams - improve URI parsing in SitecoreFieldExtens…
Krishanthaudayakumara Aug 5, 2025
ab6e358
Improve URI parsing in SitecoreFieldExtensions to handle relative and…
Krishanthaudayakumara Aug 6, 2025
03285bf
Avoid multiple return statements and return null in GetMediaLink
Krishanthaudayakumara Aug 19, 2025
d227223
fix IDE0090 violations in the SitecoreFieldExtensions file by replac…
Krishanthaudayakumara Aug 19, 2025
d87bdfc
Refactor AddParametersToResult using a switch expression pattern and …
Krishanthaudayakumara Aug 19, 2025
7916141
set blank line after closing brace of following change
Krishanthaudayakumara Aug 19, 2025
47c09bf
remove the redundant else block and consolidate to a single return st…
Krishanthaudayakumara Aug 19, 2025
6b64d7a
uses the range indexer original[queryIndex..] in parseUrlParams
Krishanthaudayakumara Aug 19, 2025
8b709c5
Remove var - use explicit types instead of var in changes
Krishanthaudayakumara Aug 19, 2025
c5e6a17
use original[..queryIndex]; in ParseUrlParams
Krishanthaudayakumara Aug 19, 2025
e0652bc
Remove unnessasary if branch in GenerateSrcSetAttribute - ImageTagHel…
Krishanthaudayakumara Aug 19, 2025
50f7051
use collection initializer syntax to create the list in GenerateSrcSe…
Krishanthaudayakumara Aug 19, 2025
9abddc0
Refactor SrcSet Implementation changes based on PR review - 2025-08-1…
Krishanthaudayakumara Aug 19, 2025
40e565b
Remove multiple return statements - GetMediaLinkForSrcSet
Krishanthaudayakumara Sep 8, 2025
07f9727
Refactor URL parsing to single-return style
Krishanthaudayakumara Sep 8, 2025
be01987
Keep null semantics and the explicit IsNullOrEmpty guard, and use a s…
Krishanthaudayakumara Sep 8, 2025
3184370
Ensure we only drop the path when URI parsing succeedsin GetSitecoreM…
Krishanthaudayakumara Sep 8, 2025
7a55d67
Refactor URL parsing and `GetMediaLinkForSrcSet `to single-return sty…
Krishanthaudayakumara Sep 8, 2025
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.Web;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.WebUtilities;
using Sitecore.AspNetCore.SDK.LayoutService.Client.Response.Model.Fields;
Expand All @@ -21,12 +23,85 @@ public static partial class SitecoreFieldExtensions
ArgumentNullException.ThrowIfNull(imageField);
string? urlStr = imageField.Value.Src;

if (urlStr == null)
string? result = null;
if (urlStr != null)
{
return null;
result = GetSitecoreMediaUri(urlStr, imageParams);
}

return GetSitecoreMediaUri(urlStr, imageParams);
return result;
}

/// <summary>
/// Gets modified URL string to Sitecore media item for srcSet.
/// This method preserves existing URL parameters and merges them with new ones.
/// </summary>
/// <param name="imageField">The image field.</param>
/// <param name="imageParams">Base image parameters.</param>
/// <param name="srcSetParams">SrcSet specific parameters that override imageParams.</param>
/// <returns>Media item URL.</returns>
public static string? GetMediaLinkForSrcSet(this ImageField imageField, object? imageParams, object? srcSetParams)
{
ArgumentNullException.ThrowIfNull(imageField);
string? urlStr = imageField.Value.Src;

string? result = null;
if (urlStr != null)
{
Dictionary<string, object?> mergedParams = MergeParameters(imageParams, srcSetParams);
result = GetSitecoreMediaUriWithPreservation(urlStr, mergedParams);
}

return result;
}

/// <summary>
/// Merges base parameters with override parameters.
/// </summary>
/// <param name="imageParams">Base image parameters.</param>
/// <param name="srcSetParams">SrcSet specific parameters that take precedence.</param>
/// <returns>Merged parameters as dictionary.</returns>
private static Dictionary<string, object?> MergeParameters(object? imageParams, object? srcSetParams)
{
Dictionary<string, object?> result = new(StringComparer.OrdinalIgnoreCase);

// Add base parameters first
AddParametersToResult(result, imageParams);

// Override with srcSet parameters
AddParametersToResult(result, srcSetParams);

return result;
}

/// <summary>
/// Adds parameters from an object to the result dictionary.
/// </summary>
/// <param name="result">The result dictionary to add parameters to.</param>
/// <param name="parameters">The parameters object (can be Dictionary or any object with properties).</param>
/// <param name="skipNullValues">Whether to skip null values when adding parameters.</param>
private static void AddParametersToResult(Dictionary<string, object?> result, object? parameters, bool skipNullValues = false)
{
switch (parameters)
{
case null:
break;
case Dictionary<string, object?> paramDict:
foreach (KeyValuePair<string, object?> kvp in paramDict.Where(kvp => !skipNullValues || kvp.Value != null))
{
result[kvp.Key] = kvp.Value;
}

break;
default:
RouteValueDictionary routeValues = new(parameters);
foreach (KeyValuePair<string, object?> kvp in routeValues.Where(kvp => !skipNullValues || kvp.Value != null))
{
result[kvp.Key] = kvp.Value;
}

break;
}
}

/// <summary>
Expand All @@ -53,6 +128,129 @@ private static string GetSitecoreMediaUri(string url, object? imageParams)
}
}

return ApplyJssMediaUrlPrefix(url);
}

/// <summary>
/// Gets modified URL string to Sitecore media item with parameter preservation.
/// This method preserves existing URL parameters and merges them with new ones.
/// </summary>
/// <param name="urlStr">The URL string.</param>
/// <param name="parameters">Parameters to merge.</param>
/// <returns>Modified URL string.</returns>
private static string? GetSitecoreMediaUriWithPreservation(string? urlStr, object? parameters)
{
string? url;

if (string.IsNullOrEmpty(urlStr))
{
url = urlStr;
}
else
{
// Parse existing query parameters and build merged parameters dictionary
Dictionary<string, object?> mergedParams = new(StringComparer.OrdinalIgnoreCase);

if (!Uri.TryCreate(urlStr, UriKind.RelativeOrAbsolute, out Uri? uri))
{
url = urlStr;
}
else
{
url = ParseUrlParams(uri, mergedParams);

// Add new parameters (these will override existing ones)
AddParametersToResult(mergedParams, parameters, skipNullValues: true);

// Add query parameters
foreach (KeyValuePair<string, object?> kvp in mergedParams)
{
if (kvp.Value != null)
{
url = QueryHelpers.AddQueryString(url, kvp.Key, kvp.Value.ToString() ?? string.Empty);
}
}
}
}

string? result = url == null ? null : ApplyJssMediaUrlPrefix(url);
return result;
}

/// <summary>
/// Parses URL query string parameters and adds them to the provided dictionary.
/// </summary>
/// <param name="uri">The Uri with potential query parameters.</param>
/// <param name="parameters">The dictionary to add parsed parameters to.</param>
/// <returns>The URL without query parameters.</returns>
private static string ParseUrlParams(Uri? uri, Dictionary<string, object?> parameters)
{
string result;

if (uri == null)
{
result = string.Empty;
}
else if (uri.IsAbsoluteUri)
{
result = ParseAbsoluteUriParams(uri, parameters);
}
else
{
result = ParseRelativeUriParams(uri, parameters);
}

return result;
}

private static string ParseAbsoluteUriParams(Uri uri, Dictionary<string, object?> parameters)
{
string url = $"{uri.Scheme}://{uri.Host}{uri.AbsolutePath}";
NameValueCollection queryParams = HttpUtility.ParseQueryString(uri.Query);
foreach (string? param in queryParams.AllKeys)
{
if (!string.IsNullOrEmpty(param))
{
parameters[param] = queryParams[param];
}
}

return url;
}

private static string ParseRelativeUriParams(Uri uri, Dictionary<string, object?> parameters)
{
// For relative URIs, accessing Uri.Query throws InvalidOperationException, so we use string manipulation
string original = uri.OriginalString;
int queryIndex = original.IndexOf('?');
string result;

if (queryIndex >= 0)
{
string query = original[queryIndex..];
Dictionary<string, Microsoft.Extensions.Primitives.StringValues> parsedQuery = QueryHelpers.ParseQuery(query);
foreach (KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues> kvp in parsedQuery)
{
parameters[kvp.Key] = kvp.Value.Count > 0 ? kvp.Value[0] : null;
}

result = original[..queryIndex];
}
else
{
result = original;
}

return result;
}

/// <summary>
/// Applies JSS media URL prefix replacement to the given URL.
/// </summary>
/// <param name="url">The URL to transform.</param>
/// <returns>The URL with JSS media prefix applied if applicable.</returns>
private static string ApplyJssMediaUrlPrefix(string url)
{
// TODO Review hardcoded matching and replacement
Match match = MediaUrlPrefixRegex().Match(url);
if (match.Success)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public class ImageTagHelper(IEditableChromeRenderer chromeRenderer) : TagHelper
private const string VSpaceAttribute = "vspace";
private const string TitleAttribute = "title";
private const string BorderAttribute = "border";
private const string SrcSetAttribute = "srcset";
private const string SizesAttribute = "sizes";
private readonly IEditableChromeRenderer _chromeRenderer = chromeRenderer ?? throw new ArgumentNullException(nameof(chromeRenderer));

/// <summary>
Expand All @@ -53,6 +55,19 @@ public class ImageTagHelper(IEditableChromeRenderer chromeRenderer) : TagHelper
/// </summary>
public object? ImageParams { get; set; }

/// <summary>
/// Gets or sets the srcset configurations for responsive images.
/// Supports: object[] (anonymous objects) or Dictionary arrays.
/// Each item should contain width parameters like { mw = 300 }, { w = 100 }.
/// </summary>
public object? SrcSet { get; set; }

/// <summary>
/// Gets or sets the sizes attribute for responsive images.
/// Example: "(min-width: 960px) 300px, 100px".
/// </summary>
public string? Sizes { get; set; }

/// <inheritdoc/>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
Expand Down Expand Up @@ -98,6 +113,20 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.Add(ScrAttribute, field.GetMediaLink(ImageParams));

if (SrcSet != null)
{
string srcSetValue = GenerateSrcSetAttribute(field);
if (!string.IsNullOrEmpty(srcSetValue))
{
output.Attributes.Add(SrcSetAttribute, srcSetValue);
}
}

if (!string.IsNullOrEmpty(Sizes))
{
output.Attributes.Add(SizesAttribute, Sizes);
}

if (!string.IsNullOrWhiteSpace(field.Value.Alt))
{
output.Attributes.Add(AltAttribute, field.Value.Alt);
Expand Down Expand Up @@ -141,6 +170,42 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
}
}

private static string? GetWidthDescriptor(object? parameters)
{
if (parameters == null)
{
return null;
}

IDictionary<string, object> dictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(parameters);

// Priority: w > mw > width > maxWidth (matching Content SDK behavior + legacy support)
string? width = null;
if (dictionary.TryGetValue("w", out object? wValue))
{
width = wValue.ToString();
}
else if (dictionary.TryGetValue("mw", out object? mwValue))
{
width = mwValue.ToString();
}
else if (dictionary.TryGetValue("width", out object? widthValue))
{
width = widthValue.ToString();
}
else if (dictionary.TryGetValue("maxWidth", out object? maxWidthValue))
{
width = maxWidthValue.ToString();
}

if (width != null && int.TryParse(width, out int widthValueInt) && widthValueInt <= 0)
{
return null;
}

return width != null ? $"{width}w" : null;
}

private TagBuilder GenerateImage(ImageField imageField, TagHelperOutput output)
{
Image image = imageField.Value;
Expand All @@ -152,6 +217,20 @@ private TagBuilder GenerateImage(ImageField imageField, TagHelperOutput output)
if (!string.IsNullOrWhiteSpace(image.Src))
{
tagBuilder.Attributes.Add(ScrAttribute, imageField.GetMediaLink(ImageParams));

if (SrcSet != null)
{
string srcSetValue = GenerateSrcSetAttribute(imageField);
if (!string.IsNullOrEmpty(srcSetValue))
{
tagBuilder.Attributes.Add(SrcSetAttribute, srcSetValue);
}
}

if (!string.IsNullOrEmpty(Sizes))
{
tagBuilder.Attributes.Add(SizesAttribute, Sizes);
}
}

if (!string.IsNullOrWhiteSpace(image.Alt))
Expand Down Expand Up @@ -230,8 +309,52 @@ private HtmlString MergeEditableMarkupWithCustomAttributes(string editableMarkUp
}

imageNode.SetAttributeValue(ScrAttribute, imageField.GetMediaLink(ImageParams));

if (SrcSet != null)
{
string srcSetValue = GenerateSrcSetAttribute(imageField);
if (!string.IsNullOrEmpty(srcSetValue))
{
imageNode.SetAttributeValue(SrcSetAttribute, srcSetValue);
}
}

if (!string.IsNullOrEmpty(Sizes))
{
imageNode.SetAttributeValue(SizesAttribute, Sizes);
}
}

return new HtmlString(doc.DocumentNode.OuterHtml);
}

private string GenerateSrcSetAttribute(ImageField imageField)
{
if (SrcSet is not object[] parsedSrcSet || parsedSrcSet.Length == 0)
{
return string.Empty;
}

List<string> srcSetEntries = [];

foreach (object srcSetItem in parsedSrcSet)
{
// Get width descriptor first to check if this entry should be included
string? descriptor = GetWidthDescriptor(srcSetItem);
if (descriptor == null)
{
// Skip entries without valid width parameters (matching Content SDK behavior)
continue;
}

// Use GetMediaLinkForSrcSet to preserve existing URL parameters (like ttc, tt, hash, quality, format) because in preview context id the images doesn't get loaded with src-set implementation
string? mediaUrl = imageField.GetMediaLinkForSrcSet(ImageParams, srcSetItem);
if (!string.IsNullOrEmpty(mediaUrl))
{
srcSetEntries.Add($"{mediaUrl} {descriptor}");
}
}

return string.Join(", ", srcSetEntries);
}
}
Loading
Loading