diff --git a/zero/Extensions/HttpContextExtensions.cs b/zero/Extensions/HttpContextExtensions.cs index eff39b0a..314dde5a 100644 --- a/zero/Extensions/HttpContextExtensions.cs +++ b/zero/Extensions/HttpContextExtensions.cs @@ -41,4 +41,30 @@ public static class HttpContextExtensions //string ipAddress = context.GetServerVariable("HTTP_X_FORWARDED_FOR"); //return !ipAddress.IsNullOrEmpty() ? ipAddress.Split(',')[0] : context.GetServerVariable("REMOTE_ADDR"); } + + + public static bool IsPartOfUrl(this HttpContext model, string url) + { + if (url.IsNullOrEmpty()) + { + return false; + } + + string destination = url.EnsureStartsWith('/').TrimEnd('/'); + string current = model.Request.Path.ToUriComponent().TrimEnd('/'); + return current.StartsWith(destination, StringComparison.InvariantCultureIgnoreCase); + } + + + public static bool IsUrl(this HttpContext model, string url) + { + if (url.IsNullOrEmpty()) + { + return false; + } + + string destination = url.EnsureStartsWith('/').TrimEnd('/'); + string current = model.Request.Path.ToUriComponent().TrimEnd('/'); + return current.Equals(destination, StringComparison.InvariantCultureIgnoreCase); + } } diff --git a/zero/Extensions/StringExtensions.cs b/zero/Extensions/StringExtensions.cs index 6c3b8da7..7d9327f3 100644 --- a/zero/Extensions/StringExtensions.cs +++ b/zero/Extensions/StringExtensions.cs @@ -1,4 +1,6 @@ using System.Globalization; +using System.Net; +using System.Text; using System.Text.RegularExpressions; namespace zero.Extensions; @@ -193,4 +195,21 @@ public static class StringExtensions } return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); } + + + public static string NewLinesToBr(this string source) + { + StringBuilder builder = new(); + string[] lines = source.Split('\n'); + for (int i = 0; i < lines.Length; i++) + { + if (i > 0) + { + builder.Append("
"); + } + builder.Append(WebUtility.HtmlEncode(lines[i])); + } + + return builder.ToString(); + } } diff --git a/zero/Rendering/IconOptions.cs b/zero/Rendering/IconOptions.cs new file mode 100644 index 00000000..69e71649 --- /dev/null +++ b/zero/Rendering/IconOptions.cs @@ -0,0 +1,12 @@ +namespace zero.Rendering; + +public class IconOptions +{ + public string CssClass { get; set; } = "app-icon"; + + public int DefaultSize { get; set; } = 18; + + public decimal DefaultStrokeWidth { get; set; } = 2; + + public string Path { get; set; } +} \ No newline at end of file diff --git a/zero/Rendering/ZeroRenderingModule.cs b/zero/Rendering/ZeroRenderingModule.cs index 5d975059..261d1df0 100644 --- a/zero/Rendering/ZeroRenderingModule.cs +++ b/zero/Rendering/ZeroRenderingModule.cs @@ -8,5 +8,7 @@ public class ZeroRenderingModule : ZeroModule public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) { services.AddScoped(); + + services.AddOptions().Bind(configuration.GetSection("Zero:Icons")); } } \ No newline at end of file diff --git a/zero/TagHelpers/ActiveTagHelper.cs b/zero/TagHelpers/ActiveTagHelper.cs new file mode 100644 index 00000000..b2ee3aaf --- /dev/null +++ b/zero/TagHelpers/ActiveTagHelper.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace zero.TagHelpers; + +[HtmlTargetElement(Attributes = "app-active")] +public class ActiveTagHelper : TagHelper +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + [HtmlAttributeName("class")] + public string Classes { get; set; } + + [HtmlAttributeName("href")] + public string Href { get; set; } + + + public ActiveTagHelper(IHttpContextAccessor contextAccessor) + { + _httpContextAccessor = contextAccessor; + } + + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.RemoveAll("app-active"); + + HashSet classes = Classes?.Split(" ").ToHashSet() ?? new HashSet(); + + if (_httpContextAccessor.HttpContext.IsPartOfUrl(Href)) + { + classes.Add("is-active"); + } + if (_httpContextAccessor.HttpContext.IsUrl(Href)) + { + classes.Add("is-active-exact"); + } + + output.Attributes.SetAttribute("href", Href); + output.Attributes.SetAttribute("class", string.Join(" ", classes)); + } +} \ No newline at end of file diff --git a/zero/TagHelpers/BaseSelectTagHelper.cs b/zero/TagHelpers/BaseSelectTagHelper.cs new file mode 100644 index 00000000..35a8bc5b --- /dev/null +++ b/zero/TagHelpers/BaseSelectTagHelper.cs @@ -0,0 +1,134 @@ +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using System.Collections; + +namespace zero.TagHelpers; + +public abstract class BaseSelectTagHelper : TagHelper +{ + protected const string FOR_ATTRIBUTE_NAME = "asp-for"; + protected bool AllowMultiple; + protected ICollection CurrentValues; + + protected BaseSelectTagHelper(IHtmlGenerator generator) + { + Generator = generator; + } + + + /// + /// Gets the used to generate the 's output. + /// + protected IHtmlGenerator Generator { get; } + + /// + /// Gets the of the executing view. + /// + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + /// + /// An expression to be evaluated against the current model. + /// + [HtmlAttributeName(FOR_ATTRIBUTE_NAME)] + public ModelExpression For { get; set; } + + /// + /// A collection of objects used to populate the <select> element with + /// <optgroup> and <option> elements. + /// + public List Items { get; set; } = new List(); + + /// + /// The name of the <input> element. + /// + /// + /// Passed through to the generated HTML in all cases. Also used to determine whether is + /// valid with an empty . + /// + public string Name { get; set; } + + + /// + public override void Init(TagHelperContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (For == null) + { + // Informs contained elements that they're running within a targeted