From 13e0bfb28a1ebf6fc5e694bb4f0cc37686981a4e Mon Sep 17 00:00:00 2001 From: bruno Date: Tue, 21 Jul 2026 22:39:09 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20Database=20button=20in=20page=20editor?= =?UTF-8?q?=20=E2=80=94=20creates=20real=20inline=20collections?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 'Get started with' Database/Form buttons now POST to /db/inline/api - Creates real collections linked to the page via parent_page_id - Inline DBs rendered as embed blocks with links to collection views - Persisted in page blocks JSON (embed_type='inline_dbs') - Templates button opens the 'More' dropdown with all view types - 175 tests pass --- app/templates/page_editor.html | 73 ++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/app/templates/page_editor.html b/app/templates/page_editor.html index 025e65d..e921535 100644 --- a/app/templates/page_editor.html +++ b/app/templates/page_editor.html @@ -457,19 +457,19 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %} @@ -575,6 +575,7 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %} if(b.type==='divider')return `

`; 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==='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'; @@ -636,6 +637,7 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %} else if(fmt==='blocks'&&raw){try{this.blocks=JSON.parse(raw);this.blocks.forEach(b=>{if(!b.id)b.id=genId()});}catch(e){this.blocks=[];}} else if(raw&&raw.trim())this.blocks=this.md2b(raw);}catch(e){}} if(!this.blocks.length)this.blocks=[this.mkB('paragraph','')]; + this.loadInlineDBs(); window.E=this;SM.init();this.render(); setTimeout(()=>{this.focusBlock();const ct=document.getElementById('_blocksCt');if(!ct)return; ct.addEventListener('keydown',e=>this.onKd(e)); @@ -841,6 +843,71 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %} }, 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:[], + async createDatabase(){ + const name=prompt('Database name:','New Database'); + if(!name||!name.trim())return; + const csrf=document.cookie.match(/csrf_token=([^;]+)/); + try{ + const r=await fetch('/db/inline/api',{ + method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''}, + body:JSON.stringify({name:name.trim(),parent_page_id:this.pid}) + }); + const d=await r.json(); + if(d.id){ + this.inlineDBs.push({id:d.id,name:d.name,view_type:'table'}); + this.showToast('Database created — '+d.name); + this.saveDBsToPage(); + this.renderInlineDBs(); + }else{ + this.showToast('Failed to create database'); + } + }catch(e){this.showToast('Error: '+e.message);} + }, + async createForm(){ + const name=prompt('Form name:','New Form'); + if(!name||!name.trim())return; + const csrf=document.cookie.match(/csrf_token=([^;]+)/); + try{ + const r=await fetch('/db/inline/api',{ + method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''}, + body:JSON.stringify({name:name.trim(),parent_page_id:this.pid}) + }); + const d=await r.json(); + if(d.id){ + this.inlineDBs.push({id:d.id,name:d.name,view_type:'form'}); + this.showToast('Form created — '+d.name); + this.saveDBsToPage(); + this.renderInlineDBs(); + }else{ + this.showToast('Failed to create form'); + } + }catch(e){this.showToast('Error: '+e.message);} + }, + saveDBsToPage(){ + // Store inline DB refs in the page's blocks as a special embed block + if(this.inlineDBs.length===0)return; + const existing=this.blocks.filter(b=>b.type==='embed'&&b.embed_type==='inline_dbs'); + if(existing.length===0){ + this.blocks.push(this.mkB('embed','',{embed_type:'inline_dbs',dbs:this.inlineDBs})); + }else{ + existing.forEach(b=>{b.dbs=this.inlineDBs;}); + } + this.render(); + this.dirty=true;this.autoSave(); + }, + renderInlineDBs(){ + // Re-render with inline DB blocks + this.render(); + }, + loadInlineDBs(){ + const existing=this.blocks.filter(b=>b.type==='embed'&&b.embed_type==='inline_dbs'); + if(existing.length>0){ + this.inlineDBs=existing[0].dbs||[]; + } + }, + onKd(e){ const ab=this.getActiveBlock();if(!ab)return; const{el,bid,idx}=ab;const block=this.blocks[idx];if(!block)return;