Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEST-536: Liquid Parser Tag and ShapeTagHelperBase #282

Merged
merged 20 commits into from
Aug 23, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@

<ItemGroup>
<PackageReference Include="linq2db" Version="5.4.0" />
<PackageReference Include="OrchardCore.Data.YesSql" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Data.YesSql" Version="2.0.0-preview-18315" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions Lombiq.HelpfulLibraries.OrchardCore/Docs/TagHelpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
Make sure to use `services.AddTagHelpers<T>()` in the `Startup` class and `@addTagHelper *, Lombiq.HelpfulLibraries.OrchardCore` in the __ViewImports.cshtml_ file.

- `EditorFieldSetTagHelper`: Eliminates significant boilerplate for editor fields in the admin dashboard. With this you can add `asp-for` to an empty `<fieldset>` element and skip the usual label, input, and validation elements that normally reside in them.
- `ShapeTagHelperBase`: A base class for tag helpers that return a shape.
25 changes: 25 additions & 0 deletions Lombiq.HelpfulLibraries.OrchardCore/Liquid/ILiquidParserTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Fluid;
using Fluid.Ast;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace Lombiq.HelpfulLibraries.OrchardCore.Liquid;

/// <summary>
/// Describes a Liquid parser tag's renderer method. This is used in <see
/// cref="LiquidServiceCollectionExtensions.AddLiquidParserTag{T}"/> to add a custom <c>{% tag_name %}</c> tag.
/// </summary>
public interface ILiquidParserTag
{
/// <summary>
/// Renders the output of the parser tag.
/// </summary>
ValueTask<Completion> WriteToAsync(
IReadOnlyList<FilterArgument> argumentsList,
TextWriter writer,
TextEncoder encoder,
TemplateContext context);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Fluid;
using Fluid.Values;
using Lombiq.HelpfulLibraries.OrchardCore.Liquid;
using OrchardCore.DisplayManagement.Liquid;
using OrchardCore.Liquid;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -12,11 +13,11 @@ public static class LiquidServiceCollectionExtensions
/// <summary>
/// Allows registering a new Liquid property with the provided <paramref name="name"/>.
/// </summary>
public static void RegisterLiquidPropertyAccessor<TService>(this IServiceCollection services, string name)
public static IServiceCollection RegisterLiquidPropertyAccessor<TService>(this IServiceCollection services, string name)
where TService : class, ILiquidPropertyRegistrar
{
services.AddScoped<ILiquidPropertyRegistrar, TService>();
services.Configure<TemplateOptions>(options =>
return services.Configure<TemplateOptions>(options =>
options
.MemberAccessStrategy
.Register<LiquidContentAccessor, LiquidPropertyAccessor>(name, (_, context) =>
Expand All @@ -36,4 +37,24 @@ public static void RegisterLiquidPropertyAccessor<TService>(this IServiceCollect
});
}));
}

/// <summary>
/// Configures the <see cref="LiquidViewOptions"/> with an additional parser tag.
/// </summary>
public static IServiceCollection AddLiquidParserTag<T>(this IServiceCollection services, string tagName)
where T : class, ILiquidParserTag
{
services.AddKeyedScoped<ILiquidParserTag, T>(tagName);

return services.Configure<LiquidViewOptions>(options =>
options.LiquidViewParserConfiguration.Add(parser => parser.RegisterParserTag(
tagName,
parser.ArgumentsListParser,
(arguments, writer, encoder, context) =>
{
var provider = ((LiquidTemplateContext)context).Services;
var service = provider.GetKeyedService<ILiquidParserTag>(tagName);
return service.WriteToAsync(arguments, writer, encoder, context);
})));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,31 @@

<ItemGroup>
<PackageReference Include="NodaTime" Version="3.1.11" />
<PackageReference Include="OrchardCore.Alias" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Autoroute" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.ContentFields" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.ContentManagement" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.ContentManagement.Abstractions" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Email.Abstractions" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.FileStorage.Abstractions" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Html" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Infrastructure" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Markdown" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Media.Core" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Queries" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Settings" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Setup.Abstractions" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Taxonomies" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Themes" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Title" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Users.Core" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Workflows.Abstractions" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Alias" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Autoroute" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.ContentFields" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.ContentManagement" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.ContentManagement.Abstractions" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Email.Abstractions" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.FileStorage.Abstractions" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Html" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Infrastructure" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Markdown" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Media.Core" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Queries" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Settings" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Setup.Abstractions" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Taxonomies" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Themes" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Title" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Users.Core" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Workflows.Abstractions" Version="2.0.0-preview-18315" />
</ItemGroup>

<!-- Necessary so tag helpers will work in the projects depending on this. -->
<ItemGroup>
<PackageReference Include="OrchardCore.DisplayManagement" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="2.0.0-preview-18315" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
Expand All @@ -19,7 +18,9 @@ public class HtmlShape : IHtmlContent, IPositioned, IShape
private static readonly IDictionary<string, string> _dummyAttributes = new Dictionary<string, string>().ToFrozenDictionary();
private static readonly IDictionary<string, object> _dummyProperties = new Dictionary<string, object>().ToFrozenDictionary();

private readonly List<IHtmlContent> _beforeContent = [];
private readonly Func<IHtmlContent?> _getHtml;
private readonly List<IHtmlContent> _afterContent = [];

public string? Position { get; set; }

Expand Down Expand Up @@ -53,7 +54,29 @@ public HtmlShape(string? value, string? position = null)
{
}

public void WriteTo(TextWriter writer, HtmlEncoder encoder) => _getHtml.Invoke()?.WriteTo(writer, encoder);
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
foreach (var before in _beforeContent)
{
before.WriteTo(writer, encoder);
}

_getHtml.Invoke()?.WriteTo(writer, encoder);

foreach (var after in _afterContent)
{
after.WriteTo(writer, encoder);
}
}

public ValueTask<IShape> AddAsync(object item, string position) => throw new ReadOnlyException();
public ValueTask<IShape> AddAsync(object? item, string? position)
{
if (item is null) return ValueTask.FromResult<IShape>(this);

var content = item as IHtmlContent ?? new HtmlContentString(item.ToString());
var target = position?.ContainsOrdinalIgnoreCase("before") == true ? _beforeContent : _afterContent;

target.Add(content);
return ValueTask.FromResult<IShape>(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Microsoft.AspNetCore.Razor.TagHelpers;
using OrchardCore.DisplayManagement;
using System;
using System.Threading.Tasks;

namespace Lombiq.HelpfulLibraries.OrchardCore.TagHelpers;

/// <summary>
/// A base class for tag helpers that return a shape.
/// </summary>
public abstract class ShapeTagHelperBase<TModel> : TagHelper
{
protected readonly IDisplayHelper _displayHelper;
protected readonly IShapeFactory _shapeFactory;

/// <summary>
/// Gets the type name of the shape to be displayed. If it returns <see langword="null"/>, the then <see
/// cref="GetShapeTypeAsync"/> is evaluated instead.
/// </summary>
protected abstract string ShapeType { get; }

protected ShapeTagHelperBase(IDisplayHelper displayHelper, IShapeFactory shapeFactory)
{
_displayHelper = displayHelper;
_shapeFactory = shapeFactory;
}

/// <summary>
/// Returns the view-model to be used when displaying the shape.
/// </summary>
protected abstract ValueTask<TModel> GetViewModelAsync(TagHelperContext context, TagHelperOutput output);

/// <summary>
/// If <see cref="ShapeType"/> is <see langword="null"/>, this method is evaluated to calculate the shape type.
/// </summary>
protected virtual ValueTask<string> GetShapeTypeAsync(TagHelperContext context, TagHelperOutput output) =>
throw new NotSupportedException(
$"Set {nameof(ShapeType)} to not null or implement {nameof(GetShapeTypeAsync)} in the derived type.");

/// <summary>
/// Optional tasks once the <paramref name="output"/>'s tag is disabled and the <see
/// cref="TagHelperOutput.PostContent"/> is already set to the rendered shape.
/// </summary>
protected virtual ValueTask PostProcessAsync(TagHelperContext context, TagHelperOutput output) =>
ValueTask.CompletedTask;

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var shape = await _shapeFactory.CreateAsync(
ShapeType ?? await GetShapeTypeAsync(context, output),
await GetViewModelAsync(context, output));
var content = await _displayHelper.ShapeExecuteAsync(shape);

output.TagName = null;
output.TagMode = TagMode.StartTagAndEndTag;
output.PostContent.SetHtmlContent(content);
await PostProcessAsync(context, output);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="OrchardCore.ContentFields" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Module.Targets" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.ContentManagement" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.ContentTypes.Abstractions" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.Autoroute" Version="2.0.0-preview-18312" />
<PackageReference Include="OrchardCore.ContentFields" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Module.Targets" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.ContentManagement" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.ContentTypes.Abstractions" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="2.0.0-preview-18315" />
<PackageReference Include="OrchardCore.Autoroute" Version="2.0.0-preview-18315" />
</ItemGroup>

<ItemGroup>
Expand Down