Edit.cshtml 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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 id="fAdminWrite" 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">@(Model.IsQnA ? "문의 유형" : "말머리")</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. <span asp-validation-for="Input.Content" class="text-danger"></span>
  37. </div>
  38. </div>
  39. <!-- 대표 이미지 -->
  40. <div class="row mb-2">
  41. <label class="col-sm-2 col-form-label">대표 이미지</label>
  42. <div class="col-sm-10">
  43. @if (!string.IsNullOrWhiteSpace(Model.CurrentThumbnail))
  44. {
  45. <div class="mb-2">
  46. <img class="img-fluid img-thumbnail" alt="대표 이미지" src="@Model.CurrentThumbnail" style="max-width: 300px;" /><br />
  47. <button type="submit"
  48. class="btn btn-sm btn-danger mt-2 mb-2"
  49. formaction="?handler=DeleteThumbnail"
  50. formnovalidate
  51. onclick="return confirm('대표 이미지를 삭제하시겠습니까?');">
  52. 삭제
  53. </button>
  54. </div>
  55. }
  56. <div id="thumbPrev" hidden>
  57. <img class="img-fluid img-thumbnail" alt="대표 이미지 미리보기" style="max-width: 300px;" /><br />
  58. <button type="button" class="btn btn-sm btn-danger mt-2 mb-2 btn-remove-preview">삭제</button>
  59. </div>
  60. <input type="file" id="Input_ThumbnailFile" asp-for="Input.ThumbnailFile" class="form-control" />
  61. <span class="form-text text-muted">
  62. 지원 확장자: <code>.jpg</code>, <code>.jpeg</code>, <code>.png</code>, <code>.gif</code>, <code>.webp</code>, <code>.bmp</code>
  63. </span>
  64. </div>
  65. </div>
  66. <!-- 기존 첨부파일 -->
  67. <div class="row mb-2">
  68. <label class="col-sm-2 col-form-label">기존 첨부파일</label>
  69. <div class="col-sm-10 align-self-center">
  70. @if (Model.ExistingFiles.Count > 0)
  71. {
  72. <ul class="list-unstyled mb-1">
  73. @foreach (var file in Model.ExistingFiles)
  74. {
  75. <li class="d-flex align-items-center gap-2 mb-1">
  76. <a href="@file.Url" target="_blank">@file.FileName</a>
  77. @if (file.Size.HasValue)
  78. {
  79. <small class="text-muted">(@(file.Size.Value > 1048576 ? $"{file.Size.Value / 1048576.0:F1}MB" : $"{file.Size.Value / 1024.0:F1}KB"))</small>
  80. }
  81. <small class="text-muted">다운로드: @file.Downloads</small>
  82. <button type="submit"
  83. class="btn btn-sm btn-outline-danger py-0 px-1"
  84. formaction="?handler=DeleteFile&amp;fileID=@file.ID"
  85. formnovalidate
  86. onclick="return confirm(`첨부파일 '@file.FileName'을(를) 삭제하시겠습니까?`);">
  87. 삭제
  88. </button>
  89. </li>
  90. }
  91. </ul>
  92. }
  93. else
  94. {
  95. <span class="text-muted p-2">없음</span>
  96. }
  97. </div>
  98. </div>
  99. <!-- 새 첨부파일 -->
  100. <div class="row mb-2">
  101. <label class="col-sm-2 col-form-label">첨부파일 추가</label>
  102. <div class="col-sm-10">
  103. <input type="file" asp-for="Input.Files" class="form-control" multiple />
  104. <span class="form-text text-muted">허용 확장자: <code>.jpg</code>, <code>.jpeg</code>, <code>.png</code>, <code>.gif</code>, <code>.webp</code>, <code>.bmp</code>, <code>.pdf</code>, <code>.doc</code>, <code>.docx</code>, <code>.xls</code>, <code>.xlsx</code>, <code>.ppt</code>, <code>.pptx</code>, <code>.txt</code>, <code>.zip</code>, <code>.rar</code>, <code>.7z</code>, <code>.hwp</code>, <code>.hwpx</code>, <code>.csv</code></span>
  105. </div>
  106. </div>
  107. <!-- 태그 -->
  108. <div class="row mb-2">
  109. <label class="col-sm-2 col-form-label">태그</label>
  110. <div class="col-sm-10">
  111. <input type="text" asp-for="Input.TagsInput" class="form-control"
  112. placeholder="태그를 쉼표(,)로 구분하여 입력하세요" />
  113. <span class="form-text text-muted">예: 비트코인, 이더리움, 블록체인</span>
  114. </div>
  115. </div>
  116. <!-- 상태 -->
  117. <div class="row mb-2">
  118. <label class="col-sm-2 col-form-label">상태</label>
  119. <div class="col-sm-10 align-content-center">
  120. <div class="form-check form-check-inline">
  121. <input type="checkbox" class="form-check-input" asp-for="Input.IsNotice" />
  122. <label class="form-check-label" for="Input_IsNotice">공지</label>
  123. </div>
  124. <div class="form-check form-check-inline">
  125. <input type="checkbox" class="form-check-input" asp-for="Input.IsSecret" />
  126. <label class="form-check-label" for="Input_IsSecret">비밀</label>
  127. </div>
  128. <div class="form-check form-check-inline">
  129. <input type="checkbox" class="form-check-input" asp-for="Input.IsAnonymous" />
  130. <label class="form-check-label" for="Input_IsAnonymous">익명</label>
  131. </div>
  132. </div>
  133. </div>
  134. <hr />
  135. <div class="d-grid gap-2 text-center d-md-block">
  136. <button type="submit" class="btn btn-success">저장</button>
  137. <a href="@(Model.ReturnUrl ?? "/Forum/Posts/List")" class="btn btn-secondary">취소</a>
  138. <button type="submit"
  139. class="btn btn-danger"
  140. formaction="?handler=Delete"
  141. formnovalidate
  142. onclick="return confirm('삭제 하시겠습니까? 삭제된 게시물은 복구가 불가능합니다.');">
  143. 삭제
  144. </button>
  145. </div>
  146. <br />
  147. </form>
  148. </div>
  149. @section Scripts {
  150. <script>
  151. setupImagePreview("Input_ThumbnailFile", "thumbPrev");
  152. // 페이지 로드 시 현재 게시판의 말머리 로드
  153. (async function () {
  154. const boardIDInput = document.querySelector("input[name='Input.BoardID']");
  155. const boardID = boardIDInput ? boardIDInput.value : "";
  156. const currentPrefixID = "@(Model.Input.BoardPrefixID?.ToString() ?? "")";
  157. const postID = "@Model.Input.ID";
  158. if (boardID) {
  159. try {
  160. const res = await fetch(`/Forum/Posts/List/Edit/${postID}?handler=Prefixes&boardID=${boardID}`);
  161. const items = await res.json();
  162. const prefixSelect = document.getElementById("boardPrefixSelect");
  163. if (items.length > 0) {
  164. items.forEach(item => {
  165. const opt = document.createElement("option");
  166. opt.value = item.id;
  167. opt.textContent = item.name;
  168. if (item.id.toString() === currentPrefixID) {
  169. opt.selected = true;
  170. }
  171. prefixSelect.appendChild(opt);
  172. });
  173. document.getElementById("prefixRow").hidden = false;
  174. } else {
  175. document.getElementById("prefixRow").hidden = true;
  176. }
  177. } catch {
  178. document.getElementById("prefixRow").hidden = true;
  179. }
  180. } else {
  181. document.getElementById("prefixRow").hidden = true;
  182. }
  183. })();
  184. </script>
  185. }