/* * editing.js * Inline contenteditable name editing for tree nodes (blur-to-save, strips * leading emoji prefix) and SortableJS drag-and-drop reorder wiring. * * SortableJS is loaded as an external CDN script (must precede this file). * _sortables is managed entirely within this module; render() in init.js * only needs to call initSortables() to refresh after a full re-render. * * Depends on: S (state.js); req, toast (api.js / helpers.js); * walkTree (tree-render.js); Sortable (CDN global) * Provides: attachEditables(), initSortables() */ // ── SortableJS instances (destroyed and recreated on each render) ───────────── let _sortables = []; // ── Inline name editing ────────────────────────────────────────────────────── function attachEditables() { document.querySelectorAll('[contenteditable=true]').forEach(el => { el.dataset.orig = el.textContent.trim(); el.addEventListener('keydown', e => { if (e.key==='Enter') { e.preventDefault(); el.blur(); } if (e.key==='Escape') { el.textContent=el.dataset.orig; el.blur(); } e.stopPropagation(); }); el.addEventListener('blur', async () => { const val = el.textContent.trim(); if (!val||val===el.dataset.orig) { if(!val) el.textContent=el.dataset.orig; return; } const newName = val.replace(/^[🏠📚]\s*/u,'').trim(); const {type, id} = el.dataset; const url = {room:`/api/rooms/${id}`,cabinet:`/api/cabinets/${id}`,shelf:`/api/shelves/${id}`}[type]; if (!url) return; try { await req('PUT', url, {name: newName}); el.dataset.orig = el.textContent.trim(); walkTree(n=>{ if(n.id===id) n.name=newName; }); // Update sidebar label if editing from header (sidebar has non-editable nname spans) const sideLabel = document.querySelector(`.node[data-id="${id}"] .nname`); if (sideLabel && sideLabel !== el) { const prefix = type==='room' ? '🏠 ' : type==='cabinet' ? '📚 ' : ''; sideLabel.textContent = prefix + newName; } } catch(err) { el.textContent=el.dataset.orig; toast('Rename failed: '+err.message); } }); el.addEventListener('click', e=>e.stopPropagation()); }); } // ── SortableJS drag-and-drop ───────────────────────────────────────────────── function initSortables() { _sortables.forEach(s=>{ try{s.destroy();}catch(_){} }); _sortables = []; document.querySelectorAll('.sortable-list').forEach(el => { const type = el.dataset.type; _sortables.push(Sortable.create(el, { handle:'.drag-h', animation:120, ghostClass:'drag-ghost', onEnd: async () => { const ids = [...el.querySelectorAll(':scope > .node')].map(n=>n.dataset.id); try { await req('PATCH',`/api/${type}/reorder`,{ids}); } catch(err) { toast('Reorder failed'); await loadTree(); } }, })); }); }