housekeeping updates #7

Merged
nseguin merged 15 commits from dev into main 2024-03-29 14:10:45 -05:00
24 changed files with 367 additions and 334 deletions

View file

@ -13,4 +13,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: docker build - name: docker build
run: docker build -t nseguin42/website:${{ github.sha }} . run: docker build -t nseguin42/website:${{ github.sha }} .
- name: cleanup old images
run: docker image prune -f --filter "until=24h"

View file

@ -1,19 +1,30 @@
<!DOCTYPE html> @using Microsoft.AspNetCore.Mvc.ViewFeatures
@using JetBrains.Annotations
@inject IFileVersionProvider FileVersionProvider
<!DOCTYPE html>
<html lang="en"> <html lang="en">
<!--suppress HtmlRequiredTitleElement -->
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/> <base href="@PathBase"/>
<link rel="stylesheet" href="css/styles.css" asp-append-version="true"/> <link rel="stylesheet" href="@AddVersionToPath("css/styles.css")"/>
<link rel="stylesheet" href="app.css"/> <link rel="stylesheet" href="@AddVersionToPath("app.css")"/>
<link rel="stylesheet" href="NSeguin.Dev.Web.styles.css"/>
<link rel="icon" type="image/png" href="favicon.ico"/> <link rel="icon" type="image/png" href="favicon.ico"/>
<HeadOutlet/> <HeadOutlet/>
</head> </head>
<body> <body>
<Routes/> <Routes/>
@* ReSharper disable once Html.PathError *@
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>
<script src="js/app.js"></script> <script src="@AddVersionToPath("js/app.js")"></script>
</body> </body>
</html> </html>
@code {
private const string PathBase = "/";
private string AddVersionToPath([PathReference] string path) => FileVersionProvider.AddFileVersionToPath(PathBase, path);
}

View file

@ -1,15 +1,26 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
<div class="page"> <div class="fixed inset-y-0 right-0 left-[40rem] hidden lg:block xl:left-[50rem] z-0">
<main> <svg
<article class="content px-4"> viewBox="0 0 1080 957"
@Body fill="none"
</article> aria-hidden="true"
</main> class="absolute inset-0 h-full w-full"
preserveAspectRatio="xMinYMid slice">
<path fill="#2e3440" d="M0 0h1080v957H0z"/>
</svg>
</div> </div>
<div id="blazor-error-ui"> <main>
An unhandled error has occurred. <div class="px-4 py-10 sm:py-28 sm:px-6 lg:px-8 xl:py-32 xl:px-28">
<a href="" class="reload">Reload</a> <div class="mx-auto max-w-xl lg:mx-0">
<a class="dismiss">🗙</a> <article class="px-4">
</div> <ErrorBoundary>
@Body
</ErrorBoundary>
</article>
</div>
</div>
</main>
<BlazorErrorUI/>

View file

@ -1,92 +0,0 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

View file

@ -0,0 +1,65 @@
@page "/attributions"
@using System.Text.Json
<PageTitle>Attributions</PageTitle>
<h1 class="mt-4 text-4xl md:text-5xl lg:text-6xl font-extrabold text-[2rem] leading-10 tracking-tighter">
Attributions
</h1>
<p class="mt-4 text-base text-slate-900">
The source code for this website is available on
<a href="https://www.git.nseguin.dev/nseguin/website" class="text-blue-600">Forgejo (git.nseguin.dev).</a>
</p>
<p class="mt-4 text-base text-slate-900">
It is licensed under the <a href="@LicenseUrl" class="text-blue-600">MIT License</a>.
</p>
<ul class="list-disc mt-4 text-base leading-7 text-slate-900">
@if (AttributionsList != null)
{
@foreach (var attribution in AttributionsList)
{
<li class="mt-4" key="@attribution.Name">
<h2 class="text-lg font-bold">@attribution.Name</h2>
@if (!string.IsNullOrWhiteSpace(attribution.Url?.ToString()))
{
<p class="mt-2">URL: <a href="@attribution.Url.ToString()" class="text-blue-600">@attribution.Url</a></p>
}
@if (attribution.Authors?.Any() ?? false)
{
<p class="mt-2">Author(s): @string.Join(", ", attribution.Authors)</p>
}
@if (!string.IsNullOrWhiteSpace(attribution.License))
{
<p class="mt-2">License: <a href="@attribution.LicenseUrl?.ToString()" class="text-blue-600">@attribution.License</a></p>
}
</li>
}
}
</ul>
@code {
public record Attribution(string Name, Uri Url, string? License, Uri? LicenseUrl, string[]? Authors);
private string? AttributionsJson { get; set; }
private List<Attribution>? AttributionsList { get; set; }
private string? LicenseUrl => "static/LICENSE.txt";
[Inject]
private IWebHostEnvironment HostEnvironment { get; set; }
protected override async Task OnInitializedAsync()
{
var attributionsFile = HostEnvironment.WebRootFileProvider.GetFileInfo("static/attributions.json");
using var reader = new StreamReader(attributionsFile.CreateReadStream());
AttributionsJson = await reader.ReadToEndAsync();
AttributionsList = JsonSerializer.Deserialize<List<Attribution>>(AttributionsJson);
await base.OnInitializedAsync();
}
}

View file

@ -1,42 +0,0 @@
@page "/authentication/{action}"
@using System.Security.Claims
@code{
private const string Login = "login";
private const string Logout = "logout";
public ClaimsPrincipal? User { get; private set; }
[CascadingParameter]
public required Task<AuthenticationState> AuthenticationState { get; set; }
[Parameter]
public string? Action { get; set; }
[Inject]
public required NavigationManager NavigationManager { get; set; }
private void SignOut()
{
NavigationManager.NavigateTo("MicrosoftIdentity/Account/SignOut", true);
}
private void SignIn()
{
NavigationManager.NavigateTo("MicrosoftIdentity/Account/SignIn", true);
}
protected override async Task OnInitializedAsync()
{
var authenticationState = await AuthenticationState;
User = authenticationState.User;
if (Action == Logout)
{
SignOut();
}
else if (Action == Login)
{
SignIn();
}
}
}

View file

@ -1,40 +1,49 @@
@page "/Error" @page "/Error"
@using System.Diagnostics @using System.Diagnostics
@using Microsoft.AspNetCore.Diagnostics
<PageTitle>Error</PageTitle> <PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1> <h1 class="mt-4 text-4xl md:text-5xl lg:text-6xl font-semibold text-[2rem] leading-10 tracking-tighter">Error</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
<p class="text-slate text-xl mt-4">
An error occurred while processing your request.
</p>
@if (ShowRequestId) @if (ShowRequestId)
{ {
<p> <p class="text-slate text-md mt-4 mb-4">
<strong>Request ID:</strong> <code>@RequestId</code> <strong>Request ID:</strong> <code>@RequestId</code>
</p> </p>
} }
<h3>Development Mode</h3> @if (ShowException)
<p> {
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred. <p class="text-slate mt-4 mb-4">
</p> <h2 class="text-2xl font-bold">Exception</h2>
<p> <pre class="text-md">
<strong>The Development environment shouldn't be enabled for deployed applications.</strong> <code>@(Exception?.ToString())</code>
It can result in displaying sensitive information from exceptions to end users. </pre>
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> </p>
and restarting the app. }
</p>
@code{ @code{
[CascadingParameter] [CascadingParameter]
private HttpContext? HttpContext { get; set; } private HttpContext? HttpContext { get; set; }
[Inject]
private IHostEnvironment HostEnvironment { get; set; } = default!;
private Exception? Exception { get; set; }
private string? RequestId { get; set; } private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private bool ShowException => Exception is not null && HostEnvironment.IsDevelopment();
protected override void OnInitialized() protected override void OnInitialized()
{ {
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
Exception = HttpContext?.Features.Get<IExceptionHandlerFeature>()?.Error ?? new Exception("An error occurred while processing your request.");
} }
} }

View file

@ -1,84 +1,68 @@
@page "/" @page "/"
@using NSeguin.Dev.Web.Components.Shared.HeroIcons @using NSeguin.Dev.Web.Components.Shared.HeroIcons
@layout SimpleLayout
@rendermode InteractiveServer @rendermode InteractiveServer
<PageTitle>Home</PageTitle> <PageTitle>Home</PageTitle>
<div class="fixed inset-y-0 right-0 left-[40rem] hidden lg:block xl:left-[50rem] z-0"> <p class="mt-10 flex items-center text-sm font-semibold leading-6 text-brand">
<svg <a href="https://git.nseguin.dev/nseguin/website">nseguin/website</a>
viewBox="0 0 1080 957" <small class="ml-3 rounded-full bg-brand/5 px-2 text-[0.8125rem] font-medium leading-6">
fill="none" </small>
aria-hidden="true" </p>
class="absolute inset-0 h-full w-full"
preserveAspectRatio="xMinYMid slice">
<path fill="#2e3440" d="M0 0h1080v957H0z"/>
</svg>
</div>
<div class="px-4 py-10 sm:py-28 sm:px-6 lg:px-8 xl:py-32 xl:px-28"> <h1 class="mt-4 text-4xl md:text-5xl lg:text-6xl font-extrabold text-[2rem] leading-10 tracking-tighter">
<div class="mx-auto max-w-xl lg:mx-0"> Nick Seguin
<h1 class="mt-10 flex items-center text-sm font-semibold leading-6 text-brand"> </h1>
<a href="https://git.nseguin.dev/nseguin/website">nseguin/website</a>
<small class="ml-3 rounded-full bg-brand/5 px-2 text-[0.8125rem] font-medium leading-6">
</small>
</h1>
<h1 class="mt-4 text-4xl md:text-5xl lg:text-6xl font-extrabold text-[2rem] leading-10 tracking-tighter"> <p class="mt-4 text-base leading-7 text-slate-900">
Nick Seguin I'm a software developer based in Iowa.
</h1> I'm currently working on a few projects, including this website.
Thanks for visiting!
</p>
<p class="mt-4 text-base leading-7 text-slate-900"> <div class="flex">
I'm a software developer based in Iowa. <div class="w-full sm:w-auto">
I'm currently working on a few projects, including this website. <div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
Thanks for visiting! <a
</p> href="/resume"
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-nord-1 sm:py-6">
<div class="flex"> <span class="absolute inset-0 rounded-2xl bg-slate-50 transition group-hover:bg-slate-100 sm:group-hover:scale-105">
<div class="w-full sm:w-auto"> </span>
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3"> <span class="relative flex items-center gap-4 sm:flex-col">
<a <div class="w-6 h-6">
href="/resume" <HeroIcon Kind="HeroIcon.DocumentText"/>
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-nord-1 sm:py-6"> </div>
<span class="absolute inset-0 rounded-2xl bg-slate-50 transition group-hover:bg-slate-100 sm:group-hover:scale-105"> Resumé
</span> </span>
<span class="relative flex items-center gap-4 sm:flex-col"> </a>
<div class="w-6 h-6"> <a
<HeroIcon Kind="HeroIcon.DocumentText"/> href="https://github.com/nseguin42"
</div> class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-nord-1 sm:py-6">
Resumé <span class="absolute inset-0 rounded-2xl bg-slate-50 transition group-hover:bg-slate-100 sm:group-hover:scale-105">
</span> </span>
</a> <span class="relative flex items-center gap-4 sm:flex-col">
<a <svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6">
href="https://github.com/nseguin42" <path
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-nord-1 sm:py-6"> fill-rule="evenodd"
<span class="absolute inset-0 rounded-2xl bg-slate-50 transition group-hover:bg-slate-100 sm:group-hover:scale-105"> clip-rule="evenodd"
</span> d="M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z"
<span class="relative flex items-center gap-4 sm:flex-col"> fill="#18181B"/>
<svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6"> </svg>
<path GitHub
fill-rule="evenodd" </span>
clip-rule="evenodd" </a>
d="M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z" <a
fill="#18181B"/> href="mailto:mail@nseguin.dev"
</svg> class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-nord-1 sm:py-6">
GitHub <span class="absolute inset-0 rounded-2xl bg-slate-50 transition group-hover:bg-slate-100 sm:group-hover:scale-105">
</span> </span>
</a> <span class="relative flex items-center gap-4 sm:flex-col text-black">
<a <div class="w-6 h-6">
href="mailto:mail@nseguin.dev" <HeroIcon Kind="HeroIcon.Envelope"/>
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-nord-1 sm:py-6"> </div>
<span class="absolute inset-0 rounded-2xl bg-slate-50 transition group-hover:bg-slate-100 sm:group-hover:scale-105"> Contact
</span> </span>
<span class="relative flex items-center gap-4 sm:flex-col text-black"> </a>
<div class="w-6 h-6">
<HeroIcon Kind="HeroIcon.Envelope"/>
</div>
Contact
</span>
</a>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,34 +1,17 @@
@page "/NotFound" @page "/NotFound"
@using Microsoft.AspNetCore.WebUtilities @using Microsoft.AspNetCore.WebUtilities
@layout SimpleLayout
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<PageTitle>Not Found</PageTitle> <PageTitle>Not Found</PageTitle>
<div class="fixed inset-y-0 right-0 left-[40rem] hidden lg:block xl:left-[50rem] z-0"> <h1 class="mt-4 text-4xl md:text-5xl lg:text-6xl font-semibold text-[2rem] leading-10 tracking-tighter">Not Found</h1>
<svg <p class="text-slate text-xl mt-4">
viewBox="0 0 1080 957" No page was found at <code>@Path</code>.
fill="none" </p>
aria-hidden="true"
class="absolute inset-0 h-full w-full"
preserveAspectRatio="xMinYMid slice">
<path fill="#2e3440" d="M0 0h1080v957H0z"/>
</svg>
</div>
<div class="px-4 py-10 sm:py-28 sm:px-6 lg:px-8 xl:py-32 xl:px-28"> <p class="text-blue-600 text-xl mt-4">
<div class="mx-auto max-w-xl lg:mx-0"> <a href="/" aria-label="Home" class="underline">Go to home page</a>
<h1 class="mt-4 text-4xl md:text-5xl lg:text-6xl font-semibold text-[2rem] leading-10 tracking-tighter">Not Found</h1> </p>
<p class="text-slate text-xl mt-4">
No page was found at <code>@Path</code>.<br/>
</p>
<p class="text-blue-600 text-xl mt-4">
<a href="/" aria-label="Home" class="underline">Go to home page</a>
</p>
</div>
</div>
@code { @code {
private string Path { get; set; } private string Path { get; set; }

View file

@ -1,24 +1,5 @@
<CascadingAuthenticationState> <Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] {typeof(Client._Imports).Assembly, typeof(AsyncEventHandler).Assembly}">
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] {typeof(Client._Imports).Assembly}"> <Found Context="routeData">
<Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(SimpleLayout)"/>
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(SimpleLayout)"> </Found>
<NotAuthorized> </Router>
@if (context.User.Identity?.IsAuthenticated != true)
{
<RedirectToLogin/>
}
else
{
<p role="alert">You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(SimpleLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

View file

@ -1,14 +1,21 @@
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@code { @code {
[Parameter] [Parameter]
public Func<ElementReference> GetControl { get; set; } public Func<ElementReference>? GetControl { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (GetControl is null) if (GetControl is null)
{
throw new ArgumentNullException(nameof(GetControl)); throw new ArgumentNullException(nameof(GetControl));
}
if (firstRender) if (firstRender)
{
await JSRuntime.InvokeVoidAsync("App.scrollIntoView", GetControl(), true); await JSRuntime.InvokeVoidAsync("App.scrollIntoView", GetControl(), true);
}
} }
} }

View file

@ -0,0 +1,11 @@
@inject IHostEnvironment HostEnvironment
<div id="blazor-error-ui"
class="bg-yellow-400 bottom-0 shadow-md fixed left-0 py-2 px-4 w-full z-50 hidden"
role="alertdialog"
tabindex="-1"
data-nosnippet>
An unhandled error has occurred.
<a href="#" class="reload underline">Reload</a>
<button class="dismiss" type="button">🗙</button>
</div>

View file

@ -1,20 +0,0 @@
@inject NavigationManager Navigation
<AuthorizeView>
<Authorized>
Hello, @context.User.Identity?.Name!
<button class="nav-link btn btn-link" @onclick="BeginLogout">Log out</button>
</Authorized>
<NotAuthorized>
<a href="authentication/login">Log in</a>
</NotAuthorized>
</AuthorizeView>
@code{
private void BeginLogout(MouseEventArgs args)
{
Navigation.NavigateTo("authentication/logout");
}
}

View file

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

View file

@ -38,6 +38,12 @@
<AdditionalFiles Include="pnpm-lock.yaml" CopyToOutputDirectory="Always"/> <AdditionalFiles Include="pnpm-lock.yaml" CopyToOutputDirectory="Always"/>
<Content Remove="libman.json"/> <Content Remove="libman.json"/>
<AdditionalFiles Include="libman.json" CopyToOutputDirectory="Always"/> <AdditionalFiles Include="libman.json" CopyToOutputDirectory="Always"/>
<Content Update="wwwroot\static\attributions.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\static\LICENSE.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=components_005Cmodels/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -1,6 +1,8 @@
using Azure.Identity; using Azure.Identity;
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using Microsoft.Identity.Web; using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI; using Microsoft.Identity.Web.UI;
using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect;
@ -30,6 +32,20 @@ builder.Services.AddRazorComponents()
.AddInteractiveServerComponents() .AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents(); .AddInteractiveWebAssemblyComponents();
builder.Services.AddProblemDetails(
options =>
{
options.CustomizeProblemDetails = ctx =>
{
ctx.ProblemDetails.Extensions.TryAdd("traceId", ctx.HttpContext.TraceIdentifier);
if (ctx.HttpContext.RequestServices.GetRequiredService<IHostEnvironment>()
.IsDevelopment())
{
ctx.ProblemDetails.Extensions.TryAdd("exception", ctx.Exception?.ToString());
}
};
});
if (builder.Environment.IsStaging() || builder.Environment.IsProduction()) if (builder.Environment.IsStaging() || builder.Environment.IsProduction())
{ {
builder.Configuration.AddAzureAppConfiguration( builder.Configuration.AddAzureAppConfiguration(
@ -97,14 +113,21 @@ app.UseAuthorization();
app.UseAntiforgery(); app.UseAntiforgery();
app.MapRazorComponents<App>() app.MapRazorComponents<App>()
.AddAdditionalAssemblies(typeof(Counter).Assembly) .AddAdditionalAssemblies(typeof(Counter).Assembly)
.AddAdditionalAssemblies(typeof(AsyncEventHandler).Assembly)
.AddInteractiveServerRenderMode() .AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode(); .AddInteractiveWebAssemblyRenderMode();
app.MapControllers(); app.UseExceptionHandler();
app.MapFallback( app.MapFallback(
ctx => ctx =>
{ {
ctx.Response.Redirect($"/NotFound?path={ctx.Request.Path}"); ctx.Response.StatusCode = StatusCodes.Status404NotFound;
var headers = ctx.Request.GetTypedHeaders();
if (headers.Accept.Any(h => h.MatchesMediaType("text/html")))
{
ctx.Response.Redirect($"/NotFound?path={ctx.Request.Path}");
}
return Task.CompletedTask; return Task.CompletedTask;
}); });

View file

@ -2,7 +2,6 @@
"name": "nseguin.dev.web", "name": "nseguin.dev.web",
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"bun-types": "latest"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.0.0" "typescript": "^5.0.0"
@ -12,7 +11,11 @@
"css:watch": "pnpm tailwindcss -i ./wwwroot/css/site.css -o ./wwwroot/css/styles.css -c ./tailwind.config.ts --watch" "css:watch": "pnpm tailwindcss -i ./wwwroot/css/site.css -o ./wwwroot/css/styles.css -c ./tailwind.config.ts --watch"
}, },
"dependencies": { "dependencies": {
"tailwind-nord": "^1.3.0", "tailwindcss": "^3.4.1",
"tailwindcss": "^3.4.1" "@tailwindcss/typography": "^0.5.10",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.1",
"tailwind-nord": "^1.3.0"
} }
} }

View file

@ -5,6 +5,18 @@ settings:
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
dependencies: dependencies:
'@tailwindcss/aspect-ratio':
specifier: ^0.4.2
version: 0.4.2(tailwindcss@3.4.1)
'@tailwindcss/container-queries':
specifier: ^0.1.1
version: 0.1.1(tailwindcss@3.4.1)
'@tailwindcss/forms':
specifier: ^0.5.7
version: 0.5.7(tailwindcss@3.4.1)
'@tailwindcss/typography':
specifier: ^0.5.10
version: 0.5.10(tailwindcss@3.4.1)
tailwind-nord: tailwind-nord:
specifier: ^1.3.0 specifier: ^1.3.0
version: 1.3.0 version: 1.3.0
@ -15,11 +27,6 @@ dependencies:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.3.3 version: 5.3.3
devDependencies:
bun-types:
specifier: latest
version: 1.0.15
packages: packages:
/@alloc/quick-lru@5.2.0: /@alloc/quick-lru@5.2.0:
@ -78,6 +85,43 @@ packages:
fastq: 1.15.0 fastq: 1.15.0
dev: false dev: false
/@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.1):
resolution: {integrity: sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==}
peerDependencies:
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
dependencies:
tailwindcss: 3.4.1
dev: false
/@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.1):
resolution: {integrity: sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==}
peerDependencies:
tailwindcss: '>=3.2.0'
dependencies:
tailwindcss: 3.4.1
dev: false
/@tailwindcss/forms@0.5.7(tailwindcss@3.4.1):
resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==}
peerDependencies:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
dependencies:
mini-svg-data-uri: 1.4.4
tailwindcss: 3.4.1
dev: false
/@tailwindcss/typography@0.5.10(tailwindcss@3.4.1):
resolution: {integrity: sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
dependencies:
lodash.castarray: 4.4.0
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
tailwindcss: 3.4.1
dev: false
/any-promise@1.3.0: /any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
dev: false dev: false
@ -117,10 +161,6 @@ packages:
fill-range: 7.0.1 fill-range: 7.0.1
dev: false dev: false
/bun-types@1.0.15:
resolution: {integrity: sha512-XkEvWLV1JIhcVIpf2Lu6FXnZUxRUkQVJmgY+VT7os6Tk5X1nkXx11q4Rtu6txsqpDJZfUeZXblnnD59K+6wsVA==}
dev: true
/camelcase-css@2.0.1: /camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -296,6 +336,18 @@ packages:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: false dev: false
/lodash.castarray@4.4.0:
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
dev: false
/lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
dev: false
/lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: false
/merge2@1.4.1: /merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -309,6 +361,11 @@ packages:
picomatch: 2.3.1 picomatch: 2.3.1
dev: false dev: false
/mini-svg-data-uri@1.4.4:
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
hasBin: true
dev: false
/minimatch@3.1.2: /minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies: dependencies:
@ -427,6 +484,14 @@ packages:
postcss-selector-parser: 6.0.13 postcss-selector-parser: 6.0.13
dev: false dev: false
/postcss-selector-parser@6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
engines: {node: '>=4'}
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
dev: false
/postcss-selector-parser@6.0.13: /postcss-selector-parser@6.0.13:
resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==}
engines: {node: '>=4'} engines: {node: '>=4'}

View file

@ -145,7 +145,11 @@ export default {
} }
}, },
plugins: [ plugins: [
require('tailwind-nord') require('tailwind-nord'),
require('@tailwindcss/typography'),
require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/container-queries')
] ]
} }
} satisfies Config } satisfies Config

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,15 @@
var App = App || {}; const App = App || {};
App.setFocus = function (element) { App.setFocus = function (element) {
element.focus(); element.focus();
} }
App.scrollIntoView = function (element, args) { App.scrollIntoView = function (element, args) {
if (element === undefined) {
console.error('Element is undefined');
} else if (element === null) {
console.error('Element is null');
}
element.scrollIntoView(args); element.scrollIntoView(args);
} }

View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2023 Nicholas Seguin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,2 @@
[
]