fix(project): 설명란 이미지 삭제 시 첨부파일 목록에서도 같이 삭제

설명에 붙여넣기/업로드로 넣은 이미지는 실제로는 task_attachments 레코드라,
에디터에서 이미지만 지우면(× 버튼 또는 Delete/Backspace) 서버 첨부는
그대로 남아 첨부파일 목록에 계속 보였다. src 의 attachment id 를 뽑아
DELETE 호출까지 같이 하고, 첨부파일 목록도 새로고침한다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 01:12:08 +09:00
parent 278151a3fc
commit 33b6bd2201
4 changed files with 34 additions and 9 deletions
+31 -6
View File
@@ -211,6 +211,12 @@
const descEditor = initDescEditor();
const subtaskBlock = initSubtaskBlock();
const linksBlock = initLinksBlock();
if (descEditor) {
descEditor.setOnImageRemoved(function () {
const tid = el("pj-t-id").value;
if (tid) loadHomeAttachments(tid);
});
}
// 닫을 때(배경 클릭·Esc·취소 버튼) 수정한 내용이 있으면 저장할지 물어본다.
let homeTaskSnapshot = null;
@@ -1631,6 +1637,12 @@
const descEditor = initDescEditor();
const subtaskBlock = initSubtaskBlock();
const linksBlock = initLinksBlock();
if (descEditor) {
descEditor.setOnImageRemoved(function () {
const tid = el("pj-t-id").value;
if (tid) loadAttachments(tid);
});
}
// 닫을 때(배경 클릭·Esc·취소 버튼) 수정한 내용이 있으면 저장할지 물어본다.
// 하위업무/첨부/댓글/연결은 각자 즉시 저장돼서 여기서 추적할 필요 없다 —
@@ -2139,6 +2151,22 @@
const imgDelBtn = imgTools.querySelector(".pj-img-del");
const imgHandle = imgTools.querySelector(".pj-img-handle");
let selectedImg = null;
let onImageRemoved = null;
// 설명란 이미지는 첨부파일 업로드로 들어간 실제 첨부 레코드다 — 에디터에서
// 지우면 첨부파일 목록에도 안 남게 서버 첨부도 같이 삭제한다.
async function deleteSelectedImage() {
if (!selectedImg) return;
const img = selectedImg;
const m = (img.getAttribute("src") || "").match(/\/attachments\/(\d+)\/inline/);
img.remove();
selectImg(null);
if (!m) return;
try {
await api("DELETE", "/project/api/attachments/" + m[1]);
} catch (_) { /* 이미 지워졌을 수 있음 — 에디터에서는 이미 빠졌으니 조용히 무시 */ }
if (onImageRemoved) onImageRemoved();
}
function positionImgTools() {
if (!selectedImg || !selectedImg.isConnected) { selectedImg = null; imgTools.hidden = true; return; }
@@ -2161,10 +2189,7 @@
document.addEventListener("click", function (e) {
if (selectedImg && !wrap.contains(e.target)) selectImg(null);
});
imgDelBtn.addEventListener("click", function () {
if (selectedImg) selectedImg.remove();
selectImg(null);
});
imgDelBtn.addEventListener("click", function () { deleteSelectedImage(); });
let dragging = false, dragStartX = 0, dragStartW = 0;
imgHandle.addEventListener("mousedown", function (e) {
if (!selectedImg) return;
@@ -2190,8 +2215,7 @@
const active = document.activeElement;
if (active && ["INPUT", "TEXTAREA", "SELECT"].indexOf(active.tagName) >= 0) return;
e.preventDefault();
selectedImg.remove();
selectImg(null);
deleteSelectedImage();
});
// 업로드 버튼 클릭 → 파일 선택창이 뜨는 동안 contenteditable의 선택 범위가
@@ -2322,6 +2346,7 @@
setHtml: function (html) { selectImg(null); lastRange = null; box.innerHTML = html || ""; },
getHtml: function () { return box.innerHTML; },
isUploading: function () { return pendingUploads > 0; },
setOnImageRemoved: function (fn) { onImageRemoved = fn; },
};
}