Skip to content

Commit

Permalink
Merge pull request #350 from dorthl/mian
Browse files Browse the repository at this point in the history
fix post editor
  • Loading branch information
dorthl authored Jun 19, 2023
2 parents 0ce6212 + 7ba3047 commit 12900df
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 301 deletions.
1 change: 1 addition & 0 deletions Blogifier.sln
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{512E41FF-2
docs\04-Localization.md = docs\04-Localization.md
docs\05-Emails.md = docs\05-Emails.md
docs\06-Newsletters.md = docs\06-Newsletters.md
README-zh_CN.md = README-zh_CN.md
README.md = README.md
EndProjectSection
EndProject
Expand Down
145 changes: 145 additions & 0 deletions src/Blogifier.Admin/Components/PostEditorComponent.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
@inject IStringLocalizer<Resource> _localizer
@inject IJSRuntime _jsruntime
@inject NavigationManager _navigation
@inject IToaster _toaster

<div class="bfeditor">
<div class="bfeditor-header">
<img class="bfeditor-cover" src="@Post.Cover" alt="@_localizer["cover"]" id="postCover">
<div class="bfeditor-actions">
<div class="container d-flex">
@if (string.IsNullOrEmpty(Post.Slug))
{
<button type="button" class="btn btn-blogifier me-3 px-4" @onclick="() => PublishAsync()">@_localizer["publish"]</button>
<button type="button" class="btn btn-default me-auto" @onclick="() => SaveAsync()" @onclick:preventDefault>@_localizer["save"]</button>
}
else if (Post.State >= PostState.Release)
{
<button type="button" class="btn btn-blogifier me-3 px-4" @onclick="() => PublishAsync()">@_localizer["save"]</button>
<button type="button" class="btn btn-default me-auto" @onclick="() => UnpublishAsync()" @onclick:preventDefault>@_localizer["unpublish"]</button>
<button class="btn btn-link text-white me-1" @onclick="(() => RemoveAsync(Post.Id))">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
</svg>
<span class="ms-2 d-none d-lg-inline">@_localizer["delete"]</span>
</button>
<a href="/post/@Post.Slug" class="btn btn-link text-white" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-up-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14 2.5a.5.5 0 0 0-.5-.5h-6a.5.5 0 0 0 0 1h4.793L2.146 13.146a.5.5 0 0 0 .708.708L13 3.707V8.5a.5.5 0 0 0 1 0v-6z" />
</svg>
<span class="ms-2 d-none d-lg-inline">@_localizer["view"]</span>
</a>
}
else
{
<button type="button" class="btn btn-blogifier me-3 px-4" @onclick="() => PublishAsync()">@_localizer["publish"]</button>
<button type="button" class="btn btn-default me-auto" @onclick="() => SaveAsync()" @onclick:preventDefault>@_localizer["save"]</button>
<button class="btn btn-link text-white" @onclick="(() => RemoveAsync(Post.Id))">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
</svg>
<span class="ms-2 d-none d-lg-inline">@_localizer["delete"]</span>
</button>
}
</div>
</div>
<div class="bfeditor-header-inner">
<div class="container">
<textarea class="bfeditor-header-textarea bfeditor-header-title autosize" @bind="Post.Title" name="title" placeholder="@_localizer["post-title"]" rows="1" autofocus></textarea>
<textarea class="bfeditor-header-textarea bfeditor-header-desc autosize" @bind="Post.Description" name="description" placeholder="@_localizer["description"]..." rows="1"></textarea>
<div class="bfeditor-meta d-flex">
<div class="dropdown me-3">
<a class="bfeditor-meta-link" href="#" id="coverDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-image me-1" viewBox="0 0 16 16">
<path d="M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z" />
<path d="M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1h12z" />
</svg>
@_localizer["cover"]
</a>
<ul class="dropdown-menu" aria-labelledby="coverDropdown">
<li>
<button class="dropdown-item" onclick="return fileManager.uploadClick('@UploadType.PostCover', @Post.Id);" type="button">@_localizer["change"]</button>
<input type="hidden" class="txt-upload" @bind="Post.Cover" name="cover" id="cover" readonly />
</li>
<li>
<button class="dropdown-item" type="button" @onclick="() => ResetCoverAsync()">@_localizer["reset"]</button>
</li>
</ul>
</div>
<CategoriesComponent Categories="Post.Categories" />
</div>
</div>
</div>
</div>
<EditorComponent @ref="_editorComponent" Toolbar="fullToolbar" />
</div>

@code {

[Parameter] public PostEditorDto Post { get; set; } = default!;
[Parameter] public EventCallback<PostEditorDto> OnSaveCallback { get; set; }
[Parameter] public EventCallback<int> OnRemoveCallback { get; set; }

private EditorComponent _editorComponent = default!;

protected override async Task OnParametersSetAsync()
{
var headTitle = _localizer["edit"] + " - " + Post.Title;
await _jsruntime.InvokeVoidAsync("commonJsFunctions.setTitle", headTitle);
await _editorComponent.SetValueAsync(Post.Content);
}

protected async Task SaveCoreAsync(PostState postState)
{
var content = await _editorComponent.GetValueAsync();
if (string.IsNullOrEmpty(Post.Title) || string.IsNullOrEmpty(content))
{
_toaster.Error(_localizer["title-content-required"]);
return;
}
Post.Content = content;
Post.Cover = await _jsruntime.InvokeAsync<string>("commonJsFunctions.getSrcValue", "postCover");
Post.Cover = Post.Cover.Replace(_navigation.BaseUri, "");
if (string.IsNullOrEmpty(Post.Cover)) Post.Cover = BlogifierSharedConstant.DefaultCover;
if (string.IsNullOrEmpty(Post.Description)) Post.Description = Post.Title;
Post.State = postState;
await OnSaveCallback.InvokeAsync(Post);
}

protected async Task SaveAsync()
{
await SaveCoreAsync(PostState.Draft);
}

protected async Task PublishAsync()
{
await SaveCoreAsync(PostState.Release);
}

protected async Task UnpublishAsync()
{
await SaveCoreAsync(PostState.Draft);
}

protected async Task RemoveAsync(int id)
{
if (await _jsruntime.InvokeAsync<bool>("confirm", _localizer["confirm-delete"]))
{
await OnRemoveCallback.InvokeAsync(id);
}
}

protected async Task ResetCoverAsync()
{
Post.Cover = BlogifierSharedConstant.DefaultCover;
await SaveAsync();
}

protected async Task RemoveCoverAsync()
{
Post.Cover = null;
await SaveAsync();
}
}
146 changes: 14 additions & 132 deletions src/Blogifier.Admin/Pages/Blogs/EditorView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,92 +3,17 @@
@inject HttpClient _httpClient
@inject IStringLocalizer<Resource> _localizer
@inject NavigationManager _navigation
@inject IJSRuntime _jsruntime
@inject IToaster _toaster
@inject ToasterService _toasterService
@inject ILogger<EditorView> _logger

<PageTitleComponent Title="@_localizer["new-post"]" />

<div class="bfeditor">
<div class="bfeditor-header">
<img class="bfeditor-cover" src="@Post.Cover" alt="@_localizer["cover"]" id="postCover">
<div class="bfeditor-actions">
<div class="container d-flex">
@if (string.IsNullOrEmpty(Post.Slug))
{
<button type="button" class="btn btn-blogifier me-3 px-4" @onclick="() => PublishAsync()">@_localizer["publish"]</button>
<button type="button" class="btn btn-default me-auto" @onclick="() => SaveAsync()" @onclick:preventDefault>@_localizer["save"]</button>
}
else if (Post.State >= PostState.Release)
{
<button type="button" class="btn btn-blogifier me-3 px-4" @onclick="() => PublishAsync()">@_localizer["save"]</button>
<button type="button" class="btn btn-default me-auto" @onclick="() => UnpublishAsync()" @onclick:preventDefault>@_localizer["unpublish"]</button>
<button class="btn btn-link text-white me-1" @onclick="(() => RemoveAsync(Post.Id))">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
</svg>
<span class="ms-2 d-none d-lg-inline">@_localizer["delete"]</span>
</button>
<a href="/post/@Post.Slug" class="btn btn-link text-white" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-up-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14 2.5a.5.5 0 0 0-.5-.5h-6a.5.5 0 0 0 0 1h4.793L2.146 13.146a.5.5 0 0 0 .708.708L13 3.707V8.5a.5.5 0 0 0 1 0v-6z" />
</svg>
<span class="ms-2 d-none d-lg-inline">@_localizer["view"]</span>
</a>
}
else
{
<button type="button" class="btn btn-blogifier me-3 px-4" @onclick="() => PublishAsync()">@_localizer["publish"]</button>
<button type="button" class="btn btn-default me-auto" @onclick="() => SaveAsync()" @onclick:preventDefault>@_localizer["save"]</button>
<button class="btn btn-link text-white" @onclick="(() => RemoveAsync(Post.Id))">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
</svg>
<span class="ms-2 d-none d-lg-inline">@_localizer["delete"]</span>
</button>
}
</div>
</div>
<div class="bfeditor-header-inner">
<div class="container">
<textarea class="bfeditor-header-textarea bfeditor-header-title autosize" @bind="Post.Title" name="title" placeholder="@_localizer["post-title"]" rows="1" autofocus></textarea>
<textarea class="bfeditor-header-textarea bfeditor-header-desc autosize" @bind="Post.Description" name="description" placeholder="@_localizer["description"]..." rows="1"></textarea>
<div class="bfeditor-meta d-flex">
<div class="dropdown me-3">
<a class="bfeditor-meta-link" href="#" id="coverDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-image me-1" viewBox="0 0 16 16">
<path d="M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z" />
<path d="M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1h12z" />
</svg>
@_localizer["cover"]
</a>
<ul class="dropdown-menu" aria-labelledby="coverDropdown">
<li>
<button class="dropdown-item" onclick="return fileManager.uploadClick('@UploadType.PostCover', @Post.Id);" type="button">@_localizer["change"]</button>
<input type="hidden" class="txt-upload" @bind="Post.Cover" name="cover" id="cover" readonly />
</li>
<li>
<button class="dropdown-item" type="button" @onclick="() => ResetCoverAsync()">@_localizer["reset"]</button>
</li>
</ul>
</div>
<CategoriesComponent Categories="Post.Categories" />
</div>
</div>
</div>
</div>
<EditorComponent @ref="_editorComponent" Toolbar="fullToolbar" />
</div>
<PostEditorComponent Post="@Post" OnSaveCallback="OnSaveAsync" OnRemoveCallback="OnRemoveAsync"></PostEditorComponent>

@code {

[Parameter] public string? Slug { get; set; }

private EditorComponent _editorComponent = default!;

protected PostEditorDto Post { get; set; } = new PostEditorDto
{
Title = string.Empty,
Expand All @@ -99,34 +24,26 @@
Categories = new List<CategoryDto>(),
};

protected override async Task OnInitializedAsync()

protected override async Task OnParametersSetAsync()
{
if (!string.IsNullOrEmpty(Slug))
{
Post = (await _httpClient.GetFromJsonAsync<PostEditorDto>($"api/post/byslug/{Slug}"))!;
if (Post.Categories == null) Post.Categories = new List<CategoryDto>();
await SetJsPostAsync(Post);
}
}

protected async Task SavePostAsync(PostState postState)
protected async Task OnSaveAsync(PostEditorDto post)
{
var content = await _editorComponent.GetValueAsync();
if (string.IsNullOrEmpty(Post.Title) || string.IsNullOrEmpty(content))
{
_toaster.Error(_localizer["title-content-required"]);
return;
}
Post.Content = content;
Post.Cover = await _jsruntime.InvokeAsync<string>("commonJsFunctions.getSrcValue", "postCover");
Post.Cover = Post.Cover.Replace(_navigation.BaseUri, "");
if (string.IsNullOrEmpty(Post.Cover)) Post.Cover = BlogifierSharedConstant.DefaultCover;
if (string.IsNullOrEmpty(Post.Description)) Post.Description = Post.Title;
Post.State = postState;
if (Post.Id == 0)
{
var response = await _httpClient.PostAsJsonAsync<PostEditorDto>($"api/post/add", Post);
_toasterService.CheckResponse(response);
if (_toasterService.CheckResponse(response))
{
var slug = await response.Content.ReadAsStringAsync();
_navigation.NavigateTo($"/admin/blogs/editor/{slug}");
}
}
else
{
Expand All @@ -135,47 +52,12 @@
}
}

protected async Task SaveAsync()
protected async Task OnRemoveAsync(int id)
{
await SavePostAsync(PostState.Draft);
var result = await _httpClient.DeleteAsync($"api/post/{id}");
if (result.IsSuccessStatusCode) _toaster.Success(_localizer["completed"]);
else _toaster.Error(_localizer["generic-error"]);
_navigation.NavigateTo($"admin");
}

protected async Task PublishAsync()
{
await SavePostAsync(PostState.Release);
}

protected async Task UnpublishAsync()
{
await SavePostAsync(PostState.Draft);
}

protected async Task RemoveAsync(int id)
{
if (await _jsruntime.InvokeAsync<bool>("confirm", _localizer["confirm-delete"]))
{
var response = await _httpClient.DeleteAsync($"api/post/{id}");
_toasterService.CheckResponse(response);
_navigation.NavigateTo($"admin");
}
}

protected async Task ResetCoverAsync()
{
Post.Cover = BlogifierSharedConstant.DefaultCover;
await SaveAsync();
}

protected async Task RemoveCoverAsync()
{
Post.Cover = null;
await SaveAsync();
}

private async Task SetJsPostAsync(PostEditorDto post)
{
var headTitle = _localizer["edit"] + " - " + Post.Title;
await _jsruntime.InvokeVoidAsync("commonJsFunctions.setTitle", headTitle);
await _editorComponent.SetValueAsync(Post.Content);
}
}
Loading

0 comments on commit 12900df

Please sign in to comment.