Edit.cshtml 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. @page "{id:int}"
  2. @model Admin.Pages.Forum.Posts.List.EditModel
  3. @{
  4. ViewData["Title"] = "게시글 수정";
  5. }
  6. <partial name="_Editor" />
  7. <div class="container">
  8. <h3 class="mb-3">@ViewData["Title"]</h3>
  9. <hr />
  10. <partial name="_StatusMessage" />
  11. <form method="post" accept-charset="utf-8" autocomplete="off" enctype="multipart/form-data">
  12. <input type="hidden" asp-for="Input.ID" />
  13. <input type="hidden" asp-for="Input.BoardID" />
  14. <input type="hidden" asp-for="Input.ReturnUrl" />
  15. <!-- 말머리 -->
  16. <div class="row mb-2" id="prefixRow">
  17. <label class="col-sm-2 col-form-label">말머리</label>
  18. <div class="col-sm-10">
  19. <select asp-for="Input.BoardPrefixID" class="form-select w-auto" id="boardPrefixSelect">
  20. <option value="">- 선택 -</option>
  21. </select>
  22. </div>
  23. </div>
  24. <!-- 제목 -->
  25. <div class="row mb-2">
  26. <label class="col-sm-2 col-form-label"><span class="text-danger">*</span> 제목</label>
  27. <div class="col-sm-10">
  28. <input type="text" asp-for="Input.Subject" class="form-control" maxlength="255" required />
  29. </div>
  30. </div>
  31. <!-- 내용 (CKEditor) -->
  32. <div class="row mb-2">
  33. <label class="col-sm-2 col-form-label">내용</label>
  34. <div class="col-sm-10">
  35. <textarea asp-for="Input.Content" class="ck-editor" id="Input_Content" rows="12"></textarea>
  36. </div>
  37. </div>
  38. <!-- 대표 이미지 -->
  39. <div class="row mb-2">
  40. <label class="col-sm-2 col-form-label">대표 이미지</label>
  41. <div class="col-sm-10">
  42. <div id="thumbPrev" @(string.IsNullOrWhiteSpace(Model.CurrentThumbnail) ? "hidden" : "")>
  43. <img class="img-fluid img-thumbnail" alt="대표 이미지 미리보기" src="@(Model.CurrentThumbnail ?? "")" style="max-width: 300px;" />
  44. </div>
  45. <input type="file" id="Input_ThumbnailFile" asp-for="Input.ThumbnailFile" class="form-control" accept=".jpg,.jpeg,.png,.gif,.webp,.bmp" />
  46. <span class="form-text text-muted">
  47. 지원 확장자: <code>.jpg</code>, <code>.jpeg</code>, <code>.png</code>, <code>.gif</code>, <code>.webp</code>, <code>.bmp</code>
  48. </span>
  49. </div>
  50. </div>
  51. <!-- 기존 첨부파일 -->
  52. <div class="row mb-2">
  53. <label class="col-sm-2 col-form-label">기존 첨부파일</label>
  54. <div class="col-sm-10">
  55. @if (Model.ExistingFiles.Count > 0)
  56. {
  57. <ul class="list-unstyled mb-1">
  58. @foreach (var file in Model.ExistingFiles)
  59. {
  60. <li>
  61. <a href="@file.Url" target="_blank">@file.FileName</a>
  62. @if (file.Size.HasValue)
  63. {
  64. <small class="text-muted">(@(file.Size.Value > 1048576 ? $"{file.Size.Value / 1048576.0:F1}MB" : $"{file.Size.Value / 1024.0:F1}KB"))</small>
  65. }
  66. <small class="text-muted">다운로드: @file.Downloads</small>
  67. </li>
  68. }
  69. </ul>
  70. }
  71. else
  72. {
  73. <span class="text-muted">없음</span>
  74. }
  75. </div>
  76. </div>
  77. <!-- 새 첨부파일 -->
  78. <div class="row mb-2">
  79. <label class="col-sm-2 col-form-label">첨부파일 추가</label>
  80. <div class="col-sm-10">
  81. <input type="file" asp-for="Input.Files" class="form-control" multiple
  82. accept=".jpg,.jpeg,.png,.gif,.webp,.bmp,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar,.7z,.hwp,.hwpx,.csv" />
  83. <span class="form-text text-muted">여러 파일을 선택할 수 있습니다.</span>
  84. </div>
  85. </div>
  86. <!-- 태그 -->
  87. <div class="row mb-2">
  88. <label class="col-sm-2 col-form-label">태그</label>
  89. <div class="col-sm-10">
  90. <input type="text" asp-for="Input.TagsInput" class="form-control"
  91. placeholder="태그를 쉼표(,)로 구분하여 입력하세요" />
  92. <span class="form-text text-muted">예: 비트코인, 이더리움, 블록체인</span>
  93. </div>
  94. </div>
  95. <!-- 상태 -->
  96. <div class="row mb-2">
  97. <label class="col-sm-2 col-form-label">상태</label>
  98. <div class="col-sm-10 align-content-center">
  99. <div class="form-check form-check-inline">
  100. <input type="checkbox" class="form-check-input" asp-for="Input.IsNotice" />
  101. <label class="form-check-label" for="Input_IsNotice">공지</label>
  102. </div>
  103. <div class="form-check form-check-inline">
  104. <input type="checkbox" class="form-check-input" asp-for="Input.IsSecret" />
  105. <label class="form-check-label" for="Input_IsSecret">비밀</label>
  106. </div>
  107. <div class="form-check form-check-inline">
  108. <input type="checkbox" class="form-check-input" asp-for="Input.IsAnonymous" />
  109. <label class="form-check-label" for="Input_IsAnonymous">익명</label>
  110. </div>
  111. </div>
  112. </div>
  113. <hr />
  114. <div class="d-grid gap-2 text-center d-md-block">
  115. <button type="submit" class="btn btn-success">저장</button>
  116. <a href="@(Model.ReturnUrl ?? "/Forum/Posts/List")" class="btn btn-secondary">취소</a>
  117. <button type="submit"
  118. class="btn btn-danger"
  119. formaction="?handler=Delete"
  120. formnovalidate
  121. onclick="return confirm('삭제 하시겠습니까? 삭제된 게시물은 복구가 불가능합니다.');">
  122. 삭제
  123. </button>
  124. </div>
  125. <br />
  126. </form>
  127. </div>
  128. @section Scripts {
  129. <script>
  130. setupImagePreview("Input_ThumbnailFile", "thumbPrev");
  131. // 페이지 로드 시 현재 게시판의 말머리 로드
  132. (async function () {
  133. const boardIDInput = document.querySelector("input[name='Input.BoardID']");
  134. const boardID = boardIDInput ? boardIDInput.value : "";
  135. const currentPrefixID = "@(Model.Input.BoardPrefixID?.ToString() ?? "")";
  136. const postID = "@Model.Input.ID";
  137. if (boardID) {
  138. try {
  139. const res = await fetch(`/Forum/Posts/List/Edit/${postID}?handler=Prefixes&boardID=${boardID}`);
  140. const items = await res.json();
  141. const prefixSelect = document.getElementById("boardPrefixSelect");
  142. if (items.length > 0) {
  143. items.forEach(item => {
  144. const opt = document.createElement("option");
  145. opt.value = item.id;
  146. opt.textContent = item.name;
  147. if (item.id.toString() === currentPrefixID) {
  148. opt.selected = true;
  149. }
  150. prefixSelect.appendChild(opt);
  151. });
  152. document.getElementById("prefixRow").hidden = false;
  153. } else {
  154. document.getElementById("prefixRow").hidden = true;
  155. }
  156. } catch {
  157. document.getElementById("prefixRow").hidden = true;
  158. }
  159. } else {
  160. document.getElementById("prefixRow").hidden = true;
  161. }
  162. })();
  163. </script>
  164. }