Changed ArticleEditor to be interactive
This commit is contained in:
parent
ce0ffbe711
commit
04169ec4e2
74
Wave/Components/AdvancedMarkdownEditor.razor
Normal file
74
Wave/Components/AdvancedMarkdownEditor.razor
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
@implements IDisposable
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@using Wave.Utilities
|
||||||
|
|
||||||
|
<section class="grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-4">
|
||||||
|
<div class="flex">
|
||||||
|
@ChildContent
|
||||||
|
</div>
|
||||||
|
<div class="bg-base-200 p-2">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Title)) {
|
||||||
|
<h2 class="text-2xl lg:text-4xl font-bold mb-6">@Title</h2>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Markdown)) {
|
||||||
|
<div class="prose prose-neutral max-w-none">
|
||||||
|
@HtmlPreview
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="skeleton h-4 w-full"></div>
|
||||||
|
<div class="skeleton h-4 w-full"></div>
|
||||||
|
<div class="skeleton h-32 w-full"></div>
|
||||||
|
<div class="skeleton h-4 w-full"></div>
|
||||||
|
<div class="skeleton h-4 w-full"></div>
|
||||||
|
<div class="skeleton h-4 w-full"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public required Func<string?> MarkdownCallback { get; set; }
|
||||||
|
[Parameter]
|
||||||
|
public string? Title { get; set; }
|
||||||
|
[Parameter]
|
||||||
|
public required RenderFragment ChildContent { get; set; }
|
||||||
|
|
||||||
|
private string? Markdown { get; set; }
|
||||||
|
private MarkupString HtmlPreview { get; set; } = new();
|
||||||
|
|
||||||
|
private CancellationTokenSource? Token { get; set; }
|
||||||
|
private Timer? Timer { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized() {
|
||||||
|
Timer = new Timer(_ => {
|
||||||
|
UpdateHtml();
|
||||||
|
}, null, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHtml() {
|
||||||
|
try {
|
||||||
|
string? markdown = MarkdownCallback.Invoke();
|
||||||
|
if (string.IsNullOrWhiteSpace(markdown)) return;
|
||||||
|
|
||||||
|
Token?.Cancel();
|
||||||
|
Token = new CancellationTokenSource();
|
||||||
|
|
||||||
|
string html = MarkdownUtilities.Parse(markdown);
|
||||||
|
Markdown = markdown;
|
||||||
|
HtmlPreview = new MarkupString(html);
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Console.Error.WriteLine(ex);
|
||||||
|
} finally {
|
||||||
|
Token = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
Timer?.Dispose();
|
||||||
|
Timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
@using Wave.Utilities
|
@using Wave.Utilities
|
||||||
|
|
||||||
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize(Policy = "ArticleEditOrReviewPermissions")]
|
@attribute [Authorize(Policy = "ArticleEditOrReviewPermissions")]
|
||||||
@inject IDbContextFactory<ApplicationDbContext> ContextFactory
|
@inject IDbContextFactory<ApplicationDbContext> ContextFactory
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
@ -20,43 +21,50 @@
|
||||||
|
|
||||||
<h1 class="text-3xl lg:text-5xl font-light mb-6">@Localizer["EditorTitle"]</h1>
|
<h1 class="text-3xl lg:text-5xl font-light mb-6">@Localizer["EditorTitle"]</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ul class="steps">
|
||||||
|
<li class="step @(Article?.Status >= ArticleStatus.Draft ? "step-primary": "")">@Localizer["Draft"]</li>
|
||||||
|
<li class="step @(Article?.Status >= ArticleStatus.InReview ? "step-primary": "")">@Localizer["InReview"]</li>
|
||||||
|
<li class="step @(Article?.Status >= ArticleStatus.Published ? "step-primary": "")">@Localizer["Published"]</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<EditForm method="post" FormName="article-editor" Model="@Model" OnValidSubmit="OnValidSubmit">
|
<EditForm method="post" FormName="article-editor" Model="@Model" OnValidSubmit="OnValidSubmit">
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
<input type="hidden" @bind-value="@Model.Id"/>
|
<input type="hidden" @bind-value="@Model.Id"/>
|
||||||
|
|
||||||
<InputLabelComponent LabelText="@Localizer["Title_Label"]" For="() => Model.Title">
|
<InputLabelComponent LabelText="@Localizer["Title_Label"]" For="() => Model.Title">
|
||||||
<InputText class="input input-bordered w-full" maxlength="256" required aria-required disabled="@CannotEdit"
|
<InputText class="input input-bordered w-full" maxlength="256" required aria-required disabled="@CannotEdit"
|
||||||
@bind-Value="@Model.Title" placeholder="@Localizer["Title_Placeholder"]" />
|
@bind-Value="@Model.Title" placeholder="@Localizer["Title_Placeholder"]" autocomplete="off" />
|
||||||
</InputLabelComponent>
|
</InputLabelComponent>
|
||||||
|
|
||||||
<InputLabelComponent LabelText="@Localizer["PublishDate_Label"]" For="() => Model.PublishDate">
|
<InputLabelComponent LabelText="@Localizer["PublishDate_Label"]" For="() => Model.PublishDate">
|
||||||
<InputDate class="input input-bordered w-full" disabled="@CannotEdit" Type="InputDateType.DateTimeLocal"
|
@if (Article?.Status is null or ArticleStatus.Draft) {
|
||||||
@bind-Value="@Model.PublishDate" placeholder="@Localizer["PublishDate_Placeholder"]" />
|
<InputDate class="input input-bordered w-full" disabled="@CannotEdit" Type="InputDateType.DateTimeLocal"
|
||||||
|
@bind-Value="@Model.PublishDate" placeholder="@Localizer["PublishDate_Placeholder"]" autocomplete="off" />
|
||||||
|
} else {
|
||||||
|
<input class="input input-bordered w-full"
|
||||||
|
type="datetime-local" readonly value="@Article?.PublishDate.ToString("yyyy-MM-dd\\THH:mm:ss")" />
|
||||||
|
}
|
||||||
</InputLabelComponent>
|
</InputLabelComponent>
|
||||||
|
|
||||||
<InputLabelComponent LabelText="@Localizer["Body_Label"]" For="() => Model.Body">
|
|
||||||
<InputTextArea class="textarea textarea-bordered w-full h-96" required aria-required disabled="@CannotEdit"
|
|
||||||
@bind-Value="@Model.Body" placeholder="@Localizer["Body_Placeholder"]" />
|
|
||||||
</InputLabelComponent>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-wide" disabled="@CannotEdit">
|
|
||||||
@Localizer["EditorSubmit"]
|
|
||||||
</button>
|
|
||||||
</EditForm>
|
|
||||||
|
|
||||||
@if (Article is not null) {
|
<AdvancedMarkdownEditor Title="@Model.Title" MarkdownCallback="() => Model.Body">
|
||||||
<section>
|
<InputLabelComponent LabelText="@Localizer["Body_Label"]" For="() => Model.Body">
|
||||||
<h2 class="text-2xl lg:text-4xl my-3">Preview</h2>
|
<textarea class="textarea textarea-bordered w-full min-h-96 h-full" required aria-required disabled="@CannotEdit"
|
||||||
<div class="card bg-base-200">
|
@bind="@Model.Body" @bind:event="oninput" placeholder="@Localizer["Body_Placeholder"]" autocomplete="off"></textarea>
|
||||||
<div class="card-body">
|
</InputLabelComponent>
|
||||||
<h3 class="card-title">@Article.Title</h3>
|
</AdvancedMarkdownEditor>
|
||||||
<div class="prose prose-neutral max-w-none">
|
|
||||||
@Content
|
<div class="flex gap-2 flex-wrap mt-3">
|
||||||
</div>
|
<button type="submit" class="btn btn-primary w-full sm:btn-wide" disabled="@CannotEdit">
|
||||||
</div>
|
@Localizer["EditorSubmit"]
|
||||||
</div>
|
</button>
|
||||||
</section>
|
@if (Article is not null) {
|
||||||
}
|
<a class="btn w-full sm:btn-wide" href="/article/@(Article.Id)">
|
||||||
|
@Localizer["ViewArticle_Label"]
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[CascadingParameter(Name = "TitlePrefix")]
|
[CascadingParameter(Name = "TitlePrefix")]
|
||||||
|
|
2
Wave/wwwroot/css/main.min.css
vendored
2
Wave/wwwroot/css/main.min.css
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue