Wave/Wave/Components/Pages/Home.razor

187 lines
8.7 KiB
Plaintext

@page "/"
@using Microsoft.EntityFrameworkCore
@using Microsoft.Extensions.Options
@using Wave.Data
@using Wave.Utilities
@using System.Globalization
@inject IOptions<Customization> Customizations
@inject IOptions<Features> Features
@inject NavigationManager Navigation
@inject IDbContextFactory<ApplicationDbContext> ContextFactory;
@inject IStringLocalizer<Home> Localizer
@inject IMessageDisplay Message
<HeadContent>
<meta property="og:type" content="website">
<meta property="og:title" content="@Customizations.Value.AppName">
<meta property="og:site_name" content="@Customizations.Value.AppName">
<meta property="og:url" content="@Navigation.BaseUri">
@if (!string.IsNullOrWhiteSpace(Customizations.Value.LogoLink)) {
<meta property="og:image" content="@Customizations.Value.LogoLink">
} else {
<meta property="og:image" content="@Navigation.ToAbsoluteUri("/img/logo.png")">
}
@if (!string.IsNullOrWhiteSpace(Customizations.Value.AppDescription)) {
<meta name="description" content="@Customizations.Value.AppDescription">
<meta property="og:description" content="@Customizations.Value.AppDescription">
}
@if (Features.Value.Rss) {
<link rel="alternate" type="application/rss+xml" title="RSS Feed on @Customizations.Value.AppName" href="/rss/rss.xml">
<link rel="alternate" type="application/atom+xml" title="Atom RSS Feed on @Customizations.Value.AppName" href="/rss/atom.xml">
}
@if (Page >= TotalPages) {
<meta name="robots" content="noindex">
} else if (Page > 0) {
<link rel="canonical" href="@(new UriBuilder(Navigation.BaseUri) { Scheme = "https", Query = "?page=" + Page, Port = -1 }.Uri.AbsoluteUri)" />
} else {
<link rel="canonical" href="@(new UriBuilder(Navigation.BaseUri) { Scheme = "https", Port = -1 }.Uri.AbsoluteUri)" />
}
</HeadContent>
<PageTitle>@(Customizations.Value.AppName ?? "Wave")</PageTitle>
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 xl:grid-rows-4 gap-4">
<div class="sm:col-span-2 flex flex-col">
<h1 class="text-3xl lg:text-5xl font-light mb-3 text-primary">@Customizations.Value.AppName</h1>
<p class="flex-1 my-3">@Customizations.Value.AppDescription</p>
<section class="flex gap-2 justify-between sm:justify-start flex-wrap mb-3">
@if (Features.Value.Rss) {
<a class="btn btn-sm bg-orange-500 text-slate-50" title="RSS Feed on @Customizations.Value.AppName" href="/rss/rss.xml">
RSS
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M3.75 4.5a.75.75 0 0 1 .75-.75h.75c8.284 0 15 6.716 15 15v.75a.75.75 0 0 1-.75.75h-.75a.75.75 0 0 1-.75-.75v-.75C18 11.708 12.292 6 5.25 6H4.5a.75.75 0 0 1-.75-.75V4.5Zm0 6.75a.75.75 0 0 1 .75-.75h.75a8.25 8.25 0 0 1 8.25 8.25v.75a.75.75 0 0 1-.75.75H12a.75.75 0 0 1-.75-.75v-.75a6 6 0 0 0-6-6H4.5a.75.75 0 0 1-.75-.75v-.75Zm0 7.5a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z" clip-rule="evenodd" />
</svg>
</a>
<a class="btn btn-sm bg-orange-500 text-slate-50" title="Atom RSS Feed on @Customizations.Value.AppName" href="/rss/atom.xml">
Atom RSS
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M3.75 4.5a.75.75 0 0 1 .75-.75h.75c8.284 0 15 6.716 15 15v.75a.75.75 0 0 1-.75.75h-.75a.75.75 0 0 1-.75-.75v-.75C18 11.708 12.292 6 5.25 6H4.5a.75.75 0 0 1-.75-.75V4.5Zm0 6.75a.75.75 0 0 1 .75-.75h.75a8.25 8.25 0 0 1 8.25 8.25v.75a.75.75 0 0 1-.75.75H12a.75.75 0 0 1-.75-.75v-.75a6 6 0 0 0-6-6H4.5a.75.75 0 0 1-.75-.75v-.75Zm0 7.5a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z" clip-rule="evenodd" />
</svg>
</a>
}
@if (Features.Value.EmailSubscriptions) {
<a class="btn btn-sm btn-primary" title="E-Mail Newsletter" href="/Email/Subscribe">
E-Mail
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path d="M1.5 8.67v8.58a3 3 0 0 0 3 3h15a3 3 0 0 0 3-3V8.67l-8.928 5.493a3 3 0 0 1-3.144 0L1.5 8.67Z" />
<path d="M22.5 6.908V6.75a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3v.158l9.714 5.978a1.5 1.5 0 0 0 1.572 0L22.5 6.908Z" />
</svg>
</a>
}
</section>
</div>
@if (Featured is {} featured) {
<div class="sm:col-span-2 row-span-2 aspect-square xl:order-first" style="padding: 0 4px 4px 0">
<ArticleLink Article="featured">
<article class="relative h-full bg-secondary text-secondary-content border-2 border-current shadow-[4px_4px_0_0_currentColor]">
<div class="absolute inset-8 overflow-hidden fade-away">
<div class="mb-3">
<h2 class="text-2xl lg:text-4xl line-clamp-2 hyphens-auto">
@featured.Title
</h2>
<p class="flex flex-wrap gap-2">
@foreach (var category in featured.Categories.OrderBy(c => c.Color)) {
<span class="badge badge-@CategoryUtilities.GetCssClassPostfixForColor(category.Color)">
@category.Name
</span>
}
</p>
</div>
<div class="hidden sm:block w-1/3 float-left mr-2">
<ProfilePictureComponent ProfileId="@featured.Author.Id" LoadAsync="false" Size="400" />
</div>
<p class="font-bold">@featured.Author.FullName</p>
<small class="text-sm">@featured.PublishDate.ToString("g")</small>
<p class="hyphens-auto text-justify" lang="@CultureInfo.CurrentCulture">
@((MarkupString) featured.BodyPlain[..Math.Min(1500, featured.BodyPlain.Length)]) ...
</p>
</div>
</article>
</ArticleLink>
</div>
} else {
<div class="sm:col-span-4">
<h2 class="text-2xl lg:text-4xl mb-6">@Localizer["NoArticles_Title"]</h2>
<p>@Localizer["NoArticles_Message"]</p>
</div>
}
<PageComponent LoadCallback="LoadArticles" Page="@Page">
<div class="aspect-square">
<ArticleTile Article="context" />
</div>
</PageComponent>
</div>
<div class="grid place-content-center my-3">
<div class="join">
@if (Page < 1) {
<button class="join-item btn" disabled title="@Localizer["Paging_Previous"]">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M7.72 12.53a.75.75 0 0 1 0-1.06l7.5-7.5a.75.75 0 1 1 1.06 1.06L9.31 12l6.97 6.97a.75.75 0 1 1-1.06 1.06l-7.5-7.5Z" clip-rule="evenodd" />
</svg>
</button>
} else {
<a class="join-item btn" target="_top" href="@(Page < 2 ? "/" : $"/?page={Page - 1}")" title="@Localizer["Paging_Previous"]">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M7.72 12.53a.75.75 0 0 1 0-1.06l7.5-7.5a.75.75 0 1 1 1.06 1.06L9.31 12l6.97 6.97a.75.75 0 1 1-1.06 1.06l-7.5-7.5Z" clip-rule="evenodd"/>
</svg>
</a>
}
<button class="join-item btn md:btn-wide no-animation">@Localizer["Paging_Page"] @(Page + 1)</button>
@if (Page >= TotalPages - 1) {
<button class="join-item btn" disabled title="@Localizer["Paging_Next"]">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M16.28 11.47a.75.75 0 0 1 0 1.06l-7.5 7.5a.75.75 0 0 1-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 0 1 1.06-1.06l7.5 7.5Z" clip-rule="evenodd"/>
</svg>
</button>
} else {
<a class="join-item btn" target="_top" href="/?page=@(Page + 1)" title="@Localizer["Paging_Next"]">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M16.28 11.47a.75.75 0 0 1 0 1.06l-7.5 7.5a.75.75 0 0 1-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 0 1 1.06-1.06l7.5 7.5Z" clip-rule="evenodd"/>
</svg>
</a>
}
</div>
</div>
@code {
[CascadingParameter(Name = "TitlePrefix")]
private string TitlePrefix { get; set; } = default!;
[SupplyParameterFromQuery]
public int Page { get; set; } = 0;
private int TotalPages { get; set; } = 1;
private Article? Featured { get; set; }
protected override async Task OnInitializedAsync() {
try {
await using var context = await ContextFactory.CreateDbContextAsync();
var query = context.Set<Article>()
.Include(a => a.Author).Include(a => a.Categories)
.OrderByDescending(a => a.PublishDate).ThenBy(a => a.Id);
Featured = await query.FirstOrDefaultAsync();
TotalPages = (int) Math.Max(Math.Ceiling((await query.CountAsync() - 1) / 10.0), 1);
} catch {
Message.ShowError(Localizer["Articles_Load_Error"]);
}
}
private async ValueTask<IEnumerable<Article>> LoadArticles(int page, int count) {
try {
await using var context = await ContextFactory.CreateDbContextAsync();
return await context.Set<Article>()
.Include(a => a.Author).Include(a => a.Categories)
.OrderByDescending(a => a.PublishDate).ThenBy(a => a.Id)
.Skip(page + 1).Take(count).ToListAsync();
} catch {
Message.ShowError(Localizer["Articles_Load_Error"]);
return [];
}
}
}