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);
+ }
}
}