From 9c4de01fd2674648a92e3e6f817268bd6a167e6e Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 22 Jul 2026 16:23:02 -0400 Subject: [PATCH] fix(database): column resize follows mouse, inline rename in context menu - Column resize: use getBoundingClientRect() + setPointerCapture for proper tracking - Context menu 'Name' field: click to edit, Enter to save, Escape to cancel - Rename persists to server via PUT /db/properties/{id}/api --- app/templates/_database_table_scripts.html | 90 +++++++++++++++++++--- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/app/templates/_database_table_scripts.html b/app/templates/_database_table_scripts.html index ca9e0e2..8bc5dbe 100644 --- a/app/templates/_database_table_scripts.html +++ b/app/templates/_database_table_scripts.html @@ -164,30 +164,29 @@ thead.querySelectorAll('.db-col-resize-handle').forEach(handle => { handle.onpointerdown = function(e) { e.preventDefault(); + e.stopPropagation(); + this.setPointerCapture(e.pointerId); const propId = parseInt(this.dataset.propId); const th = this.parentElement; const startX = e.clientX; - const startW = th.offsetWidth; + const startW = th.getBoundingClientRect().width; function onMove(ev) { const diff = ev.clientX - startX; const newW = Math.max(60, startW + diff); state.colWidths[propId] = newW; - // Update header th.style.width = newW + 'px'; th.style.minWidth = newW + 'px'; - // Update all cells in this column const colIdx = state.properties.findIndex(p => p.id === propId); if (colIdx >= 0) { document.querySelectorAll('#db-tbody .db-row').forEach(row => { const cells = row.querySelectorAll('.db-td-cell'); - if (cells[colIdx]) { - cells[colIdx].style.minWidth = newW + 'px'; - } + if (cells[colIdx]) cells[colIdx].style.minWidth = newW + 'px'; }); } } - function onUp() { + function onUp(ev) { + handle.releasePointerCapture(ev.pointerId); document.removeEventListener('pointermove', onMove); document.removeEventListener('pointerup', onUp); document.body.style.cursor = ''; @@ -242,7 +241,38 @@ // Set header document.getElementById('db-col-menu-icon').innerHTML = propTypeSvg(propType); - document.getElementById('db-col-menu-name').textContent = propName; + const nameEl = document.getElementById('db-col-menu-name'); + nameEl.textContent = propName; + nameEl.contentEditable = 'false'; + nameEl.style.cursor = 'text'; + nameEl.style.border = 'none'; + nameEl.style.outline = 'none'; + nameEl.style.padding = '0'; + nameEl.style.background = 'transparent'; + nameEl.title = 'Click to rename'; + // Click on name to edit + nameEl.onclick = function(e) { + e.stopPropagation(); + if (nameEl.contentEditable === 'true') return; + nameEl.contentEditable = 'true'; + nameEl.style.border = '1px solid var(--accent, #2383E2)'; + nameEl.style.borderRadius = '3px'; + nameEl.style.padding = '2px 4px'; + nameEl.style.outline = 'none'; + nameEl.focus(); + // Select all text + const range = document.createRange(); + range.selectNodeContents(nameEl); + const sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + }; + // Blur/Enter → save rename + nameEl.onblur = function() { commitColRename(propId); }; + nameEl.onkeydown = function(e) { + if (e.key === 'Enter') { e.preventDefault(); commitColRename(propId); } + else if (e.key === 'Escape') { cancelColRename(propId, propName); } + }; document.getElementById('db-col-menu-info').textContent = 'ⓘ'; // Position near the anchor @@ -268,13 +298,55 @@ const menu = document.getElementById('db-col-menu'); if (menu) { menu.style.display = 'none'; - // Hide all submenus menu.querySelectorAll('.db-col-menu-sub').forEach(s => s.style.display = 'none'); } state.colMenuPropId = null; state.colMenuSubOpen = null; } + async function commitColRename(propId) { + const nameEl = document.getElementById('db-col-menu-name'); + if (!nameEl) return; + const newName = nameEl.textContent.trim(); + if (!newName) return; + nameEl.contentEditable = 'false'; + nameEl.style.border = 'none'; + nameEl.style.borderRadius = '0'; + nameEl.style.padding = '0'; + + const prop = state.properties.find(p => p.id === propId); + if (!prop || prop.name === newName) return; + + // Update local state + prop.name = newName; + // Update header in table + renderTable(); + // If menu still open, update its display + if (state.colMenuPropId === propId) { + nameEl.textContent = newName; + } + + // Persist to server + const csrf = document.cookie.match(/csrf_token=([^;]+)/); + try { + await fetch('/db/properties/' + propId + '/api', { + method: 'PUT', + headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf ? csrf[1] : '' }, + body: JSON.stringify({ name: newName }) + }); + } catch(e) { console.error('[DB] rename property error:', e); } + } + + function cancelColRename(propId, originalName) { + const nameEl = document.getElementById('db-col-menu-name'); + if (!nameEl) return; + nameEl.contentEditable = 'false'; + nameEl.textContent = originalName; + nameEl.style.border = 'none'; + nameEl.style.borderRadius = '0'; + nameEl.style.padding = '0'; + } + function toggleColSubmenu(subId) { const sub = document.getElementById(subId); if (!sub) return;