|
1 |
| -@using LinkDotNet.Blog.Domain |
| 1 | +@using LinkDotNet.Blog.Domain |
2 | 2 | @using LinkDotNet.Blog.Infrastructure
|
3 | 3 | @using LinkDotNet.Blog.Infrastructure.Persistence
|
4 | 4 | @using LinkDotNet.Blog.Web.Features.Services
|
| 5 | +@using LinkDotNet.Blog.Web.Features.ShowBlogPost.Components |
5 | 6 | @using NCronJob
|
6 | 7 | @inject IJSRuntime JSRuntime
|
7 | 8 | @inject ICacheInvalidator CacheInvalidator
|
8 | 9 | @inject IInstantJobRegistry InstantJobRegistry
|
9 | 10 | @inject IRepository<ShortCode> ShortCodeRepository
|
10 | 11 |
|
11 | 12 | <div class="container">
|
12 |
| - <h3 class="fw-bold">@Title</h3> |
13 |
| - <EditForm Model="@model" OnValidSubmit="OnValidBlogPostCreatedAsync"> |
14 |
| - <DataAnnotationsValidator /> |
15 |
| - <div class="form-floating mb-3"> |
16 |
| - <input type="text" class="form-control" id="title" placeholder="Title" |
17 |
| - @oninput="args => model.Title = args.Value!.ToString()!" value="@model.Title"/> |
18 |
| - <label for="title">Title</label> |
19 |
| - <ValidationMessage For="() => model.Title"></ValidationMessage> |
20 |
| - </div> |
21 |
| - <div class="form-floating mb-3"> |
22 |
| - <MarkdownTextArea Id="short" Class="form-control" Rows="4" Placeholder="Short Description" |
23 |
| - @bind-Value="@model.ShortDescription" |
24 |
| - PreviewFunction="ReplaceShortCodes" |
25 |
| - ></MarkdownTextArea> |
26 |
| - <ValidationMessage For="() => model.ShortDescription"></ValidationMessage> |
27 |
| - </div> |
28 |
| - <div class="form-floating mb-3 relative"> |
29 |
| - <MarkdownTextArea Id="content" Class="form-control" Rows="20" Placeholder="Content" |
30 |
| - PreviewFunction="ReplaceShortCodes" |
31 |
| - @bind-Value="@model.Content"></MarkdownTextArea> |
32 |
| - <ValidationMessage For="() => model.Content"></ValidationMessage> |
33 |
| - |
34 |
| - <div class="btn-group position-absolute bottom-0 end-0 m-5 extra-buttons"> |
35 |
| - <button class="btn btn-primary btn-outlined btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> |
36 |
| - More |
37 |
| - </button> |
38 |
| - <ul class="dropdown-menu"> |
39 |
| - @if (shortCodes.Count > 0) |
40 |
| - { |
41 |
| - <li> |
42 |
| - <button type="button" @onclick="OpenShortCodeDialog" class="dropdown-item"> |
43 |
| - <span>Get shortcode</span> |
44 |
| - </button> |
45 |
| - </li> |
46 |
| - } |
47 |
| - <li><button type="button" class="dropdown-item" @onclick="FeatureDialog.Open">Experimental Features</button></li> |
48 |
| - </ul> |
49 |
| - </div> |
50 |
| - </div> |
51 |
| - <div class="form-floating mb-3"> |
52 |
| - <InputText type="url" class="form-control" id="preview" placeholder="Preview-Url" @bind-Value="model.PreviewImageUrl"/> |
53 |
| - <label for="preview">Preview-Url</label> |
54 |
| - <small for="preview" class="form-text text-body-secondary">The primary image which will be used.</small> |
55 |
| - <ValidationMessage For="() => model.PreviewImageUrl"></ValidationMessage> |
56 |
| - </div> |
57 |
| - <div class="form-floating mb-3"> |
58 |
| - <InputText type="url" class="form-control" id="fallback-preview" placeholder="Fallback Preview-Url" @bind-Value="model.PreviewImageUrlFallback"/> |
59 |
| - <label for="fallback-preview">Fallback Preview-Url</label> |
60 |
| - <small for="fallback-preview" class="form-text text-body-secondary">Optional: Used as a fallback if the preview image can't be used by the browser. |
61 |
| - <br>For example using a jpg or png as fallback for avif which is not supported in Safari or Edge.</small> |
62 |
| - <ValidationMessage For="() => model.PreviewImageUrlFallback"></ValidationMessage> |
63 |
| - </div> |
64 |
| - <div class="form-floating mb-3"> |
65 |
| - <InputDate Type="InputDateType.DateTimeLocal" class="form-control" id="scheduled" |
66 |
| - placeholder="Scheduled Publish Date" @bind-Value="model.ScheduledPublishDate" |
67 |
| - @bind-Value:after="@(() => model.IsPublished &= !IsScheduled)"/> |
68 |
| - <label for="scheduled">Scheduled Publish Date</label> |
69 |
| - <small for="scheduled" class="form-text text-body-secondary">If set the blog post will be published at the given date. |
70 |
| - A blog post with a schedule date can't be set to published.</small> |
71 |
| - <ValidationMessage For="() => model.ScheduledPublishDate"></ValidationMessage> |
72 |
| - </div> |
73 |
| - <div class="form-check form-switch mb-3"> |
74 |
| - <InputCheckbox class="form-check-input" id="published" @bind-Value="model.IsPublished"/> |
75 |
| - <label class="form-check-label" for="published">Publish</label><br/> |
76 |
| - <small for="published" class="form-text text-body-secondary">If this blog post is only draft or it will be scheduled, uncheck the box.</small> |
77 |
| - <ValidationMessage For="() => model.IsPublished"></ValidationMessage> |
78 |
| - </div> |
79 |
| - <div class="form-floating mb-3"> |
80 |
| - <InputText type="text" class="form-control" id="tags" placeholder="Tags" @bind-Value="model.Tags"/> |
81 |
| - <label for="tags">Tags (Comma separated)</label> |
82 |
| - </div> |
83 |
| - @if (BlogPost is not null && !IsScheduled) |
84 |
| - { |
85 |
| - <div class="form-check form-switch mb-3"> |
86 |
| - <InputCheckbox class="form-check-input" id="updatedate" @bind-Value="model.ShouldUpdateDate" /> |
87 |
| - <label class="form-check-label" for="updatedate">Update Publish Date</label><br/> |
88 |
| - <small for="updatedate" class="form-text text-body-secondary">If set the publish date is set to now, |
89 |
| - otherwise its original date.</small> |
90 |
| - </div> |
91 |
| - } |
92 |
| - <div class="mb-3"> |
93 |
| - <button class="btn btn-primary position-relative" type="submit" disabled="@(!canSubmit)">Submit</button> |
94 |
| - <div class="alert alert-info text-muted form-text mt-3 mb-3"> |
95 |
| - The first page of the blog is cached. Therefore, the blog post is not immediately visible. |
96 |
| - Head over to <a href="/settings">settings</a> to invalidate the cache or enable the checkmark. |
97 |
| - <br> |
98 |
| - The option should be enabled if you want to publish the blog post immediately and it should be visible on the first page. |
99 |
| - </div> |
100 |
| - <div class="form-check form-switch mb-3"> |
101 |
| - <InputCheckbox class="form-check-input" id="invalidate-cache" @bind-Value="model.ShouldInvalidateCache"/> |
102 |
| - <label class="form-check-label" for="invalidate-cache">Make it visible immediately</label><br/> |
103 |
| - </div> |
104 |
| - </div> |
105 |
| - </EditForm> |
| 13 | + <h3 class="fw-bold">@Title</h3> |
| 14 | + <EditForm Model="@model" OnValidSubmit="OnValidBlogPostCreatedAsync"> |
| 15 | + <DataAnnotationsValidator /> |
| 16 | + <div class="form-floating mb-3"> |
| 17 | + <input type="text" class="form-control" id="title" placeholder="Title" |
| 18 | + @oninput="args => model.Title = args.Value!.ToString()!" value="@model.Title" /> |
| 19 | + <label for="title">Title</label> |
| 20 | + <ValidationMessage For="() => model.Title"></ValidationMessage> |
| 21 | + </div> |
| 22 | + <div class="form-floating mb-3"> |
| 23 | + <MarkdownTextArea Id="short" Class="form-control" Rows="4" Placeholder="Short Description" |
| 24 | + @bind-Value="@model.ShortDescription" |
| 25 | + PreviewFunction="ReplaceShortCodes"></MarkdownTextArea> |
| 26 | + <ValidationMessage For="() => model.ShortDescription"></ValidationMessage> |
| 27 | + </div> |
| 28 | + <div class="form-floating mb-3 relative"> |
| 29 | + <MarkdownTextArea Id="content" Class="form-control" Rows="20" Placeholder="Content" |
| 30 | + PreviewFunction="ReplaceShortCodes" |
| 31 | + @bind-Value="@model.Content"></MarkdownTextArea> |
| 32 | + <ValidationMessage For="() => model.Content"></ValidationMessage> |
| 33 | + |
| 34 | + <div class="btn-group position-absolute bottom-0 end-0 m-5 extra-buttons"> |
| 35 | + <button class="btn btn-primary btn-outlined btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> |
| 36 | + More |
| 37 | + </button> |
| 38 | + <ul class="dropdown-menu"> |
| 39 | + @if (shortCodes.Count > 0) |
| 40 | + { |
| 41 | + <li> |
| 42 | + <button type="button" @onclick="OpenShortCodeDialog" class="dropdown-item"> |
| 43 | + <span>Get shortcode</span> |
| 44 | + </button> |
| 45 | + </li> |
| 46 | + } |
| 47 | + <li><button type="button" class="dropdown-item" @onclick="FeatureDialog.Open">Experimental Features</button></li> |
| 48 | + <li><button id="convert" type="button" class="dropdown-item" @onclick="ConvertContent">@ConvertLabel <i class="lab"></i></button></li> |
| 49 | + </ul> |
| 50 | + </div> |
| 51 | + </div> |
| 52 | + <div class="form-floating mb-3"> |
| 53 | + <InputText type="url" class="form-control" id="preview" placeholder="Preview-Url" @bind-Value="model.PreviewImageUrl" /> |
| 54 | + <label for="preview">Preview-Url</label> |
| 55 | + <small for="preview" class="form-text text-body-secondary">The primary image which will be used.</small> |
| 56 | + <ValidationMessage For="() => model.PreviewImageUrl"></ValidationMessage> |
| 57 | + </div> |
| 58 | + <div class="form-floating mb-3"> |
| 59 | + <InputText type="url" class="form-control" id="fallback-preview" placeholder="Fallback Preview-Url" @bind-Value="model.PreviewImageUrlFallback" /> |
| 60 | + <label for="fallback-preview">Fallback Preview-Url</label> |
| 61 | + <small for="fallback-preview" class="form-text text-body-secondary"> |
| 62 | + Optional: Used as a fallback if the preview image can't be used by the browser. |
| 63 | + <br>For example using a jpg or png as fallback for avif which is not supported in Safari or Edge. |
| 64 | + </small> |
| 65 | + <ValidationMessage For="() => model.PreviewImageUrlFallback"></ValidationMessage> |
| 66 | + </div> |
| 67 | + <div class="form-floating mb-3"> |
| 68 | + <InputDate Type="InputDateType.DateTimeLocal" class="form-control" id="scheduled" |
| 69 | + placeholder="Scheduled Publish Date" @bind-Value="model.ScheduledPublishDate" |
| 70 | + @bind-Value:after="@(() => model.IsPublished &= !IsScheduled)" /> |
| 71 | + <label for="scheduled">Scheduled Publish Date</label> |
| 72 | + <small for="scheduled" class="form-text text-body-secondary"> |
| 73 | + If set the blog post will be published at the given date. |
| 74 | + A blog post with a schedule date can't be set to published. |
| 75 | + </small> |
| 76 | + <ValidationMessage For="() => model.ScheduledPublishDate"></ValidationMessage> |
| 77 | + </div> |
| 78 | + <div class="form-check form-switch mb-3"> |
| 79 | + <InputCheckbox class="form-check-input" id="published" @bind-Value="model.IsPublished" /> |
| 80 | + <label class="form-check-label" for="published">Publish</label><br /> |
| 81 | + <small for="published" class="form-text text-body-secondary">If this blog post is only draft or it will be scheduled, uncheck the box.</small> |
| 82 | + <ValidationMessage For="() => model.IsPublished"></ValidationMessage> |
| 83 | + </div> |
| 84 | + <div class="form-floating mb-3"> |
| 85 | + <InputText type="text" class="form-control" id="tags" placeholder="Tags" @bind-Value="model.Tags" /> |
| 86 | + <label for="tags">Tags (Comma separated)</label> |
| 87 | + </div> |
| 88 | + @if (BlogPost is not null && !IsScheduled) |
| 89 | + { |
| 90 | + <div class="form-check form-switch mb-3"> |
| 91 | + <InputCheckbox class="form-check-input" id="updatedate" @bind-Value="model.ShouldUpdateDate" /> |
| 92 | + <label class="form-check-label" for="updatedate">Update Publish Date</label><br /> |
| 93 | + <small for="updatedate" class="form-text text-body-secondary"> |
| 94 | + If set the publish date is set to now, |
| 95 | + otherwise its original date. |
| 96 | + </small> |
| 97 | + </div> |
| 98 | + } |
| 99 | + <div class="mb-3"> |
| 100 | + <button class="btn btn-primary position-relative" type="submit" disabled="@(!canSubmit)">Submit</button> |
| 101 | + <div class="alert alert-info text-muted form-text mt-3 mb-3"> |
| 102 | + The first page of the blog is cached. Therefore, the blog post is not immediately visible. |
| 103 | + Head over to <a href="/settings">settings</a> to invalidate the cache or enable the checkmark. |
| 104 | + <br> |
| 105 | + The option should be enabled if you want to publish the blog post immediately and it should be visible on the first page. |
| 106 | + </div> |
| 107 | + <div class="form-check form-switch mb-3"> |
| 108 | + <InputCheckbox class="form-check-input" id="invalidate-cache" @bind-Value="model.ShouldInvalidateCache" /> |
| 109 | + <label class="form-check-label" for="invalidate-cache">Make it visible immediately</label><br /> |
| 110 | + </div> |
| 111 | + </div> |
| 112 | + </EditForm> |
106 | 113 | </div>
|
107 | 114 |
|
108 | 115 | <FeatureInfoDialog @ref="FeatureDialog"></FeatureInfoDialog>
|
|
127 | 134 |
|
128 | 135 | private CreateNewModel model = new();
|
129 | 136 |
|
130 |
| - private bool canSubmit = true; |
131 |
| - private IPagedList<ShortCode> shortCodes = PagedList<ShortCode>.Empty; |
| 137 | + private string? originalContent = null; |
| 138 | + private bool IsContentConverted => !string.IsNullOrWhiteSpace(originalContent); |
| 139 | + private string ConvertLabel => !IsContentConverted ? "Convert to markdown" : "Restore"; |
132 | 140 |
|
133 |
| - private bool IsScheduled => model.ScheduledPublishDate.HasValue; |
| 141 | + private bool canSubmit = true; |
| 142 | + private IPagedList<ShortCode> shortCodes = PagedList<ShortCode>.Empty; |
134 | 143 |
|
135 |
| - protected override async Task OnInitializedAsync() |
136 |
| - { |
137 |
| - shortCodes = await ShortCodeRepository.GetAllAsync(); |
138 |
| - } |
| 144 | + private bool IsScheduled => model.ScheduledPublishDate.HasValue; |
| 145 | + |
| 146 | + protected override async Task OnInitializedAsync() |
| 147 | + { |
| 148 | + shortCodes = await ShortCodeRepository.GetAllAsync(); |
| 149 | + } |
139 | 150 |
|
140 |
| - protected override void OnParametersSet() |
| 151 | + protected override void OnParametersSet() |
141 | 152 | {
|
142 | 153 | if (BlogPost is null)
|
143 | 154 | {
|
|
149 | 160 |
|
150 | 161 | private async Task OnValidBlogPostCreatedAsync()
|
151 | 162 | {
|
152 |
| - canSubmit = false; |
| 163 | + canSubmit = false; |
153 | 164 | await OnBlogPostCreated.InvokeAsync(model.ToBlogPost());
|
154 | 165 | if (model.ShouldInvalidateCache)
|
155 |
| - { |
156 |
| - CacheInvalidator.Cancel(); |
157 |
| - } |
| 166 | + { |
| 167 | + CacheInvalidator.Cancel(); |
| 168 | + } |
158 | 169 |
|
159 | 170 | InstantJobRegistry.RunInstantJob<SimilarBlogPostJob>(parameter: true);
|
160 | 171 | ClearModel();
|
161 |
| - canSubmit = true; |
| 172 | + canSubmit = true; |
162 | 173 | }
|
163 | 174 |
|
164 | 175 | private void ClearModel()
|
|
186 | 197 |
|
187 | 198 | private Task<string> ReplaceShortCodes(string markdown)
|
188 | 199 | {
|
189 |
| - foreach (var code in shortCodes) |
190 |
| - { |
191 |
| - markdown = markdown.Replace($"[[{code.Name}]]", code.MarkdownContent); |
192 |
| - } |
| 200 | + foreach (var code in shortCodes) |
| 201 | + { |
| 202 | + markdown = markdown.Replace($"[[{code.Name}]]", code.MarkdownContent); |
| 203 | + } |
193 | 204 |
|
194 |
| - return Task.FromResult(MarkdownConverter.ToMarkupString(markdown).Value); |
| 205 | + return Task.FromResult(MarkdownConverter.ToMarkupString(markdown).Value); |
195 | 206 | }
|
196 | 207 |
|
197 | 208 | private void OpenShortCodeDialog()
|
198 | 209 | {
|
199 |
| - ShortCodeDialog.Open(); |
200 |
| - StateHasChanged(); |
| 210 | + ShortCodeDialog.Open(); |
| 211 | + StateHasChanged(); |
| 212 | + } |
| 213 | + |
| 214 | + /// <summary> |
| 215 | + /// Convert content from HTML to Markdown and viceversa |
| 216 | + /// </summary> |
| 217 | + private void ConvertContent() |
| 218 | + { |
| 219 | + if (IsContentConverted) |
| 220 | + { |
| 221 | + model.Content = originalContent!; |
| 222 | + originalContent = null; |
| 223 | + } |
| 224 | + else |
| 225 | + { |
| 226 | + originalContent = model.Content; |
| 227 | + var converter = new ReverseMarkdown.Converter(); |
| 228 | + model.Content = converter.Convert(model.Content); |
| 229 | + } |
201 | 230 | }
|
202 | 231 | }
|
0 commit comments