diff --git a/CHANGELOG.md b/CHANGELOG.md
index a9fd693..2c3822f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,29 @@
# Changelog — FlowDeck
-## v5.1.1 (2026-09-07) — Rendu Markdown naturel (éditeur + panneau Agent)
+## v5.1.2 (2026-09-07) — Éditeur style Notion AI (hint, menu slash, AI inline)
+
+> Adapte le visuel des blocs à l'expérience Notion AI : hint sur bloc vide, menu
+> `/` compact avec raccourcis + aperçu au survol, et composer AI inline
+> (« Edit with AI » → « Brewing… » → résultat encadré → « Insert below »).
+
+### Bloc vide & commandes (Images 1 & 3)
+- **Hint de bloc vide** : sur un paragraphe vide, le placeholder devient
+ « Press 'space' for AI or '/' for commands ».
+- **Menu slash refait** : compact (icône + nom + raccourci `#`/`##`/`###`… à droite),
+ groupes renommés (« Basic blocks », « Media », « Data »), footer **« Close menu (esc) »**,
+ champ **« Type to search »** en bas, et **aperçu au survol** d'un item (carte
+ « Our Values » / exemple de rendu de bloc), positionnée à côté du menu.
+
+### Composer AI inline (Images 2, 4, 5, 6)
+- **Espace sur un bloc vide** ouvre le composer AI inline (pill « **Edit with AI** »).
+- **Entrée** → l'agent génère : indicateur « **Brewing…** » animé (bouton ■ pour
+ stopper), puis **résultat encadré** (markdown rendu : titres, listes, gras…)
+ avec barre d'actions (👍/👎) et bouton **« Insert below »**.
+- **Insert below** insère le contenu comme vrais blocs (via `applyAIBlocks` → `md2b`).
+
+### Tests / validation
+- 271 tests pytest verts (aucune régression) ; éditeur rendu 200 (hint, menu, AIC présents) ;
+ JS validé (`node --check`).
> L'agent FlowDeck et l'insertion dans les pages affichent désormais le markdown
> rendu (titre, gras, tableaux, code…) au lieu des caractères bruts `##`, `**`…
diff --git a/VERSION b/VERSION
index 3bff059..1b47e8f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.1.1
\ No newline at end of file
+5.1.2
\ No newline at end of file
diff --git a/app/main.py b/app/main.py
index 02c56a4..030b285 100644
--- a/app/main.py
+++ b/app/main.py
@@ -60,7 +60,7 @@ async def lifespan(_app: FastAPI):
app = FastAPI(
title="FlowDeck",
- version="5.1.1",
+ version="5.1.2",
docs_url="/docs" if settings.log_level == "DEBUG" else None,
redoc_url=None,
lifespan=lifespan,
diff --git a/app/templates/_page_editor_scripts.html b/app/templates/_page_editor_scripts.html
index 99eb179..27819e9 100644
--- a/app/templates/_page_editor_scripts.html
+++ b/app/templates/_page_editor_scripts.html
@@ -181,31 +181,73 @@
});
}
- const SM={_o:false,_i:-1,_s:0,_q:'',_it:[],
+ // ── Block type shortcuts (shown on the right of each slash item) ──
+ const _SHORTCUT={text:'T',heading_1:'#',heading_2:'##',heading_3:'###',heading_4:'####',bulleted_list:'•',numbered_list:'1.',to_do:'[ ]',toggle:'▸',quote:'"',callout:'!',table_of_contents:'TOC',math:'∑',columns:'‖',divider:'———',code:'<>',table:'|',image:'[img]',database:'DB'};
+ const _GROUP_LABEL={BASIC:'Basic blocks',MEDIA:'Media',DATA:'Data'};
+ // ── Hover preview card (matches Notion's visual hint) ──
+ function _previewHtml(blockId){
+ const p=function(inner,cls){return '
'+inner+'
';};
+ switch(blockId){
+ case 'heading_1': return p('Big section heading
');
+ case 'heading_2': return p('Our Values
• Ownership
• Altruism
• Integrity
Medium section heading
');
+ case 'heading_3': return p('Small section heading
Body text goes here…
');
+ case 'heading_4': return p('Tiny section heading
');
+ case 'bulleted_list': return p('• List item
• Another item
• Third item
');
+ case 'numbered_list': return p('1. First
2. Second
3. Third
');
+ case 'to_do': return p('☐ Todo item
☑ Checked todo
☐ Another
');
+ case 'toggle': return p('▸ Toggle content
');
+ case 'quote': return p('A blockquote to highlight a quotation.
');
+ case 'callout': return p('💡 A callout draws attention.
');
+ case 'table_of_contents': return p('On this page
· Heading 1
· Heading 2
· Heading 3
');
+ case 'math': return p('E = mc²
');
+ case 'columns': return p('');
+ case 'divider': return p('
');
+ case 'code': return p('const x = 1;
');
+ case 'table': return p('');
+ case 'image': return p('[Image]
');
+ case 'database': return p('📊 Inline database table
');
+ default: return p('Text block
');
+ }
+ }
+
+ const SM={_o:false,_i:-1,_s:0,_q:'',_it:[],_pv:null,
init(){const e=document.getElementById('_slashMenu');if(!e)return;e.innerHTML='';
- const inp=document.createElement('input');inp.type='text';inp.placeholder='Filter...';
- inp.style.cssText='width:100%;padding:6px 10px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);font-size:14px;outline:none;box-sizing:border-box';
- inp.oninput=()=>SM.flt();inp.onkeydown=ev=>SM._kd(ev);
- const hdr=document.createElement('div');hdr.style.cssText='padding:8px;border-bottom:1px solid var(--border)';hdr.appendChild(inp);
- const list=document.createElement('div');list.style.cssText='overflow-y:auto;max-height:340px';
+ const list=document.createElement('div');list.className='sm-list';list.style.cssText='overflow-y:auto;flex:1;min-height:120px;max-height:360px;padding:6px';
list.addEventListener('click',ev=>{const it=ev.target.closest('.slash-item');if(it){ev.preventDefault();SM.sel(it.dataset.sid);}});
- e.appendChild(hdr);e.appendChild(list);
- SM._e=e;SM._in=inp;SM._li=list;
+ list.addEventListener('mouseover',ev=>{const it=ev.target.closest('.slash-item');if(it)SM._hover(it);});
+ const footer=document.createElement('div');footer.className='sm-footer';footer.style.cssText='display:flex;align-items:center;justify-content:space-between;padding:7px 12px;border-top:1px solid var(--border);font-size:12px;color:var(--text-dim);cursor:pointer';
+ footer.innerHTML='⌘ Close menuesc';
+ footer.onclick=()=>SM.close();
+ const inp=document.createElement('input');inp.type='text';inp.placeholder='Type to search';
+ inp.style.cssText='width:100%;padding:9px 12px;background:var(--bg-secondary);border:none;border-top:1px solid var(--border);color:var(--text-primary);font-size:14px;outline:none;box-sizing:border-box';
+ inp.oninput=()=>SM.flt();inp.onkeydown=ev=>SM._kd(ev);
+ e.appendChild(list);e.appendChild(footer);e.appendChild(inp);
+ SM._e=e;SM._in=inp;SM._li=list;SM._f=footer;
document.addEventListener('mousedown',ev=>{if(!e.contains(ev.target))SM.close();});
},
+ _hover(it){ if(!it) return; const id=it.dataset.sid; if(!id) return;
+ if(!SM._pv){ const d=document.createElement('div'); d.className='sm-preview'; d.style.cssText='position:fixed;z-index:1000;background:var(--bg-modal);border:1px solid var(--border-light);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.5);padding:12px;width:220px;pointer-events:none;'; document.body.appendChild(d); SM._pv=d; }
+ SM._pv.innerHTML=_previewHtml(id);
+ const r=SM._e.getBoundingClientRect(); const ir=it.getBoundingClientRect();
+ let left = r.right + 8; let top = ir.top - 12;
+ if(left+220 > window.innerWidth-8) left = r.left - 228;
+ top = Math.max(8, Math.min(top, window.innerHeight - SM._pv.offsetHeight - 8));
+ SM._pv.style.left=left+'px'; SM._pv.style.top=top+'px'; SM._pv.style.display='block';
+ },
open(i,el){if(!SM._e)SM.init();SM.close();SM._i=i;SM._s=0;SM._in.value='';SM.flt();
- const r=el.getBoundingClientRect();SM._e.style.display='block';
- SM._e.style.top=Math.max(4,(r.bottom+4>window.innerHeight-340)?r.top-344:r.bottom+4)+'px';
- SM._e.style.left=Math.min(r.left,window.innerWidth-324)+'px';
+ const r=el.getBoundingClientRect();SM._e.style.display='flex';
+ SM._e.style.top=Math.max(4,(r.bottom+4>window.innerHeight-360)?r.top-364:r.bottom+4)+'px';
+ SM._e.style.left=Math.min(r.left,window.innerWidth-340)+'px';
setTimeout(()=>SM._in.focus(),10);SM._o=true;},
- close(){if(SM._e)SM._e.style.display='none';SM._o=false;SM._in&&(SM._in.value='');},
+ close(){if(SM._e)SM._e.style.display='none';SM._o=false;SM._in&&(SM._in.value='');if(SM._pv)SM._pv.style.display='none';},
flt(){if(!SM._li)return;SM._it=[];const q=(SM._in.value||'').toLowerCase();SM._q=q;let h='';
for(const g of CMDS){let items=g.items;if(q)items=items.filter(c=>c.name.toLowerCase().includes(q)||c.id.includes(q));if(!items.length)continue;
- h+=`${g.name}
`;
+ h+=`${_GROUP_LABEL[g.name]||g.name}
`;
items.forEach(c=>{SM._it.push(c);
- h+=`
- ${c.icon}
- ${c.name}
`;});}
+ h+=`
+ ${c.icon}
+ ${c.name}
+ ${_SHORTCUT[c.id]||''}
`;});}
SM._li.innerHTML=h;},
_kd(e){if(e.key==='ArrowDown'){e.preventDefault();SM._s=Math.min(SM._s+1,SM._it.length-1);SM._rs();}
else if(e.key==='ArrowUp'){e.preventDefault();SM._s=Math.max(SM._s-1,0);SM._rs();}
@@ -215,6 +257,77 @@
sel(id){if(window.E)window.E.applySlash(id);SM.close();},
get isOpen(){return SM._o;}};
+ // ═══════════ Inline AI composer (Notion AI-style) ═══════════
+ // Space on an empty paragraph → "Edit with AI" pill → Enter → "Brewing…"
+ // → framed result → "Insert below" → applyAIBlocks.
+ const AIC={_open:false,_idx:-1,_stage:'',_html:'',
+ ROOT:null,
+ mk(){ if(this.ROOT) return this.ROOT;
+ const d=document.createElement('div'); d.id='_aiC'; d.style.cssText='position:fixed;z-index:1200;width:420px;max-width:92vw;display:none';
+ document.body.appendChild(d); this.ROOT=d; return d; },
+ open(idx){ const self=this; this._idx=idx; this._stage='input'; this.mk();
+ const r=this._anchorRect(idx); this.ROOT.style.left=Math.max(8,Math.min(r.left,window.innerWidth-440))+ 'px';
+ this.ROOT.style.top=Math.max(8,(r.bottom+6>window.innerHeight-260)?r.top-180:r.bottom+6)+'px';
+ this.paint(); this.ROOT.style.display='block'; this._open=true; this._focusIngest();
+ document.addEventListener('mousedown',function onDoc(ev){ if(!self.ROOT.contains(ev.target)){ self.close(); document.removeEventListener('mousedown',onDoc); } });
+ },
+ close(){ if(this.ROOT)this.ROOT.style.display='none'; this._open=false; this._html=''; if(window.E)window.E.dirty=true; this._stage=''; },
+ _anchorRect(idx){ const E=window.E; if(!E||!E.blocks)return{x:Math.max(24,window.innerWidth/2-210),bottom:window.innerHeight/2};
+ const b=E.blocks[idx]; const el=E.getEl(b?b.id:null); if(el){const r=el.getBoundingClientRect();return r;} return {left:24,top:120,bottom:180}; },
+ _focusIngest(){ const i=this.ROOT.querySelector('.aici-input'); if(i){i.focus();} },
+ _escTag(tag,s){return '<'+tag+'>'+s+''+tag+'>';},
+ _mdToHtml(md){ // compact block render (headings, lists, bold, paragraphs)
+ const lines=String(md||'').split(/\r?\n/); let out=[]; let i=0;
+ const inline=function(t){return esc(t).replace(/`([^`]+)`/g,'$1').replace(/\*\*([^*]+)\*\*/g,'$1').replace(/\*([^*]+)\*/g,'$1').replace(/\[([^\]]+)\]\((https?:[^)\s]+)\)/g,'$1');};
+ while(i'+inline(s.replace(/^#+\s+/,''))+''); i++; continue; }
+ if(/^[-*+] /.test(s)){ const items=[]; while(i'+inline(lines[i].trim().replace(/^[-*+] /,''))+''); i++;} out.push(''); continue; }
+ if(/^\d+\. /.test(s)){ const items=[]; while(i'+inline(lines[i].trim().replace(/^\d+\. /,''))+''); i++;} out.push(''+items.join('')+'
'); continue; }
+ if(s.startsWith('> ')){ out.push(''+inline(s.replace(/^> /,''))+'
'); i++; continue; }
+ out.push(''+inline(s)+'
'); i++;
+ }
+ return out.join(''); },
+ paint(){ const self=this; const h=this.ROOT;
+ if(this._stage==='input'){
+ h.innerHTML=''
+ +'✨'
+ +''
+ +''
+ +'
';
+ const inp=h.querySelector('.aici-input'); if(inp){inp.focus(); const go=function(){const v=inp.value.trim(); if(v){self._idx=self._idx>=0?self._idx:0; self._run(v);} }; inp.onkeydown=function(ev){ if(ev.key==='Enter'){ev.preventDefault();go();} else if(ev.key==='Escape'){ev.preventDefault();self.close();} }; h.querySelector('.aici-send').onclick=go; }
+ } else if(this._stage==='brewing'){
+ h.innerHTML=''
+ +'✨Brewing…'
+ +'
';
+ h.querySelector('.aici-stop').onclick=function(){ self.close(); if(window.fdAgent&&window.fdAgent.cancel)window.fdAgent.cancel(); };
+ } else if(this._stage==='result'){
+ h.innerHTML=''
+ +'
'+this._html+'
'
+ +'
'
+ +''
+ +''
+ +''
+ +''
+ +'
';
+ const self2=this; h.querySelector('.aici-insert').onclick=function(){ self2.insert(); }; h.querySelectorAll('.aici-fb').forEach(function(b){ b.onclick=function(){ self2.toast(b.dataset.fb==='up'?'👍 Merci':'👎 Enregistré'); }; });
+ }
+ },
+ _run(prompt){ const self=this; this._stage='brewing'; this.paint();
+ const E=window.E; const ctx=(typeof window.fdGetPageContext==='function')?String(window.fdGetPageContext()||''):'';
+ if(!(window.fdAgent&&window.fdAgent.generate)){ this._err('Agent IA non configuré'); return; }
+ window.fdAgent.generate(prompt, ctx).then(function(text){
+ self._html=self._mdToHtml(text||''); self._stage='result'; self.paint();
+ }).catch(function(err){ self._err((err&&err.message)?err.message:'Erreur'); });
+ },
+ _err(msg){ this._html='⚠ '+esc(msg)+'
'; this._stage='result'; this.paint(); },
+ insert(){ const E=window.E; if(!E||typeof E.applyAIBlocks!=='function')return; E.sync();
+ const result=this._resultText(); if(!result)return; E.applyAIBlocks(result); E.dirty=true; E.autoSave(); this.close(); },
+ _resultText(){ const el=this.ROOT&&this.ROOT.querySelector('.aici-result'); return el?el.textContent:''; },
+ toast(m){ if(window.showToast)window.showToast(m,'info'); }
+ };
+ window.AIC=AIC;
+
function renderBlock(b,idx){
if(b.type==='meeting')return renderMeetingBlock(b,idx);
if(b.type==='table')return renderTableBlock(b,idx);
@@ -232,7 +345,7 @@
return `{{ fd_icon("paperclip",14) }}
${esc(b.file_name||'File')}
${szStr} · ${esc(b.file_mime||'')}
⬇ Download `;
}
}
- const ph=b.type==='heading_1'?'Heading 1':b.type==='heading_2'?'Heading 2':b.type==='heading_3'?'Heading 3':b.type==='heading_4'?'Heading 4':b.type==='bulleted_list'?'List':b.type==='numbered_list'?'List':b.type==='to_do'?'To-do':b.type==='toggle'?'Toggle list':b.type==='quote'?'Empty quote':b.type==='code'?'Type code...':b.type==='callout'?'Type something...':'Press \'/\' for commands...';
+ const ph=b.type==='heading_1'?'Heading 1':b.type==='heading_2'?'Heading 2':b.type==='heading_3'?'Heading 3':b.type==='heading_4'?'Heading 4':b.type==='bulleted_list'?'List':b.type==='numbered_list'?'List':b.type==='to_do'?'To-do':b.type==='toggle'?'Toggle list':b.type==='quote'?'Empty quote':b.type==='code'?'Type code...':b.type==='callout'?'Type something...':(b.type==='paragraph'&&!b.content?'Press \'space\' for AI or \'/\' for commands':'Press \'/\' for commands...');
let inner='';
if(b.type==='to_do')inner=`${mdEsc(b.content)}
`;
else if(b.type==='toggle')inner=`${(b.children||[]).map((ch)=>renderNestedBlock(ch)).join('')}
`;
@@ -980,7 +1093,12 @@ applyAIBlocks(text){
return;
}
if(e.key.length===1||e.key==='Backspace'||e.key==='Delete'){const i2=idx;setTimeout(()=>{const e2=this.getEl(this.blocks[i2]?.id);if(e2&&e2.textContent?.trim()==='/'&&!SM.isOpen)SM.open(i2,e2);},15);}
- if(e.key===' '){const i2=idx;setTimeout(()=>this.checkMd(i2),15);}
+ if(e.key===' '){
+ if(block.type==='paragraph'&&!(block.content||'').trim()&&!(el.textContent||'').trim()&&!SM.isOpen&&window.AIC){
+ e.preventDefault(); window.AIC.open(idx); return;
+ }
+ const i2=idx;setTimeout(()=>this.checkMd(i2),15);
+ }
if(e.key==='Enter'){
if(e.shiftKey){this.dirty=true;this.autoSave();return;}
e.preventDefault();
diff --git a/static/css/app.css b/static/css/app.css
index 094dc3c..3757073 100644
--- a/static/css/app.css
+++ b/static/css/app.css
@@ -4575,3 +4575,22 @@ button.breadcrumb-current { max-width: 260px; }
.ftable-editor .ftable-rowbar { opacity: 1; }
.ftable-editor .ftable-addcol { opacity: 1; }
}
+
+/* ── v5.2.0: Notion AI-style block editor ── */
+@keyframes ai-pulse { 0%,100%{opacity:.45} 50%{opacity:1} }
+.slash-menu { flex-direction: column; }
+.slash-menu .sm-footer:hover { background: var(--bg-hover); }
+.slash-item.selected { background: var(--bg-hover); }
+.slash-item-hint { font-size: 12px; color: var(--text-dim); opacity: .7; }
+.sm-preview { font-family: inherit; }
+#_aiC { font-family: inherit; }
+.aici-result p { margin: 0 0 8px; }
+.aici-result h1,.aici-result h2,.aici-result h3,.aici-result h4 { margin: 10px 0 6px; font-weight: 700; }
+.aici-result h1{font-size:1.4em}.aici-result h2{font-size:1.25em}.aici-result h3{font-size:1.1em}.aici-result h4{font-size:1em}
+.aici-result ul,.aici-result ol { margin: 0 0 8px; padding-left: 22px; }
+.aici-result li { margin: 2px 0; }
+.aici-result code { background: rgba(127,127,127,.18); border-radius: 4px; padding: 1px 5px; font-size: .9em; font-family: ui-monospace,SFMono-Regular,Menlo,monospace; }
+.aici-result blockquote { border-left: 3px solid var(--accent,#2383E2); margin: 8px 0; padding: 2px 12px; color: var(--text-dim); }
+.aici-result a { color: var(--accent,#2383E2); }
+.aici-fb:disabled { opacity:.4; cursor:default; }
+@media (max-width:768px){ #_aiC { width: 92vw; } }