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 Wave.Utilities | ||||
| 
 | ||||
| @rendermode InteractiveServer | ||||
| @attribute [Authorize(Policy = "ArticleEditOrReviewPermissions")] | ||||
| @inject IDbContextFactory<ApplicationDbContext> ContextFactory | ||||
| @inject NavigationManager Navigation | ||||
|  | @ -20,44 +21,51 @@ | |||
| 
 | ||||
| <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"> | ||||
|     <DataAnnotationsValidator /> | ||||
|     <input type="hidden" @bind-value="@Model.Id"/> | ||||
|      | ||||
|     <InputLabelComponent LabelText="@Localizer["Title_Label"]" For="() => Model.Title"> | ||||
|         <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 LabelText="@Localizer["PublishDate_Label"]" For="() => Model.PublishDate"> | ||||
|         <InputDate class="input input-bordered w-full" disabled="@CannotEdit" Type="InputDateType.DateTimeLocal" | ||||
|                    @bind-Value="@Model.PublishDate" placeholder="@Localizer["PublishDate_Placeholder"]" /> | ||||
|         @if (Article?.Status is null or ArticleStatus.Draft) { | ||||
|             <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 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> | ||||
|     <AdvancedMarkdownEditor Title="@Model.Title" MarkdownCallback="() => Model.Body"> | ||||
|         <InputLabelComponent LabelText="@Localizer["Body_Label"]" For="() => Model.Body"> | ||||
|             <textarea class="textarea textarea-bordered w-full min-h-96 h-full" required aria-required disabled="@CannotEdit" | ||||
|                       @bind="@Model.Body" @bind:event="oninput" placeholder="@Localizer["Body_Placeholder"]" autocomplete="off"></textarea> | ||||
|         </InputLabelComponent> | ||||
|     </AdvancedMarkdownEditor> | ||||
|      | ||||
|     <button type="submit" class="btn btn-primary btn-wide" disabled="@CannotEdit"> | ||||
| 	    @Localizer["EditorSubmit"] | ||||
|     </button> | ||||
|     <div class="flex gap-2 flex-wrap mt-3"> | ||||
|         <button type="submit" class="btn btn-primary w-full sm:btn-wide" disabled="@CannotEdit"> | ||||
|             @Localizer["EditorSubmit"] | ||||
|         </button> | ||||
|         @if (Article is not null) { | ||||
|             <a class="btn w-full sm:btn-wide" href="/article/@(Article.Id)"> | ||||
|                 @Localizer["ViewArticle_Label"] | ||||
|             </a> | ||||
|         } | ||||
|     </div> | ||||
| </EditForm> | ||||
| 
 | ||||
| @if (Article is not null) { | ||||
|     <section> | ||||
|         <h2 class="text-2xl lg:text-4xl my-3">Preview</h2> | ||||
|         <div class="card bg-base-200"> | ||||
|             <div class="card-body"> | ||||
|                 <h3 class="card-title">@Article.Title</h3> | ||||
|                 <div class="prose prose-neutral max-w-none"> | ||||
|                     @Content | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </section> | ||||
| } | ||||
| 
 | ||||
| @code { | ||||
| 	[CascadingParameter(Name = "TitlePrefix")] | ||||
| 	private string TitlePrefix { get; set; } = default!; | ||||
|  |  | |||
							
								
								
									
										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