abad1630b6
TaxBaik CI/CD / build-and-deploy (push) Failing after 41s
- Add EasyMDE 2.18.0 CDN to App.razor - Add Marked.js for markdown preview rendering - Replace MudTextField with EasyMDE editor in BlogCreate.razor - Replace MudTextField with EasyMDE editor in BlogEdit.razor - Add JavaScript interop for editor initialization and content sync - Support markdown syntax highlighting and formatting toolbar Features: ✅ Bold, italic, strikethrough ✅ Headings (H1-H6) ✅ Code blocks and inline code ✅ Lists (ordered/unordered) ✅ Links and images ✅ Tables ✅ Quotes ✅ Horizontal rules ✅ Real-time preview (side-by-side mode) ✅ Full-screen editing ✅ Markdown guide The editor syncs content with Blazor form on save. Markdown syntax is preserved in database and rendered as HTML on blog pages.
168 lines
5.8 KiB
Plaintext
168 lines
5.8 KiB
Plaintext
@page "/admin/blog/create"
|
|
@attribute [Authorize]
|
|
@rendermode @(new InteractiveServerRenderMode(prerender: false))
|
|
@using TaxBaik.Application.DTOs
|
|
@using TaxBaik.Application.Services
|
|
@using TaxBaik.Domain.Interfaces
|
|
@inject BlogService BlogService
|
|
@inject ICategoryRepository CategoryRepository
|
|
@inject NavigationManager Navigation
|
|
@inject ISnackbar Snackbar
|
|
|
|
<PageTitle>새 포스트 작성</PageTitle>
|
|
|
|
<section class="admin-page-hero">
|
|
<div>
|
|
<MudText Typo="Typo.caption" Class="admin-eyebrow">Content</MudText>
|
|
<MudText Typo="Typo.h4" Class="admin-page-title">새 포스트 작성</MudText>
|
|
<MudText Typo="Typo.body2" Class="admin-page-subtitle">새로운 블로그 포스트를 작성합니다.</MudText>
|
|
</div>
|
|
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Close" @onclick="GoBack">취소</MudButton>
|
|
</section>
|
|
|
|
<MudPaper Class="pa-4 mt-4" Elevation="1">
|
|
<MudForm @ref="form">
|
|
<MudTextField @bind-Value="model.Title" Label="제목 *"
|
|
Variant="Variant.Outlined" Class="mb-4" Required="true" RequiredError="제목을 입력하세요." Counter="100" MaxLength="100" />
|
|
|
|
<MudSelect T="int?" @bind-Value="model.CategoryId" Label="카테고리"
|
|
Variant="Variant.Outlined" Class="mb-4">
|
|
@foreach (var category in categories)
|
|
{
|
|
<MudSelectItem T="int?" Value="@((int?)category.Id)">@category.Name</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
|
|
<div class="mb-4">
|
|
<label class="d-block mb-2" style="font-weight: 500;">본문 내용 (마크다운) *</label>
|
|
<textarea id="markdown-editor" @bind="model.Content" style="display: none;"></textarea>
|
|
<div id="editor-container" style="border: 1px solid #d0d0d0; border-radius: 4px; min-height: 400px;"></div>
|
|
</div>
|
|
|
|
<MudTextField @bind-Value="model.Tags" Label="태그 (쉼표로 구분)"
|
|
Variant="Variant.Outlined" Class="mb-4" />
|
|
|
|
<MudTextField @bind-Value="model.SeoTitle" Label="SEO 제목"
|
|
Variant="Variant.Outlined" Class="mb-4" />
|
|
|
|
<MudTextField @bind-Value="model.SeoDescription" Label="SEO 설명"
|
|
Variant="Variant.Outlined" Lines="3" Class="mb-4" />
|
|
|
|
<MudCheckBox @bind-Checked="model.IsPublished" Label="즉시 발행" Class="mb-4" />
|
|
|
|
<div class="d-flex gap-2">
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
|
@onclick="SavePost">저장</MudButton>
|
|
</div>
|
|
</MudForm>
|
|
</MudPaper>
|
|
|
|
@code {
|
|
private MudForm? form;
|
|
private List<Domain.Entities.Category> categories = [];
|
|
private CreatePostModel model = new();
|
|
private EasyMDE.Editor? editor;
|
|
|
|
[Inject]
|
|
private IJSRuntime JS { get; set; } = null!;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
categories = (await CategoryRepository.GetAllAsync()).ToList();
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
{
|
|
await JS.InvokeVoidAsync("window.initMarkdownEditor", "markdown-editor", model.Content ?? "");
|
|
}
|
|
}
|
|
|
|
private void GoBack()
|
|
{
|
|
Navigation.NavigateTo("/taxbaik/admin/blog");
|
|
}
|
|
|
|
private async Task SavePost()
|
|
{
|
|
if (form == null)
|
|
return;
|
|
|
|
// 에디터에서 최신 내용 가져오기
|
|
model.Content = await JS.InvokeAsync<string>("window.getMarkdownContent");
|
|
|
|
if (string.IsNullOrWhiteSpace(model.Content))
|
|
{
|
|
Snackbar.Add("본문 내용을 입력하세요.", Severity.Error);
|
|
return;
|
|
}
|
|
|
|
await form.Validate();
|
|
if (!form.IsValid)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await BlogService.CreateAsync(new CreateBlogPostDto
|
|
{
|
|
Title = model.Title,
|
|
Content = model.Content,
|
|
CategoryId = model.CategoryId,
|
|
Tags = model.Tags,
|
|
SeoTitle = model.SeoTitle,
|
|
SeoDescription = model.SeoDescription,
|
|
IsPublished = model.IsPublished
|
|
});
|
|
|
|
Snackbar.Add("포스트가 저장되었습니다.", Severity.Success);
|
|
Navigation.NavigateTo("/taxbaik/admin/blog");
|
|
}
|
|
catch (ValidationException ex)
|
|
{
|
|
Snackbar.Add(ex.Message, Severity.Error);
|
|
}
|
|
}
|
|
|
|
private class CreatePostModel
|
|
{
|
|
public string Title { get; set; } = "";
|
|
public string Content { get; set; } = "";
|
|
public int? CategoryId { get; set; }
|
|
public string? Tags { get; set; }
|
|
public string? SeoTitle { get; set; }
|
|
public string? SeoDescription { get; set; }
|
|
public bool IsPublished { get; set; }
|
|
}
|
|
}
|
|
|
|
<!-- EasyMDE 초기화 스크립트 -->
|
|
<script>
|
|
window.initMarkdownEditor = function(editorId, initialContent) {
|
|
if (!window.easyMDEInstance) {
|
|
window.easyMDEInstance = new EasyMDE({
|
|
element: document.getElementById(editorId),
|
|
spellChecker: false,
|
|
autoDownloadFontAwesome: false,
|
|
initialValue: initialContent || "",
|
|
toolbar: [
|
|
"bold", "italic", "strikethrough", "|",
|
|
"heading", "code", "|",
|
|
"unordered-list", "ordered-list", "|",
|
|
"link", "image", "table", "|",
|
|
"quote", "horizontal-rule", "|",
|
|
"preview", "side-by-side", "fullscreen", "|",
|
|
"guide"
|
|
],
|
|
previewRender: function(plainText) {
|
|
return marked.parse(plainText);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
window.getMarkdownContent = function() {
|
|
return window.easyMDEInstance ? window.easyMDEInstance.value() : "";
|
|
};
|
|
</script>
|