diff --git a/MyApp.ServiceInterface/Data/DbExtensions.cs b/MyApp.ServiceInterface/Data/DbExtensions.cs new file mode 100644 index 0000000..a26020a --- /dev/null +++ b/MyApp.ServiceInterface/Data/DbExtensions.cs @@ -0,0 +1,58 @@ +using MyApp.ServiceModel; +using ServiceStack; +using ServiceStack.OrmLite; +using ServiceStack.Text; + +namespace MyApp.Data; + +public static class DbExtensions +{ + public static SqlExpression WhereContainsTag(this SqlExpression q, string? tag) + { + if (tag != null) + { + tag = tag.UrlDecode().Replace("'","").Replace("\\","").SqlVerifyFragment(); + q.UnsafeWhere("',' || tags || ',' like '%," + tag + ",%'"); + } + return q; + } + + public static SqlExpression WhereSearch(this SqlExpression q, string? search) + { + if (!string.IsNullOrEmpty(search)) + { + search = search.Trim(); + if (search.StartsWith('[') && search.EndsWith(']')) + { + q.WhereContainsTag(search.TrimStart('[').TrimEnd(']')); + } + else + { + var sb = StringBuilderCache.Allocate(); + var words = search.Split(' '); + for (var i = 0; i < words.Length; i++) + { + if (sb.Length > 0) + sb.Append(" AND "); + sb.AppendLine("(title like '%' || {" + i + "} || '%' or summary like '%' || {" + i + "} || '%' or tags like '%' || {" + i + "} || '%')"); + } + + var sql = StringBuilderCache.ReturnAndFree(sb); + q.UnsafeWhere(sql, words.Cast().ToArray()); + } + } + return q; + } + + public static SqlExpression OrderByView(this SqlExpression q, string? view) + { + view = view?.ToLower(); + if (view is "popular" or "most-views") + q.OrderByDescending(x => x.ViewCount); + else if (view is "interesting" or "most-votes") + q.OrderByDescending(x => x.Score); + else + q.OrderByDescending(x => x.Id); + return q; + } +} diff --git a/MyApp.ServiceModel/RenderComponent.cs b/MyApp.ServiceModel/RenderComponent.cs index da3752f..407d821 100644 --- a/MyApp.ServiceModel/RenderComponent.cs +++ b/MyApp.ServiceModel/RenderComponent.cs @@ -2,8 +2,15 @@ namespace MyApp.ServiceModel; +public class RenderHome +{ + public string? Tab { get; set; } + public List Posts { get; set; } +} + public class RenderComponent : IReturnVoid { public int? IfQuestionModified { get; set; } public QuestionAndAnswers? Question { get; set; } + public RenderHome? Home { get; set; } } \ No newline at end of file diff --git a/MyApp/Components/Pages/Home.razor b/MyApp/Components/Pages/Home.razor index f3a98ee..85f2759 100644 --- a/MyApp/Components/Pages/Home.razor +++ b/MyApp/Components/Pages/Home.razor @@ -1,26 +1,43 @@ @page "/" +@inject RendererCache RendererCache +@inject IMessageProducer MessageProducer +@inject IDbConnectionFactory DbFactory -Home - -
-
-

- Top Questions -

-
- - -
+pvq.app +@if (Html != null) +{ + @BlazorHtml.Raw(Html) +} +else +{ + +} + @code { + string? Html; + + [SupplyParameterFromQuery] + string? Tab { get; set; } List Posts { get; set; } = []; - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { - using var db = HostContext.AppHost.GetDbConnection(); - Posts = db.Select(db.From() - .OrderByDescending(x => x.Score) - .Take(50)); + Html = await RendererCache.GetHomeTabHtmlAsync(Tab); + if (Html != null) + return; + + using var db = await DbFactory.OpenAsync(); + var q = db.From(); + q.OrderByView(Tab); + Posts = await db.SelectAsync(q.Take(50)); + + MessageProducer.Publish(new RenderComponent { + Home = new() { + Tab = Tab, + Posts = Posts, + } + }); } } diff --git a/MyApp/Components/Pages/Questions/Index.razor b/MyApp/Components/Pages/Questions/Index.razor new file mode 100644 index 0000000..6295223 --- /dev/null +++ b/MyApp/Components/Pages/Questions/Index.razor @@ -0,0 +1,113 @@ +@page "/questions" +@inject IDbConnectionFactory DbFactory + +@Title + +@if (posts != null) +{ +
+
+

+ @Title +

+
+ + + @if (posts.Count > 0) + { +
+ +
+ + @if (total > PageSize) + { + + } + + + + @if (total > PageSize) + { + + } + } + else + { +
+ This search return no results. +
+ } +
+} +else +{ +
+ @if (error != null) + { + + } + else + { + + } +
+} + +@code { + string Path => "/questions".AddQueryParam("q", Q); + int? Skip => Page > 1 ? (Page - 1) * PageSize : 0; + string Title => "All Questions" + (!string.IsNullOrEmpty(Q) ? $" with '{Q}'" : ""); + + static string[] Tabs = ["most-votes", "most-views", "most-recent"]; + + [SupplyParameterFromQuery] + string? Q { get; set; } + + [SupplyParameterFromQuery] + string? Tab { get; set; } + + [SupplyParameterFromQuery] + int? Page { get; set; } + + [SupplyParameterFromQuery] + int? PageSize { get; set; } + + List? posts; + ResponseStatus? error = null; + long? total; + + async Task Load() + { + try + { + if (Tab == null || !Tabs.Contains(Tab)) + Tab = Tabs[0]; + if (PageSize is null or <= 0) + PageSize = 25; + + using var db = await DbFactory.OpenAsync(); + var q = db.From(); + + q.OrderByView(Tab); + if (!string.IsNullOrEmpty(Q)) + { + q.WhereSearch(Q); + } + + posts = await db.SelectAsync(q + .OrderByView(Tab) + .Skip(Page > 1 ? (Page - 1) * PageSize : 0) + .Take(PageSize.ToPageSize())); + + total = db.Count(q); + } + catch (Exception ex) + { + error = ex.ToResponseStatus(); + } + } + + protected override Task OnInitializedAsync() => Load(); + + protected override Task OnParametersSetAsync() => Load(); +} diff --git a/MyApp/Components/Pages/Questions/Question.razor b/MyApp/Components/Pages/Questions/Question.razor index 08355d1..00401df 100644 --- a/MyApp/Components/Pages/Questions/Question.razor +++ b/MyApp/Components/Pages/Questions/Question.razor @@ -1,10 +1,8 @@ @page "/questions/{Id:int}/{*Slug}" -@using ServiceStack.IO -@using MyApp.Data @inject R2VirtualFiles R2 @inject RendererCache RendererCache @inject NavigationManager NavigationManager -@inject ServiceStack.Messaging.IMessageProducer MessageProducer +@inject IMessageProducer MessageProducer @title @@ -42,7 +40,7 @@ else AuthorInfo? author; string title = ""; - string? Html = null; + string? Html; async Task load() { @@ -51,11 +49,22 @@ else if (!string.IsNullOrEmpty(Html)) { - var attrPrefix = "data-title=\""; - title = Html.IndexOf(attrPrefix, StringComparison.Ordinal) >= 0 - ? Html.RightPart(attrPrefix).LeftPart('"') - : title; + var attrPrefix = "") + : null; + if (json != null) + { + var post = json.FromJson(); + var slug = post.Slug.GenerateSlug(200); + if (slug != Slug) + { + NavigationManager.NavigateTo($"/questions/{Id}/{slug}"); + return; + } + } + MessageProducer.Publish(new RenderComponent { IfQuestionModified = Id }); @@ -77,7 +86,7 @@ else MessageProducer.Publish(new RenderComponent { Question = question }); - var slug = question.Post.Slug.GenerateSlug(); + var slug = question.Post.Slug.GenerateSlug(200); if (slug != Slug) { NavigationManager.NavigateTo($"/questions/{Id}/{slug}"); diff --git a/MyApp/Components/Pages/Questions/Tagged.razor b/MyApp/Components/Pages/Questions/Tagged.razor index 728c62f..7faa121 100644 --- a/MyApp/Components/Pages/Questions/Tagged.razor +++ b/MyApp/Components/Pages/Questions/Tagged.razor @@ -1,17 +1,39 @@ @page "/questions/tagged/{Slug}" +@inject IDbConnectionFactory DbFactory -@if (selectedTag != null) +@if (posts != null) { @selectedTag tagged questions

- All questions tagged [@selectedTag] + Questions tagged [@selectedTag]

+ + @if (posts.Count > 0) + { +
+ +
- + + + + + @if (total > PageSize) + { + + } + } + else + { +
+ There are no questions tagged with @Slug +
+ } +
} else @@ -29,20 +51,51 @@ else } @code { - [Parameter] public required string Slug { get; set; } + string Path => $"/questions/tagged/{selectedTag}"; + int? Skip => Page > 1 ? (Page - 1) * PageSize : 0; + + [Parameter] + public required string Slug { get; set; } - string? selectedTag; - List Posts = []; - readonly ResponseStatus? error = null; + static string[] Tabs = ["interesting", "popular", "newest"]; + + [SupplyParameterFromQuery] + string? Tab { get; set; } + + [SupplyParameterFromQuery] + int? PageSize { get; set; } + + [SupplyParameterFromQuery] + int? Page { get; set; } + + string selectedTag = ""; + List? posts; + ResponseStatus? error; + long? total; async Task Load() { - selectedTag = Slug.UrlDecode().Replace("'","").SqlVerifyFragment(); - using var db = HostContext.AppHost.GetDbConnection(); - Posts = await db.SelectAsync(db.From() - .UnsafeWhere("',' || tags || ',' like '%," + selectedTag + ",%'") - .OrderByDescending(x => x.CreationDate) - .Take(25)); + try + { + if (Tab == null || !Tabs.Contains(Tab)) + Tab = Tabs[0]; + if (PageSize is null or <= 0) + PageSize = 25; + + selectedTag = Slug.UrlDecode().Replace("'","").SqlVerifyFragment(); + using var db = await DbFactory.OpenAsync(); + var q = db.From() + .WhereContainsTag(selectedTag); + posts = await db.SelectAsync(q + .OrderByView(Tab) + .Skip(Page > 1 ? (Page - 1) * PageSize : 0) + .Take(PageSize.ToPageSize())); + total = db.Count(q); + } + catch (Exception ex) + { + error = ex.ToResponseStatus(); + } } protected override Task OnInitializedAsync() => Load(); diff --git a/MyApp/Components/Shared/Header.razor b/MyApp/Components/Shared/Header.razor index 0056095..79e8ac5 100644 --- a/MyApp/Components/Shared/Header.razor +++ b/MyApp/Components/Shared/Header.razor @@ -17,7 +17,13 @@
  • - +
    + + +
    +
  • +
  • + Questions
  • Leaderboard @@ -37,14 +43,14 @@
  • @@ -87,6 +93,9 @@ @code { private string? currentUrl; + [SupplyParameterFromQuery] + string? Q { get; set; } + protected override void OnInitialized() { currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); diff --git a/MyApp/Components/Shared/HomeTab.razor b/MyApp/Components/Shared/HomeTab.razor new file mode 100644 index 0000000..a6a96f6 --- /dev/null +++ b/MyApp/Components/Shared/HomeTab.razor @@ -0,0 +1,29 @@ +@if (Posts != null) +{ +
    +
    +

    + Questions +

    + +
    + + +
    +} + +@code { + static string[] Tabs = ["interesting", "popular", "newest"]; + + [Parameter] + public string? Tab { get; set; } + + [Parameter] + public required List? Posts { get; set; } + + protected override void OnInitialized() + { + if (Tab == null || !Tabs.Contains(Tab)) + Tab = Tabs[0]; + } +} diff --git a/MyApp/Components/Shared/PagesNav.razor b/MyApp/Components/Shared/PagesNav.razor new file mode 100644 index 0000000..fdb0b87 --- /dev/null +++ b/MyApp/Components/Shared/PagesNav.razor @@ -0,0 +1,112 @@ +
    + @if (Pages > 1 && Total > 0 && Total > PageSize) + { + + } + else + { +
    +

    + Showing + @($"{Total:N0}") + results: +

    +
    + } +
    + +@code { + const int DefaultPageSize = 25; + int UsePageSize => PageSize ?? DefaultPageSize; + static readonly int[] PageSizes = [10, 25, 50]; + + [Parameter] public string? @class { get; set; } + [Parameter] public required string Path { get; set; } + [Parameter] public required long? Total { get; set; } + [Parameter] public required int? PageSize { get; set; } + [Parameter] public required int Page { get; set; } + int Pages => (int)Math.Ceiling((Total ?? 0) / (double) UsePageSize); + + int StartPage => Page > 1 + ? Math.Max(0, Page - 3) + : 0; + + int EndPage => Math.Min(StartPage + 5, Pages); + + public string GetRoute(int? page = 1, int? pageSize = null) => + Path.AddQueryParam("page", page).AddQueryParam("pagesize", pageSize); +} \ No newline at end of file diff --git a/MyApp/Components/Shared/QuestionPost.razor b/MyApp/Components/Shared/QuestionPost.razor index 628dcfc..b657d8e 100644 --- a/MyApp/Components/Shared/QuestionPost.razor +++ b/MyApp/Components/Shared/QuestionPost.razor @@ -2,6 +2,8 @@ @inject AppConfig AppConfig @inject MarkdownQuestions Markdown + +

    @@ -10,7 +12,7 @@
    - asked + asked @((DateTime.UtcNow - Question.Post.CreationDate).TimeAgo())
    @if (Question.Post.LastEditDate != null) @@ -34,7 +36,7 @@
    -
    +
    @BlazorHtml.Raw(Markdown.GenerateHtml(Question.Post.Body))
    @@ -49,7 +51,7 @@ @if (Question.Post.LastEditDate != null) {
    -
    edited
    + edited
    @@ -104,6 +106,8 @@
    @code { + string StackOverflowUrl => $"https://stackoverflow.com/questions/{Question.Post.Id}/{Question.Post.Title.GenerateSlug()}"; + [Parameter] public required QuestionAndAnswers Question { get; set; } } diff --git a/MyApp/Components/Shared/QuestionPosts.razor b/MyApp/Components/Shared/QuestionPosts.razor index fc8813e..3d34625 100644 --- a/MyApp/Components/Shared/QuestionPosts.razor +++ b/MyApp/Components/Shared/QuestionPosts.razor @@ -21,7 +21,7 @@

    - @post.Title + @post.Title

    @post.Summary

    @@ -30,7 +30,7 @@
    @foreach (var tag in post.Tags.Safe()) { - @tag + @tag }
    diff --git a/MyApp/Components/Shared/QuestionViewTabs.razor b/MyApp/Components/Shared/QuestionViewTabs.razor new file mode 100644 index 0000000..51d132c --- /dev/null +++ b/MyApp/Components/Shared/QuestionViewTabs.razor @@ -0,0 +1,49 @@ +@using ServiceStack.FluentValidation.Internal +
    +
    + + +
    + +
    + +@code { + static string TabName(string tab) => string.Join(" ", tab.Split('-').Select(x => char.ToUpper(x[0]) + x[1..])); + + [Parameter] + public required string Path { get; set; } + + [Parameter] + public required string[] Tabs { get; set; } + + [Parameter] + public required string Active { get; set; } +} diff --git a/MyApp/Components/_Imports.razor b/MyApp/Components/_Imports.razor index 0ca5ca7..845b788 100644 --- a/MyApp/Components/_Imports.razor +++ b/MyApp/Components/_Imports.razor @@ -9,13 +9,17 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop @using ServiceStack +@using ServiceStack.IO @using ServiceStack.Web @using ServiceStack.Html @using ServiceStack.OrmLite +@using ServiceStack.Data +@using ServiceStack.Messaging @using ServiceStack.Blazor @using ServiceStack.Blazor.Components @using ServiceStack.Blazor.Components.Tailwind @using MyApp +@using MyApp.Data @using MyApp.Components @using MyApp.Components.Shared @using MyApp.ServiceModel diff --git a/MyApp/Configure.Renderer.cs b/MyApp/Configure.Renderer.cs index ea6305c..e563b54 100644 --- a/MyApp/Configure.Renderer.cs +++ b/MyApp/Configure.Renderer.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components.Web; +using MyApp.Components.Pages; using MyApp.Components.Shared; using MyApp.Data; using MyApp.ServiceInterface; @@ -50,6 +51,40 @@ public async Task SetQuestionPostHtmlAsync(int id, string html) var filePath = GetCachedQuestionPostPath(id); await File.WriteAllTextAsync(filePath, html); } + + private string GetHtmlTabFilePath(string? tab) + { + var partialName = string.IsNullOrEmpty(tab) + ? "" + : $".{tab}"; + var filePath = appConfig.CacheDir.CombineWith($"HomeTab{partialName}.html"); + return filePath; + } + + static TimeSpan HomeTabValidDuration = TimeSpan.FromMinutes(5); + + public async Task SetHomeTabHtmlAsync(string? tab, string html) + { + appConfig.CacheDir.AssertDir(); + var filePath = GetHtmlTabFilePath(tab); + await File.WriteAllTextAsync(filePath, html); + } + + public async Task GetHomeTabHtmlAsync(string? tab) + { + var filePath = GetHtmlTabFilePath(tab); + var fileInfo = new FileInfo(filePath); + if (fileInfo.Exists) + { + if (DateTime.UtcNow - fileInfo.LastWriteTimeUtc > HomeTabValidDuration) + return null; + + var html = await fileInfo.ReadAllTextAsync(); + if (!string.IsNullOrEmpty(html)) + return html; + } + return null; + } } public class RenderServices(R2VirtualFiles r2, BlazorRenderer renderer, RendererCache cache) : Service @@ -70,10 +105,21 @@ public async Task Any(RenderComponent request) } } } + if (request.Question != null) { var html = await renderer.RenderComponent(new() { ["Question"] = request.Question }); await cache.SetQuestionPostHtmlAsync(request.Question.Id, html); } + + if (request.Home != null) + { + var html = await renderer.RenderComponent(new() + { + ["Tab"] = request.Home.Tab, + ["Posts"] = request.Home.Posts, + }); + await cache.SetHomeTabHtmlAsync(request.Home.Tab, html); + } } } diff --git a/MyApp/UiExtensions.cs b/MyApp/UiUtils.cs similarity index 88% rename from MyApp/UiExtensions.cs rename to MyApp/UiUtils.cs index 2f0d9ea..53ed259 100644 --- a/MyApp/UiExtensions.cs +++ b/MyApp/UiUtils.cs @@ -1,7 +1,14 @@ namespace MyApp; -public static class UiExtensions +public static class UiUtils { + public const int MaxPageSize = 50; + public const int DefaultPageSize = 25; + + public static int ToPageSize(this int? pageSize) => pageSize != null + ? Math.Min(MaxPageSize, pageSize.Value) + : DefaultPageSize; + public static string ToHumanReadable(this int? number) => number == null ? "0" : number.Value.ToHumanReadable(); public static string ToHumanReadable(this int number) { diff --git a/MyApp/wwwroot/css/app.css b/MyApp/wwwroot/css/app.css index f642b13..4512ca2 100644 --- a/MyApp/wwwroot/css/app.css +++ b/MyApp/wwwroot/css/app.css @@ -888,6 +888,10 @@ select{ top: 0.5rem; } +.isolate { + isolation: isolate; +} + .z-0 { z-index: 0; } @@ -1197,6 +1201,10 @@ select{ margin-top: auto; } +.-mt-12 { + margin-top: -3rem; +} + .block { display: block; } @@ -1768,6 +1776,12 @@ select{ row-gap: 1.75rem; } +.-space-x-px > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(-1px * var(--tw-space-x-reverse)); + margin-left: calc(-1px * calc(1 - var(--tw-space-x-reverse))); +} + .space-x-1 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.25rem * var(--tw-space-x-reverse)); @@ -3007,6 +3021,11 @@ select{ --tw-ring-color: rgb(30 64 175 / var(--tw-ring-opacity)); } +.ring-gray-300 { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity)); +} + .ring-green-600\/20 { --tw-ring-color: rgb(22 163 74 / 0.2); } @@ -3419,6 +3438,14 @@ select{ background-color: rgb(237 233 254 / var(--tw-bg-opacity)); } +.focus\:z-10:focus { + z-index: 10; +} + +.focus\:z-20:focus { + z-index: 20; +} + .focus\:\!border-none:focus { border-style: none !important; } @@ -3458,6 +3485,10 @@ select{ outline-offset: 2px; } +.focus\:outline-offset-0:focus { + outline-offset: 0px; +} + .focus\:ring-1:focus { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); @@ -3542,6 +3573,22 @@ select{ --tw-ring-offset-color: #fefce8; } +.focus-visible\:outline:focus-visible { + outline-style: solid; +} + +.focus-visible\:outline-2:focus-visible { + outline-width: 2px; +} + +.focus-visible\:outline-offset-2:focus-visible { + outline-offset: 2px; +} + +.focus-visible\:outline-indigo-600:focus-visible { + outline-color: #4f46e5; +} + .disabled\:border-slate-200:disabled { --tw-border-opacity: 1; border-color: rgb(226 232 240 / var(--tw-border-opacity)); @@ -3747,6 +3794,11 @@ select{ background-color: rgb(30 64 175 / var(--tw-bg-opacity)); } +:is(.dark .dark\:bg-blue-900) { + --tw-bg-opacity: 1; + background-color: rgb(30 58 138 / var(--tw-bg-opacity)); +} + :is(.dark .dark\:bg-cyan-600) { --tw-bg-opacity: 1; background-color: rgb(8 145 178 / var(--tw-bg-opacity)); @@ -3846,11 +3898,6 @@ select{ background-color: rgb(254 240 138 / var(--tw-bg-opacity)); } -:is(.dark .dark\:bg-blue-900) { - --tw-bg-opacity: 1; - background-color: rgb(30 58 138 / var(--tw-bg-opacity)); -} - :is(.dark .dark\:bg-opacity-75) { --tw-bg-opacity: 0.75; } @@ -3864,6 +3911,11 @@ select{ color: rgb(0 0 0 / var(--tw-text-opacity)); } +:is(.dark .dark\:text-blue-200) { + --tw-text-opacity: 1; + color: rgb(191 219 254 / var(--tw-text-opacity)); +} + :is(.dark .dark\:text-blue-300) { --tw-text-opacity: 1; color: rgb(147 197 253 / var(--tw-text-opacity)); @@ -4024,11 +4076,6 @@ select{ color: rgb(228 228 231 / var(--tw-text-opacity)); } -:is(.dark .dark\:text-blue-200) { - --tw-text-opacity: 1; - color: rgb(191 219 254 / var(--tw-text-opacity)); -} - :is(.dark .dark\:ring-gray-600) { --tw-ring-opacity: 1; --tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity)); @@ -4057,6 +4104,11 @@ select{ border-color: rgb(37 99 235 / var(--tw-border-opacity)); } +:is(.dark .dark\:hover\:border-gray-600:hover) { + --tw-border-opacity: 1; + border-color: rgb(75 85 99 / var(--tw-border-opacity)); +} + :is(.dark .dark\:hover\:border-indigo-700:hover) { --tw-border-opacity: 1; border-color: rgb(67 56 202 / var(--tw-border-opacity)); @@ -4067,6 +4119,11 @@ select{ background-color: rgb(29 78 216 / var(--tw-bg-opacity)); } +:is(.dark .dark\:hover\:bg-blue-800:hover) { + --tw-bg-opacity: 1; + background-color: rgb(30 64 175 / var(--tw-bg-opacity)); +} + :is(.dark .dark\:hover\:bg-blue-900:hover) { --tw-bg-opacity: 1; background-color: rgb(30 58 138 / var(--tw-bg-opacity)); @@ -4132,11 +4189,6 @@ select{ background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } -:is(.dark .dark\:hover\:bg-blue-800:hover) { - --tw-bg-opacity: 1; - background-color: rgb(30 64 175 / var(--tw-bg-opacity)); -} - :is(.dark .dark\:hover\:text-blue-200:hover) { --tw-text-opacity: 1; color: rgb(191 219 254 / var(--tw-text-opacity)); @@ -4152,6 +4204,11 @@ select{ color: rgb(165 243 252 / var(--tw-text-opacity)); } +:is(.dark .dark\:hover\:text-gray-200:hover) { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity)); +} + :is(.dark .dark\:hover\:text-gray-300:hover) { --tw-text-opacity: 1; color: rgb(209 213 219 / var(--tw-text-opacity)); @@ -4397,6 +4454,10 @@ select{ max-width: 65ch; } + .sm\:flex-1 { + flex: 1 1 0%; + } + .sm\:translate-y-0 { --tw-translate-y: 0px; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -4571,10 +4632,18 @@ select{ margin-top: 1.25rem; } + .md\:block { + display: block; + } + .md\:inline { display: inline; } + .md\:inline-flex { + display: inline-flex; + } + .md\:table-cell { display: table-cell; }