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); }
|
||||
}
|
||||
|
||||
// ── Property modal (uses global modal from page) ──
|
||||
// ── Property modal (inline, self-contained) ──
|
||||
function showPropModal() {
|
||||
const modal = document.getElementById('db-prop-modal');
|
||||
if (!modal) return;
|
||||
modal._dbInstance = this;
|
||||
modal.style.display = 'flex';
|
||||
document.getElementById('db-prop-name-input').value = '';
|
||||
document.getElementById('db-prop-name-input').focus();
|
||||
// Store ref to this instance for the create handler
|
||||
modal._collectionId = collectionId;
|
||||
modal._onPropCreated = function(newProp) { state.properties.push(newProp); render(); };
|
||||
// Remove any existing inline modal
|
||||
var old = containerEl.querySelector('.db-prop-modal-inline');
|
||||
if (old) old.remove();
|
||||
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'db-prop-modal-inline';
|
||||
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.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">'+
|
||||
'<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) {
|
||||
const menu = document.getElementById('db-col-menu');
|
||||
if (!menu) return;
|
||||
state.colMenuPropId = propId;
|
||||
document.getElementById('db-col-menu-icon').innerHTML = propTypeSvg(propType);
|
||||
const nameEl = document.getElementById('db-col-menu-name');
|
||||
nameEl.textContent = propName;
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
menu.style.top = (rect.bottom+4)+'px'; menu.style.left = rect.left+'px'; menu.style.display = 'block';
|
||||
// Remove any existing
|
||||
var old = document.querySelector('.db-col-menu-inline');
|
||||
if (old) old.remove();
|
||||
|
||||
var menu = document.createElement('div');
|
||||
menu.className = 'db-col-menu-inline';
|
||||
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)';
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -292,13 +333,40 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── Global Side Peek for Database Pages ──
|
||||
// ── Global Side Peek for Database Pages (auto-creates panel) ──
|
||||
function openPageInSidePeek(pageId) {
|
||||
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');
|
||||
if (!panel || !iframe) return;
|
||||
iframe.src = '/pages/' + pageId + '?embed=1';
|
||||
panel.style.display = 'flex';
|
||||
if (iframe) { iframe.src = '/pages/'+pageId+'?embed=1'; panel.style.display = 'flex'; }
|
||||
}
|
||||
function closeSidePeek() {
|
||||
var panel = document.getElementById('db-side-peek');
|
||||
@@ -306,37 +374,5 @@
|
||||
var iframe = document.getElementById('db-side-peek-iframe');
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user