diff --git a/.gitignore b/.gitignore index 6aaa7e81..7389491d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ node_modules/ *.user .pnpm-debug.log .editorconfig +*.orig diff --git a/Lombiq.HelpfulLibraries.Common/Extensions/DictionaryExtensions.cs b/Lombiq.HelpfulLibraries.Common/Extensions/DictionaryExtensions.cs index a3f081e4..5bb09616 100644 --- a/Lombiq.HelpfulLibraries.Common/Extensions/DictionaryExtensions.cs +++ b/Lombiq.HelpfulLibraries.Common/Extensions/DictionaryExtensions.cs @@ -141,6 +141,25 @@ public static void AddRange( } } + /// + /// Adds a collection of to the dictionary by generating a key for each item using the + /// . If the selector's result in that value is not added. + /// + public static void AddRange( + this IDictionary dictionary, + IEnumerable values, + Func keySelector) + { + if (values == null) return; + foreach (var value in values) + { + if (keySelector(value) is { } key) + { + dictionary[key] = value; + } + } + } + /// /// Adds a new item to the list identified by a key in the dictionary. If the item is already part of the list /// then it won't add it again. diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs b/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs index 35b150af..05a935da 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Contents/ContentOrchardHelperExtensions.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.DependencyInjection; using OrchardCore.ContentManagement; -using OrchardCore.DisplayManagement; using System; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Threading.Tasks; @@ -15,33 +16,49 @@ public static class ContentOrchardHelperExtensions /// /// Gets the given content item's edit URL. /// - public static async Task GetItemEditUrlAsync( - this IOrchardHelper orchardHelper, - ContentItem contentItem) - { - var urlHelperFactory = orchardHelper.HttpContext.RequestServices.GetService(); - var viewContextAccessor = orchardHelper.HttpContext.RequestServices.GetService(); - var contentManager = orchardHelper.HttpContext.RequestServices.GetService(); + [Obsolete($"Use {nameof(GetItemEditUrl)} instead as this method does not need to be async.")] + public static Task GetItemEditUrlAsync(this IOrchardHelper orchardHelper, ContentItem contentItem) => + Task.FromResult(orchardHelper.GetItemEditUrl(contentItem)); + + /// + /// Gets the given content item's edit URL. + /// + [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] + public static string GetItemEditUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) => + orchardHelper.GetItemEditUrl(contentItem.ContentItemId); - var urlHelper = urlHelperFactory.GetUrlHelper(viewContextAccessor.ViewContext); - var metadata = await contentManager.PopulateAspectAsync(contentItem); - return urlHelper.Action(metadata.EditorRouteValues["action"].ToString(), metadata.EditorRouteValues); + /// + /// Gets the given content item's edit URL. + /// + [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] + public static string GetItemEditUrl(this IOrchardHelper orchardHelper, string contentItemId) + { + var urlHelper = orchardHelper.GetUrlHelper(); + return urlHelper.EditContentItem(contentItemId); } /// /// Gets the given content item's display URL. /// - public static async Task GetItemDisplayUrlAsync( - this IOrchardHelper orchardHelper, - ContentItem contentItem) - { - var urlHelperFactory = orchardHelper.HttpContext.RequestServices.GetService(); - var viewContextAccessor = orchardHelper.HttpContext.RequestServices.GetService(); - var contentManager = orchardHelper.HttpContext.RequestServices.GetService(); + [Obsolete($"Use {nameof(GetItemDisplayUrl)} instead as this method does not need to be async.")] + public static Task GetItemDisplayUrlAsync(this IOrchardHelper orchardHelper, ContentItem contentItem) => + Task.FromResult(orchardHelper.GetItemDisplayUrl(contentItem)); - var urlHelper = urlHelperFactory.GetUrlHelper(viewContextAccessor.ViewContext); - var metadata = await contentManager.PopulateAspectAsync(contentItem); - return urlHelper.Action(metadata.DisplayRouteValues["action"].ToString(), metadata.DisplayRouteValues); + /// + /// Gets the given content item's display URL. + /// + [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] + public static string GetItemDisplayUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) => + orchardHelper.GetItemDisplayUrl(contentItem.ContentItemId); + + /// + /// Gets the given content item's display URL. + /// + [SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")] + public static string GetItemDisplayUrl(this IOrchardHelper orchardHelper, string contentItemId) + { + var urlHelper = orchardHelper.GetUrlHelper(); + return urlHelper.DisplayContentItem(contentItemId); } /// @@ -83,4 +100,13 @@ public static string Action( params (string Key, object Value)[] additionalArguments) where TController : ControllerBase => orchardHelper.HttpContext.Action(taskActionExpression.StripResult(), additionalArguments); + + private static IUrlHelper GetUrlHelper(this IOrchardHelper orchardHelper) + { + var serviceProvider = orchardHelper.HttpContext.RequestServices; + var urlHelperFactory = serviceProvider.GetService(); + var actionContext = serviceProvider.GetService()?.ActionContext; + + return urlHelperFactory.GetUrlHelper(actionContext); + } } diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Mvc/OrchardControllerExtensions.cs b/Lombiq.HelpfulLibraries.OrchardCore/Mvc/OrchardControllerExtensions.cs index 4e6c26eb..fd32683d 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Mvc/OrchardControllerExtensions.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Mvc/OrchardControllerExtensions.cs @@ -13,8 +13,8 @@ namespace Microsoft.AspNetCore.Mvc; public static class OrchardControllerExtensions { /// - /// Uses extension method to redirect to this 's display page. + /// Uses + /// extension method to redirect to this 's display page. /// public static RedirectResult RedirectToContentDisplay(this Controller controller, IContent content) => controller.Redirect(controller.Url.DisplayContentItem(content)); diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Mvc/UrlHelperExtensions.cs b/Lombiq.HelpfulLibraries.OrchardCore/Mvc/UrlHelperExtensions.cs index c0bfe9d7..1d46837a 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Mvc/UrlHelperExtensions.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Mvc/UrlHelperExtensions.cs @@ -47,12 +47,18 @@ public static string EditContentItemWithTab(this IUrlHelper helper, string tabId /// Returns a relative URL for the display page for the given . /// public static string DisplayContentItem(this IUrlHelper helper, IContent content) => + helper.DisplayContentItem(content.ContentItem.ContentItemId); + + /// + /// Returns a relative URL for the display page for the given . + /// + public static string DisplayContentItem(this IUrlHelper helper, string contentItemId) => helper.Action( "Display", "Item", new { area = OrchardCoreContentsArea, - content.ContentItem.ContentItemId, + contentItemId, }); } diff --git a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs index fb6542e5..191a4a88 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/TagHelpers/EditorFieldSetTagHelper.cs @@ -11,7 +11,7 @@ namespace Lombiq.HelpfulLibraries.OrchardCore.TagHelpers; -[HtmlTargetElement("fieldset", Attributes = "asp-for")] +[HtmlTargetElement("fieldset", Attributes = "asp-for,label")] public class EditorFieldSetTagHelper : TagHelper { private const string Class = "class"; @@ -37,6 +37,9 @@ public class EditorFieldSetTagHelper : TagHelper [HtmlAttributeName("required")] public bool IsRequired { get; set; } + [HtmlAttributeName("readonly")] + public bool IsReadOnly { get; set; } + [HtmlAttributeName("options")] public IEnumerable Options { get; set; } @@ -81,8 +84,13 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) Label.Html().Trim() + (isRequired ? " *" : string.Empty), htmlAttributes: null); + var attributes = new Dictionary(); + AddBoolAttribute(attributes, IsReadOnly, "readonly"); + AddBoolAttribute(attributes, isRequired, "required"); + if (InputType.EqualsOrdinalIgnoreCase("checkbox")) { + attributes[Class] = "custom-control-input"; var checkbox = _htmlGenerator.GenerateCheckBox( ViewContext, For.ModelExplorer, @@ -93,9 +101,7 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) bool value => value, _ => bool.TryParse(For.Model.ToString(), out var parsedValue) ? parsedValue : null, }, - new { @class = "form-check-input" }); - - if (isRequired) MakeRequired(checkbox); + attributes); label.Attributes[Class] = "form-check-label"; @@ -112,6 +118,13 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) var inputType = InputType; if (Options != null) inputType = "select"; + attributes[Class] = "form-select"; + if (inputType != "select") + { + attributes[Class] = "form-control"; + attributes["type"] = InputType; + } + var input = inputType == "select" ? _htmlGenerator.GenerateSelect( ViewContext, @@ -120,23 +133,14 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired) For.Name, Options, allowMultiple: false, - new - { - @class = "form-select", - }) + attributes) : _htmlGenerator.GenerateTextBox( ViewContext, For.ModelExplorer, For.Name, For.Model, For.ModelExplorer.Metadata.EditFormatString, - new - { - @class = "form-control", - type = InputType, - }); - - if (isRequired) MakeRequired(input); + attributes); AppendContent(output, label); AppendContent(output, input); @@ -164,5 +168,11 @@ private static bool HasRequiredAttribute(ModelExpression modelExpression) => .GetCustomAttributes(typeof(RequiredAttribute), inherit: false) .FirstOrDefault() is RequiredAttribute; - private static void MakeRequired(TagBuilder tagBuilder) => tagBuilder.Attributes.Add("required", "required"); + private static void AddBoolAttribute(IDictionary attributes, bool value, string attributeName) + { + if (value) + { + attributes[attributeName] = attributeName; + } + } }