This commit is contained in:
Eugenio Chiodo 2022-02-14 01:51:52 +01:00
parent ba4a4b14c8
commit 5cba76554a
84 changed files with 18148 additions and 11553 deletions

4
.gitignore vendored
View File

@ -360,4 +360,6 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
/.idea/.idea.decePubClient/.idea
/.idea/config

View File

@ -1,12 +1,27 @@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
<CascadingState>
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView DefaultLayout="@typeof(MainLayout)" RouteData="@routeData">
<NotAuthorized>
@if (context.User.Identity?.IsAuthenticated != true)
{
<RedirectToLogin/>
}
else
{
<p role="alert">You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="section"/>
</Found>
<NotFound>
<Title>Not found</Title>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
</CascadingState>

View File

@ -1,5 +1,106 @@
<h3>ActionBar</h3>
<section class="flex flex-col space-y-4">
@if (isPosting)
{
<div class="block p-3 md:p-4 neomorph rounded-xl is-nxsmall">
<MessageUpsertForm OnMessageSubmit="OnMessageSubmit" />
</div>
}
<div class="flex justify-between space-x-3">
<EditForm Model="Filters">
<div class="field flex flex-row space-x-3">
@if (OnTimelineChanged.HasDelegate)
{
<div class="control has-icons-left">
<span class="icon is-left">
<i class="@Filters.TimelineType.GetTimelineTypeIcon()"></i>
</span>
<span class="select is-rounded neoSelect">
<InputSelect TValue="TimelineType" Value="Filters.TimelineType"
ValueChanged="async v => await OnTimelineChange(v)" ValueExpression="() => Filters.TimelineType">
@foreach (var timelineType in Enum.GetValues<TimelineType>())
{
<option value="@timelineType">@CascadingState.Localizer[timelineType.ToString()]</option>
}
</InputSelect>
</span>
</div>
}
@if (OnTimeSortingChanged.HasDelegate)
{
<div class="control has-icons-left">
<span class="icon is-left">
<i class="ion-md-funnel"></i>
</span>
<span class="select is-rounded neoSelect">
<InputSelect TValue="TimeSortingType" Value="Filters.TimeSortingType"
ValueChanged="async v => await OnTimeSortChange(v)" ValueExpression="() => Filters.TimeSortingType">
@foreach (var timeSortingType in Enum.GetValues<TimeSortingType>())
{
<option value="@timeSortingType">@CascadingState.Localizer[timeSortingType.ToString()]</option>
}
</InputSelect>
</span>
</div>
}
</div>
</EditForm>
@if (OnMessageSubmit.HasDelegate)
{
<div>
<button class="button is-rounded @SUtility.IfTrueThen(isPosting, "neoBtnInsetPlain", "neoBtn")"
@onclick="OpenCloseMessageForm">
<span class="icon is-left">
<i class="ion-md-create"></i>
</span>
<span>@CascadingState.Localizer["Post"]</span>
</button>
</div>
}
else
{
<div></div>
}
</div>
</section>
@code {
[CascadingParameter] CascadingState CascadingState { get; set; }
[Parameter] public EventCallback<MessageForm> OnMessageSubmit { get; set; }
[Parameter] public EventCallback<TimelineType> OnTimelineChanged { get; set; }
[Parameter] public EventCallback<TimeSortingType> OnTimeSortingChanged { get; set; }
[Inject] ILocalStorageService LocalStorage { get; set; }
bool isPosting { get; set; } = false;
ActionBarFilter Filters { get; set; } = new();
protected override async Task OnInitializedAsync()
{
var filters = await LocalStorage.GetItemAsync<ActionBarFilter>(nameof(ActionBarFilter));
if (filters == default)
await LocalStorage.SetItemAsync(nameof(ActionBarFilter), Filters);
else
Filters = filters;
}
void OpenCloseMessageForm()
{
isPosting = !isPosting;
}
async Task OnTimelineChange(TimelineType timelineType)
{
Filters.TimelineType = timelineType;
await LocalStorage.SetItemAsync(nameof(ActionBarFilter), Filters);
await OnTimelineChanged.InvokeAsync(Filters.TimelineType);
}
async Task OnTimeSortChange(TimeSortingType timeSorting)
{
Filters.TimeSortingType = timeSorting;
await LocalStorage.SetItemAsync(nameof(ActionBarFilter), Filters);
await OnTimeSortingChanged.InvokeAsync(Filters.TimeSortingType);
}
}

248
Components/Content.razor Normal file
View File

@ -0,0 +1,248 @@
<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)');">
<a class="block h-12 w-12 md:h-16 md:w-16" href="@Message.User.ProfileUrl" title="@Message.User.UserName">
<img alt="@Message.User.UserName" class="h-12 w-12 md:h-16 md:w-16 object-cover rounded-full neomorph is-nxxsmall" src="@(Message.User.PictureUrl ?? "/imgs/icon-192.png")" />
</a>
</div>
<div class="flex flex-col space-y-3 flex-1 py-3 pr-3 md:py-4 md:pr-4 min-w-0">
<div class="flex flex-col space-y-1 flex-1 min-w-0">
<p class="inline-flex flex-1 space-x-2 min-w-0 justify-between text-xs md:text-sm">
<span class="inline-flex space-x-2 min-w-0">
<b class="shrink truncate max-w-[80%]" title="@Message.User.DisplayName">
@Message.User.DisplayName
</b>
<a class="underline flex-1 min-w-6 opacity-50 truncate self-center text-xs" href="@Message.User.ProfileUrl" title="@Message.User.UserName">
@Message.User.UserName
</a>
</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)
</span>
<i aria-hidden="true" class="@Message.MessageType.GetMessageTypeIcon() text-md"
title="@CascadingState.Localizer[Message.MessageType.ToString()]">
</i>
</span>
</p>
@if (Message.Title is { Length: > 0 })
{
<p class="text-sm md:text-base font-bold break-all">@Message.Title</p>
}
@if (Message.Content is { Length: > 0 })
{
<div class="text-sm md:text-base break-all">
@((MarkupString)Message.Content)
</div>
}
@if (Message.Medias.Count != 0)
{
<div class="grid gap-4 auto-cols-auto grid-rows-1 grid-flow-col-dense">
@foreach (var media in Message.Medias)
{
if (media.ContentType.StartsWith("image"))
{
<a class="w-auto" href="@media.Url">
<img alt="@media.AltText" class="w-full rounded-lg @SUtility.IfTrueThen(Message.Medias.Count > 1, "max-h-[30vh]")" src="@media.Url" title="@media.AltText">
</a>
}
else if (media.ContentType.StartsWith("video"))
{
<video class="w-full max-h-[50vh] aspect-video rounded-lg mx-auto neomorphInset is-nxxsmall" controls="controls" playsinline="playsinline" preload="metadata" title="@media.FileName">
<source src="@media.Url" type="@media.ContentType" />
</video>
}
else if (media.ContentType.StartsWith("audio"))
{
<audio class="w-full max-h-8" controls="controls" preload="metadata" title="@media.FileName">
<source src="@media.Url" type="@media.ContentType" />
</audio>
}
else
{
<div class="flex items-center space-x-3 align-center rounded-lg p-3 md:p-4 neomorph is-nxxsmall">
<span>
<i class="text-2xl ion-md-document"></i>
</span>
<div class="flex flex-col w-full space-y-1">
<p class="text-xs md:text-sm break-all">
<b>@media.FileName</b>
</p>
<p class="text-xs break-all">
<i class="ion-md-code"></i> @media.ContentType
</p>
</div>
<button class="button is-small is-rounded neoBtnSmall" @onclick="async () => await OnMessageMediaDownload.InvokeAsync(media)" type="button">
<span class="icon">
<i class="ion-md-download text-base"></i>
</span>
</button>
</div>
}
}
</div>
}
</div>
<div class="flex space-x-3 mt-3 justify-between">
<div class="flex space-x-3">
@if (OnMessageReply.HasDelegate)
{
<button class="button is-small is-rounded @(isAnswering ? "neoBtnSmallInsetPlain" : "neoBtnSmall")" @onclick="Reply"
title="@CascadingState.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>
</button>
}
@if (OnMessageBoost.HasDelegate)
{
<button class="button is-small is-rounded @(Message.IsBoosted ? "neoBtnSmallInsetPlain" : "neoBtnSmall")" @onclick="() => OnMessageBoost.InvokeAsync(Message)"
title="@CascadingState.Localizer["Boost"]">
<span class="icon">
<i aria-hidden="true" class="ion-md-repeat text-lg has-text-info"></i>
</span>
</button>
}
@if (OnMessageFavourite.HasDelegate)
{
<button class="button is-small is-rounded @SUtility.IfTrueThen(Message.IsFavourite, "neoBtnSmallInsetPlain", "neoBtnSmall")" @onclick="() => OnMessageFavourite.InvokeAsync(Message)"
title="@CascadingState.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>
</button>
}
</div>
<div class="flex space-x-3">
<DropdownButton IsOpen="Message.IsOptionsOpen">
<DropdownTrigger>
<button class="button is-small is-rounded neoBtnSmall" @onclick="() => Message.IsOptionsOpen = !Message.IsOptionsOpen"
title="@CascadingState.Localizer["Other"]">
<span class="icon">
<i aria-hidden="true" class="ion-md-more text-lg"></i>
</span>
</button>
</DropdownTrigger>
<DropdownContent>
@if (OnUserDirectMessage.HasDelegate)
{
<div class="dropdown-item">
<button class="button is-small is-rounded has-icons-left neoBtnSmall" @onclick="() => OnUserDirectMessage.InvokeAsync(Message)">
<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>
</button>
</div>
}
@if (OnUserSilence.HasDelegate)
{
<div class="dropdown-item">
<button class="button is-small is-rounded has-icons-left neoBtnSmall" @onclick="() => OnUserSilence.InvokeAsync(Message.User)">
<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>
</button>
</div>
}
@if (OnUserBlock.HasDelegate)
{
<div class="dropdown-item">
<button class="button is-small is-rounded has-icons-left neoBtnSmall" @onclick="() => OnUserBlock.InvokeAsync(Message.User)">
<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>
</button>
</div>
}
@if (@*Message.User.UserName == CurrentUserName &&*@ OnMessageDelete.HasDelegate)
{
<div class="dropdown-item">
<button class="button is-small has-icons-left is-rounded neoBtnSmall" @onclick="DeleteMessage"
title="@CascadingState.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>
</button>
</div>
}
</DropdownContent>
</DropdownButton>
@if (IncludeExpand)
{
<NavLink class="button is-small is-rounded neoBtnSmall" href="@($"expand/{Message.MessageId}")"
title="@CascadingState.Localizer["Expand"]">
<span class="icon">
<i aria-hidden="true" class="ion-md-expand text-lg"></i>
</span>
</NavLink>
}
</div>
</div>
@if (isAnswering)
{
<MessageUpsertForm AnsweringMessage="Message" OnMessageSubmit="SubmitReply"></MessageUpsertForm>
}
</div>
</div>
@code {
[CascadingParameter] Task<AuthenticationState> AuthState { get; set; }
[CascadingParameter] CascadingState CascadingState { get; set; }
[Parameter] public Message Message { get; set; } = new();
[Parameter] public EventCallback<MessageForm> OnMessageReply { get; set; }
[Parameter] public EventCallback<Message> OnMessageBoost { get; set; }
[Parameter] public EventCallback<Message> OnMessageFavourite { get; set; }
[Parameter] public EventCallback<Message> OnMessageDelete { get; set; }
[Parameter] public EventCallback<Message> OnUserDirectMessage { get; set; }
[Parameter] public EventCallback<Media> OnMessageMediaDownload { get; set; }
[Parameter] public EventCallback<MessageUser> OnUserBlock { get; set; }
[Parameter] public EventCallback<MessageUser> OnUserSilence { get; set; }
[Parameter] public string CssContainer { get; set; }
[Parameter] public bool IncludeExpand { get; set; } = true;
bool isAnswering { get; set; } = false;
string CurrentUserName
{
get
{
return AuthState.Result.User.Identity?.Name;
}
}
async Task DeleteMessage()
{
isAnswering = false;
await OnMessageDelete.InvokeAsync(Message);
}
async Task SubmitReply(MessageForm messageForm)
{
isAnswering = false;
await OnMessageReply.InvokeAsync(messageForm);
}
async Task Reply()
{
await Task.Run(() =>
{
});
isAnswering = !isAnswering;
}
}

View File

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

View File

@ -1,7 +1,430 @@
<EditForm Model="">
<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"
ValueChanged="(v) => OnTitleChanged(v)"
ValueExpression="() => MessageForm.Title" />
</div>
<div class="control relative mt-1">
<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>
<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 })
{
<div class="control relative mt-1 px-8">
<div class="neomorphInset rounded-t-lg rounded-b-[1.4rem] px-2 py-1 md:px-3 md:py-2 text-xs md:text-sm">
@switch (MessageForm.ContentType)
{
case ContentType.Markdown:
@((MarkupString)Markdown.ToHtml(MessageForm.Content))
break;
case ContentType.HTML:
<p>@((MarkupString)MessageForm.Content)</p>
break;
}
</div>
</div>
}
<div class="help is-danger">
<ValidationMessage For="() => MessageForm.Content" />
@contentError
</div>
</div>
<div class="flex flex-col space-y-3 mb-3 @SUtility.IfTrueThen(MessageForm.Media.Count == 0, "hidden")">
@foreach (var media in MessageForm.Media)
{
switch (MessageForm.MediaType)
{
case MediaType.Images:
<div class="flex w-full items-center space-x-3 rounded-xl p-3 md:p-4 neomorph is-nxxsmall">
<img alt="@media.AltText" class="object-cover rounded-lg neomorph is-nxxsmall max-h-24 md:max-h-40 max-w-[6rem] md:max-w-[12rem]" src="@media.Base64Preview" />
<div class="flex w-full self-start flex-col justify-between space-y-2">
<div class="flex w-full space-x-3">
<div class="flex-1">
<p class="text-xs md:text-sm break-all">
<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)
</p>
</div>
<button class="button is-small is-rounded self-start neoBtnSmall" @onclick="() => RemoveFile(media)" type="button">
<span class="icon">
<i class="ion-md-trash text-base text-red-400"></i>
</span>
</button>
</div>
<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" />
</div>
</div>
</div>
</div>
break;
case MediaType.Video:
case MediaType.Documents:
<div class="flex items-center space-x-3 align-center rounded-xl p-3 md:p-4 neomorph is-nxxsmall">
<span>
<i class="text-2xl @MessageForm.MediaType.GetMediaTypeIcon()"></i>
</span>
<div class="flex flex-col w-full space-y-1">
<p class="text-xs md:text-sm break-all">
<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)
</p>
</div>
<button class="button is-small is-rounded neoBtnSmall" @onclick="() => RemoveFile(media)" type="button">
<span class="icon">
<i class="ion-md-trash text-base text-red-400"></i>
</span>
</button>
</div>
break;
case MediaType.Audio:
<div class="flex items-center space-x-3 align-center rounded-xl p-3 md:p-4 neomorph is-nxxsmall">
<span>
<i class="text-2xl @MessageForm.MediaType.GetMediaTypeIcon()"></i>
</span>
<div class="flex flex-col w-full space-y-1">
<p class="text-xs md:text-sm break-all">
<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)
</p>
<audio controls="controls" class="w-full max-h-8">
<source src="@media.Base64Preview" type="@media.ContentType">
</audio>
</div>
<button class="button is-small is-rounded neoBtnSmall" @onclick="() => RemoveFile(media)" type="button">
<span class="icon">
<i class="ion-md-trash text-base text-red-400"></i>
</span>
</button>
</div>
break;
}
}
</div>
@if (fileInputErrorMessage is { Length: > 0 })
{
<div class="help is-danger p-1 md:p-2 mb-3 rounded-xl neomorphInset is-nxxsmall">
@((MarkupString)fileInputErrorMessage)
</div>
}
<div class="flex justify-between space-x-3 h-[30px]">
<div class="flex space-x-3">
<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">
<span class="icon">
<i class="@MessageForm.MessageType.GetMessageTypeIcon() text-base"></i>
</span>
</button>
</DropdownTrigger>
<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">
<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">
<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">
<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">
<span class="icon">
<i class="ion-md-globe text-base"></i>
</span>
</button>
</div>
</DropdownContent>
</DropdownButton>
<div class="file is-small">
<label class="file-label overflow-visible rounded-full neoBtnSmall h-[30px]">
<InputFile accept="@acceptedFilesTypes" class="file-input" multiple="@ShouldHaveMultipleUpload()" OnChange="OnFileChange" disabled="@ShouldDisableUpload()" />
<span class="file-cta">
<span class="file-icon @SUtility.IfTrueThen(MessageForm.Media.Count == 0, "mr-0")">
<i class="ion-md-attach text-base"></i>
</span>
@if (MessageForm.Media.Count != 0)
{
<span class="file-label">+@MessageForm.Media.Count</span>
}
</span>
</label>
</div>
<div class="field">
<div class="control has-icons-left">
<div class="select is-small is-rounded neoSelect">
<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>
}
</InputSelect>
</div>
<span class="icon is-small is-left">
<i class="@MessageForm.MediaType.GetMediaTypeIcon()"></i>
</span>
</div>
</div>
<div class="field">
<div class="control has-icons-left">
<div class="select is-small is-rounded neoSelect">
<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>
}
</InputSelect>
</div>
<span class="icon is-small is-left">
<i class="@MessageForm.ContentType.GetContentTypeIcon()"></i>
</span>
<div class="help is-danger">
<ValidationMessage For="() => MessageForm.ContentType" />
</div>
</div>
</div>
@if (showPreviewButton)
{
<button class="button is-small is-rounded @SUtility.IfTrueThen(isPreviewOpen, "neoBtnSmallInsetPlain", "neoBtnSmall")" @onclick="OnOpenClosePreview"
title="@CascadingState.Localizer["Show preview"]" type="button">
<span class="icon">
<i class="@SUtility.IfTrueThen(isPreviewOpen, "ion-md-eye-off", "ion-md-eye") text-base"></i>
</span>
</button>
}
</div>
<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 class="icon is-right">
<i class="ion-md-send"></i>
</span>
</button>
</div>
</div>
</EditForm>
@code {
[CascadingParameter] CascadingState CascadingState { get; set; }
[Parameter] public Message AnsweringMessage { get; set; }
[Parameter] public EventCallback<MessageForm> OnMessageSubmit { get; set; }
MessageForm MessageForm { get; set; } = new();
int totalCharacters { get; set; } = 0;
string fileInputErrorMessage { get; set; }
string contentError { get; set; }
string acceptedFilesTypes { get; set; } = ".jpg,.jpeg,.png,.gif";
bool showPreviewButton { get; set; } = false;
bool isPreviewOpen { get; set; } = false;
void OpenCloseMessageType()
{
MessageForm.IsScopeOptionsOpen = !MessageForm.IsScopeOptionsOpen;
}
void UpdateMessageType(MessageType messageType)
{
MessageForm.MessageType = messageType;
MessageForm.IsScopeOptionsOpen = false;
}
protected override void OnInitialized()
{
if (AnsweringMessage != default)
{
MessageForm.Title = AnsweringMessage.Title;
MessageForm.RootMessageId = AnsweringMessage.RootMessageId ?? AnsweringMessage.MessageId;
}
}
bool ShouldDisableUpload()
{
switch (MessageForm.MediaType)
{
case MediaType.Images:
return MessageForm.Media.Count == 5;
case MediaType.Video:
case MediaType.Audio:
return MessageForm.Media.Count == 1;
case MediaType.Documents:
return MessageForm.Media.Count == 3;
default:
return true;
}
}
bool ShouldHaveMultipleUpload()
{
return MessageForm.MediaType is MediaType.Images or MediaType.Documents;
}
async Task OnFileChange(InputFileChangeEventArgs eventArgs)
{
try
{
fileInputErrorMessage = string.Empty;
var maximumFileCount = MessageForm.MediaType switch
{
MediaType.Images => 5,
MediaType.Audio => 1,
MediaType.Video => 1,
MediaType.Documents => 3
};
if (eventArgs.FileCount > maximumFileCount)
{
fileInputErrorMessage = string.Format(CascadingState.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}");
return;
}
var maxAllowedSize = MessageForm.MediaType switch
{
MediaType.Images => 3_145_728,
MediaType.Audio => 5_242_880,
MediaType.Video => 20_971_520,
MediaType.Documents => 3_145_728
};
var uploadMedia = default(UploadMedia);
using (var memStream = new MemoryStream())
foreach (var file in eventArgs.GetMultipleFiles(maximumFileCount))
{
if (file.Name == default || file.ContentType == default) continue;
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/>";
continue;
}
uploadMedia = new()
{
FileName = file.Name,
ContentType = file.ContentType,
Size = file.Size
};
try
{
using (var imgStream = file.OpenReadStream(maxAllowedSize))
{
await imgStream.CopyToAsync(memStream);
memStream.Position = 0;
uploadMedia.Blob = memStream.ToArray();
await memStream.FlushAsync();
}
}
catch (IOException e)
{
fileInputErrorMessage = e.Message;
continue;
}
catch (Exception e)
{
fileInputErrorMessage = e.Message;
continue;
}
if (MessageForm.MediaType is MediaType.Images or MediaType.Audio)
uploadMedia.Base64Preview = $"data:{uploadMedia.ContentType};base64,{Convert.ToBase64String(uploadMedia.Blob)}";
MessageForm.Media.Add(uploadMedia);
}
}
catch (Exception e)
{
fileInputErrorMessage = e.Message;
}
}
void RemoveFile(UploadMedia media)
{
MessageForm.Media.Remove(media);
}
void OnTitleChanged(string value)
{
MessageForm.Title = value;
}
void ContentLengthChanged()
{
totalCharacters = MessageForm.Content?.Length ?? 0;
StateHasChanged();
}
void OnContentTypeChanged(ContentType contentType)
{
MessageForm.ContentType = contentType;
showPreviewButton = contentType is ContentType.Markdown or ContentType.HTML;
}
void OnMediaTypeChanged(MediaType mediaType)
{
MessageForm.MediaType = mediaType;
acceptedFilesTypes = mediaType switch
{
MediaType.Images => ".jpg,.jpeg,.png,.gif",
MediaType.Video => ".webm,.mp4,.m4v",
MediaType.Audio => ".mp3,.wav,.flac,.m4a",
MediaType.Documents => ".xlsx,.csv,.ppt,.odt",
_ => default
};
}
void OnOpenClosePreview()
{
isPreviewOpen = !isPreviewOpen;
}
async Task OnValidSubmit()
{
contentError = default;
if ((MessageForm.Content is { Length: 0 } && MessageForm.Media.Count == 0))
{
contentError = CascadingState.Localizer["Missing content, either message or media"];
return;
}
await OnMessageSubmit.InvokeAsync(MessageForm);
}
}

View File

@ -0,0 +1,36 @@
<div class="flex flex-col w-full space-y-4">
<div class="flex w-full justify-between items-center py-2 md:py-3 px-3 md:px-4 space-x-2 rounded-lg neomorph is-nxsmall">
@TitleChildren
<button class="button is-rounded is-small @ButtonCss"
@onclick:preventDefault @onclick="OpenCloseInnerContent" disabled="@(!HasInnerContent)">
<span class="icon">
<i aria-hidden="true" class="@LeftIconCss"></i>
</span>
</button>
</div>
<div class="@InnerContentContainerCss @Hidden">
@InnerContent
</div>
</div>
@code {
[Parameter] public RenderFragment TitleChildren { get; set; }
[Parameter] public RenderFragment InnerContent { get; set; }
[Parameter] public bool HasInnerContent { get; set; } = true;
[Parameter] public string InnerContentContainerCss { get; set; } = "block w-auto ml-5 md:ml-10 rounded-lg neomorph is-nxsmall";
[Parameter]
public bool IsOpen { get; set; } = false;
string Hidden { get; set; } = VConstants.HideClass;
string ButtonCss => $"{(HasInnerContent ? default : "cursor-not-allowed")} neoBtnSmall";
string LeftIconCss => IsOpen ? "ion-md-arrow-dropup" : "ion-md-arrow-dropdown";
void OpenCloseInnerContent()
{
IsOpen = !IsOpen;
Hidden = IsOpen ? default : VConstants.HideClass;
}
}

View File

@ -1,6 +1,187 @@
namespace decePubClient.Extensions;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Web;
public class GenericExtensions
using Blazored.LocalStorage;
using decePubClient.Models;
using decePubClient.Models.Types;
using decePubClient.Resources;
using decePubClient.Services;
using DnetIndexedDb;
using DnetIndexedDb.Fluent;
using DnetIndexedDb.Models;
using Markdig;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Localization;
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 =>
{
var model = new IndexedDbDatabaseModel()
.WithName("data")
.WithVersion(1)
.WithModelId(1);
model.AddStore(nameof(Message))
.WithKey(nameof(Message.MessageId))
.AddUniqueIndex(nameof(Message.MessageId))
.AddIndex(nameof(Message.RootMessageId))
.AddIndex(nameof(Message.User))
.AddIndex(nameof(Message.MessageType))
.AddIndex(nameof(Message.Title))
.AddIndex(nameof(Message.Content))
.AddIndex(nameof(Message.CreatedAt))
.AddIndex(nameof(Message.Medias));
options.UseDatabase(model);
});
}
public static string GetPassedTime(this DateTime dateTime, IStringLocalizer<AllStrings> localizer)
{
var timeframe = DateTime.Now - dateTime.ToLocalTime();
switch ((int)timeframe.TotalHours)
{
case >= 24:
return string.Format(localizer["{0}d"], (int)timeframe.TotalDays);
case >= 1 and < 24:
return string.Format(localizer["{0}h"], (int)timeframe.TotalHours);
case 0:
return string.Format(localizer["{0}m"], (int)timeframe.TotalMinutes);
default:
return default;
}
}
public static string GetFileSize(this long size, IStringLocalizer<AllStrings> localizer)
{
switch (size)
{
case >= 1_048_576:
return string.Format(localizer["{0:N0}MB"], size / 1_048_576);
case < 1_048_576:
return string.Format(localizer["{0}KB"], size / 1_024);
default:
return default;
}
}
public static string ParseContent(this string content, ContentType contentType)
{
switch (contentType)
{
case ContentType.PlainText:
return content;
case ContentType.HTML:
return ((MarkupString)content).Value;
case ContentType.Markdown:
return Markdown.ToHtml(content);
default:
return default;
}
}
public static string GetMessageTypeIcon(this MessageType messageType)
{
switch (messageType)
{
case MessageType.Public:
return "ion-md-globe";
case MessageType.Unlisted:
return "ion-md-unlock";
case MessageType.FollowersOnly:
return "ion-md-lock";
case MessageType.Direct:
return "ion-md-paper-plane";
default:
return default;
}
}
public static string GetTimelineTypeIcon(this TimelineType timelineType)
{
switch (timelineType)
{
case TimelineType.Home:
return "ion-md-home";
case TimelineType.Local:
return "ion-md-people";
case TimelineType.Federation:
return "ion-md-globe";
default:
return default;
}
}
public static string GetContentTypeIcon(this ContentType contentType)
{
switch (contentType)
{
case ContentType.PlainText:
return "ion-md-quote";
case ContentType.HTML:
return "ion-logo-html5";
case ContentType.Markdown:
return "ion-logo-markdown";
default:
return default;
}
}
public static string GetMediaTypeIcon(this MediaType mediaType)
{
switch (mediaType)
{
case MediaType.Images:
return "ion-md-images";
case MediaType.Video:
return "ion-md-videocam";
case MediaType.Documents:
return "ion-md-document";
case MediaType.Audio:
return "ion-md-volume-high";
default:
return default;
}
}
}

View File

@ -1 +1,34 @@

using decePubClient.Models;
namespace decePubClient.Helpers;
public static class Faker
{
static IReadOnlyList<MessageUser> Users => new List<MessageUser>
{
new(),
new()
{
UserId = "7b5703dc-aee8-46b1-aed2-cd06021a1c0c",
DisplayName = "loweel",
UserName = "@loweel@bbs.keinpfusch.net",
PictureUrl = "https://bbs.keinpfusch.net/media/4729611f9aaef76399600ba2f117e5da609e5bf46dd7d502dae3e7b9fdc5cc78.WBMX2L9V1D00",
BackgroundUrl = "https://bbs.keinpfusch.net/media/6e283b943ca297629cb35b7fdfc790907dfd24b6303518e10992b2b5a6658947.3EUB6O4OMR2X",
ProfileUrl = "https://letsrulethe.world/users/AG6rE2nRya826QEJFY"
},
new()
{
UserId = "bc9c2a2b-fc5f-42fc-b907-ac30203eed45",
DisplayName = "Valentina Nappi",
UserName = "@valentina.nappi@mastodon.uno",
PictureUrl = "https://cdn.masto.host/mastodonuno/cache/accounts/avatars/106/816/797/491/758/442/original/2b2995b82af966fb.jpg",
BackgroundUrl = "https://cdn.masto.host/mastodonuno/cache/accounts/headers/106/816/797/491/758/442/original/898aedf6cd3a2da3.jpeg",
ProfileUrl = "https://mastodon.uno/web/@valenappi@beta.birdsite.live"
}
};
public static MessageUser GetRandomUser()
{
return Users[Random.Shared.Next(0, Users.Count)];
}
}

View File

@ -1,10 +1,6 @@
using collAnon.Shared;
using System;
using System.IO;
using System.Net.Mail;
using System.Text.Json;
using System.Net.Mail;
namespace collAnon.Client.Helpers
namespace decePubClient.Helpers
{
public static class SUtility
{
@ -27,12 +23,6 @@ namespace collAnon.Client.Helpers
return !(end < now && now < start);
}
public static bool CacheHasExpired(long? lastTimeCacheTimeTicks)
{
if (!lastTimeCacheTimeTicks.HasValue) return true;
return (DateTime.Now.Ticks - lastTimeCacheTimeTicks.Value) > VConstants.CacheExpirationPeriod.Ticks;
}
public static string GetFileIcon(string fileName)
{
switch (Path.GetExtension(fileName))
@ -62,21 +52,6 @@ namespace collAnon.Client.Helpers
}
}
public static string GetMissingMimeType(string fileName)
{
switch (Path.GetExtension(fileName))
{
case ".docx":
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
case ".xlsx":
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
default:
return string.Empty;
}
}
public static bool IsValidEmail(string email)
{
try
@ -89,18 +64,5 @@ namespace collAnon.Client.Helpers
return false;
}
}
public static string GetQrCodeBase64(string base64String) => $"data:image/png;base64,{base64String}";
public static int GetRand()
{
var random = new Random(DateTime.Now.Millisecond);
return random.Next(0, random.Next(10, 1000));
}
public static T Deserialize<T>(string value)
{
return JsonSerializer.Deserialize<T>(value, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
}
}

View File

@ -1,5 +1,179 @@
<h3>CascadingState</h3>
@if (PublicCacheData != null)
{
if (PublicCacheData.ThemeIsDarkMode)
{
<Meta Content="@($"hsl({PublicCacheData.ThemeIndexColour},16%,12%)")" Name="theme-color"/>
<Meta Content="@($"hsl({PublicCacheData.ThemeIndexColour},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"/>
}
}
<style>
:root {
@if (PublicCacheData != null)
{@if (PublicCacheData.ThemeIsDarkMode)
{
@($@"--background: hsl({PublicCacheData.ThemeIndexColour},16%,12%);
--text-color: hsl({PublicCacheData.ThemeIndexColour},16%,73.6%);
--placeholder-text-color: hsla({PublicCacheData.ThemeIndexColour},84%,52.8%,.3);
--primary-color: hsl({PublicCacheData.ThemeIndexColour},16%,12%);
--primary-color-light: hsl({PublicCacheData.ThemeIndexColour},84%,100%);
--primary-color-dark: hsl({PublicCacheData.ThemeIndexColour},16%,33%);
--primary-gradiend-light: hsl({PublicCacheData.ThemeIndexColour},16%,16%);
--primary-gradiend-dark: hsl({PublicCacheData.ThemeIndexColour},16%,8%);
--primary-gradiend-lighter: hsl({PublicCacheData.ThemeIndexColour},16%,20%);
--primary-gradiend-darker: hsl({PublicCacheData.ThemeIndexColour},16%,4%);
--light-shadow: hsla({PublicCacheData.ThemeIndexColour},84%,66%,.1);
--dark-shadow: hsla({PublicCacheData.ThemeIndexColour},16%,1%,.5);")
}
else
{
@($@"--background: hsl({PublicCacheData.ThemeIndexColour},84%,88%);
--text-color: hsl({PublicCacheData.ThemeIndexColour},84%,26.4%);
--placeholder-text-color: hsla({PublicCacheData.ThemeIndexColour},84%,26.4%,.3);
--primary-color: hsl({PublicCacheData.ThemeIndexColour},84%,88%);
--primary-color-light: hsl({PublicCacheData.ThemeIndexColour},84%,100%);
--primary-color-dark: hsl({PublicCacheData.ThemeIndexColour},84%,66%);
--primary-gradiend-light: hsl({PublicCacheData.ThemeIndexColour},84%,92%);
--primary-gradiend-dark: hsl({PublicCacheData.ThemeIndexColour},84%,84%);
--primary-gradiend-lighter: hsl({PublicCacheData.ThemeIndexColour},84%,96%);
--primary-gradiend-darker: hsl({PublicCacheData.ThemeIndexColour},84%,80%);
--light-shadow: hsla({PublicCacheData.ThemeIndexColour},84%,100%,.5);
--dark-shadow: hsla({PublicCacheData.ThemeIndexColour},84%,66%,.5);")
}
}
}
</style>
<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; }
// [Inject] DataService DataService { get; set; }
[Inject] public AppStatusService Status { get; set; }
[Inject] ILogger<CascadingState> Logger { get; set; }
public bool IsOnline { get; set; } = true;
Timer IsOnlineTimer { get; set; }
public PublicCacheData PublicCacheData { get; set; }
public User User { get; set; }
DotNetObjectReference<CascadingState> cascadingStateReference;
protected override async Task OnInitializedAsync()
{
try
{
IsOnlineTimer = new Timer(async _ => await UpdateIsOnline(), new AutoResetEvent(false), 0, 10000);
cascadingStateReference = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("cascadingStateInstanceReference", cascadingStateReference);
PublicCacheData = await Storage.GetItemAsync<PublicCacheData>(nameof(PublicCacheData));
if (PublicCacheData == null)
{
PublicCacheData = new();
await UpdatePublicCache(PublicCacheData);
}
User = new();
}
catch (Exception ex)
{
Logger.LogError(ex, $"{nameof(CascadingState)}.{nameof(OnInitializedAsync)}");
}
}
public async ValueTask UpdatePublicCache(PublicCacheData publicCacheData)
{
try
{
PublicCacheData = publicCacheData;
await Storage.SetItemAsync(nameof(PublicCacheData), PublicCacheData);
StateHasChanged();
}
catch (Exception ex)
{
Console.WriteLine($"{nameof(CascadingState)}.{nameof(UpdatePublicCache)}");
Console.WriteLine(ex.ToString());
}
}
[JSInvokable]
public async Task LogFromJs(string message, string where)
{
try
{
await ProcessError(new(message), where);
}
catch (Exception ex)
{
Logger.LogError(ex, $"{nameof(CascadingState)}.{nameof(LogFromJs)}");
}
}
async Task UpdateIsOnline()
{
try
{
var latestOnlineState = await Status.IsOnline();
//var latestOnlineState = Random.Shared.Next() % 2 == 0;
if (latestOnlineState != IsOnline)
{
IsOnline = latestOnlineState;
StateHasChanged();
}
// else
// {
// var pingIsOnline = await DataService.Ping();
// if (pingIsOnline != IsOnline)
// {
// IsOnline = pingIsOnline;
// StateHasChanged();
// }
// }
}
catch (Exception ex)
{
Logger.LogError(ex, $"{nameof(CascadingState)}.{nameof(UpdateIsOnline)}");
}
}
public async ValueTask ProcessError(Exception ex, string where)
{
try
{
await DbStorage.AddLog(ex, where);
Logger.LogError(ex, where);
await Task.Run(() => {
});
}
catch (Exception exception)
{
Logger.LogError(exception, $"{nameof(CascadingState)}.{nameof(ProcessError)}");
}
}
public async ValueTask ProcessWarning(string message, string where)
{
try
{
await DbStorage.AddLog(message, where);
Logger.LogWarning("{where} - {message}", where, message);
await Task.Run(() => {
});
}
catch (Exception exception)
{
Logger.LogError(exception, $"{nameof(CascadingState)}.{nameof(ProcessWarning)}");
}
}
}

View File

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

View File

@ -1,6 +1,9 @@
namespace decePubClient.Models;
using decePubClient.Models.Types;
namespace decePubClient.Models;
public class ActionBarFilter
{
public TimelineType TimelineType { get; set; } = TimelineType.Home;
public TimeSortingType TimeSortingType { get; set; } = TimeSortingType.Ascending;
}

11
Models/Media.cs Normal file
View File

@ -0,0 +1,11 @@
namespace decePubClient.Models
{
public class Media
{
public string FileName { get; set; }
public string Url { get; set; }
public string ContentType { get; set; }
public string AltText { get; set; }
public byte[] Blob { get; set; } //TODO TEMPORARY
}
}

10
Models/Mention.cs Normal file
View File

@ -0,0 +1,10 @@
namespace decePubClient.Models
{
public class Mention
{
public string DisplayName { get; set; }
public string UserName { get; set; }
public string UserId { get; set; }
public string ProfileUrl { get; set; }
}
}

23
Models/Message.cs Normal file
View File

@ -0,0 +1,23 @@
using System.ComponentModel;
using System.Text.Json.Serialization;
using decePubClient.Models.Types;
namespace decePubClient.Models
{
public class Message
{
public MessageUser User { get; set; } = new();
public MessageType MessageType { get; set; } = MessageType.Public;
public string RootMessageId { get; set; }
public string MessageId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public bool IsFavourite { get; set; } = false;
public bool IsBoosted { get; set; } = false;
public List<Media> Medias { get; set; } = new();
public DateTime CreatedAt { get; set; }
[Bindable(false), JsonIgnore]
public bool IsOptionsOpen { get; set; } = false;
}
}

View File

@ -1,6 +1,28 @@
namespace decePubClient.Models;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using decePubClient.Models.Types;
using decePubClient.Resources;
namespace decePubClient.Models;
public class MessageForm
{
public string RootMessageId { get; set; }
[Required(ErrorMessageResourceName = ValidationNames.Required, ErrorMessageResourceType = typeof(ErrorMessages))]
public MessageType MessageType { get; set; } = MessageType.Public;
[Required(ErrorMessageResourceName = ValidationNames.Required, ErrorMessageResourceType = typeof(ErrorMessages))]
public ContentType ContentType { get; set; } = ContentType.PlainText;
[StringLength(64, ErrorMessageResourceName = ValidationNames.MaxLength, ErrorMessageResourceType = typeof(ErrorMessages))]
public string Title { get; set; }
[StringLength(5_000, ErrorMessageResourceName = ValidationNames.MaxLength, ErrorMessageResourceType = typeof(ErrorMessages))]
public string Content { get; set; }
public List<UploadMedia> Media { get; set; } = new();
[JsonIgnore, Bindable(false)]
public MediaType MediaType { get; set; } = MediaType.Images;
[JsonIgnore, Bindable(false)]
public bool IsScopeOptionsOpen { get; set; } = false;
}

12
Models/MessageUser.cs Normal file
View File

@ -0,0 +1,12 @@
namespace decePubClient.Models
{
public class MessageUser
{
public string UserId { get; set; } = "45f14fa8-c40f-4121-997c-ef2542196a50";
public string UserName { get; set; } = "@loosy@letsrulethe.world";
public string DisplayName { get; set; } = "loosy";
public string PictureUrl { get; set; } = "https://letsrulethe.world/media/c22d7a6dfcce11e4d2d8d4f6298842a36751b0a179dc5333d24663e4b93793b4.jpg";
public string BackgroundUrl { get; set; } = "https://letsrulethe.world/media/717cc7f5a090cfbe77be46941060b9a54454c351c74ff2f056363e002c8e2c3f.png";
public string ProfileUrl { get; set; } = "https://letsrulethe.world/users/loosy";
}
}

View File

@ -0,0 +1,7 @@
namespace decePubClient.Models
{
public class SettingsOptions
{
public bool IsOpen { get; set; }
}
}

View File

@ -1,6 +1,8 @@
namespace decePubClient.Models.Types;
public class ContentType
public enum ContentType
{
PlainText,
HTML,
Markdown
}

View File

@ -0,0 +1,10 @@
namespace decePubClient.Models.Types
{
public enum MessageType
{
Direct,
FollowersOnly,
Unlisted,
Public
}
}

View File

@ -1,3 +1,7 @@
namespace decePubClient.Models.Types;
public enum TimeSortingType { }
public enum TimeSortingType
{
Ascending,
Descending
}

View File

@ -1,6 +1,16 @@
namespace decePubClient.Models;
using System.ComponentModel;
using System.Text.Json.Serialization;
namespace decePubClient.Models;
public class UploadMedia
{
public string ContentType { get; set; }
public string FileName { get; set; }
public string AltText { get; set; }
public byte[] Blob { get; set; }
public string Base64Preview { get; set; }
[JsonIgnore, Bindable(false)]
public long Size { get; set; }
}

21
Models/User.cs Normal file
View File

@ -0,0 +1,21 @@
namespace decePubClient.Models
{
public class User
{
public string Id { get; set; } = "45f14fa8-c40f-4121-997c-ef2542196a50";
public bool IsAuthenticated { get; set; } = false;
public List<UserClaim> Claims { get; set; } = new();
public string UserName { get; set; } = "@loosy@letsrulethe.world";
public string DisplayName { get; set; } = "loosy";
public string PictureUrl { get; set; } = "https://letsrulethe.world/media/c22d7a6dfcce11e4d2d8d4f6298842a36751b0a179dc5333d24663e4b93793b4.jpg";
public string BackgroundUrl { get; set; } = "https://letsrulethe.world/media/717cc7f5a090cfbe77be46941060b9a54454c351c74ff2f056363e002c8e2c3f.png";
public string ProfileUrl { get; set; } = "https://letsrulethe.world/users/loosy";
}
public class UserClaim
{
public string Type { get; set; }
public string Value { get; set; }
}
}

7
Models/VConstants.cs Normal file
View File

@ -0,0 +1,7 @@
namespace decePubClient.Models
{
public static class VConstants
{
public const string HideClass = "hidden";
}
}

View File

@ -1,4 +1,5 @@
@page "/administration"
@inherits PagesBase
<section class="block relative w-full h-full neomorphInset is-nxsmall rounded-xl">

View File

@ -0,0 +1,23 @@
@page "/authentication/{action}"
@inherits PagesBase
<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">
<RemoteAuthenticatorView Action="@Action" />
</div>
</section>
@code {
[Parameter]
public string Action { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
}
}

View File

@ -1,18 +0,0 @@
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View File

@ -1,42 +1,207 @@
@page "/expand/{messageId}"
<Title>@Localizer</Title>
@page "/expand"
@page "/expand/{messageId}"
@inherits PagesBase
<Title>@CascadingState.Localizer</Title>
<section class="block relative w-full h-full neomorphInset is-nxsmall rounded-xl">
<div class="flex w-full h-full flex-col space-y-4">
<div class="flex flex-col space-y-4 p-4 md:p-5 w-full h-full absolute overflow-y-auto">
<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 @SUtility.IfTrueThen(TimeSortingType is TimeSortingType.Descending, "flex-col-reverse")">
</div>
@if (IsLoading)
{
<LoadingData/>
}
</section>
@foreach (var message in Ancestors)
{
<Content CssContainer="ml-6 w-auto" IncludeExpand="false" Message="message"
OnMessageBoost="async (m) => await OnMessageBoost(m)" OnMessageDelete="async (m) => await OnMessageDelete(m)"
OnMessageFavourite="async (m) => await OnMessageFavourite(m)" OnMessageReply="async (m) => await OnMessageReply(m)"
OnMessageMediaDownload="async (m) => await OnMessageMediaDownload(m)"
OnUserBlock="async (u) => await OnUserBlock(u)" OnUserDirectMessage="async (u) => await OnUserDirectMessage(u)" OnUserSilence="async (u) => await OnUserSilence(u)" />
}
<Content CssContainer="is-nsmall" IncludeExpand="false" Message="CurrentMessage"
OnMessageBoost="async (m) => await OnMessageBoost(m)" OnMessageDelete="async (m) => await OnMessageDelete(m)"
OnMessageFavourite="async (m) => await OnMessageFavourite(m)" OnMessageReply="async (m) => await OnMessageReply(m)"
OnMessageMediaDownload="async (m) => await OnMessageMediaDownload(m)"
OnUserBlock="async (u) => await OnUserBlock(u)" OnUserDirectMessage="async (u) => await OnUserDirectMessage(u)" OnUserSilence="async (u) => await OnUserSilence(u)" />
@foreach (var message in Descendants)
{
<Content CssContainer="ml-6 w-auto" IncludeExpand="false" Message="message"
OnMessageBoost="async (m) => await OnMessageBoost(m)" OnMessageDelete="async (m) => await OnMessageDelete(m)"
OnMessageFavourite="async (m) => await OnMessageFavourite(m)" OnMessageReply="async (m) => await OnMessageReply(m)"
OnMessageMediaDownload="async (m) => await OnMessageMediaDownload(m)"
OnUserBlock="async (u) => await OnUserBlock(u)" OnUserDirectMessage="async (u) => await OnUserDirectMessage(u)" OnUserSilence="async (u) => await OnUserSilence(u)" />
}
</div>
</section>
</div>
@code {
[CascadingParameter] IStringLocalizer<AllStrings> Localizer { get; set; }
[CascadingParameter] CascadingState CascadingState { get; set; }
[Inject] NavigationManager Navigation { get; set; }
[Inject] IStorage DbStorage { get; set; }
[SupplyParameterFromQuery] string messageId { get; set; }
[Inject] ILocalStorageService LocalStorage { get; set; }
[Inject] IBlazorDownloadFileService BlazorDownloadFileService { get; set; }
[Inject] MessagesService MessagesService { get; set; }
[Parameter] public string MessageId { get; set; }
List<Message> Messages { get; set; } = new();
List<Message> Ancestors { get; set; } = new();
Message CurrentMessage { get; set; } = new();
List<Message> Descendants { get; set; } = new();
TimeSortingType TimeSortingType { get; set; } = TimeSortingType.Ascending;
protected override async Task OnInitializedAsync()
{
if (messageId is { Length: 0 })
if (MessageId is { Length: 0 })
{
Navigation.NavigateTo("/");
return;
}
var currentMessage = await DbStorage.GetMessage(messageId);
var messages = await DbStorage.GetMessages();
if (currentMessage.RootMessageId is { Length: > 0 })
Messages = messages.Where(m => m.RootMessageId == currentMessage.RootMessageId)
.OrderByDescending(m => m.CreatedAt)
.ToList();
var filters = await LocalStorage.GetItemAsync<ActionBarFilter>(nameof(ActionBarFilter));
if (filters == default)
await LocalStorage.SetItemAsync(nameof(ActionBarFilter), new ActionBarFilter());
else
Messages = messages.Where(m => m.RootMessageId == messageId)
.OrderByDescending(m => m.CreatedAt)
.ToList();
TimeSortingType = filters.TimeSortingType;
CurrentMessage = await DbStorage.GetMessage(MessageId);
Ancestors = new List<Message>
{
new()
{
RootMessageId = default,
MessageId = "51C698E3-7C28-4C90-9212-48D5C81DE089",
User = Faker.GetRandomUser(),
MessageType = (MessageType)Random.Shared.Next(0, 4),
Title = CurrentMessage?.Title,
Content = "<p>esempio di messaggio <b>precedente</b> a quello espanso</p>",
Medias = new(),
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
IsFavourite = Random.Shared.Next() % 2 == 0,
IsBoosted = Random.Shared.Next() % 2 == 0,
}
};
Descendants = new List<Message>
{
new()
{
RootMessageId = default,
MessageId = "0569A1DF-46FC-4485-9F11-F8CDFB794404",
User = Faker.GetRandomUser(),
MessageType = (MessageType)Random.Shared.Next(0, 4),
Title = CurrentMessage?.Title,
Content = "<p>esempio di messaggio <b>successivo</b> a quello espanso</p>",
Medias = new(),
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
IsFavourite = Random.Shared.Next() % 2 == 0,
IsBoosted = Random.Shared.Next() % 2 == 0,
}
};
IsLoading = false;
}
}
void OnTimeSortingChanged(TimeSortingType timeSortingType)
{
TimeSortingType = timeSortingType;
}
async Task OnMessageReply(MessageForm messageForm)
{
await Task.Run(() =>
{
});
var replyMessage = await MessagesService.SubmitMessage(messageForm);
Descendants.Add(new()
{
MessageId = Guid.NewGuid().ToString(),
Content = messageForm.Content?.ParseContent(messageForm.ContentType),
CreatedAt = DateTime.UtcNow,
MessageType = messageForm.MessageType,
Medias = messageForm.Media.Select(m => new Media
{
FileName = m.FileName,
AltText = m.AltText,
ContentType = m.ContentType,
Url = m.Base64Preview,
Blob = m.Blob
}).ToList(),
Title = messageForm.Title,
User = new MessageUser
{
UserId = CascadingState.User.Id,
UserName = CascadingState.User.UserName,
DisplayName = CascadingState.User.DisplayName,
PictureUrl = CascadingState.User.PictureUrl,
ProfileUrl = CascadingState.User.ProfileUrl,
BackgroundUrl = CascadingState.User.BackgroundUrl
},
});
}
async ValueTask OnMessageBoost(Message message)
{
await Task.Run(() =>
{
});
message.IsBoosted = !message.IsBoosted;
var boostedMessage = await MessagesService.BoostUnboostMessage(message);
}
async ValueTask OnMessageFavourite(Message message)
{
await Task.Run(() =>
{
});
message.IsFavourite = !message.IsFavourite;
var favouriteMessage = await MessagesService.FavouriteUnfavouriteMessage(message);
}
async ValueTask OnMessageDelete(Message message)
{
await Task.Run(() =>
{
});
var deleteResult = await MessagesService.DeleteMessage(message);
}
async ValueTask OnMessageMediaDownload(Media media)
{
await Task.Run(() =>
{
});
await BlazorDownloadFileService.DownloadFileAsync(media.FileName, media.Blob, media.ContentType);
}
async ValueTask OnUserBlock(MessageUser messageUser)
{
await Task.Run(() =>
{
});
var blockResult = await MessagesService.BlockUserFromMessage(messageUser);
}
async ValueTask OnUserDirectMessage(Message message)
{
await Task.Run(() =>
{
});
var directMessage = await MessagesService.ReplyMessage(message, MessageType.Direct);
}
async ValueTask OnUserSilence(MessageUser messageUser)
{
await Task.Run(() =>
{
});
var silenceResult = await MessagesService.SilenceUserFromMessage(messageUser);
}
}

View File

@ -1,57 +0,0 @@
@page "/fetchdata"
@inject HttpClient Http
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

View File

@ -1,9 +1,225 @@
@page "/"
@page "/home"
@page "/index"
@inherits PagesBase
<Title>@CascadingState.Localizer["Index"]</Title>
<PageTitle>Index</PageTitle>
<div class="flex w-full h-full flex-col space-y-4">
<h1>Hello, world!</h1>
<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 @SUtility.IfTrueThen(TimeSortingType is TimeSortingType.Descending, "flex-col-reverse")">
Welcome to your new app.
@if (IsLoading)
{
<LoadingData/>
}
else if (Messages.Count == 0)
{
<p class="w-full text-center text-lg">
<i class="ion-ios-remove-circle-outline"></i> @CascadingState.Localizer["Empty"]
</p>
}
<SurveyPrompt Title="How is Blazor working for you?" />
@foreach (var message in Messages)
{
<Content Message="message"
OnMessageBoost="async (m) => await OnMessageBoost(m)" OnMessageDelete="async (m) => await OnMessageDelete(m)"
OnMessageFavourite="async (m) => await OnMessageFavourite(m)" OnMessageReply="async (m) => await OnMessageReply(m)"
OnMessageMediaDownload="async (m) => await OnMessageMediaDownload(m)"
OnUserBlock="async (u) => await OnUserBlock(u)" OnUserDirectMessage="async (u) => await OnUserDirectMessage(u)" OnUserSilence="async (u) => await OnUserSilence(u)" />
}
@*<AuthorizeView>
<Authorized>
@foreach (var message in Messages)
{
<Content Message="message"
OnMessageReply="(m) => OnMessageReply(m)" OnMessageBoost="(m) => OnMessageBoost(m)"
OnMessageFavourite="(m) => OnMessageFavourite(m)" OnMessageDelete="(m) => OnMessageDelete(m)"
OnUserBlock="(u) => OnUserBlock(u)" OnUserDirectMessage="(u) => OnUserDirectMessage(u)" OnUserMute="(u) => OnUserMute(u)" />
}
</Authorized>
<NotAuthorized>
@foreach (var message in Messages)
{
<Content Message="message" OnMessageReply="(m) => OnMessageReply(m)" />
}
</NotAuthorized>
</AuthorizeView>*@
</div>
</section>
<ActionBar OnMessageSubmit="OnMessageSubmit" OnTimelineChanged="OnTimelineChanged" OnTimeSortingChanged="OnTimeSortingChanged" />
</div>
@code {
[CascadingParameter] Task<AuthenticationState> AuthState { get; set; }
[CascadingParameter] CascadingState CascadingState { get; set; }
[Inject] IBlazorDownloadFileService BlazorDownloadFileService { get; set; }
[Inject] IStorage Storage { get; set; }
[Inject] ILocalStorageService LocalStorage { get; set; }
[Inject] HttpClient Http { get; set; }
[Inject] MessagesService MessagesService { get; set; }
TimelineType TimelineType { get; set; } = TimelineType.Home;
TimeSortingType TimeSortingType { get; set; } = TimeSortingType.Ascending;
IReadOnlyList<Claim> userClaim
{
get
{
var auth = AuthState.Result;
if (auth.User.Identity?.IsAuthenticated ?? false)
return auth.User.Claims.ToList();
return new List<Claim>();
}
}
List<Message> IncomingMessages { get; set; } = new();
List<Message> Messages { get; set; } = new();
string Response { get; set; }
protected override async Task OnInitializedAsync()
{
var filters = await LocalStorage.GetItemAsync<ActionBarFilter>(nameof(ActionBarFilter));
if (filters == default)
await LocalStorage.SetItemAsync(nameof(ActionBarFilter), new ActionBarFilter());
else
{
TimelineType = filters.TimelineType;
TimeSortingType = filters.TimeSortingType;
}
Messages = await Storage.GetMessages();
//try
//{
// var response = await Http.GetAsync("api/test");
// Response = await response.Content.ReadAsStringAsync();
//}
//catch
//{
//}
IsLoading = false;
}
void OnTimelineChanged(TimelineType timelineType)
{
TimelineType = timelineType;
}
void OnTimeSortingChanged(TimeSortingType timeSortingType)
{
TimeSortingType = timeSortingType;
}
async Task OnMessageSubmit(MessageForm messageForm)
{
await Task.Run(() =>
{
});
var submitMessage = await MessagesService.SubmitMessage(messageForm);
Messages.Insert(0, new()
{
MessageId = Guid.NewGuid().ToString(),
Content = messageForm.Content?.ParseContent(messageForm.ContentType),
CreatedAt = DateTime.UtcNow,
MessageType = messageForm.MessageType,
Medias = messageForm.Media.Select(m => new Media
{
FileName = m.FileName,
AltText = m.AltText,
ContentType = m.ContentType,
Url = m.Base64Preview,
Blob = m.Blob
}).ToList(),
Title = messageForm.Title,
User = new MessageUser
{
UserId = CascadingState.User.Id,
UserName = CascadingState.User.UserName,
DisplayName = CascadingState.User.DisplayName,
PictureUrl = CascadingState.User.PictureUrl,
ProfileUrl = CascadingState.User.ProfileUrl,
BackgroundUrl = CascadingState.User.BackgroundUrl
},
});
}
async Task OnMessageReply(MessageForm messageForm)
{
await Task.Run(() =>
{
});
var replyMessage = await MessagesService.SubmitMessage(messageForm);
Messages.Insert(0, new()
{
RootMessageId = messageForm.RootMessageId,
MessageId = Guid.NewGuid().ToString(),
Content = messageForm.Content?.ParseContent(messageForm.ContentType),
CreatedAt = DateTime.UtcNow,
MessageType = messageForm.MessageType,
Medias = messageForm.Media.Select(m => new Media
{
FileName = m.FileName,
AltText = m.AltText,
ContentType = m.ContentType,
Url = m.Base64Preview,
Blob = m.Blob
}).ToList(),
Title = messageForm.Title,
User = new MessageUser
{
UserId = CascadingState.User.Id,
UserName = CascadingState.User.UserName,
DisplayName = CascadingState.User.DisplayName,
PictureUrl = CascadingState.User.PictureUrl,
ProfileUrl = CascadingState.User.ProfileUrl,
BackgroundUrl = CascadingState.User.BackgroundUrl
}
});
}
async ValueTask OnMessageBoost(Message message)
{
message.IsBoosted = !message.IsBoosted;
var boostedMessage = await MessagesService.BoostUnboostMessage(message);
}
async ValueTask OnMessageFavourite(Message message)
{
message.IsFavourite = !message.IsFavourite;
var favouriteMessage = await MessagesService.FavouriteUnfavouriteMessage(message);
}
async ValueTask OnMessageDelete(Message message)
{
Messages.Remove(message);
var deleteResult = await MessagesService.DeleteMessage(message);
}
async ValueTask OnMessageMediaDownload(Media media)
{
await Task.Run(() =>
{
});
await BlazorDownloadFileService.DownloadFileAsync(media.FileName, media.Blob, media.ContentType);
}
async ValueTask OnUserBlock(MessageUser messageUser)
{
var blockResult = await MessagesService.BlockUserFromMessage(messageUser);
}
async ValueTask OnUserDirectMessage(Message message)
{
var directMessage = await MessagesService.ReplyMessage(message, MessageType.Direct);
}
async ValueTask OnUserSilence(MessageUser messageUser)
{
var silenceResult = await MessagesService.SilenceUserFromMessage(messageUser);
}
}

16
Pages/Login.razor Normal file
View File

@ -0,0 +1,16 @@
@page "/login"
@inherits PagesBase
<Title>@CascadingState.Localizer["Login"]</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">
<LoginDisplay />
</div>
</section>
@code {
[CascadingParameter] CascadingState CascadingState { get; set; }
}

25
Pages/Logout.razor Normal file
View File

@ -0,0 +1,25 @@
@page "/logout"
@inherits PagesBase
<Title>@CascadingState.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>
</div>
</section>
@code {
[CascadingParameter] CascadingState CascadingState { get; set; }
//[Inject] TokenAuthStateProvider AuthStateProvider { get; set; }
[Inject] SignOutSessionStateManager SignOutSessionStateManager { get; set; }
[Inject] NavigationManager Navigation { get; set; }
protected override async Task OnInitializedAsync()
{
//await AuthStateProvider.LogoutAsync(true);
await SignOutSessionStateManager.SetSignOutState();
Navigation.NavigateTo("authenticatio/logout");
}
}

75
Pages/Settings.razor Normal file
View File

@ -0,0 +1,75 @@
@page "/settings"
@inherits PagesBase
<Title>@CascadingState.Localizer["Settings"]</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">
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-cog text-lg"></i> @CascadingState.Localizer["General"]
</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"]
</p>
</div>
</InnerContent>
</OpenDownContainer>
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-person text-lg"></i> @CascadingState.Localizer["Profile"]
</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"]
</p>
</div>
</InnerContent>
</OpenDownContainer>
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-archive text-lg"></i> @CascadingState.Localizer["Data import/export"]
</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"]
</p>
</div>
</InnerContent>
</OpenDownContainer>
<OpenDownContainer>
<TitleChildren>
<p class="inline-flex items-center space-x-2">
<i class="ion-md-eye-off text-lg"></i> @CascadingState.Localizer["Mutes/Blocks"]
</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"]
</p>
</div>
</InnerContent>
</OpenDownContainer>
</div>
</section>
@code {
[CascadingParameter] CascadingState CascadingState { get; set; }
}

View File

@ -1,12 +1,72 @@
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.Services;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Toolbelt.Blazor.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Components.Authorization;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddBlazorDownloadFile();
builder.Services.AddOptions()
.AddTransient<AppStatusService>();
builder.Services.AddBlazoredLocalStorage(config =>
{
config.JsonSerializerOptions.WriteIndented = false;
config.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
config.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
config.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
})
.AddBlazoredModal()
//.AddScoped<TokenAuthStateProvider>()
.AddScoped<CustomAuthenticationMessageHandler>()
.AddScoped<MessagesService>()
//.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<TokenAuthStateProvider>())
//.AddTransient<IHttpService, HttpService>()
.AddHeadElementHelper(options => options.DisableClientScriptAutoInjection = true)
.AddLocalization()
.AddNotifications()
.AddTransient<IStorage, Storage>()
.AddIndexedDb();
await builder.Build().RunAsync();
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>();
var host = builder.Build();
await host.SetDefaultCulture();
await host.RunAsync();

72
Resources/ErrorMessages.Designer.cs generated Normal file
View File

@ -0,0 +1,72 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace decePubClient.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ErrorMessages {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ErrorMessages() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("decePubClient.Resources.ErrorMessages", typeof(ErrorMessages).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to This field is required..
/// </summary>
public static string Required {
get {
return ResourceManager.GetString("Required", resourceCulture);
}
}
}
}

View File

@ -1,6 +0,0 @@
namespace decePubClient.Resources;
public class ErrorMessages
{
}

View File

@ -1,21 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Required" xml:space="preserve">
<value>This field is required.</value>
</data>
</root>

View File

@ -0,0 +1,92 @@
body {
color: var(--text-color);
background-color: var(--background);
}
.background {
background-color: var(--background);
}
details > summary::-webkit-details-marker {
color: var(--text-color)
}
:focus-visible {
outline: none
}
*, ::after, ::before {
scrollbar-color: inherit;
scrollbar-width: inherit;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-button {
width: 8px;
height: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
border: thin solid transparent;
box-shadow: none;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--primary-color-dark);
border: thin solid transparent;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--primary-color-dark);
}
::-moz-selection { /* Code for Firefox */
color: var(--primary-color);
background: var(--primary-color-dark);
}
::selection {
color: var(--primary-color);
background: var(--primary-color-dark);
}
.flex.flex-col-reverse>div:first-child {
margin-top: 1rem;
}
.loadAnimation span {
animation-name: blink;
animation-duration: 1.4s;
animation-iteration-count: infinite;
animation-fill-mode: both;
}
.loadAnimation span:nth-child(1) {
animation-delay: .4s;
}
.loadAnimation span:nth-child(2) {
animation-delay: .3s;
}
.loadAnimation span:nth-child(3) {
animation-delay: .2s;
}
.loadAnimation span:nth-child(4) {
animation-delay: .2s;
}
.loadAnimation span:nth-child(5) {
animation-delay: .3s;
}
.loadAnimation span:nth-child(6) {
animation-delay: .4s;
}

View File

@ -0,0 +1,4 @@
@import "mixins.scss";
@import "variables.scss";
@import "neomorph.scss";
@import "base.scss";

View File

@ -1,57 +1,32 @@
//MEDIA QUERY MANAGER
/*$breakpoint argument choices:
- phone
- tab-port
- tab-land
- desk
- big-desktop
*/
@mixin MediaQuery($breakpoint) {
// max-width:768px;
@if $breakpoint==phone {
@media screen and (max-width: 768px) {
@if $breakpoint==sm {
@media (min-width: 640px) {
@content
}
}
@if $breakpoint==tab-p {
// min-width:768px;
// max-width:900px;
@media screen and (min-width: 768px) and (max-width: 900px) {
@if $breakpoint==md {
@media (min-width: 768px) {
@content
}
}
@if $breakpoint==tab-l {
// min-width:901px;
// max-width:1200px;
@media screen and (min-width: 901px) and (max-width: 1200px) {
@if $breakpoint==lg {
@media (min-width: 1024px) {
@content
}
}
@if $breakpoint==laptop {
// min-width:1201px;
// max-width:1366px;
@media screen and (min-width: 1201px) and (max-width: 1366px) {
@if $breakpoint==xl {
@media (min-width: 1280px) {
@content
}
}
@if $breakpoint==desk {
// min-width:1201px;
// max-width:1800px;
@media screen and (min-width: 1201px) and (max-width: 1800px) {
@content
}
}
@if $breakpoint==big-d {
// min-width:1801px;
// max-width:4000px;
@media screen and (min-width: 1801px) and (max-width: 4000px) {
@if $breakpoint=="2xl" {
@media (min-width: 1536px) {
@content
}
}

View File

@ -0,0 +1,653 @@
:root {
--shadow-offset: 8px;
--blur-radius: 16px;
--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);
&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);
&.is-nxxsmall {
--shadow-offset: 2px;
--blur-radius: 4px;
&-sm {
@include MediaQuery(sm) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
&-md {
@include MediaQuery(md) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
&-lg {
@include MediaQuery(lg) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
&-xl {
@include MediaQuery(xl) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
&-2xl {
@include MediaQuery(2xl) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
}
&.is-nxsmall {
--shadow-offset: 3px;
--blur-radius: 6px;
&-sm {
@include MediaQuery(sm) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
&-md {
@include MediaQuery(md) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
&-lg {
@include MediaQuery(lg) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
&-xl {
@include MediaQuery(xl) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
&-2xl {
@include MediaQuery(2xl) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
}
&.is-nsmall {
--shadow-offset: 6px;
--blur-radius: 12px;
&-sm {
@include MediaQuery(sm) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
&-md {
@include MediaQuery(md) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
&-lg {
@include MediaQuery(lg) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
&-xl {
@include MediaQuery(xl) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
&-2xl {
@include MediaQuery(2xl) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
}
}
&.is-nxxsmall {
--shadow-offset: 2px;
--blur-radius: 4px;
&-sm {
@include MediaQuery(sm) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
&-md {
@include MediaQuery(md) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
&-lg {
@include MediaQuery(lg) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
&-xl {
@include MediaQuery(xl) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
&-2xl {
@include MediaQuery(2xl) {
--shadow-offset: 2px;
--blur-radius: 4px;
}
}
}
&.is-nxsmall {
--shadow-offset: 3px;
--blur-radius: 6px;
&-sm {
@include MediaQuery(sm) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
&-md {
@include MediaQuery(md) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
&-lg {
@include MediaQuery(lg) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
&-xl {
@include MediaQuery(xl) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
&-2xl {
@include MediaQuery(2xl) {
--shadow-offset: 3px;
--blur-radius: 6px;
}
}
}
&.is-nsmall {
--shadow-offset: 6px;
--blur-radius: 12px;
&-sm {
@include MediaQuery(sm) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
&-md {
@include MediaQuery(md) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
&-lg {
@include MediaQuery(lg) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
&-xl {
@include MediaQuery(xl) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
&-2xl {
@include MediaQuery(2xl) {
--shadow-offset: 6px;
--blur-radius: 12px;
}
}
}
&.is-nnormal {
--shadow-offset: 8px;
--blur-radius: 16px;
&-sm {
@include MediaQuery(sm) {
--shadow-offset: 8px;
--blur-radius: 16px;
}
}
&-md {
@include MediaQuery(md) {
--shadow-offset: 8px;
--blur-radius: 16px;
}
}
&-lg {
@include MediaQuery(lg) {
--shadow-offset: 8px;
--blur-radius: 16px;
}
}
&-xl {
@include MediaQuery(xl) {
--shadow-offset: 8px;
--blur-radius: 16px;
}
}
&-2xl {
@include MediaQuery(2xl) {
--shadow-offset: 8px;
--blur-radius: 16px;
}
}
}
}
button.neo,.neo {
&Btn {
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;
&: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;
}
&Small {
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;
&: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;
&: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);
}
& input[type=file] {
background-image: linear-gradient(-45deg, var(--primary-gradiend-light), var(--primary-gradiend-dark));
}
}
@include MediaQuery(phone) {
&-mobile {
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;
&: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;
&: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);
}
}
}
}
&Plain {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none;
&:focus {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none;
}
@include MediaQuery(phone) {
&-mobile {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none;
&:focus {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none;
}
}
}
}
&InsetPlain {
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;
&: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;
}
@include MediaQuery(phone) {
&-mobile {
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;
&: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;
}
}
}
}
&XInsetPlain {
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;
&: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;
}
@include MediaQuery(phone) {
&-mobile {
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;
&: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;
}
}
}
}
}
&InsetPlain {
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;
&: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;
&: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);
}
}
@include MediaQuery(phone) {
&-mobile {
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;
&: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;
&: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);
}
}
}
}
}
}
&File {
box-shadow: 0px 0px 0px var(--light-shadow), 0px 0px 0px var(--dark-shadow);
transition: all .2s linear;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
@include MediaQuery(phone) {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
background: var(--primary-color);
}
&:hover {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
}
&: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;
}
&.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);
&:focus-visible {
background: linear-gradient(-45deg, var(--primary-gradiend-lighter), var(--primary-gradiend-darker));
outline: none;
}
}
&.is-active, &.active {
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
color: var(--black);
}
}
&Input {
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;
&:focus {
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
border: none;
}
}
&Select {
& > 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;
&:focus {
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
border: none;
}
}
}
}
.neoCheckbox {
opacity: 0;
width: 0;
&:focus + label:before {
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter)) !important;
}
&Container {
position: relative;
}
& + label {
padding: .15rem .15rem .15rem 2rem;
cursor: pointer;
font-size: 1rem;
line-height: 1.5;
&:before {
animation-name: none;
width: 1.5rem;
height: 1.5rem;
border-radius: 100px;
position: absolute;
left: 0;
top: 0rem;
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;
}
}
&:checked.is-checked-bold + label {
font-weight: bold
}
&:checked + label:after {
display: inline-block;
width: .375rem;
height: .6rem;
top: .35rem;
left: .55rem;
transform: translateY(0rem) rotate(45deg);
border-width: .1rem;
border-top-width: 0.1rem;
border-left-width: 0.1rem;
border-style: solid;
border-top-style: solid;
border-left-style: solid;
border-color: var(--text-color);
border-top: 0;
border-left: 0;
position: absolute;
content: '';
}
}
hr.neoSeparator {
&Flat {
height: 10px;
border: none;
border-radius: 20px;
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
background: var(--background);
}
&Pressed {
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));
}
}
input.neoRange[type=range] {
height: 30px;
-webkit-appearance: none;
width: 100%;
background: transparent;
&:focus {
outline: none;
}
&::-webkit-slider-runnable-track {
width: 100%;
height: 20px;
cursor: pointer;
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, hsl(0, 90%, 80%), hsl(10, 90%, 80%), hsl(20, 90%, 80%), hsl(30, 90%, 80%), hsl(40, 90%, 80%), hsl(50, 90%, 80%), hsl(60, 90%, 80%), hsl(70, 90%, 80%), hsl(80, 90%, 80%), hsl(90, 90%, 80%), hsl(100, 90%, 80%), hsl(110, 90%, 80%), hsl(120, 90%, 80%), hsl(130, 90%, 80%), hsl(140, 90%, 80%), hsl(150, 90%, 80%), hsl(160, 90%, 80%), hsl(170, 90%, 80%), hsl(180, 90%, 80%), hsl(190, 90%, 80%), hsl(200, 90%, 80%), hsl(210, 90%, 80%), hsl(220, 90%, 80%), hsl(230, 90%, 80%), hsl(240, 90%, 80%), hsl(250, 90%, 80%), hsl(260, 90%, 80%), hsl(270, 90%, 80%), hsl(280, 90%, 80%), hsl(290, 90%, 80%), hsl(300, 90%, 80%), hsl(310, 90%, 80%), hsl(320, 90%, 80%), hsl(330, 90%, 80%), hsl(340, 90%, 80%), hsl(350, 90%, 80%), hsl(359, 90%, 80%)) !important;
}
&::-webkit-slider-thumb {
border: none;
height: 26px;
width: 26px;
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;
-webkit-appearance: none;
margin-top: -3px;
}
&:focus::-webkit-slider-runnable-track {
background: linear-gradient(90deg, hsl(0, 90%, 80%), hsl(10, 90%, 80%), hsl(20, 90%, 80%), hsl(30, 90%, 80%), hsl(40, 90%, 80%), hsl(50, 90%, 80%), hsl(60, 90%, 80%), hsl(70, 90%, 80%), hsl(80, 90%, 80%), hsl(90, 90%, 80%), hsl(100, 90%, 80%), hsl(110, 90%, 80%), hsl(120, 90%, 80%), hsl(130, 90%, 80%), hsl(140, 90%, 80%), hsl(150, 90%, 80%), hsl(160, 90%, 80%), hsl(170, 90%, 80%), hsl(180, 90%, 80%), hsl(190, 90%, 80%), hsl(200, 90%, 80%), hsl(210, 90%, 80%), hsl(220, 90%, 80%), hsl(230, 90%, 80%), hsl(240, 90%, 80%), hsl(250, 90%, 80%), hsl(260, 90%, 80%), hsl(270, 90%, 80%), hsl(280, 90%, 80%), hsl(290, 90%, 80%), hsl(300, 90%, 80%), hsl(310, 90%, 80%), hsl(320, 90%, 80%), hsl(330, 90%, 80%), hsl(340, 90%, 80%), hsl(350, 90%, 80%), hsl(359, 90%, 80%)) !important;
}
&::-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, hsl(0, 90%, 80%), hsl(10, 90%, 80%), hsl(20, 90%, 80%), hsl(30, 90%, 80%), hsl(40, 90%, 80%), hsl(50, 90%, 80%), hsl(60, 90%, 80%), hsl(70, 90%, 80%), hsl(80, 90%, 80%), hsl(90, 90%, 80%), hsl(100, 90%, 80%), hsl(110, 90%, 80%), hsl(120, 90%, 80%), hsl(130, 90%, 80%), hsl(140, 90%, 80%), hsl(150, 90%, 80%), hsl(160, 90%, 80%), hsl(170, 90%, 80%), hsl(180, 90%, 80%), hsl(190, 90%, 80%), hsl(200, 90%, 80%), hsl(210, 90%, 80%), hsl(220, 90%, 80%), hsl(230, 90%, 80%), hsl(240, 90%, 80%), hsl(250, 90%, 80%), hsl(260, 90%, 80%), hsl(270, 90%, 80%), hsl(280, 90%, 80%), hsl(290, 90%, 80%), hsl(300, 90%, 80%), hsl(310, 90%, 80%), hsl(320, 90%, 80%), hsl(330, 90%, 80%), hsl(340, 90%, 80%), hsl(350, 90%, 80%), hsl(359, 90%, 80%)) !important;
border: none;
}
&::-moz-range-thumb {
border: none;
height: 26px;
width: 26px;
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;
}
}

View File

@ -0,0 +1,34 @@
:root{
--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: 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: 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%);
}

View File

@ -1,6 +1,54 @@
namespace decePubClient.Services;
using Microsoft.JSInterop;
namespace decePubClient.Services;
public class AppStatusService
{
readonly IJSRuntime JsRuntime;
readonly IJSInProcessRuntime JsSyncRuntime;
public AppStatusService(IJSRuntime JSRuntime)
{
JsRuntime = JSRuntime;
JsSyncRuntime = (IJSInProcessRuntime)JSRuntime;
}
public async ValueTask<bool> IsOnline() => await JsSyncRuntime.InvokeAsync<bool>("isOnline");
public bool InputCaptureSupported() => JsSyncRuntime.Invoke<bool>("inputCaptureSupported");
public bool IsServiceWorkerAvailable() => JsSyncRuntime.Invoke<bool>("isServiceWorkerAvailable");
public string Language() => JsSyncRuntime.Invoke<string>("getLanguage");
public void Language(string language) => JsSyncRuntime.InvokeVoid("setLanguage", language);
public long GetHeight(string classId) => JsSyncRuntime.Invoke<long>("getHeight", classId);
public void SetAppBadge(int counter) => JsSyncRuntime.InvokeVoid("setAppBadge", counter);
public void ClearAppBadge() => JsSyncRuntime.InvokeVoid("clearAppBadge");
public async ValueTask ClearCache()
{
await JsRuntime.InvokeVoidAsync("clearCache");
}
public async ValueTask ClearLocalStorage()
{
await JsRuntime.InvokeVoidAsync("clearLocalStorage");
}
public void ReloadPage()
{
JsSyncRuntime.InvokeVoid("reloadPage");
}
public bool IsMobileMedia() => JsSyncRuntime.Invoke<bool>("isMobileMedia");
public bool CanShare() => JsSyncRuntime.Invoke<bool>("canShareStuff");
public async ValueTask ShareTextUrl(string title, string text, Uri uri) => await JsRuntime.InvokeVoidAsync("shareTextUrl", title, text, uri.ToString());
public async ValueTask ShareText(string title, string text) => await JsRuntime.InvokeVoidAsync("shareText", title, text);
}

View File

@ -8,15 +8,15 @@ namespace decePubClient.Services
{
public interface IHttpService
{
Task<HttpResponseMessage> Get(string uri, object payload = default, string?[] queryParams = default);
Task<HttpResponseMessage> Get(string uri, object payload = default, string[] queryParams = default);
Task<HttpResponseMessage> GetAnon(string uri, object payload = default, string?[] queryParams = default);
Task<HttpResponseMessage> GetAnon(string uri, object payload = default, string[] queryParams = default);
Task<HttpResponseMessage> Post(string uri, object payload = default);
Task<HttpResponseMessage> PostAnon(string uri, object payload = default);
Task<HttpResponseMessage> Delete(string uri, string?[] queryParams = default, object payload = default);
Task<HttpResponseMessage> Delete(string uri, string[] queryParams = default, object payload = default);
}
public class HttpService : IHttpService
@ -41,7 +41,7 @@ namespace decePubClient.Services
DbStorage = dbStorage;
}
public async Task<HttpResponseMessage> Get(string uri, object payload = default, string?[] queryParams = default)
public async Task<HttpResponseMessage> Get(string uri, object payload = default, string[] queryParams = default)
{
try
{
@ -82,7 +82,7 @@ namespace decePubClient.Services
}
}
public async Task<HttpResponseMessage> GetAnon(string uri, object payload = default, string?[] queryParams = default)
public async Task<HttpResponseMessage> GetAnon(string uri, object payload = default, string[] queryParams = default)
{
try
{
@ -123,7 +123,7 @@ namespace decePubClient.Services
}
}
public async Task<HttpResponseMessage> Delete(string uri, string?[] queryParams = default, object payload = default)
public async Task<HttpResponseMessage> Delete(string uri, string[] queryParams = default, object payload = default)
{
try
{

322
Services/IStorage.cs Normal file
View File

@ -0,0 +1,322 @@
using decePubClient.Helpers;
using decePubClient.Models;
using decePubClient.Models.Types;
using Markdig;
namespace decePubClient.Services
{
public interface IStorage
{
ValueTask<List<Message>> GetMessages();
ValueTask<Message> GetMessage(string messageId);
ValueTask<string> AddMessages(List<Message> messages);
ValueTask<string> UpdateMessages(List<Message> messages);
ValueTask<string> RemoveMessage(string messageId);
ValueTask<ICollection<ClientLogs>> GetClientLogs();
ValueTask AddLog(Exception exception, string where);
ValueTask AddLog(string message, string where);
ValueTask<bool> RemoveAll(bool includeClientLogs = false);
}
public class Storage : IStorage
{
readonly IndexedDb _db;
readonly ILogger<Storage> _logger;
public Storage(IndexedDb indexedDb, ILogger<Storage> logger)
{
_db = indexedDb;
_logger = logger;
}
#region Messagges
public async ValueTask<Message> GetMessage(string messageId)
{
await _db.OpenIndexedDb();
var message = await _db.GetByKey<string, Message>(nameof(Message), messageId);
if (message is null)
{
var messages = await GetMessages();
return messages.FirstOrDefault(m => m.MessageId == messageId);
}
return message;
}
public async ValueTask<List<Message>> GetMessages()
{
await _db.OpenIndexedDb();
var messages = await _db.GetAll<Message>(nameof(Message)) ?? new();
if (messages.Count == 0)
{
messages.Add(new()
{
RootMessageId = default,
MessageId = "992167EE-8947-4823-B1E2-B4E1019B6974",
User = Faker.GetRandomUser(),
MessageType = (MessageType)Random.Shared.Next(0, 4),
Title = default,
Content = "test di messaggio normale senza titolo",
Medias = new(),
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
IsFavourite = Random.Shared.Next() % 2 == 0,
IsBoosted = Random.Shared.Next() % 2 == 0,
});
messages.Add(new()
{
RootMessageId = default,
MessageId = "C5307644-4EA8-4B5F-A240-7CFE4DE34741",
User = Faker.GetRandomUser(),
MessageType = (MessageType)Random.Shared.Next(0, 4),
Title = "sopra la panca la capra canta",
Content = Markdown.ToHtml("test di messaggio con titolo"),
Medias = new(),
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
IsFavourite = Random.Shared.Next() % 2 == 0,
IsBoosted = Random.Shared.Next() % 2 == 0,
});
messages.Add(new()
{
RootMessageId = default,
MessageId = "E473D562-D210-4453-93DA-157D4B90B0D8",
User = Faker.GetRandomUser(),
MessageType = (MessageType)Random.Shared.Next(0, 4),
Title = default,
Content = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd test di messaggio con overflow del testo",
Medias = new(),
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
IsFavourite = Random.Shared.Next() % 2 == 0,
IsBoosted = Random.Shared.Next() % 2 == 0,
});
messages.Add(new()
{
RootMessageId = default,
MessageId = "adf89dc6-0484-430e-a7c9-fcb1179b0c28",
User = Faker.GetRandomUser(),
MessageType = (MessageType)Random.Shared.Next(0, 4),
Title = default,
Content = "test video",
Medias = new()
{
new()
{
FileName = "8252d175e714db88beb8ee0349dbac90405d3d5148e96e0cfd7de3b0876dbb1b.mp4",
Url = "https://ihatebeinga.live/media/8252d175e714db88beb8ee0349dbac90405d3d5148e96e0cfd7de3b0876dbb1b.mp4",
AltText = "https://ihatebeinga.live/media/8252d175e714db88beb8ee0349dbac90405d3d5148e96e0cfd7de3b0876dbb1b.mp4",
ContentType = "video/mp4"
}
},
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
IsFavourite = Random.Shared.Next() % 2 == 0,
IsBoosted = Random.Shared.Next() % 2 == 0,
});
messages.Add(new()
{
RootMessageId = default,
MessageId = "96025b43-5235-44e9-a3fa-98df31edfbfb",
User = Faker.GetRandomUser(),
MessageType = (MessageType)Random.Shared.Next(0, 4),
Title = default,
Content = "test immagine",
Medias = new()
{
new()
{
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
ContentType = "image/jpg"
}
},
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
IsFavourite = Random.Shared.Next() % 2 == 0,
IsBoosted = Random.Shared.Next() % 2 == 0,
});
messages.Add(new()
{
RootMessageId = default,
MessageId = "96025b43-5235-44e9-a3fa-98df31edfbfb",
User = Faker.GetRandomUser(),
MessageType = (MessageType)Random.Shared.Next(0, 4),
Title = default,
Content = "test immagini",
Medias = new()
{
new()
{
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
ContentType = "image/jpg"
},
new()
{
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
ContentType = "image/jpg"
},
new()
{
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
ContentType = "image/jpg"
}
},
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
IsFavourite = Random.Shared.Next() % 2 == 0,
IsBoosted = Random.Shared.Next() % 2 == 0,
});
}
messages = messages.OrderByDescending(m => m.CreatedAt).ToList();
return messages;
}
public async ValueTask<string> AddMessages(List<Message> messages)
{
await _db.OpenIndexedDb();
var result = await _db.AddItems(nameof(Message), messages);
_logger.LogInformation(result);
return result;
}
public async ValueTask<string> UpdateMessages(List<Message> messages)
{
await _db.OpenIndexedDb();
var result = await _db.UpdateItems(nameof(Message), messages);
_logger.LogInformation(result);
return result;
}
public async ValueTask<string> RemoveMessage(string messageId)
{
await _db.OpenIndexedDb();
var result = await _db.DeleteByKey(nameof(Message), messageId);
_logger.LogInformation(result);
return result;
}
#endregion
#region Logs
public async ValueTask<ICollection<ClientLogs>> GetClientLogs()
{
try
{
_logger.LogInformation($"opening on {nameof(GetClientLogs)}");
await _db.OpenIndexedDb();
return (await _db.GetAll<ClientLogs>(nameof(ClientLogs))).OrderByDescending(cl => cl.TimeStamp).ToArray();
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return new List<ClientLogs>();
}
}
public async ValueTask AddLog(Exception exception, string where)
{
try
{
await _db.OpenIndexedDb();
var maxKey = await _db.GetMaxKey<long>(nameof(ClientLogs));
var minKey = await _db.GetMinKey<long>(nameof(ClientLogs));
if (minKey - maxKey <= -100)
for (var i = minKey; i < minKey + 50; i++)
_ = await _db.DeleteByKey(nameof(ClientLogs), i);
maxKey += 1;
var result = await _db.AddItems<ClientLogs>(nameof(ClientLogs), new()
{
new()
{
Id = maxKey,
Where = where,
Exception = new()
{
Type = exception.GetType().ToString(),
HelpLink = exception.HelpLink,
HResult = exception.HResult.ToString(),
InnerExceptionMessage = exception.InnerException?.Message,
Message = exception.Message,
Source = exception.Source,
StackTrace = exception.StackTrace,
TargetSiteName = exception.TargetSite?.Name
}
}
});
_logger.LogDebug($"{nameof(AddLog)}() add logs result = {result}");
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
public async ValueTask AddLog(string message, string where)
{
try
{
await _db.OpenIndexedDb();
var maxKey = await _db.GetMaxKey<long>(nameof(ClientLogs));
var minKey = await _db.GetMinKey<long>(nameof(ClientLogs));
if (minKey - maxKey <= -100)
for (var i = minKey; i < minKey + 50; i++)
_ = await _db.DeleteByKey(nameof(ClientLogs), i);
maxKey += 1;
var result = await _db.AddItems<ClientLogs>(nameof(ClientLogs), new()
{
new()
{
Id = maxKey,
Where = where,
Exception = null,
WarningMessage = message
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
#endregion
public async ValueTask<bool> RemoveAll(bool includeClientLogs = false)
{
try
{
_logger.LogInformation($"opening on {nameof(RemoveAll)}");
await _db.OpenIndexedDb();
_ = await _db.DeleteAll(nameof(Message));
if (includeClientLogs)
_ = await _db.DeleteAll(nameof(ClientLogs));
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return default;
}
}
}
}

14
Services/IndexedDb.cs Normal file
View File

@ -0,0 +1,14 @@
using DnetIndexedDb;
using Microsoft.JSInterop;
namespace decePubClient.Services
{
public class IndexedDb : IndexedDbInterop
{
public IndexedDb(IJSRuntime jsRuntime, IndexedDbOptions<IndexedDb> options)
: base(jsRuntime, options)
{
}
}
}

View File

@ -1,10 +1,77 @@
namespace decePubClient.Services
using Blazored.Modal.Services;
using decePubClient.Models;
using decePubClient.Models.Types;
namespace decePubClient.Services
{
public class MessagesService
{
public MessagesService()
{
readonly IModalService modalService;
readonly IStorage storage;
readonly HttpClient http;
public MessagesService(IHttpClientFactory clientFactory, IModalService modalService, IStorage storage)
{
this.modalService = modalService;
this.storage = storage;
http = clientFactory.CreateClient("default");
}
//Covers also direct message
public async Task<Message> SubmitMessage(MessageForm messageForm)
{
//TODO
await Task.Run(() => {});
return new();
}
//Covers also direct message
public async Task<Message> ReplyMessage(Message messageToReply, MessageType messageType = MessageType.Public)
{
//TODO
await Task.Run(() => {});
return messageToReply;
}
public async Task<Message> BoostUnboostMessage(Message messageToReply)
{
//TODO
await Task.Run(() => {});
return messageToReply;
}
public async Task<Message> FavouriteUnfavouriteMessage(Message messageToReply)
{
//TODO
await Task.Run(() => {});
return messageToReply;
}
public async Task<Message> DeleteMessage(Message messageToReply)
{
//TODO
await Task.Run(() => {});
return messageToReply;
}
public async Task<MessageUser> BlockUserFromMessage(MessageUser messageUser)
{
//TODO
await Task.Run(() => {});
return messageUser;
}
public async Task<MessageUser> SilenceUserFromMessage(MessageUser messageUser)
{
//TODO
await Task.Run(() => {});
return messageUser;
}
}
}
}

24
Shared/LoginDisplay.razor Normal file
View File

@ -0,0 +1,24 @@
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
<Authorized>
<button class="button" @onclick="BeginSignOut">
@CascadingState.Localizer["Logout"]
</button>
</Authorized>
<NotAuthorized>
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button is-rounded neoBtnSmallPlain" href="authentication/login">
@CascadingState.Localizer["Login"]
</NavLink>
</NotAuthorized>
</AuthorizeView>
@code {
[CascadingParameter] CascadingState CascadingState { get; set; }
private async Task BeginSignOut(MouseEventArgs args)
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}

View File

@ -1,17 +1,11 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="relative flex flex-col md:flex-row w-full h-full">
<NavMenu />
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
<div class="block flex-1 relative w-full h-full">
<div class="block absolute overflow-y-auto w-full h-full p-3 md:py-4 md:pl-2 md:pr-4">
@Body
</div>
</div>
</div>

View File

@ -1,39 +1,222 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">decePubClient</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<nav class="flex justify-between align-center pt-3 px-3 md:p-0">
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</div>
</nav>
</div>
<div class="relative md:w-52">
<button class="button is-rounded inline-flex is-small neoBtnSmall relative md:hidden" @onclick="ToggleNavMenu">
<i class="ion-md-menu text-lg"></i>
</button>
<div class="absolute background md:relative top-full md:top-auto mt-2 w-52 md:w-full overflow-y-auto rounded-lg neomorph is-nsmall md:shadow-none z-50 @menuToggle md:block">
<div class="flex flex-col space-y-3 p-3 md:p-4">
<div class="flex space-x-3 p-3 md:p-4 neomorph is-nxsmall rounded-xl bg-cover bg-no-repeat bg-right" style="background-image:linear-gradient(to left, var(--background) 55%, transparent), url('@(CascadingState.User?.BackgroundUrl)');">
<a class="block h-8 w-8 md:h-12 md:w-12 flex-none" href="@CascadingState.User?.ProfileUrl" title="@CascadingState.User?.UserName">
<img alt="@CascadingState.User?.UserName" class="h-8 w-8 md:h-12 md:w-12 object-cover rounded-full neomorph is-nxxsmall" src="@(CascadingState.User?.PictureUrl ?? "/imgs/icon-192.png")" />
</a>
<div class="flex flex-col w-full min-w-0 space-y-2">
<p class="shrink truncate">
<b title="@CascadingState.User?.DisplayName">
@CascadingState.User?.DisplayName
</b>
</p>
<p class="flex-1 min-w-6 truncate text-xs">
<a class="underline" href="@CascadingState.User?.ProfileUrl" title="@CascadingState.User?.UserName">
@CascadingState.User?.UserName
</a>
</p>
</div>
</div>
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button has-icons-left is-rounded neoBtnSmallPlain" href="/" Match="NavLinkMatch.All">
<span class="icon is-left">
<i class="ion-md-home"></i>
</span>
<span>@CascadingState.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>
</NavLink>
@*<AuthorizeView>
<Authorized>
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button is-rounded neoBtnSmallPlain" href="administration">
@Localizer["Administration"]
</NavLink>
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button is-rounded neoBtnSmallPlain" href="logout">
@Localizer["Logout"]
</NavLink>
</Authorized>
<NotAuthorized>
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button is-rounded neoBtnSmallPlain" href="login">
@Localizer["Login"]
</NavLink>
</NotAuthorized>
</AuthorizeView>*@
<NavLink ActiveClass="neoBtnSmallInsetPlain" class="button has-icons-left is-rounded neoBtnSmallPlain" href="administration">
<span class="icon is-left has-text-danger">
<i class="ion-md-switch"></i>
</span>
<span>@CascadingState.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>
</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>
</NavLink>
<div class="field is-grouped is-align-items-center">
<div class="control">
<button @onclick:stopPropagation @onclick:preventDefault @onclick="ResetToOriginalColour" class="button is-rounded has-icons-left is-small mb-0 ml-0 neoBtn" style="background:hsl(25,84%,88%) !important"
type="button">
<span class="icon is-left"></span>
</button>
</div>
<div class="control is-expanded is-flex">
<input class="neoRange fullwidth" id="ColourIndex_@(RandomId)"
max="359" min="0" @onchange="UpdateThemeColour" step="1" type="range" value="@ThemeIndexColour">
</div>
</div>
<EditForm class="field is-grouped is-grouped-right" Context="DarkModeForm" Model="ThemeIsDarkMode">
<div class="control">
<label>
<InputCheckbox class="toggle-checkbox"
Value="ThemeIsDarkMode" ValueChanged="async v => await UpdateThemeDarkMode(v)" ValueExpression="() => ThemeIsDarkMode" />
<div class="toggle-slot neomorph is-nxxsmall cursor-pointer">
<div class="sun-icon-wrapper">
<div class="sun-icon">
<i class="ion-md-sunny text-xs"></i>
</div>
</div>
<div class="toggle-button"></div>
<div class="moon-icon-wrapper">
<div class="moon-icon">
<i class="ion-md-moon text-xs"></i>
</div>
</div>
</div>
</label>
</div>
</EditForm>
</div>
</div>
</div>
<div class="md:hidden">
<img alt="" class="h-[30px]" src="imgs/icon-512.png">
</div>
<div class="md:hidden">
<a class="neomorph is-nxxsmall" href="settings">
<img alt="@CascadingState.User?.UserName" class="object-cover w-[30px] h-[30px] rounded-full"
src="@CascadingState.User?.PictureUrl">
</a>
@* <button class="button is-rounded is-small has-icons-left neoBtnSmall "> *@
@* $1$ <AuthorizeView> #1# *@
@* $1$ <Authorized> #1# *@
@* $1$ <span class="icon is-left"> #1# *@
@* $1$ <img alt="@CascadingState.User.UserName" class="object-cover" src="@CascadingState.User.PictureUrl"> #1# *@
@* $1$ </span> #1# *@
@* $1$ </Authorized> #1# *@
@* $1$ <NotAuthorized> #1# *@
@* $1$ <span class="icon is-left"> #1# *@
@* $1$ <i class="ion-md-person"></i> #1# *@
@* $1$ </span> #1# *@
@* $1$ </NotAuthorized> #1# *@
@* $1$ </AuthorizeView> #1# *@
@* </button> *@
</div>
</nav>
@code {
private bool collapseNavMenu = true;
[CascadingParameter] CascadingState CascadingState { get; set; }
string menuToggle = "hidden";
bool IsThemeChanging { get; set; } = false;
bool ThemeIsDarkMode { get; set; } = false;
short ThemeIndexColour { get; set; } = 25;
int RandomId { get; set; }
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
protected override async Task OnInitializedAsync()
{
await Task.Run(() =>
{
});
RandomId = Random.Shared.Next(0, int.MaxValue);
if (!CascadingState.Status.IsMobileMedia())
menuToggle = default;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
ThemeIsDarkMode = CascadingState.PublicCacheData?.ThemeIsDarkMode ?? false;
ThemeIndexColour = CascadingState.PublicCacheData?.ThemeIndexColour ?? 25;
}
private void ToggleNavMenu()
{
menuToggle = menuToggle is { Length: > 0 } ? default : "hidden";
}
protected async Task ResetToOriginalColour()
{
IsThemeChanging = true;
CascadingState.PublicCacheData.ThemeIndexColour =
ThemeIndexColour =
25;
// if (AuthData?.User != null)
// {
// AuthData.User.UserSettings.ThemeIndexColour = ThemeIndexColour;
// await Storage.SetItemAsync(nameof(AuthData), AuthData);
// }
await CascadingState.UpdatePublicCache(CascadingState.PublicCacheData);
IsThemeChanging = false;
}
protected async Task UpdateThemeColour(ChangeEventArgs eventArgs)
{
IsThemeChanging = true;
var indexColour = short.Parse(eventArgs.Value?.ToString());
CascadingState.PublicCacheData.ThemeIndexColour =
ThemeIndexColour =
indexColour;
// if (AuthData?.User != null)
// {
// AuthData.User.UserSettings.ThemeIndexColour = ThemeIndexColour;
// await Storage.SetItemAsync(nameof(AuthData), AuthData);
// }
await CascadingState.UpdatePublicCache(CascadingState.PublicCacheData);
IsThemeChanging = false;
}
protected async Task UpdateThemeDarkMode(bool isDarkMode)
{
IsThemeChanging = true;
CascadingState.PublicCacheData.ThemeIsDarkMode =
ThemeIsDarkMode = isDarkMode;
Console.WriteLine("Dark updated {0}", ThemeIsDarkMode);
// if (AuthData?.User != null)
// {
// AuthData.User.UserSettings.ThemeIsDarkMode = ThemeIsDarkMode;
// await Storage.SetItemAsync(nameof(AuthData), AuthData);
// }
await CascadingState.UpdatePublicCache(CascadingState.PublicCacheData);
IsThemeChanging = false;
}
}

View File

@ -0,0 +1,8 @@
@inject NavigationManager Navigation
@code {
protected override void OnInitialized()
{
Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
}
}

View File

@ -1,16 +0,0 @@
<div class="alert alert-secondary mt-4">
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2148851">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}

View File

@ -1,10 +1,31 @@
@using System.Net.Http
@using System.IO
@using System.Net.Http
@using System.Net.Http.Json
@using System.Security.Claims
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@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
@using decePubClient
@using decePubClient.Services
@using decePubClient.Shared
@using decePubClient.Models
@using decePubClient.Models.Types
@using decePubClient.Helpers
@using decePubClient.Resources
@using decePubClient.Extensions
@using decePubClient.Components
@using decePubClient.LayerComponents

View File

@ -1,3 +1,22 @@
{
}
[
{
"outputFileName": "wwwroot/css/style.min.css",
"inputFiles": [
// "wwwroot/vendor/solid.css",
// "wwwroot/vendor/fontawesome.css",
//"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/tailwind.css",
"wwwroot/css/tailwind-override.css"
],
"minify": {
"enabled": false,
"gzip": false,
"brotli": false
},
"sourceMap": false
}
]

View File

@ -1 +0,0 @@
///<binding AfterBuild='Clean output files, Update all files, Stylesheets, wwwroot/css/style.min.css' />

View File

@ -1,3 +1,12 @@
{
}
[
{
"outputFile": "wwwroot/css/main.css",
"inputFile": "SCSS/main.scss",
"minify": {
"enabled": false
},
"options": {
"sourceMap": false
}
}
]

View File

@ -1,19 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.1" PrivateAssets="all" />
<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="6.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Append.Blazor.Notifications" Version="1.1.0" />
<PackageReference Include="BlazorDownloadFileFast" Version="0.2.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.2.0" />
<PackageReference Include="Blazored.Modal" Version="6.0.1" />
<PackageReference Include="DnetIndexedDb" Version="2.3.1" />
<PackageReference Include="Markdig" Version="0.27.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.2" />
<PackageReference Include="Toolbelt.Blazor.HeadElement" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\ErrorMessages.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ErrorMessages.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\AllStrings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AllStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Resources\ErrorMessages.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>ErrorMessages.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="npm run buildcss" />
</Target>
</Project>

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "decePubClient", "decePubClient.csproj", "{EBE69805-3005-49C2-81E4-A314A19F68B6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "decePubClient", "decePubClient.csproj", "{EBE69805-3005-49C2-81E4-A314A19F68B6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

216
package-lock.json generated
View File

@ -8,6 +8,7 @@
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
"integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
"dev": true,
"requires": {
"@babel/highlight": "^7.16.7"
}
@ -15,12 +16,14 @@
"@babel/helper-validator-identifier": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
"dev": true
},
"@babel/highlight": {
"version": "7.16.10",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
"integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.16.7",
"chalk": "^2.0.0",
@ -31,6 +34,7 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
@ -39,6 +43,7 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@ -49,6 +54,7 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
@ -56,17 +62,20 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
@ -96,20 +105,55 @@
"fastq": "^1.6.0"
}
},
"@tailwindcss/aspect-ratio": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.0.tgz",
"integrity": "sha512-WJu0I4PpqNPuutpaA9zDUq2JXR+lorZ7PbLcKNLmb6GL9/HLfC7w3CRsMhJF4BbYd/lkY6CfXOvkYpuGnZfkpQ==",
"dev": true
},
"@tailwindcss/forms": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.4.0.tgz",
"integrity": "sha512-DeaQBx6EgEeuZPQACvC+mKneJsD8am1uiJugjgQK1+/Vt+Ai0GpFBC2T2fqnUad71WgOxyrZPE6BG1VaI6YqfQ==",
"dev": true,
"requires": {
"mini-svg-data-uri": "^1.2.3"
}
},
"@tailwindcss/line-clamp": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.3.1.tgz",
"integrity": "sha512-pNr0T8LAc3TUx/gxCfQZRe9NB2dPEo/cedPHzUGIPxqDMhgjwNm6jYxww4W5l0zAsAddxr+XfZcqttGiFDgrGg==",
"dev": true
},
"@tailwindcss/typography": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.1.tgz",
"integrity": "sha512-AmSzZSgLhHKlILKduU+PKBTHL6c+al82syZlRid1xgmlWwXagLigO+O++B4C0scpMfzW//f/3YCRcwwEHWoU3w==",
"dev": true,
"requires": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2"
}
},
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
},
"acorn-node": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
"integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
"dev": true,
"requires": {
"acorn": "^7.0.0",
"acorn-walk": "^7.0.0",
@ -119,7 +163,8 @@
"acorn-walk": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
"dev": true
},
"ansi-regex": {
"version": "5.0.1",
@ -146,7 +191,8 @@
"arg": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz",
"integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA=="
"integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==",
"dev": true
},
"array-union": {
"version": "3.0.1",
@ -157,6 +203,7 @@
"version": "10.4.2",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz",
"integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==",
"dev": true,
"requires": {
"browserslist": "^4.19.1",
"caniuse-lite": "^1.0.30001297",
@ -183,6 +230,7 @@
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz",
"integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001286",
"electron-to-chromium": "^1.4.17",
@ -194,22 +242,26 @@
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
"camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001306",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001306.tgz",
"integrity": "sha512-Wd1OuggRzg1rbnM5hv1wXs2VkxJH/AA+LuudlIqvZiCvivF+wJJe2mgBZC8gPMgI7D76PP5CTx8Luvaqc1V6OQ=="
"integrity": "sha512-Wd1OuggRzg1rbnM5hv1wXs2VkxJH/AA+LuudlIqvZiCvivF+wJJe2mgBZC8gPMgI7D76PP5CTx8Luvaqc1V6OQ==",
"dev": true
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@ -257,6 +309,7 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
"integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
"dev": true,
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
@ -268,12 +321,14 @@
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
"defined": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
"dev": true
},
"dependency-graph": {
"version": "0.11.0",
@ -284,6 +339,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
"integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
"dev": true,
"requires": {
"acorn-node": "^1.6.1",
"defined": "^1.0.0",
@ -293,7 +349,8 @@
"didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true
},
"dir-glob": {
"version": "3.0.1",
@ -306,12 +363,14 @@
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
"electron-to-chromium": {
"version": "1.4.63",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.63.tgz",
"integrity": "sha512-e0PX/LRJPFRU4kzJKLvTobxyFdnANCvcoDCe8XcyTqP58nTWIwdsHvXLIl1RkB39X5yaosLaroMASWB0oIsgCA=="
"integrity": "sha512-e0PX/LRJPFRU4kzJKLvTobxyFdnANCvcoDCe8XcyTqP58nTWIwdsHvXLIl1RkB39X5yaosLaroMASWB0oIsgCA==",
"dev": true
},
"emoji-regex": {
"version": "8.0.0",
@ -322,6 +381,7 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "^0.2.1"
}
@ -334,7 +394,8 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"fast-glob": {
"version": "3.2.11",
@ -367,7 +428,8 @@
"fraction.js": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz",
"integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA=="
"integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==",
"dev": true
},
"fs-extra": {
"version": "10.0.0",
@ -388,7 +450,8 @@
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"get-caller-file": {
"version": "2.0.5",
@ -430,6 +493,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
@ -437,7 +501,8 @@
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"ignore": {
"version": "5.2.0",
@ -448,6 +513,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@ -456,7 +522,8 @@
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-binary-path": {
"version": "2.1.0",
@ -470,6 +537,7 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
"integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
@ -500,12 +568,14 @@
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
},
"jsonfile": {
"version": "6.1.0",
@ -524,7 +594,26 @@
"lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
},
"lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=",
"dev": true
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
"dev": true
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"merge2": {
"version": "1.4.1",
@ -540,20 +629,29 @@
"picomatch": "^2.2.3"
}
},
"mini-svg-data-uri": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.3.tgz",
"integrity": "sha512-gSfqpMRC8IxghvMcxzzmMnWpXAChSA+vy4cia33RgerMS8Fex95akUyQZPbxJJmeBGiGmK7n/1OpUX8ksRjIdA==",
"dev": true
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"nanoid": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA=="
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==",
"dev": true
},
"node-releases": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
"integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA=="
"integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==",
"dev": true
},
"normalize-path": {
"version": "3.0.0",
@ -563,17 +661,20 @@
"normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI="
"integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
"dev": true
},
"object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
"dev": true
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
}
@ -582,6 +683,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
@ -592,7 +694,8 @@
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"path-type": {
"version": "4.0.0",
@ -618,6 +721,7 @@
"version": "8.4.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz",
"integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==",
"dev": true,
"requires": {
"nanoid": "^3.2.0",
"picocolors": "^1.0.0",
@ -643,10 +747,28 @@
"yargs": "^17.0.0"
}
},
"postcss-discard-comments": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.2.tgz",
"integrity": "sha512-6VQ3pYTsJHEsN2Bic88Aa7J/Brn4Bv8j/rqaFQZkH+pcVkKYwxCIvoMQkykEW7fBjmofdTnQgcivt5CCBJhtrg==",
"dev": true
},
"postcss-import": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.0.2.tgz",
"integrity": "sha512-BJ2pVK4KhUyMcqjuKs9RijV5tatNzNa73e/32aBVE/ejYPe37iH+6vAu9WvqUkB5OAYgLHzbSvzHnorybJCm9g==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0",
"resolve": "^1.1.7"
}
},
"postcss-js": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz",
"integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==",
"dev": true,
"requires": {
"camelcase-css": "^2.0.1"
}
@ -664,10 +786,20 @@
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
"integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.6"
}
},
"postcss-nesting": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.1.2.tgz",
"integrity": "sha512-dJGmgmsvpzKoVMtDMQQG/T6FSqs6kDtUDirIfl4KnjMCiY9/ETX8jdKyCd20swSRAbUYkaBKV20pxkzxoOXLqQ==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.8"
}
},
"postcss-reporter": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.0.5.tgz",
@ -681,6 +813,7 @@
"version": "6.0.9",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz",
"integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -689,7 +822,8 @@
"postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"pretty-hrtime": {
"version": "1.0.3",
@ -704,7 +838,8 @@
"quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true
},
"read-cache": {
"version": "1.0.0",
@ -731,6 +866,7 @@
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
"integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
"dev": true,
"requires": {
"is-core-module": "^2.8.1",
"path-parse": "^1.0.7",
@ -740,7 +876,8 @@
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"reusify": {
"version": "1.0.4",
@ -763,7 +900,8 @@
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"string-width": {
"version": "4.2.3",
@ -787,6 +925,7 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
@ -794,12 +933,14 @@
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"tailwindcss": {
"version": "3.0.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.18.tgz",
"integrity": "sha512-ihPTpEyA5ANgZbwKlgrbfnzOp9R5vDHFWmqxB1PT8NwOGCOFVVMl+Ps1cQQ369acaqqf1BEF77roCwK0lvNmTw==",
"dev": true,
"requires": {
"arg": "^5.0.1",
"chalk": "^4.1.2",
@ -827,6 +968,7 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"requires": {
"is-glob": "^4.0.3"
}
@ -854,7 +996,8 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
},
"wrap-ansi": {
"version": "7.0.0",
@ -869,7 +1012,8 @@
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true
},
"y18n": {
"version": "5.0.8",

View File

@ -4,12 +4,27 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"buildcss": "postcss wwwroot/vendor/tailwind-generator.css -o wwwroot/vendor/tailwind.css"
},
"repository": {
"type": "git",
"url": "https://git.thepra.dev/thepra/decePubClient.git"
},
"author": "",
"license": "ISC"
"license": "ISC",
"dependencies": {
"postcss-cli": "^9.1.0"
},
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.0",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/line-clamp": "^0.3.0",
"@tailwindcss/typography": "^0.5.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.5",
"postcss-import": "^14.0.2",
"postcss-nesting": "^10.0.3",
"tailwindcss": "^3.0.18",
"postcss-discard-comments": "^5.0.2"
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-discard-comments': {}
}
}

View File

@ -1,7 +1,13 @@
const colors = require('tailwindcss/colors');
module.exports = {
content: [],
theme: {
extend: {},
},
plugins: [],
content: [
'./**/*.cs',
'./**/*.html',
'./**/*.razor'
],
theme: {
extend: {},
},
variants: {},
plugins: [],
}

13
wwwroot/appsettings.json Normal file
View File

@ -0,0 +1,13 @@
{
"Local": {
"Authority": "https://openidconnect.net",
"ClientId": "1234.locahost",
"RedirectUri": "",
"MetadataUrl": "",
"PostLogoutRedirectUri": "",
"ResponseType": "",
"ResponseMode": "",
"AdditionalProviderParameters": [],
"DefaultScopes": []
}
}

View File

@ -2,10 +2,10 @@
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/imgs/mstile-70x70.png" />
<square150x150logo src="/imgs/mstile-150x150.png" />
<square310x310logo src="/imgs/mstile-310x310.png" />
<wide310x150logo src="/imgs/mstile-310x150.png" />
<square70x70logo src="/imgs/icon-192.png" />
<square150x150logo src="/imgs/icon-512.png" />
<square310x310logo src="/imgs/icon-512.png" />
<wide310x150logo src="/imgs/icon-512.png" />
<TileColor>#fadcc7</TileColor>
</tile>
</msapplication>

View File

@ -1,3 +1,447 @@
: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);
/*--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(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; }
:root {
--shadow-offset: 8px;
--blur-radius: 16px;
--is-inset: inherit; }
/*# sourceMappingURL=main.css.map */
.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); }
.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); }
.neomorphInset.is-nxxsmall {
--shadow-offset: 2px;
--blur-radius: 4px; }
@media (min-width: 640px) {
.neomorphInset.is-nxxsmall-sm {
--shadow-offset: 2px;
--blur-radius: 4px; } }
@media (min-width: 768px) {
.neomorphInset.is-nxxsmall-md {
--shadow-offset: 2px;
--blur-radius: 4px; } }
@media (min-width: 1024px) {
.neomorphInset.is-nxxsmall-lg {
--shadow-offset: 2px;
--blur-radius: 4px; } }
@media (min-width: 1280px) {
.neomorphInset.is-nxxsmall-xl {
--shadow-offset: 2px;
--blur-radius: 4px; } }
.neomorphInset.is-nxsmall {
--shadow-offset: 3px;
--blur-radius: 6px; }
@media (min-width: 640px) {
.neomorphInset.is-nxsmall-sm {
--shadow-offset: 3px;
--blur-radius: 6px; } }
@media (min-width: 768px) {
.neomorphInset.is-nxsmall-md {
--shadow-offset: 3px;
--blur-radius: 6px; } }
@media (min-width: 1024px) {
.neomorphInset.is-nxsmall-lg {
--shadow-offset: 3px;
--blur-radius: 6px; } }
@media (min-width: 1280px) {
.neomorphInset.is-nxsmall-xl {
--shadow-offset: 3px;
--blur-radius: 6px; } }
.neomorphInset.is-nsmall {
--shadow-offset: 6px;
--blur-radius: 12px; }
@media (min-width: 640px) {
.neomorphInset.is-nsmall-sm {
--shadow-offset: 6px;
--blur-radius: 12px; } }
@media (min-width: 768px) {
.neomorphInset.is-nsmall-md {
--shadow-offset: 6px;
--blur-radius: 12px; } }
@media (min-width: 1024px) {
.neomorphInset.is-nsmall-lg {
--shadow-offset: 6px;
--blur-radius: 12px; } }
@media (min-width: 1280px) {
.neomorphInset.is-nsmall-xl {
--shadow-offset: 6px;
--blur-radius: 12px; } }
.neomorph.is-nxxsmall {
--shadow-offset: 2px;
--blur-radius: 4px; }
@media (min-width: 640px) {
.neomorph.is-nxxsmall-sm {
--shadow-offset: 2px;
--blur-radius: 4px; } }
@media (min-width: 768px) {
.neomorph.is-nxxsmall-md {
--shadow-offset: 2px;
--blur-radius: 4px; } }
@media (min-width: 1024px) {
.neomorph.is-nxxsmall-lg {
--shadow-offset: 2px;
--blur-radius: 4px; } }
@media (min-width: 1280px) {
.neomorph.is-nxxsmall-xl {
--shadow-offset: 2px;
--blur-radius: 4px; } }
.neomorph.is-nxsmall {
--shadow-offset: 3px;
--blur-radius: 6px; }
@media (min-width: 640px) {
.neomorph.is-nxsmall-sm {
--shadow-offset: 3px;
--blur-radius: 6px; } }
@media (min-width: 768px) {
.neomorph.is-nxsmall-md {
--shadow-offset: 3px;
--blur-radius: 6px; } }
@media (min-width: 1024px) {
.neomorph.is-nxsmall-lg {
--shadow-offset: 3px;
--blur-radius: 6px; } }
@media (min-width: 1280px) {
.neomorph.is-nxsmall-xl {
--shadow-offset: 3px;
--blur-radius: 6px; } }
.neomorph.is-nsmall {
--shadow-offset: 6px;
--blur-radius: 12px; }
@media (min-width: 640px) {
.neomorph.is-nsmall-sm {
--shadow-offset: 6px;
--blur-radius: 12px; } }
@media (min-width: 768px) {
.neomorph.is-nsmall-md {
--shadow-offset: 6px;
--blur-radius: 12px; } }
@media (min-width: 1024px) {
.neomorph.is-nsmall-lg {
--shadow-offset: 6px;
--blur-radius: 12px; } }
@media (min-width: 1280px) {
.neomorph.is-nsmall-xl {
--shadow-offset: 6px;
--blur-radius: 12px; } }
.neomorph.is-nnormal {
--shadow-offset: 8px;
--blur-radius: 16px; }
@media (min-width: 640px) {
.neomorph.is-nnormal-sm {
--shadow-offset: 8px;
--blur-radius: 16px; } }
@media (min-width: 768px) {
.neomorph.is-nnormal-md {
--shadow-offset: 8px;
--blur-radius: 16px; } }
@media (min-width: 1024px) {
.neomorph.is-nnormal-lg {
--shadow-offset: 8px;
--blur-radius: 16px; } }
@media (min-width: 1280px) {
.neomorph.is-nnormal-xl {
--shadow-offset: 8px;
--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; }
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; }
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; }
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; }
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); }
button.neoBtnSmall:focus input[type=file], .neoBtnSmall:focus input[type=file] {
background-image: linear-gradient(-45deg, var(--primary-gradiend-light), var(--primary-gradiend-dark)); }
button.neoBtnSmallPlain, .neoBtnSmallPlain {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
border: none; }
button.neoBtnSmallPlain:focus, .neoBtnSmallPlain:focus {
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
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; }
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; }
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; }
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; }
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; }
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; }
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); }
button.neoFile, .neoFile {
box-shadow: 0px 0px 0px var(--light-shadow), 0px 0px 0px var(--dark-shadow);
transition: all .2s linear;
-webkit-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); }
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; }
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); }
button.neoFile.isSelected:focus-visible, .neoFile.isSelected:focus-visible {
background: linear-gradient(-45deg, var(--primary-gradiend-lighter), var(--primary-gradiend-darker));
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); }
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; }
button.neoInput:focus, .neoInput:focus {
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
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; }
button.neoSelect > select:focus, .neoSelect > select:focus {
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter));
border: none; }
.neoCheckbox {
opacity: 0;
width: 0; }
.neoCheckbox:focus + label:before {
background: linear-gradient(145deg, var(--primary-gradiend-darker), var(--primary-gradiend-lighter)) !important; }
.neoCheckboxContainer {
position: relative; }
.neoCheckbox + label {
padding: .15rem .15rem .15rem 2rem;
cursor: pointer;
font-size: 1rem;
line-height: 1.5; }
.neoCheckbox + label:before {
animation-name: none;
width: 1.5rem;
height: 1.5rem;
border-radius: 100px;
position: absolute;
left: 0;
top: 0rem;
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; }
.neoCheckbox:checked.is-checked-bold + label {
font-weight: bold; }
.neoCheckbox:checked + label:after {
display: inline-block;
width: .375rem;
height: .6rem;
top: .35rem;
left: .55rem;
transform: translateY(0rem) rotate(45deg);
border-width: .1rem;
border-top-width: 0.1rem;
border-left-width: 0.1rem;
border-style: solid;
border-top-style: solid;
border-left-style: solid;
border-color: var(--text-color);
border-top: 0;
border-left: 0;
position: absolute;
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); }
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)); }
input.neoRange[type=range] {
height: 30px;
-webkit-appearance: none;
width: 100%;
background: transparent; }
input.neoRange[type=range]:focus {
outline: none; }
input.neoRange[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 20px;
cursor: pointer;
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; }
input.neoRange[type=range]::-webkit-slider-thumb {
border: none;
height: 26px;
width: 26px;
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;
-webkit-appearance: none;
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; }
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; }
input.neoRange[type=range]::-moz-range-thumb {
border: none;
height: 26px;
width: 26px;
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; }
body {
color: var(--text-color);
background-color: var(--background); }
.background {
background-color: var(--background); }
details > summary::-webkit-details-marker {
color: var(--text-color); }
:focus-visible {
outline: none; }
*, ::after, ::before {
scrollbar-color: inherit;
scrollbar-width: inherit; }
::-webkit-scrollbar {
width: 8px; }
::-webkit-scrollbar-button {
width: 8px;
height: 5px; }
::-webkit-scrollbar-track {
background: transparent;
border: thin solid transparent;
box-shadow: none;
border-radius: 10px; }
::-webkit-scrollbar-thumb {
background: var(--primary-color-dark);
border: thin solid transparent;
border-radius: 10px; }
::-webkit-scrollbar-thumb:hover {
background: var(--primary-color-dark); }
::-moz-selection {
/* Code for Firefox */
color: var(--primary-color);
background: var(--primary-color-dark); }
::selection {
color: var(--primary-color);
background: var(--primary-color-dark); }
.flex.flex-col-reverse > div:first-child {
margin-top: 1rem; }
.loadAnimation span {
animation-name: blink;
animation-duration: 1.4s;
animation-iteration-count: infinite;
animation-fill-mode: both; }
.loadAnimation span:nth-child(1) {
animation-delay: .4s; }
.loadAnimation span:nth-child(2) {
animation-delay: .3s; }
.loadAnimation span:nth-child(3) {
animation-delay: .2s; }
.loadAnimation span:nth-child(4) {
animation-delay: .2s; }
.loadAnimation span:nth-child(5) {
animation-delay: .3s; }
.loadAnimation span:nth-child(6) {
animation-delay: .4s; }

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,6 @@
a {
text-decoration: underline;
}
a.button{
text-decoration: none;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

Binary file not shown.

View File

@ -2,28 +2,32 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>decePubClient</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="decePubClient.styles.css" rel="stylesheet" />
<link href="manifest.json" rel="manifest" />
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
<meta charset="utf-8" />
<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" />
<link rel="apple-touch-icon" sizes="192x192" href="imgs/icon-192.png" />
</head>
<body>
<div id="app">Loading...</div>
<div id="app" class="block relative h-screen w-full">
<div class="mx-auto my-auto">Loading...</div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script>navigator.serviceWorker.register('service-worker.js');</script>
<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>
<script>navigator.serviceWorker.register('service-worker.js');</script>
<script src="main.js"></script>
</body>
</html>

View File

@ -0,0 +1,164 @@
window.cascadingStateInstanceReference = (dotNetReference) => {
try {
window.cascadingStateInstance = dotNetReference
} catch (e) {
console.error(e)
}
}
window.logFromJs = (message, where) => {
try {
if (!window.cascadingStateInstance ||
typeof window.cascadingStateInstance.invokeMethodAsync != "function")
return
window.cascadingStateInstance.invokeMethodAsync('LogFromJs', message, where)
} catch (e) {
console.error(e)
}
}
window.isServiceWorkerAvailable = () => {
try {
return navigator.serviceWorker.valueOf().controller != null
} catch (e) {
console.error(e)
window.logFromJs(e.message, "isServiceWorkerAvailable")
}
}
window.getLanguage = () => {
try {
return navigator.language.substr(0, 2)
} catch (e) {
console.error(e)
window.logFromJs(e.message, "getLanguage")
}
}
window.setLanguage = (lang) => {
try {
navigator.language = lang
} catch (e) {
console.error(e)
window.logFromJs(e.message, "setLanguage")
}
}
window.isOnline = async () => {
try {
const isOnline = navigator.onLine
//const url = "https://contents.nuvola.xyz/api/assets/6d9f5e55-2034-456f-92e7-d9ec51066443"
//let isOnlineStatusCode = true
//try {
// const online = await fetch(url)
// isOnlineStatusCode = online.status >= 200 && online.status < 399
//} catch (_) {
// isOnlineStatusCode = false
//}
if (navigator.connection !== undefined &&
navigator.connection.effectiveType !== undefined &&
navigator.connection.type !== undefined) {
if (!isOnline)
return isOnline
return navigator.connection.type === "cellular" ?
navigator.connection.effectiveType.indexOf("2g") === -1 /*&& isOnlineStatusCode*/ :
true
} else
return isOnline //&& isOnlineStatusCode
} catch (e) {
console.error(e)
window.logFromJs(e.message, "isOnline")
}
}
window.clearCache = async () => {
try {
const cachesNames = await caches.keys()
for (let i = 0; i < cachesNames.length; i++)
await caches.delete(cachesNames[i])
} catch (e) {
console.error(e)
window.logFromJs(e.message, "clearCache")
}
}
window.clearLocalStorage = () => {
try {
localStorage.removeItem("PrivateCacheData")
localStorage.removeItem("AuthData")
} catch (e) {
console.error(e)
window.logFromJs(e.message, "clearLocalStorage")
}
}
window.reloadPage = () => {
try {
location.reload(true)
} catch (e) {
console.error(e)
window.logFromJs(e.message, "reloadPage")
}
}
window.getHeight = (classId) => {
try {
const elements = document.querySelectorAll(classId)
let px = 0
if (!elements) return 0
elements.forEach(v => px += v.offsetHeight)
return px
} catch (e) {
console.error(e)
window.logFromJs(e.message, "getHeight")
}
}
window.setAppBadge = (counter) => {
try {
navigator.setAppBadge(counter)
} catch (e) {
console.error(e)
window.logFromJs(e.message, "setAppBadge")
}
}
window.clearAppBadge = () => {
try {
navigator.clearAppBadge()
} catch (e) {
console.error(e)
window.logFromJs(e.message, "clearAppBadge")
}
}
window.isMobileMedia = () => {
try {
return ((window.innerWidth > 0) ? window.innerWidth : screen.width) <= 768
} catch (e) {
console.error(e)
window.logFromJs(e.message, "isMobileMedia")
}
}
window.canShareStuff = () => {
try {
return navigator.share !== undefined
} catch (e) {
console.error(e)
window.logFromJs(e.message, "canShareStuff")
}
}
window.shareTextUrl = (title, text, url) => {
try {
navigator.share({
title: title,
text: text,
url: url
})
} catch (e) {
console.error(e)
window.logFromJs(e.message, "shareTextUrl")
}
}
window.shareText = (title, text) => {
try {
navigator.share({
title: title,
text: text
})
} catch (e) {
console.error(e)
window.logFromJs(e.message, "shareText")
}
}

View File

@ -1,21 +1,42 @@
{
"name": "decePubClient",
"short_name": "decePubClient",
"description": "Dece this pub",
"dir": "ltr",
"start_url": "./",
"scope": "/",
"id": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#03173d",
"background_color": "#FADCC7",
"theme_color": "#FADCC7",
"prefer_related_applications": false,
"icons": [
{
"src": "icon-512.png",
"src": "/imgs/icon-512.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "icon-192.png",
"src": "/imgs/icon-192.png",
"type": "image/png",
"sizes": "192x192"
}
]
],
"categories": [
"fediverse",
"government",
"social",
"threads",
"discussions",
"communication"
],
"screenshots": [
{
"src": "/imgs/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"shortcuts": [],
"version": "0.1"
}

View File

@ -1,27 +0,0 @@
[
{
"date": "2018-05-06",
"temperatureC": 1,
"summary": "Freezing"
},
{
"date": "2018-05-07",
"temperatureC": 14,
"summary": "Bracing"
},
{
"date": "2018-05-08",
"temperatureC": -13,
"summary": "Freezing"
},
{
"date": "2018-05-09",
"temperatureC": -16,
"summary": "Balmy"
},
{
"date": "2018-05-10",
"temperatureC": -2,
"summary": "Chilly"
}
]

8019
wwwroot/vendor/bulma.css vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,3 @@
/*!
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
.fa,
.fas,
.far,
@ -187,8 +183,6 @@
.fa-inverse {
color: #fff; }
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */
.fa-500px:before {
content: "\f26e"; }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,10 @@
/*!
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face {
font-family: 'Font Awesome 5 Free';
font-style: normal;
font-weight: 900;
font-display: block;
src: url("../webfonts/fa-solid-900.eot");
src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); }
src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
.fa,
.fas {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
.toggle-checkbox {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.toggle-slot {
position: relative;
height: 2em;
width: 3em;
border: none;
border-radius: 10em;
background-color: white;
transition: background-color 250ms;
}
.toggle-checkbox:checked ~ .toggle-slot {
background-color: #374151;
}
.toggle-button {
transform: translate(1.6em, 0.5em);
position: absolute;
height: 1em;
width: 1em;
border-radius: 50%;
background-color: #ffeccf;
transition: background-color 250ms, border-color 250ms, transform 500ms cubic-bezier(.26, 2, .46, .71);
}
.toggle-checkbox:checked ~ .toggle-slot .toggle-button {
background-color: #485367;
transform: translate(0.5em, 0.5em);
}
.sun-icon {
position: absolute;
height: 1em;
width: 1em;
color: #ffbb52;
}
.sun-icon-wrapper {
position: absolute;
height: 1em;
width: 1em;
opacity: 1;
transform: translate(0.55em, 0.25em) rotate(15deg);
transform-origin: 50% 50%;
transition: opacity 150ms, transform 500ms cubic-bezier(.26, 2, .46, .71);
}
.toggle-checkbox:checked ~ .toggle-slot .sun-icon-wrapper {
opacity: 0;
transform: translate(1.6em, 0.5em) rotate(0deg);
}
.moon-icon {
position: absolute;
height: 1em;
width: 1em;
color: white;
}
.moon-icon-wrapper {
position: absolute;
height: 1em;
width: 1em;
opacity: 0;
transform: translate(1.6em, .5em) rotate(0deg);
transform-origin: 50% 50%;
transition: opacity 150ms, transform 500ms cubic-bezier(.26, 2.5, .46, .71);
}
.toggle-checkbox:checked ~ .toggle-slot .moon-icon-wrapper {
opacity: 1;
transform: translate(1.85em, 0.15em) rotate(-15deg);
}