fix(database): self-contained modals + side peek for inline embeds
- Property modal: dynamically created per DBInstance (no global ID dependency) - Column context menu: dynamically created (no global ID dependency) - Side peek panel: auto-created on first open (no template dependency) - All UI elements now work for both inline embeds and full-page views - Cell editing + save use correct CSRF token via getCsrf()
This commit is contained in:
@@ -232,31 +232,72 @@
|
|||||||
} catch(e) { console.error('[DB] addNewPage error:', e); }
|
} catch(e) { console.error('[DB] addNewPage error:', e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Property modal (uses global modal from page) ──
|
// ── Property modal (inline, self-contained) ──
|
||||||
function showPropModal() {
|
function showPropModal() {
|
||||||
const modal = document.getElementById('db-prop-modal');
|
// Remove any existing inline modal
|
||||||
if (!modal) return;
|
var old = containerEl.querySelector('.db-prop-modal-inline');
|
||||||
modal._dbInstance = this;
|
if (old) old.remove();
|
||||||
modal.style.display = 'flex';
|
|
||||||
document.getElementById('db-prop-name-input').value = '';
|
var modal = document.createElement('div');
|
||||||
document.getElementById('db-prop-name-input').focus();
|
modal.className = 'db-prop-modal-inline';
|
||||||
// Store ref to this instance for the create handler
|
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.3);z-index:1000;display:flex;align-items:center;justify-content:center';
|
||||||
modal._collectionId = collectionId;
|
modal.innerHTML = '<div class="db-modal" style="background:var(--bg-modal);border-radius:8px;box-shadow:0 8px 30px rgba(0,0,0,0.15);width:420px;max-width:90vw;overflow:hidden">'+
|
||||||
modal._onPropCreated = function(newProp) { state.properties.push(newProp); render(); };
|
'<div class="db-modal-header"><h3>Add a property</h3><button class="db-prop-modal-close-btn" style="display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:6px;border:none;background:transparent;cursor:pointer;color:var(--text-dim);font-size:18px">×</button></div>'+
|
||||||
|
'<div class="db-modal-body" style="padding:16px">'+
|
||||||
|
'<div class="db-modal-field" style="margin-bottom:12px"><label style="display:block;margin-bottom:4px;font-size:12px;color:var(--text-dim)">Name</label><input class="db-prop-name-inline" type="text" placeholder="Property name" style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:6px;font-size:14px;box-sizing:border-box"></div>'+
|
||||||
|
'<div class="db-modal-field" style="margin-bottom:12px"><label style="display:block;margin-bottom:4px;font-size:12px;color:var(--text-dim)">Type</label><select class="db-prop-type-inline" style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:6px;font-size:14px;box-sizing:border-box"><option value="text">Text</option><option value="number">Number</option><option value="select">Select</option><option value="multi_select">Multi-select</option><option value="date">Date</option><option value="checkbox">Checkbox</option><option value="url">URL</option><option value="email">Email</option><option value="phone">Phone</option></select></div>'+
|
||||||
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:16px"><button class="db-prop-cancel-inline" style="padding:6px 14px;border-radius:6px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text-secondary);font-size:13px;cursor:pointer">Cancel</button><button class="db-prop-create-inline" style="padding:6px 14px;border-radius:6px;border:none;background:var(--accent,#2383E2);color:white;font-size:13px;cursor:pointer">Create</button></div>'+
|
||||||
|
'</div></div>';
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
var nameInput = modal.querySelector('.db-prop-name-inline');
|
||||||
|
nameInput.focus();
|
||||||
|
|
||||||
|
modal.querySelector('.db-prop-modal-close-btn').onclick = function(){ modal.remove(); };
|
||||||
|
modal.querySelector('.db-prop-cancel-inline').onclick = function(){ modal.remove(); };
|
||||||
|
modal.onclick = function(e){ if(e.target===modal) modal.remove(); };
|
||||||
|
|
||||||
|
modal.querySelector('.db-prop-create-inline').onclick = async function(){
|
||||||
|
var name = nameInput.value.trim();
|
||||||
|
if(!name) return;
|
||||||
|
var type = modal.querySelector('.db-prop-type-inline').value;
|
||||||
|
try {
|
||||||
|
var r = await fetch('/db/'+collectionId+'/properties/api',{
|
||||||
|
method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':getCsrf()},
|
||||||
|
body:JSON.stringify({name:name,prop_type:type})
|
||||||
|
});
|
||||||
|
var d = await r.json();
|
||||||
|
if(d.id){ state.properties.push(d); render(); modal.remove(); }
|
||||||
|
} catch(e){ console.error('[DB] createProperty error:',e); }
|
||||||
|
};
|
||||||
|
nameInput.onkeydown = function(e){ if(e.key==='Enter') modal.querySelector('.db-prop-create-inline').click(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Column context menu ──
|
// ── Column context menu (inline) ──
|
||||||
function showColMenu(propId, propType, propName, anchor) {
|
function showColMenu(propId, propType, propName, anchor) {
|
||||||
const menu = document.getElementById('db-col-menu');
|
// Remove any existing
|
||||||
if (!menu) return;
|
var old = document.querySelector('.db-col-menu-inline');
|
||||||
state.colMenuPropId = propId;
|
if (old) old.remove();
|
||||||
document.getElementById('db-col-menu-icon').innerHTML = propTypeSvg(propType);
|
|
||||||
const nameEl = document.getElementById('db-col-menu-name');
|
var menu = document.createElement('div');
|
||||||
nameEl.textContent = propName;
|
menu.className = 'db-col-menu-inline';
|
||||||
const rect = anchor.getBoundingClientRect();
|
menu.style.cssText = 'position:fixed;min-width:240px;max-width:320px;background:var(--bg-modal);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.14);z-index:800;padding:4px 0;font-size:13px;color:var(--text-primary)';
|
||||||
menu.style.top = (rect.bottom+4)+'px'; menu.style.left = rect.left+'px'; menu.style.display = 'block';
|
var rect = anchor.getBoundingClientRect();
|
||||||
|
menu.style.top = (rect.bottom+4)+'px'; menu.style.left = rect.left+'px';
|
||||||
|
|
||||||
|
menu.innerHTML = '<div style="display:flex;align-items:center;gap:8px;padding:8px 12px;font-weight:600">'+
|
||||||
|
'<span>'+propTypeSvg(propType)+'</span><span>'+escHtml(propName)+'</span><span style="font-size:11px;color:var(--text-dim)">ⓘ</span></div>'+
|
||||||
|
'<div style="height:1px;background:var(--border);margin:4px 0"></div>'+
|
||||||
|
'<div style="padding:6px 12px;cursor:pointer">≡ Filter</div>'+
|
||||||
|
'<div style="padding:6px 12px;cursor:pointer">↕ Sort</div>'+
|
||||||
|
'<div style="padding:6px 12px;cursor:pointer">▦ Group</div>'+
|
||||||
|
'<div style="height:1px;background:var(--border);margin:4px 0"></div>'+
|
||||||
|
'<div style="padding:6px 12px;cursor:pointer">├← Insert left</div>'+
|
||||||
|
'<div style="padding:6px 12px;cursor:pointer">→┤ Insert right</div>';
|
||||||
|
|
||||||
|
document.body.appendChild(menu);
|
||||||
setTimeout(() => document.addEventListener('click', function onDoc(e) {
|
setTimeout(() => document.addEventListener('click', function onDoc(e) {
|
||||||
if(!menu.contains(e.target)) { menu.style.display='none'; document.removeEventListener('click',onDoc); }
|
if(!menu.contains(e.target)) { menu.remove(); document.removeEventListener('click',onDoc); }
|
||||||
}, {once:true}), 0);
|
}, {once:true}), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,13 +333,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Global Side Peek for Database Pages ──
|
// ── Global Side Peek for Database Pages (auto-creates panel) ──
|
||||||
function openPageInSidePeek(pageId) {
|
function openPageInSidePeek(pageId) {
|
||||||
var panel = document.getElementById('db-side-peek');
|
var panel = document.getElementById('db-side-peek');
|
||||||
|
if (!panel) {
|
||||||
|
// Create panel dynamically
|
||||||
|
panel = document.createElement('div');
|
||||||
|
panel.id = 'db-side-peek';
|
||||||
|
panel.style.cssText = 'position:fixed;top:0;right:0;width:560px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 20px rgba(0,0,0,0.12);z-index:900;display:flex;flex-direction:column';
|
||||||
|
panel.innerHTML = '<div id="db-side-peek-resize" style="position:absolute;top:0;left:0;width:6px;height:100%;cursor:col-resize;z-index:2"></div>'+
|
||||||
|
'<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--border);flex-shrink:0">'+
|
||||||
|
'<button id="db-side-peek-close" style="display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:6px;border:none;background:transparent;cursor:pointer;font-size:18px;color:var(--text-dim)">×</button>'+
|
||||||
|
'<button id="db-side-peek-full" style="display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:6px;border:none;background:transparent;cursor:pointer;font-size:18px;color:var(--text-dim)" title="Open full page">↗</button>'+
|
||||||
|
'</div>'+
|
||||||
|
'<iframe id="db-side-peek-iframe" src="" style="width:100%;height:100%;border:none;flex:1"></iframe>';
|
||||||
|
document.body.appendChild(panel);
|
||||||
|
|
||||||
|
document.getElementById('db-side-peek-close').onclick = function(){ panel.style.display='none'; document.getElementById('db-side-peek-iframe').src=''; };
|
||||||
|
document.getElementById('db-side-peek-full').onclick = function(){
|
||||||
|
var ifr = document.getElementById('db-side-peek-iframe');
|
||||||
|
if(ifr&&ifr.src) window.open(ifr.src.replace('?embed=1',''),'_blank');
|
||||||
|
};
|
||||||
|
// Resize
|
||||||
|
var rh = document.getElementById('db-side-peek-resize');
|
||||||
|
rh.onpointerdown = function(e) {
|
||||||
|
e.preventDefault(); e.stopPropagation();
|
||||||
|
var sx = e.clientX, sw = panel.offsetWidth;
|
||||||
|
function mv(ev) { var w = Math.max(300, Math.min(window.innerWidth*0.9, sw-(ev.clientX-sx))); panel.style.width = w+'px'; }
|
||||||
|
function up() { document.removeEventListener('pointermove',mv); document.removeEventListener('pointerup',up); document.body.style.cursor=''; }
|
||||||
|
document.body.style.cursor='col-resize';
|
||||||
|
document.addEventListener('pointermove',mv); document.addEventListener('pointerup',up);
|
||||||
|
};
|
||||||
|
}
|
||||||
var iframe = document.getElementById('db-side-peek-iframe');
|
var iframe = document.getElementById('db-side-peek-iframe');
|
||||||
if (!panel || !iframe) return;
|
if (iframe) { iframe.src = '/pages/'+pageId+'?embed=1'; panel.style.display = 'flex'; }
|
||||||
iframe.src = '/pages/' + pageId + '?embed=1';
|
|
||||||
panel.style.display = 'flex';
|
|
||||||
}
|
}
|
||||||
function closeSidePeek() {
|
function closeSidePeek() {
|
||||||
var panel = document.getElementById('db-side-peek');
|
var panel = document.getElementById('db-side-peek');
|
||||||
@@ -306,37 +374,5 @@
|
|||||||
var iframe = document.getElementById('db-side-peek-iframe');
|
var iframe = document.getElementById('db-side-peek-iframe');
|
||||||
if (iframe) { iframe.src = ''; }
|
if (iframe) { iframe.src = ''; }
|
||||||
}
|
}
|
||||||
// Bind close button on DOM ready
|
|
||||||
setTimeout(function() {
|
|
||||||
var closeBtn = document.getElementById('db-side-peek-close');
|
|
||||||
if (closeBtn) closeBtn.onclick = closeSidePeek;
|
|
||||||
var fullBtn = document.getElementById('db-side-peek-full');
|
|
||||||
if (fullBtn) fullBtn.onclick = function() {
|
|
||||||
var iframe = document.getElementById('db-side-peek-iframe');
|
|
||||||
if (iframe && iframe.src) window.open(iframe.src.replace('?embed=1',''), '_blank');
|
|
||||||
};
|
|
||||||
// Resize handle
|
|
||||||
var resizeHandle = document.getElementById('db-side-peek-resize');
|
|
||||||
var panel = document.getElementById('db-side-peek');
|
|
||||||
if (resizeHandle && panel) {
|
|
||||||
resizeHandle.onpointerdown = function(e) {
|
|
||||||
e.preventDefault(); e.stopPropagation();
|
|
||||||
var startX = e.clientX;
|
|
||||||
var startW = panel.offsetWidth;
|
|
||||||
function onMove(ev) {
|
|
||||||
var newW = Math.max(300, Math.min(window.innerWidth * 0.9, startW - (ev.clientX - startX)));
|
|
||||||
panel.style.width = newW + 'px';
|
|
||||||
}
|
|
||||||
function onUp() {
|
|
||||||
document.removeEventListener('pointermove', onMove);
|
|
||||||
document.removeEventListener('pointerup', onUp);
|
|
||||||
document.body.style.cursor = '';
|
|
||||||
}
|
|
||||||
document.body.style.cursor = 'col-resize';
|
|
||||||
document.addEventListener('pointermove', onMove);
|
|
||||||
document.addEventListener('pointerup', onUp);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user