Updated the proostekrant to work properly again.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
@page "/archief"
|
||||
@inject LatexService Latex
|
||||
@inject ArticleService Artikelen
|
||||
@inject SeasonService Seizoenen
|
||||
|
||||
<PageTitle>Archief — ProostenKrant</PageTitle>
|
||||
|
||||
<h1 class="page-heading">Archief</h1>
|
||||
<p class="page-intro">Alle gedrukte edities van de ProostenKrant en de gepubliceerde artikelen per seizoen.</p>
|
||||
|
||||
<h2 class="section-heading">Gedrukte edities</h2>
|
||||
@if (_edities.Count > 0)
|
||||
{
|
||||
<table class="krant-tabel">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Editie</th>
|
||||
<th>Datum</th>
|
||||
<th>Grootte</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var editie in _edities)
|
||||
{
|
||||
<tr>
|
||||
<td>@editie.DisplayName</td>
|
||||
<td>@Krant.DutchShortDate(editie.Date)</td>
|
||||
<td>@(editie.SizeBytes / 1024) kB</td>
|
||||
<td><a class="btn" href="/edities/@editie.FileName">Download PDF</a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="muted">Er zijn nog geen gedrukte edities beschikbaar.</p>
|
||||
}
|
||||
|
||||
<h2 class="section-heading">Gepubliceerde artikelen</h2>
|
||||
@foreach (var seizoen in _seizoenenMetArtikelen)
|
||||
{
|
||||
<section class="season-archive">
|
||||
<h3 class="season-heading seizoen-@seizoen.Season?.CssKey">
|
||||
@(seizoen.Season?.Name ?? "Zonder seizoen")
|
||||
@if (seizoen.Season is not null && seizoen.Season.StartDate <= DateTime.Now && seizoen.Season.EndDate >= DateTime.Now)
|
||||
{
|
||||
<span class="tag">huidige editie</span>
|
||||
}
|
||||
</h3>
|
||||
@if (seizoen.Articles.Count == 0)
|
||||
{
|
||||
<p class="muted">Nog geen gepubliceerde artikelen.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ul class="archive-article-list">
|
||||
@foreach (var artikel in seizoen.Articles)
|
||||
{
|
||||
<li>
|
||||
<a href="/artikelen/@artikel.Id" class="archive-article-link">@artikel.Title</a>
|
||||
<span class="byline-inline">— @artikel.AuthorName, @Krant.DutchShortDate(artikel.EventDate)</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Edition> _edities = new();
|
||||
private List<(Season? Season, List<Article> Articles)> _seizoenenMetArtikelen = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_edities = Latex.ListEditions();
|
||||
var seasons = await Seizoenen.GetAllAsync();
|
||||
var published = await Artikelen.GetAllAsync("published");
|
||||
_seizoenenMetArtikelen = seasons
|
||||
.Select(s => (s, published.Where(a => a.SeasonId == s.Id).ToList()))
|
||||
.Select(x => (Season: (Season?)x.Item1, Articles: x.Item2))
|
||||
.ToList();
|
||||
var overige = published.Where(a => a.SeasonId is null || seasons.All(s => s.Id != a.SeasonId)).ToList();
|
||||
if (overige.Count > 0)
|
||||
{
|
||||
_seizoenenMetArtikelen.Add((null, overige));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
@page "/artikelen/{Id:int}"
|
||||
@inject ArticleService Artikelen
|
||||
@inject UserService Gebruikers
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject IWebHostEnvironment Env
|
||||
|
||||
<PageTitle>@(_artikel?.Title ?? "Artikel") — ProostenKrant</PageTitle>
|
||||
|
||||
@if (_artikel is null)
|
||||
{
|
||||
<section class="card">
|
||||
<h1 class="headline">Artikel niet gevonden</h1>
|
||||
<p>Dit artikel bestaat niet (meer). <a href="/artikelen">Naar het overzicht</a>.</p>
|
||||
</section>
|
||||
return;
|
||||
}
|
||||
|
||||
<article class="article-page">
|
||||
<span class="kicker">@(string.IsNullOrEmpty(_artikel.SeasonName) ? "ProostenKrant" : _artikel.SeasonName)</span>
|
||||
<h1 class="article-headline">@_artikel.Title</h1>
|
||||
<p class="byline">
|
||||
Door @_artikel.AuthorName
|
||||
@if (_artikel.EventDate is not null)
|
||||
{
|
||||
<text>— @Krant.DutchShortDate(_artikel.EventDate)</text>
|
||||
}
|
||||
@if (_isEditor)
|
||||
{
|
||||
<span class="status status-@_artikel.Status">@(_artikel.Status == "published" ? "gepubliceerd" : "klad")</span>
|
||||
}
|
||||
</p>
|
||||
|
||||
@if (_deelnemers.Count > 0)
|
||||
{
|
||||
<div class="participant-row">
|
||||
@foreach (var deelnemer in _deelnemers)
|
||||
{
|
||||
<span class="participant">
|
||||
<img class="participant-avatar" src="@ProfileWebPath(deelnemer)" alt="@deelnemer.DisplayName" />
|
||||
<span>@deelnemer.DisplayName</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(_artikel.WebImage))
|
||||
{
|
||||
<figure class="article-figure">
|
||||
<img src="@_artikel.WebImage" alt="@_artikel.Title" />
|
||||
</figure>
|
||||
}
|
||||
|
||||
<div class="article-body">
|
||||
@foreach (var alinea in _artikel.Content.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
{
|
||||
<p>@alinea</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (_isEditor)
|
||||
{
|
||||
<div class="editor-actions">
|
||||
@if (_artikel.Status == "draft")
|
||||
{
|
||||
<EditForm FormName="publiceer" Method="post" Model="PubliceerInput" OnValidSubmit="Publiceer" class="inline-form">
|
||||
<button type="submit" class="btn btn-primary">Publiceren</button>
|
||||
</EditForm>
|
||||
}
|
||||
else
|
||||
{
|
||||
<EditForm FormName="depubliceer" Method="post" Model="DepubliceerInput" OnValidSubmit="Depubliceer" class="inline-form">
|
||||
<button type="submit" class="btn">Terug naar klad</button>
|
||||
</EditForm>
|
||||
}
|
||||
<a class="btn" href="/indienen/bewerken/@_artikel.Id">Bewerken</a>
|
||||
<EditForm FormName="verwijder" Method="post" Model="VerwijderInput" OnValidSubmit="Verwijder" class="inline-form">
|
||||
<button type="submit" class="btn btn-danger">Verwijderen</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
}
|
||||
</article>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public int Id { get; set; }
|
||||
|
||||
private Article? _artikel;
|
||||
private List<User> _deelnemers = new();
|
||||
private bool _isEditor;
|
||||
[SupplyParameterFromForm(FormName = "publiceer")]
|
||||
private ActieInput PubliceerInput { get; set; } = new();
|
||||
|
||||
[SupplyParameterFromForm(FormName = "depubliceer")]
|
||||
private ActieInput DepubliceerInput { get; set; } = new();
|
||||
|
||||
[SupplyParameterFromForm(FormName = "verwijder")]
|
||||
private ActieInput VerwijderInput { get; set; } = new();
|
||||
|
||||
private sealed class ActieInput { }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
PubliceerInput ??= new();
|
||||
DepubliceerInput ??= new();
|
||||
VerwijderInput ??= new();
|
||||
_artikel = await Artikelen.FindAsync(Id);
|
||||
var state = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
_isEditor = KrantAuth.IsEditor(state.User);
|
||||
if (_artikel is not null && (_artikel.Status == "published" || _isEditor))
|
||||
{
|
||||
_deelnemers = await Gebruikers.GetByIdsAsync(_artikel.ParticipantIds);
|
||||
}
|
||||
}
|
||||
|
||||
private string ProfileWebPath(User user) => Gebruikers.GetProfileWebPath(user);
|
||||
|
||||
private async Task Publiceer()
|
||||
{
|
||||
if (_artikel is not null)
|
||||
{
|
||||
await Artikelen.SetStatusAsync(_artikel.Id, "published");
|
||||
Nav.NavigateTo($"/artikelen/{Id}", forceLoad: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Depubliceer()
|
||||
{
|
||||
if (_artikel is not null)
|
||||
{
|
||||
await Artikelen.SetStatusAsync(_artikel.Id, "draft");
|
||||
Nav.NavigateTo($"/artikelen/{Id}", forceLoad: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Verwijder()
|
||||
{
|
||||
if (_artikel is not null)
|
||||
{
|
||||
await Artikelen.DeleteAsync(_artikel.Id);
|
||||
Nav.NavigateTo("/artikelen", forceLoad: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
@page "/artikelen/nieuw"
|
||||
@attribute [Authorize(Policy = "Editor")]
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@inject ArticleService Artikelen
|
||||
@inject SeasonService Seizoenen
|
||||
@inject UserService Gebruikers
|
||||
@inject UploadService Uploads
|
||||
|
||||
<PageTitle>Nieuw artikel — ProostenKrant</PageTitle>
|
||||
|
||||
<section class="card">
|
||||
<h1 class="headline">Nieuw artikel</h1>
|
||||
<p class="muted">Artikel voor de redactie aanmaken en eventueel direct publiceren.</p>
|
||||
|
||||
@if (_fout is not null)
|
||||
{
|
||||
<p class="alert alert-error">@_fout</p>
|
||||
}
|
||||
|
||||
<EditForm FormName="artikel-nieuw" Method="post" Model="Input" OnValidSubmit="Opslaan"
|
||||
class="krant-form" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="title">Titel</label>
|
||||
<InputText @bind-Value="Input.Title" id="title" class="form-control" placeholder="De kop boven het artikel" />
|
||||
<ValidationMessage For="() => Input.Title" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content">Tekst</label>
|
||||
<InputTextArea @bind-Value="Input.Content" id="content" rows="10" class="form-control" placeholder="Schrijf hier de inhoud van je artikel…" />
|
||||
<ValidationMessage For="() => Input.Content" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="writer">Schrijver</label>
|
||||
<InputSelect @bind-Value="Input.WriterId" id="writer" class="form-control">
|
||||
<option value="0">— Kies schrijver —</option>
|
||||
@foreach (var gebruiker in _gebruikers)
|
||||
{
|
||||
<option value="@gebruiker.Id">@gebruiker.DisplayName</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="() => Input.WriterId" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="eventdate">Datum van de gebeurtenis</label>
|
||||
<InputDate @bind-Value="Input.EventDate" id="eventdate" class="form-control" />
|
||||
<ValidationMessage For="() => Input.EventDate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="season">Seizoen</label>
|
||||
<InputSelect @bind-Value="Input.SeasonId" id="season" class="form-control">
|
||||
<option value="0">— Kies seizoen —</option>
|
||||
@foreach (var seizoen in _seizoenen)
|
||||
{
|
||||
<option value="@seizoen.Id">@seizoen.Name</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="() => Input.SeasonId" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="status">Status</label>
|
||||
<InputSelect @bind-Value="Input.Status" id="status" class="form-control">
|
||||
<option value="draft">Klad</option>
|
||||
<option value="published">Gepubliceerd</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Deelnemers</label>
|
||||
<div class="checkbox-group">
|
||||
@foreach (var gebruiker in _gebruikers)
|
||||
{
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="Participants" value="@gebruiker.Id"
|
||||
checked="@(_geselecteerd.Contains(gebruiker.Id))" />
|
||||
<span>@gebruiker.DisplayName</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
@if (_participantsFout is not null)
|
||||
{
|
||||
<span class="error-message">@_participantsFout</span>
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="image">Afbeelding (optioneel)</label>
|
||||
<input type="file" name="Afbeelding" id="image" class="form-control" accept="image/*" />
|
||||
<span class="help-text">JPG, PNG, GIF of WEBP, maximaal 25 MB.</span>
|
||||
@if (_afbeeldingFout is not null)
|
||||
{
|
||||
<span class="error-message">@_afbeeldingFout</span>
|
||||
}
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Artikel opslaan</button>
|
||||
<a class="btn" href="/artikelen">Annuleren</a>
|
||||
</div>
|
||||
</EditForm>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
[SupplyParameterFromForm(FormName = "artikel-nieuw")]
|
||||
private ArtikelInput Input { get; set; } = new();
|
||||
|
||||
private List<User> _gebruikers = new();
|
||||
private List<Season> _seizoenen = new();
|
||||
private List<int> _geselecteerd = new();
|
||||
private string? _fout;
|
||||
private string? _participantsFout;
|
||||
private string? _afbeeldingFout;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Input ??= new();
|
||||
_gebruikers = await Gebruikers.GetAllAsync();
|
||||
_seizoenen = await Seizoenen.GetAllAsync();
|
||||
var huidig = await Seizoenen.GetCurrentAsync();
|
||||
Input.SeasonId ??= huidig?.Id ?? (_seizoenen.FirstOrDefault()?.Id ?? 0);
|
||||
Input.EventDate ??= DateTime.Today;
|
||||
Input.Status ??= "published";
|
||||
}
|
||||
|
||||
private async Task Opslaan()
|
||||
{
|
||||
Input ??= new();
|
||||
var form = HttpContext.Request.Form;
|
||||
_geselecteerd = form["Participants"].SelectMany(v => (v ?? "").Split(','))
|
||||
.Select(s => int.TryParse(s, out var id) ? id : 0).Where(id => id > 0).ToList();
|
||||
if (Input.WriterId <= 0)
|
||||
{
|
||||
_fout = "Kies een schrijver.";
|
||||
return;
|
||||
}
|
||||
if (!_geselecteerd.Contains(Input.WriterId))
|
||||
{
|
||||
_geselecteerd.Insert(0, Input.WriterId);
|
||||
}
|
||||
if (_geselecteerd.Count == 0)
|
||||
{
|
||||
_participantsFout = "Selecteer minstens één deelnemer.";
|
||||
return;
|
||||
}
|
||||
|
||||
var (webPad, fout) = await Uploads.SaveArticleImageAsync(HttpContext.Request.Form.Files["Afbeelding"]);
|
||||
if (fout is not null)
|
||||
{
|
||||
_afbeeldingFout = fout;
|
||||
return;
|
||||
}
|
||||
|
||||
var artikel = new Article
|
||||
{
|
||||
Title = Input.Title,
|
||||
Content = Input.Content,
|
||||
AuthorId = Input.WriterId,
|
||||
Participants = string.Join(",", _geselecteerd),
|
||||
EventDate = Input.EventDate,
|
||||
SeasonId = Input.SeasonId,
|
||||
ArticleImage = webPad,
|
||||
Status = Input.Status == "published" ? "published" : "draft",
|
||||
};
|
||||
await Artikelen.CreateAsync(artikel);
|
||||
Nav.NavigateTo("/artikelen?melding=" + Uri.EscapeDataString("Artikel succesvol opgeslagen."), forceLoad: true);
|
||||
}
|
||||
|
||||
private sealed class ArtikelInput
|
||||
{
|
||||
[Required(ErrorMessage = "Geef het artikel een titel (minstens 5 tekens).")]
|
||||
[MinLength(5, ErrorMessage = "De titel moet minstens 5 tekens bevatten.")]
|
||||
[MaxLength(255)]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
[Required(ErrorMessage = "Schrijf de tekst van het artikel (minstens 10 tekens).")]
|
||||
[MinLength(10, ErrorMessage = "De tekst moet minstens 10 tekens bevatten.")]
|
||||
public string Content { get; set; } = "";
|
||||
|
||||
public int WriterId { get; set; }
|
||||
|
||||
public DateTime? EventDate { get; set; }
|
||||
|
||||
public int? SeasonId { get; set; }
|
||||
|
||||
public string? Status { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
@page "/artikelen"
|
||||
@attribute [Authorize(Policy = "Editor")]
|
||||
@inject ArticleService ArtikelSvc
|
||||
|
||||
<PageTitle>Artikelen — ProostenKrant</PageTitle>
|
||||
|
||||
<h1 class="page-heading">Artikelen</h1>
|
||||
|
||||
<nav class="filter-bar">
|
||||
<a href="/artikelen" class="btn @(SelectedStatus is null ? "btn-active" : "")">Alle</a>
|
||||
<a href="/artikelen?status=draft" class="btn @(_status == "draft" ? "btn-active" : "")">Klad</a>
|
||||
<a href="/artikelen?status=published" class="btn @(_status == "published" ? "btn-active" : "")">Gepubliceerd</a>
|
||||
<a href="/artikelen/nieuw" class="btn btn-primary">Nieuw artikel</a>
|
||||
</nav>
|
||||
|
||||
@if (_artikelen.Count == 0)
|
||||
{
|
||||
<p class="muted">Geen artikelen gevonden.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="krant-tabel">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titel</th>
|
||||
<th>Schrijver</th>
|
||||
<th>Gebeurtenis</th>
|
||||
<th>Seizoen</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var artikel in _artikelen)
|
||||
{
|
||||
<tr>
|
||||
<td><a href="/artikelen/@artikel.Id">@artikel.Title</a></td>
|
||||
<td>@artikel.AuthorName</td>
|
||||
<td>@Krant.DutchShortDate(artikel.EventDate)</td>
|
||||
<td>@(artikel.SeasonName ?? "—")</td>
|
||||
<td><span class="status status-@artikel.Status">@(artikel.Status == "published" ? "gepubliceerd" : "klad")</span></td>
|
||||
<td><a class="btn" href="/artikelen/@artikel.Id">Openen</a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromQuery(Name = "status")]
|
||||
public string? Status { get; set; }
|
||||
|
||||
private string? _status;
|
||||
private List<Article> _artikelen = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_status = Status;
|
||||
_artikelen = await ArtikelSvc.GetAllAsync(string.IsNullOrEmpty(Status) ? null : Status);
|
||||
}
|
||||
|
||||
private string? SelectedStatus => _status;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
@page "/indienen/bewerken/{Id:int}"
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@inject ArticleService Artikelen
|
||||
@inject UserService Gebruikers
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>Verifieer schrijver — ProostenKrant</PageTitle>
|
||||
|
||||
@if (_artikel is null)
|
||||
{
|
||||
<section class="card">
|
||||
<h1 class="headline">Artikel niet gevonden</h1>
|
||||
<p><a href="/indienen/bewerken/lijst">Terug naar de lijst</a>.</p>
|
||||
</section>
|
||||
return;
|
||||
}
|
||||
|
||||
<section class="card auth-card">
|
||||
<span class="kicker">Beveiliging</span>
|
||||
<h1 class="headline">Wie schreef dit artikel?</h1>
|
||||
<p class="muted">
|
||||
Om „@_artikel.Title“ te bewerken, bevestig je eerst dat jij de schrijver bent.
|
||||
</p>
|
||||
|
||||
@if (_fout is not null)
|
||||
{
|
||||
<p class="alert alert-error">@_fout</p>
|
||||
}
|
||||
|
||||
<EditForm FormName="verifieer-schrijver" Method="post" Model="Input" OnValidSubmit="Verifieer" class="krant-form">
|
||||
<div class="form-group">
|
||||
<label for="writer">Schrijver</label>
|
||||
<InputSelect @bind-Value="Input.WriterId" id="writer" class="form-control">
|
||||
<option value="0">— Kies schrijver —</option>
|
||||
@foreach (var gebruiker in _gebruikers)
|
||||
{
|
||||
<option value="@gebruiker.Id">@gebruiker.DisplayName</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="() => Input.WriterId" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Doorgaan naar bewerken</button>
|
||||
<a class="btn" href="/indienen/bewerken/lijst">Annuleren</a>
|
||||
</div>
|
||||
</EditForm>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public int Id { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
[SupplyParameterFromForm(FormName = "verifieer-schrijver")]
|
||||
private VerificatieInput Input { get; set; } = new();
|
||||
|
||||
private Article? _artikel;
|
||||
private List<User> _gebruikers = new();
|
||||
private string? _fout;
|
||||
|
||||
private sealed class VerificatieInput
|
||||
{
|
||||
[Required(ErrorMessage = "Kies de schrijver.")]
|
||||
public int WriterId { get; set; }
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Input ??= new();
|
||||
var state = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
if (!KrantAuth.HasValidPassphrase(state.User) && !KrantAuth.IsEditor(state.User))
|
||||
{
|
||||
Nav.NavigateTo("/indienen/bewerken", forceLoad: true);
|
||||
return;
|
||||
}
|
||||
_artikel = await Artikelen.FindAsync(Id);
|
||||
_gebruikers = await Gebruikers.GetAllAsync();
|
||||
}
|
||||
|
||||
private async Task Verifieer()
|
||||
{
|
||||
if (_artikel is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Input.WriterId != _artikel.AuthorId)
|
||||
{
|
||||
_fout = "Alleen de schrijver van het artikel kan het bewerken.";
|
||||
return;
|
||||
}
|
||||
await HttpContext.SignInAsync(KrantAuth.Scheme,
|
||||
KrantAuth.PassphrasePrincipal(editArticleId: _artikel.Id));
|
||||
Nav.NavigateTo("/indienen/formulier", forceLoad: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
@page "/indienen/bewerken/lijst"
|
||||
@inject ArticleService Artikelen
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>Kladartikelen — ProostenKrant</PageTitle>
|
||||
|
||||
<h1 class="page-heading">Kladartikelen bewerken</h1>
|
||||
<p class="page-intro">Kies het artikel dat je wilt aanpassen. Alleen de schrijver van het artikel kan het bewerken.</p>
|
||||
|
||||
@if (_klad.Count == 0)
|
||||
{
|
||||
<p class="muted">Er zijn momenteel geen kladartikelen.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="krant-tabel">
|
||||
<thead>
|
||||
<tr><th>Titel</th><th>Schrijver</th><th>Gebeurtenis</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var artikel in _klad)
|
||||
{
|
||||
<tr>
|
||||
<td>@artikel.Title</td>
|
||||
<td>@artikel.AuthorName</td>
|
||||
<td>@Krant.DutchShortDate(artikel.EventDate)</td>
|
||||
<td><a class="btn" href="/indienen/bewerken/@artikel.Id">Bewerken</a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Article> _klad = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var state = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
if (!KrantAuth.HasValidPassphrase(state.User) && !KrantAuth.IsEditor(state.User))
|
||||
{
|
||||
Nav.NavigateTo("/indienen/bewerken", forceLoad: true);
|
||||
return;
|
||||
}
|
||||
_klad = await Artikelen.GetAllAsync("draft");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
@page "/indienen/bewerken"
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
|
||||
<PageTitle>Artikel bewerken — ProostenKrant</PageTitle>
|
||||
|
||||
<section class="card auth-card">
|
||||
<span class="kicker">Beveiliging</span>
|
||||
<h1 class="headline">Artikel bewerken</h1>
|
||||
<p class="muted">Voer de paszin in om de lijst met kladartikelen te openen.</p>
|
||||
|
||||
@if (_fout is not null)
|
||||
{
|
||||
<p class="alert alert-error">@_fout</p>
|
||||
}
|
||||
|
||||
<EditForm FormName="paszin-bewerken" Method="post" Model="Input" OnValidSubmit="Verifieer" class="krant-form">
|
||||
<div class="form-group">
|
||||
<label for="passphrase">Paszin</label>
|
||||
<InputText type="password" @bind-Value="Input.Paszin" id="passphrase" class="form-control" autocomplete="off" />
|
||||
<ValidationMessage For="() => Input.Paszin" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Verder</button>
|
||||
<a class="btn" href="/">Annuleren</a>
|
||||
</div>
|
||||
</EditForm>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
[SupplyParameterFromForm(FormName = "paszin-bewerken")]
|
||||
private PaszinInput Input { get; set; } = new();
|
||||
|
||||
private string? _fout;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
Input ??= new();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private sealed class PaszinInput
|
||||
{
|
||||
[Required(ErrorMessage = "Voer de paszin in.")]
|
||||
public string Paszin { get; set; } = "";
|
||||
}
|
||||
|
||||
private async Task Verifieer()
|
||||
{
|
||||
if (Input.Paszin != Krant.UniversalPassword)
|
||||
{
|
||||
_fout = "Onjuiste paszin. Probeer het opnieuw.";
|
||||
return;
|
||||
}
|
||||
await HttpContext.SignInAsync(KrantAuth.Scheme, KrantAuth.PassphrasePrincipal());
|
||||
Nav.NavigateTo("/indienen/bewerken/lijst", forceLoad: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
@page "/dashboard"
|
||||
@attribute [Authorize(Policy = "Editor")]
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject ArticleService Artikelen
|
||||
@inject SeasonService Seizoenen
|
||||
@inject LatexService Latex
|
||||
@inject UserService Gebruikers
|
||||
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Af
|
||||
|
||||
<PageTitle>Dashboard — ProostenKrant</PageTitle>
|
||||
|
||||
<h1 class="page-heading">Dashboard redactie</h1>
|
||||
<p class="page-intro">Welkom, @_editorNaam. Overzicht van de krant en sneltoetsen voor de redactie.</p>
|
||||
|
||||
@if (_melding is not null)
|
||||
{
|
||||
<p class="alert alert-success">@_melding</p>
|
||||
}
|
||||
@if (_fout is not null)
|
||||
{
|
||||
<div class="alert alert-error">
|
||||
<p>@_fout</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-number">@_drafts</span>
|
||||
<span class="stat-label">Kladartikelen</span>
|
||||
<a href="/artikelen?status=draft" class="stat-link">Bekijken →</a>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-number">@_gepubliceerd</span>
|
||||
<span class="stat-label">Gepubliceerd</span>
|
||||
<a href="/artikelen?status=published" class="stat-link">Bekijken →</a>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-number">@_gebruikers.Count</span>
|
||||
<span class="stat-label">Familieleden</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-number">@_edities.Count</span>
|
||||
<span class="stat-label">Gedrukte edities</span>
|
||||
<a href="/archief" class="stat-link">Archief →</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-columns">
|
||||
<section class="dashboard-main">
|
||||
<h2 class="section-heading">Huidige editie</h2>
|
||||
@if (_huidigSeizoen is not null)
|
||||
{
|
||||
<div class="season-card seizoen-@_huidigSeizoen.CssKey">
|
||||
<h3>@_huidigSeizoen.Name @DateTime.Now.Year</h3>
|
||||
<p class="muted">
|
||||
Van @Krant.DutchShortDate(_huidigSeizoen.StartDate) t/m @Krant.DutchShortDate(_huidigSeizoen.EndDate).
|
||||
@(_seizoenDrafts.Count) klad, @(_seizoenPublished.Count) gepubliceerd.
|
||||
</p>
|
||||
<div class="form-actions">
|
||||
<form method="post" action="/api/genereer-pdf" class="inline-form">
|
||||
<input type="hidden" name="SeizoenId" value="@_huidigSeizoen.Id" />
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@_afToken" />
|
||||
<button type="submit" class="btn btn-primary">Druk de @_huidigSeizoen.Name-editie</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="muted">Er is geen lopend seizoen gedefinieerd.</p>
|
||||
}
|
||||
|
||||
<h2 class="section-heading">Laatste artikelen</h2>
|
||||
<table class="krant-tabel">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titel</th>
|
||||
<th>Schrijver</th>
|
||||
<th>Seizoen</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var artikel in _recent.Take(8))
|
||||
{
|
||||
<tr>
|
||||
<td><a href="/artikelen/@artikel.Id">@artikel.Title</a></td>
|
||||
<td>@artikel.AuthorName</td>
|
||||
<td>@(artikel.SeasonName ?? "—")</td>
|
||||
<td>
|
||||
<span class="status status-@artikel.Status">@(artikel.Status == "published" ? "gepubliceerd" : "klad")</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<aside class="dashboard-side">
|
||||
<h2 class="section-heading">Sneltoetsen</h2>
|
||||
<ul class="quick-links">
|
||||
<li><a class="btn" href="/artikelen/nieuw">Nieuw artikel</a></li>
|
||||
<li><a class="btn" href="/seizoenen">Seizoenen & drukwerk</a></li>
|
||||
<li><a class="btn" href="/artikelen?status=draft">Kladartikelen publiceren</a></li>
|
||||
</ul>
|
||||
|
||||
<h2 class="section-heading">Laatste edities</h2>
|
||||
<ul class="edition-list">
|
||||
@foreach (var editie in _edities.Take(5))
|
||||
{
|
||||
<li>
|
||||
<a href="/edities/@editie.FileName" class="edition-link">
|
||||
<span class="edition-name">@editie.DisplayName</span>
|
||||
<span class="edition-size">@(editie.SizeBytes / 1024) kB</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string? _editorNaam;
|
||||
private Season? _huidigSeizoen;
|
||||
private List<Article> _recent = new();
|
||||
private List<Article> _seizoenDrafts = new();
|
||||
private List<Article> _seizoenPublished = new();
|
||||
private List<User> _gebruikers = new();
|
||||
private List<Edition> _edities = new();
|
||||
private int _drafts;
|
||||
private int _gepubliceerd;
|
||||
private string? _melding;
|
||||
private string? _fout;
|
||||
private string? _afToken;
|
||||
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_afToken = Af.GetAndStoreTokens(HttpContext).RequestToken;
|
||||
var state = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
_editorNaam = KrantAuth.EditorName(state.User) ?? "redacteur";
|
||||
_huidigSeizoen = await Seizoenen.GetCurrentAsync();
|
||||
_recent = await Artikelen.GetAllAsync();
|
||||
_gebruikers = await Gebruikers.GetAllAsync();
|
||||
_edities = Latex.ListEditions();
|
||||
_drafts = _recent.Count(a => a.Status == "draft");
|
||||
_gepubliceerd = _recent.Count(a => a.Status == "published");
|
||||
if (_huidigSeizoen is not null)
|
||||
{
|
||||
_seizoenDrafts = _recent.Where(a => a.SeasonId == _huidigSeizoen.Id && a.Status == "draft").ToList();
|
||||
_seizoenPublished = await Artikelen.GetBySeasonAsync(_huidigSeizoen.Id);
|
||||
}
|
||||
LeesMelding();
|
||||
}
|
||||
|
||||
private void LeesMelding()
|
||||
{
|
||||
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(new Uri(Nav.Uri).Query);
|
||||
query.TryGetValue("melding", out var melding);
|
||||
query.TryGetValue("fout", out var fout);
|
||||
_melding = melding;
|
||||
_fout = fout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@page "/fout"
|
||||
|
||||
<PageTitle>Fout — ProostenKrant</PageTitle>
|
||||
|
||||
<section class="card">
|
||||
<span class="kicker">Persstop</span>
|
||||
<h1 class="headline">Er ging iets mis</h1>
|
||||
<p>De redactie kon deze pagina niet drukken. Probeer het opnieuw of ga terug naar de voorpagina.</p>
|
||||
<div class="form-actions">
|
||||
<a class="btn btn-primary" href="/">Naar de voorpagina</a>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,169 @@
|
||||
@page "/"
|
||||
@inject ArticleService Artikelen
|
||||
@inject SeasonService Seizoenen
|
||||
@inject LatexService Latex
|
||||
|
||||
<PageTitle>ProostenKrant — Voorpagina</PageTitle>
|
||||
|
||||
<section class="edition-banner">
|
||||
<div class="edition-banner-left">
|
||||
@if (_huidigSeizoen is not null)
|
||||
{
|
||||
<span class="kicker seizoen-@_huidigSeizoen.CssKey">Editie @_huidigSeizoen.Name @DateTime.Now.Year</span>
|
||||
<h2 class="edition-title">In wording</h2>
|
||||
<p class="edition-sub">
|
||||
@if (_drafts.Count == 0)
|
||||
{
|
||||
<text>Er liggen nog geen artikelen klaar voor de komende editie.</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>Er liggen <strong>@(_drafts.Count == 1 ? "één artikel" : $"{_drafts.Count} artikelen")</strong> klaar voor de komende editie.</text>
|
||||
}
|
||||
Sluit @Krant.DutchShortDate(_huidigSeizoen.EndDate).
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h2 class="edition-title">ProostenKrant</h2>
|
||||
<p class="edition-sub">Een krant van en voor Proosten.</p>
|
||||
}
|
||||
</div>
|
||||
<div class="edition-banner-right">
|
||||
<a class="btn btn-primary" href="/indienen">Lever zelf een artikel in</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@if (_lead is not null)
|
||||
{
|
||||
<article class="lead-article">
|
||||
<span class="kicker">Voorpagina nieuws</span>
|
||||
<h2 class="lead-headline">@_lead.Title</h2>
|
||||
<p class="byline">
|
||||
Door @_lead.AuthorName
|
||||
@if (_lead.EventDate is not null)
|
||||
{
|
||||
<text>— @Krant.DutchShortDate(_lead.EventDate)</text>
|
||||
}
|
||||
</p>
|
||||
@if (!string.IsNullOrEmpty(_lead.WebImage))
|
||||
{
|
||||
<figure class="lead-figure">
|
||||
<img src="@_lead.WebImage" alt="@_lead.Title" loading="lazy" />
|
||||
</figure>
|
||||
}
|
||||
<p class="lead-text">@Teaser(_lead, 400)</p>
|
||||
</article>
|
||||
}
|
||||
|
||||
@if (_teasers.Count > 0)
|
||||
{
|
||||
<h3 class="section-heading">Ook in deze editie</h3>
|
||||
<div class="teaser-columns">
|
||||
@foreach (var artikel in _teasers)
|
||||
{
|
||||
<article class="teaser">
|
||||
<h4 class="teaser-headline">@artikel.Title</h4>
|
||||
<p class="byline">
|
||||
Door @artikel.AuthorName
|
||||
@if (artikel.SeasonName is not null)
|
||||
{
|
||||
<text>— @artikel.SeasonName</text>
|
||||
}
|
||||
</p>
|
||||
<p class="teaser-text">@Teaser(artikel, 220)</p>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else if (_lead is null && _gepubliceerd.Count == 0)
|
||||
{
|
||||
<section class="card empty-state">
|
||||
<h2 class="headline">De eerste editie wacht op jou</h2>
|
||||
<p>Er zijn nog geen artikelen ingediend. De ProostenKrant wordt pas gedrukt als de familie schrijft.</p>
|
||||
<p><a class="btn btn-primary" href="/indienen">Schrijf het eerste artikel</a></p>
|
||||
</section>
|
||||
}
|
||||
|
||||
@if (_gepubliceerd.Count > 0)
|
||||
{
|
||||
<h3 class="section-heading">Verschenen in de @_huidigSeizoen?.Name-editie</h3>
|
||||
<div class="teaser-columns">
|
||||
@foreach (var artikel in _gepubliceerd.Take(6))
|
||||
{
|
||||
<article class="teaser">
|
||||
<h4 class="teaser-headline"><a href="/artikelen/@artikel.Id">@artikel.Title</a></h4>
|
||||
<p class="byline">
|
||||
Door @artikel.AuthorName
|
||||
@if (artikel.EventDate is not null)
|
||||
{
|
||||
<text>— @Krant.DutchShortDate(artikel.EventDate)</text>
|
||||
}
|
||||
</p>
|
||||
<p class="teaser-text">@Teaser(artikel, 200)</p>
|
||||
<p class="teaser-more"><a href="/artikelen/@artikel.Id">Lees verder →</a></p>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<aside class="editions-strip">
|
||||
<h3 class="section-heading">Verschenen edities</h3>
|
||||
@if (_edities.Count > 0)
|
||||
{
|
||||
<ul class="edition-list">
|
||||
@foreach (var editie in _edities.Take(4))
|
||||
{
|
||||
<li>
|
||||
<a href="/edities/@editie.FileName" class="edition-link">
|
||||
<span class="edition-name">@editie.DisplayName</span>
|
||||
@if (editie.Date is not null)
|
||||
{
|
||||
<span class="edition-date">@Krant.DutchShortDate(editie.Date)</span>
|
||||
}
|
||||
<span class="edition-size">@Kb(editie.SizeBytes) kB — PDF</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<p class="edition-more"><a href="/archief">Bekijk het volledige archief →</a></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="muted">Er zijn nog geen gedrukte edities. Zodra de redactie de krant genereert, verschijnen hier de PDF’s.</p>
|
||||
}
|
||||
</aside>
|
||||
|
||||
@code {
|
||||
private Season? _huidigSeizoen;
|
||||
private List<Article> _drafts = new();
|
||||
private List<Article> _teasers = new();
|
||||
private List<Article> _gepubliceerd = new();
|
||||
private Article? _lead;
|
||||
private List<Edition> _edities = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_huidigSeizoen = await Seizoenen.GetCurrentAsync();
|
||||
_drafts = await Artikelen.GetAllAsync("draft");
|
||||
_lead = _drafts.FirstOrDefault();
|
||||
_teasers = _drafts.Skip(1).ToList();
|
||||
_gepubliceerd = _huidigSeizoen is not null
|
||||
? await Artikelen.GetBySeasonAsync(_huidigSeizoen.Id)
|
||||
: await Artikelen.GetAllAsync("published");
|
||||
_edities = Latex.ListEditions();
|
||||
}
|
||||
|
||||
private static string Teaser(Article a, int length)
|
||||
{
|
||||
var text = a.Content.Replace("\r", " ").Replace("\n", " ").Trim();
|
||||
if (text.Length <= length)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
var cut = text.LastIndexOf(' ', length);
|
||||
return text[..(cut > 0 ? cut : length)] + "…";
|
||||
}
|
||||
|
||||
private static string Kb(long bytes) => Math.Max(1, bytes / 1024).ToString("N0");
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
@page "/indienen"
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
|
||||
<PageTitle>Artikel indienen — ProostenKrant</PageTitle>
|
||||
|
||||
<section class="card auth-card">
|
||||
<span class="kicker">Medewerking gevraagd</span>
|
||||
<h1 class="headline">Lever een artikel in</h1>
|
||||
<p class="muted">
|
||||
De ProostenKrant bestaat door bijdragen van de familie. Voer de paszin van de familie in
|
||||
om een artikel te schrijven (of een eerder artikel te bewerken).
|
||||
</p>
|
||||
|
||||
@if (_fout is not null)
|
||||
{
|
||||
<p class="alert alert-error">@_fout</p>
|
||||
}
|
||||
|
||||
<EditForm FormName="paszin" Method="post" Model="Input" OnValidSubmit="Verifieer" class="krant-form">
|
||||
<div class="form-group">
|
||||
<label for="passphrase">Paszin</label>
|
||||
<InputText type="password" @bind-Value="Input.Paszin" id="passphrase" class="form-control"
|
||||
placeholder="De geheime paszin van de familie" autocomplete="off" />
|
||||
<ValidationMessage For="() => Input.Paszin" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Verder naar het formulier</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
[SupplyParameterFromForm(FormName = "paszin")]
|
||||
private PaszinInput Input { get; set; } = new();
|
||||
|
||||
private string? _fout;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
Input ??= new();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private sealed class PaszinInput
|
||||
{
|
||||
[Required(ErrorMessage = "Voer de paszin in.")]
|
||||
public string Paszin { get; set; } = "";
|
||||
}
|
||||
|
||||
private async Task Verifieer()
|
||||
{
|
||||
if (Input.Paszin != Krant.UniversalPassword)
|
||||
{
|
||||
_fout = "Onjuiste paszin. Probeer het opnieuw.";
|
||||
return;
|
||||
}
|
||||
var principal = KrantAuth.PassphrasePrincipal();
|
||||
await HttpContext.SignInAsync(KrantAuth.Scheme, principal);
|
||||
Nav.NavigateTo("/indienen/formulier", forceLoad: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@page "/indienen/bevestiging"
|
||||
@inject ArticleService Artikelen
|
||||
|
||||
<PageTitle>Bevestiging — ProostenKrant</PageTitle>
|
||||
|
||||
<section class="card">
|
||||
<span class="kicker">Bedankt!</span>
|
||||
<h1 class="headline">@( _artikel is not null ? "Artikel ingediend" : "Bevestiging")</h1>
|
||||
|
||||
@if (_artikel is null)
|
||||
{
|
||||
<p>Er is geen recent ingediend artikel gevonden.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>Het artikel <strong>@_artikel.Title</strong> is veilig in de brievenbus van de redactie gevallen.</p>
|
||||
<dl class="bevestiging-lijst">
|
||||
<dt>Schrijver</dt>
|
||||
<dd>@_artikel.AuthorName</dd>
|
||||
<dt>Gebeurtenis</dt>
|
||||
<dd>@Krant.DutchShortDate(_artikel.EventDate)</dd>
|
||||
@if (_artikel.SeasonName is not null)
|
||||
{
|
||||
<dt>Editie</dt>
|
||||
<dd>@_artikel.SeasonName</dd>
|
||||
}
|
||||
</dl>
|
||||
<p class="muted">De redactie bekijkt alle inzendingen en publiceert ze in de komende editie.</p>
|
||||
<div class="form-actions">
|
||||
<a class="btn btn-primary" href="/indienen">Nog een artikel indienen</a>
|
||||
<a class="btn" href="/">Naar de voorpagina</a>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromQuery(Name = "artikel")]
|
||||
public int? ArtikelId { get; set; }
|
||||
|
||||
private Article? _artikel;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (ArtikelId is int id)
|
||||
{
|
||||
_artikel = await Artikelen.FindAsync(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
@page "/indienen/formulier"
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@inject ArticleService Artikelen
|
||||
@inject SeasonService Seizoenen
|
||||
@inject UserService Gebruikers
|
||||
@inject UploadService Uploads
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>@(_bewerkArtikel is not null ? "Artikel bewerken" : "Artikel indienen") — ProostenKrant</PageTitle>
|
||||
|
||||
<section class="card">
|
||||
<span class="kicker">@(_bewerkArtikel is not null ? "Bewerken" : "Medewerking gevraagd")</span>
|
||||
<h1 class="headline">@( _bewerkArtikel is not null ? "Artikel bewerken" : "Nieuw artikel indienen")</h1>
|
||||
|
||||
@if (_fout is not null)
|
||||
{
|
||||
<p class="alert alert-error">@_fout</p>
|
||||
}
|
||||
|
||||
<EditForm FormName="indienen" Method="post" Model="Input" OnValidSubmit="Opslaan"
|
||||
class="krant-form" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="title">Titel</label>
|
||||
<InputText @bind-Value="Input.Title" id="title" class="form-control" placeholder="De kop boven je artikel" />
|
||||
<ValidationMessage For="() => Input.Title" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content">Tekst</label>
|
||||
<InputTextArea @bind-Value="Input.Content" id="content" rows="10" class="form-control"
|
||||
placeholder="Schrijf hier de inhoud van je artikel…" />
|
||||
<ValidationMessage For="() => Input.Content" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="writer">Schrijver</label>
|
||||
<InputSelect @bind-Value="Input.WriterId" id="writer" class="form-control">
|
||||
<option value="0">— Kies schrijver —</option>
|
||||
@foreach (var gebruiker in _gebruikers)
|
||||
{
|
||||
<option value="@gebruiker.Id">@gebruiker.DisplayName</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="() => Input.WriterId" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="eventdate">Datum van de gebeurtenis</label>
|
||||
<InputDate @bind-Value="Input.EventDate" id="eventdate" class="form-control" />
|
||||
<ValidationMessage For="() => Input.EventDate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Deelnemers</label>
|
||||
<p class="help-text">Selecteer alle personages die bij dit verhaal betrokken zijn.</p>
|
||||
<div class="checkbox-group">
|
||||
@foreach (var gebruiker in _gebruikers)
|
||||
{
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="Participants" value="@gebruiker.Id"
|
||||
checked="@(_geselecteerd.Contains(gebruiker.Id))" />
|
||||
<span>@gebruiker.DisplayName</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
@if (_participantsFout is not null)
|
||||
{
|
||||
<span class="error-message">@_participantsFout</span>
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="image">Afbeelding</label>
|
||||
<input type="file" name="Afbeelding" id="image" class="form-control" accept="image/*" />
|
||||
<span class="help-text">
|
||||
JPG, PNG, GIF of WEBP, maximaal 25 MB.
|
||||
@if (_bewerkArtikel is not null && !string.IsNullOrEmpty(_bewerkArtikel.WebImage))
|
||||
{
|
||||
<text>Laat leeg om de huidige afbeelding te houden.</text>
|
||||
}
|
||||
</span>
|
||||
@if (_afbeeldingFout is not null)
|
||||
{
|
||||
<span class="error-message">@_afbeeldingFout</span>
|
||||
}
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
@(_bewerkArtikel is not null ? "Wijzigingen opslaan" : "Artikel indienen")
|
||||
</button>
|
||||
<a class="btn" href="/">Annuleren</a>
|
||||
</div>
|
||||
</EditForm>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
[SupplyParameterFromForm(FormName = "indienen")]
|
||||
private IndienInput Input { get; set; } = new();
|
||||
|
||||
private List<User> _gebruikers = new();
|
||||
private List<int> _geselecteerd = new();
|
||||
private Article? _bewerkArtikel;
|
||||
private string? _fout;
|
||||
private string? _participantsFout;
|
||||
private string? _afbeeldingFout;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Input ??= new();
|
||||
var state = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
var isEditor = KrantAuth.IsEditor(state.User);
|
||||
var heeftPaszin = KrantAuth.HasValidPassphrase(state.User);
|
||||
var bewerkId = KrantAuth.EditArticleId(state.User);
|
||||
|
||||
if (!isEditor && !heeftPaszin)
|
||||
{
|
||||
Nav.NavigateTo("/indienen", forceLoad: true);
|
||||
return;
|
||||
}
|
||||
|
||||
_gebruikers = await Gebruikers.GetAllAsync();
|
||||
Input.EventDate ??= DateTime.Today;
|
||||
|
||||
if (bewerkId is int id)
|
||||
{
|
||||
_bewerkArtikel = await Artikelen.FindAsync(id);
|
||||
if (_bewerkArtikel is null)
|
||||
{
|
||||
Nav.NavigateTo("/indienen/bewerken/lijst", forceLoad: true);
|
||||
return;
|
||||
}
|
||||
// Alleen bij een GET-request het formulier vullen; bij een POST
|
||||
// zijn de waarden al door de formulierbinding gezet.
|
||||
if (string.Equals(HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Input.Title = _bewerkArtikel.Title;
|
||||
Input.Content = _bewerkArtikel.Content;
|
||||
Input.WriterId = _bewerkArtikel.AuthorId;
|
||||
Input.EventDate = _bewerkArtikel.EventDate;
|
||||
_geselecteerd = _bewerkArtikel.ParticipantIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Opslaan()
|
||||
{
|
||||
Input ??= new();
|
||||
var form = HttpContext.Request.Form;
|
||||
_geselecteerd = form["Participants"].SelectMany(v => (v ?? "").Split(','))
|
||||
.Select(s => int.TryParse(s, out var id) ? id : 0).Where(id => id > 0).ToList();
|
||||
|
||||
if (Input.WriterId <= 0)
|
||||
{
|
||||
_fout = "Kies een schrijver.";
|
||||
return;
|
||||
}
|
||||
if (!_geselecteerd.Contains(Input.WriterId))
|
||||
{
|
||||
_geselecteerd.Insert(0, Input.WriterId);
|
||||
}
|
||||
if (_geselecteerd.Count == 0)
|
||||
{
|
||||
_participantsFout = "Selecteer minstens één deelnemer.";
|
||||
return;
|
||||
}
|
||||
|
||||
string? webPad = _bewerkArtikel?.ArticleImage;
|
||||
var bestand = HttpContext.Request.Form.Files["Afbeelding"];
|
||||
if (bestand is { Length: > 0 } || _bewerkArtikel is null)
|
||||
{
|
||||
var (nieuwPad, fout) = await Uploads.SaveArticleImageAsync(bestand);
|
||||
if (fout is not null)
|
||||
{
|
||||
_afbeeldingFout = fout;
|
||||
return;
|
||||
}
|
||||
webPad = nieuwPad ?? webPad;
|
||||
}
|
||||
|
||||
var season = await Seizoenen.GetByDateAsync(Input.EventDate ?? DateTime.Today);
|
||||
|
||||
if (_bewerkArtikel is not null)
|
||||
{
|
||||
_bewerkArtikel.Title = Input.Title;
|
||||
_bewerkArtikel.Content = Input.Content;
|
||||
_bewerkArtikel.AuthorId = Input.WriterId;
|
||||
_bewerkArtikel.Participants = string.Join(",", _geselecteerd);
|
||||
_bewerkArtikel.EventDate = Input.EventDate;
|
||||
_bewerkArtikel.SeasonId = season?.Id ?? _bewerkArtikel.SeasonId;
|
||||
_bewerkArtikel.ArticleImage = webPad;
|
||||
await Artikelen.UpdateAsync(_bewerkArtikel);
|
||||
|
||||
var artikelId = _bewerkArtikel.Id;
|
||||
await HttpContext.SignOutAsync(KrantAuth.Scheme);
|
||||
Nav.NavigateTo($"/indienen/bevestiging?artikel={artikelId}", forceLoad: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var artikel = new Article
|
||||
{
|
||||
Title = Input.Title,
|
||||
Content = Input.Content,
|
||||
AuthorId = Input.WriterId,
|
||||
Participants = string.Join(",", _geselecteerd),
|
||||
EventDate = Input.EventDate,
|
||||
SeasonId = season?.Id,
|
||||
ArticleImage = webPad,
|
||||
Status = "draft",
|
||||
};
|
||||
var nieuwId = await Artikelen.CreateAsync(artikel);
|
||||
await HttpContext.SignOutAsync(KrantAuth.Scheme);
|
||||
Nav.NavigateTo($"/indienen/bevestiging?artikel={nieuwId}", forceLoad: true);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class IndienInput
|
||||
{
|
||||
[Required(ErrorMessage = "Geef het artikel een titel (minstens 5 tekens).")]
|
||||
[MinLength(5, ErrorMessage = "De titel moet minstens 5 tekens bevatten.")]
|
||||
[MaxLength(255)]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
[Required(ErrorMessage = "Schrijf de tekst van het artikel (minstens 10 tekens).")]
|
||||
[MinLength(10, ErrorMessage = "De tekst moet minstens 10 tekens bevatten.")]
|
||||
public string Content { get; set; } = "";
|
||||
|
||||
public int WriterId { get; set; }
|
||||
|
||||
public DateTime? EventDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
@page "/login"
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@inject UserService Gebruikers
|
||||
|
||||
<PageTitle>Inloggen — ProostenKrant</PageTitle>
|
||||
|
||||
<section class="card auth-card">
|
||||
<h1 class="headline">Inloggen redactie</h1>
|
||||
<p class="muted">Kies je naam en voer het familiewachtwoord in.</p>
|
||||
|
||||
@if (_fout is not null)
|
||||
{
|
||||
<p class="alert alert-error">@_fout</p>
|
||||
}
|
||||
|
||||
<EditForm FormName="login" Method="post" Model="Input" OnValidSubmit="LoginUser" class="krant-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Familielid</label>
|
||||
<InputSelect @bind-Value="Input.Username" id="username" class="form-control">
|
||||
<option value="">— Kies je naam —</option>
|
||||
@foreach (var gebruiker in _gebruikers)
|
||||
{
|
||||
<option value="@gebruiker.Username">@gebruiker.DisplayName</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="() => Input.Username" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Wachtwoord</label>
|
||||
<InputText type="password" @bind-Value="Input.Password" id="password" class="form-control" autocomplete="current-password" />
|
||||
<ValidationMessage For="() => Input.Password" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Inloggen</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
[SupplyParameterFromForm(FormName = "login")]
|
||||
private LoginInput Input { get; set; } = new();
|
||||
|
||||
private List<User> _gebruikers = new();
|
||||
private string? _fout;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Input ??= new();
|
||||
_gebruikers = await Gebruikers.GetAllAsync();
|
||||
}
|
||||
|
||||
private async Task LoginUser()
|
||||
{
|
||||
var gebruiker = _gebruikers.FirstOrDefault(g =>
|
||||
string.Equals(g.Username, Input.Username, StringComparison.OrdinalIgnoreCase));
|
||||
if (gebruiker is null || Input.Password != Krant.UniversalPassword)
|
||||
{
|
||||
_fout = "Ongeldige gebruikersnaam of wachtwoord.";
|
||||
return;
|
||||
}
|
||||
|
||||
var principal = KrantAuth.EditorPrincipal(gebruiker);
|
||||
await HttpContext.SignInAsync(KrantAuth.Scheme, principal);
|
||||
Nav.NavigateTo("/dashboard", forceLoad: true);
|
||||
}
|
||||
|
||||
private sealed class LoginInput
|
||||
{
|
||||
[Required(ErrorMessage = "Kies je naam.")]
|
||||
public string Username { get; set; } = "";
|
||||
|
||||
[Required(ErrorMessage = "Voer het wachtwoord in.")]
|
||||
public string Password { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
@page "/seizoenen/{Id:int}"
|
||||
@attribute [Authorize(Policy = "Editor")]
|
||||
@inject SeasonService Seizoenen
|
||||
@inject ArticleService Artikelen
|
||||
@inject LatexService Latex
|
||||
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Af
|
||||
|
||||
<PageTitle>@(_seizoen?.Name ?? "Seizoen") — ProostenKrant</PageTitle>
|
||||
|
||||
@if (_seizoen is null)
|
||||
{
|
||||
<section class="card">
|
||||
<h1 class="headline">Seizoen niet gevonden</h1>
|
||||
<p><a href="/seizoenen">Naar het seizoensoverzicht</a>.</p>
|
||||
</section>
|
||||
return;
|
||||
}
|
||||
|
||||
<section class="card">
|
||||
<span class="kicker seizoen-@_seizoen.CssKey">Editie @_seizoen.Name @_seizoen.StartDate.Year</span>
|
||||
<h1 class="headline">@_seizoen.Name @(_seizoen.StartDate.Year)</h1>
|
||||
<p class="muted">
|
||||
@Krant.DutchShortDate(_seizoen.StartDate) — @Krant.DutchShortDate(_seizoen.EndDate).
|
||||
@_artikelen.Count gepubliceerde artikelen in deze editie.
|
||||
</p>
|
||||
<div class="form-actions">
|
||||
<form method="post" action="/api/genereer-pdf" class="inline-form">
|
||||
<input type="hidden" name="SeizoenId" value="@Id" />
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@_afToken" />
|
||||
<button type="submit" class="btn btn-primary">Druk de @_seizoen.Name-editie</button>
|
||||
</form>
|
||||
<a class="btn" href="/seizoenen">Terug</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@if (_fout is not null)
|
||||
{
|
||||
<p class="alert alert-error">@_fout</p>
|
||||
}
|
||||
@if (_melding is not null)
|
||||
{
|
||||
<p class="alert alert-success">
|
||||
@_melding
|
||||
@if (_nieuwPdf is not null)
|
||||
{
|
||||
<text> <a href="/edities/@_nieuwPdf">Download →</a></text>
|
||||
}
|
||||
</p>
|
||||
}
|
||||
|
||||
<h2 class="section-heading">Artikelen in deze editie</h2>
|
||||
@if (_artikelen.Count == 0)
|
||||
{
|
||||
<p class="muted">Er zijn nog geen gepubliceerde artikelen in deze editie.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="krant-tabel">
|
||||
<thead>
|
||||
<tr><th>Titel</th><th>Schrijver</th><th>Gebeurtenis</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var artikel in _artikelen)
|
||||
{
|
||||
<tr>
|
||||
<td><a href="/artikelen/@artikel.Id">@artikel.Title</a></td>
|
||||
<td>@artikel.AuthorName</td>
|
||||
<td>@Krant.DutchShortDate(artikel.EventDate)</td>
|
||||
<td><a class="btn" href="/artikelen/@artikel.Id">Openen</a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public int Id { get; set; }
|
||||
|
||||
[SupplyParameterFromForm(FormName = "genereer-detail")]
|
||||
private ActieInput Input { get; set; } = new();
|
||||
|
||||
private sealed class ActieInput { }
|
||||
private Season? _seizoen;
|
||||
private List<Article> _artikelen = new();
|
||||
private string? _melding;
|
||||
private string? _fout;
|
||||
private string? _nieuwPdf;
|
||||
private string? _afToken;
|
||||
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_afToken = Af.GetAndStoreTokens(HttpContext).RequestToken;
|
||||
_seizoen = await Seizoenen.FindAsync(Id);
|
||||
if (_seizoen is not null)
|
||||
{
|
||||
_artikelen = await Artikelen.GetBySeasonAsync(Id);
|
||||
}
|
||||
LeesMelding();
|
||||
}
|
||||
|
||||
private void LeesMelding()
|
||||
{
|
||||
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(new Uri(Nav.Uri).Query);
|
||||
query.TryGetValue("melding", out var melding);
|
||||
query.TryGetValue("fout", out var fout);
|
||||
_melding = melding;
|
||||
_fout = fout;
|
||||
query.TryGetValue("pdf", out var pdf);
|
||||
_nieuwPdf = pdf;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
@page "/seizoenen"
|
||||
@attribute [Authorize(Policy = "Editor")]
|
||||
@inject SeasonService SeizoenSvc
|
||||
@inject ArticleService Artikelen
|
||||
@inject LatexService Latex
|
||||
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Af
|
||||
|
||||
<PageTitle>Seizoenen — ProostenKrant</PageTitle>
|
||||
|
||||
<h1 class="page-heading">Seizoenen & drukwerk</h1>
|
||||
<p class="page-intro">Per seizoen wordt een editie van de krant gedrukt. Publiceer artikelen en druk daarna de editie.</p>
|
||||
|
||||
@if (_melding is not null)
|
||||
{
|
||||
<p class="alert alert-success">
|
||||
@_melding
|
||||
@if (_nieuwPdf is not null)
|
||||
{
|
||||
<text> <a href="/edities/@_nieuwPdf">Download de krant →</a></text>
|
||||
}
|
||||
</p>
|
||||
}
|
||||
@if (_fout is not null)
|
||||
{
|
||||
<div class="alert alert-error">
|
||||
<p>@_fout</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="season-grid">
|
||||
@foreach (var seizoen in _seizoenInfo)
|
||||
{
|
||||
<section class="season-card seizoen-@seizoen.Season.CssKey">
|
||||
<div class="season-card-head">
|
||||
<h2>@seizoen.Season.Name @seizoen.Season.StartDate.Year</h2>
|
||||
@if (seizoen.IsHuidig)
|
||||
{
|
||||
<span class="tag">lopende editie</span>
|
||||
}
|
||||
</div>
|
||||
<p class="muted">
|
||||
@Krant.DutchShortDate(seizoen.Season.StartDate) — @Krant.DutchShortDate(seizoen.Season.EndDate)
|
||||
</p>
|
||||
<ul class="season-stats">
|
||||
<li><strong>@seizoen.Gepubliceerd</strong> gepubliceerd</li>
|
||||
<li><strong>@seizoen.Klad</strong> klad</li>
|
||||
</ul>
|
||||
<div class="form-actions">
|
||||
<form method="post" action="/api/genereer-pdf" class="inline-form">
|
||||
<input type="hidden" name="SeizoenId" value="@seizoen.Season.Id" />
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@_afToken" />
|
||||
<button type="submit" class="btn btn-primary">Druk deze editie</button>
|
||||
</form>
|
||||
<a class="btn" href="/seizoenen/@seizoen.Season.Id">Artikelen bekijken</a>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
|
||||
<h2 class="section-heading">Gedrukte edities</h2>
|
||||
@if (_edities.Count > 0)
|
||||
{
|
||||
<table class="krant-tabel">
|
||||
<thead>
|
||||
<tr><th>Editie</th><th>Datum</th><th>Grootte</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var editie in _edities)
|
||||
{
|
||||
<tr>
|
||||
<td>@editie.DisplayName</td>
|
||||
<td>@Krant.DutchShortDate(editie.Date)</td>
|
||||
<td>@(editie.SizeBytes / 1024) kB</td>
|
||||
<td><a class="btn" href="/edities/@editie.FileName">Download PDF</a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="muted">Er werd nog geen editie gedrukt.</p>
|
||||
}
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm(FormName = "genereer-seizoen")]
|
||||
private GenereerInput Input { get; set; } = new();
|
||||
|
||||
private sealed class GenereerInput
|
||||
{
|
||||
public int SeasonId { get; set; }
|
||||
}
|
||||
private List<(Season Season, int Gepubliceerd, int Klad, bool IsHuidig)> _seizoenInfo = new();
|
||||
private List<Edition> _edities = new();
|
||||
private string? _melding;
|
||||
private string? _fout;
|
||||
private string? _nieuwPdf;
|
||||
|
||||
private string? _afToken;
|
||||
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_afToken = Af.GetAndStoreTokens(HttpContext).RequestToken;
|
||||
var seasons = await SeizoenSvc.GetAllAsync();
|
||||
var alle = await Artikelen.GetAllAsync();
|
||||
_seizoenInfo = seasons.Select(s =>
|
||||
{
|
||||
var vanS = alle.Where(a => a.SeasonId == s.Id).ToList();
|
||||
return (s, vanS.Count(a => a.Status == "published"), vanS.Count(a => a.Status == "draft"),
|
||||
s.StartDate <= DateTime.Now && s.EndDate >= DateTime.Now);
|
||||
}).ToList();
|
||||
_edities = Latex.ListEditions();
|
||||
LeesMelding();
|
||||
}
|
||||
|
||||
private void LeesMelding()
|
||||
{
|
||||
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(new Uri(Nav.Uri).Query);
|
||||
query.TryGetValue("melding", out var melding);
|
||||
query.TryGetValue("fout", out var fout);
|
||||
_melding = melding;
|
||||
_fout = fout;
|
||||
query.TryGetValue("pdf", out var pdf);
|
||||
_nieuwPdf = pdf;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@page "/uitloggen"
|
||||
|
||||
<PageTitle>Uitloggen — ProostenKrant</PageTitle>
|
||||
|
||||
<section class="card auth-card">
|
||||
<h1 class="headline">Uitloggen</h1>
|
||||
<p class="muted">Bevestig dat je wilt uitloggen bij de redactie.</p>
|
||||
<EditForm FormName="uitloggen" Method="post" Model="Input" OnValidSubmit="UitloggenUitvoeren" class="krant-form">
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Uitloggen</button>
|
||||
<a class="btn" href="/">Annuleren</a>
|
||||
</div>
|
||||
</EditForm>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
[SupplyParameterFromForm(FormName = "uitloggen")]
|
||||
private ActieInput Input { get; set; } = new();
|
||||
|
||||
private sealed class ActieInput { }
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
Input ??= new();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task UitloggenUitvoeren()
|
||||
{
|
||||
await HttpContext.SignOutAsync(KrantAuth.Scheme);
|
||||
Nav.NavigateTo("/", forceLoad: true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user