Generate navigation and render page embeds from PocketBase
This commit is contained in:
1 parent
6b5d9dda55
commit
3fd3796d73
26 files changed
+544
-114
No files matched your search
@@ -0,0 +1,25 @@
|
||||
<section class="embed embed-events">
|
||||
<h2>Kommende Termine</h2>
|
||||
|
||||
@if (_upcoming.Count == 0)
|
||||
{
|
||||
<p>Zurzeit sind keine Termine geplant.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ul class="event-list">
|
||||
@foreach (var ev in _upcoming)
|
||||
{
|
||||
<li>
|
||||
<time datetime="@ev.Start.ToString("s")">@EventFormat.Range(ev)</time>
|
||||
<span class="event-title">@ev.Title</span>
|
||||
@if (!string.IsNullOrWhiteSpace(ev.Location))
|
||||
{
|
||||
<span class="event-location">@ev.Location</span>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<p><a href="/events">Alle Termine →</a></p>
|
||||
}
|
||||
</section>
|
||||
@@ -0,0 +1,43 @@
|
||||
using Elternbeirat.Contracts;
|
||||
using Elternbeirat.PocketBase;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Elternbeirat.Web.Features.Embeds;
|
||||
|
||||
public partial class EventsEmbed
|
||||
{
|
||||
[LoggerMessage(Level = LogLevel.Warning, Message = "Could not load events for an embed from PocketBase.")]
|
||||
private static partial void LogLoadFailed(ILogger logger, Exception exception);
|
||||
|
||||
[Inject]
|
||||
private PocketBaseClient PocketBase { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<EventsEmbed> Logger { get; set; } = default!;
|
||||
|
||||
private IReadOnlyList<Event> _upcoming = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
IReadOnlyList<Event> events;
|
||||
try
|
||||
{
|
||||
events = await PocketBase.GetEventsAsync();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
// Source unreachable -> show no teaser instead of a 500.
|
||||
LogLoadFailed(Logger, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Teaser: the three soonest upcoming events, earliest first. An event
|
||||
// earlier today still counts as upcoming.
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
_upcoming = events
|
||||
.Where(e => DateOnly.FromDateTime(e.Start) >= today)
|
||||
.OrderBy(e => e.Start)
|
||||
.Take(3)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
.embed-events {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.event-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.event-list li {
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.event-list time {
|
||||
display: block;
|
||||
color: #777;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.event-location::before {
|
||||
content: " · ";
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.event-location {
|
||||
color: #555;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<section class="embed embed-faqs">
|
||||
<h2>Häufige Fragen</h2>
|
||||
|
||||
@if (_faqs.Count == 0)
|
||||
{
|
||||
<p>Zurzeit gibt es keine Fragen.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var faq in _faqs)
|
||||
{
|
||||
<details class="faq-item">
|
||||
<summary>@faq.Question</summary>
|
||||
<div class="faq-answer">
|
||||
@((MarkupString)Markdown.ToHtml(faq.Answer))
|
||||
</div>
|
||||
</details>
|
||||
}
|
||||
<p><a href="/faqs">Alle Fragen →</a></p>
|
||||
}
|
||||
</section>
|
||||
@@ -0,0 +1,34 @@
|
||||
using Elternbeirat.Contracts;
|
||||
using Elternbeirat.PocketBase;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Elternbeirat.Web.Features.Embeds;
|
||||
|
||||
public partial class FaqsEmbed
|
||||
{
|
||||
[LoggerMessage(Level = LogLevel.Warning, Message = "Could not load FAQs for an embed from PocketBase.")]
|
||||
private static partial void LogLoadFailed(ILogger logger, Exception exception);
|
||||
|
||||
[Inject]
|
||||
private PocketBaseClient PocketBase { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<FaqsEmbed> Logger { get; set; } = default!;
|
||||
|
||||
private IReadOnlyList<Faq> _faqs = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Teaser: the first three questions, with a link to the full hub.
|
||||
var faqs = await PocketBase.GetFaqsAsync();
|
||||
_faqs = [.. faqs.Take(3)];
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
// Source unreachable -> show no teaser instead of a 500.
|
||||
LogLoadFailed(Logger, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
.embed-faqs {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.faq-item summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
padding: 0.5rem 0 0.25rem;
|
||||
color: #333;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
@* Renders the dynamic blocks a page opts into via its "embed" field, in the
|
||||
given order. Unknown keys are ignored so a typo in PocketBase can never break
|
||||
a page. Each block loads its own data. *@
|
||||
|
||||
@foreach (var key in Embed)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case "posts":
|
||||
<PostsEmbed />
|
||||
break;
|
||||
case "events":
|
||||
<EventsEmbed />
|
||||
break;
|
||||
case "faqs":
|
||||
<FaqsEmbed />
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
/// <summary>The page's embed keys ("posts", "events", "faqs"), in order.</summary>
|
||||
[Parameter]
|
||||
public IReadOnlyList<string> Embed { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<section class="embed embed-posts">
|
||||
<h2>Aktuelle Beiträge</h2>
|
||||
|
||||
@if (_latest.Count == 0)
|
||||
{
|
||||
<p>Zurzeit gibt es keine Beiträge.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ul class="post-list">
|
||||
@foreach (var post in _latest)
|
||||
{
|
||||
<li>
|
||||
<a href="@($"/posts/{post.Slug}")">@post.Title</a>
|
||||
<time datetime="@post.Date.ToString("yyyy-MM-dd")">
|
||||
@post.Date.ToString("d. MMMM yyyy", German)
|
||||
</time>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<p><a href="/posts">Alle Beiträge →</a></p>
|
||||
}
|
||||
</section>
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Globalization;
|
||||
using Elternbeirat.Contracts;
|
||||
using Elternbeirat.PocketBase;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Elternbeirat.Web.Features.Embeds;
|
||||
|
||||
public partial class PostsEmbed
|
||||
{
|
||||
private static readonly CultureInfo German = CultureInfo.GetCultureInfo("de-DE");
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Warning, Message = "Could not load posts for an embed from PocketBase.")]
|
||||
private static partial void LogLoadFailed(ILogger logger, Exception exception);
|
||||
|
||||
[Inject]
|
||||
private PocketBaseClient PocketBase { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<PostsEmbed> Logger { get; set; } = default!;
|
||||
|
||||
private IReadOnlyList<Post> _latest = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Teaser: the three most recent posts, with a link to the full list.
|
||||
var posts = await PocketBase.GetPostsAsync();
|
||||
_latest = [.. posts.Take(3)];
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
// Source unreachable -> show no teaser instead of a 500.
|
||||
LogLoadFailed(Logger, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
.embed-posts {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.post-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.post-list li {
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.post-list a {
|
||||
font-size: 1.1rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post-list time {
|
||||
display: block;
|
||||
color: #777;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
Reference in new issue
Block a user