diff --git a/Spark.Library/Extensions/ListExtensions.cs b/Spark.Library/Extensions/ListExtensions.cs new file mode 100644 index 0000000..1a386db --- /dev/null +++ b/Spark.Library/Extensions/ListExtensions.cs @@ -0,0 +1,57 @@ +using System.Text.Json; + +namespace Spark.Library.Extensions +{ + public static class ListExtensions + { + /// + /// The method serializer object to json + /// + /// + /// + /// string + public static string ToJson(this List source) + { + if (source is null) + { + throw new ArgumentException("You need list"); + } + + return JsonSerializer.Serialize(source); + } + + /// + /// The collapse method collapses a collection of List into a single, flat collection + /// + /// + /// + /// List + public static List Collapse(this List> source) + { + if (source is null) + { + throw new ArgumentException("You need list"); + } + return source + .Aggregate(new List(), (x, y) => x.Concat(y) + .ToList()); + } + + /// + /// The diff method compares the List against another List. This method will return the values in the original List that are not present in the given List + /// + /// + /// + /// + /// List + public static List Diff(this List source, List anotherList) + { + if (source is null || anotherList is null) + { + throw new ArgumentException("You need list"); + } + + return source.Except(anotherList).ToList(); + } + } +} diff --git a/Spark.Library/Extensions/Queryable/PagedResponse.cs b/Spark.Library/Extensions/Queryable/PagedResponse.cs new file mode 100644 index 0000000..15bc756 --- /dev/null +++ b/Spark.Library/Extensions/Queryable/PagedResponse.cs @@ -0,0 +1,38 @@ + +namespace Spark.Library.Extensions.Queryable +{ + public class PagedResponse : ResponsePage + { + public PagedResponse(T data, int page, int limit) + { + Page = page; + Limit = limit; + Data = data; + } + + public int Page { get; set; } + public int Limit { get; set; } + public Uri? FirstPage { get; set; } + public Uri? LastPage { get; set; } + public int Total { get; set; } + public Uri? NextPage { get; set; } + public Uri? PreviousPage { get; set; } + + public string LinksRazor() + { + bool hasNext = NextPage is not null; + + bool hasPrevious = PreviousPage is not null && Page > 1; + + string style = ".pagination a {\r\n color: black;\r\n float: left;\r\n padding: 8px 16px;\r\n text-decoration: none;\r\n transition: background-color .3s;\r\n border: 1px solid #ddd;\r\n}\r\n\r\n.pagination a.active {\r\n background-color: #4CAF50;\r\n color: white;\r\n border: 1px solid #4CAF50;\r\n}\r\n\r\n.pagination a:hover:not(.active) {background-color: #ddd;} .isDisabled {\r\n color: currentColor;\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n text-decoration: none; pointer-events: none;\r\n cursor: default; \r\n }"; + + string html = $"" + + $""; + + return html; + } + } +} diff --git a/Spark.Library/Extensions/Queryable/QueryableExtensions.cs b/Spark.Library/Extensions/Queryable/QueryableExtensions.cs new file mode 100644 index 0000000..f8d0999 --- /dev/null +++ b/Spark.Library/Extensions/Queryable/QueryableExtensions.cs @@ -0,0 +1,93 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; +using System.Linq.Expressions; + +namespace Spark.Library.Extensions.Queryable +{ + public static class QueryableExtensions + { + /// + /// The paginate method automatically takes care of setting the query's "limit" and "offset" based on the current page being viewed by the user. + /// By default, the current page is detected by the value of the page query string argument on the HTTP request. + /// This value is automatically detected by Laravel, and is also automatically inserted into links generated by the paginator. + /// Case if you not to use HttpRequest and IHttpContextAccessor you won't create links as + /// + /// + /// + /// + /// + /// + /// + /// PagedResponse + public static PagedResponse> Paginate(this IQueryable items, int page = 1, int limit = 15, HttpRequest? request = null, IHttpContextAccessor? httpContextAccessor = null) + { + page = page <= 1 ? 1 : page; + + var total = items + .ToList() + .Count(); + + var listPaginated = items + .Skip((page - 1) * limit) + .Take(limit); + + var respose = new PagedResponse>(listPaginated.ToList(), page, limit); + + var totalPages = total / (double)limit; + + int roundedTotalPages = Convert.ToInt32(Math.Ceiling(totalPages)); + + if (request is not null && httpContextAccessor is not null) + { + var route = request.Path.Value!; + + string url = string.Concat(httpContextAccessor?.HttpContext?.Request.Scheme, "://", httpContextAccessor?.HttpContext?.Request.Host.ToUriComponent()); + + + respose.NextPage = + page >= 1 && page < roundedTotalPages + ? GetPageUri(url,page + 1, limit, route) + : null; + + respose.PreviousPage = + page - 1 >= 1 && page <= roundedTotalPages + ? GetPageUri(url, page + 1, limit, route) + : null; + + respose.FirstPage = GetPageUri(url, 1, limit, route); + + respose.LastPage = GetPageUri(url, roundedTotalPages, limit, route); + } + + respose.Total = total; + + return respose; + } + + /// + /// The when method will execute the given callback when the first argument given to the method evaluates to true. + /// + /// + /// + /// + /// + /// + public static IQueryable When(this IQueryable source, bool conditional, Expression> predicate) + { + if (conditional) + { + return source.Where(predicate); + } + return source; + } + + + private static Uri GetPageUri(string baseUri, int page = 1, int limit = 15, string route = "/not-found") + { + var _enpointUri = new Uri(string.Concat(baseUri, route)); + var modifiedUri = QueryHelpers.AddQueryString(_enpointUri.ToString(), "page", page.ToString()); + modifiedUri = QueryHelpers.AddQueryString(modifiedUri, "limit", limit.ToString()); + return new Uri(modifiedUri); + } + } +} \ No newline at end of file diff --git a/Spark.Library/Extensions/Queryable/ResponsePage.cs b/Spark.Library/Extensions/Queryable/ResponsePage.cs new file mode 100644 index 0000000..7c6f8f5 --- /dev/null +++ b/Spark.Library/Extensions/Queryable/ResponsePage.cs @@ -0,0 +1,18 @@ +namespace Spark.Library.Extensions.Queryable +{ + public class ResponsePage + { + + public ResponsePage() + { + + } + public ResponsePage(T? data) + { + Data = data; + } + + + public T? Data { get; set; } + } +} diff --git a/Spark.Library/Extensions/StringExtensions.cs b/Spark.Library/Extensions/StringExtensions.cs index 33dfb3c..5052593 100644 --- a/Spark.Library/Extensions/StringExtensions.cs +++ b/Spark.Library/Extensions/StringExtensions.cs @@ -1,19 +1,41 @@ -using DotNetEnv; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; +using System.Globalization; using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Spark.Library.Extensions; public static class StringExtensions { - public static string Clamp(this string value, int maxChars) + + private static Dictionary> _snakeCache = new Dictionary>(); + + public static string Clamp(this string value, int maxChars,string end = "...") { - return value.Length <= maxChars ? value : value.Substring(0, maxChars) + "..."; + if (value != null && Encoding.UTF8.GetByteCount(value) <= maxChars) + { + return value; + } + + if (value.Length <= maxChars) + { + return value; + } + + if (value.Length >= maxChars) + { + return string.Concat(value.AsSpan(0, maxChars), end); + } + + string truncated = value != null ? value.Substring(0, maxChars) : ""; + + if (truncated.Length > 0) + { + truncated = truncated.Remove(truncated.LastIndexOf(" ", StringComparison.Ordinal)); + } + + return $"{truncated}{end}"; + } public static string ToSlug(this string input) @@ -71,4 +93,120 @@ public static string ToUpperFirst(this string value) string restOfString = value[1..]; return firstChar + restOfString; } + + + /// + /// The mask method masks a portion of a string with a repeated character, and may be used to obfuscate segments of strings such as email addresses and phone numbers: + /// + /// + /// + /// + /// + /// + /// string + public static string Mask(this string str, string character, int index, int? length = null, string encoding = "UTF-8") + { + if (character == "") + { + return str; + } + + string segment = length != null ? str.Substring(index, length.Value) : str.Substring(index); + + if (segment == "") + { + return str; + } + + int strlen = Encoding.GetEncoding(encoding).GetByteCount(str); + int startIndex = index; + + if (index < 0) + { + startIndex = index < -strlen ? 0 : strlen + index; + } + + string start = str.Substring(0, startIndex); + int segmentLen = Encoding.GetEncoding(encoding).GetByteCount(segment); + string end = str.Substring(startIndex + segmentLen); + + return start + new string(character[0], segmentLen) + end; + } + + + /// + /// The snake method converts the given string to snake_case: + /// + /// + /// + /// string + public static string Snake(this string value, string delimiter = "_") + { + string key = value; + + if (_snakeCache.ContainsKey(key) && _snakeCache[key].ContainsKey(delimiter)) + { + return _snakeCache[key][delimiter]; + } + + if (!string.IsNullOrEmpty(value) && !value.All(char.IsLower)) + { + value = Regex.Replace(value, @"\s+", string.Empty); + value = Regex.Replace(value, @"(.)(?=[A-Z])", "$1" + delimiter); + value = value.ToLowerInvariant(); + } + + string result = value; + + if (_snakeCache.ContainsKey(key)) + { + _snakeCache[key].Add(delimiter, result); + } + else + { + _snakeCache.Add(key, new Dictionary { { delimiter, result } }); + } + + return result; + } + + + /// + /// The method determines if the given string is valid JSON: + /// + /// + /// bool + public static bool IsJson(this string value) + { + if (string.IsNullOrEmpty(value)) + { + return false; + } + + try + { + var result = JsonSerializer.Deserialize(value); + return true; + } + catch (JsonException) + { + return false; + } + } + + + /// + /// the method converts the given string to Title Case + /// + /// + /// string + public static string Title(this string text) + { + string cultureName = CultureInfo.CurrentCulture.Name ?? "en-US"; + + var textinfo = new CultureInfo(cultureName, false).TextInfo; + + return textinfo.ToTitleCase(text); + } + } diff --git a/Spark.Tests/Extensions/ExtensionTests.cs b/Spark.Tests/Extensions/ExtensionTests.cs index 013759b..65766ea 100644 --- a/Spark.Tests/Extensions/ExtensionTests.cs +++ b/Spark.Tests/Extensions/ExtensionTests.cs @@ -1,10 +1,5 @@ using Spark.Library.Extensions; -using Microsoft.AspNetCore.Components; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + namespace BlazorSpark.Tests.Extensions { @@ -30,5 +25,17 @@ public void ShouldClampString() Assert.AreEqual("This...", clampedStr); } + + [TestMethod] + public void ShouldTitleString() + { + string input = "this is a string"; + + var title = input.Title(); + + string expectedTitle = "This Is A String"; + + Assert.AreEqual(expectedTitle, title); + } } }