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

@ -1,5 +1,5 @@
<CascadingState>
<CascadingAuthenticationState>
<CascadingAuthenticationState>
<CascadingState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView DefaultLayout="@typeof(MainLayout)" RouteData="@routeData">
@ -23,5 +23,5 @@
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
</CascadingState>
</CascadingAuthenticationState>

@ -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>
}

@ -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>

@ -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; }
}

@ -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;
}

@ -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" });
}
}
}

@ -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
}
}

@ -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)

@ -1,60 +1,202 @@
@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"/>
}
}
<HeadContent>
<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);")
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
{
@($@"--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);")
@($@"@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
</CascadingValue>
@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; }

@ -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;
}
}

@ -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

@ -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;
}
}

@ -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;
}
}

@ -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>

@ -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">

@ -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>
}

@ -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">

@ -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>

@ -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>

@ -1,26 +1,37 @@
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.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>,
ApiAuthorizationOptionsConfiguration>());
builder.Services.AddBlazoredLocalStorage(config =>
{
config.JsonSerializerOptions.WriteIndented = false;
@ -29,45 +40,23 @@ builder.Services.AddBlazoredLocalStorage(config =>
config.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
})
.AddBlazoredModal()
//.AddScoped<TokenAuthStateProvider>()
.AddScoped<CustomAuthenticationMessageHandler>()
.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

@ -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"

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

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

@ -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
body,
button,
input,
optgroup,
select,
textarea
font-family: $body-family
code,
pre
-moz-osx-font-smoothing: auto
-webkit-font-smoothing: auto
font-family: $code-family
body
color: $body-color
font-size: $body-font-size
font-weight: $body-weight
line-height: $body-line-height
// Inline
a
color: $link
cursor: pointer
text-decoration: none
strong
color: currentColor
&:hover
color: $link-hover
code
background-color: $code-background
color: $code
font-size: $code-size
font-weight: $code-weight
padding: $code-padding
hr
background-color: $hr-background-color
border: none
display: block
height: $hr-height
margin: $hr-margin
img
height: auto
max-width: 100%
input[type="checkbox"],
input[type="radio"]
vertical-align: baseline
small
font-size: $small-font-size
span
font-style: inherit
font-weight: inherit
strong
color: $strong-color
font-weight: $strong-weight
// Block
fieldset
border: none
pre
+overflow-touch
background-color: $pre-background
color: $pre
font-size: $pre-font-size
overflow-x: auto
padding: $pre-padding
white-space: pre
word-wrap: normal
code
background-color: transparent
color: currentColor
font-size: $pre-code-font-size
padding: 0
table
td,
th
vertical-align: top
&:not([align])
text-align: inherit
th
color: $text-strong

@ -0,0 +1,6 @@
/* Bulma Components */
@charset "utf-8"
@import "dropdown"
@import "message"
@import "pagination"

@ -0,0 +1,83 @@
@import "../utilities/mixins"
$dropdown-menu-min-width: 12rem !default
$dropdown-content-background-color: $scheme-main !default
$dropdown-content-arrow: $link !default
$dropdown-content-offset: 4px !default
$dropdown-content-padding-bottom: 0.5rem !default
$dropdown-content-padding-top: 0.5rem !default
$dropdown-content-radius: $radius !default
$dropdown-content-shadow: $shadow !default
$dropdown-content-z: 20 !default
$dropdown-item-color: $text !default
$dropdown-item-hover-color: $scheme-invert !default
$dropdown-item-hover-background-color: $background !default
$dropdown-item-active-color: $link-invert !default
$dropdown-item-active-background-color: $link !default
$dropdown-divider-background-color: $border-light !default
.dropdown
display: inline-flex
position: relative
vertical-align: top
&.is-active,
&.is-hoverable:hover
.dropdown-menu
display: block
&.is-right
.dropdown-menu
left: auto
right: 0
&.is-up
.dropdown-menu
bottom: 100%
padding-bottom: $dropdown-content-offset
padding-top: initial
top: auto
.dropdown-menu
display: none
+ltr-position(0, false)
min-width: $dropdown-menu-min-width
padding-top: $dropdown-content-offset
position: absolute
top: 100%
z-index: $dropdown-content-z
.dropdown-content
background-color: $dropdown-content-background-color
border-radius: $dropdown-content-radius
box-shadow: $dropdown-content-shadow
padding-bottom: $dropdown-content-padding-bottom
padding-top: $dropdown-content-padding-top
.dropdown-item
color: $dropdown-item-color
display: block
font-size: 0.875rem
line-height: 1.5
padding: 0.375rem 1rem
position: relative
a.dropdown-item,
button.dropdown-item
+ltr-property("padding", 3rem)
text-align: inherit
white-space: nowrap
width: 100%
&:hover
background-color: $dropdown-item-hover-background-color
color: $dropdown-item-hover-color
&.is-active
background-color: $dropdown-item-active-background-color
color: $dropdown-item-active-color
.dropdown-divider
background-color: $dropdown-divider-background-color
border: none
display: block
height: 1px
margin: 0.5rem 0

@ -0,0 +1,101 @@
@import "../utilities/mixins"
$message-background-color: $background !default
$message-radius: $radius !default
$message-header-background-color: $text !default
$message-header-color: $text-invert !default
$message-header-weight: $weight-bold !default
$message-header-padding: 0.75em 1em !default
$message-header-radius: $radius !default
$message-body-border-color: $border !default
$message-body-border-width: 0 0 0 4px !default
$message-body-color: $text !default
$message-body-padding: 1.25em 1.5em !default
$message-body-radius: $radius !default
$message-body-pre-background-color: $scheme-main !default
$message-body-pre-code-background-color: transparent !default
$message-header-body-border-width: 0 !default
$message-colors: $colors !default
.message
@extend %block
background-color: $message-background-color
border-radius: $message-radius
font-size: $size-normal
strong
color: currentColor
a:not(.button):not(.tag):not(.dropdown-item)
color: currentColor
text-decoration: underline
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large
// Colors
@each $name, $components in $message-colors
$color: nth($components, 1)
$color-invert: nth($components, 2)
$color-light: null
$color-dark: null
@if length($components) >= 3
$color-light: nth($components, 3)
@if length($components) >= 4
$color-dark: nth($components, 4)
@else
$color-luminance: colorLuminance($color)
$darken-percentage: $color-luminance * 70%
$desaturate-percentage: $color-luminance * 30%
$color-dark: desaturate(darken($color, $darken-percentage), $desaturate-percentage)
@else
$color-lightning: max((100% - lightness($color)) - 2%, 0%)
$color-light: lighten($color, $color-lightning)
&.is-#{$name}
background-color: $color-light
.message-header
background-color: $color
color: $color-invert
.message-body
border-color: $color
color: $color-dark
.message-header
align-items: center
background-color: $message-header-background-color
border-radius: $message-header-radius $message-header-radius 0 0
color: $message-header-color
display: flex
font-weight: $message-header-weight
justify-content: space-between
line-height: 1.25
padding: $message-header-padding
position: relative
.delete
flex-grow: 0
flex-shrink: 0
+ltr-property("margin", 0.75em, false)
& + .message-body
border-width: $message-header-body-border-width
border-top-left-radius: 0
border-top-right-radius: 0
.message-body
border-color: $message-body-border-color
border-radius: $message-body-radius
border-style: solid
border-width: $message-body-border-width
color: $message-body-color
padding: $message-body-padding
code,
pre
background-color: $message-body-pre-background-color
pre code
background-color: $message-body-pre-code-background-color

@ -0,0 +1,167 @@
@import "../utilities/controls"
@import "../utilities/mixins"
$pagination-color: $text-strong !default
$pagination-border-color: $border !default
$pagination-margin: -0.25rem !default
$pagination-min-width: $control-height !default
$pagination-item-font-size: 1em !default
$pagination-item-margin: 0.25rem !default
$pagination-item-padding-left: 0.5em !default
$pagination-item-padding-right: 0.5em !default
$pagination-nav-padding-left: 0.75em !default
$pagination-nav-padding-right: 0.75em !default
$pagination-hover-color: $link-hover !default
$pagination-hover-border-color: $link-hover-border !default
$pagination-focus-color: $link-focus !default
$pagination-focus-border-color: $link-focus-border !default
$pagination-active-color: $link-active !default
$pagination-active-border-color: $link-active-border !default
$pagination-disabled-color: $text-light !default
$pagination-disabled-background-color: $border !default
$pagination-disabled-border-color: $border !default
$pagination-current-color: $link-invert !default
$pagination-current-background-color: $link !default
$pagination-current-border-color: $link !default
$pagination-ellipsis-color: $grey-light !default
$pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2) !default
.pagination
@extend %block
font-size: $size-normal
margin: $pagination-margin
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large
&.is-rounded
.pagination-previous,
.pagination-next
padding-left: 1em
padding-right: 1em
border-radius: $radius-rounded
.pagination-link
border-radius: $radius-rounded
.pagination,
.pagination-list
align-items: center
display: flex
justify-content: center
text-align: center
.pagination-previous,
.pagination-next,
.pagination-link,
.pagination-ellipsis
@extend %control
@extend %unselectable
font-size: $pagination-item-font-size
justify-content: center
margin: $pagination-item-margin
padding-left: $pagination-item-padding-left
padding-right: $pagination-item-padding-right
text-align: center
.pagination-previous,
.pagination-next,
.pagination-link
border-color: $pagination-border-color
color: $pagination-color
min-width: $pagination-min-width
&:hover
border-color: $pagination-hover-border-color
color: $pagination-hover-color
&:focus
border-color: $pagination-focus-border-color
&:active
box-shadow: $pagination-shadow-inset
&[disabled],
&.is-disabled
background-color: $pagination-disabled-background-color
border-color: $pagination-disabled-border-color
box-shadow: none
color: $pagination-disabled-color
opacity: 0.5
.pagination-previous,
.pagination-next
padding-left: $pagination-nav-padding-left
padding-right: $pagination-nav-padding-right
white-space: nowrap
.pagination-link
&.is-current
background-color: $pagination-current-background-color
border-color: $pagination-current-border-color
color: $pagination-current-color
.pagination-ellipsis
color: $pagination-ellipsis-color
pointer-events: none
.pagination-list
flex-wrap: wrap
li
list-style: none
+mobile
.pagination
flex-wrap: wrap
.pagination-previous,
.pagination-next
flex-grow: 1
flex-shrink: 1
.pagination-list
li
flex-grow: 1
flex-shrink: 1
+tablet
.pagination-list
flex-grow: 1
flex-shrink: 1
justify-content: flex-start
order: 1
.pagination-previous,
.pagination-next,
.pagination-link,
.pagination-ellipsis
margin-bottom: 0
margin-top: 0
.pagination-previous
order: 2
.pagination-next
order: 3
.pagination
justify-content: space-between
margin-bottom: 0
margin-top: 0
&.is-centered
.pagination-previous
order: 1
.pagination-list
justify-content: center
order: 2
.pagination-next
order: 3
&.is-right
.pagination-previous
order: 1
.pagination-next
order: 2
.pagination-list
justify-content: flex-end
order: 3

@ -0,0 +1,12 @@
/* Bulma Elements */
@charset "utf-8"
@import "button"
@import "content"
@import "icon"
@import "image"
@import "notification"
@import "progress"
@import "tag"
@import "other"

@ -0,0 +1,357 @@
@import "../utilities/controls"
@import "../utilities/mixins"
$button-color: $text-strong !default
$button-background-color: $scheme-main !default
$button-family: false !default
$button-border-color: $border !default
$button-border-width: $control-border-width !default
$button-padding-vertical: calc(0.5em - #{$button-border-width}) !default
$button-padding-horizontal: 1em !default
$button-hover-color: $link-hover !default
$button-hover-border-color: $link-hover-border !default
$button-focus-color: $link-focus !default
$button-focus-border-color: $link-focus-border !default
$button-focus-box-shadow-size: 0 0 0 0.125em !default
$button-focus-box-shadow-color: bulmaRgba($link, 0.25) !default
$button-active-color: $link-active !default
$button-active-border-color: $link-active-border !default
$button-text-color: $text !default
$button-text-decoration: underline !default
$button-text-hover-background-color: $background !default
$button-text-hover-color: $text-strong !default
$button-ghost-background: none !default
$button-ghost-border-color: transparent !default
$button-ghost-color: $link !default
$button-ghost-decoration: none !default
$button-ghost-hover-color: $link !default
$button-ghost-hover-decoration: underline !default
$button-disabled-background-color: $scheme-main !default
$button-disabled-border-color: $border !default
$button-disabled-shadow: none !default
$button-disabled-opacity: 0.5 !default
$button-static-color: $text-light !default
$button-static-background-color: $scheme-main-ter !default
$button-static-border-color: $border !default
$button-colors: $colors !default
$button-responsive-sizes: ("mobile": ("small": ($size-small * 0.75), "normal": ($size-small * 0.875), "medium": $size-small, "large": $size-normal), "tablet-only": ("small": ($size-small * 0.875), "normal": ($size-small), "medium": $size-normal, "large": $size-medium)) !default
// The button sizes use mixins so they can be used at different breakpoints
=button-small
&:not(.is-rounded)
border-radius: $radius-small
font-size: $size-small
=button-normal
font-size: $size-normal
=button-medium
font-size: $size-medium
=button-large
font-size: $size-large
.button
@extend %control
@extend %unselectable
background-color: $button-background-color
border-color: $button-border-color
border-width: $button-border-width
color: $button-color
cursor: pointer
@if $button-family
font-family: $button-family
justify-content: center
padding-bottom: $button-padding-vertical
padding-left: $button-padding-horizontal
padding-right: $button-padding-horizontal
padding-top: $button-padding-vertical
text-align: center
white-space: nowrap
strong
color: inherit
.icon
&,
&.is-small,
&.is-medium,
&.is-large
height: 1.5em
width: 1.5em
&:first-child:not(:last-child)
+ltr-property("margin", calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width}), false)
+ltr-property("margin", $button-padding-horizontal * 0.25)
&:last-child:not(:first-child)
+ltr-property("margin", $button-padding-horizontal * 0.25, false)
+ltr-property("margin", calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width}))
&:first-child:last-child
margin-left: calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width})
margin-right: calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width})
// States
&:hover,
&.is-hovered
border-color: $button-hover-border-color
color: $button-hover-color
&:focus,
&.is-focused
border-color: $button-focus-border-color
color: $button-focus-color
&:not(:active)
box-shadow: $button-focus-box-shadow-size $button-focus-box-shadow-color
&:active,
&.is-active
border-color: $button-active-border-color
color: $button-active-color
// Colors
&.is-text
background-color: transparent
border-color: transparent
color: $button-text-color
text-decoration: $button-text-decoration
&:hover,
&.is-hovered,
&:focus,
&.is-focused
background-color: $button-text-hover-background-color
color: $button-text-hover-color
&:active,
&.is-active
background-color: bulmaDarken($button-text-hover-background-color, 5%)
color: $button-text-hover-color
&[disabled],
fieldset[disabled] &
background-color: transparent
border-color: transparent
box-shadow: none
&.is-ghost
background: $button-ghost-background
border-color: $button-ghost-border-color
color: $button-ghost-color
text-decoration: $button-ghost-decoration
&:hover,
&.is-hovered
color: $button-ghost-hover-color
text-decoration: $button-ghost-hover-decoration
@each $name, $pair in $button-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
background-color: $color
border-color: transparent
color: $color-invert
&:hover,
&.is-hovered
background-color: bulmaDarken($color, 2.5%)
border-color: transparent
color: $color-invert
&:focus,
&.is-focused
border-color: transparent
color: $color-invert
&:not(:active)
box-shadow: $button-focus-box-shadow-size bulmaRgba($color, 0.25)
&:active,
&.is-active
background-color: bulmaDarken($color, 5%)
border-color: transparent
color: $color-invert
&[disabled],
fieldset[disabled] &
background-color: $color
border-color: $color
box-shadow: none
&.is-inverted
background-color: $color-invert
color: $color
&:hover,
&.is-hovered
background-color: bulmaDarken($color-invert, 5%)
&[disabled],
fieldset[disabled] &
background-color: $color-invert
border-color: transparent
box-shadow: none
color: $color
&.is-loading
&::after
border-color: transparent transparent $color-invert $color-invert !important
&.is-outlined
background-color: transparent
border-color: $color
color: $color
&:hover,
&.is-hovered,
&:focus,
&.is-focused
background-color: $color
border-color: $color
color: $color-invert
&.is-loading
&::after
border-color: transparent transparent $color $color !important
&:hover,
&.is-hovered,
&:focus,
&.is-focused
&::after
border-color: transparent transparent $color-invert $color-invert !important
&[disabled],
fieldset[disabled] &
background-color: transparent
border-color: $color
box-shadow: none
color: $color
&.is-inverted.is-outlined
background-color: transparent
border-color: $color-invert
color: $color-invert
&:hover,
&.is-hovered,
&:focus,
&.is-focused
background-color: $color-invert
color: $color
&.is-loading
&:hover,
&.is-hovered,
&:focus,
&.is-focused
&::after
border-color: transparent transparent $color $color !important
&[disabled],
fieldset[disabled] &
background-color: transparent
border-color: $color-invert
box-shadow: none
color: $color-invert
// If light and dark colors are provided
@if length($pair) >= 4
$color-light: nth($pair, 3)
$color-dark: nth($pair, 4)
&.is-light
background-color: $color-light
color: $color-dark
&:hover,
&.is-hovered
background-color: bulmaDarken($color-light, 2.5%)
border-color: transparent
color: $color-dark
&:active,
&.is-active
background-color: bulmaDarken($color-light, 5%)
border-color: transparent
color: $color-dark
// Sizes
&.is-small
+button-small
&.is-normal
+button-normal
&.is-medium
+button-medium
&.is-large
+button-large
// Modifiers
&[disabled],
fieldset[disabled] &
background-color: $button-disabled-background-color
border-color: $button-disabled-border-color
box-shadow: $button-disabled-shadow
opacity: $button-disabled-opacity
&.is-fullwidth
display: flex
width: 100%
&.is-loading
color: transparent !important
pointer-events: none
&::after
@extend %loader
+center(1em)
position: absolute !important
&.is-static
background-color: $button-static-background-color
border-color: $button-static-border-color
color: $button-static-color
box-shadow: none
pointer-events: none
&.is-rounded
border-radius: $radius-rounded
padding-left: calc(#{$button-padding-horizontal} + 0.25em)
padding-right: calc(#{$button-padding-horizontal} + 0.25em)
.buttons
align-items: center
display: flex
flex-wrap: wrap
justify-content: flex-start
.button
margin-bottom: 0.5rem
&:not(:last-child):not(.is-fullwidth)
+ltr-property("margin", 0.5rem)
&:last-child
margin-bottom: -0.5rem
&:not(:last-child)
margin-bottom: 1rem
// Sizes
&.are-small
.button:not(.is-normal):not(.is-medium):not(.is-large)
+button-small
&.are-medium
.button:not(.is-small):not(.is-normal):not(.is-large)
+button-medium
&.are-large
.button:not(.is-small):not(.is-normal):not(.is-medium)
+button-large
&.has-addons
.button
&:not(:first-child)
border-bottom-left-radius: 0
border-top-left-radius: 0
&:not(:last-child)
border-bottom-right-radius: 0
border-top-right-radius: 0
+ltr-property("margin", -1px)
&:last-child
+ltr-property("margin", 0)
&:hover,
&.is-hovered
z-index: 2
&:focus,
&.is-focused,
&:active,
&.is-active,
&.is-selected
z-index: 3
&:hover
z-index: 4
&.is-expanded
flex-grow: 1
flex-shrink: 1
&.is-centered
justify-content: center
&:not(.has-addons)
.button:not(.is-fullwidth)
margin-left: 0.25rem
margin-right: 0.25rem
&.is-right
justify-content: flex-end
&:not(.has-addons)
.button:not(.is-fullwidth)
margin-left: 0.25rem
margin-right: 0.25rem
@each $bp-name, $bp-sizes in $button-responsive-sizes
+breakpoint($bp-name)
@each $size, $value in $bp-sizes
@if $size != "normal"
.button.is-responsive.is-#{$size}
font-size: $value
@else
.button.is-responsive,
.button.is-responsive.is-normal
font-size: $value

@ -0,0 +1,162 @@
@import "../utilities/mixins"
$content-heading-color: $text-strong !default
$content-heading-weight: $weight-semibold !default
$content-heading-line-height: 1.125 !default
$content-block-margin-bottom: 1em !default
$content-blockquote-background-color: $background !default
$content-blockquote-border-left: 5px solid $border !default
$content-blockquote-padding: 1.25em 1.5em !default
$content-pre-padding: 1.25em 1.5em !default
$content-table-cell-border: 1px solid $border !default
$content-table-cell-border-width: 0 0 1px !default
$content-table-cell-padding: 0.5em 0.75em !default
$content-table-cell-heading-color: $text-strong !default
$content-table-head-cell-border-width: 0 0 2px !default
$content-table-head-cell-color: $text-strong !default
$content-table-body-last-row-cell-border-bottom-width: 0 !default
$content-table-foot-cell-border-width: 2px 0 0 !default
$content-table-foot-cell-color: $text-strong !default
.content
@extend %block
// Inline
li + li
margin-top: 0.25em
// Block
p,
dl,
ol,
ul,
blockquote,
pre,
table
&:not(:last-child)
margin-bottom: $content-block-margin-bottom
h1,
h2,
h3,
h4,
h5,
h6
color: $content-heading-color
font-weight: $content-heading-weight
line-height: $content-heading-line-height
h1
font-size: 2em
margin-bottom: 0.5em
&:not(:first-child)
margin-top: 1em
h2
font-size: 1.75em
margin-bottom: 0.5714em
&:not(:first-child)
margin-top: 1.1428em
h3
font-size: 1.5em
margin-bottom: 0.6666em
&:not(:first-child)
margin-top: 1.3333em
h4
font-size: 1.25em
margin-bottom: 0.8em
h5
font-size: 1.125em
margin-bottom: 0.8888em
h6
font-size: 1em
margin-bottom: 1em
blockquote
background-color: $content-blockquote-background-color
+ltr-property("border", $content-blockquote-border-left, false)
padding: $content-blockquote-padding
ol
list-style-position: outside
+ltr-property("margin", 2em, false)
margin-top: 1em
&:not([type])
list-style-type: decimal
&.is-lower-alpha
list-style-type: lower-alpha
&.is-lower-roman
list-style-type: lower-roman
&.is-upper-alpha
list-style-type: upper-alpha
&.is-upper-roman
list-style-type: upper-roman
ul
list-style: disc outside
+ltr-property("margin", 2em, false)
margin-top: 1em
ul
list-style-type: circle
margin-top: 0.5em
ul
list-style-type: square
dd
+ltr-property("margin", 2em, false)
figure
margin-left: 2em
margin-right: 2em
text-align: center
&:not(:first-child)
margin-top: 2em
&:not(:last-child)
margin-bottom: 2em
img
display: inline-block
figcaption
font-style: italic
pre
+overflow-touch
overflow-x: auto
padding: $content-pre-padding
white-space: pre
word-wrap: normal
sup,
sub
font-size: 75%
table
width: 100%
td,
th
border: $content-table-cell-border
border-width: $content-table-cell-border-width
padding: $content-table-cell-padding
vertical-align: top
th
color: $content-table-cell-heading-color
&:not([align])
text-align: inherit
thead
td,
th
border-width: $content-table-head-cell-border-width
color: $content-table-head-cell-color
tfoot
td,
th
border-width: $content-table-foot-cell-border-width
color: $content-table-foot-cell-color
tbody
tr
&:last-child
td,
th
border-bottom-width: $content-table-body-last-row-cell-border-bottom-width
.tabs
li + li
margin-top: 0
// Sizes
&.is-small
font-size: $size-small
&.is-normal
font-size: $size-normal
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large

@ -0,0 +1,46 @@
$icon-dimensions: 1.5rem !default
$icon-dimensions-small: 1rem !default
$icon-dimensions-medium: 2rem !default
$icon-dimensions-large: 3rem !default
$icon-text-spacing: 0.25em !default
.icon
align-items: center
display: inline-flex
justify-content: center
height: $icon-dimensions
width: $icon-dimensions
// Sizes
&.is-small
height: $icon-dimensions-small
width: $icon-dimensions-small
&.is-medium
height: $icon-dimensions-medium
width: $icon-dimensions-medium
&.is-large
height: $icon-dimensions-large
width: $icon-dimensions-large
.icon-text
align-items: flex-start
color: inherit
display: inline-flex
flex-wrap: wrap
line-height: $icon-dimensions
vertical-align: top
.icon
flex-grow: 0
flex-shrink: 0
&:not(:last-child)
+ltr
margin-right: $icon-text-spacing
+rtl
margin-left: $icon-text-spacing
&:not(:first-child)
+ltr
margin-left: $icon-text-spacing
+rtl
margin-right: $icon-text-spacing
div.icon-text
display: flex

@ -0,0 +1,73 @@
@import "../utilities/mixins"
$dimensions: 16 24 32 48 64 96 128 !default
.image
display: block
position: relative
img
display: block
height: auto
width: 100%
&.is-rounded
border-radius: $radius-rounded
&.is-fullwidth
width: 100%
// Ratio
&.is-square,
&.is-1by1,
&.is-5by4,
&.is-4by3,
&.is-3by2,
&.is-5by3,
&.is-16by9,
&.is-2by1,
&.is-3by1,
&.is-4by5,
&.is-3by4,
&.is-2by3,
&.is-3by5,
&.is-9by16,
&.is-1by2,
&.is-1by3
img,
.has-ratio
@extend %overlay
height: 100%
width: 100%
&.is-square,
&.is-1by1
padding-top: 100%
&.is-5by4
padding-top: 80%
&.is-4by3
padding-top: 75%
&.is-3by2
padding-top: 66.6666%
&.is-5by3
padding-top: 60%
&.is-16by9
padding-top: 56.25%
&.is-2by1
padding-top: 50%
&.is-3by1
padding-top: 33.3333%
&.is-4by5
padding-top: 125%
&.is-3by4
padding-top: 133.3333%
&.is-2by3
padding-top: 150%
&.is-3by5
padding-top: 166.6666%
&.is-9by16
padding-top: 177.7777%
&.is-1by2
padding-top: 200%
&.is-1by3
padding-top: 300%
// Sizes
@each $dimension in $dimensions
&.is-#{$dimension}x#{$dimension}
height: $dimension * 1px
width: $dimension * 1px

@ -0,0 +1,52 @@
@import "../utilities/mixins"
$notification-background-color: $background !default
$notification-code-background-color: $scheme-main !default
$notification-radius: $radius !default
$notification-padding: 1.25rem 2.5rem 1.25rem 1.5rem !default
$notification-padding-ltr: 1.25rem 2.5rem 1.25rem 1.5rem !default
$notification-padding-rtl: 1.25rem 1.5rem 1.25rem 2.5rem !default
$notification-colors: $colors !default
.notification
@extend %block
background-color: $notification-background-color
border-radius: $notification-radius
position: relative
+ltr
padding: $notification-padding-ltr
+rtl
padding: $notification-padding-rtl
a:not(.button):not(.dropdown-item)
color: currentColor
text-decoration: underline
strong
color: currentColor
code,
pre
background: $notification-code-background-color
pre code
background: transparent
& > .delete
+ltr-position(0.5rem)
position: absolute
top: 0.5rem
.title,
.subtitle,
.content
color: currentColor
// Colors
@each $name, $pair in $notification-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
background-color: $color
color: $color-invert
// If light and dark colors are provided
@if length($pair) >= 4
$color-light: nth($pair, 3)
$color-dark: nth($pair, 4)
&.is-light
background-color: $color-light
color: $color-dark

@ -0,0 +1,28 @@
@import "../utilities/mixins"
.delete
@extend %delete
.heading
display: block
font-size: 11px
letter-spacing: 1px
margin-bottom: 5px
text-transform: uppercase
.loader
@extend %loader
.number
align-items: center
background-color: $background
border-radius: $radius-rounded
display: inline-flex
font-size: $size-medium
height: 2em
justify-content: center
margin-right: 1.5rem
min-width: 2.5em
padding: 0.25rem 0.5rem
text-align: center
vertical-align: top

@ -0,0 +1,73 @@
@import "../utilities/mixins"
$progress-bar-background-color: $border-light !default
$progress-value-background-color: $text !default
$progress-border-radius: $radius-rounded !default
$progress-indeterminate-duration: 1.5s !default
$progress-colors: $colors !default
.progress
@extend %block
-moz-appearance: none
-webkit-appearance: none
border: none
border-radius: $progress-border-radius
display: block
height: $size-normal
overflow: hidden
padding: 0
width: 100%
&::-webkit-progress-bar
background-color: $progress-bar-background-color
&::-webkit-progress-value
background-color: $progress-value-background-color
&::-moz-progress-bar
background-color: $progress-value-background-color
&::-ms-fill
background-color: $progress-value-background-color
border: none
// Colors
@each $name, $pair in $progress-colors
$color: nth($pair, 1)
&.is-#{$name}
&::-webkit-progress-value
background-color: $color
&::-moz-progress-bar
background-color: $color
&::-ms-fill
background-color: $color
&:indeterminate
background-image: linear-gradient(to right, $color 30%, $progress-bar-background-color 30%)
&:indeterminate
animation-duration: $progress-indeterminate-duration
animation-iteration-count: infinite
animation-name: moveIndeterminate
animation-timing-function: linear
background-color: $progress-bar-background-color
background-image: linear-gradient(to right, $text 30%, $progress-bar-background-color 30%)
background-position: top left
background-repeat: no-repeat
background-size: 150% 150%
&::-webkit-progress-bar
background-color: transparent
&::-moz-progress-bar
background-color: transparent
&::-ms-fill
animation-name: none
// Sizes
&.is-small
height: $size-small
&.is-medium
height: $size-medium
&.is-large
height: $size-large
@keyframes moveIndeterminate
from
background-position: 200% 0
to
background-position: -200% 0

@ -0,0 +1,140 @@
@import "../utilities/mixins"
$tag-background-color: $background !default
$tag-color: $text !default
$tag-radius: $radius !default
$tag-delete-margin: 1px !default
$tag-colors: $colors !default
.tags
align-items: center
display: flex
flex-wrap: wrap
justify-content: flex-start
.tag
margin-bottom: 0.5rem
&:not(:last-child)
+ltr-property("margin", 0.5rem)
&:last-child
margin-bottom: -0.5rem
&:not(:last-child)
margin-bottom: 1rem
// Sizes
&.are-medium
.tag:not(.is-normal):not(.is-large)
font-size: $size-normal
&.are-large
.tag:not(.is-normal):not(.is-medium)
font-size: $size-medium
&.is-centered
justify-content: center
.tag
margin-right: 0.25rem
margin-left: 0.25rem
&.is-right
justify-content: flex-end
.tag
&:not(:first-child)
margin-left: 0.5rem
&:not(:last-child)
margin-right: 0
&.has-addons
.tag
+ltr-property("margin", 0)
&:not(:first-child)
+ltr-property("margin", 0, false)
+ltr
border-top-left-radius: 0
border-bottom-left-radius: 0
+rtl
border-top-right-radius: 0
border-bottom-right-radius: 0
&:not(:last-child)
+ltr
border-top-right-radius: 0
border-bottom-right-radius: 0
+rtl
border-top-left-radius: 0
border-bottom-left-radius: 0
.tag:not(body)
align-items: center
background-color: $tag-background-color
border-radius: $tag-radius
color: $tag-color
display: inline-flex
font-size: $size-small
height: 2em
justify-content: center
line-height: 1.5
padding-left: 0.75em
padding-right: 0.75em
white-space: nowrap
.delete
+ltr-property("margin", 0.25rem, false)
+ltr-property("margin", -0.375rem)
// Colors
@each $name, $pair in $tag-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
background-color: $color
color: $color-invert
// If a light and dark colors are provided
@if length($pair) > 3
$color-light: nth($pair, 3)
$color-dark: nth($pair, 4)
&.is-light
background-color: $color-light
color: $color-dark
// Sizes
&.is-normal
font-size: $size-small
&.is-medium
font-size: $size-normal
&.is-large
font-size: $size-medium
.icon
&:first-child:not(:last-child)
+ltr-property("margin", -0.375em, false)
+ltr-property("margin", 0.1875em)
&:last-child:not(:first-child)
+ltr-property("margin", 0.1875em, false)
+ltr-property("margin", -0.375em)
&:first-child:last-child
+ltr-property("margin", -0.375em, false)
+ltr-property("margin", -0.375em)
// Modifiers
&.is-delete
+ltr-property("margin", $tag-delete-margin, false)
padding: 0
position: relative
width: 2em
&::before,
&::after
background-color: currentColor
content: ""
display: block
left: 50%
position: absolute
top: 50%
transform: translateX(-50%) translateY(-50%) rotate(45deg)
transform-origin: center center
&::before
height: 1px
width: 50%
&::after
height: 50%
width: 1px
&:hover,
&:focus
background-color: darken($tag-background-color, 5%)
&:active
background-color: darken($tag-background-color, 10%)
&.is-rounded
border-radius: $radius-rounded
a.tag
&:hover
text-decoration: underline

@ -0,0 +1,9 @@
/* Bulma Form */
@charset "utf-8"
@import "shared"
@import "input-textarea"
@import "checkbox-radio"
@import "select"
@import "file"
@import "tools"

@ -0,0 +1,22 @@
%checkbox-radio
cursor: pointer
display: inline-block
line-height: 1.25
position: relative
input
cursor: pointer
&:hover
color: $input-hover-color
&[disabled],
fieldset[disabled] &,
input[disabled]
color: $input-disabled-color
cursor: not-allowed
.checkbox
@extend %checkbox-radio
.radio
@extend %checkbox-radio
& + .radio
+ltr-property("margin", 0.5em, false)

@ -0,0 +1,184 @@
$file-border-color: $border !default
$file-radius: $radius !default
$file-cta-background-color: $scheme-main-ter !default
$file-cta-color: $text !default
$file-cta-hover-color: $text-strong !default
$file-cta-active-color: $text-strong !default
$file-name-border-color: $border !default
$file-name-border-style: solid !default
$file-name-border-width: 1px 1px 1px 0 !default
$file-name-max-width: 16em !default
$file-colors: $form-colors !default
.file
@extend %unselectable
align-items: stretch
display: flex
justify-content: flex-start
position: relative
// Colors
@each $name, $pair in $file-colors
$color: nth($pair, 1)
$color-invert: nth($pair, 2)
&.is-#{$name}
.file-cta
background-color: $color
border-color: transparent
color: $color-invert
&:hover,
&.is-hovered
.file-cta
background-color: bulmaDarken($color, 2.5%)
border-color: transparent
color: $color-invert
&:focus,
&.is-focused
.file-cta
border-color: transparent
box-shadow: 0 0 0.5em bulmaRgba($color, 0.25)
color: $color-invert
&:active,
&.is-active
.file-cta
background-color: bulmaDarken($color, 5%)
border-color: transparent
color: $color-invert
// Sizes
&.is-small
font-size: $size-small
&.is-normal
font-size: $size-normal
&.is-medium
font-size: $size-medium
.file-icon
.fa
font-size: 21px
&.is-large
font-size: $size-large
.file-icon
.fa
font-size: 28px
// Modifiers
&.has-name
.file-cta
border-bottom-right-radius: 0
border-top-right-radius: 0
.file-name
border-bottom-left-radius: 0
border-top-left-radius: 0
&.is-empty
.file-cta
border-radius: $file-radius
.file-name
display: none
&.is-boxed
.file-label
flex-direction: column
.file-cta
flex-direction: column
height: auto
padding: 1em 3em
.file-name
border-width: 0 1px 1px
.file-icon
height: 1.5em
width: 1.5em
.fa
font-size: 21px
&.is-small
.file-icon .fa
font-size: 14px
&.is-medium
.file-icon .fa
font-size: 28px
&.is-large
.file-icon .fa
font-size: 35px
&.has-name
.file-cta
border-radius: $file-radius $file-radius 0 0
.file-name
border-radius: 0 0 $file-radius $file-radius
border-width: 0 1px 1px
&.is-centered
justify-content: center
&.is-fullwidth
.file-label
width: 100%
.file-name
flex-grow: 1
max-width: none
&.is-right
justify-content: flex-end
.file-cta
border-radius: 0 $file-radius $file-radius 0
.file-name
border-radius: $file-radius 0 0 $file-radius
border-width: 1px 0 1px 1px
order: -1
.file-label
align-items: stretch
display: flex
cursor: pointer
justify-content: flex-start
overflow: hidden
position: relative
&:hover
.file-cta
background-color: bulmaDarken($file-cta-background-color, 2.5%)
color: $file-cta-hover-color
.file-name
border-color: bulmaDarken($file-name-border-color, 2.5%)
&:active
.file-cta
background-color: bulmaDarken($file-cta-background-color, 5%)
color: $file-cta-active-color
.file-name
border-color: bulmaDarken($file-name-border-color, 5%)
.file-input
height: 100%
left: 0
opacity: 0
outline: none
position: absolute
top: 0
width: 100%
.file-cta,
.file-name
@extend %control
border-color: $file-border-color
border-radius: $file-radius
font-size: 1em
padding-left: 1em
padding-right: 1em
white-space: nowrap
.file-cta
background-color: $file-cta-background-color
color: $file-cta-color
.file-name
border-color: $file-name-border-color
border-style: $file-name-border-style
border-width: $file-name-border-width
display: block
max-width: $file-name-max-width
overflow: hidden
text-align: inherit
text-overflow: ellipsis
.file-icon
align-items: center
display: flex
height: 1em
justify-content: center
+ltr-property("margin", 0.5em)
width: 1em
.fa
font-size: 14px

@ -0,0 +1,66 @@
$textarea-padding: $control-padding-horizontal !default
$textarea-max-height: 40em !default
$textarea-min-height: 8em !default
$textarea-colors: $form-colors !default
%input-textarea
@extend %input
box-shadow: $input-shadow
max-width: 100%
width: 100%
&[readonly]
box-shadow: none
// Colors
@each $name, $pair in $textarea-colors
$color: nth($pair, 1)
&.is-#{$name}
border-color: $color
&:focus,
&.is-focused,
&:active,
&.is-active
box-shadow: $input-focus-box-shadow-size bulmaRgba($color, 0.25)
// Sizes
&.is-small
+control-small
&.is-medium
+control-medium
&.is-large
+control-large
// Modifiers
&.is-fullwidth
display: block
width: 100%
&.is-inline
display: inline
width: auto
.input
@extend %input-textarea
&.is-rounded
border-radius: $radius-rounded
padding-left: calc(#{$control-padding-horizontal} + 0.375em)
padding-right: calc(#{$control-padding-horizontal} + 0.375em)
&.is-static
background-color: transparent
border-color: transparent
box-shadow: none
padding-left: 0
padding-right: 0
.textarea
@extend %input-textarea
display: block
max-width: 100%
min-width: 100%
padding: $textarea-padding
resize: vertical
&:not([rows])
max-height: $textarea-max-height
min-height: $textarea-min-height
&[rows]
height: initial
// Modifiers
&.has-fixed-size
resize: none

@ -0,0 +1,88 @@
$select-colors: $form-colors !default
.select
display: inline-block
max-width: 100%
position: relative
vertical-align: top
&:not(.is-multiple)
height: $input-height
&:not(.is-multiple):not(.is-loading)
&::after
@extend %arrow
border-color: $input-arrow
+ltr-position(1.125em)
z-index: 4
&.is-rounded
select
border-radius: $radius-rounded
+ltr-property("padding", 1em, false)
select
@extend %input
cursor: pointer
display: block
font-size: 1em
max-width: 100%
outline: none
&::-ms-expand
display: none
&[disabled]:hover,
fieldset[disabled] &:hover
border-color: $input-disabled-border-color
&:not([multiple])
+ltr-property("padding", 2.5em)
&[multiple]
height: auto
padding: 0
option
padding: 0.5em 1em
// States
&:not(.is-multiple):not(.is-loading):hover
&::after
border-color: $input-hover-color
// Colors
@each $name, $pair in $select-colors
$color: nth($pair, 1)
&.is-#{$name}
&:not(:hover)::after
border-color: $color
select
border-color: $color
&:hover,
&.is-hovered
border-color: bulmaDarken($color, 5%)
&:focus,
&.is-focused,
&:active,
&.is-active
box-shadow: $input-focus-box-shadow-size bulmaRgba($color, 0.25)
// Sizes
&.is-small
+control-small
&.is-medium
+control-medium
&.is-large
+control-large
// Modifiers
&.is-disabled
&::after
border-color: $input-disabled-color !important
opacity: 0.5
&.is-fullwidth
width: 100%
select
width: 100%
&.is-loading
&::after
@extend %loader
margin-top: 0
position: absolute
+ltr-position(0.625em)
top: 0.625em
transform: none
&.is-small:after
font-size: $size-small
&.is-medium:after
font-size: $size-medium
&.is-large:after
font-size: $size-large

@ -0,0 +1,60 @@
@import "../utilities/controls"
@import "../utilities/mixins"
$form-colors: $colors !default
$input-color: $text-strong !default
$input-background-color: $scheme-main !default
$input-border-color: $border !default
$input-height: $control-height !default
$input-shadow: inset 0 0.0625em 0.125em rgba($scheme-invert, 0.05) !default
$input-placeholder-color: bulmaRgba($input-color, 0.3) !default
$input-hover-color: $text-strong !default
$input-hover-border-color: $border-hover !default
$input-focus-color: $text-strong !default
$input-focus-border-color: $link !default
$input-focus-box-shadow-size: 0 0 0 0.125em !default
$input-focus-box-shadow-color: bulmaRgba($link, 0.25) !default
$input-disabled-color: $text-light !default
$input-disabled-background-color: $background !default
$input-disabled-border-color: $background !default
$input-disabled-placeholder-color: bulmaRgba($input-disabled-color, 0.3) !default
$input-arrow: $link !default
$input-icon-color: $border !default
$input-icon-active-color: $text !default
$input-radius: $radius !default
=input
@extend %control
background-color: $input-background-color
border-color: $input-border-color
border-radius: $input-radius
color: $input-color
+placeholder
color: $input-placeholder-color
&:hover,
&.is-hovered
border-color: $input-hover-border-color
&:focus,
&.is-focused,
&:active,
&.is-active
border-color: $input-focus-border-color
box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color
&[disabled],
fieldset[disabled] &
background-color: $input-disabled-background-color
border-color: $input-disabled-border-color
box-shadow: none
color: $input-disabled-color
+placeholder
color: $input-disabled-placeholder-color
%input
+input

@ -0,0 +1,215 @@
$label-color: $text-strong !default
$label-weight: $weight-bold !default
$help-size: $size-small !default
$label-colors: $form-colors !default
.label
color: $label-color
display: block
font-size: $size-normal
font-weight: $label-weight
&:not(:last-child)
margin-bottom: 0.5em
// Sizes
&.is-small
font-size: $size-small
&.is-medium
font-size: $size-medium
&.is-large
font-size: $size-large
.help
display: block
font-size: $help-size
margin-top: 0.25rem
@each $name, $pair in $label-colors
$color: nth($pair, 1)
&.is-#{$name}
color: $color
// Containers
.field
&:not(:last-child)
margin-bottom: 0.75rem
// Modifiers
&.has-addons
display: flex
justify-content: flex-start
.control
&:not(:last-child)
+ltr-property("margin", -1px)
&:not(:first-child):not(:last-child)
.button,
.input,
.select select
border-radius: 0
&:first-child:not(:only-child)
.button,
.input,
.select select
+ltr
border-bottom-right-radius: 0
border-top-right-radius: 0
+rtl
border-bottom-left-radius: 0
border-top-left-radius: 0
&:last-child:not(:only-child)
.button,
.input,
.select select
+ltr
border-bottom-left-radius: 0
border-top-left-radius: 0
+rtl
border-bottom-right-radius: 0
border-top-right-radius: 0
.button,
.input,
.select select
&:not([disabled])
&:hover,
&.is-hovered
z-index: 2
&:focus,
&.is-focused,
&:active,
&.is-active
z-index: 3
&:hover
z-index: 4
&.is-expanded
flex-grow: 1
flex-shrink: 1
&.has-addons-centered
justify-content: center
&.has-addons-right
justify-content: flex-end
&.has-addons-fullwidth
.control
flex-grow: 1
flex-shrink: 0
&.is-grouped
display: flex
justify-content: flex-start
& > .control
flex-shrink: 0
&:not(:last-child)
margin-bottom: 0
+ltr-property("margin", 0.75rem)
&.is-expanded
flex-grow: 1
flex-shrink: 1
&.is-grouped-centered
justify-content: center
&.is-grouped-right
justify-content: flex-end
&.is-grouped-multiline
flex-wrap: wrap
& > .control
&:last-child,
&:not(:last-child)
margin-bottom: 0.75rem
&:last-child
margin-bottom: -0.75rem
&:not(:last-child)
margin-bottom: 0
&.is-horizontal
+tablet
display: flex
.field-label
.label
font-size: inherit
+mobile
margin-bottom: 0.5rem
+tablet
flex-basis: 0
flex-grow: 1
flex-shrink: 0
+ltr-property("margin", 1.5rem)
text-align: right
&.is-small
font-size: $size-small
padding-top: 0.375em
&.is-normal
padding-top: 0.375em
&.is-medium
font-size: $size-medium
padding-top: 0.375em
&.is-large
font-size: $size-large
padding-top: 0.375em
.field-body
.field .field
margin-bottom: 0
+tablet
display: flex
flex-basis: 0
flex-grow: 5
flex-shrink: 1
.field
margin-bottom: 0
& > .field
flex-shrink: 1
&:not(.is-narrow)
flex-grow: 1
&:not(:last-child)
+ltr-property("margin", 0.75rem)
.control
box-sizing: border-box
clear: both
font-size: $size-normal
position: relative
text-align: inherit
// Modifiers
&.has-icons-left,
&.has-icons-right
.input,
.select
&:focus
& ~ .icon
color: $input-icon-active-color
&.is-small ~ .icon
font-size: $size-small
&.is-medium ~ .icon
font-size: $size-medium
&.is-large ~ .icon
font-size: $size-large
.icon
color: $input-icon-color
height: $input-height
pointer-events: none
position: absolute
top: 0
width: $input-height
z-index: 4
&.has-icons-left
.input,
.select select
padding-left: $input-height
.icon.is-left
left: 0
&.has-icons-right
.input,
.select select
padding-right: $input-height
.icon.is-right
right: 0
&.is-loading
&::after
@extend %loader
position: absolute !important
+ltr-position(0.625em)
top: 0.625em
z-index: 4
&.is-small:after
font-size: $size-small
&.is-medium:after
font-size: $size-medium
&.is-large:after
font-size: $size-large

@ -0,0 +1,9 @@
/* Bulma Utilities */
@charset "utf-8"
@import "initial-variables"
@import "functions"
@import "derived-variables"
@import "mixins"
@import "controls"
@import "extends"

@ -0,0 +1,49 @@
@import "derived-variables"
$control-radius: $radius !default
$control-radius-small: $radius-small !default
$control-border-width: 1px !default
$control-height: 2.5em !default
$control-line-height: 1.5 !default
$control-padding-vertical: calc(0.5em - #{$control-border-width}) !default
$control-padding-horizontal: calc(0.75em - #{$control-border-width}) !default
=control
-moz-appearance: none
-webkit-appearance: none
align-items: center
border: $control-border-width solid transparent
border-radius: $control-radius
box-shadow: none
display: inline-flex
font-size: $size-normal
height: $control-height
justify-content: flex-start
line-height: $control-line-height
padding-bottom: $control-padding-vertical
padding-left: $control-padding-horizontal
padding-right: $control-padding-horizontal
padding-top: $control-padding-vertical
position: relative
vertical-align: top
// States
&:focus,
&.is-focused,
&:active,
&.is-active
outline: none
&[disabled],
fieldset[disabled] &
cursor: not-allowed
// The controls sizes use mixins so they can be used at different breakpoints
=control-small
border-radius: $control-radius-small
font-size: $size-small
=control-medium
font-size: $size-medium
=control-large
font-size: $size-large

@ -0,0 +1,114 @@
@import "initial-variables"
@import "functions"
$primary: $turquoise !default
$info: $cyan !default
$success: $green !default
$warning: $yellow !default
$danger: $red !default
$light: $white-ter !default
$dark: $grey-darker !default
// Invert colors
$orange-invert: findColorInvert($orange) !default
$yellow-invert: findColorInvert($yellow) !default
$green-invert: findColorInvert($green) !default
$turquoise-invert: findColorInvert($turquoise) !default
$cyan-invert: findColorInvert($cyan) !default
$blue-invert: findColorInvert($blue) !default
$purple-invert: findColorInvert($purple) !default
$red-invert: findColorInvert($red) !default
$primary-invert: findColorInvert($primary) !default
$primary-light: findLightColor($primary) !default
$primary-dark: findDarkColor($primary) !default
$info-invert: findColorInvert($info) !default
$info-light: findLightColor($info) !default
$info-dark: findDarkColor($info) !default
$success-invert: findColorInvert($success) !default
$success-light: findLightColor($success) !default
$success-dark: findDarkColor($success) !default
$warning-invert: findColorInvert($warning) !default
$warning-light: findLightColor($warning) !default
$warning-dark: findDarkColor($warning) !default
$danger-invert: findColorInvert($danger) !default
$danger-light: findLightColor($danger) !default
$danger-dark: findDarkColor($danger) !default
$light-invert: findColorInvert($light) !default
$dark-invert: findColorInvert($dark) !default
// General colors
$scheme-main: $white !default
$scheme-main-bis: $white-bis !default
$scheme-main-ter: $white-ter !default
$scheme-invert: $black !default
$scheme-invert-bis: $black-bis !default
$scheme-invert-ter: $black-ter !default
$background: $white-ter !default
$border: $grey-lighter !default
$border-hover: $grey-light !default
$border-light: $grey-lightest !default
$border-light-hover: $grey-light !default
// Text colors
$text: $grey-dark !default
$text-invert: findColorInvert($text) !default
$text-light: $grey !default
$text-strong: $grey-darker !default
// Code colors
$code: darken($red, 15%) !default
$code-background: $background !default
$pre: $text !default
$pre-background: $background !default
// Link colors
$link: $blue !default
$link-invert: findColorInvert($link) !default
$link-light: findLightColor($link) !default
$link-dark: findDarkColor($link) !default
$link-visited: $purple !default
$link-hover: $grey-darker !default
$link-hover-border: $grey-light !default
$link-focus: $grey-darker !default
$link-focus-border: $blue !default
$link-active: $grey-darker !default
$link-active-border: $grey-dark !default
// Typography
$family-primary: $family-sans-serif !default
$family-secondary: $family-sans-serif !default
$family-code: $family-monospace !default
$size-small: $size-7 !default
$size-normal: $size-6 !default
$size-medium: $size-5 !default
$size-large: $size-4 !default
// Effects
$shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default
// Lists and maps
$custom-colors: null !default
$custom-shades: null !default
$colors: mergeColorMaps(("white": ($white, $black), "black": ($black, $white), "light": ($light, $light-invert), "dark": ($dark, $dark-invert), "primary": ($primary, $primary-invert, $primary-light, $primary-dark), "link": ($link, $link-invert, $link-light, $link-dark), "info": ($info, $info-invert, $info-light, $info-dark), "success": ($success, $success-invert, $success-light, $success-dark), "warning": ($warning, $warning-invert, $warning-light, $warning-dark), "danger": ($danger, $danger-invert, $danger-light, $danger-dark)), $custom-colors) !default
$shades: mergeColorMaps(("black-bis": $black-bis, "black-ter": $black-ter, "grey-darker": $grey-darker, "grey-dark": $grey-dark, "grey": $grey, "grey-light": $grey-light, "grey-lighter": $grey-lighter, "white-ter": $white-ter, "white-bis": $white-bis), $custom-shades) !default
$sizes: $size-1 $size-2 $size-3 $size-4 $size-5 $size-6 $size-7 !default

@ -0,0 +1,25 @@
@import "mixins"
%control
+control
%unselectable
+unselectable
%arrow
+arrow
%block
+block
%delete
+delete
%loader
+loader
%overlay
+overlay
%reset
+reset

@ -0,0 +1,135 @@
@function mergeColorMaps($bulma-colors, $custom-colors)
// We return at least Bulma's hard-coded colors
$merged-colors: $bulma-colors
// We want a map as input
@if type-of($custom-colors) == 'map'
@each $name, $components in $custom-colors
// The color name should be a string
// and the components either a single color
// or a colors list with at least one element
@if type-of($name) == 'string' and (type-of($components) == 'list' or type-of($components) == 'color') and length($components) >= 1
$color-base: null
$color-invert: null
$color-light: null
$color-dark: null
$value: null
// The param can either be a single color
// or a list of 2 colors
@if type-of($components) == 'color'
$color-base: $components
$color-invert: findColorInvert($color-base)
$color-light: findLightColor($color-base)
$color-dark: findDarkColor($color-base)
@else if type-of($components) == 'list'
$color-base: nth($components, 1)
// If Invert, Light and Dark are provided
@if length($components) > 3
$color-invert: nth($components, 2)
$color-light: nth($components, 3)
$color-dark: nth($components, 4)
// If only Invert and Light are provided
@else if length($components) > 2
$color-invert: nth($components, 2)
$color-light: nth($components, 3)
$color-dark: findDarkColor($color-base)
// If only Invert is provided
@else
$color-invert: nth($components, 2)
$color-light: findLightColor($color-base)
$color-dark: findDarkColor($color-base)
$value: ($color-base, $color-invert, $color-light, $color-dark)
// We only want to merge the map if the color base is an actual color
@if type-of($color-base) == 'color'
// We merge this colors elements as map with Bulma's colors map
// (we can override them this way, no multiple definition for the same name)
// $merged-colors: map_merge($merged-colors, ($name: ($color-base, $color-invert, $color-light, $color-dark)))
$merged-colors: map_merge($merged-colors, ($name: $value))
@return $merged-colors
@function powerNumber($number, $exp)
$value: 1
@if $exp > 0
@for $i from 1 through $exp
$value: $value * $number
@else if $exp < 0
@for $i from 1 through -$exp
$value: divide($value, $number)
@return $value
@function colorLuminance($color)
@if type-of($color) != 'color'
@return 0.55
$color-rgb: ('red': red($color),'green': green($color),'blue': blue($color))
@each $name, $value in $color-rgb
$adjusted: 0
$value: divide($value, 255)
@if $value < 0.03928
$value: divide($value, 12.92)
@else
$value: divide(($value + .055), 1.055)
$value: powerNumber($value, 2)
$color-rgb: map-merge($color-rgb, ($name: $value))
@return (map-get($color-rgb, 'red') * .2126) + (map-get($color-rgb, 'green') * .7152) + (map-get($color-rgb, 'blue') * .0722)
@function findColorInvert($color)
@if (colorLuminance($color) > 0.55)
@return rgba(#000, 0.7)
@else
@return #fff
@function findLightColor($color, $l: 96%)
@if type-of($color) == 'color'
$l: 96%
@if lightness($color) > 96%
$l: lightness($color)
@return change-color($color, $lightness: $l)
@return $background
@function findDarkColor($color, $base-l: 29%)
@if type-of($color) == 'color'
$luminance: colorLuminance($color)
$luminance-delta: (0.53 - $luminance)
$target-l: round($base-l + ($luminance-delta * 53))
@return change-color($color, $lightness: max($base-l, $target-l))
@return $text-strong
@function bulmaRgba($color, $alpha)
@if type-of($color) != 'color'
@return $color
@return rgba($color, $alpha)
@function bulmaDarken($color, $amount)
@if type-of($color) != 'color'
@return $color
@return darken($color, $amount)
@function bulmaLighten($color, $amount)
@if type-of($color) != 'color'
@return $color
@return lighten($color, $amount)
// Custom divide function by @mdo from https://github.com/twbs/bootstrap/pull/34245
// Replaces old slash division deprecated in Dart Sass
@function divide($dividend, $divisor, $precision: 10)
$sign: if($dividend > 0 and $divisor > 0, 1, -1)
$dividend: abs($dividend)
$divisor: abs($divisor)
$quotient: 0
$remainder: $dividend
@if $dividend == 0
@return 0
@if $divisor == 0
@error "Cannot divide by 0"
@if $divisor == 1
@return $dividend
@while $remainder >= $divisor
$quotient: $quotient + 1
$remainder: $remainder - $divisor
@if $remainder > 0 and $precision > 0
$remainder: divide($remainder * 10, $divisor, $precision - 1) * .1
@return ($quotient + $remainder) * $sign

@ -0,0 +1,79 @@
// Colors
$black: hsl(0, 0%, 4%) !default
$black-bis: hsl(0, 0%, 7%) !default
$black-ter: hsl(0, 0%, 14%) !default
$grey-darker: hsl(0, 0%, 21%) !default
$grey-dark: hsl(0, 0%, 29%) !default
$grey: hsl(0, 0%, 48%) !default
$grey-light: hsl(0, 0%, 71%) !default
$grey-lighter: hsl(0, 0%, 86%) !default
$grey-lightest: hsl(0, 0%, 93%) !default
$white-ter: hsl(0, 0%, 96%) !default
$white-bis: hsl(0, 0%, 98%) !default
$white: hsl(0, 0%, 100%) !default
$orange: hsl(14, 100%, 53%) !default
$yellow: hsl(44, 100%, 77%) !default
$green: hsl(153, 53%, 53%) !default
$turquoise: hsl(171, 100%, 41%) !default
$cyan: hsl(207, 61%, 53%) !default
$blue: hsl(229, 53%, 53%) !default
$purple: hsl(271, 100%, 71%) !default
$red: hsl(348, 86%, 61%) !default
// Typography
$family-sans-serif: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif !default
$family-monospace: monospace !default
$render-mode: optimizeLegibility !default
$size-1: 3rem !default
$size-2: 2.5rem !default
$size-3: 2rem !default
$size-4: 1.5rem !default
$size-5: 1.25rem !default
$size-6: 1rem !default
$size-7: 0.75rem !default
$weight-light: 300 !default
$weight-normal: 400 !default
$weight-medium: 500 !default
$weight-semibold: 600 !default
$weight-bold: 700 !default
// Spacing
$block-spacing: 1.5rem !default
// Responsiveness
// The container horizontal gap, which acts as the offset for breakpoints
$gap: 32px !default
// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16
$tablet: 769px !default
// 960px container + 4rem
$desktop: 960px + (2 * $gap) !default
// 1152px container + 4rem
$widescreen: 1152px + (2 * $gap) !default
$widescreen-enabled: true !default
// 1344px container + 4rem
$fullhd: 1344px + (2 * $gap) !default
$fullhd-enabled: true !default
$breakpoints: ("mobile": ("until": $tablet), "tablet": ("from": $tablet), "tablet-only": ("from": $tablet, "until": $desktop), "touch": ("from": $desktop), "desktop": ("from": $desktop), "desktop-only": ("from": $desktop, "until": $widescreen), "until-widescreen": ("until": $widescreen), "widescreen": ("from": $widescreen), "widescreen-only": ("from": $widescreen, "until": $fullhd), "until-fullhd": ("until": $fullhd), "fullhd": ("from": $fullhd)) !default
// Miscellaneous
$easing: ease-out !default
$radius-small: 2px !default
$radius: 4px !default
$radius-large: 6px !default
$radius-rounded: 9999px !default
$speed: 86ms !default
// Flags
$variable-columns: true !default
$rtl: false !default

@ -0,0 +1,303 @@
@import "derived-variables"
=clearfix
&::after
clear: both
content: " "
display: table
=center($width, $height: 0)
position: absolute
@if $height != 0
left: calc(50% - (#{$width} * 0.5))
top: calc(50% - (#{$height} * 0.5))
@else
left: calc(50% - (#{$width} * 0.5))
top: calc(50% - (#{$width} * 0.5))
=fa($size, $dimensions)
display: inline-block
font-size: $size
height: $dimensions
line-height: $dimensions
text-align: center
vertical-align: top
width: $dimensions
=hamburger($dimensions)
-moz-appearance: none
-webkit-appearance: none
appearance: none
background: none
border: none
cursor: pointer
display: block
height: $dimensions
position: relative
width: $dimensions
span
background-color: currentColor
display: block
height: 1px
left: calc(50% - 8px)
position: absolute
transform-origin: center
transition-duration: $speed
transition-property: background-color, opacity, transform
transition-timing-function: $easing
width: 16px
&:nth-child(1)
top: calc(50% - 6px)
&:nth-child(2)
top: calc(50% - 1px)
&:nth-child(3)
top: calc(50% + 4px)
&:hover
background-color: bulmaRgba(black, 0.05)
// Modifers
&.is-active
span
&:nth-child(1)
transform: translateY(5px) rotate(45deg)
&:nth-child(2)
opacity: 0
&:nth-child(3)
transform: translateY(-5px) rotate(-45deg)
=overflow-touch
-webkit-overflow-scrolling: touch
=placeholder
$placeholders: ':-moz' ':-webkit-input' '-moz' '-ms-input'
@each $placeholder in $placeholders
&:#{$placeholder}-placeholder
@content
=reset
-moz-appearance: none
-webkit-appearance: none
appearance: none
background: none
border: none
color: currentColor
font-family: inherit
font-size: 1em
margin: 0
padding: 0
// Responsiveness
=from($device)
@media screen and (min-width: $device)
@content
=until($device)
@media screen and (max-width: $device - 1px)
@content
=between($from, $until)
@media screen and (min-width: $from) and (max-width: $until - 1px)
@content
=mobile
@media screen and (max-width: $tablet - 1px)
@content
=tablet
@media screen and (min-width: $tablet), print
@content
=tablet-only
@media screen and (min-width: $tablet) and (max-width: $desktop - 1px)
@content
=touch
@media screen and (max-width: $desktop - 1px)
@content
=desktop
@media screen and (min-width: $desktop)
@content
=desktop-only
@if $widescreen-enabled
@media screen and (min-width: $desktop) and (max-width: $widescreen - 1px)
@content
=until-widescreen
@if $widescreen-enabled
@media screen and (max-width: $widescreen - 1px)
@content
=widescreen
@if $widescreen-enabled
@media screen and (min-width: $widescreen)
@content
=widescreen-only
@if $widescreen-enabled and $fullhd-enabled
@media screen and (min-width: $widescreen) and (max-width: $fullhd - 1px)
@content
=until-fullhd
@if $fullhd-enabled
@media screen and (max-width: $fullhd - 1px)
@content
=fullhd
@if $fullhd-enabled
@media screen and (min-width: $fullhd)
@content
=breakpoint($name)
$breakpoint: map-get($breakpoints, $name)
@if $breakpoint
$from: map-get($breakpoint, "from")
$until: map-get($breakpoint, "until")
@if $from and $until
+between($from, $until)
@content
@else if $from
+from($from)
@content
@else if $until
+until($until)
@content
=ltr
@if not $rtl
@content
=rtl
@if $rtl
@content
=ltr-property($property, $spacing, $right: true)
$normal: if($right, "right", "left")
$opposite: if($right, "left", "right")
@if $rtl
#{$property}-#{$opposite}: $spacing
@else
#{$property}-#{$normal}: $spacing
=ltr-position($spacing, $right: true)
$normal: if($right, "right", "left")
$opposite: if($right, "left", "right")
@if $rtl
#{$opposite}: $spacing
@else
#{$normal}: $spacing
// Placeholders
=unselectable
-webkit-touch-callout: none
-webkit-user-select: none
-moz-user-select: none
-ms-user-select: none
user-select: none
=arrow($color: transparent)
border: 3px solid $color
border-radius: 2px
border-right: 0
border-top: 0
content: " "
display: block
height: 0.625em
margin-top: -0.4375em
pointer-events: none
position: absolute
top: 50%
transform: rotate(-45deg)
transform-origin: center
width: 0.625em
=block($spacing: $block-spacing)
&:not(:last-child)
margin-bottom: $spacing
=delete
+unselectable
-moz-appearance: none
-webkit-appearance: none
background-color: bulmaRgba($scheme-invert, 0.2)
border: none
border-radius: $radius-rounded
cursor: pointer
pointer-events: auto
display: inline-block
flex-grow: 0
flex-shrink: 0
font-size: 0
height: 20px
max-height: 20px
max-width: 20px
min-height: 20px
min-width: 20px
outline: none
position: relative
vertical-align: top
width: 20px
&::before,
&::after
background-color: $scheme-main
content: ""
display: block
left: 50%
position: absolute
top: 50%
transform: translateX(-50%) translateY(-50%) rotate(45deg)
transform-origin: center center
&::before
height: 2px
width: 50%
&::after
height: 50%
width: 2px
&:hover,
&:focus
background-color: bulmaRgba($scheme-invert, 0.3)
&:active
background-color: bulmaRgba($scheme-invert, 0.4)
// Sizes
&.is-small
height: 16px
max-height: 16px
max-width: 16px
min-height: 16px
min-width: 16px
width: 16px
&.is-medium
height: 24px
max-height: 24px
max-width: 24px
min-height: 24px
min-width: 24px
width: 24px
&.is-large
height: 32px
max-height: 32px
max-width: 32px
min-height: 32px
min-width: 32px
width: 32px
=loader
animation: spinAround 500ms infinite linear
border: 2px solid $grey-lighter
border-radius: $radius-rounded
border-right-color: transparent
border-top-color: transparent
content: ""
display: block
height: 1em
position: relative
width: 1em
=overlay($offset: 0)
bottom: $offset
left: $offset
position: absolute
right: $offset
top: $offset

@ -6,9 +6,11 @@
.neomorph {
box-shadow: calc(-1 * var(--shadow-offset)) calc(-1 * var(--shadow-offset)) var(--blur-radius) var(--light-shadow), var(--shadow-offset) var(--shadow-offset) var(--blur-radius) var(--dark-shadow);
background-color: var(--background);
&Inset {
box-shadow: inset var(--shadow-offset) var(--shadow-offset) var(--blur-radius) var(--dark-shadow), inset calc(-1 * var(--shadow-offset)) calc(-1 * var(--shadow-offset)) var(--blur-radius) var(--light-shadow);
background-color: var(--background);
&.is-nxxsmall {
--shadow-offset: 2px;
@ -346,6 +348,7 @@ button.neo,.neo {
}
&Plain {
background: var(--background);
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none;

@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Extensions.Options;
namespace decePubClient.Services
{
public class ApiAuthorizationOptionsConfiguration : IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>
{
public void Configure(RemoteAuthenticationOptions<ApiAuthorizationProviderOptions> options)
{
options.UserOptions.RoleClaim ??= "role";
}
public void PostConfigure(string name, RemoteAuthenticationOptions<ApiAuthorizationProviderOptions> options)
{
if (string.Equals(name, Options.DefaultName))
Configure(options);
}
}
}

@ -0,0 +1,56 @@
using decePubClient.Resources;
using Microsoft.Extensions.Localization;
using SocialPub.ClientModels.Resources;
namespace collAnon.Client.Services
{
public sealed class CoalescingStringLocalizer
{
public readonly IStringLocalizer<AllStrings> _pLocalizer;
readonly IStringLocalizer<FieldsNameResource> _fLocalizer;
readonly IStringLocalizer<ErrorsResource> _eLocalizer;
public CoalescingStringLocalizer(
IStringLocalizer<AllStrings> pLocalizer,
IStringLocalizer<FieldsNameResource> fLocalizer,
IStringLocalizer<ErrorsResource> eLocalizer) =>
(_pLocalizer, _fLocalizer, _eLocalizer) =
(pLocalizer, fLocalizer, eLocalizer);
internal LocalizedString this[string name]
{
get
{
if (_pLocalizer[name].ResourceNotFound)
if (_fLocalizer[name].ResourceNotFound)
if (_eLocalizer[name].ResourceNotFound)
return new(name, name, false);
else
return _eLocalizer[name];
else
return _fLocalizer[name];
else
return _pLocalizer[name];
}
}
internal LocalizedString this[string name, params object[] arguments]
{
get
{
if (_pLocalizer[name].ResourceNotFound)
if (_fLocalizer[name].ResourceNotFound)
if (_eLocalizer[name].ResourceNotFound)
return new(name, name, false);
else
return _eLocalizer[name, arguments];
else
return _fLocalizer[name, arguments];
else
return _pLocalizer[name, arguments];
}
}
}
}

@ -1,18 +1,15 @@
using Blazored.Modal.Services;
using decePubClient.Models;
using decePubClient.Models;
using decePubClient.Models.Types;
namespace decePubClient.Services
{
public class MessagesService
{
readonly IModalService modalService;
readonly IStorage storage;
readonly HttpClient http;
public MessagesService(IHttpClientFactory clientFactory, IModalService modalService, IStorage storage)
public MessagesService(IHttpClientFactory clientFactory, IStorage storage)
{
this.modalService = modalService;
this.storage = storage;
http = clientFactory.CreateClient("default");
}

@ -1,14 +1,15 @@
@inject NavigationManager Navigation
@inherits LocalizableComponentBase
@inject NavigationManager Navigation
<AuthorizeView>
<Authorized>
<button class="button" @onclick="BeginSignOut">
@CascadingState.Localizer["Logout"]
@Localizer["Logout"]
</button>
</Authorized>
<NotAuthorized>
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button is-rounded neoBtnSmallPlain" href="authentication/login">
@CascadingState.Localizer["Login"]
@Localizer["Login"]
</NavLink>
</NotAuthorized>
</AuthorizeView>

@ -1,4 +1,6 @@
<nav class="flex justify-between align-center pt-3 px-3 md:p-0">
@inherits LocalizableComponentBase
<nav class="flex justify-between align-center pt-3 px-3 md:p-0">
<div class="relative md:w-52">
<button class="button is-rounded inline-flex is-small neoBtnSmall relative md:hidden" @onclick="ToggleNavMenu">
@ -29,13 +31,13 @@
<span class="icon is-left">
<i class="ion-md-home"></i>
</span>
<span>@CascadingState.Localizer["Home"]</span>
<span>@Localizer["Home"]</span>
</NavLink>
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button has-icons-left is-rounded neoBtnSmallPlain" href="settings">
<span class="icon is-left">
<i class="ion-md-settings"></i>
</span>
<span>@CascadingState.Localizer["Settings"]</span>
<span>@Localizer["Settings"]</span>
</NavLink>
@*<AuthorizeView>
<Authorized>
@ -57,19 +59,19 @@
<span class="icon is-left has-text-danger">
<i class="ion-md-switch"></i>
</span>
<span>@CascadingState.Localizer["Administration"]</span>
<span>@Localizer["Administration"]</span>
</NavLink>
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button has-icons-left is-rounded neoBtnSmallPlain" href="login">
<span class="icon is-left has-text-success">
<i class="ion-md-log-in"></i>
</span>
<span>@CascadingState.Localizer["Login"]</span>
<span>@Localizer["Login"]</span>
</NavLink>
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button has-icons-left is-rounded neoBtnSmallPlain" href="logout">
<span class="icon is-left has-text-danger">
<i class="ion-md-log-out"></i>
</span>
<span>@CascadingState.Localizer["Logout"]</span>
<span>@Localizer["Logout"]</span>
</NavLink>
<div class="field is-grouped is-align-items-center">
@ -156,8 +158,9 @@
if (!CascadingState.Status.IsMobileMedia())
menuToggle = default;
ThemeIsDarkMode = CascadingState.PublicCacheData?.ThemeIsDarkMode ?? false;
ThemeIndexColour = CascadingState.PublicCacheData?.ThemeIndexColour ?? 25;
ThemeIsDarkMode = CascadingState.PublicCacheData?.PageSettings.ThemeIsDarkMode ?? false;
ThemeIndexColour = ThemeIsDarkMode ? CascadingState.PublicCacheData?.PageSettings.DarkThemeIndexColour ?? 215 :
CascadingState.PublicCacheData?.PageSettings.LightThemeIndexColour ?? 25;
}
private void ToggleNavMenu()
@ -168,7 +171,7 @@
protected async Task ResetToOriginalColour()
{
IsThemeChanging = true;
CascadingState.PublicCacheData.ThemeIndexColour =
CascadingState.PublicCacheData.PageSettings.LightThemeIndexColour =
ThemeIndexColour =
25;
@ -187,7 +190,7 @@
{
IsThemeChanging = true;
var indexColour = short.Parse(eventArgs.Value?.ToString());
CascadingState.PublicCacheData.ThemeIndexColour =
CascadingState.PublicCacheData.PageSettings.LightThemeIndexColour =
ThemeIndexColour =
indexColour;
@ -205,7 +208,7 @@
protected async Task UpdateThemeDarkMode(bool isDarkMode)
{
IsThemeChanging = true;
CascadingState.PublicCacheData.ThemeIsDarkMode =
CascadingState.PublicCacheData.PageSettings.ThemeIsDarkMode =
ThemeIsDarkMode = isDarkMode;
Console.WriteLine("Dark updated {0}", ThemeIsDarkMode);

@ -14,8 +14,6 @@
@using Microsoft.Extensions.Logging
@using Microsoft.JSInterop
@using Toolbelt.Blazor.HeadElement
@using Blazored.Modal
@using Blazored.Modal.Services
@using Blazored.LocalStorage
@using Blazor.DownloadFileFast.Interfaces
@using Markdig

@ -7,15 +7,15 @@
//"wwwroot/vendor/open-iconic.css",
"wwwroot/vendor/ionicons.css",
"wwwroot/vendor/toggle-dark-light-mode.css",
"wwwroot/vendor/bulma.css",
"wwwroot/css/main.css",
"wwwroot/vendor/bulma.min.css",
"wwwroot/css/main.min.css",
"wwwroot/vendor/tailwind.css",
"wwwroot/css/tailwind-override.css"
],
"minify": {
"enabled": false,
"gzip": false,
"brotli": false
"gzip": true,
"brotli": true
},
"sourceMap": false
}

@ -3,7 +3,17 @@
"outputFile": "wwwroot/css/main.css",
"inputFile": "SCSS/main.scss",
"minify": {
"enabled": false
"enabled": true
},
"options": {
"sourceMap": false
}
},
{
"outputFile": "wwwroot/vendor/bulma.css",
"inputFile": "SCSS/bulma/bulma.sass",
"minify": {
"enabled": true
},
"options": {
"sourceMap": false

@ -9,21 +9,19 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BlazorZXingJs" Version="0.5.13" />
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
<PackageReference Include="BuildWebCompiler" Version="1.12.405" PrivateAssets="all" />
<PackageReference Include="BundlerMinifier.Core" Version="3.2.449" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Append.Blazor.Notifications" Version="1.1.0" />
<PackageReference Include="BlazorDownloadFileFast" Version="0.2.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.3.0" />
<PackageReference Include="Blazored.Modal" Version="7.1.0" />
<PackageReference Include="DnetIndexedDb" Version="2.4.1" />
<PackageReference Include="Markdig" Version="0.30.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.1" />
<PackageReference Include="Toolbelt.Blazor.HeadElement" Version="7.3.0" />
</ItemGroup>
@ -37,8 +35,6 @@
<AutoGen>True</AutoGen>
<DependentUpon>ErrorMessages.resx</DependentUpon>
</Compile>
<Content Remove="wwwroot\vendor\bulma.css" />
<None Include="wwwroot\vendor\bulma.css" />
<Content Remove="wwwroot\vendor\fontawesome.css" />
<None Include="wwwroot\vendor\fontawesome.css" />
<Content Remove="wwwroot\vendor\ionicons.css" />
@ -85,6 +81,10 @@
<None Include="compilerconfig.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SocialPub\SocialPub\SocialPub.ClientModels\SocialPub.ClientModels.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\AllStrings.resx">
<Generator>ResXFileCodeGenerator</Generator>

@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "decePubClient", "decePubClient.csproj", "{EBE69805-3005-49C2-81E4-A314A19F68B6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocialPub.ClientModels", "..\SocialPub\SocialPub\SocialPub.ClientModels\SocialPub.ClientModels.csproj", "{5DA9311B-B6BC-4EC6-A26E-10367288F52B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -15,6 +17,10 @@ Global
{EBE69805-3005-49C2-81E4-A314A19F68B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBE69805-3005-49C2-81E4-A314A19F68B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBE69805-3005-49C2-81E4-A314A19F68B6}.Release|Any CPU.Build.0 = Release|Any CPU
{5DA9311B-B6BC-4EC6-A26E-10367288F52B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5DA9311B-B6BC-4EC6-A26E-10367288F52B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DA9311B-B6BC-4EC6-A26E-10367288F52B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DA9311B-B6BC-4EC6-A26E-10367288F52B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

@ -1,286 +1,381 @@
:root {
--background: #fadcc7;
--text-color: #7c3a0b;
--placeholder-text-color: rgba(124, 58, 11, 0.3);
--black: #0a0a0a;
--black-bis: #121212;
--black-ter: #242424;
--white: #fffefe;
--primary-color: #fadcc7;
--primary-color-light: white;
--primary-color-dark: #f19c5f;
--primary-gradiend-light: #fce8d9;
--primary-gradiend-dark: #f8d0b4;
--primary-gradiend-lighter: #fdf3ec;
--primary-gradiend-darker: #f7c5a1;
--secondary-color: #c7e5fa;
--secondary-color-light: white;
--secondary-color-dark: #b4dcf8;
--light-shadow: rgba(255, 255, 255, 0.5);
--dark-shadow: rgba(241, 156, 95, 0.5);
--background: hsl(25,84%,88%);
--text-color: hsl(25,84%,26.4%);
--placeholder-text-color: hsla(25,84%,26.4%,.3);
--black: hsl(0, 0%, 4%);
--black-bis: hsl(0, 0%, 7%);
--black-ter: hsl(0, 0%, 14%);
--white: hsl(0, 4%, 99.8%);
--primary-color: hsl(25,84%,88%);
--primary-color-light: hsl(25,84%,100%);
--primary-color-dark: hsl(25,84%,66%);
--primary-gradiend-light: hsl(25,84%,92%);
--primary-gradiend-dark: hsl(25,84%,84%);
--primary-gradiend-lighter: hsl(25,84%,96%);
--primary-gradiend-darker: hsl(25,84%,80%);
--secondary-color: hsl(205, 84%, 88%);
--secondary-color-light: hsl(205, 84%, 100%);
--secondary-color-dark: hsl(205, 84%, 84%);
--light-shadow: hsla(25,84%,100%, .5);
--dark-shadow: hsla(25,84%,66%, .5);
/*--fa-primary-color: hsl(25,84%,26.4%);
--fa-secondary-color: hsl(25,84%,66%);*/
--fa-primary-color: #0b4d7c;
--fa-secondary-color: #5fb4f1;
--fa-primary-color: hsl(205,84%,26.4%);
--fa-secondary-color: hsl(205,84%,66%);
/*--fa-primary-color: hsl(115,84%,26.4%);
--fa-secondary-color: hsl(115,84%,66%);*/
--fa-primary-opacity: 0.80;
--fa-secondary-opacity: 0.80;
--danger-color: #fbd5d5;
--warning-color: #fbf3d5;
--info-color: #d5f1fb;
--plus-green: #71ba40;
--plus-gold: #cab021; }
--danger-color: hsl(0, 82%, 91%);
--warning-color: hsl(48, 82%, 91%);
--info-color: hsl(196, 82%, 91%);
--plus-green: hsl(96, 49%, 49%);
--plus-gold: hsl(51, 72%, 46%);
}
:root {
--shadow-offset: 8px;
--blur-radius: 16px;
--is-inset: inherit; }
--is-inset: inherit;
}
.neomorph {
box-shadow: calc(-1 * var(--shadow-offset)) calc(-1 * var(--shadow-offset)) var(--blur-radius) var(--light-shadow), var(--shadow-offset) var(--shadow-offset) var(--blur-radius) var(--dark-shadow); }
box-shadow: calc(-1 * var(--shadow-offset)) calc(-1 * var(--shadow-offset)) var(--blur-radius) var(--light-shadow), var(--shadow-offset) var(--shadow-offset) var(--blur-radius) var(--dark-shadow);
background-color: var(--background);
}
.neomorphInset {
box-shadow: inset var(--shadow-offset) var(--shadow-offset) var(--blur-radius) var(--dark-shadow), inset calc(-1 * var(--shadow-offset)) calc(-1 * var(--shadow-offset)) var(--blur-radius) var(--light-shadow); }
box-shadow: inset var(--shadow-offset) var(--shadow-offset) var(--blur-radius) var(--dark-shadow), inset calc(-1 * var(--shadow-offset)) calc(-1 * var(--shadow-offset)) var(--blur-radius) var(--light-shadow);
background-color: var(--background);
}
.neomorphInset.is-nxxsmall {
--shadow-offset: 2px;
--blur-radius: 4px; }
--blur-radius: 4px;
}
@media (min-width: 640px) {
.neomorphInset.is-nxxsmall-sm {
--shadow-offset: 2px;
--blur-radius: 4px; } }
--blur-radius: 4px;
}
}
@media (min-width: 768px) {
.neomorphInset.is-nxxsmall-md {
--shadow-offset: 2px;
--blur-radius: 4px; } }
--blur-radius: 4px;
}
}
@media (min-width: 1024px) {
.neomorphInset.is-nxxsmall-lg {
--shadow-offset: 2px;
--blur-radius: 4px; } }
--blur-radius: 4px;
}
}
@media (min-width: 1280px) {
.neomorphInset.is-nxxsmall-xl {
--shadow-offset: 2px;
--blur-radius: 4px; } }
--blur-radius: 4px;
}
}
.neomorphInset.is-nxsmall {
--shadow-offset: 3px;
--blur-radius: 6px; }
--blur-radius: 6px;
}
@media (min-width: 640px) {
.neomorphInset.is-nxsmall-sm {
--shadow-offset: 3px;
--blur-radius: 6px; } }
--blur-radius: 6px;
}
}
@media (min-width: 768px) {
.neomorphInset.is-nxsmall-md {
--shadow-offset: 3px;
--blur-radius: 6px; } }
--blur-radius: 6px;
}
}
@media (min-width: 1024px) {
.neomorphInset.is-nxsmall-lg {
--shadow-offset: 3px;
--blur-radius: 6px; } }
--blur-radius: 6px;
}
}
@media (min-width: 1280px) {
.neomorphInset.is-nxsmall-xl {
--shadow-offset: 3px;
--blur-radius: 6px; } }
--blur-radius: 6px;
}
}
.neomorphInset.is-nsmall {
--shadow-offset: 6px;
--blur-radius: 12px; }
--blur-radius: 12px;
}
@media (min-width: 640px) {
.neomorphInset.is-nsmall-sm {
--shadow-offset: 6px;
--blur-radius: 12px; } }
--blur-radius: 12px;
}
}
@media (min-width: 768px) {
.neomorphInset.is-nsmall-md {
--shadow-offset: 6px;
--blur-radius: 12px; } }
--blur-radius: 12px;
}
}
@media (min-width: 1024px) {
.neomorphInset.is-nsmall-lg {
--shadow-offset: 6px;
--blur-radius: 12px; } }
--blur-radius: 12px;
}
}
@media (min-width: 1280px) {
.neomorphInset.is-nsmall-xl {
--shadow-offset: 6px;
--blur-radius: 12px; } }
--blur-radius: 12px;
}
}
.neomorph.is-nxxsmall {
--shadow-offset: 2px;
--blur-radius: 4px; }
--blur-radius: 4px;
}
@media (min-width: 640px) {
.neomorph.is-nxxsmall-sm {
--shadow-offset: 2px;
--blur-radius: 4px; } }
--blur-radius: 4px;
}
}
@media (min-width: 768px) {
.neomorph.is-nxxsmall-md {
--shadow-offset: 2px;
--blur-radius: 4px; } }
--blur-radius: 4px;
}
}
@media (min-width: 1024px) {
.neomorph.is-nxxsmall-lg {
--shadow-offset: 2px;
--blur-radius: 4px; } }
--blur-radius: 4px;
}
}
@media (min-width: 1280px) {
.neomorph.is-nxxsmall-xl {
--shadow-offset: 2px;
--blur-radius: 4px; } }
--blur-radius: 4px;
}
}
.neomorph.is-nxsmall {
--shadow-offset: 3px;
--blur-radius: 6px; }
--blur-radius: 6px;
}
@media (min-width: 640px) {
.neomorph.is-nxsmall-sm {
--shadow-offset: 3px;
--blur-radius: 6px; } }
--blur-radius: 6px;
}
}
@media (min-width: 768px) {
.neomorph.is-nxsmall-md {
--shadow-offset: 3px;
--blur-radius: 6px; } }
--blur-radius: 6px;
}
}
@media (min-width: 1024px) {
.neomorph.is-nxsmall-lg {
--shadow-offset: 3px;
--blur-radius: 6px; } }
--blur-radius: 6px;
}
}
@media (min-width: 1280px) {
.neomorph.is-nxsmall-xl {
--shadow-offset: 3px;
--blur-radius: 6px; } }
--blur-radius: 6px;
}
}
.neomorph.is-nsmall {
--shadow-offset: 6px;
--blur-radius: 12px; }
--blur-radius: 12px;
}
@media (min-width: 640px) {
.neomorph.is-nsmall-sm {
--shadow-offset: 6px;
--blur-radius: 12px; } }
--blur-radius: 12px;
}
}
@media (min-width: 768px) {
.neomorph.is-nsmall-md {
--shadow-offset: 6px;
--blur-radius: 12px; } }
--blur-radius: 12px;
}
}
@media (min-width: 1024px) {
.neomorph.is-nsmall-lg {
--shadow-offset: 6px;
--blur-radius: 12px; } }
--blur-radius: 12px;
}
}
@media (min-width: 1280px) {
.neomorph.is-nsmall-xl {
--shadow-offset: 6px;
--blur-radius: 12px; } }
--blur-radius: 12px;
}
}
.neomorph.is-nnormal {
--shadow-offset: 8px;
--blur-radius: 16px; }
--blur-radius: 16px;
}
@media (min-width: 640px) {
.neomorph.is-nnormal-sm {
--shadow-offset: 8px;
--blur-radius: 16px; } }
--blur-radius: 16px;
}
}
@media (min-width: 768px) {
.neomorph.is-nnormal-md {
--shadow-offset: 8px;
--blur-radius: 16px; } }
--blur-radius: 16px;
}
}
@media (min-width: 1024px) {
.neomorph.is-nnormal-lg {
--shadow-offset: 8px;
--blur-radius: 16px; } }
--blur-radius: 16px;
}
}
@media (min-width: 1280px) {
.neomorph.is-nnormal-xl {
--shadow-offset: 8px;
--blur-radius: 16px; } }
--blur-radius: 16px;
}
}
button.neoBtn, .neoBtn {
background-image: linear-gradient(145deg, var(--primary-gradiend-light), var(--primary-gradiend-dark));
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none;
-webkit-backface-visibility: hidden;
backface-visibility: hidden; }
backface-visibility: hidden;
}
button.neoBtn:focus, .neoBtn:focus {
background-image: linear-gradient(-45deg, var(--primary-gradiend-light), var(--primary-gradiend-dark));
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none; }
border: none;
}
button.neoBtnSmall, .neoBtnSmall {
background-image: linear-gradient(145deg, var(--primary-gradiend-light), var(--primary-gradiend-dark));
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
border: none; }
border: none;
}
button.neoBtnSmall:focus, .neoBtnSmall:focus {
background-image: linear-gradient(-45deg, var(--primary-gradiend-light), var(--primary-gradiend-dark));
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
border: none; }
border: none;
}
button.neoBtnSmall:focus:not(:active), .neoBtnSmall:focus:not(:active) {
background-image: linear-gradient(-45deg, var(--primary-gradiend-light), var(--primary-gradiend-dark));
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow); }
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
}
button.neoBtnSmall:focus input[type=file], .neoBtnSmall:focus input[type=file] {
background-image: linear-gradient(-45deg, var(--primary-gradiend-light), var(--primary-gradiend-dark)); }
background-image: linear-gradient(-45deg, var(--primary-gradiend-light), var(--primary-gradiend-dark));
}
button.neoBtnSmallPlain, .neoBtnSmallPlain {
background: var(--background);
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none; }
border: none;
}
button.neoBtnSmallPlain:focus, .neoBtnSmallPlain:focus {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none; }
border: none;
}
button.neoBtnSmallInsetPlain, .neoBtnSmallInsetPlain {
background-image: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light));
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
border: none; }
border: none;
}
button.neoBtnSmallInsetPlain:focus, .neoBtnSmallInsetPlain:focus {
background-image: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
border: none; }
border: none;
}
button.neoBtnSmallXInsetPlain, .neoBtnSmallXInsetPlain {
background-image: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light));
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
border: none; }
border: none;
}
button.neoBtnSmallXInsetPlain:focus, .neoBtnSmallXInsetPlain:focus {
background-image: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
border: none; }
border: none;
}
button.neoBtnInsetPlain, .neoBtnInsetPlain {
background-image: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light));
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
border: none; }
border: none;
}
button.neoBtnInsetPlain:focus, .neoBtnInsetPlain:focus {
background-image: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
border: none; }
border: none;
}
button.neoBtnInsetPlain:focus:not(:active), .neoBtnInsetPlain:focus:not(:active) {
background-image: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow); }
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
}
button.neoFile, .neoFile {
box-shadow: 0px 0px 0px var(--light-shadow), 0px 0px 0px var(--dark-shadow);
transition: all .2s linear;
transition: all 0.2s linear;
-webkit-backface-visibility: hidden;
backface-visibility: hidden; }
backface-visibility: hidden;
}
button.neoFile:hover, .neoFile:hover {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow); }
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
}
button.neoFile:focus-visible, .neoFile:focus-visible {
background: linear-gradient(-45deg, var(--primary-gradiend-light), var(--primary-gradiend-dark));
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
outline: none; }
outline: none;
}
button.neoFile.isSelected, .neoFile.isSelected {
background: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light));
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow); }
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
}
button.neoFile.isSelected:focus-visible, .neoFile.isSelected:focus-visible {
background: linear-gradient(-45deg, var(--primary-gradiend-lighter), var(--primary-gradiend-darker));
outline: none; }
outline: none;
}
button.neoFile.is-active, button.neoFile.active, .neoFile.is-active, .neoFile.active {
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
color: var(--black); }
color: var(--black);
}
button.neoInput, .neoInput {
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
background: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light));
border: none; }
border: none;
}
button.neoInput:focus, .neoInput:focus {
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
border: none; }
border: none;
}
button.neoSelect > select, .neoSelect > select {
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
background: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light));
border: none; }
border: none;
}
button.neoSelect > select:focus, .neoSelect > select:focus {
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
border: none; }
border: none;
}
.neoCheckbox {
opacity: 0;
width: 0; }
width: 0;
}
.neoCheckbox:focus + label:before {
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter)) !important; }
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter)) !important;
}
.neoCheckboxContainer {
position: relative; }
position: relative;
}
.neoCheckbox + label {
padding: .15rem .15rem .15rem 2rem;
padding: 0.15rem 0.15rem 0.15rem 2rem;
cursor: pointer;
font-size: 1rem;
line-height: 1.5; }
line-height: 1.5;
}
.neoCheckbox + label:before {
animation-name: none;
width: 1.5rem;
@ -289,20 +384,22 @@ button.neoSelect > select, .neoSelect > select {
position: absolute;
left: 0;
top: 0rem;
content: '';
content: "";
border: none;
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
background: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light)) !important; }
background: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light)) !important;
}
.neoCheckbox:checked.is-checked-bold + label {
font-weight: bold; }
font-weight: bold;
}
.neoCheckbox:checked + label:after {
display: inline-block;
width: .375rem;
height: .6rem;
top: .35rem;
left: .55rem;
width: 0.375rem;
height: 0.6rem;
top: 0.35rem;
left: 0.55rem;
transform: translateY(0rem) rotate(45deg);
border-width: .1rem;
border-width: 0.1rem;
border-top-width: 0.1rem;
border-left-width: 0.1rem;
border-style: solid;
@ -312,29 +409,33 @@ button.neoSelect > select, .neoSelect > select {
border-top: 0;
border-left: 0;
position: absolute;
content: ''; }
content: "";
}
hr.neoSeparatorFlat {
height: 10px;
border: none;
border-radius: 20px;
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
background: var(--background); }
background: var(--background);
}
hr.neoSeparatorPressed {
height: 10px;
border: none;
border-radius: 20px;
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
background: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light)); }
background: linear-gradient(145deg, var(--primary-gradiend-dark), var(--primary-gradiend-light));
}
input.neoRange[type=range] {
height: 30px;
-webkit-appearance: none;
width: 100%;
background: transparent; }
background: transparent;
}
input.neoRange[type=range]:focus {
outline: none; }
outline: none;
}
input.neoRange[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 20px;
@ -342,7 +443,8 @@ input.neoRange[type=range] {
animate: 0.2s;
border-radius: 20px;
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
background: linear-gradient(90deg, #fa9e9e, #faad9e, #fabd9e, #facc9e, #fadb9e, #faeb9e, #fafa9e, #ebfa9e, #dbfa9e, #ccfa9e, #bdfa9e, #adfa9e, #9efa9e, #9efaad, #9efabd, #9efacc, #9efadb, #9efaeb, #9efafa, #9eebfa, #9edbfa, #9eccfa, #9ebdfa, #9eadfa, #9e9efa, #ad9efa, #bd9efa, #cc9efa, #db9efa, #eb9efa, #fa9efa, #fa9eeb, #fa9edb, #fa9ecc, #fa9ebd, #fa9ead, #fa9ea0) !important; }
background: linear-gradient(90deg, hsl(0deg, 90%, 80%), hsl(10deg, 90%, 80%), hsl(20deg, 90%, 80%), hsl(30deg, 90%, 80%), hsl(40deg, 90%, 80%), hsl(50deg, 90%, 80%), hsl(60deg, 90%, 80%), hsl(70deg, 90%, 80%), hsl(80deg, 90%, 80%), hsl(90deg, 90%, 80%), hsl(100deg, 90%, 80%), hsl(110deg, 90%, 80%), hsl(120deg, 90%, 80%), hsl(130deg, 90%, 80%), hsl(140deg, 90%, 80%), hsl(150deg, 90%, 80%), hsl(160deg, 90%, 80%), hsl(170deg, 90%, 80%), hsl(180deg, 90%, 80%), hsl(190deg, 90%, 80%), hsl(200deg, 90%, 80%), hsl(210deg, 90%, 80%), hsl(220deg, 90%, 80%), hsl(230deg, 90%, 80%), hsl(240deg, 90%, 80%), hsl(250deg, 90%, 80%), hsl(260deg, 90%, 80%), hsl(270deg, 90%, 80%), hsl(280deg, 90%, 80%), hsl(290deg, 90%, 80%), hsl(300deg, 90%, 80%), hsl(310deg, 90%, 80%), hsl(320deg, 90%, 80%), hsl(330deg, 90%, 80%), hsl(340deg, 90%, 80%), hsl(350deg, 90%, 80%), hsl(359deg, 90%, 80%)) !important;
}
input.neoRange[type=range]::-webkit-slider-thumb {
border: none;
height: 26px;
@ -352,17 +454,20 @@ input.neoRange[type=range] {
background: linear-gradient(145deg, var(--primary-gradiend-light), var(--primary-gradiend-dark)) !important;
cursor: pointer;
-webkit-appearance: none;
margin-top: -3px; }
margin-top: -3px;
}
input.neoRange[type=range]:focus::-webkit-slider-runnable-track {
background: linear-gradient(90deg, #fa9e9e, #faad9e, #fabd9e, #facc9e, #fadb9e, #faeb9e, #fafa9e, #ebfa9e, #dbfa9e, #ccfa9e, #bdfa9e, #adfa9e, #9efa9e, #9efaad, #9efabd, #9efacc, #9efadb, #9efaeb, #9efafa, #9eebfa, #9edbfa, #9eccfa, #9ebdfa, #9eadfa, #9e9efa, #ad9efa, #bd9efa, #cc9efa, #db9efa, #eb9efa, #fa9efa, #fa9eeb, #fa9edb, #fa9ecc, #fa9ebd, #fa9ead, #fa9ea0) !important; }
background: linear-gradient(90deg, hsl(0deg, 90%, 80%), hsl(10deg, 90%, 80%), hsl(20deg, 90%, 80%), hsl(30deg, 90%, 80%), hsl(40deg, 90%, 80%), hsl(50deg, 90%, 80%), hsl(60deg, 90%, 80%), hsl(70deg, 90%, 80%), hsl(80deg, 90%, 80%), hsl(90deg, 90%, 80%), hsl(100deg, 90%, 80%), hsl(110deg, 90%, 80%), hsl(120deg, 90%, 80%), hsl(130deg, 90%, 80%), hsl(140deg, 90%, 80%), hsl(150deg, 90%, 80%), hsl(160deg, 90%, 80%), hsl(170deg, 90%, 80%), hsl(180deg, 90%, 80%), hsl(190deg, 90%, 80%), hsl(200deg, 90%, 80%), hsl(210deg, 90%, 80%), hsl(220deg, 90%, 80%), hsl(230deg, 90%, 80%), hsl(240deg, 90%, 80%), hsl(250deg, 90%, 80%), hsl(260deg, 90%, 80%), hsl(270deg, 90%, 80%), hsl(280deg, 90%, 80%), hsl(290deg, 90%, 80%), hsl(300deg, 90%, 80%), hsl(310deg, 90%, 80%), hsl(320deg, 90%, 80%), hsl(330deg, 90%, 80%), hsl(340deg, 90%, 80%), hsl(350deg, 90%, 80%), hsl(359deg, 90%, 80%)) !important;
}
input.neoRange[type=range]::-moz-range-track {
width: 100%;
height: 20px;
cursor: pointer;
border-radius: 20px;
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
background: linear-gradient(90deg, #fa9e9e, #faad9e, #fabd9e, #facc9e, #fadb9e, #faeb9e, #fafa9e, #ebfa9e, #dbfa9e, #ccfa9e, #bdfa9e, #adfa9e, #9efa9e, #9efaad, #9efabd, #9efacc, #9efadb, #9efaeb, #9efafa, #9eebfa, #9edbfa, #9eccfa, #9ebdfa, #9eadfa, #9e9efa, #ad9efa, #bd9efa, #cc9efa, #db9efa, #eb9efa, #fa9efa, #fa9eeb, #fa9edb, #fa9ecc, #fa9ebd, #fa9ead, #fa9ea0) !important;
border: none; }
background: linear-gradient(90deg, hsl(0deg, 90%, 80%), hsl(10deg, 90%, 80%), hsl(20deg, 90%, 80%), hsl(30deg, 90%, 80%), hsl(40deg, 90%, 80%), hsl(50deg, 90%, 80%), hsl(60deg, 90%, 80%), hsl(70deg, 90%, 80%), hsl(80deg, 90%, 80%), hsl(90deg, 90%, 80%), hsl(100deg, 90%, 80%), hsl(110deg, 90%, 80%), hsl(120deg, 90%, 80%), hsl(130deg, 90%, 80%), hsl(140deg, 90%, 80%), hsl(150deg, 90%, 80%), hsl(160deg, 90%, 80%), hsl(170deg, 90%, 80%), hsl(180deg, 90%, 80%), hsl(190deg, 90%, 80%), hsl(200deg, 90%, 80%), hsl(210deg, 90%, 80%), hsl(220deg, 90%, 80%), hsl(230deg, 90%, 80%), hsl(240deg, 90%, 80%), hsl(250deg, 90%, 80%), hsl(260deg, 90%, 80%), hsl(270deg, 90%, 80%), hsl(280deg, 90%, 80%), hsl(290deg, 90%, 80%), hsl(300deg, 90%, 80%), hsl(310deg, 90%, 80%), hsl(320deg, 90%, 80%), hsl(330deg, 90%, 80%), hsl(340deg, 90%, 80%), hsl(350deg, 90%, 80%), hsl(359deg, 90%, 80%)) !important;
border: none;
}
input.neoRange[type=range]::-moz-range-thumb {
border: none;
height: 26px;
@ -370,78 +475,98 @@ input.neoRange[type=range] {
border-radius: 20px;
box-shadow: 2px 2px 4px var(--dark-shadow), -2px -2px 4px var(--light-shadow);
background: linear-gradient(145deg, var(--primary-gradiend-light), var(--primary-gradiend-dark)) !important;
cursor: pointer; }
cursor: pointer;
}
body {
color: var(--text-color);
background-color: var(--background); }
background-color: var(--background);
}
.background {
background-color: var(--background); }
background-color: var(--background);
}
details > summary::-webkit-details-marker {
color: var(--text-color); }
color: var(--text-color);
}
:focus-visible {
outline: none; }
outline: none;
}
*, ::after, ::before {
scrollbar-color: inherit;
scrollbar-width: inherit; }
scrollbar-width: inherit;
}
::-webkit-scrollbar {
width: 8px; }
width: 8px;
}
::-webkit-scrollbar-button {
width: 8px;
height: 5px; }
height: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
border: thin solid transparent;
box-shadow: none;
border-radius: 10px; }
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--primary-color-dark);
border: thin solid transparent;
border-radius: 10px; }
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--primary-color-dark); }
background: var(--primary-color-dark);
}
::-moz-selection {
/* Code for Firefox */
::-moz-selection { /* Code for Firefox */
color: var(--primary-color);
background: var(--primary-color-dark); }
background: var(--primary-color-dark);
}
::selection {
color: var(--primary-color);
background: var(--primary-color-dark); }
background: var(--primary-color-dark);
}
.flex.flex-col-reverse > div:first-child {
margin-top: 1rem; }
margin-top: 1rem;
}
.loadAnimation span {
animation-name: blink;
animation-duration: 1.4s;
animation-iteration-count: infinite;
animation-fill-mode: both; }
animation-fill-mode: both;
}
.loadAnimation span:nth-child(1) {
animation-delay: .4s; }
animation-delay: 0.4s;
}
.loadAnimation span:nth-child(2) {
animation-delay: .3s; }
animation-delay: 0.3s;
}
.loadAnimation span:nth-child(3) {
animation-delay: .2s; }
animation-delay: 0.2s;
}
.loadAnimation span:nth-child(4) {
animation-delay: .2s; }
animation-delay: 0.2s;
}
.loadAnimation span:nth-child(5) {
animation-delay: .3s; }
animation-delay: 0.3s;
}
.loadAnimation span:nth-child(6) {
animation-delay: .4s; }
animation-delay: 0.4s;
}

1
wwwroot/css/main.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Index</title>
<base href="/" />
<link href="_content/Blazored.Modal/blazored-modal.css" rel="stylesheet"/>
<link href="css/style.min.css" rel="stylesheet" />
<link href="manifest.json" rel="manifest" />
<link rel="apple-touch-icon" sizes="512x512" href="imgs/icon-512.png" />
@ -20,8 +19,6 @@
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
<script src="_content/Append.Blazor.Notifications/scripts.js" type="module"></script>
<script src="_content/Blazored.Modal/blazored.modal.js"></script>
<script src="rxjs.7.4.0.min.js"></script>
<script src="_content/DnetIndexedDb/dnet-indexeddb.js"></script>
<script src="_content/Toolbelt.Blazor.HeadElement.Services/script.min.js"></script>

2961
wwwroot/vendor/bulma.css vendored

File diff suppressed because it is too large Load Diff

2
wwwroot/vendor/bulma.min.css vendored Normal file

File diff suppressed because one or more lines are too long