From c586513b03aae3989d40e118110bc14c85b3ae14 Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 22 Jul 2026 16:52:19 -0400 Subject: [PATCH] feat(database): inline embed + Open button + side peek + title placeholder - Page title placeholder: new pages created with empty title, CSS :empty::before shows 'New page' in gray - Database is now inline embed within page (not full-page replacement) - Embed block 'collection' renders database table via FlowDeckDB.renderInto() - Open button in column 1 of each row opens page in side peek panel - Side peek: iframe loading /pages/{id}?embed=1, close/fullscreen/resize - DB scripts refactored as reusable DBInstance + global FlowDeckDB API --- app/routers/board.py | 2 +- app/templates/_database_table.html | 39 +- app/templates/_database_table_scripts.html | 735 ++++++++------------- app/templates/_page_editor_scripts.html | 16 +- app/templates/base.html | 2 +- app/templates/page_editor.html | 1 + static/css/app.css | 30 + 7 files changed, 331 insertions(+), 494 deletions(-) diff --git a/app/routers/board.py b/app/routers/board.py index 9f1ff2a..423d067 100644 --- a/app/routers/board.py +++ b/app/routers/board.py @@ -814,7 +814,7 @@ async def create_page(request: Request, title: str = Query(default=""), """Create a new Markdown page, optionally as a sub-page.""" user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) ws_key = project if project else (user.get("login", "Bruno") if user else "Bruno") - page_title = title.strip() if title else "Untitled" + page_title = title.strip() if title else "" try: with get_conn() as conn: # Compute next sort_order for this parent diff --git a/app/templates/_database_table.html b/app/templates/_database_table.html index b468927..8e41ab7 100644 --- a/app/templates/_database_table.html +++ b/app/templates/_database_table.html @@ -86,38 +86,24 @@ ›
≡ Filter
-
- ↕ Sort - › -
+
↕ Sort›
▦ Group
-
- Σ Calculate - › -
+
Σ Calculate›
diff --git a/app/templates/_database_table_scripts.html b/app/templates/_database_table_scripts.html index 1c5b8bd..a5b282b 100644 --- a/app/templates/_database_table_scripts.html +++ b/app/templates/_database_table_scripts.html @@ -2,21 +2,8 @@ diff --git a/app/templates/_page_editor_scripts.html b/app/templates/_page_editor_scripts.html index 2b837f4..bc1c10f 100644 --- a/app/templates/_page_editor_scripts.html +++ b/app/templates/_page_editor_scripts.html @@ -55,6 +55,7 @@ if(b.type==='image')return `
${esc(b.alt||'')}
`; if(b.type==='embed'){ if(b.embed_type==='inline_dbs')return `
📊 Inline Databases
`; + if(b.embed_type==='collection')return `
Loading database...
`; if(b.embed_type==='pdf')return `

Failed to load PDF

Open in new tab →
`; if(b.embed_type==='download'){ var sz=b.file_size||0;var szStr=sz<1024?sz+' B':sz<1048576?(sz/1024).toFixed(1)+' KB':(sz/1048576).toFixed(1)+' MB'; @@ -322,22 +323,23 @@ }, deletePage(){this.moreOpen=false;if(!confirm('Move to Trash?'))return;const csrf=document.cookie.match(/csrf_token=([^;]+)/);fetch(`/board/api/pages/${this.pid}/trash`,{method:'POST',headers:{'X-CSRF-Token':csrf?csrf[1]:''}}).then(()=>{window.location.href='/';}).catch(()=>{window.showToast('Failed','error');});}, - // ── v4.5.0+: Get Started actions ── - inlineDBs:[], + // ── v4.6.0: Get Started actions ── async createDatabase(){ const name=prompt('Database name:',this.pageTitle||'New Database'); if(!name||!name.trim())return; const csrf=document.cookie.match(/csrf_token=([^;]+)/); try{ - const r=await fetch(`/api/pages/${this.pid}/convert-to-database`,{ + // Create collection via API + const r=await fetch('/db/api',{ method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''}, body:JSON.stringify({name:name.trim()}) }); const d=await r.json(); - if(d.status==='converted'){ - this.showToast('Database created — reloading...'); - // Reload to show the database table view - setTimeout(()=>{window.location.href=d.view_url;},500); + if(d.id){ + // Add inline database embed block to the page + this.blocks.push(this.mkB('embed','',{embed_type:'collection',collection_id:d.id})); + this.render(); this.dirty=true; this.autoSave(); + this.showToast('Database created: '+d.name); }else{ this.showToast('Failed to create database'); } diff --git a/app/templates/base.html b/app/templates/base.html index 082e556..dde5715 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -756,7 +756,7 @@ // ── Workspace context → create workspace item ── if (ctx.wsId > 0) { - var body = { name: 'Untitled', type: 'page' }; + var body = { name: '', type: 'page' }; if (ctx.isGitea) body.workspace_id = ctx.wsId; fetch('/api/local-workspace/items', { diff --git a/app/templates/page_editor.html b/app/templates/page_editor.html index f389d96..5607076 100644 --- a/app/templates/page_editor.html +++ b/app/templates/page_editor.html @@ -8,4 +8,5 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %} {% include "_page_editor_content.html" %} {% endblock %} {% block scripts %} {% include "_page_editor_scripts.html" %} +{% include "_database_table_scripts.html" %} {% endblock %} diff --git a/static/css/app.css b/static/css/app.css index 5178087..653aad5 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -3952,3 +3952,33 @@ button.breadcrumb-current { max-width: 260px; } .db-th-property { min-width: 120px; } .db-td-cell { min-width: 120px; } } + +/* ── Database Side Peek Panel ── */ +.db-side-peek { + 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; +} +.db-side-peek-resize { + position: absolute; top: 0; left: 0; + width: 6px; height: 100%; + cursor: col-resize; + z-index: 2; +} +.db-side-peek-resize:hover { background: var(--accent, #2383E2); } +.db-side-peek-header { + display: flex; align-items: center; justify-content: space-between; + padding: 8px 12px; border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.db-side-peek-close, .db-side-peek-full { + 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); +} +.db-side-peek-close:hover, .db-side-peek-full:hover { background: var(--bg-hover); } +.db-side-peek iframe { flex: 1; }