Fix drag regression by gating inline edit behind double-click

The previous version had contenteditable always on for title/artist
spans, which intercepted mousedown and prevented dragstart from firing
on the row. Now the spans render as plain text, and double-click
activates contenteditable + focus + select-all. blur or Enter/Escape
exits edit mode, saves to state, and re-enables row dragging.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-12 14:18:01 +09:00
parent 7ac07a58ef
commit e617c71b0a

View File

@@ -70,14 +70,16 @@
li.className = 'trackRow' li.className = 'trackRow'
li.draggable = true li.draggable = true
li.dataset.index = String(idx) li.dataset.index = String(idx)
// 기본 상태에서는 contenteditable 을 켜지 않는다. 더블클릭 시에만 편집 모드 ON.
// 이렇게 해야 어디를 눌러도 드래그가 시작될 수 있다.
li.innerHTML = li.innerHTML =
'<span class="rowNum">' + (idx + 1) + '</span>' + '<span class="rowNum">' + (idx + 1) + '</span>' +
'<img class="rowThumb" src="' + thumbUrl(entry.url) + '" alt="" loading="lazy"/>' + '<img class="rowThumb" src="' + thumbUrl(entry.url) + '" alt="" loading="lazy" draggable="false"/>' +
'<div class="rowMeta">' + '<div class="rowMeta">' +
'<div class="rowTitle" contenteditable="true" spellcheck="false" data-field="title" data-placeholder="(제목 없음)">' + '<div class="rowTitle" spellcheck="false" data-field="title" data-placeholder="(제목 없음)" title="더블클릭해서 수정">' +
escapeHtml(entry.title || '') + escapeHtml(entry.title || '') +
'</div>' + '</div>' +
'<div class="rowSub" contenteditable="true" spellcheck="false" data-field="artist" data-placeholder="(가수 미상)">' + '<div class="rowSub" spellcheck="false" data-field="artist" data-placeholder="(가수 미상)" title="더블클릭해서 수정">' +
escapeHtml(entry.artist || '') + escapeHtml(entry.artist || '') +
'</div>' + '</div>' +
'</div>' + '</div>' +
@@ -112,18 +114,43 @@
} }
// ── 인라인 편집 (제목/가수) ───────────────────────── // ── 인라인 편집 (제목/가수) ─────────────────────────
// 기본 상태에서는 contenteditable 이 꺼져 있어서 row 어디를 클릭해도 드래그가 시작된다.
// 더블클릭 시점에 해당 칸만 contenteditable 로 켜고, 드래그는 일시 비활성화.
// blur 또는 Enter 키로 편집 종료 + 상태 저장 + 드래그 복원.
function attachInlineEdit(li, idx) { function attachInlineEdit(li, idx) {
li.querySelectorAll('[contenteditable="true"]').forEach(function (el) { li.querySelectorAll('[data-field]').forEach(function (el) {
el.addEventListener('focus', function () { li.draggable = false }) el.addEventListener('mousedown', function (e) {
el.addEventListener('blur', function () { li.draggable = true }) // 편집 모드일 때만 드래그를 막아서 텍스트 선택을 허용.
el.addEventListener('input', function () { if (el.getAttribute('contenteditable') === 'true') e.stopPropagation()
})
el.addEventListener('dblclick', function (e) {
e.stopPropagation()
if (el.getAttribute('contenteditable') === 'true') return
el.setAttribute('contenteditable', 'true')
li.draggable = false
el.focus()
try {
var range = document.createRange()
range.selectNodeContents(el)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
} catch (_) {}
})
el.addEventListener('blur', function () {
if (el.getAttribute('contenteditable') !== 'true') return
el.removeAttribute('contenteditable')
li.draggable = true
var field = el.getAttribute('data-field') var field = el.getAttribute('data-field')
var value = (el.textContent || '').replace(/\r?\n/g, ' ').trim() var value = (el.textContent || '').replace(/\r?\n/g, ' ').trim()
if (!state.music[idx]) return
if (field === 'title') state.music[idx].title = value if (field === 'title') state.music[idx].title = value
else if (field === 'artist') state.music[idx].artist = value else if (field === 'artist') state.music[idx].artist = value
}) })
el.addEventListener('keydown', function (e) { el.addEventListener('keydown', function (e) {
if (el.getAttribute('contenteditable') !== 'true') return
if (e.key === 'Enter') { e.preventDefault(); el.blur() } if (e.key === 'Enter') { e.preventDefault(); el.blur() }
else if (e.key === 'Escape') { e.preventDefault(); el.blur() }
}) })
}) })
} }