This commit is contained in:
Eugene ;) 2022-12-15 20:03:40 +01:00
parent f622fbcf0a
commit 9ef06db411
69 changed files with 5421 additions and 6426 deletions

View File

@ -1,12 +1,12 @@
<CascadingState>
<CascadingAuthenticationState>
<CascadingAuthenticationState>
<CascadingState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView DefaultLayout="@typeof(MainLayout)" RouteData="@routeData">
<NotAuthorized>
@if (context.User.Identity?.IsAuthenticated != true)
{
<RedirectToLogin/>
<RedirectToLogin />
}
else
{
@ -14,7 +14,7 @@
}
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="section"/>
<FocusOnNavigate RouteData="@routeData" Selector="section" />
</Found>
<NotFound>
<Title>Not found</Title>
@ -23,5 +23,5 @@
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
</CascadingState>
</CascadingState>
</CascadingAuthenticationState>

View File

@ -1,4 +1,6 @@
<section class="flex flex-col space-y-4">
@inherits LocalizableComponentBase
<section class="flex flex-col space-y-4">
@if (isPosting)
{
@ -22,7 +24,7 @@
ValueChanged="async v => await OnTimelineChange(v)" ValueExpression="() => Filters.TimelineType">
@foreach (var timelineType in Enum.GetValues<TimelineType>())
{
<option value="@timelineType">@CascadingState.Localizer[timelineType.ToString()]</option>
<option value="@timelineType">@Localizer[timelineType.ToString()]</option>
}
</InputSelect>
</span>
@ -39,7 +41,7 @@
ValueChanged="async v => await OnTimeSortChange(v)" ValueExpression="() => Filters.TimeSortingType">
@foreach (var timeSortingType in Enum.GetValues<TimeSortingType>())
{
<option value="@timeSortingType">@CascadingState.Localizer[timeSortingType.ToString()]</option>
<option value="@timeSortingType">@Localizer[timeSortingType.ToString()]</option>
}
</InputSelect>
</span>
@ -56,7 +58,7 @@
<span class="icon is-left">
<i class="ion-md-create"></i>
</span>
<span>@CascadingState.Localizer["Post"]</span>
<span>@Localizer["Post"]</span>
</button>
</div>
}

View File

@ -1,4 +1,6 @@
<div class="flex space-x-3 w-full neomorph is-nxxsmall rounded-xl @CssContainer">
@inherits LocalizableComponentBase
<div class="flex space-x-3 w-full neomorph is-nxxsmall rounded-xl @CssContainer">
<div class="flex flex-col py-3 pl-3 md:py-4 md:pl-4 flex-none rounded-l-xl bg-cover bg-no-repeat bg-center"
style="background-image:linear-gradient(to left, var(--background), transparent), url('@(Message.User?.BackgroundUrl)');">
@ -17,7 +19,7 @@
<p class="inline-flex flex-1 space-x-2 min-w-0 text-xs text-xs">
<a class="font-bold" href="@Message.BoostingUser.ProfileUrl" title="@Message.BoostingUser.UserName">@Message.BoostingUser.DisplayName</a>
<i class="ion-md-repeat has-text-info" aria-hidden="true"></i>
<span>@CascadingState.Localizer["boosted"]</span>
<span>@Localizer["boosted"]</span>
</p>
}
<p class="inline-flex flex-1 space-x-2 min-w-0 justify-between text-xs md:text-sm">
@ -31,10 +33,10 @@
</span>
<span class="flex-none inline-flex space-x-1 items-center">
<span title="@Message.CreatedAt.ToLocalTime().ToString("📅dd/MM/yy 🕒HH:mm")">
@Message.CreatedAt.GetPassedTime(CascadingState.Localizer)
@Message.CreatedAt.GetPassedTime(Localizer._pLocalizer)
</span>
<i aria-hidden="true" class="@Message.MessageType.GetMessageTypeIcon() text-md"
title="@CascadingState.Localizer[Message.MessageType.ToString()]">
title="@Localizer[Message.MessageType.ToString()]">
</i>
</span>
</p>
@ -105,7 +107,7 @@
@if (OnMessageReply.HasDelegate)
{
<button class="button is-small is-rounded @(isAnswering ? "neoBtnSmallInsetPlain" : "neoBtnSmall")" @onclick="Reply"
title="@CascadingState.Localizer[isAnswering ? "Close" : "Reply"]">
title="@Localizer[isAnswering ? "Close" : "Reply"]">
<span class="icon">
<i aria-hidden="true" class="@SUtility.IfTrueThen(isAnswering, "ion-md-close-circle", "ion-md-text") text-lg has-text-success"></i>
</span>
@ -114,7 +116,7 @@
@if (OnMessageBoost.HasDelegate)
{
<button class="button is-small is-rounded @SUtility.IfTrueThen(Message.IsBoostedByCurrentUser,"neoBtnSmallInsetPlain","neoBtnSmall") @SUtility.IfTrueThen(Message.BoostsCounter != 0, "has-icons-left")" @onclick="() => OnMessageBoost.InvokeAsync(Message)"
title="@CascadingState.Localizer["Boost"]">
title="@Localizer["Boost"]">
<span class="icon is-left">
<i aria-hidden="true" class="ion-md-repeat @SUtility.IfTrueThen(Message.BoostsCounter != 0,null,"text-lg") has-text-info"></i>
</span>
@ -127,7 +129,7 @@
@if (OnMessageFavourite.HasDelegate)
{
<button class="button is-small is-rounded @SUtility.IfTrueThen(Message.IsFavourite, "neoBtnSmallInsetPlain", "neoBtnSmall")" @onclick="() => OnMessageFavourite.InvokeAsync(Message)"
title="@CascadingState.Localizer["Favourite"]">
title="@Localizer["Favourite"]">
<span class="icon">
<i aria-hidden="true" class="@SUtility.IfTrueThen(Message.IsFavourite, "ion-md-heart-dislike", "ion-md-heart") text-lg text-pink-300"></i>
</span>
@ -139,7 +141,7 @@
<DropdownButton IsOpen="Message.IsOptionsOpen">
<DropdownTrigger>
<button class="button is-small is-rounded neoBtnSmall" @onclick="() => Message.IsOptionsOpen = !Message.IsOptionsOpen"
title="@CascadingState.Localizer["Other"]">
title="@Localizer["Other"]">
<span class="icon">
<i aria-hidden="true" class="ion-md-more text-lg"></i>
</span>
@ -153,7 +155,7 @@
<span class="icon is-left">
<i aria-hidden="true" class="ion-md-paper-plane text-base has-text-success"></i>
</span>
<span>@CascadingState.Localizer["Direct message"]</span>
<span>@Localizer["Direct message"]</span>
</button>
</div>
}
@ -164,7 +166,7 @@
<span class="icon is-left">
<i aria-hidden="true" class="ion-md-eye-off text-base drop-shadow has-text-warning"></i>
</span>
<span>@CascadingState.Localizer["Mute"]</span>
<span>@Localizer["Mute"]</span>
</button>
</div>
}
@ -175,7 +177,7 @@
<span class="icon is-left">
<i aria-hidden="true" class="ion-md-remove-circle text-base has-text-danger"></i>
</span>
<span>@CascadingState.Localizer["Block"]</span>
<span>@Localizer["Block"]</span>
</button>
</div>
}
@ -183,11 +185,11 @@
{
<div class="dropdown-item">
<button class="button is-small has-icons-left is-rounded neoBtnSmall" @onclick="DeleteMessage"
title="@CascadingState.Localizer["Delete"]">
title="@Localizer["Delete"]">
<span class="icon is-left">
<i aria-hidden="true" class="ion-md-trash text-lg has-text-danger"></i>
</span>
<span>@CascadingState.Localizer["Delete"]</span>
<span>@Localizer["Delete"]</span>
</button>
</div>
}
@ -196,7 +198,7 @@
@if (IncludeExpand)
{
<NavLink class="button is-small is-rounded neoBtnSmall" href="@($"expand/{Message.MessageId}")"
title="@CascadingState.Localizer["Expand"]">
title="@Localizer["Expand"]">
<span class="icon">
<i aria-hidden="true" class="ion-md-expand text-lg"></i>
</span>

View File

@ -1,13 +1,10 @@
<p class="w-full loadAnimation text-center">
@inherits LocalizableComponentBase
<p class="w-full loadAnimation text-center">
<span>.</span>
<span>.</span>
<span>.</span>
@CascadingState.Localizer["loading"]
@Localizer["loading"]
<span>.</span>
<span>.</span>
<span>.</span>
</p>
@code {
[CascadingParameter] CascadingState CascadingState { get; set; }
}

View File

@ -1,10 +1,12 @@
<EditForm Model="MessageForm" OnValidSubmit="OnValidSubmit">
@inherits LocalizableComponentBase
<EditForm Model="MessageForm" OnValidSubmit="OnValidSubmit">
<DataAnnotationsValidator />
<div class="field mb-3">
<div class="control">
<InputText class="input rounded-t-[1.4rem] rounded-b-lg neoInput" maxlength="64"
placeholder="@CascadingState.Localizer["Title (optional)"]" Value="@MessageForm.Title"
placeholder="@Localizer["Title (optional)"]" Value="@MessageForm.Title"
ValueChanged="(v) => OnTitleChanged(v)"
ValueExpression="() => MessageForm.Title" />
</div>
@ -12,7 +14,7 @@
<textarea @bind="@MessageForm.Content" @bind:event="oninput"
class="textarea rounded-b-[1.4rem] rounded-t-lg neoInput"
maxlength="5000"
placeholder="@CascadingState.Localizer["oh shit... here we go again"]" rows="3"></textarea>
placeholder="@Localizer["oh shit... here we go again"]" rows="3"></textarea>
<span class="absolute text-xs opacity-50 right-2 bottom-1">@(MessageForm.Content?.Length ?? 0)/5000</span>
</div>
@if (showPreviewButton && isPreviewOpen && MessageForm.Content is { Length: > 0 })
@ -52,7 +54,7 @@
<i class="ion-md-image"></i> <b>@media.FileName</b>
</p>
<p class="text-xs break-all">
<i class="ion-md-code"></i> @media.ContentType <i class="ion-md-fitness"></i> @media.Size.GetFileSize(CascadingState.Localizer)
<i class="ion-md-code"></i> @media.ContentType <i class="ion-md-fitness"></i> @media.Size.GetFileSize(Localizer._pLocalizer)
</p>
</div>
<button class="button is-small is-rounded self-start neoBtnSmall" @onclick="() => RemoveFile(media)" type="button">
@ -64,7 +66,7 @@
<div class="field w-full">
<div class="control w-full">
<InputTextArea @bind-Value="media.AltText" class="textarea w-full is-small !rounded-lg neoInput"
placeholder="@CascadingState.Localizer["Alternative text"]" rows="1" />
placeholder="@Localizer["Alternative text"]" rows="1" />
</div>
</div>
</div>
@ -81,7 +83,7 @@
<b>@media.FileName</b>
</p>
<p class="text-xs break-all">
<i class="ion-md-code"></i> @media.ContentType <i class="ion-md-fitness"></i> @media.Size.GetFileSize(CascadingState.Localizer)
<i class="ion-md-code"></i> @media.ContentType <i class="ion-md-fitness"></i> @media.Size.GetFileSize(Localizer._pLocalizer)
</p>
</div>
<button class="button is-small is-rounded neoBtnSmall" @onclick="() => RemoveFile(media)" type="button">
@ -101,7 +103,7 @@
<b>@media.FileName</b>
</p>
<p class="text-xs break-all">
<i class="ion-md-code"></i> @media.ContentType <i class="ion-md-fitness"></i> @media.Size.GetFileSize(CascadingState.Localizer)
<i class="ion-md-code"></i> @media.ContentType <i class="ion-md-fitness"></i> @media.Size.GetFileSize(Localizer._pLocalizer)
</p>
<audio controls="controls" class="w-full max-h-8">
<source src="@media.Base64Preview" type="@media.ContentType">
@ -131,7 +133,7 @@
<DropdownButton CssDirection="is-left" IsOpen="MessageForm.IsScopeOptionsOpen">
<DropdownTrigger>
<button class="button is-small is-rounded @SUtility.IfTrueThen(MessageForm.IsScopeOptionsOpen, "neoBtnSmallInsetPlain", "neoBtnSmall")" @onclick="OpenCloseMessageType"
title="@string.Format(CascadingState.Localizer["Message scope type: {0}"], CascadingState.Localizer[MessageType.Direct.ToString()])" type="button">
title="@string.Format(Localizer["Message scope type: {0}"], Localizer[MessageType.Direct.ToString()])" type="button">
<span class="icon">
<i class="@MessageForm.MessageType.GetMessageTypeIcon() text-base"></i>
</span>
@ -140,25 +142,25 @@
<DropdownContent>
<div class="flex space-x-3 px-2">
<button class="button is-small is-rounded @SUtility.IfTrueThen(MessageForm.MessageType is MessageType.Direct, "neoBtnSmallInsetPlain", "neoBtnSmall")" @onclick="() => UpdateMessageType(MessageType.Direct)"
title="@CascadingState.Localizer[MessageType.Direct.ToString()]" type="button">
title="@Localizer[MessageType.Direct.ToString()]" type="button">
<span class="icon">
<i class="ion-md-paper-plane text-base"></i>
</span>
</button>
<button class="button is-small is-rounded @SUtility.IfTrueThen(MessageForm.MessageType is MessageType.FollowersOnly, "neoBtnSmallInsetPlain", "neoBtnSmall")" @onclick="() => UpdateMessageType(MessageType.FollowersOnly)"
title="@CascadingState.Localizer[MessageType.FollowersOnly.ToString()]" type="button">
title="@Localizer[MessageType.FollowersOnly.ToString()]" type="button">
<span class="icon">
<i class="ion-md-lock text-base"></i>
</span>
</button>
<button class="button is-small is-rounded @SUtility.IfTrueThen(MessageForm.MessageType is MessageType.Unlisted, "neoBtnSmallInsetPlain", "neoBtnSmall")" @onclick="() => UpdateMessageType(MessageType.Unlisted)"
title="@CascadingState.Localizer[MessageType.Unlisted.ToString()]" type="button">
title="@Localizer[MessageType.Unlisted.ToString()]" type="button">
<span class="icon">
<i class="ion-md-unlock text-base"></i>
</span>
</button>
<button class="button is-small is-rounded @SUtility.IfTrueThen(MessageForm.MessageType is MessageType.Public, "neoBtnSmallInsetPlain", "neoBtnSmall")" @onclick="() => UpdateMessageType(MessageType.Public)"
title="@CascadingState.Localizer[MessageType.Public.ToString()]" type="button">
title="@Localizer[MessageType.Public.ToString()]" type="button">
<span class="icon">
<i class="ion-md-globe text-base"></i>
</span>
@ -188,7 +190,7 @@
<InputSelect TValue="MediaType" Value="MessageForm.MediaType" ValueChanged="v => OnMediaTypeChanged(v)" ValueExpression="() => MessageForm.MediaType" disabled="@(MessageForm.Media.Count > 0)">
@foreach (var mediaType in Enum.GetValues<MediaType>())
{
<option value="@mediaType">@CascadingState.Localizer[mediaType.ToString()]</option>
<option value="@mediaType">@Localizer[mediaType.ToString()]</option>
}
</InputSelect>
</div>
@ -204,7 +206,7 @@
<InputSelect TValue="ContentType" Value="MessageForm.ContentType" ValueChanged="v => OnContentTypeChanged(v)" ValueExpression="() => MessageForm.ContentType">
@foreach (var contentType in Enum.GetValues<ContentType>())
{
<option value="@contentType">@CascadingState.Localizer[contentType.ToString()]</option>
<option value="@contentType">@Localizer[contentType.ToString()]</option>
}
</InputSelect>
</div>
@ -220,7 +222,7 @@
@if (showPreviewButton)
{
<button class="button is-small is-rounded @SUtility.IfTrueThen(isPreviewOpen, "neoBtnSmallInsetPlain", "neoBtnSmall")" @onclick="OnOpenClosePreview"
title="@CascadingState.Localizer["Show preview"]" type="button">
title="@Localizer["Show preview"]" type="button">
<span class="icon">
<i class="@SUtility.IfTrueThen(isPreviewOpen, "ion-md-eye-off", "ion-md-eye") text-base"></i>
</span>
@ -232,7 +234,7 @@
<div class="flex space-x-3">
<button class="button is-small is-rounded has-icons-right neoBtnSmall" type="submit">
<span>@CascadingState.Localizer["Post"]</span>
<span>@Localizer["Post"]</span>
<span class="icon is-right">
<i class="ion-md-send"></i>
</span>
@ -312,12 +314,12 @@
};
if (eventArgs.FileCount > maximumFileCount)
{
fileInputErrorMessage = string.Format(CascadingState.Localizer["The maximum number of files accepted is {0}, but {1} were supplied."], maximumFileCount, eventArgs.FileCount);
fileInputErrorMessage = string.Format(Localizer["The maximum number of files accepted is {0}, but {1} were supplied."], maximumFileCount, eventArgs.FileCount);
return;
}
if (eventArgs.FileCount + MessageForm.Media.Count > maximumFileCount)
{
fileInputErrorMessage = string.Format(CascadingState.Localizer["The maximum number of files accepted is {0}, but {1} were supplied."], maximumFileCount, $"{MessageForm.Media.Count}+{eventArgs.FileCount}");
fileInputErrorMessage = string.Format(Localizer["The maximum number of files accepted is {0}, but {1} were supplied."], maximumFileCount, $"{MessageForm.Media.Count}+{eventArgs.FileCount}");
return;
}
var maxAllowedSize = MessageForm.MediaType switch
@ -336,7 +338,7 @@
if (MessageForm.Media.Any(m => m.FileName == file.Name)) continue;
if (file.Size > maxAllowedSize)
{
fileInputErrorMessage += string.Format(CascadingState.Localizer["Supplied file \"{0}\" with size {1:N0}MBs exceeds the maximum of {2:N0}MBs."], file.Name, file.Size / 1_048_576, maxAllowedSize / 1_048_576) + "<br/>";
fileInputErrorMessage += string.Format(Localizer["Supplied file \"{0}\" with size {1:N0}MBs exceeds the maximum of {2:N0}MBs."], file.Name, file.Size / 1_048_576, maxAllowedSize / 1_048_576) + "<br/>";
continue;
}
@ -423,7 +425,7 @@
contentError = default;
if ((MessageForm.Content is { Length: 0 } && MessageForm.Media.Count == 0))
{
contentError = CascadingState.Localizer["Missing content, either message or media"];
contentError = Localizer["Missing content, either message or media"];
return;
}

View File

@ -1,14 +0,0 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
namespace decePubClient.Extensions
{
public class CustomAuthenticationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthenticationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation)
: base(provider, navigation)
{
ConfigureHandler(new string[] { "https://demo.identityserver.io" });
}
}
}

View File

@ -0,0 +1,153 @@
using Blazored.LocalStorage;
using collAnon.Client.Services;
using decePubClient.Models;
using decePubClient.Services;
using Markdig;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using SocialPub.ClientModels;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Text.Json;
using System.Web;
namespace decePubClient.Extensions
{
public static class ExtensionMethods
{
#region Navigation Manager
public static NameValueCollection QueryString(this NavigationManager navigationManager) =>
HttpUtility.ParseQueryString(new Uri(navigationManager.Uri).Query);
public static string QueryString(this NavigationManager navigationManager, string key) =>
navigationManager.QueryString()[key];
public static T QueryString<T>(this NavigationManager navigationManager, string key)
{
var value = navigationManager.QueryString()[key];
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter.IsValid(value))
return (T)Convert.ChangeType(value, typeof(T));
else
return default;
}
#endregion Navigation Manager
#region Setups
public static async Task SetDefaultCulture(this WebAssemblyHost host)
{
var culture = default(CultureInfo);
try
{
var storage = host.Services.GetRequiredService<ILocalStorageService>();
var publicCache = await storage.GetItemAsync<PublicCacheData>(nameof(PublicCacheData));
if (publicCache is null)
{
publicCache = new();
var status = host.Services.GetRequiredService<AppStatusService>();
var browserLanguage = status.Language();
if (browserLanguage.Contains("en"))
browserLanguage = "en-GB";
var appConfig = host.Services.GetRequiredService<AppConfiguration>();
if (!string.IsNullOrEmpty(publicCache.PageSettings.CurrentLanguageCode) &&
publicCache.PageSettings.CurrentLanguageCode.Contains("en"))
publicCache.PageSettings.CurrentLanguageCode = "en-GB";
culture = new(publicCache.PageSettings.CurrentLanguageCode ?? browserLanguage);
publicCache.PageSettings.CurrentLanguageCode = culture.TwoLetterISOLanguageName;
await storage.SetItemAsync(nameof(PublicCacheData), publicCache);
}
culture ??= new(publicCache.PageSettings.CurrentLanguageCode == "en" ? "en-GB" : publicCache.PageSettings.CurrentLanguageCode);
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
}
catch (Exception ex)
{
Console.WriteLine(ex);
culture = new("en-GB");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
}
}
#endregion Setups
public static string GetLogLevelClass(this string logType) => logType switch
{
"Debug" => "has-text-grey",
"Information" => "has-text-info",
"Warning" => "has-text-warning-dark",
"Error" => "has-text-danger",
"Fatal" => "has-text-danger-dark",
_ => string.Empty
};
#region Formats
public static MarkdownPipeline Pipeline =>
new MarkdownPipelineBuilder().UseGenericAttributes().Build();
public static string ToJson(this object value) =>
JsonSerializer.Serialize(value);
public static string ToBase64PngImage(this string base64Image) =>
$"data:image/png;base64,{base64Image}";
#endregion Formats
#region Models
public static string ToString(this WebResult webResult, CoalescingStringLocalizer localizer) =>
localizer["{0} - {1}", webResult.StatusCode, webResult.ErrorMessage];
public static string Timeframe(this DateTime creationDate, CoalescingStringLocalizer localizer)
{
var timeframe = DateTime.Now - creationDate.ToLocalTime();
switch ((int)timeframe.TotalHours)
{
case >= 24:
return localizer["{0} day/s", (int)timeframe.TotalDays];
case >= 1 and < 24:
return localizer["{0} hour/s", (int)timeframe.TotalHours];
case 0:
return localizer["{0} minute/s", (int)timeframe.TotalMinutes];
default:
return string.Empty;
}
}
public static AuthorizationPolicy IsAdminPolicy() =>
new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
.RequireClaim(Policies.IsAdmin, true.ToString().ToLower())
.RequireClaim(Policies.IsUser, true.ToString().ToLower())
.RequireClaim(Policies.IsModerator, true.ToString().ToLower())
.Build();
public static AuthorizationPolicy IsUserPolicy() =>
new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
.RequireClaim(Policies.IsUser, true.ToString().ToLower())
.Build();
public static AuthorizationPolicy IsUserModeratorPolicy() =>
new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
.RequireClaim(Policies.IsModerator, true.ToString().ToLower())
.Build();
#endregion
}
}

View File

@ -25,34 +25,6 @@ namespace decePubClient.Extensions;
public static class GenericExtensions
{
public static NameValueCollection QueryString(this NavigationManager navigationManager) =>
HttpUtility.ParseQueryString(new Uri(navigationManager.Uri).Query);
public static string QueryString(this NavigationManager navigationManager, string key) =>
navigationManager.QueryString()[key];
public static T QueryString<T>(this NavigationManager navigationManager, string key)
{
var value = navigationManager.QueryString()[key];
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter.IsValid(value))
return (T)Convert.ChangeType(value, typeof(T));
else
return default;
}
public static async Task SetDefaultCulture(this WebAssemblyHost host)
{
var storage = host.Services.GetRequiredService<ILocalStorageService>();
var language = await storage.GetItemAsync<string>("languageCode");
if (language is { Length: 0 })
await storage.SetItemAsync("languageCode", language);
var culture = new CultureInfo(language ??= "en-GB");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
}
public static IServiceCollection AddIndexedDb(this IServiceCollection services)
{
return services.AddIndexedDbDatabase<IndexedDb>(options =>
@ -77,7 +49,7 @@ public static class GenericExtensions
});
}
public static string GetPassedTime(this DateTime dateTime, IStringLocalizer<AllStrings> localizer)
public static string GetPassedTime(this DateTime dateTime, IStringLocalizer localizer)
{
var timeframe = DateTime.Now - dateTime.ToLocalTime();
switch ((int)timeframe.TotalHours)

View File

@ -1,52 +1,195 @@
@if (PublicCacheData != null)
{
if (PublicCacheData.ThemeIsDarkMode)
if (PublicCacheData.PageSettings.ThemeIsDarkMode)
{
<Meta Content="@($"hsl({PublicCacheData.ThemeIndexColour},16%,12%)")" Name="theme-color"/>
<Meta Content="@($"hsl({PublicCacheData.ThemeIndexColour},16%,12%)")" Name="background-color"/>
<Meta Content="@($"hsl({PublicCacheData.PageSettings.DarkThemeIndexColour},16%,12%)")" Name="theme-color"/>
<Meta Content="@($"hsl({PublicCacheData.PageSettings.DarkThemeIndexColour},16%,12%)")" Name="background-color"/>
}
else
{
<Meta Content="@($"hsl({PublicCacheData.ThemeIndexColour},84%,88%)")" Name="theme-color"/>
<Meta Content="@($"hsl({PublicCacheData.ThemeIndexColour},84%,88%)")" Name="background-color"/>
<Meta Content="@($"hsl({PublicCacheData.PageSettings.LightThemeIndexColour},84%,88%)")" Name="theme-color"/>
<Meta Content="@($"hsl({PublicCacheData.PageSettings.LightThemeIndexColour},84%,88%)")" Name="background-color"/>
}
}
<style>
:root {
@if (PublicCacheData != null)
{@if (PublicCacheData.ThemeIsDarkMode)
{
@($@"--background: hsl({PublicCacheData.ThemeIndexColour},16%,12%);
--text-color: hsl({PublicCacheData.ThemeIndexColour},16%,73.6%);
--placeholder-text-color: hsla({PublicCacheData.ThemeIndexColour},84%,52.8%,.3);
--primary-color: hsl({PublicCacheData.ThemeIndexColour},16%,12%);
--primary-color-light: hsl({PublicCacheData.ThemeIndexColour},84%,100%);
--primary-color-dark: hsl({PublicCacheData.ThemeIndexColour},16%,33%);
--primary-gradiend-light: hsl({PublicCacheData.ThemeIndexColour},16%,16%);
--primary-gradiend-dark: hsl({PublicCacheData.ThemeIndexColour},16%,8%);
--primary-gradiend-lighter: hsl({PublicCacheData.ThemeIndexColour},16%,20%);
--primary-gradiend-darker: hsl({PublicCacheData.ThemeIndexColour},16%,4%);
--light-shadow: hsla({PublicCacheData.ThemeIndexColour},84%,66%,.1);
--dark-shadow: hsla({PublicCacheData.ThemeIndexColour},16%,1%,.5);")
}
else
{
@($@"--background: hsl({PublicCacheData.ThemeIndexColour},84%,88%);
--text-color: hsl({PublicCacheData.ThemeIndexColour},84%,26.4%);
--placeholder-text-color: hsla({PublicCacheData.ThemeIndexColour},84%,26.4%,.3);
--primary-color: hsl({PublicCacheData.ThemeIndexColour},84%,88%);
--primary-color-light: hsl({PublicCacheData.ThemeIndexColour},84%,100%);
--primary-color-dark: hsl({PublicCacheData.ThemeIndexColour},84%,66%);
--primary-gradiend-light: hsl({PublicCacheData.ThemeIndexColour},84%,92%);
--primary-gradiend-dark: hsl({PublicCacheData.ThemeIndexColour},84%,84%);
--primary-gradiend-lighter: hsl({PublicCacheData.ThemeIndexColour},84%,96%);
--primary-gradiend-darker: hsl({PublicCacheData.ThemeIndexColour},84%,80%);
--light-shadow: hsla({PublicCacheData.ThemeIndexColour},84%,100%,.5);
--dark-shadow: hsla({PublicCacheData.ThemeIndexColour},84%,66%,.5);")
}
}
}
</style>
<HeadContent>
<style>
@if (PublicCacheData != null)
{
var iconsColour = PublicCacheData.PageSettings.IconsThemeIndexColour is >= 0 and <= 359 ?
$"--fa-primary-color: hsl({PublicCacheData.PageSettings.IconsThemeIndexColour},84%,26.4%);--fa-secondary-color: hsl({PublicCacheData.PageSettings.IconsThemeIndexColour},84%,66%);" :
PublicCacheData.PageSettings.IconsThemeIndexColour is -2 ?
$"--fa-primary-color: hsl(0,0%,1%);--fa-secondary-color: hsl(0,0%,40%);" :
$"--fa-primary-color: hsl(0,0%,60%);--fa-secondary-color: hsl(0,0%,99%);";
@if (PublicCacheData.PageSettings.PreferSystemTheming)
{
if (PublicCacheData.PageSettings.ThemeIsDarkGray || PublicCacheData.PageSettings.ThemeIsLightGray)
{
@($@"@media screen and (prefers-color-scheme: light) {{
:root {{
--colour-index: 0;
--background: hsl(0,0%,88%);
--text-color: hsl(0,0%,26.4%);
--primary-color: hsl(0,0%,88%);
--primary-color-light: hsl(0,0%,100%);
--primary-color-dark: hsl(0,0%,66%);
--primary-gradiend-light: hsl(0,0%,92%);
--primary-gradiend-dark: hsl(0,0%,84%);
--primary-gradiend-lighter: hsl(0,0%,96%);
--primary-gradiend-darker: hsl(0,0%,80%);
--light-shadow: hsla(0,0%,100%, .5);
--dark-shadow: hsla(0,0%,66%, .5);
--danger-gradiend-light: hsl(0,0%,92%);
--danger-gradiend-dark: hsl(0,0%,84%);
{iconsColour}
}}
}}
@media screen and (prefers-color-scheme: dark) {{
:root {{
--colour-index: 0;
--background: hsl(0,0%,12%);
--text-color: hsl(0,0%,73.6%);
--primary-color: hsl(0,0%,12%);
--primary-color-light: hsl(0,0%,100%);
--primary-color-dark: hsl(0,0%,33%);
--primary-gradiend-light: hsl(0,0%,16%);
--primary-gradiend-dark: hsl(0,0%,8%);
--primary-gradiend-lighter: hsl(0,0%,20%);
--primary-gradiend-darker: hsl(0,0%,4%);
--light-shadow: hsla(0,0%,66%,.1);
--dark-shadow: hsla(0,0%,1%,.5);
--danger-gradiend-light: hsl(0,16%,16%);
--danger-gradiend-dark: hsl(0,16%,8%);
{iconsColour}
}}
}}")
}
else
{
@($@"@media screen and (prefers-color-scheme: light) {{
:root {{
--colour-index: {PublicCacheData.PageSettings.LightThemeIndexColour};
--background: hsl(var(--colour-index,25),84%,88%);
--text-color: hsl(var(--colour-index,25),84%,26.4%);
--primary-color: hsl(var(--colour-index,25),84%,88%);
--primary-color-light: hsl(var(--colour-index,25),84%,100%);
--primary-color-dark: hsl(var(--colour-index,25),84%,66%);
--primary-gradiend-light: hsl(var(--colour-index,25),84%,92%);
--primary-gradiend-dark: hsl(var(--colour-index,25),84%,84%);
--primary-gradiend-lighter: hsl(var(--colour-index,25),84%,96%);
--primary-gradiend-darker: hsl(var(--colour-index,25),84%,80%);
--light-shadow: hsla(var(--colour-index,25),84%,100%, .5);
--dark-shadow: hsla(var(--colour-index,25),84%,66%, .5);
--danger-gradiend-light: hsl(0,84%,92%);
--danger-gradiend-dark: hsl(0,84%,84%);
{iconsColour}
}}
}}
@media screen and (prefers-color-scheme: dark) {{
:root {{
--colour-index: {PublicCacheData.PageSettings.DarkThemeIndexColour};
--background: hsl(var(--colour-index,215),16%,12%);
--text-color: hsl(var(--colour-index,215),16%,73.6%);
--primary-color: hsl(var(--colour-index,215),16%,12%);
--primary-color-light: hsl(var(--colour-index,215),84%,100%);
--primary-color-dark: hsl(var(--colour-index,215),16%,33%);
--primary-gradiend-light: hsl(var(--colour-index,215),16%,16%);
--primary-gradiend-dark: hsl(var(--colour-index,215),16%,8%);
--primary-gradiend-lighter: hsl(var(--colour-index,215),16%,20%);
--primary-gradiend-darker: hsl(var(--colour-index,215),16%,4%);
--light-shadow: hsla(var(--colour-index,215),84%,66%,.1);
--dark-shadow: hsla(var(--colour-index,215),16%,1%,.5);
--danger-gradiend-light: hsl(0,16%,16%);
--danger-gradiend-dark: hsl(0,16%,8%);
{iconsColour}
}}
}}")
}
}
else
{
if (PublicCacheData.PageSettings.ThemeIsDarkGray || PublicCacheData.PageSettings.ThemeIsLightGray)
{
if (PublicCacheData.PageSettings.ThemeIsDarkGray)
{
@($@":root{{--colour-index: 0;
--background: hsl(0,0%,12%);
--text-color: hsl(0,0%,73.6%);
--primary-color: hsl(0,0%,12%);
--primary-color-light: hsl(0,0%,100%);
--primary-color-dark: hsl(0,0%,33%);
--primary-gradiend-light: hsl(0,0%,16%);
--primary-gradiend-dark: hsl(0,0%,8%);
--primary-gradiend-lighter: hsl(0,0%,20%);
--primary-gradiend-darker: hsl(0,0%,4%);
--light-shadow: hsla(0,0%,66%,.1);
--dark-shadow: hsla(0,0%,1%,.5);
--danger-gradiend-light: hsl(0,16%,16%);
--danger-gradiend-dark: hsl(0,16%,8%);{iconsColour}}}")
}
else
{
@($@":root{{--colour-index: 0;
--background: hsl(0,0%,88%);
--text-color: hsl(0,0%,26.4%);
--primary-color: hsl(0,0%,88%);
--primary-color-light: hsl(0,0%,100%);
--primary-color-dark: hsl(0,0%,66%);
--primary-gradiend-light: hsl(0,0%,92%);
--primary-gradiend-dark: hsl(0,0%,84%);
--primary-gradiend-lighter: hsl(0,0%,96%);
--primary-gradiend-darker: hsl(0,0%,80%);
--light-shadow: hsla(0,0%,100%, .5);
--dark-shadow: hsla(0,0%,66%, .5);
--danger-gradiend-light: hsl(0,84%,92%);
--danger-gradiend-dark: hsl(0,84%,84%);{iconsColour}}}")
}
}
else
{
if (PublicCacheData.PageSettings.ThemeIsDarkMode)
{
@($@":root{{--colour-index: {PublicCacheData.PageSettings.DarkThemeIndexColour};
--background: hsl(var(--colour-index,215),16%,12%);
--text-color: hsl(var(--colour-index,215),16%,73.6%);
--primary-color: hsl(var(--colour-index,215),16%,12%);
--primary-color-light: hsl(var(--colour-index,215),84%,100%);
--primary-color-dark: hsl(var(--colour-index,215),16%,33%);
--primary-gradiend-light: hsl(var(--colour-index,215),16%,16%);
--primary-gradiend-dark: hsl(var(--colour-index,215),16%,8%);
--primary-gradiend-lighter: hsl(var(--colour-index,215),16%,20%);
--primary-gradiend-darker: hsl(var(--colour-index,215),16%,4%);
--light-shadow: hsla(var(--colour-index,215),84%,66%,.1);
--dark-shadow: hsla(var(--colour-index,215),16%,1%,.5);
--danger-gradiend-light: hsl(0,16%,16%);
--danger-gradiend-dark: hsl(0,16%,8%);{iconsColour}}}")
}
else
{
@($@":root{{--colour-index: {PublicCacheData.PageSettings.LightThemeIndexColour};
--background: hsl(var(--colour-index,25),84%,88%);
--text-color: hsl(var(--colour-index,25),84%,26.4%);
--primary-color: hsl(var(--colour-index,25),84%,88%);
--primary-color-light: hsl(var(--colour-index,25),84%,100%);
--primary-color-dark: hsl(var(--colour-index,25),84%,66%);
--primary-gradiend-light: hsl(var(--colour-index,25),84%,92%);
--primary-gradiend-dark: hsl(var(--colour-index,25),84%,84%);
--primary-gradiend-lighter: hsl(var(--colour-index,25),84%,96%);
--primary-gradiend-darker: hsl(var(--colour-index,25),84%,80%);
--light-shadow: hsla(var(--colour-index,25),84%,100%, .5);
--dark-shadow: hsla(var(--colour-index,25),84%,66%, .5);
--danger-gradiend-light: hsl(0,84%,92%);
--danger-gradiend-dark: hsl(0,84%,84%);{iconsColour}}}")
}
}
}
}
</style>
</HeadContent>
<CascadingValue IsFixed=false Value=this>
@ChildContent
@ -54,7 +197,6 @@
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Inject] public IStringLocalizer<AllStrings> Localizer { get; set; }
[Inject] IStorage DbStorage { get; set; }
[Inject] ILocalStorageService Storage { get; set; }
[Inject] IJSRuntime JS { get; set; }

View File

@ -0,0 +1,12 @@
using collAnon.Client.Services;
using Microsoft.AspNetCore.Components;
namespace decePubClient.LayerComponents
{
public class LocalizableComponentBase : ComponentBase
{
[Inject] public CoalescingStringLocalizer Localizer { get; set; }
protected List<Func<Task>> AfterRenderAsyncJobs = new();
protected bool IsLoading { get; set; } = true;
}
}

View File

@ -1,9 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace decePubClient.LayerComponents
namespace decePubClient.LayerComponents
{
public class PagesBase : ComponentBase
public class PagesBase : LocalizableComponentBase
{
public bool IsLoading { get; set; } = true;
}
}

28
Models/PageSettings.cs Normal file
View File

@ -0,0 +1,28 @@

using SocialPub.ClientModels.Resources;
using System.ComponentModel.DataAnnotations;
namespace decePubClient.Models
{
public class PageSettings
{
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)),
Display(Name = "LanguageId", ResourceType = typeof(FieldsNameResource))]
public string CurrentLanguageCode { get; set; } = "en";
//public bool NativeNotificationsEnabled { get; set; } = false;
[Range(0, 359, ErrorMessageResourceName = nameof(Range), ErrorMessageResourceType = typeof(ErrorsResource))]
public short LightThemeIndexColour { get; set; } = 25;
[Range(0, 359, ErrorMessageResourceName = nameof(Range), ErrorMessageResourceType = typeof(ErrorsResource))]
public short DarkThemeIndexColour { get; set; } = 215;
[Range(-2, 359, ErrorMessageResourceName = nameof(Range), ErrorMessageResourceType = typeof(ErrorsResource))]
public short IconsThemeIndexColour { get; set; } = 25;
public bool PreferSystemTheming { get; set; } = true;
public bool ThemeIsDarkMode { get; set; } = false;
public bool ThemeIsLightGray { get; set; } = true;
public bool ThemeIsDarkGray { get; set; } = false;
public bool ShowDonatorBadge { get; set; } = false;
}
}

View File

@ -1,16 +1,17 @@
using System.Collections.ObjectModel;
using BlazorZXingJs;
using System.Collections.ObjectModel;
namespace decePubClient.Models
{
public class PublicCacheData
{
public IReadOnlyList<ViewLanguage> Languages { get; set; } = new List<ViewLanguage>();
public List<MediaDeviceInfo> QrCodeDevices { get; set; } = new();
public string LastPage { get; set; }
public string CurrentSubPage { get; set; }
public PageSettings PageSettings { get; set; } = new();
public bool ShowLogs { get; set; } = false;
public IReadOnlyDictionary<string, string> Policies { get; set; } = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
public string CurrentLanguageCode { get; set; } = "en-GB";
public bool NativeNotificationsEnabled { get; set; } = false;
public short ThemeIndexColour { get; set; } = 25;
public bool ThemeIsDarkMode { get; set; } = false;
}
}

View File

@ -1,7 +1,7 @@
@page "/administration"
@inherits PagesBase
@inherits LocalizableComponentBase
<Title>@CascadingState.Localizer["Settings"]</Title>
<Title>@Localizer["Settings"]</Title>
<section class="block relative w-full h-full neomorphInset is-nxsmall rounded-xl">
@ -10,13 +10,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Users"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Users"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -25,13 +25,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Reports"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Reports"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -40,13 +40,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["ActivityPub?"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["ActivityPub?"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -55,13 +55,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Authentication"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Authentication"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -70,13 +70,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Emoji?"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Emoji?"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -85,13 +85,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Frontend"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Frontend"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -100,13 +100,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Instance"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Instance"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -115,13 +115,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Link formatter"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Link formatter"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -130,13 +130,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Metadata"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Metadata"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -145,13 +145,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Policies"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Policies"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -160,13 +160,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-albums text-xl"></i> <span>@CascadingState.Localizer["Upload"]</span>
<i class="ion-md-albums text-xl"></i> <span>@Localizer["Upload"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>

View File

@ -1,7 +1,6 @@
@page "/expand"
@page "/expand/{messageId}"
@inherits PagesBase
<Title>@CascadingState.Localizer</Title>
@inherits LocalizableComponentBase
<div class="flex w-full h-full flex-col space-y-4">

View File

@ -1,8 +1,8 @@
@page "/"
@page "/home"
@page "/index"
@inherits PagesBase
<Title>@CascadingState.Localizer["Index"]</Title>
@inherits LocalizableComponentBase
<Title>@Localizer["Index"]</Title>
<div class="flex w-full h-full flex-col space-y-4">
@ -16,7 +16,7 @@
else if (Messages.Count == 0)
{
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
}

View File

@ -1,6 +1,6 @@
@page "/login"
@inherits PagesBase
<Title>@CascadingState.Localizer["Login"]</Title>
@inherits LocalizableComponentBase
<Title>@Localizer["Login"]</Title>
<section class="block relative w-full h-full neomorphInset is-nxsmall rounded-xl">

View File

@ -1,11 +1,11 @@
@page "/logout"
@inherits PagesBase
<Title>@CascadingState.Localizer["Logout"]</Title>
@inherits LocalizableComponentBase
<Title>@Localizer["Logout"]</Title>
<section class="block relative w-full h-full neomorphInset is-nxsmall rounded-xl">
<div class="flex flex-col space-y-4 p-4 md:p-5 w-full h-full absolute overflow-y-auto">
<h1 class="text-center">@CascadingState.Localizer["Logging out..."]</h1>
<h1 class="text-center">@Localizer["Logging out..."]</h1>
</div>
</section>

View File

@ -1,6 +1,6 @@
@page "/settings"
@inherits PagesBase
<Title>@CascadingState.Localizer["Settings"]</Title>
@inherits LocalizableComponentBase
<Title>@Localizer["Settings"]</Title>
<section class="block relative w-full h-full neomorphInset is-nxsmall rounded-xl">
@ -9,13 +9,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-cog text-xl"></i> <span>@CascadingState.Localizer["General"]</span>
<i class="ion-md-cog text-xl"></i> <span>@Localizer["General"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -24,13 +24,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-person text-xl"></i> <span>@CascadingState.Localizer["Profile"]</span>
<i class="ion-md-person text-xl"></i> <span>@Localizer["Profile"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -39,13 +39,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-archive text-xl"></i> <span>@CascadingState.Localizer["Data import/export"]</span>
<i class="ion-md-archive text-xl"></i> <span>@Localizer["Data import/export"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>
@ -54,13 +54,13 @@
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-eye-off text-xl"></i> <span>@CascadingState.Localizer["Mutes/Blocks"]</span>
<i class="ion-md-eye-off text-xl"></i> <span>@Localizer["Mutes/Blocks"]</span>
</p>
</TitleChildren>
<InnerContent>
<div class="block w-full p-3 md:p-4">
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
<i class="ion-ios-remove-circle-outline"></i> @Localizer["Empty"]
</p>
</div>
</InnerContent>

View File

@ -1,73 +1,62 @@
using decePubClient;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using System.Net.Http.Headers;
using System.Text.Json;
using Append.Blazor.Notifications;
using Blazored.LocalStorage;
using Blazored.Modal;
using decePubClient.Extensions;
using decePubClient.Models;
using decePubClient.Services;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Toolbelt.Blazor.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using SocialPub.ClientModels;
using collAnon.Client.Services;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
// builder.RootComponents.Add<HeadOutlet>("head::after");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddBlazorDownloadFile();
builder.Services.AddApiAuthorization();
builder.Services.AddOptions()
.AddAuthorizationCore(options =>
{
options.AddPolicy(Policies.IsAdmin, ExtensionMethods.IsAdminPolicy());
options.AddPolicy(Policies.IsUser, ExtensionMethods.IsUserPolicy());
options.AddPolicy(Policies.IsModerator, ExtensionMethods.IsUserModeratorPolicy());
})
.AddTransient<AppStatusService>();
builder.Services.AddBlazoredLocalStorage(config =>
{
config.JsonSerializerOptions.WriteIndented = false;
config.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
config.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
config.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
})
.AddBlazoredModal()
//.AddScoped<TokenAuthStateProvider>()
.AddScoped<CustomAuthenticationMessageHandler>()
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>,
ApiAuthorizationOptionsConfiguration>());
builder.Services.AddBlazoredLocalStorage(config =>
{
config.JsonSerializerOptions.WriteIndented = false;
config.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
config.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
config.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
})
.AddScoped<TokenAuthStateProvider>()
.AddScoped<MessagesService>()
//.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<TokenAuthStateProvider>())
.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<TokenAuthStateProvider>())
.AddSingleton(typeof(CoalescingStringLocalizer))
//.AddTransient<IHttpService, HttpService>()
.AddHeadElementHelper(options => options.DisableClientScriptAutoInjection = true)
.AddLocalization()
.AddNotifications()
.AddTransient<IStorage, Storage>()
.AddLogging(lb => lb.SetMinimumLevel(LogLevel.Debug))
.AddIndexedDb();
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Local", options.ProviderOptions);
options.ProviderOptions.Authority = "https://demo.identityserver.io";
options.ProviderOptions.ClientId = "interactive.public";
options.ProviderOptions.ResponseType = "code";
options.ProviderOptions.DefaultScopes.Add("api");
options.ProviderOptions.DefaultScopes.Add("email");
options.ProviderOptions.DefaultScopes.Add("profile");
});
builder.Services.AddHttpClient("default", client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
});
builder.Services.AddHttpClient("ComponentsWebAssembly_CSharp.ServerAPI", client =>
{
client.BaseAddress = new Uri("https://demo.identityserver.io");
})
//.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>()
.AddHttpMessageHandler<CustomAuthenticationMessageHandler>();
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ComponentsWebAssembly_CSharp.ServerAPI"));
builder.Services.AddScoped<MessagesService>();
builder.Services.AddSingleton(serviceProvider =>
{
var conf = serviceProvider.GetRequiredService<IConfiguration>();

7
SCSS/bulma/bulma.sass vendored Normal file
View File

@ -0,0 +1,7 @@
@charset "utf-8"
/*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */
@import "sass/utilities/_all"
@import "sass/base/_all"
@import "sass/elements/_all"
@import "sass/form/_all"
@import "sass/components/_all"

View File

@ -0,0 +1,5 @@
/* Bulma Base */
@charset "utf-8"
@import "generic"
@import "animations"

View File

@ -0,0 +1,5 @@
@keyframes spinAround
from
transform: rotate(0deg)
to
transform: rotate(359deg)

View File

@ -0,0 +1,145 @@
@import "../utilities/mixins"
$body-background-color: $scheme-main !default
$body-size: 16px !default
$body-min-width: 300px !default
$body-rendering: optimizeLegibility !default
$body-family: $family-primary !default
$body-overflow-x: hidden !default
$body-overflow-y: scroll !default
$body-color: $text !default
$body-font-size: 1em !default
$body-weight: $weight-normal !default
$body-line-height: 1.5 !default
$code-family: $family-code !default
$code-padding: 0.25em 0.5em 0.25em !default
$code-weight: normal !default
$code-size: 0.875em !default
$small-font-size: 0.875em !default
$hr-background-color: $background !default
$hr-height: 2px !default
$hr-margin: 1.5rem 0 !default
$strong-color: $text-strong !default
$strong-weight: $weight-bold !default
$pre-font-size: 0.875em !default
$pre-padding: 1.25rem 1.5rem !default
$pre-code-font-size: 1em !default
html
background-color: $body-background-color
font-size: $body-size
-moz-osx-font-smoothing: grayscale
-webkit-font-smoothing: antialiased
min-width: $body-min-width
overflow-x: $body-overflow-x
overflow-y: $body-overflow-y
text-rendering: $body-rendering
text-size-adjust: 100%
article,
aside,
figure,
footer,
header,
hgroup,
section
display: block