Files
flowdeck/app/templates/_page_editor_scripts.html
T
bruno 6fe5d2f723 feat(v5.10.0): v5.4.0 + v5.5.0 features — backlinks, page/collection duplication, trash purge, version history, markdown import, embed/bookmark/video/audio blocks, cover & icon, OG metadata
- Migration v7: page_versions table + pages.cover_url/page_icon columns
- Trash service (purge_expired 30-day) + daily scheduler
- Board API: duplicate page, backlinks, versions (snapshot/list/restore), cover/icon upload, markdown/file import
- Collections API: duplicate collection (deep copy properties/views/pages/data sources)
- Export service: render embed/bookmark/video/audio in markdown/HTML/PDF/public
- Public page renderer: cover image + icon
- Editor: slash commands for media blocks, lightbox, version history UI, backlinks panel, cover/icon picker, import modal
- OG metadata endpoint for bookmark cards
- 386 tests passing
2026-09-11 08:56:48 -04:00

2053 lines
164 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script type="application/json" id="page-data">
{{ page_data | tojson }}
</script>
<script>
/* eslint-disable */
const CMDS=[{name:'BASIC',items:[
{id:'paragraph',name:'Text',icon:'¶'},{id:'heading_1',name:'Heading 1',icon:'H1'},{id:'heading_2',name:'Heading 2',icon:'H2'},
{id:'heading_3',name:'Heading 3',icon:'H3'},{id:'heading_4',name:'Heading 4',icon:'H4'},{id:'bulleted_list',name:'Bulleted list',icon:'•'},{id:'numbered_list',name:'Numbered list',icon:'1.'},
{id:'to_do',name:'To-do',icon:'☐'},{id:'toggle',name:'Toggle',icon:'▶'},{id:'quote',name:'Quote',icon:'❝'},
{id:'callout',name:'Callout',icon:'💡'},{id:'table_of_contents',name:'Table of contents',icon:'☰'},{id:'math',name:'Math equation',icon:'∑'},
{id:'columns',name:'Column list',icon:'▮'},{id:'divider',name:'Divider',icon:'—'},{id:'code',name:'Code block',icon:'<>'},
{id:'table',name:'Table',icon:'⊞'},{id:'button',name:'Button / Automation',icon:'⚡'},
]},{name:'AI',items:[
{id:'ai_write',name:'Write with AI',icon:'✨'},{id:'ai_summarize',name:'Summarize',icon:'📝'},
{id:'ai_translate',name:'Translate',icon:'🌐'},{id:'ai_continue',name:'Continue writing',icon:'➡️'},
]},
{name:'MEDIA',items:[
{id:'image',name:'Image',icon:'🖼️'},{id:'embed',name:'Embed',icon:'🔗'},
{id:'bookmark',name:'Bookmark',icon:'🔖'},{id:'video',name:'Video',icon:'🎬'},
{id:'audio',name:'Audio',icon:'🎵'},
]},
{name:'DATA',items:[{id:'database',name:'Database / Inline database',icon:'📊'}]},
{name:'ACTIONS',items:[{id:'duplicate',name:'Duplicate',icon:'▣'},{id:'turn_into',name:'Turn into…',icon:'↩'},{id:'copy_link',name:'Copy link to block',icon:'🔗'},{id:'delete',name:'Delete block',icon:'✕'}]}];
let _bid=0;function genId(){return 'b'+(++_bid)+'_'+Date.now().toString(36);}
function cp(e){const s=window.getSelection();if(!s.rangeCount)return 0;const r=s.getRangeAt(0).cloneRange();r.selectNodeContents(e);r.setEnd(s.getRangeAt(0).endContainer,s.getRangeAt(0).endOffset);return r.toString().length;}
function gt(e){return (e&&e.innerText!==undefined)?e.innerText:(e?e.textContent:'');}
function splitCaret(e){
const s=window.getSelection(); if(!s.rangeCount||!s.rangeCount)return null; const sel=s.getRangeAt(0);
const MK='\uF000FD'; const mark=document.createElement('span'); mark.textContent=MK;
sel.insertNode(mark);
const full=gt(e)||'';
const parts=full.split(MK);
mark.remove(); e.normalize();
return {before:parts[0]||'',after:(parts.length>1?parts[1]:'')||'',content:full.split(MK).join('')};
}
function ce(e){const r=document.createRange();r.selectNodeContents(e);r.collapse(false);window.getSelection().removeAllRanges();window.getSelection().addRange(r);}
function cs(e){const r=document.createRange();r.selectNodeContents(e);r.collapse(true);window.getSelection().removeAllRanges();window.getSelection().addRange(r);}
function esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
// Escape then render inline markdown (bold/italic/code/links/strikethrough)
// so agent-generated content displays without raw `**`/backtick characters.
function mdEsc(s){
if(!s) return '';
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/`([^`]+)`/g,'<code>$1</code>')
.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>')
.replace(/\*([^*]+)\*/g,'<em>$1</em>')
.replace(/~~([^~]+)~~/g,'<s>$1</s>')
.replace(/\[([^\]]+)\]\((https?:[^)\s]+)\)/g,'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
.replace(/\n/g,'<br>');
}
const CALLOUT_ICONS=['💡','💡','📌','⚠️','ℹ️','✅','❌','🚀','🔔','📣','⭐','❤️','🔥','💬','👀','🧠','🎯','🔑','📅','💰','💡','📚','🧪','🌍'];
/* ═══════════ Simple Table block (v4.8.0) ═══════════ */
function _tblSplitLine(s){s=(s||'').trim();if(s.charAt(0)==='|')s=s.slice(1);if(s.charAt(s.length-1)==='|')s=s.slice(0,-1);return s.split('|').map(function(x){return x.trim();});}
function _tblIsSep(s){s=(s||'').trim();if(!s||s.charAt(0)!=='|')return false;var cells=_tblSplitLine(s);if(!cells.length)return false;return cells.every(function(c){return /^:?-{1,}:?$/.test(c);});}
function _tblIsData(s){s=(s||'').trim();return /^\|/.test(s)&&s.indexOf('|',1)>=0;}
function _tblNew(rows,cols){var out=[];for(var i=0;i<rows;i++){var r=new Array(cols);for(var j=0;j<cols;j++)r[j]='';out.push(r);}return out;}
function _tblClone(b){b.rows=(b.rows||[]).map(function(r){return (r||[]).slice();});return b;}
function _tblAddCol(b,index){_tblClone(b);var w=b.rows.length?b.rows[0].length:1;if(index<0)index=0;if(index>w)index=w;b.rows.forEach(function(r){r.splice(index,0,'');});}
function _tblAddRow(b,index){_tblClone(b);var w=b.rows.length&&b.rows[0]?b.rows[0].length:1;if(index<0)index=0;if(index>b.rows.length)index=b.rows.length;b.rows.splice(index,0,_tblNew(1,w)[0]);}
function _tblDupCol(b,index){_tblClone(b);b.rows.forEach(function(r){var v=(index<r.length)?r[index]:'';var at=index+1>r.length?r.length:index+1;r.splice(at,0,v);});}
function _tblDupRow(b,index){_tblClone(b);if(index<0||index>b.rows.length)return;b.rows.splice(index+1,0,(b.rows[index]||[]).slice());}
function _tblClearCol(b,index){_tblClone(b);b.rows.forEach(function(r){if(r&&index<r.length)r[index]='';});}
function _tblClearRow(b,index){_tblClone(b);var w=b.rows[0]?b.rows[0].length:1;if(index>=0&&index<b.rows.length)b.rows[index]=_tblNew(1,w)[0];}
function _tblDelCol(b,index){_tblClone(b);if(!b.rows.length)return;if(b.rows[0].length<=1)return;b.rows.forEach(function(r){if(index>=0&&index<r.length)r.splice(index,1);});}
function _tblDelRow(b,index){_tblClone(b);if(b.rows.length<=1)return;if(index>=0&&index<b.rows.length)b.rows.splice(index,1);}
function _tblGrip(){return '<svg viewBox="0 0 16 16" width="12" height="12" style="display:block"><circle cx="4" cy="3" r="1.4"/><circle cx="11" cy="3" r="1.4"/><circle cx="4" cy="8" r="1.4"/><circle cx="11" cy="8" r="1.4"/><circle cx="4" cy="13" r="1.4"/><circle cx="11" cy="13" r="1.4"/></svg>';}
function _tblDefault(){return {rows:_tblNew(3,3)};}
function _tblFocus(bid,r,c){var sel=document.querySelector('.ftable-editor[data-bid="'+bid+'"]');if(!sel)return;var q='[data-c="'+c+'"]';var cell=sel.querySelector(q);if(!cell){var first=sel.querySelector('.ftable-cell[data-r]');if(first){first.focus();ce(first);}return;}cell.focus();if(r===undefined)cs(cell);else ce(cell);}
function renderTableBlock(b,idx){
var rows=(b&&Array.isArray(b.rows)&&b.rows.length)?b.rows:_tblNew(2,2);
var w=0;rows.forEach(function(r){if(r&&r.length>w)w=r.length;});if(w<1)w=2;
var widths=(b&&Array.isArray(b.colsW))?b.colsW:[];
var bid=b.id;var h='';
var hasHeader=(b.has_header!==false);var firstCol=(!!b.first_col_header);
h+='<div class="block-wrapper" data-id="'+bid+'"><div class="block-content ftable-editor" data-bid="'+bid+'">';
h+='<div class="ftable-colbar">';
for(var c=0;c<w;c++){
h+='<div class="ftable-colcell"><button type="button" class="ftable-colgrip" data-tbl="colmenu" data-bid="'+bid+'" data-col="'+c+'" title="Column options">'+_tblGrip()+'</button></div>';
}
h+='</div>';
h+='<table class="ftable ftable-edit"><tbody>';
for(var ri=0;ri<rows.length;ri++){
var isHead=(ri===0&&hasHeader)||(ri>0&&firstCol);var tag='td';
if(isHead){tag='th';}
h+='<tr class="ftable-row">';
for(var ci=0;ci<w;ci++){
var rcH=((ri===0&&hasHeader)||(ci===0&&firstCol));
var v=rows[ri]&&ci<rows[ri].length?rows[ri][ci]:'';
var wpx=(widths[ci]&&widths[ci]>0)?(' style="width:'+widths[ci]+'px"'):'';
h+='<'+tag+' class="ftable-cell'+(rcH?' head':'')+'" contenteditable="true" data-r="'+ri+'" data-c="'+ci+'"'+wpx+' spellcheck="false">'+esc(v);
if(ri===0&&hasHeader)h+='<span class="ftable-resize" data-col="'+ci+'" title="Drag to resize column"></span>';
h+='</'+tag+'>';
}
h+='</tr>';
}
h+='</tbody></table>';
h+='<div class="ftable-rowbar"><button type="button" class="ftable-addrow" data-tbl="rowadd" data-bid="'+bid+'" title="Click to add a row">+ Row</button><button type="button" class="ftable-addrow" data-tbl="coladdright" data-bid="'+bid+'" title="Add column on the right">+ Col</button><span class="ftable-hdr-toggle"><label><input type="checkbox" data-tbl="header" data-bid="'+bid+'" '+(hasHeader?'checked':'')+'> Header row</label></span><span class="ftable-hdr-toggle"><label><input type="checkbox" data-tbl="firstcol" data-bid="'+bid+'" '+(firstCol?'checked':'')+'> First col</label></span></div>';
h+='</div></div>';
return h;
}
function _tblBlockById(bid){var e=window.E;if(!e||!e.blocks)return null;for(var i=0;i<e.blocks.length;i++){if(e.blocks[i].id===bid)return e.blocks[i];}return null;}
function _tblIdxByBid(bid){var e=window.E;if(!e||!e.blocks)return -1;for(var i=0;i<e.blocks.length;i++){if(e.blocks[i].id===bid)return i;}return -1;}
function _tblMenuClose(){var m=document.getElementById('_tblMenu');if(m){m.parentNode&&m.parentNode.removeChild(m);}if(window._tblCur)window._tblCur=null;}
function _tblMenuOpen(x,y,bid,kind,index){
_tblMenuClose();
var items=[];
if(kind==='col'){
items=[['col-insert-left','Insert left'],['col-insert-right','Insert right'],['col-dup','Duplicate'],null,['col-clear','Clear contents'],['col-del','Delete column',1],null,['tbl-del','Delete table',1]];
}else{
items=[['row-insert-above','Insert above'],['row-insert-below','Insert below'],['row-dup','Duplicate'],null,['row-clear','Clear row'],['row-del','Delete row',1],null,['tbl-del','Delete table',1]];
}
var d=document.createElement('div');d.className='ftable-menu';d.id='_tblMenu';d.style.cssText='position:fixed;z-index:3000;';
var html='';
items.forEach(function(it){
if(!it){html+='<div class="ftable-menu-sep"></div>';return;}
html+='<div class="ftable-menu-item'+(it[2]?' danger':'')+'" data-tbl-act="'+it[0]+'">'+it[1]+'</div>';
});
d.innerHTML=html;
document.body.appendChild(d);
var r=d.getBoundingClientRect();
x=Math.min(Math.max(8,x),window.innerWidth-r.width-8);
y=Math.min(Math.max(8,y),window.innerHeight-r.height-8);
d.style.left=x+'px';d.style.top=y+'px';
window._tblCur={bid:bid,kind:kind,index:index};
}
function _tblDo(action,cur){
if(!cur)return;
var bid=cur.bid,index=cur.index,b=_tblBlockById(bid),e=window.E;
if(!b||!e)return;
e.sync();
if(action!=='tbl-del')e.pushHistory();
if(!(b.rows&&b.rows.length))b.rows=_tblNew(2,2);
var focusR=1,focusC=0;
if(action==='col-insert-left'){_tblAddCol(b,index);focusC=index;}
else if(action==='col-insert-right'){_tblAddCol(b,index+1);focusC=index+1;}
else if(action==='col-dup'){_tblDupCol(b,index);focusC=index+1;}
else if(action==='col-clear'){_tblClearCol(b,index);focusC=index;}
else if(action==='col-del'){_tblDelCol(b,index);focusC=Math.min(index,b.rows[0].length-1);}
else if(action==='row-insert-above'){_tblAddRow(b,index);focusR=index;}
else if(action==='row-insert-below'){_tblAddRow(b,index+1);focusR=index+1;}
else if(action==='row-dup'){_tblDupRow(b,index);focusR=index+1;}
else if(action==='row-clear'){_tblClearRow(b,index);focusR=index;}
else if(action==='row-del'){_tblDelRow(b,index);focusR=Math.min(index,b.rows.length-1);if(focusR<1)focusR=1;}
else if(action==='tbl-del'){_tblMenuClose();var bi=_tblIdxByBid(bid);if(bi>=0)e.removeBlock(bi);return;}
e.render();e.dirty=true;e.autoSave();
_tblFocus(bid,focusR,focusC);
}
function _tblResizeStart(ev){
var sp=ev.target&&ev.target.closest?ev.target.closest('.ftable-resize'):null;if(!sp)return;
var th=sp.parentElement;if(!th)return;
var wrap=th.closest('.ftable-editor');if(!wrap)return;
var bid=wrap.getAttribute('data-bid');var col=parseInt(sp.getAttribute('data-col')||'0',10);
var b=_tblBlockById(bid);if(!b)return;
ev.preventDefault();ev.stopPropagation();
var E=window.E;if(E&&E.sync)E.sync();
var widths=(b.colsW&&Array.isArray(b.colsW))?b.colsW.slice():[];
while(widths.length<=col)widths.push(null);
var startX=ev.clientX;var base=widths[col]||th.getBoundingClientRect().width;
document.body.style.cursor='col-resize';document.body.style.userSelect='none';
function move(me){var dx=me.clientX-startX;var nw=Math.max(48,Math.min(760,base+dx));th.style.width=Math.round(nw)+'px';widths[col]=Math.round(nw);}
function up(me){
document.removeEventListener('mousemove',move);document.removeEventListener('mouseup',up);
document.body.style.cursor='';document.body.style.userSelect='';
move(me);
b.colsW=widths;
if(E){E.dirty=true;E.autoSave();}
}
document.addEventListener('mousemove',move);
document.addEventListener('mouseup',up);
}
function _tblInitDelegate(){
if(window._tblDelegate)return;window._tblDelegate=true;
document.addEventListener('click',function(ev){
if(ev.target.closest&&ev.target.closest('#_tblMenu')){var it=ev.target.closest('.ftable-menu-item');if(it){var act=it.getAttribute('data-tbl-act');_tblDo(act,window._tblCur);}return;}
var g=ev.target.closest?ev.target.closest('[data-tbl]'):null;
if(g){var act=g.getAttribute('data-tbl'),bid=g.getAttribute('data-bid'),col=parseInt(g.getAttribute('data-col')||'0',10);
var gr=g.getBoundingClientRect?g.getBoundingClientRect():null;
if(act==='colmenu'){_tblMenuOpen(gr?gr.left:ev.clientX,(gr?gr.bottom:ev.clientY)+4,bid,'col',col);ev.preventDefault();}
else if(act==='colplus'){var b=_tblBlockById(bid);if(b){window.E.sync();_tblAddCol(b,col+1);window.E.render();window.E.dirty=true;window.E.autoSave();_tblFocus(bid,1,col+1);}}
else if(act==='rowadd'){var b2=_tblBlockById(bid);if(b2){window.E.sync();var n=b2.rows.length;_tblAddRow(b2,n);window.E.render();window.E.dirty=true;window.E.autoSave();_tblFocus(bid,n,0);}}
else if(act==='coladdright'){var b4=_tblBlockById(bid);if(b4){window.E.sync();var cw=(b4.rows&&b4.rows[0])?b4.rows[0].length:1;_tblAddCol(b4,cw);window.E.render();window.E.dirty=true;window.E.autoSave();_tblFocus(bid,1,cw);}}
else if(act==='header'||act==='firstcol'){var b5=_tblBlockById(bid);if(b5){window.E.sync();window.E.pushHistory();if(act==='header')b5.has_header=g.checked?true:false;else b5.first_col_header=g.checked?true:false;window.E.render();window.E.dirty=true;window.E.autoSave();_rtSync();}}
return;
}
if(document.getElementById('_tblMenu')&&!(ev.target.closest&&ev.target.closest('[data-tbl]')))_tblMenuClose();
});
document.addEventListener('contextmenu',function(ev){
var cell=ev.target.closest?ev.target.closest('.ftable-cell'):null;
if(!cell)return;
var w=cell.closest('.ftable-editor');if(!w)return;
var bid=w.getAttribute('data-bid'),r=parseInt(cell.getAttribute('data-r')||'0',10),c=parseInt(cell.getAttribute('data-c')||'0',10);
ev.preventDefault();
if(r===0)_tblMenuOpen(ev.clientX,ev.clientY,bid,'col',c);
else _tblMenuOpen(ev.clientX,ev.clientY,bid,'row',r);
});
document.addEventListener('mousedown',function(ev){
if(ev.target&&ev.target.closest&&ev.target.closest('.ftable-resize')){_tblResizeStart(ev);return;}
if(document.getElementById('_tblMenu')&&!(ev.target.closest&&ev.target.closest('#_tblMenu')))_tblMenuClose();
});
}
// ── 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]',embed:'[🔗]',bookmark:'[🔖]',video:'[🎬]',audio:'[🎵]',database:'DB',ai_write:'/ai',ai_summarize:'/ai',ai_translate:'/ai',ai_continue:'/ai',duplicate:'Ctrl+D',turn_into:'⋯',copy_link:'#',delete:'⌫'};
const _GROUP_LABEL={BASIC:'Basic blocks',AI:'AI writing',MEDIA:'Media',DATA:'Data',ACTIONS:'Actions'};
// ── Hover preview card (matches Notion's visual hint) ──
function _previewHtml(blockId){
const p=function(inner,cls){return '<div class="slash-prev" style="padding:0">'+inner+'</div>';};
switch(blockId){
case 'heading_1': return p('<div style="font-size:24px;font-weight:700">Big section heading</div>');
case 'heading_2': return p('<div style="font-size:19px;font-weight:700;margin-bottom:8px">Our Values</div><div style="font-size:13px;line-height:1.6">• Ownership<br>• Altruism<br>• Integrity</div><div style="font-size:11px;color:var(--text-dim);margin-top:8px">Medium section heading</div>');
case 'heading_3': return p('<div style="font-size:16px;font-weight:700;margin-bottom:6px">Small section heading</div><div style="font-size:13px;color:var(--text-dim)">Body text goes here…</div >');
case 'heading_4': return p('<div style="font-size:14px;font-weight:700">Tiny section heading</div>');
case 'bulleted_list': return p('<div style="font-size:13px;line-height:1.9">• List item<br>• Another item<br>• Third item</div>');
case 'numbered_list': return p('<div style="font-size:13px;line-height:1.9">1. First<br>2. Second<br>3. Third</div>');
case 'to_do': return p('<div style="font-size:13px;line-height:1.9">☐ Todo item<br>☑ Checked todo<br>☐ Another</div>');
case 'toggle': return p('<div style="font-size:13px">▸ Toggle content</div>');
case 'quote': return p('<div style="border-left:3px solid var(--accent);padding-left:10px;font-size:13px;color:var(--text-dim)">A blockquote to highlight a quotation.</div>');
case 'callout': return p('<div style="background:var(--blue-bg);padding:12px;border-radius:6px;font-size:13px">💡 A callout draws attention.</div>');
case 'table_of_contents': return p('<div style="font-size:13px;color:var(--text-dim)">On this page</div><div style="font-size:12px;line-height:1.8">· Heading 1<br>· Heading 2<br>· Heading 3</div>');
case 'math': return p('<div style="font-size:15px;text-align:center">E = mc²</div>');
case 'columns': return p('<div style="display:flex;gap:8px"><div style="flex:1;border:1px solid var(--border);border-radius:4px;padding:8px;font-size:12px">col 1</div><div style="flex:1;border:1px solid var(--border);border-radius:4px;padding:8px;font-size:12px">col 2</div></div>');
case 'divider': return p('<hr style="border-top:1px solid var(--border);margin:6px 0">');
case 'code': return p('<pre style="background:#111;padding:10px;border-radius:6px;font-size:12px"><code>const x = 1;</code></pre>');
case 'table': return p('<div style="font-size:12px"><table style="width:100%;border-collapse:collapse"><tr><td style="border:1px solid var(--border);padding:4px">A</td><td style="border:1px solid var(--border);padding:4px">B</td></tr><tr><td style="border:1px solid var(--border);padding:4px">1</td><td style="border:1px solid var(--border);padding:4px">2</td></tr></table></div>');
case 'image': return p('<div style="border:1px solid var(--border);border-radius:6px;padding:14px;text-align:center;color:var(--text-dim);font-size:12px">[Image]</div>');
case 'embed': return p('<div style="border:1px solid var(--border);border-radius:6px;padding:14px;text-align:center;color:var(--text-dim);font-size:12px">🔗 Embed (YouTube, Figma, Maps…)</div>');
case 'bookmark': return p('<div style="border:1px solid var(--border);border-radius:6px;padding:14px;text-align:center;color:var(--text-dim);font-size:12px">🔖 Bookmark card with preview</div>');
case 'video': return p('<div style="border:1px solid var(--border);border-radius:6px;padding:14px;text-align:center;color:var(--text-dim);font-size:12px">🎬 Inline video player</div>');
case 'audio': return p('<div style="border:1px solid var(--border);border-radius:6px;padding:14px;text-align:center;color:var(--text-dim);font-size:12px">🎵 Inline audio player</div>');
case 'database': return p('<div style="border:1px solid var(--border);border-radius:6px;padding:10px;font-size:12px">📊 Inline database table</div>');
default: return p('<div style="font-size:13px;color:var(--text-dim)">Text block</div>');
}
}
const SM={_o:false,_i:-1,_s:0,_q:'',_it:[],_pv:null,
init(){const e=document.getElementById('_slashMenu');if(!e)return;e.innerHTML='';
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);}});
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='<span style="display:flex;align-items:center;gap:8px"><span style="opacity:.6">⌘</span> Close menu</span><span style="color:var(--text-dim);opacity:.7">esc</span>';
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();
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();});
if(!SM._doc){
SM._doc=true;
document.addEventListener('keydown',function(ev){
if(!SM._o)return;
if(ev.metaKey||ev.ctrlKey||ev.altKey)return;
if(ev.key==='ArrowDown'||ev.key==='ArrowUp'||ev.key==='Enter'||ev.key==='Escape'){
ev.preventDefault();ev.stopPropagation();SM._docKd(ev);
}
});
}
SM._docKd=function(ev){
if(SM._o&&(ev.key==='ArrowDown'||ev.key==='ArrowUp'||ev.key==='Enter'||ev.key==='Escape'))SM._kd(ev);
};
},
_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='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';
SM._rs();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='');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+=`<div class="sm-group" style="padding:6px 10px 3px;font-size:11px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:.4px">${_GROUP_LABEL[g.name]||g.name}</div>`;
items.forEach(c=>{SM._it.push(c);
h+=`<div class="slash-item" data-sid="${c.id}" style="display:flex;align-items:center;gap:10px;padding:7px 10px;font-size:14px;color:var(--text-primary);cursor:pointer;border-radius:5px">
<span class="slash-item-icon" style="width:26px;height:26px;display:flex;align-items:center;justify-content:center;font-size:15px;background:var(--bg-secondary);border-radius:4px;flex-shrink:0">${c.icon}</span>
<span class="slash-item-name" style="flex:1;font-weight:500">${c.name}</span>
<span class="slash-item-hint" style="font-size:12px;color:var(--text-dim);font-family:var(--font-mono);opacity:.7">${_SHORTCUT[c.id]||''}</span></div>`;});}
SM._li.innerHTML=h;SM._rs();},
_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();}
else if(e.key==='Enter'){e.preventDefault();const c=SM._it[SM._s];if(c)SM.sel(c.id);}
else if(e.key==='Escape'){e.preventDefault();SM.close();}},
_rs(){const its=SM._li.querySelectorAll('.slash-item');its.forEach((el,i)=>{el.classList.toggle('selected',i===SM._s);});const cur=its[SM._s];if(cur&&cur.scrollIntoView)cur.scrollIntoView({block:'nearest'});},
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);
const w=Math.max(r.width||0,280); this.ROOT.style.width=Math.min(w,1200)+'px';
this.ROOT.style.left=Math.max(8,Math.min(r.left,window.innerWidth-r.width-16))+ '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{left:Math.max(24,window.innerWidth/2-210),top:window.innerHeight/2,bottom:window.innerHeight/2,width:420};
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,width:420}; },
_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,'<code>$1</code>').replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/\*([^*]+)\*/g,'<em>$1</em>').replace(/\[([^\]]+)\]\((https?:[^)\s]+)\)/g,'<a href="$2" target="_blank">$1</a>');};
while(i<lines.length){ const t=lines[i].replace(/\r$/,''); const s=t.trim();
if(!s){i++;continue;}
if(/^#{1,4}\s/.test(s)){ const lvl=s.match(/^(#+)\s/)[1].length; out.push('<h'+lvl+'>'+inline(s.replace(/^#+\s+/,''))+'</h'+lvl+'>'); i++; continue; }
if(/^[-*+] /.test(s)){ const items=[]; while(i<lines.length&&/^[-*+] /.test(lines[i].trim())){items.push('<li>'+inline(lines[i].trim().replace(/^[-*+] /,''))+'</li>'); i++;} out.push('<ul>'+items.join('')+'</ul>'); continue; }
if(/^\d+\. /.test(s)){ const items=[]; while(i<lines.length&&/^\d+\. /.test(lines[i].trim())){items.push('<li>'+inline(lines[i].trim().replace(/^\d+\. /,''))+'</li>'); i++;} out.push('<ol>'+items.join('')+'</ol>'); continue; }
if(s.startsWith('> ')){ out.push('<blockquote>'+inline(s.replace(/^> /,''))+'</blockquote>'); i++; continue; }
out.push('<p>'+inline(s)+'</p>'); i++;
}
return out.join(''); },
paint(){ const self=this; const h=this.ROOT;
if(this._stage==='input'){
h.innerHTML='<div style="display:flex;align-items:center;gap:10px;background:var(--bg-modal);border:1px solid var(--border-light);border-radius:999px;padding:8px 14px;box-shadow:0 8px 30px rgba(0,0,0,.45)">'
+'<span style="font-size:18px;color:var(--text-primary)">✨</span>'
+'<input class="aici-input" type="text" placeholder="Edit with AI" autocomplete="off" spellcheck="false" style="flex:1;background:none;border:none;outline:none;color:var(--text);font-size:14px">'
+'<button class="aici-send" title="Generate" style="background:var(--bg-secondary);border:none;border-radius:50%;width:26px;height:26px;color:var(--text);cursor:pointer;font-size:14px">↑</button>'
+'</div>';
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='<div style="display:flex;align-items:center;gap:12px;background:var(--bg-modal);border:1px solid var(--border-light);border-radius:14px;padding:12px 16px;box-shadow:0 8px 30px rgba(0,0,0,.45)">'
+'<span style="font-size:20px">✨</span><span style="color:var(--text-dim);font-size:14px;animation:ai-pulse 1.2s ease-in-out infinite">Brewing…</span>'
+'<button class="aici-stop" title="Stop" style="margin-left:auto;background:var(--bg-secondary);border:none;border-radius:6px;width:26px;height:26px;color:var(--text);cursor:pointer">■</button></div>';
h.querySelector('.aici-stop').onclick=function(){ self.close(); if(window.fdAgent&&window.fdAgent.cancel)window.fdAgent.cancel(); };
} else if(this._stage==='result'){
h.innerHTML='<div style="background:var(--bg-modal);border:1px solid var(--border-light);border-radius:14px;box-shadow:0 10px 40px rgba(0,0,0,.5);overflow:hidden">'
+'<div class="aici-result" style="padding:14px 16px;font-size:14px;line-height:1.6;color:var(--text);max-height:300px;overflow-y:auto">'+this._html+'</div>'
+'<div style="display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--border)">'
+'<button class="aici-fb" data-fb="up" title="Good" style="background:none;border:none;color:var(--text-dim);cursor:pointer">👍</button>'
+'<button class="aici-fb" data-fb="down" title="Bad" style="background:none;border:none;color:var(--text-dim);cursor:pointer">👎</button>'
+'<span style="flex:1"></span>'
+'<button class="aici-insert" style="background:var(--accent,#2383E2);color:#fff;border:none;border-radius:8px;padding:6px 16px;font-size:13px;cursor:pointer">Insert below</button>'
+'</div></div>';
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='<p style="color:#e5484d">⚠ '+esc(msg)+'</p>'; 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'); if(!el)return ''; const r=document.createRange();r.selectNodeContents(el);return r.toString(); },
toast(m){ if(window.showToast)window.showToast(m,'info'); }
};
window.AIC=AIC;
// ═══════════ AI Autocomplete (v5.9.0) ═══════════
// After a short idle while typing in a text block, ask the AI for a short
// continuation and surface it as a "Tab to accept" pill anchored to the
// block. Tab inserts the suggestion; Escape dismisses it.
const AIAC={_timer:null,_sug:'',_bid:null,_busy:false,_enabled:true,_req:0,
ROOT:null,
_allowed:function(b){ if(!b)return false; return ['paragraph','heading_1','heading_2','heading_3','heading_4','bulleted_list','numbered_list','quote','callout','to_do'].indexOf(b.type)>=0; },
mk(){ if(this.ROOT)return this.ROOT; const d=document.createElement('div'); d.id='_aiAC'; d.style.cssText='position:fixed;z-index:1150;display:none;max-width:min(520px,90vw)'; document.body.appendChild(d); this.ROOT=d;
document.addEventListener('mousedown',function(ev){ if(AIAC.ROOT&&!AIAC.ROOT.contains(ev.target))AIAC.clear(); });
return d; },
schedule(bid){ if(!this._enabled)return; const E=window.E; if(!E)return; const idx=E.getIdx(bid); const b=idx>=0?E.blocks[idx]:null; if(!this._allowed(b))return this.clear();
clearTimeout(this._timer); const self=this; this._timer=setTimeout(function(){ self.fetch(bid); },900); },
fetch(bid){ const E=window.E; if(!E)return; const idx=E.getIdx(bid); const b=idx>=0?E.blocks[idx]:null; if(!this._allowed(b))return this.clear();
const el=E.getEl(bid); const prefix=((el&&el.textContent)||b.content||'').trim(); if(prefix.length<8)return this.clear();
const ctx=(typeof window.fdGetPageContext==='function')?String(window.fdGetPageContext()||''):'';
const req=++this._req; this._busy=true; const self=this; const csrf=(E.csrfTok?E.csrfTok():'');
fetch('/api/agent/writing',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf},body:JSON.stringify({action:'autocomplete',prefix:prefix,context:ctx})})
.then(function(r){return r.json();})
.then(function(d){ self._busy=false; if(req!==self._req)return; if(d&&d.ok&&d.text){ self.show(bid,d.text); } else { self.clear(); } })
.catch(function(){ self._busy=false; if(req===self._req)self.clear(); });
},
show(bid,text){ text=(text||'').trim(); if(!text){return this.clear();} const E=window.E; const el=E&&E.getEl(bid); if(!el)return;
this._sug=text; this._bid=bid; this.mk(); const r=el.getBoundingClientRect();
this.ROOT.innerHTML='<div style="display:flex;align-items:center;gap:8px;background:var(--bg-modal);border:1px solid var(--border-light);border-radius:8px;padding:6px 10px;box-shadow:0 6px 24px rgba(0,0,0,.4);font-size:13px;color:var(--text-dim)">'
+'<span style="opacity:.7">✨</span><span style="color:var(--text);font-style:italic">'+esc(text)+'</span>'
+'<span style="margin-left:6px;font-family:var(--font-mono);font-size:11px;background:var(--bg-secondary);border-radius:4px;padding:1px 6px">Tab</span>'
+'<button class="aiac-x" title="Dismiss" style="background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:14px">✕</button></div>';
const btn=this.ROOT.querySelector('.aiac-x'); if(btn)btn.onclick=function(){ AIAC.clear(); };
let left=Math.min(Math.max(8,r.left+24),window.innerWidth-360); let top=r.bottom+4;
if(top+40>window.innerHeight)top=r.top-36;
this.ROOT.style.left=left+'px'; this.ROOT.style.top=Math.max(8,top)+'px'; this.ROOT.style.display='block';
},
accept(){ if(!this._sug)return false; const E=window.E; const idx=E&&E.getIdx(this._bid); if(idx<0){ this.clear(); return false; }
const b=E.blocks[idx]; const el=E.getEl(this._bid); E.sync(); E.pushHistory();
const cur=((el&&el.textContent)||b.content||''); const next=(cur+this._sug).replace(/\s+$/,' ');
b.content=next; if(el){ el.textContent=next; } this.clear(); E.dirty=true; E.autoSave(); if(window.__fdRT&&window.__fdRT.syncNow)window.__fdRT.syncNow();
if(el){ el.focus(); ce(el); } return true;
},
clear(){ this._sug=''; this._bid=null; this._req++; if(this.ROOT){ this.ROOT.style.display='none'; this.ROOT.innerHTML=''; } },
get active(){ return !!this._sug; }
};
window.AIAC=AIAC;
function renderBlock(b,idx){
if(b.type==='meeting')return renderMeetingBlock(b,idx);
if(b.type==='table')return renderTableBlock(b,idx);
if(b.type==='divider')return `<div class="block-wrapper" data-id="${b.id}"><hr class="block-divider"></div>`;
if(b.type==='button')return renderAutomationButton(b,idx);
if(b.type==='image')return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-image" style="text-align:center;padding:12px 0;"><img src="${b.src||''}" alt="${esc(b.alt||'')}" class="fd-img" data-full="${b.src||''}" style="max-width:100%;max-height:70vh;border-radius:8px;display:block;margin:0 auto;cursor:zoom-in;" onerror="this.style.display='none';this.parentElement.innerHTML='<div style=\\'padding:40px;color:var(--text-tertiary)\\'>Image not found</div>'" onclick="E._openLightbox(this)"></div></div>`;
if(b.type==='video'){
const src=b.src||'';return src?`<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-video"><video controls preload="metadata" style="max-width:100%;border-radius:8px;display:block;margin:0 auto;"><source src="${src}"></video></div></div>`:`<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-video" style="text-align:center;padding:60px 20px;background:var(--bg-secondary);border:1px dashed var(--border);border-radius:8px;cursor:pointer;" onclick="E.setVideoSrc('${b.id}')"><div style="font-size:48px;margin-bottom:12px;">🎬</div><div style="font-size:14px;color:var(--text-dim);">Click to add video URL</div></div></div>`;
}
if(b.type==='audio'){
const src=b.src||'';return src?`<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-audio"><audio controls preload="metadata" style="width:100%;"><source src="${src}"></audio></div></div>`:`<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-audio" style="text-align:center;padding:60px 20px;background:var(--bg-secondary);border:1px dashed var(--border);border-radius:8px;cursor:pointer;" onclick="E.setAudioSrc('${b.id}')"><div style="font-size:48px;margin-bottom:12px;">🎵</div><div style="font-size:14px;color:var(--text-dim);">Click to add audio URL</div></div></div>`;
}
if(b.type==='bookmark'){
var url=b.url||b.src||'';var title=esc(b.title||url);var desc=esc(b.description||'');var img=b.image||'';var site=esc(b.site_name||'');
var imgHtml=img?'<img src="'+img+'" alt="" style="width:120px;height:90px;object-fit:cover;border-radius:8px;flex-shrink:0;">':'';
var descHtml=desc?'<div style="font-size:13px;color:var(--text-dim);margin-top:4px;">'+desc+'</div>':'';
var siteHtml=site?'<div style="font-size:11px;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.5px;margin-top:6px;">'+site+'</div>':'';
return url?`<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-bookmark"><a href="'+url+'" target="_blank" rel="noopener noreferrer" style="text-decoration:none;color:inherit;"><div style="display:flex;gap:14px;align-items:center;border:1px solid var(--border);border-radius:10px;padding:14px 16px;margin:0;background:var(--bg-secondary);"><div style="flex:1;min-width:0;"><div style="font-weight:600;font-size:15px;">'+title+'</div>'+descHtml+siteHtml+'</div>'+imgHtml+'</div></a></div></div>`:`<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-bookmark" style="text-align:center;padding:60px 20px;background:var(--bg-secondary);border:1px dashed var(--border);border-radius:8px;cursor:pointer;" onclick="E.setBookmarkUrl('${b.id}')"><div style="font-size:48px;margin-bottom:12px;">🔖</div><div style="font-size:14px;color:var(--text-dim);">Click to add bookmark URL</div></div></div>`;
}
if(b.type==='embed'){
if(b.embed_type==='inline_dbs')return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-inline-dbs"><div style="padding:20px;text-align:center;color:var(--text-secondary);">📊 Inline Databases</div><div class="inline-dbs-grid">${(b.dbs||[]).map(db=>`<a href="/db/${db.id}/view/${db.view_type||'table'}" class="inline-db-card" target="_blank" style="display:block;padding:12px 16px;background:var(--bg-secondary);border-radius:8px;margin:8px 0;text-decoration:none;color:var(--text-primary);border:1px solid var(--border);"><span style="font-weight:500">📋 ${db.name}</span><span style="float:right;font-size:12px;color:var(--text-tertiary);">${db.view_type==='form'?'📝 Form':'📊 Table'} →</span></a>`).join('')}</div></div></div>`;
if(b.embed_type==='collection')return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-embed-collection" id="inline-db-${b.collection_id}" style="padding:0;min-height:200px"><div style="padding:40px;text-align:center;color:var(--text-dim)">Loading database...</div></div><`+`script>setTimeout(function(){var el=document.getElementById('inline-db-${b.collection_id}');if(el&&window.FlowDeckDB)window.FlowDeckDB.renderInto(el,${b.collection_id},null);},50);<`+`/script></div>`;
if(b.embed_type==='pdf')return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-embed" style="padding:0;"><iframe src="${b.src||''}" style="width:100%;height:80vh;border:none;border-radius:8px;" onerror="this.style.display='none';this.nextElementSibling.style.display='block';"></iframe><div style="display:none;text-align:center;padding:32px;color:var(--text-secondary);"><svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><p style="margin-top:8px;">Failed to load PDF</p><a href="${b.src||''}" target="_blank" style="color:var(--accent);">Open in new tab →</a></div></div></div>`;
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';
return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-embed" style="text-align:center;padding:60px 20px;"><div style="font-size:48px;margin-bottom:12px;">{{ fd_icon("paperclip",14) }}</div><div style="font-size:16px;font-weight:500;color:var(--text-primary);margin-bottom:4px;">${esc(b.file_name||'File')}</div><div style="font-size:12px;color:var(--text-tertiary);margin-bottom:16px;">${szStr} · ${esc(b.file_mime||'')}</div><a href="${b.src||''}" download style="display:inline-block;padding:8px 20px;background:var(--accent);color:#fff;text-decoration:none;border-radius:8px;font-size:13px;font-weight:500;">⬇ Download</a></div></div>`;
}
var url=b.src||'';if(url){
var src=url;
try{var h=window.location.href.split('/').slice(0,3).join('/');var urlNoProto=url.replace(/^https?:\\/\\//,'');if(/^(youtube\\.com|youtu\\.be|vimeo\\.com|loom\\.com|figma\\.com|google\\.com\\/maps|codepen\\.io|miro\\.com|open\\.spotify\\.com|soundcloud\\.com|twitch\\.tv|twitter\\.com|x\\.com|pinterest\\.)/.test(urlNoProto.split('/')[0])){src=url;}}catch(e){}
var height=b.height||520;
return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-embed" style="position:relative;width:100%;height:${height}px;border-radius:8px;overflow:hidden;background:#0a0a0a;"><iframe src="${src}" loading="lazy" frameborder="0" style="position:absolute;inset:0;width:100%;height:100%;" allowfullscreen allow="autoplay; encrypted-media; picture-in-picture"></iframe></div></div>`;
}
return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-embed" style="text-align:center;padding:60px 20px;background:var(--bg-secondary);border:1px dashed var(--border);border-radius:8px;cursor:pointer;" onclick="E.setEmbedUrl('${b.id}')"><div style="font-size:48px;margin-bottom:12px;">🔗</div><div style="font-size:14px;color:var(--text-dim);">Click to add embed URL</div></div></div>`;
}
if(b.embed_type==='inline_dbs')return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-inline-dbs"><div style="padding:20px;text-align:center;color:var(--text-secondary);">📊 Inline Databases</div><div class="inline-dbs-grid">${(b.dbs||[]).map(db=>`<a href="/db/${db.id}/view/${db.view_type||'table'}" class="inline-db-card" target="_blank" style="display:block;padding:12px 16px;background:var(--bg-secondary);border-radius:8px;margin:8px 0;text-decoration:none;color:var(--text-primary);border:1px solid var(--border);"><span style="font-weight:500">📋 ${db.name}</span><span style="float:right;font-size:12px;color:var(--text-tertiary);">${db.view_type==='form'?'📝 Form':'📊 Table'} →</span></a>`).join('')}</div></div></div>`;
if(b.embed_type==='collection')return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-embed-collection" id="inline-db-${b.collection_id}" style="padding:0;min-height:200px"><div style="padding:40px;text-align:center;color:var(--text-dim)">Loading database...</div></div><`+`script>setTimeout(function(){var el=document.getElementById('inline-db-${b.collection_id}');if(el&&window.FlowDeckDB)window.FlowDeckDB.renderInto(el,${b.collection_id},null);},50);<`+`/script></div>`;
if(b.embed_type==='pdf')return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-embed" style="padding:0;"><iframe src="${b.src||''}" style="width:100%;height:80vh;border:none;border-radius:8px;" onerror="this.style.display='none';this.nextElementSibling.style.display='block';"></iframe><div style="display:none;text-align:center;padding:32px;color:var(--text-secondary);"><svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><p style="margin-top:8px;">Failed to load PDF</p><a href="${b.src||''}" target="_blank" style="color:var(--accent);">Open in new tab →</a></div></div></div>`;
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';
return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-embed" style="text-align:center;padding:60px 20px;"><div style="font-size:48px;margin-bottom:12px;">{{ fd_icon("paperclip",14) }}</div><div style="font-size:16px;font-weight:500;color:var(--text-primary);margin-bottom:4px;">${esc(b.file_name||'File')}</div><div style="font-size:12px;color:var(--text-tertiary);margin-bottom:16px;">${szStr} · ${esc(b.file_mime||'')}</div><a href="${b.src||''}" download style="display:inline-block;padding:8px 20px;background:var(--accent);color:#fff;text-decoration:none;border-radius:8px;font-size:13px;font-weight:500;">⬇ Download</a></div></div>`;
}
}
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=`<input type="checkbox" class="todo-checkbox" ${b.checked?'checked':''} onchange="E._doToggle('${b.id}',this.checked)"><div data-bid="${b.id}" contenteditable="true" data-placeholder="${ph}" spellcheck="false">${mdEsc(b.content)}</div>`;
else if(b.type==='toggle')inner=`<div class="toggle-title-row"><button class="toggle-chevron ${b.expanded?'open':''}" onclick="E._doToggleExpand('${b.id}')">▶</button><div class="toggle-title" data-bid="${b.id}" contenteditable="true" data-placeholder="${ph}" spellcheck="false">${mdEsc(b.content)}</div></div><div class="toggle-children" ${(b.expanded===false)?'style="display:none"':''}>${(b.children||[]).map((ch)=>renderNestedBlock(ch)).join('')}</div>`;
else if(b.type==='code')inner=`<div class="code-lang-label">${b.language||'Plain Text'}</div><pre><code data-bid="${b.id}" contenteditable="true" spellcheck="false">${mdEsc(b.content)}</code></pre>`;
else if(b.type==='callout')inner=`<span class="callout-icon" onclick="E._toggleCalloutIcon('${b.id}')" title="Change icon">${b.icon||'💡'}</span><div data-bid="${b.id}" contenteditable="true" data-placeholder="${ph}" spellcheck="false">${mdEsc(b.content)}</div><div class="callout-icon-picker" id="callout-picker-${b.id}" style="display:none">${CALLOUT_ICONS.map(function(i){return '<button type="button" class="callout-icon-opt" onclick="E._setCalloutIcon(\''+b.id+'\',\''+i+'\')">'+i+'</button>';}).join('')}</div>`;
else inner=`<div data-bid="${b.id}" contenteditable="true" data-placeholder="${ph}" spellcheck="false">${mdEsc(b.content)}</div>`;
const tag=b.type.startsWith('heading_')?'h'+b.type.slice(-1):b.type==='quote'?'blockquote':'div';
const cls=tag==='div'?`block-content ${b.type==='bulleted_list'?'block-bullet':b.type==='numbered_list'?'block-numbered':b.type==='to_do'?'block-todo':b.type==='toggle'?'block-toggle':b.type==='code'?'block-code':b.type==='callout'?'block-callout':''}`:`block-content block-${tag}`;
const style=b.type==='callout'?` style="background:${(b.style&&b.style.bgColor)||'var(--blue-bg)'}${(b.style&&b.style.color)?';color:'+b.style.color:''}"`:((b.style&&(b.style.bgColor||b.style.color))?` style="${(b.style.bgColor?'background:'+b.style.bgColor+';':'')+(b.style.color?'color:'+b.style.color+';':'')}"`:'');
const bidAttr=b.type.startsWith('heading_')?` id="h-${(b.id||'').replace(/[^a-zA-Z0-9]/g,'')}"`:'';
const actions=idx>=0?`<div class="block-actions"><button class="block-add-btn" onclick="E.addAfter(${idx})">+</button></div>`:'';
return `<div class="block-wrapper" data-id="${b.id}">
<div class="block-handle"><svg viewBox="0 0 16 16" width="14" height="14"><circle cx="4" cy="3" r="1.3"/><circle cx="11" cy="3" r="1.3"/><circle cx="4" cy="8" r="1.3"/><circle cx="11" cy="8" r="1.3"/><circle cx="4" cy="13" r="1.3"/><circle cx="11" cy="13" r="1.3"/></svg></div>
${actions}
<${tag}${style}${bidAttr} class="${cls}">${inner}</${tag}>
</div>`;
}
function renderTOC(b){
var out=[];
var blocks=window.E?window.E.blocks:[];
var headings=blocks.filter(function(b){return /^heading_[1-4]$/.test(b.type)&&(b.content||'').trim();});
if(headings.length===0){
out.push(`<div class="block-content block-toc block-toc-empty"><span style="color:var(--text-dim);font-size:13px;">Add headings to generate a table of contents</span></div>`);
}else{
out.push(`<div class="block-content block-toc"><div class="block-toc-title">On this page</div>`);
headings.forEach(function(h){
var lvl=parseInt(h.type.slice(-1),10);
var id='h-'+ (h.id||'').replace(/[^a-zA-Z0-9]/g,'');
out.push(`<div class="block-toc-item" style="margin-left:${(lvl-1)*12}px" onclick="var el=document.getElementById('${id}');if(el){el.scrollIntoView({behavior:'smooth',block:'start'});}">${esc(h.content)}</div>`);
});
out.push(`</div>`);
}
if(blocks.length===0){}
return `<div class="block-wrapper" data-id="${b.id}">${out.join('')}</div>`;
}
function renderMathBlock(b){
var html=`<div class="math-display" data-bid="${b.id}"></div>`;
html+=`<textarea class="math-editor" data-math-bid="${b.id}" data-placeholder="Type a LaTeX math expression (e.g. E = mc^2)..." spellcheck="false">${esc(b.content)}</textarea>`;
return html;
}
function renderAutomationButton(b,idx){
var label=(b.content&&b.content.trim())?b.content:((b.automation_name||'Run automation'));
var bound=b.automation_id>0;
var badge=bound?'<span style="opacity:.65;font-weight:400;margin-left:8px;font-size:11px;">⚡ '+esc(b.automation_name||'')+'</span>':'<span style="opacity:.65;font-weight:400;margin-left:8px;font-size:11px;">Choose…</span>';
var actions=idx>=0?`<div class="block-actions"><button class="block-add-btn" onclick="E.addAfter(${idx})">+</button></div>`:'';
return `<div class="block-wrapper" data-id="${b.id}">
<div class="block-handle"><svg viewBox="0 0 16 16" width="14" height="14"><circle cx="4" cy="3" r="1.3"/><circle cx="11" cy="3" r="1.3"/><circle cx="4" cy="8" r="1.3"/><circle cx="11" cy="8" r="1.3"/><circle cx="4" cy="13" r="1.3"/><circle cx="11" cy="13" r="1.3"/></svg></div>
${actions}
<div class="block-content"><button type="button" class="fd-automation-btn" onclick="E.runAutomationButton('${b.id}')">⚡ ${esc(label)}${badge}</button></div>
</div>`;
}
function renderColumnsBlock(b){
var cols=(b.children&&b.children.length)?b.children:[{id:genId(),type:'paragraph',content:''}];
var cells=cols.map(function(c){
return `<div class="column-cell">${renderChildColumn(c,b.id)}</div>`;
}).join('');
return `<div class="block-wrapper" data-id="${b.id}"><div class="block-content block-columns"><div class="columns-add-bar"><button class="columns-add-btn" onclick="E._addColumn('${b.id}')">+ Add column</button></div><div class="columns-row">${cells}</div></div></div>`;
}
function renderChildColumn(c,parentId){
return `<div class="block-column-wrapper" data-parent="${parentId}">
<div class="block-actions"><button class="block-add-btn" onclick="E._removeColumn('${parentId}','${c.id}')" title="Remove column">×</button></div>
<div class="block-content block-column" data-bid="${c.id}" contenteditable="true" data-placeholder="${c.type==='heading_1'?'Heading 1':c.type==='heading_2'?'Heading 2':c.type==='heading_3'?'Heading 3':'Type...'}" spellcheck="false">${esc(c.content)}</div>
</div>`;
}
function renderNestedBlock(c){
var ph=c.type==='heading_1'?'Heading 1':c.type==='heading_2'?'Heading 2':c.type==='heading_3'?'Heading 3':c.type==='heading_4'?'Heading 4':c.type==='bulleted_list'?'List':c.type==='numbered_list'?'List':c.type==='to_do'?'To-do':'Type...';
return `<div class="block-wrapper nested-block"><div class="block-content block-nested" data-bid="${c.id}" contenteditable="true" data-placeholder="${ph}" spellcheck="false">${esc(c.content)}</div></div>`;
}
// ═══════════ AI Meeting Note — widget de réunion (v4.12.0) ═══════════
// Bloc interactif « Notion-like » : consentement, transcription live (Web
// Speech API quand disponible), transcript horodaté + notes, puis résumé
// généré par le FlowDeck Agent.
function renderMeetingBlock(b, idx){
var actions=idx>=0?`<div class="block-actions"><button class="block-add-btn" onclick="E.addAfter(${idx})">+</button></div>`:'';
return `<div class="block-wrapper" data-id="${b.id}">
<div class="block-handle"><svg viewBox="0 0 16 16" width="14" height="14"><circle cx="4" cy="3" r="1.3"/><circle cx="11" cy="3" r="1.3"/><circle cx="4" cy="8" r="1.3"/><circle cx="11" cy="8" r="1.3"/><circle cx="4" cy="13" r="1.3"/><circle cx="11" cy="13" r="1.3"/></svg></div>
${actions}
<div class="block-content block-meeting" data-meeting-bid="${b.id}"><div class="fm-loading">Préparation de la réunion…</div></div>
</div>`;
}
function _fmFind(bid){var e=window.E;if(e&&e.blocks){for(var i=0;i<e.blocks.length;i++){if(e.blocks[i].id===bid)return e.blocks[i];}}return null;}
function _fmDef(){return {title:'Meeting',date:new Date().toISOString().slice(0,10),consent:'self',started:false,status:'idle',lines:[],interim:'',notes:'',manualTranscript:'',summary:'',summaryError:'',summaryLoading:false,lastTab:'transcript'};}
function _fmEnsure(bid){var b=_fmFind(bid);if(!b)return null;if(!b.meeting)b.meeting=_fmDef();if(!b.meeting.lines)b.meeting.lines=[];if(b.meeting.summaryLoading===undefined)b.meeting.summaryLoading=false;if(b.meeting.manualTranscript===undefined)b.meeting.manualTranscript='';return b;}
function _fmTime(){var d=new Date();var p=function(n){return (n<10?'0':'')+n;};return p(d.getHours())+':'+p(d.getMinutes())+':'+p(d.getSeconds());}
function _fmEsc(v){return esc(v==null?'':String(v));}
function _fmAttr(v){return _fmEsc(v).replace(/"/g,'&quot;');}
function _fmSave(){var e=window.E;if(e){e.dirty=true;if(e.autoSave)e.autoSave();}}
function _fmTranscriptText(b){var out=[];(b.meeting.lines||[]).forEach(function(l){out.push('['+l.t+'] '+(l.who||'Vous')+' : '+l.text);});if((b.meeting.manualTranscript||'').trim())out.push('(transcription saisie/collée) '+(b.meeting.manualTranscript||'').trim());if((b.meeting.notes||'').trim())out.push('(notes) '+(b.meeting.notes||'').trim());return out.join('\n');}
var FDMeeting = {
_rt:{},
_audio(){ return !!(window.SpeechRecognition||window.webkitSpeechRecognition); },
mount(bid,root){
var s=this._rt[bid]||(this._rt[bid]={});s.root=root;
if(!s.wired){
s.wired=true;
var self=this;
root.addEventListener('click',function(ev){var t=ev.target&&ev.target.closest?ev.target.closest('[data-act]'):null;if(t)self.act(bid,t.getAttribute('data-act'),t);});
root.addEventListener('input',function(ev){self.onInput(bid,ev.target);});
root.addEventListener('change',function(ev){self.onInput(bid,ev.target);});
}
this.paint(bid);
},
focus(bid){},
paint(bid){
var s=this._rt[bid];var b=_fmEnsure(bid);if(!b||!s||!s.root)return;
s.root.innerHTML=this.html(bid);
},
_statusLabel(m){
if(m.status==='running')return '● Enregistrement';
if(m.status==='paused')return '⏸ En pause';
if(m.status==='done')return '✓ Terminée';
if(m.status==='manual')return '📝 Sans audio';
return 'Prête';
},
html(bid){
var b=_fmEnsure(bid);if(!b)return '';
var m=b.meeting;var s=this._rt[bid]||{};
var audioOk=this._audio();
var st=this._statusLabel(m);
var pill=m.status==='running'||m.status==='paused'||m.status==='done'||m.status==='manual'
? `<span class="fm-pill ${m.status==='done'?'fm-pill-ok':''}">${_fmEsc(st)}</span>`:'';
var head=`<div class="fm-head">
<span class="fm-ico">🎙️</span>
<input class="fm-title" data-fm="title" value="${_fmAttr(m.title)}" placeholder="Titre de la réunion" spellcheck="false">
<span class="fm-date">📅 ${_fmEsc(m.date)}</span>
${pill}
</div>`;
var body='';
if(!m.started){
body+=`<div class="fm-step">
<div class="fm-step-title">1 · Consentement des participants</div>
<label class="fm-radio"><input type="radio" name="fm-consent-${bid}" data-fm="consent" value="self" ${m.consent==='auto'?'':'checked'}> <span>🔘 Obtenir le consentement moi-même</span></label>
<label class="fm-radio"><input type="radio" name="fm-consent-${bid}" data-fm="consent" value="auto" ${m.consent==='auto'?'checked':''}> <span>🔘 Lire automatiquement un message audio</span></label>
<div class="fm-step-desc">${audioOk?'Notion AI est prêt : la réunion sera transcrite en direct. Autorisez le micro lorsque le navigateur le demande.':'Ce navigateur ne supporte pas la transcription audio : vous pourrez saisir ou coller la transcription manuellement.'}</div>
<button class="fm-btn fm-btn-primary" data-act="start" type="button">${audioOk?'🎙 Start transcribing':'Ouvrir la boîte de réunion (sans audio)'}</button>
</div>`;
}else{
var tabs=`<div class="fm-tabs">
<button class="fm-tab ${m.lastTab==='notes'?'active':''}" data-act="tab" data-tab="notes" type="button">Notes</button>
<button class="fm-tab ${m.lastTab==='transcript'?'active':''}" data-act="tab" data-tab="transcript" type="button">Transcript</button>
<button class="fm-tab ${m.lastTab==='summary'?'active':''}" data-act="tab" data-tab="summary" type="button">Summary</button>
</div>`;
var ctrl='';
if(m.status==='running'){
ctrl=`<div class="fm-wave">•••|ı|ı|ı|ı•••</div>
<div class="fm-actions">
<button class="fm-btn" data-act="pause" type="button">⏸ Pause</button>
<button class="fm-btn fm-btn-primary" data-act="stop" type="button">⏹ Stop</button>
</div>`;
}else if(m.status==='paused'){
ctrl=`<div class="fm-actions">
<button class="fm-btn fm-btn-primary" data-act="start" type="button">▶ Reprendre</button>
<button class="fm-btn" data-act="stop" type="button">⏹ Stop</button>
</div>`;
}else if(m.status==='manual'){
ctrl=`<div class="fm-actions"><button class="fm-btn" data-act="stop" type="button">⏹ Terminer la réunion</button></div>`;
}else if(m.status==='done'){
ctrl=`<div class="fm-actions"><button class="fm-btn" data-act="new" type="button">🔄 Nouvelle transcription</button></div>`;
}
var panel='';
if(m.lastTab==='notes'){
panel=`<textarea class="fm-area" data-fm="notes" rows="6" placeholder="Prenez des notes manuelles pendant la réunion…">${_fmEsc(m.notes)}</textarea>`;
}else if(m.lastTab==='transcript'){
var lines=(m.lines||[]).map(function(l){return `<div class="fm-line"><span class="fm-ts">${_fmEsc(l.t||'')}</span><span class="fm-who">${_fmEsc(l.who||'Vous')}</span><span class="fm-txt">${_fmEsc(l.text)}</span></div>`;}).join('');
if(m.status==='running'){lines+=`<div class="fm-line fm-interim"><span class="fm-ts">·</span><span class="fm-who">Vous</span><span class="fm-txt">${_fmEsc(m.interim||'')}${(m.interim||'')?'':'…'}</span></div>`;}
var manual=m.status!=='running'?`<div class="fm-manual"><div class="fm-manual-label">Transcript saisi ou collé (si aucune capture audio)</div><textarea class="fm-area" data-fm="manual" rows="4" placeholder="Collez ici la transcription de la réunion (optionnel)…">${_fmEsc(m.manualTranscript)}</textarea></div>`:'';
panel=`<div class="fm-transcript"><div data-fm-transcript-body>${lines||'<div class="fm-empty">La transcription apparaîtra ici en direct. Enregistrez la réunion pour générer un transcript.</div>'}</div>${manual}</div>`;
}else{
var summaryHtml='';
if(m.summaryLoading){
summaryHtml=`<div class="fm-loading">Génération du résumé…</div>`;
}else if(m.summary){
summaryHtml=`<textarea class="fm-area" data-fm="summary" rows="14">${_fmEsc(m.summary)}</textarea>
<div class="fm-actions"><button class="fm-btn" data-act="copy" type="button">📋 Copier le résumé</button></div>`;
}else if(m.summaryError){
summaryHtml=`<div class="fm-error">${_fmEsc(m.summaryError)}</div>
<button class="fm-btn fm-btn-primary" data-act="generate" type="button">Réessayer</button>`;
}else{
var has=!!(_fmTranscriptText(b).trim());
if(m.status==='done'&&has){
summaryHtml=`<button class="fm-btn fm-btn-primary" data-act="generate" type="button">✨ Générer le résumé</button>
<div class="fm-step-desc">L'agent analyse le transcript et produit un aperçu structuré (sujets, décisions, actions).</div>`;
}else{
summaryHtml=`<div class="fm-empty">Arrêtez l'enregistrement (ou ajoutez un transcript) puis générez le résumé ici.</div>`;
}
}
panel=summaryHtml;
}
body=`<div class="fm-step">${ctrl}${tabs}${panel}</div>`;
}
return `<div class="fm">${head}${body}</div>`;
},
onInput(bid,el){
var b=_fmEnsure(bid);if(!b||!el)return;var m=b.meeting;var k=el.getAttribute&&el.getAttribute('data-fm');
if(!k)return;
if(k==='title'){m.title=el.value||'';var tEl=document.getElementById('_titleEl');if(tEl&&!((tEl.textContent||'').trim())){tEl.textContent=m.title;if(window.E)window.E.pageTitle=m.title;}}
else if(k==='notes'){m.notes=el.value;}
else if(k==='manual'){m.manualTranscript=el.value;}
else if(k==='summary'){m.summary=el.value;}
else if(k==='consent'){m.consent=el.value;}
else return;
_fmSave();
},
act(bid,act,el){
var b=_fmEnsure(bid);if(!b)return;var m=b.meeting;var self=this;
if(act==='start'){
if(m.status==='done'||m.status==='idle'||m.status==='paused'){
if(m.status==='done'){m.lines=[];m.summary='';m.summaryError='';m.manualTranscript='';}
m.started=true;m.lastTab='transcript';
if(!self._audio()){m.status='manual';m.interim='';_fmSave();self.paint(bid);return;}
if(m.consent==='auto'&&window.speechSynthesis){
var u=new SpeechSynthesisUtterance('Cette réunion est enregistrée et transcrite par FlowDeck. Merci de votre attention.');
try{window.speechSynthesis.speak(u);}catch(e){}
}
setTimeout(function(){self._startRec(bid);},m.consent==='auto'?1300:0);
}
}else if(act==='pause'){
this._stopRec(bid,false);m.status='paused';m.interim='';_fmSave();this.paint(bid);
}else if(act==='stop'){
this._stopRec(bid,true);m.status='done';m.lastTab='transcript';m.interim='';_fmSave();this.paint(bid);
}else if(act==='new'){
m.lines=[];m.summary='';m.summaryError='';m.status='idle';m.started=false;m.lastTab='transcript';_fmSave();this.paint(bid);
}else if(act==='tab'){
m.lastTab=(el&&el.getAttribute&&el.getAttribute('data-tab'))||'transcript';_fmSave();this.paint(bid);
}else if(act==='generate'){
this._generate(bid);
}else if(act==='copy'){
var txt=(m.summary||'').trim();
if(!txt)return;
if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(txt).catch(function(){});}
}
},
_startRec(bid){
var b=_fmEnsure(bid);if(!b)return;var m=b.meeting;var s=this._rt[bid]||(this._rt[bid]={});
var SR=window.SpeechRecognition||window.webkitSpeechRecognition;
if(!SR){m.status='manual';_fmSave();this.paint(bid);return;}
if(s.rec){try{s.rec.stop();}catch(e){}}
var rec=new SR();s.rec=rec;s.offset=0;s.stopped=false;
rec.lang='fr-FR';rec.continuous=true;rec.interimResults=true;rec.maxAlternatives=1;
var self=this;
rec.onresult=function(ev){
var final='',interim='';
for(var i=(s.offset||0);i<ev.results.length;i++){
var r=ev.results[i];
if(r.isFinal)final+=(r[0]&&r[0].transcript||'');
else interim+=(r[0]&&r[0].transcript||'');
}
s.offset=ev.results.length;
if(final&&final.trim()){
m.lines.push({t:_fmTime(),who:'Vous',text:final.trim()});
_fmSave();
self._updateTranscriptOnly(bid);
}
m.interim=interim.trim();
self._updateInterim(bid);
};
rec.onend=function(){
if(!s.stopped&&m.status==='running'&&self._audio()){try{rec.start();}catch(e){}}
};
rec.onerror=function(ev){
if(ev&&ev.error==='not-allowed'){m.summaryError='Microphone refusé : autorisez le micro ou utilisez la saisie manuelle du transcript.';m.status='manual';s.stopped=true;_fmSave();self.paint(bid);}
};
m.status='running';m.interim='';_fmSave();
this.paint(bid);
try{rec.start();}catch(e){m.status='manual';s.stopped=true;_fmSave();this.paint(bid);}
},
_stopRec(bid,stop){
var b=_fmEnsure(bid);if(!b)return;var m=b.meeting;var s=this._rt[bid];
if(s&&s.rec){s.stopped=true;try{s.rec.stop();}catch(e){}}
if(stop&&m.interim&&m.interim.trim()){m.lines.push({t:_fmTime(),who:'Vous',text:m.interim.trim()});}
m.interim='';
},
_updateTranscriptOnly(bid){var s=this._rt[bid];var b=_fmEnsure(bid);if(!b||!s||!s.root)return;var el=s.root.querySelector('[data-fm-transcript-body]');if(el)el.innerHTML=this._transcriptLines(b);},
_updateInterim(bid){var s=this._rt[bid];var b=_fmEnsure(bid);if(!b||!s||!s.root)return;var el=s.root.querySelector('.fm-interim .fm-txt');if(el&&el.textContent!==(b.meeting.interim||'')){el.textContent=(b.meeting.interim||'')+'…';}},
_transcriptLines(b){var m=b.meeting;var lines=(m.lines||[]).map(function(l){return `<div class="fm-line"><span class="fm-ts">${_fmEsc(l.t||'')}</span><span class="fm-who">${_fmEsc(l.who||'Vous')}</span><span class="fm-txt">${_fmEsc(l.text)}</span></div>`;}).join('');return lines||'<div class="fm-empty">La transcription apparaîtra ici en direct…</div>';},
_generate(bid){
var b=_fmEnsure(bid);if(!b)return;var m=b.meeting;
var content=_fmTranscriptText(b);
if(!content.trim()){m.summaryError='Aucun contenu de réunion à résumer.';_fmSave();this.paint(bid);return;}
m.summaryLoading=true;m.summaryError='';_fmSave();this.paint(bid);
var self=this;
var prompt='Rédige le résumé structuré de cette réunion (format AI Meeting Note) : une ligne d\'en-tête avec la date du '+_fmEsc(m.date)+', puis les sections « Résumé » (Overview), « Décisions » et « Action items » avec des puces concrètes. Si le contenu est trop court ou fragmentaire, indique « No meaningful content could be extracted ». Ne précède pas la réponse de commentaires.';
if(!(window.fdAgent&&window.fdAgent.generate)){
m.summaryLoading=false;m.summaryError='Agent IA indisponible — configurez-le dans Paramètres → Agent & IA.';_fmSave();this.paint(bid);return;
}
window.fdAgent.generate(prompt, content).then(function(text){
m.summary=(text||'').trim();m.summaryLoading=false;m.summaryError='';_fmSave();self.paint(bid);
}).catch(function(err){
m.summaryLoading=false;m.summaryError=(err&&err.message)?err.message:'Échec de génération du résumé';_fmSave();self.paint(bid);
});
}
};
window.FDMeeting=FDMeeting;
// ═══════════ v5.10.0 — Interactions de bloc : sélection multiple, menu ⋮⋮, undo/redo ═══════════
let _selSet=new Set();
function _selHas(bid){return _selSet.has(bid);}
function _selToggle(bid){if(!bid)return;if(_selSet.has(bid))_selSet.delete(bid);else _selSet.add(bid);
document.querySelectorAll('.block-wrapper[data-id="'+bid+'"]').forEach(function(w){w.classList.toggle('selected',_selSet.has(bid));});}
function _selClear(){_selSet.clear();document.querySelectorAll('.block-wrapper.selected').forEach(function(w){w.classList.remove('selected');});}
function _selApply(){document.querySelectorAll('.block-wrapper[data-id]').forEach(function(w){var id=w.getAttribute('data-id');w.classList.toggle('selected',!!(_selSet.has(id)));});}
function _selIndicesSorted(){var E=window.E;var out=[];if(!E)return out;E.blocks.forEach(function(b,i){if(_selSet.has(b.id))out.push(i);});return out;}
function _rtSync(){try{var rt=window.__fdRT;if(rt&&rt.syncNow)rt.syncNow();}catch(e){}}
const _BLOCK_TURN_TYPES=[
{id:'paragraph',name:'Text'},{id:'heading_1',name:'Heading 1'},{id:'heading_2',name:'Heading 2'},
{id:'heading_3',name:'Heading 3'},{id:'heading_4',name:'Heading 4'},{id:'bulleted_list',name:'Bulleted list'},
{id:'numbered_list',name:'Numbered list'},{id:'to_do',name:'To-do'},{id:'toggle',name:'Toggle'},
{id:'quote',name:'Quote'},{id:'callout',name:'Callout'},{id:'table',name:'Table'},
{id:'math',name:'Math equation'},{id:'columns',name:'Column list'},{id:'button',name:'Button / Automation'},
{id:'code',name:'Code block'}
];
const _BLOCK_COLORS_TEXT=['default','#E5484D','#F76B15','#FFC53D','#46A758','#3E63DD','#8E4EC6','#12A594','#94A3B8'];
const _BLOCK_COLORS_BG=['default','rgba(229,72,77,.15)','rgba(247,107,21,.15)','rgba(255,197,61,.15)','rgba(70,154,88,.15)','rgba(62,99,221,.15)','rgba(142,78,198,.15)','rgba(18,165,148,.15)','rgba(148,163,184,.2)'];
function _blockMenuClose(){var m=document.getElementById('blockMenu');if(m)m.remove();m=document.getElementById('blockMenuSub');if(m)m.remove();}
function _blockMenuClick(ev){
var t=ev.target.closest&&ev.target.closest('[data-bm-act]');if(!t)return;
var act=t.getAttribute('data-bm-act');var bid=t.getAttribute('data-bid');
if(!act)return;
ev.preventDefault();ev.stopPropagation();
var E=window.E;if(!E)return;
var idx=bid?E.getIdx(bid):-1;
if(act==='turn'){var m=document.getElementById('blockMenu');if(m){var r=m.getBoundingClientRect();_blockTurnMenuOpen(r.right+4,r.top,bid||'');}return;}
if(act==='duplicate'){E.duplicateBlock(idx);}
else if(act==='copy-link'){E.copyBlockLink(bid||'');}
else if(act==='move-to'){_blockMoveToOpen(idx);}
else if(act==='delete'){E.deleteSelected(idx);}
else if(act==='color'){var key=t.getAttribute('data-color-key'),val=t.getAttribute('data-color-val');E.setBlockColor(idx,key,val);}
else if(act==='turn-type'){E.turnInto(idx,t.getAttribute('data-type'));_blockMenuClose();}
_blockMenuClose();
}
function blockMenuOpen(x,y,bid){
_blockMenuClose();
var E=window.E;if(!E)return;
var idx=E.getIdx(bid);if(idx<0)return;
var b=E.blocks[idx];if(!b)return;
var d=document.createElement('div');d.id='blockMenu';d.className='block-menu';d.style.cssText='position:fixed;z-index:4000;';
var colorsText='',colorsBg='';
_BLOCK_COLORS_TEXT.forEach(function(c){colorsText+='<button type="button" data-bm-act="color" data-color-key="color" data-color-val="'+c+'" title="Text color" style="background:'+(c==='default'?'transparent':c)+'" class="bm-swatch'+(c==='default'?' bm-default':'')+'">'+(c==='default'?'A':'')+'</button>';});
_BLOCK_COLORS_BG.forEach(function(c){colorsBg+='<button type="button" data-bm-act="color" data-color-key="bgColor" data-color-val="'+c+'" title="Background color" style="background:'+(c==='default'?'transparent':c)+'" class="bm-swatch'+(c==='default'?' bm-default':'')+'"></button>';});
d.innerHTML='<div class="bm-item" data-bm-act="turn" data-bid="'+b.id+'"><span class="bm-ico">↩</span>Turn into<span class="bm-arrow">▸</span></div>'
+'<div class="bm-sep"></div>'
+'<div class="bm-item" data-bm-act="duplicate" data-bid="'+b.id+'"><span class="bm-ico">▣</span>Duplicate <span class="bm-hint">'+(_rtMac()?'⌘D':'Ctrl+D')+'</span></div>'
+'<div class="bm-item" data-bm-act="copy-link" data-bid="'+b.id+'"><span class="bm-ico">🔗</span>Copy link to block</div>'
+'<div class="bm-item" data-bm-act="move-to" data-bid="'+b.id+'"><span class="bm-ico">↪</span>Move to…</div>'
+'<div class="bm-sep"></div>'
+'<div class="bm-colors"><div class="bm-colors-label">Text</div><div class="bm-swatches">'+colorsText+'</div></div>'
+'<div class="bm-colors"><div class="bm-colors-label">Background</div><div class="bm-swatches">'+colorsBg+'</div></div>'
+'<div class="bm-sep"></div>'
+'<div class="bm-item bm-danger" data-bm-act="delete" data-bid="'+b.id+'"><span class="bm-ico">✕</span>Delete <span class="bm-hint">⌫</span></div>';
d.addEventListener('click',_blockMenuClick);
d.addEventListener('contextmenu',function(ev){ev.preventDefault();_blockMenuClose();});
document.body.appendChild(d);
var r=d.getBoundingClientRect();
x=Math.min(Math.max(8,x),window.innerWidth-r.width-8);
y=Math.min(Math.max(8,y),window.innerHeight-r.height-8);
d.style.left=x+'px';d.style.top=y+'px';
}
function _blockTurnMenuOpen(x,y,bid){
var m=document.getElementById('blockMenuSub');if(m)m.remove();
var d=document.createElement('div');d.id='blockMenuSub';d.className='block-menu block-menu-sub';d.style.cssText='position:fixed;z-index:4001;';
var html='';
_BLOCK_TURN_TYPES.forEach(function(t){html+='<div class="bm-item" data-bm-act="turn-type" data-type="'+t.id+'" data-bid="'+bid+'">'+t.name+'</div>';});
d.innerHTML=html;
d.addEventListener('click',_blockMenuClick);
document.body.appendChild(d);
var r=d.getBoundingClientRect();
x=Math.min(Math.max(8,x),window.innerWidth-r.width-8);
y=Math.min(Math.max(8,y),window.innerHeight-r.height-8);
d.style.left=x+'px';d.style.top=y+'px';
}
function _rtMac(){return /Mac|iPhone|iPad/.test(navigator.userAgent);}
function _blockMoveToOpen(idx){
var E=window.E;if(!E)return;
var pages=[];var seen={};
document.querySelectorAll('.sidebar-item.ws-tree-item a.page-name[href^="/pages/"]').forEach(function(a){
var m=(a.getAttribute('href')||'').match(/\/pages\/(\d+)/);if(!m)return;
var id=parseInt(m[1],10);var name=(a.textContent||'').trim()||('Page '+id);
if(id===E.pid||seen[id])return;seen[id]=1;pages.push({id:id,name:name});
});
var ov=document.createElement('div');ov.id='fdMoveOverlay';ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:9999;display:flex;align-items:center;justify-content:center;';
var box=document.createElement('div');box.style.cssText='background:var(--bg-secondary,#1f1f1f);border:1px solid var(--border,#333);border-radius:12px;padding:16px;width:420px;max-width:92vw;max-height:80vh;overflow:auto;';
var listHtml=pages.length?pages.map(function(p){return '<button type="button" data-pid="'+p.id+'" class="bm-page">'+esc(p.name)+'</button>';}).join(''):'<div class="bm-empty">Aucune autre page dans l\u2019arbre du workspace.<br>Ouvrez la page cible puis réessayez.</div>';
box.innerHTML='<div style="font-weight:600;font-size:14px;margin-bottom:10px;">Move block to…</div>'
+'<input type="text" class="bm-search" placeholder="Filter pages…" autofocus>'
+'<div class="bm-page-list">'+listHtml+'</div>'
+'<button type="button" data-cancel="1" class="bm-cancel">Annuler</button>';
box.addEventListener('click',function(ev){
var p=ev.target.closest('[data-pid]');
if(p){var pid=parseInt(p.getAttribute('data-pid'),10);ov.remove();E.moveBlockToPage(idx,pid);return;}
if(ev.target.closest('[data-cancel]')){ov.remove();}
});
box.addEventListener('input',function(ev){
if(ev.target.classList&&ev.target.classList.contains('bm-search')){
var q=(ev.target.value||'').toLowerCase();
box.querySelectorAll('.bm-page').forEach(function(b2){b2.style.display=(q&&b2.textContent.toLowerCase().indexOf(q)<0)?'none':'';});
}
});
ov.appendChild(box);
document.body.appendChild(ov);
}
function _blockDocDown(ev){
if(!(ev.target&&ev.target.closest))return;
if(ev.target.closest('#blockMenu')||ev.target.closest('#blockMenuSub')||ev.target.closest('#fdMoveOverlay'))return;
if(ev.target.closest('.block-handle'))return;
if(_selSet.size)_selClear();
_blockMenuClose();
}
window.E=null;
function editorState(){
return {
pid:null,pageTitle:'',blocks:[],dirty:false,saving:false,lastSaved:'',st:null,
updatedAt:'{{ page.get("updated_at", "") }}',
fmt:{open:false,top:0,left:0,idx:-1},
shareOpen:false,moreOpen:false,gsMoreOpen:false,accessMenuOpen:false,exportOpen:false,
aiBusy:false,
moveOpen:false,moveWorkspaces:[],moveLoading:false,
activityOpen:false,pageCreated:'{{ page.get("created_at", "") }}',
shareTab:'share',favorited:false,
pagePublished:{{ page_published | tojson }},
pageIsShared:{{ page_is_shared | tojson }},
generalAccess:'{{ page_share_mode }}',publicPerm:'viewer',
pubAllowEdit:false,pubAllowComments:true,
pageUrl:window.location.href,publishedUrl:'',
inviteEmail:'',invitePermission:'edit',inviteUserId:null,inviteUsers:[],inviteSuggestOpen:false,inviteSel:-1,accessList:[],
toastVisible:false,toastMsg:'',
// ── v4.9.0: Collaboration — comments & mentions ──
commentsOpen:false,commentDraft:'',comments:[],commentCount:0,
currentUserId:{{ user.get('id', 0) if user else 0 }},
_commentSel:{range:null,bid:null,start:0,end:0},
_notifiedMentions:{},
undoStack:[],redoStack:[],_histCap:100,_histLock:false,
get timeAgo() {
if (!this.updatedAt) return 'just now';
var diff = Math.floor((Date.now() - new Date(this.updatedAt + 'Z').getTime()) / 1000);
if (diff < 60) return 'just now';
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
return Math.floor(diff / 86400) + 'd ago';
},
formatDate(d) {
if (!d) return '';
var dt = new Date(d + 'Z');
return dt.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) +
' at ' + dt.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
},
init(){
_bid=Math.floor(Math.random()*10000);
const dataEl=document.getElementById('page-data');
if(dataEl){try{const data=JSON.parse(dataEl.textContent);this.pid=data.id;this.pageTitle=data.title||'';this.favorited=data.favorited||false;const fmt=data.content_format||'blocks';this.contentFormat=fmt;const raw=data.content||'';
if(fmt==='file'){this.fileData=data;this.loadFileContent(data);}
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;
this.insertTable=function(){this.sync();this.pushHistory();var nb={id:genId(),type:'table',content:'',rows:_tblNew(3,3)};this.blocks.push(nb);this.dirty=true;this.autoSave();this.render();_rtSync();setTimeout(function(){_tblFocus(nb.id,0,0);},60);};
SM.init();_tblInitDelegate();this.render();
const tEl=document.getElementById('_titleEl');
if(tEl){const syncT=()=>{tEl.classList.toggle('empty',!(tEl.textContent||'').trim());};syncT();tEl.addEventListener('input',syncT);tEl.addEventListener('blur',syncT);}
if(this.contentFormat==='blocks'&&window.__fdRT&&window.__fdRT.start){setTimeout(()=>{try{window.__fdRT.start(this);}catch(e){}},150);}
setTimeout(()=>{this.focusBlock();const ct=document.getElementById('_blocksCt');if(!ct)return;
ct.addEventListener('keydown',e=>this.onKd(e));
ct.addEventListener('paste',e=>this.handleSmartPaste(e));
if(typeof Sortable!=='undefined')Sortable.create(ct,{draggable:'.block-wrapper:not(.nested-block):not(.block-column-wrapper)',handle:'.block-handle',animation:150,ghostClass:'sortable-ghost',dragClass:'sortable-drag',onEnd:evt=>{this._dragEnd(evt);}});
ct.addEventListener('click',e=>{const h=e.target&&e.target.closest?e.target.closest('.block-handle'):null;if(!h)return;const w=h.closest('.block-wrapper');if(!w)return;const wid=w.getAttribute('data-id');if(!wid)return;
if(e.shiftKey){_selToggle(wid);return;}
_selClear();blockMenuOpen(e.clientX,e.clientY,wid);});
ct.addEventListener('contextmenu',e=>{if(!(e.target&&e.target.closest))return;if(e.target.closest('.ftable-cell')||e.target.closest('#_tblMenu'))return;const c=e.target.closest('.block-content, .block-wrapper, .block-column-wrapper');const el0=e.target.closest('[data-bid]');if(!c)return;const w=e.target.closest('.block-wrapper');const wid=w?w.getAttribute('data-id'):null;const bid=wid||(el0?el0.getAttribute('data-bid'):null);if(!bid)return;e.preventDefault();_selClear();blockMenuOpen(e.clientX,e.clientY,bid);});
document.addEventListener('mousedown',_blockDocDown);
if(window.location.hash&&window.location.hash.indexOf('#fdblk-')===0){const hb=window.location.hash.slice(7);setTimeout(()=>{const selEl=document.querySelector(`[data-bid="${hb}"]`);if(selEl&&selEl.scrollIntoView)selEl.scrollIntoView({behavior:'smooth',block:'center'});if(selEl){selEl.focus();ce(selEl);}},500);}
},100);
},
fileData:null,
async loadFileContent(data){
const url=data.file_url||'';const mime=data.file_mime||'';const name=data.file_name||'';const self=this;
if(!url){this.blocks=[this.mkB('paragraph','File not available')];this.render();return;}
const ext=name.toLowerCase().split('.').pop();
if(mime.startsWith('image/')||['png','jpg','jpeg','gif','webp','svg','bmp','ico'].includes(ext)){
this.blocks=[this.mkB('image','',{src:url,alt:name})];this.render();return;
}
if(mime==='application/pdf'||ext==='pdf'){
this.blocks=[this.mkB('embed','',{src:url,embed_type:'pdf'})];this.render();return;
}
const codeExts=['py','js','ts','jsx','tsx','html','htm','css','json','xml','yaml','yml','md','markdown','sql','sh','bash','ps1','bat','cmd','rb','go','rs','java','c','cpp','h','hpp','php','swift','kt','scala','r','toml','ini','cfg','conf','env','txt','log'];
const langMap={'py':'python','js':'javascript','ts':'typescript','jsx':'javascript','tsx':'typescript','html':'html','htm':'html','css':'css','json':'json','xml':'xml','yaml':'yaml','yml':'yaml','md':'markdown','markdown':'markdown','sql':'sql','sh':'bash','bash':'bash','ps1':'powershell','bat':'plaintext','cmd':'plaintext','rb':'ruby','go':'go','rs':'rust','java':'java','c':'c','cpp':'cpp','h':'c','hpp':'cpp','php':'php','swift':'swift','kt':'kotlin','scala':'scala','r':'r','toml':'toml','ini':'ini','cfg':'ini','conf':'ini','env':'ini','txt':'plaintext','log':'plaintext'};
if(mime.startsWith('text/')||codeExts.includes(ext)){
try{const r=await fetch(url);const text=await r.text();
if(ext==='md'||ext==='markdown'){this.blocks=this.md2b(text);}
else{this.blocks=[this.mkB('code',text,{language:langMap[ext]||'Plain Text'})];}
this.render();
}catch(e){this.blocks=[this.mkB('paragraph','Error loading file: '+e.message)];this.render();}
return;
}
this.blocks=[this.mkB('embed','',{src:url,embed_type:'download',file_name:name,file_size:data.file_size||0,file_mime:mime})];this.render();
},
mkB(type,content,extras){const b={id:genId(),type,content:content||'',...(extras||{})};if(type==='toggle')b.expanded=true;if(type==='to_do')b.checked=false;if(type==='columns')b.children=[{id:genId(),type:'paragraph',content:''}];if(type==='toggle')b.children=[];if(type==='button'){b.automation_id=0;b.automation_name='';}return b;},
getEl(bid){const ct=document.getElementById('_blocksCt');return ct?ct.querySelector(`[data-bid="${bid}"]`):null;},
getIdx(bid){return this.blocks.findIndex(b=>b.id===bid);},
getActiveBlock(){const el=document.activeElement;if(!el||!el.hasAttribute('data-bid'))return null;return{el,bid:el.dataset.bid,idx:this.getIdx(el.dataset.bid)};},
_dragEnd(evt){
const draggedId=evt.item&&evt.item.getAttribute?evt.item.getAttribute('data-id'):null;
const selected=_selIndicesSorted();
const group=_selSet.has(draggedId)&&selected.length>1;
if(evt.oldIndex===evt.newIndex&&!group)return;
this.sync();this.pushHistory();
const byId={};this.blocks.forEach(b=>{byId[b.id]=b;});
if(group){
const groupIds={};selected.forEach(i=>{const bb=this.blocks[i];if(bb)groupIds[bb.id]=1;});
const groupBlocks=selected.map(i=>this.blocks[i]).filter(Boolean);
const ct=document.getElementById('_blocksCt');
const domIds=[];ct.querySelectorAll('.block-wrapper[data-id]').forEach(w=>{const id=w.getAttribute('data-id');if(byId[id])domIds.push(id);});
let inserted=false;const out=[];
for(let i=0;i<domIds.length;i++){
const id=domIds[i];
if(groupIds[id]){if(!inserted){groupBlocks.forEach(gb=>out.push(gb));inserted=true;}continue;}
out.push(byId[id]);
}
if(!inserted)groupBlocks.forEach(gb=>out.push(gb));
if(out.length)this.blocks=out;
}else{
const[item]=this.blocks.splice(evt.oldIndex,1);this.blocks.splice(evt.newIndex,0,item);
}
this.dirty=true;this.autoSave();this.render();_rtSync();
},
focusBlock(){const b=this.blocks[0];if(!b)return;const el=this.getEl(b.id);if(el){el.focus();ce(el);}},
onCtClick(e){if(e.target===document.getElementById('_blocksCt')||e.target?.classList?.contains('blocks-empty')){if(!this.blocks.length)this.addAt(0);else{const b=this.blocks[this.blocks.length-1];if(b&&(b.type==='table'||b.type==='divider'||b.type==='image'||b.type==='embed'||b.type==='button')){this.addAt(this.blocks.length,'paragraph');}else{const el=b&&this.getEl(b.id);if(el){el.focus();ce(el);}}}}},
sync(){const ct=document.getElementById('_blocksCt');if(!ct)return;for(const b of this.blocks){if(b.type==='table'){const wrap=ct.querySelector(`[data-bid="${b.id}"].ftable-editor`);if(wrap){const rows=[];wrap.querySelectorAll('tr.ftable-row').forEach(function(tr){const cells=tr.querySelectorAll('th.ftable-cell,td.ftable-cell');if(!cells.length)return;const rowarr=[];cells.forEach(function(cell){rowarr.push(cell.textContent||'');});rows.push(rowarr);});if(rows.length)b.rows=rows;}continue;}
if(b.type==='divider'||b.type==='image'||b.type==='embed'||b.type==='table_of_contents'||b.type==='button')continue;
if(b.type==='math'){const te=ct.querySelector(`[data-math-bid="${b.id}"]`);if(te)b.content=te.value||'';continue;}
const el=ct.querySelector(`[data-bid="${b.id}"]`);if(el&&!el.classList.contains('toggle-title'))b.content=gt(el)||'';else if(el)b.content=gt(el)||'';
if(b.type==='toggle'&&b.children)b.children.forEach(function(ch){const che=ct.querySelector(`[data-bid="${ch.id}"]`);if(che)ch.content=gt(che)||'';});
if(b.type==='columns'&&b.children)b.children.forEach(function(ch){const che=ct.querySelector(`[data-bid="${ch.id}"]`);if(che)ch.content=gt(che)||'';});
}},
render(){const ct=document.getElementById('_blocksCt');if(!ct)return;let html='';for(let i=0;i<this.blocks.length;i++)html+=renderBlock(this.blocks[i],i);ct.innerHTML=html;
// Render math blocks with KaTeX
var self=this;
ct.querySelectorAll('.math-display').forEach(function(el){var bid=el.dataset.bid;var b=self.blocks.find(function(x){return x.id===bid;});self._renderKatex(el,b?b.content:'');});
ct.querySelectorAll('.math-editor').forEach(function(ta){var disp=ct.querySelector(`.math-display[data-bid="${ta.dataset.mathBid}"]`);ta.addEventListener('input',function(){self._renderKatex(disp,ta.value);self.dirty=true;self.autoSave();});});
// Initialize inline database blocks (scripts in innerHTML don't execute)
setTimeout(()=>{ct.querySelectorAll('.block-embed-collection').forEach(el=>{const cid=parseInt(el.id.replace('inline-db-',''));if(cid&&window.FlowDeckDB)window.FlowDeckDB.renderInto(el,cid,null);});},50);
// Initialize AI Meeting Note widgets (block type 'meeting')
setTimeout(()=>{ct.querySelectorAll('[data-meeting-bid]').forEach(function(el){if(window.FDMeeting)window.FDMeeting.mount(el.getAttribute('data-meeting-bid'),el);});},50);
ct.querySelectorAll('[data-bid]').forEach(el=>{el.addEventListener('input',()=>{self.dirty=true;self.autoSave();if(el.hasAttribute&&el.hasAttribute('data-placeholder'))el.classList.toggle('empty',!(el.textContent||'').trim());if(window.AIAC)window.AIAC.schedule(el.getAttribute('data-bid'));})});
ct.querySelectorAll('[data-placeholder]').forEach(el=>{el.innerHTML=el.innerHTML.replace(/<br[^>]*>/gi,'').trim()!==''?el.innerHTML:'';el.classList.toggle('empty',!el.textContent.trim());});
_selApply();
},
_renderKatex(el,tex){
if(!el)return;if(!tex||!tex.trim()){el.innerHTML='<span style="color:var(--text-dim);font-style:italic;">Empty math block</span>';return;}
if(window.katex){try{el.innerHTML=window.katex.renderToString(tex,{displayMode:true,throwOnError:false});}catch(e){el.innerHTML='<span style="color:#e5484d;">Invalid LaTeX</span>';}}
else{el.innerHTML='<span>'+esc(tex)+'</span>';}
},
replaceBlock(idx,type){const b=this.blocks[idx];if(!b)return;this.sync();this.pushHistory();b.type=type;b.content='';if(type==='toggle'){b.expanded=true;b.children=[];}if(type==='to_do')b.checked=false;if(type==='columns')b.children=[{id:genId(),type:'paragraph',content:''}];if(type==='math'){}if(type==='button'){b.automation_id=0;b.automation_name='';}if(type==='embed'){b.embed_type='';b.src='';b.height=520;}if(type==='bookmark'){b.url='';b.title='';b.description='';b.image='';b.site_name='';}if(type==='video'){b.src='';b.title='';}if(type==='audio'){b.src='';b.title='';}this.render();this.dirty=true;this.autoSave();_rtSync();setTimeout(()=>{const el=this.getEl(b.id);if(el)el.focus();},60);},
addAt(idx,type,content){this.sync();this.pushHistory();const b=this.mkB(type||'paragraph',content||'');this.blocks.splice(idx,0,b);this.dirty=true;this.autoSave();this.render();_rtSync();setTimeout(()=>{const el=this.getEl(b.id);if(el&&type!=='divider'){el.focus();content&&ce(el);}},60);return b;},
addAfter(idx,type){return this.addAt(idx+1,type);},
removeBlock(idx){this.sync();this.pushHistory();if(this.blocks.length<=1){this.blocks[0]=this.mkB('paragraph','');}else{this.blocks.splice(idx,1);}const fi=Math.min(idx,this.blocks.length-1);this.render();this.dirty=true;this.autoSave();this._rtSync();setTimeout(()=>{const el=this.getEl(this.blocks[fi]?.id);if(el){el.focus();ce(el);}},60);},
// ── v5.4.0/v5.5.0: Media block URL setters (click empty block → prompt) ──
_mediaPrompt(bid, field, label){
const idx=this.getIdx(bid); if(idx<0)return;
const b=this.blocks[idx]; if(!b)return;
const cur=b[field]||''; const u=prompt(label+' (URL):', cur);
if(u===null)return;
if(u.trim()===cur.trim())return;
this.sync();this.pushHistory();b[field]=u.trim();this.dirty=true;this.autoSave();this.render();_rtSync();
},
setEmbedUrl(bid){this._mediaPrompt(bid,'src','Embed');},
setBookmarkUrl(bid){
const idx=this.getIdx(bid); if(idx<0)return;
const b=this.blocks[idx]; if(!b)return;
const cur=b.url||''; const u=prompt('Bookmark URL:', cur);
if(u===null)return;
if(u.trim()===cur.trim())return;
this.sync();this.pushHistory();b.url=u.trim();
this._fetchBookmarkPreview(bid, u.trim());
},
async _fetchBookmarkPreview(bid, url){
try{
const r=await fetch('/api/og/metadata',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrfTok()},body:JSON.stringify({url})});
const d=await r.json();
if(d&&d.ok){
const idx=this.getIdx(bid); if(idx<0)return;
const b=this.blocks[idx];
if(d.title)b.title=d.title;
if(d.description)b.description=d.description;
if(d.image)b.image=d.image;
if(d.site_name)b.site_name=d.site_name;
this.render();this.dirty=true;this.autoSave();_rtSync();
}
}catch(e){}
},
setVideoSrc(bid){this._mediaPrompt(bid,'src','Video');},
setAudioSrc(bid){this._mediaPrompt(bid,'src','Audio');},
// ── v5.5.0: Image lightbox ──
_openLightbox(imgEl){
const full=imgEl.dataset.full||imgEl.src;
if(!full)return;
const ov=document.createElement('div');ov.className='fd-lightbox';ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.95);z-index:5000;display:flex;align-items:center;justify-content:center;flex-direction:column;';
ov.innerHTML='<img src="'+full+'" style="max-width:95vw;max-height:90vh;border-radius:4px;box-shadow:0 0 40px rgba(0,0,0,.8);"><div style="margin-top:16px;color:var(--text-dim);font-size:13px;">Esc to close</div>';
document.body.appendChild(ov);
const close=()=>{document.body.removeChild(ov);document.removeEventListener('keydown',kd);};
const kd=(e)=>{if(e.key==='Escape')close();};
document.addEventListener('keydown',kd);
ov.addEventListener('click',e=>{if(e.target===ov)close();});
},
// ── v5.4.0: Version history UI ──
versionsOpen:false,versionsLoading:false,versionsList:[],
async toggleVersions(){
this.versionsOpen=!this.versionsOpen;
if(this.versionsOpen){await this.loadVersions();}
},
async loadVersions(){
this.versionsLoading=true;
try{
const r=await fetch('/api/pages/'+this.pid+'/versions');
const d=await r.json();
this.versionsList=d.versions||[];
}catch(e){this.versionsList=[];}
this.versionsLoading=false;
},
async restoreVersion(vid){
if(!confirm('Restore this version? Current content will be replaced.'))return;
try{
const r=await fetch('/api/pages/'+this.pid+'/versions/'+vid+'/restore',{method:'POST',headers:{'X-CSRF-Token':this.csrfTok()}});
const d=await r.json();
if(d.status==='ok'){
this.showToast('Version restored');this.versionsOpen=false;
setTimeout(()=>window.location.reload(),300);
}
}catch(e){this.showToast('Restore failed','error');}
},
// ── v5.4.0: Backlinks ──
backlinksOpen:false,backlinksLoading:false,backlinksList:[],
async toggleBacklinks(){
this.backlinksOpen=!this.backlinksOpen;
if(this.backlinksOpen){await this.loadBacklinks();}
},
async loadBacklinks(){
this.backlinksLoading=true;
try{
const r=await fetch('/api/pages/'+this.pid+'/backlinks');
const d=await r.json();
this.backlinksList=d.backlinks||[];
}catch(e){this.backlinksList=[];}
this.backlinksLoading=false;
},
// ── v5.5.0: Cover & Icon ──
coverOpen:false,coverUrl:'',coverLoading:false,iconOpen:false,iconValue:'',
async loadCoverIcon(){
try{
const r=await fetch('/board/api/pages/'+this.pid);
const d=await r.json();
this.coverUrl=d.cover_url||'';
this.iconValue=d.page_icon||'';
}catch(e){}
},
toggleCover(){this.coverOpen=!this.coverOpen;if(this.coverOpen&&!this.coverUrl){this.loadCoverIcon();}},
async setCoverUrl(){
const u=prompt('Cover image URL:', this.coverUrl);
if(u===null)return;
this.coverLoading=true;
try{
const r=await fetch('/api/pages/'+this.pid+'/cover',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrfTok()},body:JSON.stringify({cover_url:u})});
const d=await r.json();
if(d.status==='ok'){this.coverUrl=d.cover_url;this.coverOpen=false;this.showToast('Cover updated');}
}catch(e){this.showToast('Cover update failed','error');}
this.coverLoading=false;
},
async uploadCover(e){
const f=e.target.files?.[0];if(!f)return;
this.coverLoading=true;
const fd=new FormData();fd.append('file',f);
try{
const r=await fetch('/api/pages/'+this.pid+'/cover',{method:'POST',headers:{'X-CSRF-Token':this.csrfTok()},body:fd});
const d=await r.json();
if(d.status==='ok'){this.coverUrl=d.cover_url;this.coverOpen=false;this.showToast('Cover uploaded');}
}catch(e){this.showToast('Cover upload failed','error');}
this.coverLoading=false;
e.target.value='';
},
async removeCover(){
if(!confirm('Remove cover?'))return;
try{
const r=await fetch('/api/pages/'+this.pid+'/cover',{method:'DELETE',headers:{'X-CSRF-Token':this.csrfTok()}});
const d=await r.json();
if(d.status==='ok'){this.coverUrl='';this.showToast('Cover removed');}
}catch(e){this.showToast('Remove failed','error');}
},
toggleIcon(){this.iconOpen=!this.iconOpen;},
async setIcon(icon){
this.iconValue=icon;
try{
const r=await fetch('/api/pages/'+this.pid+'/icon',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrfTok()},body:JSON.stringify({icon})});
const d=await r.json();
if(d.status==='ok'){this.iconOpen=false;this.showToast('Icon updated');}
}catch(e){this.showToast('Icon update failed','error');}
},
// ── v5.4.0: Import ──
importOpen:false,importFile:null,importText:'',
toggleImport(){this.importOpen=!this.importOpen;},
handleImportFile(e){
const f=e.target.files?.[0];if(!f)return;
this.importFile=f;
const reader=new FileReader();
reader.onload=()=>{this.importText=reader.result;};
reader.readAsText(f);
},
async doImport(){
if(!this.importText.trim()&&!this.importFile){this.showToast('Nothing to import');return;}
if(this.importFile){
const fd=new FormData();fd.append('file',this.importFile);
try{
const r=await fetch('/api/pages/import/file',{method:'POST',headers:{'X-CSRF-Token':this.csrfTok()},body:fd});
const d=await r.json();
if(d.status==='ok'){this.showToast(d.count+' page(s) imported');this.importOpen=false;this.importFile=null;this.importText='';}
}catch(e){this.showToast('Import failed','error');}
}else{
try{
const r=await fetch('/api/pages/import',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrfTok()},body:JSON.stringify({markdown:this.importText,title:this.pageTitle||'Import'})});
const d=await r.json();
if(d.status==='ok'){this.showToast('Page imported');this.importOpen=false;this.importText='';setTimeout(()=>{window.location.href='/pages/'+d.id;},400);}
}catch(e){this.showToast('Import failed','error');}
}
},
// ── v5.10.0: Undo / Redo ──
pushHistory(){if(this._histLock)return;this.undoStack.push(JSON.parse(JSON.stringify(this.blocks)));if(this.undoStack.length>this._histCap)this.undoStack.shift();this.redoStack.length=0;},
undo(){if(!this.undoStack.length){this.showToast('Rien à annuler');return;}const fid=this._focusedBid();this.redoStack.push(JSON.parse(JSON.stringify(this.blocks)));this.applySnapshot(this.undoStack.pop());this._focusBid(fid);},
redo(){if(!this.redoStack.length){this.showToast('Rien à rétablir');return;}const fid=this._focusedBid();this.undoStack.push(JSON.parse(JSON.stringify(this.blocks)));this.applySnapshot(this.redoStack.pop());this._focusBid(fid);},
applySnapshot(snap){this._histLock=true;this.blocks=snap||[];if(!this.blocks.length)this.blocks=[this.mkB('paragraph','')];this.dirty=true;this.autoSave();this.render();this._histLock=false;_rtSync();},
_focusedBid(){const a=this.getActiveBlock();return a?a.bid:null;},
_focusBid(bid){if(bid)setTimeout(()=>{const el=this.getEl(bid);if(el){el.focus();ce(el);}},60);},
_rtSync(){_rtSync();},
// ── v5.10.0: Duplicate ──
_deepCloneBlock(b){const c=JSON.parse(JSON.stringify(b));c.id=genId();if(Array.isArray(c.children)){c.children=c.children.map(ch=>{const n=JSON.parse(JSON.stringify(ch));n.id=genId();return n;});}return c;},
duplicateBlock(idx){if(idx<0||idx>=this.blocks.length)return;const sel=_selIndicesSorted();if(sel.length>1&&sel.includes(idx)){this.duplicateSelected();return;}this.sync();this.pushHistory();const copy=this._deepCloneBlock(this.blocks[idx]);this.blocks.splice(idx+1,0,copy);this.dirty=true;this.autoSave();this.render();_rtSync();this._focusBid(copy.id);_selClear();return copy;},
duplicateSelected(){const indices=_selIndicesSorted();if(!indices.length)return;this.sync();this.pushHistory();const copies=indices.map(i=>this._deepCloneBlock(this.blocks[i]));const pos=indices[indices.length-1]+1;this.blocks.splice(pos,0,...copies);this.dirty=true;this.autoSave();this.render();_rtSync();this._focusBid(copies[0].id);_selClear();},
// ── v5.10.0: Turn into / couleurs / liens / déplacement ──
_blockTargets(idx){const sel=_selIndicesSorted();return(sel.includes(idx))?sel:[idx];},
turnInto(idx,type){if(idx<0||idx>=this.blocks.length)return;if(_BLOCK_TURN_TYPES.indexOf(type)<0)return;const targets=this._blockTargets(idx);const focusId=this.blocks[idx].id;this.sync();this.pushHistory();targets.forEach(i=>{const b=this.blocks[i];if(!b)return;if(b.type===type)return;const keep=(b.content||'').trim();b.type=type;if(type!=='to_do'&&type!=='toggle'&&keep&&['image','embed','meeting','divider','table_of_contents','database','button','columns'].indexOf(b.type)<0){b.content=keep;}else{b.content='';}if(type==='toggle'){b.expanded=true;if(!Array.isArray(b.children))b.children=[];}if(type==='to_do')b.checked=false;if(type==='table'&&!Array.isArray(b.rows))b.rows=_tblNew(3,3);if(type==='code'&&!b.language)b.language='Plain Text';if(type==='columns'&&(!Array.isArray(b.children)||!b.children.length))b.children=[{id:genId(),type:'paragraph',content:''}];if(type==='button'){b.automation_id=0;b.automation_name='';}});
this.dirty=true;this.autoSave();this.render();_rtSync();_selClear();
setTimeout(()=>{const bid=this.blocks[idx]?this.blocks[idx].id:focusId;if(type==='table'){_tblFocus(bid,0,0);}else{const el=this.getEl(bid);if(el){el.focus();ce(el);}}},60);},
setBlockColor(idx,key,val){if(idx<0||idx>=this.blocks.length)return;const targets=this._blockTargets(idx);const v=(!val||val==='default')?null:val;this.sync();this.pushHistory();targets.forEach(i=>{const b=this.blocks[i];if(!b)return;if(!b.style)b.style={};if(v==null)delete b.style[key];else b.style[key]=v;});this.dirty=true;this.autoSave();this.render();_rtSync();_selClear();},
copyBlockLink(bid){const url=window.location.origin+window.location.pathname+'#fdblk-'+bid;if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(url).then(()=>this.showToast('Lien du bloc copié')).catch(()=>this.showToast('Copie impossible','error'));}else{this.showToast(url,'error');}},
deleteSelected(idx){const indices=_selIndicesSorted();if(indices.length<=1)return this.removeBlock(idx);this.sync();this.pushHistory();const keep=this.blocks.filter((b,i)=>indices.indexOf(i)<0);this.blocks=keep.length?keep:[this.mkB('paragraph','')];const fi=Math.min(idx,this.blocks.length-1);this.dirty=true;this.autoSave();this.render();_rtSync();_selClear();setTimeout(()=>{const el=this.getEl(this.blocks[fi]?.id);if(el){el.focus();ce(el);}},60);},
async moveBlockToPage(idx,pageId){if(idx<0||idx>=this.blocks.length||pageId===this.pid)return;this.sync();this.pushHistory();const block=this.blocks[idx];const self=this;const csrf=document.cookie.match(/csrf_token=([^;]+)/);this.showToast('Déplacement du bloc…');try{const r=await fetch(`/board/api/pages/${pageId}`,{credentials:'same-origin'});const d=await r.json();let tblocks=[];if(d.content_format==='blocks'&&d.content){try{tblocks=JSON.parse(d.content);}catch(e){tblocks=[];}}tblocks.push(block);await fetch(`/board/api/pages/${pageId}/blocks`,{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''},body:JSON.stringify({title:d.title||'',blocks:tblocks})});
if(this.blocks.length<=1){this.blocks[0]=this.mkB('paragraph','');}else{this.blocks.splice(idx,1);}this.dirty=true;this.autoSave();this.render();_rtSync();_selClear();this.showToast('Bloc déplacé');
}catch(e){this.showToast('Échec du déplacement','error');}},
// ── v4.11.1: Inline AI — "Ask AI" & "AI meeting note" (Get started) ──
// Wire the contextual get-started actions into the FlowDeck Agent
// (Notion-style inline generation). The reply is streamed in the agent
// panel and inserted into this page via window.__fdInsertAI.
askAI(){
const self=this;
const title=(this.pageTitle||'Sans titre').trim();
if(!(window.fdAgent && window.fdAgent.generate)){ self.showToast('Agent IA indisponible — configurez-le dans Paramètres → Agent & IA'); return; }
if(self.aiBusy) return;
self.aiBusy=true;
self.showToast('FlowDeck Agent : génération en cours…');
let ctx='';
try{ if(window.fdGetPageContext) ctx=String(window.fdGetPageContext()||''); }catch(e){}
const prompt='Rédige le contenu de la page FlowDeck intitulée « '+title+' ». Fournis un texte structuré et directement utilisable : paragraphes et, si utile, des listes à puces. Sépare chaque paragraphe par une ligne vide. Ne précède pas le texte de commentaires.';
window.fdAgent.generate(prompt, ctx).then(function(text){
self.aiBusy=false;
if(text && text.trim()){ self.applyAIBlocks(text); }
else { self.showToast('Aucun retour généré par l\u2019agent'); }
}).catch(function(err){
self.aiBusy=false;
self.showToast((err && err.message) ? err.message : 'Erreur de génération');
});
},
meetingNote(){
// v4.12.0: crée une vraie « boîte de réunion » (bloc 'meeting') au lieu
// d'insérer du texte. La page est renommée « Meeting @date » si elle
// est vide, puis le widget est ajouté (ou remplace le bloc vide initial).
var self=this;
this.gsMoreOpen=false;
var today=new Date();
var iso=today.toISOString().slice(0,10);
var title='Meeting @'+iso;
var tEl=document.getElementById('_titleEl');
var curTitle=tEl?(tEl.textContent||'').trim():'';
var blank=!curTitle||curTitle==='Untitled'||curTitle==='New page';
if(blank){
this.pageTitle=title;
if(tEl)tEl.textContent=title;
}
var mb=this.mkB('meeting','');
mb.meeting={
title:title,
date:iso,
consent:'self',
started:false,
status:'idle',
lines:[],
interim:'',
notes:'',
summary:'',
summaryError:'',
lastTab:'transcript'
};
this.sync();this.pushHistory();
var emptyFirst=this.blocks.length===1 && this.blocks[0].type==='paragraph' && !(this.blocks[0].content||'').trim();
if(emptyFirst){ this.blocks=[mb]; } else { this.blocks.push(mb); }
this.dirty=true; this.autoSave(); this.render();
this.showToast('AI Meeting Note : bloc de réunion ajouté. Cliquez sur « Start transcribing ».');
var bid=mb.id;
setTimeout(function(){ var el=document.querySelector('[data-meeting-bid="'+bid+'"]'); if(el&&el.scrollIntoView)el.scrollIntoView({behavior:'smooth',block:'center'}); if(el&&window.FDMeeting)window.FDMeeting.focus(bid); },120);
},
aiRun(prompt){
const self=this;
if(self.aiBusy) return;
if(!(window.fdAgent && window.fdAgent.ask)){ self.showToast('Agent IA indisponible — configurez-le dans Paramètres → Agent & IA'); return; }
self.aiBusy=true;
window.__fdInsertAI = function(content){ window.__fdInsertAI=null; self.aiBusy=false; self.applyAIBlocks(content); };
self.showToast('FlowDeck Agent : génération en cours…');
window.fdAgent.ask(prompt);
},
applyAIBlocks(text){
// Convert agent markdown → real blocks (headings, lists, tables, code…)
// so it renders naturally (Notion-style) instead of showing raw `## …`.
const nb=this.md2b(text||'');
if(!nb.length) return;
this.sync();
const startEmpty=this.blocks.length===1 && this.blocks[0].type==='paragraph' && !(this.blocks[0].content||'').trim();
this.blocks = startEmpty ? nb : this.blocks.concat(nb);
this.dirty=true; this.autoSave(); this.render();
},
toggleFavorite(){
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
const m=this.favorited?'DELETE':'POST';
fetch(`/board/api/favorites/${this.pid}`,{method:m,headers:{'X-CSRF-Token':csrf?csrf[1]:''}})
.then(r=>r.json()).then(()=>{this.favorited=!this.favorited;this.showToast(this.favorited?'Added to favorites':'Removed from favorites');
if(window.appState&&window.appState.refreshFavorites)window.appState.refreshFavorites();
}).catch(()=>{this.showToast('Failed to toggle favorite');});
},
toggleActivityOpen(){
if(!this.activityOpen){this.activityOpen=true;this._justOpened=true;setTimeout(()=>this._justOpened=false,200);}
else{this.activityOpen=false;}
},
toggleShareOpen(){
if(!this.shareOpen){this.shareOpen=true;this._justOpened=true;setTimeout(()=>this._justOpened=false,200);this.loadShares();}
else{this.shareOpen=false;}
},
toggleMoreOpen(){
if(!this.moreOpen){this.moreOpen=true;this._justOpened=true;setTimeout(()=>this._justOpened=false,200);}
else{this.moreOpen=false;}
},
_justOpened:false,
_syncIsShared(){this.pageIsShared=!!(this.pagePublished||this.generalAccess==='anyone'||(this.accessList||[]).length>0);},
togglePublish(){this.pagePublished=!this.pagePublished;this._syncIsShared();this.saveShare();},
saveShare(){
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
fetch(`/board/api/share/${this.pid}`,{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''},body:JSON.stringify({mode:this.generalAccess,published:this.pagePublished})}).then(()=>this._syncIsShared()).catch(()=>{});
},
async copyPageLink(){
console.log('copyPageLink invoked');
const url = this.pageUrl;
const fallback = () => {
const ta = document.createElement('textarea');
ta.value = url; ta.style.position='fixed'; ta.style.opacity='0';
document.body.appendChild(ta); ta.select();
document.execCommand('copy'); document.body.removeChild(ta);
};
try {
await navigator.clipboard.writeText(url);
this.showToast('Link copied to clipboard');
} catch(e) { fallback(); this.showToast('Link copied to clipboard'); }
},
showToast(msg){
this.toastMsg = msg; this.toastVisible = true;
clearTimeout(this._toastTimer);
this._toastTimer = setTimeout(() => { this.toastVisible = false; }, 2500);
},
publishPage() {
var self = this;
var csrf = document.cookie.match(/csrf_token=([^;]+)/);
fetch('/api/pages/' + this.pid + '/publish', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf ? csrf[1] : '' }
}).then(function(r){ return r.json(); }).then(function(d){
if (d.is_published) {
self.pagePublished = true;
self.publishedUrl = window.location.origin + '/p/' + d.publish_slug;
self.showToast('Published to web — ' + self.publishedUrl);
} else {
self.showToast(d.detail || 'Publish failed');
}
}).catch(function(){ self.showToast('Publish failed'); });
},
unpublishPage() {
var self = this;
var csrf = document.cookie.match(/csrf_token=([^;]+)/);
fetch('/api/pages/' + this.pid + '/publish', {
method: 'DELETE',
headers: { 'X-CSRF-Token': csrf ? csrf[1] : '' }
}).then(function(r){ return r.json(); }).then(function(d){
if (!d.is_published) {
self.pagePublished = false;
self.publishedUrl = '';
self.showToast('Unpublished');
} else {
self.showToast(d.detail || 'Unpublish failed');
}
}).catch(function(){ self.showToast('Unpublish failed'); });
},
shareInvite() {
var self = this;
if (!this.inviteEmail.trim()) return;
var csrf = document.cookie.match(/csrf_token=([^;]+)/);
fetch('/api/pages/' + this.pid + '/share', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf ? csrf[1] : '' },
body: JSON.stringify({ user_id: this.inviteUserId, email: this.inviteEmail.trim(), permission: this.invitePermission })
}).then(function(r){ return r.json(); }).then(function(d){
if (d.status === 'shared') {
self.inviteEmail = ''; self.inviteUserId = null; self.inviteUsers = []; self.inviteSuggestOpen = false;
self.showToast('Invitation sent'); self.loadShares();
} else { self.showToast(d.detail || 'Share failed'); }
}).catch(function(){ self.showToast('Share failed'); });
},
inviteInput(){
var self = this;
var q = (this.inviteEmail || '').trim();
if (!q) { this.inviteUsers = []; this.inviteSuggestOpen = false; this.inviteUserId = null; this.inviteSel = -1; return; }
clearTimeout(this._inviteTimer);
this._inviteTimer = setTimeout(function(){ self._loadInviteUsers(q); }, 150);
},
async _loadInviteUsers(q){
try {
const r = await fetch('/api/notifications/users/search?q=' + encodeURIComponent(q), { credentials: 'same-origin' });
const d = await r.json();
var me = this.currentUserId;
var shared = {};
(this.accessList || []).forEach(function(a){ if (a.user_login) shared[a.user_login.toLowerCase()] = true; });
this.inviteUsers = (d.users || []).filter(function(u){
return u.id !== me && !shared[(u.login || '').toLowerCase()];
});
this.inviteSel = this.inviteUsers.length ? 0 : -1;
this.inviteSuggestOpen = true;
} catch(e) { this.inviteUsers = []; this.inviteSuggestOpen = false; }
},
inviteMove(dir){
var el = this.$refs.inviteSuggest;
var n = this.inviteUsers.length;
if (!n) return;
this.inviteSel = (this.inviteSel + dir + n) % n;
if (el) {
var item = el.querySelectorAll('.sd-user-item')[this.inviteSel];
if (item) item.scrollIntoView({ block: 'nearest' });
}
},
inviteEnter(){
if (this.inviteSuggestOpen && this.inviteSel >= 0 && this.inviteUsers[this.inviteSel]) {
this.pickInviteUser(this.inviteUsers[this.inviteSel]);
} else {
this.inviteSuggestOpen = false;
this.shareInvite();
}
},
pickInviteUser(u){
this.inviteUserId = u.id;
this.inviteEmail = u.login || u.full_name || '';
this.inviteUsers = []; this.inviteSuggestOpen = false; this.inviteSel = -1;
var el = this.$refs.inviteInput;
if (el) el.focus();
},
updateShare(sid, perm) {
var self = this;
var csrf = document.cookie.match(/csrf_token=([^;]+)/);
fetch('/api/pages/' + this.pid + '/share/' + sid, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf ? csrf[1] : '' },
body: JSON.stringify({ permission: perm })
}).then(function(r){ return r.json(); }).then(function(d){
self.showToast(d.status === 'updated' ? 'Permission updated' : (d.detail || 'Update failed'));
self.loadShares();
}).catch(function(){ self.showToast('Update failed'); self.loadShares(); });
},
loadShares() {
var self = this;
fetch('/api/pages/' + this.pid + '/shares').then(function(r){ return r.json(); }).then(function(d){
self.accessList = d.shares || [];
self._syncIsShared();
}).catch(function(){ self.accessList = []; self._syncIsShared(); });
},
removeShare(sid) {
var self = this;
var csrf = document.cookie.match(/csrf_token=([^;]+)/);
fetch('/api/pages/' + this.pid + '/share/' + sid, {
method: 'DELETE',
headers: { 'X-CSRF-Token': csrf ? csrf[1] : '' }
}).then(function(r){ return r.json(); }).then(function(d){
if (d.status === 'removed') { self.showToast('Share removed'); self.loadShares(); }
else { self.showToast(d.detail || 'Remove failed'); }
}).catch(function(){ self.showToast('Remove failed'); });
},
invitePerson(){if(!this.inviteEmail.trim())return;this.accessList.push({email:this.inviteEmail,permission:this.invitePermission,permOpen:false});this.inviteEmail='';},
removeAccess(email){this.accessList=this.accessList.filter(a=>a.email!==email);},
exportPage(fmt){
this.moreOpen=false;
this.exportOpen=false;
// Ensure latest blocks are persisted before exporting any format
var csrf = document.cookie.match(/csrf_token=([^;]+)/);
var self = this;
var doDownload = function(){
var title = encodeURIComponent((self.pageTitle || 'Untitled').trim() || 'Untitled');
var a = document.createElement('a');
a.href = '/api/export/' + (fmt==='markdown'?'markdown':fmt==='html'?'html':fmt==='pdf'?'pdf':'site') + '/' + self.pid;
a.download = '';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
var label = {markdown:'Markdown',html:'HTML',pdf:'PDF',site:'Site'}[fmt]||'';
self.showToast('Exported as ' + label);
};
if (this.dirty) {
this.save(function(){ doDownload(); });
} else {
doDownload();
}
},
duplicatePage(){this.moreOpen=false;const csrf=document.cookie.match(/csrf_token=([^;]+)/);const t=(this.pageTitle||'').trim()||'New Page';fetch(`/board/api/pages?title=${encodeURIComponent(t+' copy')}&section=Private&project=${encodeURIComponent('{{ workspace_key }}')}`,{method:'POST',headers:{'X-CSRF-Token':csrf?csrf[1]:''}}).then(r=>r.json()).then(d=>{window.location.href=`/pages/${d.id}`;}).catch(()=>{window.showToast('Duplicate failed','error');});},
movePage(){
this.moreOpen=false;
this.moveOpen = true;
this.moveLoading = true;
var self = this;
fetch('/api/workspaces')
.then(function(r){ return r.json(); })
.then(function(d){
self.moveWorkspaces = d.workspaces || [];
self.moveLoading = false;
})
.catch(function(){ self.moveLoading = false; });
},
doMove(wsId) {
this.moveOpen = false;
var csrf = document.cookie.match(/csrf_token=([^;]+)/);
var self = this;
fetch('/api/pages/' + this.pid + '/move', {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf ? csrf[1] : '' },
body: JSON.stringify({ workspace_id: wsId })
}).then(function(r){ return r.json(); })
.then(function(d){
if (d.status === 'ok') {
self.showToast('Moved to workspace');
setTimeout(function(){ window.location.href = '/local-workspace'; }, 800);
} else {
self.showToast(d.detail || 'Move failed');
}
})
.catch(function(){ self.showToast('Move failed'); });
},
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.6.0 / v5.3.0: Get Started actions — Database (template picker) ──
dbTplOpen:false, dbTplLoading:false, dbTemplates:[], dbTplTargetIdx:-1,
async createDatabase(){
this.openDbTemplatePicker(-1);
},
async openDbTemplatePicker(targetIdx){
this.gsMoreOpen=false;
this.dbTplOpen=true; this.dbTplTargetIdx=targetIdx; this.dbTplLoading=true;
var self=this;
try{
const r=await fetch('/workspace/templates/database');
const d=await r.json();
self.dbTemplates=d.templates||[];
}catch(e){ self.dbTemplates=[]; }
self.dbTplLoading=false;
self.$nextTick(()=>{ const f=document.getElementById('fd-dbtpl'); if(f) setTimeout(()=>{f.style.opacity='1';},10); });
},
closeDbTemplatePicker(){ this.dbTplOpen=false; },
async createDbFromTemplate(tplName){
const self=this;
var name = tplName ? (tplName+' — '+new Date().toLocaleDateString('fr-FR')) : 'Database';
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
try{
const body={name:name, parent_page_id:this.pid};
if(tplName) body.template=tplName;
const r=await fetch('/db/inline/api',{
method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''},
body:JSON.stringify(body)
});
const d=await r.json();
if(d.id){
const nb=this.mkB('embed','',{embed_type:'collection',collection_id:d.id});
this.sync();
const idx=this.dbTplTargetIdx>=0?this.dbTplTargetIdx:(this.blocks.length);
this.blocks.splice(idx,0,nb);
this.dbTplOpen=false;
this.dirty=true; this.autoSave(); this.render();
this.showToast(tplName?('Database created from template — '+tplName):'Database created');
}else{
this.showToast(d.detail||'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;
if(SM.isOpen){
if(e.key==='Escape'){e.preventDefault();SM.close();return;}
if(e.key==='ArrowDown'||e.key==='ArrowUp'||e.key==='Enter'){e.preventDefault();return;}
if(e.key==='Backspace'&&!SM._q&&(el.textContent||'').trim()==='/'){e.preventDefault();SM.close();return;}
return;
}
if(window.AIAC&&window.AIAC.active){
if(e.key==='Tab'){e.preventDefault();e.stopPropagation();window.AIAC.accept();return;}
if(e.key==='Escape'){e.preventDefault();window.AIAC.clear();return;}
if(e.key==='Enter'||e.key==='Backspace'){window.AIAC.clear();}
}
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===' '){
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();
const sp=splitCaret(el);
const before=sp?sp.before:gt(el)||'',after=sp?sp.after:'';
if(!before.trim()&&!after.trim()){
const nt=(block.type==='bulleted_list'||block.type==='numbered_list'||block.type==='to_do')?block.type:'paragraph';
this.sync();this.pushHistory();this.blocks.splice(idx+1,0,this.mkB(nt,''));this.render();_rtSync();
setTimeout(()=>{const ne=this.getEl(this.blocks[idx+1]?.id);if(ne){ne.focus();ce(ne);}},60);
this.dirty=true;this.autoSave();return;
}
const nt=(block.type==='bulleted_list'||block.type==='numbered_list'||block.type==='to_do')?block.type:'paragraph';
this.sync();this.pushHistory();block.content=before;this.blocks.splice(idx+1,0,this.mkB(nt,after));this.render();_rtSync();
setTimeout(()=>{const ne=this.getEl(this.blocks[idx+1]?.id);if(ne){ne.focus();cs(ne);}},60);
this.dirty=true;this.autoSave();return;
}
if(e.key==='ArrowUp'){const _sp=splitCaret(el);if(_sp&&!_sp.before.trim()&&idx>0){e.preventDefault();const prev=this.getEl(this.blocks[idx-1]?.id);if(prev){prev.focus();ce(prev);}}return;}
if(e.key==='ArrowDown'){const _sp=splitCaret(el);if(_sp&&!_sp.after.trim()&&idx<this.blocks.length-1){e.preventDefault();const next=this.getEl(this.blocks[idx+1]?.id);if(next){next.focus();cs(next);}}return;}
if(e.key==='Backspace'){if(!(el.textContent||'').trim()){this.sync();if(block.type!=='paragraph'){e.preventDefault();this.pushHistory();block.type='paragraph';block.content='';this.render();_rtSync();setTimeout(()=>{const ne=this.getEl(block.id);if(ne)ne.focus();},60);}else if(this.blocks.length>1){e.preventDefault();this.removeBlock(idx);}}return;}
if(e.key===' '){const b2=bid;setTimeout(()=>this.checkMd(b2),15);}
if((e.ctrlKey||e.metaKey)&&!e.altKey){
if(e.key.toLowerCase()==='z'){e.preventDefault();if(e.shiftKey)this.redo();else this.undo();return;}
if(e.key.toLowerCase()==='y'){e.preventDefault();this.redo();return;}
if(e.key.toLowerCase()==='d'){e.preventDefault();const ab2=this.getActiveBlock();if(ab2)this.duplicateBlock(ab2.idx);return;}
const m={b:'bold',i:'italic',u:'underline'};if(m[e.key]){e.preventDefault();document.execCommand(m[e.key]);this.dirty=true;this.autoSave();}}
if(e.key==='Tab'&&block.type==='code'){e.preventDefault();document.execCommand('insertText',false,' ');}
this.dirty=true;this.autoSave();
},
checkMd(idx){
if(idx<0||idx>=this.blocks.length)return;const block=this.blocks[idx];const el=this.getEl(block.id);if(!el)return;const text=el.textContent||'';
const sc={'# ':'heading_1','## ':'heading_2','### ':'heading_3','#### ':'heading_4','- ':'bulleted_list','* ':'bulleted_list','+ ':'bulleted_list','1. ':'numbered_list','[] ':'to_do','[ ] ':'to_do','> ':'quote'};
this.sync();
for(const[pfx,type] of Object.entries(sc)){if(text===pfx||text.startsWith(pfx)){this.pushHistory();block.type=type;block.content='';if(type==='toggle')block.expanded=true;if(type==='to_do')block.checked=false;this.render();_rtSync();setTimeout(()=>{const ne=this.getEl(block.id);if(ne&&type!=='divider')ne.focus();},60);return;}}
if(text==='---'){this.pushHistory();block.type='divider';block.content='';this.addAfter(idx,'paragraph');}
if(text==='```'){this.pushHistory();block.type='code';block.content='';block.language='Plain Text';this.render();_rtSync();setTimeout(()=>{const ne=this.getEl(block.id);if(ne)ne.focus();},60);}
},
applySlash(id){
const idx=SM._i;if(idx<0||idx>=this.blocks.length)return;
if(id==='database'){ this.openDbTemplatePicker(idx); return; }
if(id.indexOf('ai_')===0){ this.aiSlash(id,idx); return; }
if(id==='duplicate'){this.duplicateBlock(idx);return;}
if(id==='delete'){this.removeBlock(idx);return;}
if(id==='copy_link'){const blk=this.blocks[idx];if(blk)this.copyBlockLink(blk.id);return;}
if(id==='turn_into'){const blk=this.blocks[idx];if(!blk)return;const el=this.getEl(blk.id);const rk=el?el.getBoundingClientRect():null;blockMenuOpen(rk?rk.left:window.innerWidth/2,rk?rk.bottom:window.innerHeight/2,blk.id);return;}
const block=this.blocks[idx];this.sync();this.pushHistory();block.content='';
if(id==='divider'){block.type='divider';this.addAfter(idx,'paragraph');}
else if(id==='embed'){block.type='embed';block.embed_type='';block.src='';block.height=520;}
else if(id==='bookmark'){block.type='bookmark';block.url='';block.title='';block.description='';block.image='';block.site_name='';}
else if(id==='video'){block.type='video';block.src='';block.title='';}
else if(id==='audio'){block.type='audio';block.src='';block.title='';}
else{block.type=id;if(id==='toggle'){block.expanded=true;block.children=[];}if(id==='to_do')block.checked=false;if(id==='columns')block.children=[{id:genId(),type:'paragraph',content:''}];if(id==='code')block.language='Plain Text';if(id==='table'){block.rows=_tblNew(3,3);block.content='';}if(id==='button'){block.automation_id=0;block.automation_name='';}}
this.render();this.dirty=true;this.autoSave();_rtSync();
if(id==='table'){setTimeout(()=>_tblFocus(block.id,1,0),60);}
else if(id==='math'){setTimeout(()=>{const ne=document.querySelector(`[data-math-bid="${block.id}"]`);if(ne)ne.focus();},60);}
else if(id==='columns'){setTimeout(()=>{const ne=this.getEl(block.children[0].id);if(ne)ne.focus();},60);}
else{setTimeout(()=>{const ne=this.getEl(block.id);if(ne)ne.focus();},60);}
},
// ── v5.9.0: AI writing slash commands ──
aiSlash(id,idx){
const action=id.slice(3); const block=this.blocks[idx];
if(block){ this.sync(); this.pushHistory(); block.content=''; }
if(action==='write'){ this.render(); this.dirty=true; this.autoSave(); if(window.AIC)window.AIC.open(idx); return; }
if(action==='translate'){ this.render(); this.dirty=true; this.autoSave();
const lang=window.prompt('Traduire en…','English'); if(!lang){return;}
this._aiWritingRun('translate',idx,{target_language:lang},'replace'); return; }
this.render(); this.dirty=true; this.autoSave();
this._aiWritingRun(action,idx,{},'insert');
},
_aiWritingRun(action,idx,extra,mode){
const self=this; this.sync();
const ctx=(typeof window.fdGetPageContext==='function')?String(window.fdGetPageContext()||''):'';
const payload={action:action,context:ctx};
if(action==='continue'){ let parts=[]; for(let i=0;i<idx;i++){ const b=this.blocks[i]; if(b&&b.content)parts.push(b.content); } payload.prefix=parts.join('\n').slice(-4000); }
if(extra&&extra.target_language)payload.target_language=extra.target_language;
if(extra&&extra.prompt)payload.prompt=extra.prompt;
if(extra&&extra.title)payload.title=extra.title;
this.showToast('FlowDeck AI : génération…');
fetch('/api/agent/writing',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrfTok()},body:JSON.stringify(payload)})
.then(function(r){return r.json();})
.then(function(d){ if(d&&d.ok&&d.text){ self.applyAIBlocksAt(d.text,idx,mode); } else { self.showToast((d&&d.error)||'Échec de la génération','error'); } })
.catch(function(err){ self.showToast((err&&err.message)||'Erreur réseau','error'); });
},
applyAIBlocksAt(text,idx,mode){
const nb=this.md2b(text||''); if(!nb.length)return;
this.sync(); this.pushHistory();
if(mode==='replace'){ this.blocks=nb; }
else{ const block=this.blocks[idx]; const empty=block&&block.type==='paragraph'&&!(block.content||'').trim();
if(empty){ this.blocks.splice(idx,1,...nb); } else { this.blocks.splice(idx+1,0,...nb); } }
this.dirty=true; this.autoSave(); this.render(); _rtSync();
setTimeout(()=>{ const el=this.getEl(this.blocks[Math.min(idx,this.blocks.length-1)]?.id); if(el){el.focus();ce(el);} },60);
},
_doToggle(bid,v){const idx=this.getIdx(bid);if(idx>=0){this.pushHistory();this.blocks[idx].checked=v;this.dirty=true;this.autoSave();_rtSync();}},
_doToggleExpand(bid){const idx=this.getIdx(bid);if(idx>=0){this.pushHistory();this.blocks[idx].expanded=!this.blocks[idx].expanded;this.render();this.dirty=true;this.autoSave();_rtSync();}},
_addColumn(parentId){const idx=this.getIdx(parentId);if(idx<0)return;const b=this.blocks[idx];this.pushHistory();b.children.push({id:genId(),type:'paragraph',content:''});this.render();this.dirty=true;this.autoSave();_rtSync();setTimeout(()=>{const ne=this.getEl(b.children[b.children.length-1].id);if(ne)ne.focus();},60);},
_removeColumn(parentId,childId){const idx=this.getIdx(parentId);if(idx<0)return;const b=this.blocks[idx];this.pushHistory();b.children=b.children.filter(function(c){return c.id!==childId;});if(b.children.length===0)b.children=[{id:genId(),type:'paragraph',content:''}];this.render();this.dirty=true;this.autoSave();_rtSync();},
_toggleCalloutIcon(bid){const picker=document.getElementById('callout-picker-'+bid);if(picker)picker.style.display=picker.style.display==='none'?'flex':'none';},
_setCalloutIcon(bid,icon){const idx=this.getIdx(bid);if(idx>=0){this.pushHistory();this.blocks[idx].icon=icon;this.dirty=true;this.autoSave();this.render();_rtSync();}},
showFmt(idx){const s=window.getSelection();if(!s.rangeCount||s.isCollapsed){this.fmt.open=false;return;}const r=s.getRangeAt(0).getBoundingClientRect();this.fmt.open=true;this.fmt.idx=idx;this.fmt.top=Math.max(r.top-44,0);this.fmt.left=Math.min(r.left+r.width/2-120,window.innerWidth-260);this._sel={range:s.getRangeAt(0).cloneRange(),idx};},
fmtApply(f){this.fmt.open=false;if(!this._sel)return;const s=window.getSelection();s.removeAllRanges();s.addRange(this._sel.range);document.execCommand(f==='strikeThrough'?'strikeThrough':f);this._sel=null;this.dirty=true;this.autoSave();},
fmtLink(){this.fmt.open=false;const u=prompt('URL:');if(u){document.execCommand('createLink',false,u);this.dirty=true;this.autoSave();}},
// ── v4.9.0: Collaboration — comments & mentions ──
toggleComments(){this.commentsOpen=!this.commentsOpen;if(this.commentsOpen)this.loadComments();},
async loadComments(){try{const r=await fetch('/api/pages/'+this.pid+'/comments',{credentials:'same-origin'});const d=await r.json();this.comments=d.comments||[];this.commentCount=this.comments.length;}catch(e){this.comments=[];}},
fmtTime(s){if(!s)return '';const t=new Date((String(s).includes('T')||String(s).includes('Z'))?s:(s+'Z'));if(isNaN(t.getTime()))t=new Date(s);const diff=Math.floor((Date.now()-t.getTime())/1000);if(diff<60)return 'just now';if(diff<3600)return Math.floor(diff/60)+'m ago';if(diff<86400)return Math.floor(diff/3600)+'h ago';return Math.floor(diff/86400)+'d ago';},
csrfTok(){return (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'';},
async addPageComment(){
const text=(this.commentDraft||'').trim();if(!text)return;
const self=this;const sel=this._commentSel;
try{
const r=await fetch('/api/pages/'+this.pid+'/comments',{method:'POST',credentials:'same-origin',
headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrfTok()},
body:JSON.stringify({body:text,anchor_block_id:sel.bid||null,anchor_start:sel.bid?sel.start:null,anchor_end:sel.bid?sel.end:null})});
const d=await r.json();
if(d.status==='created'){this.commentDraft='';this._commentSel={range:null,bid:null,start:0,end:0};this.hideCommentBtn();await this.loadComments();this.showToast('Comment added');}
else{this.showToast(d.detail||'Failed to add comment');}
}catch(e){this.showToast('Failed to add comment');}
},
async resolveComment(id){try{await fetch('/api/comments/'+id,{method:'PUT',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrfTok()},body:JSON.stringify({resolved:true})});await this.loadComments();}catch(e){}},
async deleteComment(id){if(!confirm('Delete this comment?'))return;try{await fetch('/api/comments/'+id,{method:'DELETE',credentials:'same-origin',headers:{'X-CSRF-Token':this.csrfTok()}});await this.loadComments();}catch(e){}},
commentOnSelection(){
const sel=this._commentSel;if(!sel.range)return;
const bid=sel.bid||'';this.commentsOpen=true;this.commentDraft='';
const btn=document.getElementById('_commentSelBtn');if(btn)btn.style.display='none';
this.$nextTick(()=>{const ta=document.querySelector('.comments-drawer textarea');if(ta)ta.focus();});
},
showCommentBtn(){
const s=window.getSelection();if(!s||s.isCollapsed)return;
const ct=document.getElementById('_blocksCt');if(!ct||!ct.contains(s.anchorNode))return;
const bel=s.anchorNode.parentElement? s.anchorNode.parentElement.closest('[data-bid]'):null;
if(!bel)return;
const bid=bel.dataset.bid;
const range=s.getRangeAt(0).cloneRange();range.selectNodeContents(bel);range.setEnd(s.getRangeAt(0).startContainer,s.getRangeAt(0).startOffset);
const start=range.toString().length;
const end=start+s.toString().length;
if(end<=start)return;
this._commentSel={range:s.getRangeAt(0).cloneRange(),bid:bid,start:start,end:end};
const r=s.getRangeAt(0).getBoundingClientRect();
const btn=document.getElementById('_commentSelBtn');if(!btn)return;
btn.style.display='block';
const bw=btn.offsetWidth||110,bh=btn.offsetHeight||34;
let x=r.left+r.width/2-bw/2,y=r.top-bh-8;
x=Math.min(Math.max(8,x),window.innerWidth-bw-8);if(y<8)y=r.bottom+8;
btn.style.left=x+'px';btn.style.top=y+'px';
},
hideCommentBtn(){const btn=document.getElementById('_commentSelBtn');if(btn)btn.style.display='none';},
// ── @mention autocomplete ──
mentionOpen:false,mentionQuery:'',mentionResults:[],mentionEl:null,
openMention(el){
const s=window.getSelection();if(!s.rangeCount)return;
const text=el.textContent||'';const pos=s.getRangeAt(0).startOffset;
const before=text.substring(0,pos);
const m=before.match(/@([\w\-\.]*)$/);
if(!m)return;
this.mentionQuery=m[1];this.mentionEl=el;
const r=s.getRangeAt(0).getBoundingClientRect();
const menu=document.getElementById('_mentionMenu');
menu.style.display='block';
menu.style.top=Math.max(4,(r.bottom+4>window.innerHeight-280)?r.top-284:r.bottom+4)+'px';
menu.style.left=Math.min(r.left,window.innerWidth-260)+'px';
this.mentionOpen=true;this.loadMentionUsers();
},
async loadMentionUsers(){
const q=this.mentionQuery||'';
try{const r=await fetch('/api/notifications/users/search?q='+encodeURIComponent(q),{credentials:'same-origin'});const d=await r.json();this.mentionResults=d.users||[];this.renderMentionMenu();}catch(e){this.mentionResults=[];}
},
renderMentionMenu(){
const menu=document.getElementById('_mentionMenu');if(!menu)return;
let h='';const self=this;
(this.mentionResults||[]).forEach(function(u,idx){
h+='<div class="mention-item" data-login="'+u.login+'" style="display:flex;align-items:center;gap:8px;padding:7px 10px;font-size:13px;color:var(--text);cursor:pointer;border-radius:6px;">'
+'<div style="width:24px;height:24px;border-radius:50%;background:'+(u.avatar_color||'#3A3A3A')+';color:#fff;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;">'
+((u.avatar_url)?'<img src="'+u.avatar_url+'" style="width:24px;height:24px;border-radius:50%;object-fit:cover;">':((u.full_name||u.login||'?').charAt(0).toUpperCase()))
+'</div><div><div style="font-weight:500">'+u.login+'</div><div style="font-size:11px;color:var(--text-dim,#999)">'+(u.full_name||'')+'</div></div></div>';
});
if(!h)h='<div style="padding:10px;color:var(--text-dim,#999);font-size:13px;">No users found</div>';
menu.innerHTML=h;
menu.querySelectorAll('.mention-item').forEach(function(it){
it.addEventListener('click',function(){self.insertMention(it.getAttribute('data-login'));});
});
},
insertMention(login){
const el=this.mentionEl;if(!el)return;
const s=window.getSelection();if(s.rangeCount){s.deleteFromDocument();}
const text=el.textContent||'';const pos=s.rangeCount? (()=>{const r=s.getRangeAt(0);return r.startOffset;} )():text.length;
const before=text.substring(0,pos);
const at=before.lastIndexOf('@');
const full=before.substring(0,at)+'@'+login+' ';
const after=text.substring(pos);
el.textContent=full+after;
const ns=window.getSelection();const nr=document.createRange();nr.selectNodeContents(el);nr.collapse(false);ns.removeAllRanges();ns.addRange(nr);
this.closeMention();this.dirty=true;this.autoSave();
this.notifyNewMentions(full);
},
closeMention(){const menu=document.getElementById('_mentionMenu');if(menu)menu.style.display='none';this.mentionOpen=false;this.mentionEl=null;},
notifyNewMentions(text){
const self=this;
const handles=[];const re=/@([\w\-\.]+)/g;let mm;
while((mm=re.exec(text)))handles.push(mm[1].toLowerCase());
const fresh=handles.filter(function(h){return !self._notifiedMentions[h];});
if(!fresh.length)return;
fetch('/api/pages/'+this.pid+'/mentions',{method:'POST',credentials:'same-origin',
headers:{'Content-Type':'application/json','X-CSRF-Token':this.csrfTok()},
body:JSON.stringify({text:'@'+fresh.join(' @')})}).catch(function(){});
},
pastePlain(e){e.preventDefault();document.execCommand('insertText',false,(e.clipboardData||window.clipboardData).getData('text/plain'));this.dirty=true;this.autoSave();},
// ── Smart paste: analyze pasted text and split it into multiple blocks ──
paste2b(md){
const out=[];let para='';
const lines=String(md||'').replace(/\r\n/g,'\n').split('\n');
const flush=()=>{
if(!para.trim()){para='';return;}
const pa=para.split('\n').map(s=>s.trim()).filter(Boolean);
if(pa.length>1&&pa.every(l=>l.length<=55)){pa.forEach(l=>out.push(this.mkB('paragraph',l)));}
else out.push(this.mkB('paragraph',para.trim()));
para='';
};
let i=0;
for(;i<lines.length;i++){
const line=lines[i];const t=line.trim();
if(!t){flush();continue;}
if(t.startsWith('```')){
flush();
const lang=t.substring(3).trim()||'Plain Text';
let code='';i++;
for(;i<lines.length;i++){if(lines[i].trim()==='```')break;code+=(code?'\n':'')+lines[i];}
const cb=this.mkB('code',code);cb.language=lang;out.push(cb);
continue;
}
if(_tblIsData(t)&&i+1<lines.length&&_tblIsSep(lines[i+1])){
flush();
const header=_tblSplitLine(t);const rows=[header];let j=i+2;
while(j<lines.length&&_tblIsData(lines[j])){rows.push(_tblSplitLine(lines[j]));j++;}
let w=0;rows.forEach(r=>{if(r.length>w)w=r.length;});if(w<1)w=1;
const rows2=rows.map(r=>{const a=r.slice();while(a.length<w)a.push('');return a;});
out.push(this.mkB('table','',{rows:rows2}));i=j-1;
continue;
}
const hmm=t.match(/^(#{1,6})\s+(.*)$/);
if(hmm){flush();out.push(this.mkB('heading_'+Math.min(hmm[1].length,4),hmm[2]));continue;}
if(/^[-*+]\s+\[[ xX]\]\s+/.test(t)){
flush();
const nb=this.mkB('to_do',t.replace(/^[-*+]\s+\[[ xX]\]\s+/,''));nb.checked=/^[-*+]\s+\[x\]/.test(t)||/^[-*+]\s+\[X\]/.test(t);
out.push(nb);continue;
}
if(/^[-*+]\s+/.test(t)){flush();out.push(this.mkB('bulleted_list',t.replace(/^[-*+]\s+/,'')));continue;}
if(/^\d+\.\s+/.test(t)){flush();out.push(this.mkB('numbered_list',t.replace(/^\d+\.\s+/,'')));continue;}
if(t.substring(0,2)==='> '){flush();out.push(this.mkB('quote',t.substring(2)));continue;}
if(t==='---'||t==='***'){flush();out.push(this.mkB('divider',''));continue;}
para+=(para?'\n':'')+line.trim();
}
flush();
return out;
},
handleSmartPaste(e){
e.preventDefault();
const text=(((e.clipboardData||window.clipboardData))||{}).getData('text/plain')||'';
const fallback=()=>{document.execCommand('insertText',false,text);this.dirty=true;this.autoSave();};
const ab=this.getActiveBlock();
if(!ab)return fallback();
const bel=ab.el,idx=ab.idx,block=this.blocks[idx];
if(!block)return fallback();
const btype=block.type||'paragraph';
if(['code','table','math','image','embed','meeting','divider','table_of_contents','columns'].indexOf(btype)>=0)return fallback();
const parsed=this.paste2b(text);
if(parsed.length<=1)return fallback();
const sp=splitCaret(bel);
let before='',after='';
if(sp){before=sp.before;after=sp.after;}else{before=gt(bel)||'';}
this.sync();this.pushHistory();
const beforeHasText=!!(before||'').trim();
block.content=before;
const ins=[];
parsed.forEach((pb,k)=>{
if(k===0&&!beforeHasText){
const nbf={id:block.id,type:pb.type,content:pb.content};
for(const key in pb){if(key==='id'||key==='type'||key==='content')continue;nbf[key]=pb[key];}
this.blocks[idx]=nbf;
}else{
const nb=this.mkB(pb.type,pb.content);
if(pb.language!==undefined)nb.language=pb.language;
if(pb.checked!==undefined)nb.checked=pb.checked;
if(pb.children)nb.children=pb.children;
ins.push(nb);
}
});
if((after||'').trim())ins.push(this.mkB('paragraph',after.trim()));
this.blocks.splice(idx+1,0,...ins);
const focusId=ins.length?ins[ins.length-1].id:block.id;
this.render();this.dirty=true;this.autoSave();
setTimeout(()=>{const fe=this.getEl(focusId);if(fe){fe.focus();ce(fe);}},60);
},
autoSave(){if(this.fileData)return;clearTimeout(this.st);this.st=setTimeout(()=>this.save(),1500);},
save(cb){
if(this.fileData)return;
if(this.saving){ if(cb) cb(); return; }
this.sync();
const t=document.getElementById('_titleEl');if(t)this.pageTitle=t.textContent?.trim()||'';
this.saving=true;
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
fetch(`/board/api/pages/${this.pid}/blocks`,{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''},body:JSON.stringify({title:this.pageTitle,blocks:this.blocks.map(b=>{const c={id:b.id,type:b.type,content:b.content};['checked','expanded','language','icon','src','alt','style','embed_type','collection_id','dbs','file_name','file_size','file_mime','children','rows','align','has_header','first_col_header','colsW','meeting','automation_id','automation_name'].forEach(k=>{if(b[k]!=null)c[k]=b[k];});return c;})})})
.then(r=>r.json()).then(()=>{this.saving=false;this.dirty=false;this.lastSaved=new Date().toLocaleTimeString();
document.querySelectorAll(`.sidebar-item[data-page-id="page/${this.pid}"]`).forEach(item=>{const n=item.querySelector('.page-name');if(n)n.textContent=this.pageTitle||'New page';});
if(cb)cb();
}).catch(()=>{this.saving=false;});
},
runAutomationButton(bid){
const idx=this.getIdx(bid);if(idx<0)return;
const b=this.blocks[idx];
if(!b.automation_id){this.pickAutomation(bid);return;}
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
const self=this;
this.showToast('⚡ '+ (b.automation_name||'Automation') + '…');
fetch('/api/automations/'+b.automation_id+'/run',{method:'POST',
headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''},
body:JSON.stringify({page_id:this.pid})})
.then(r=>r.json())
.then(d=>{
if(d.status==='fired'){self.showToast('✅ Automation exécutée');}
else if(d.status==='skipped'){self.showToast('⏭️ Condition non remplie (skippée)');}
else{self.showToast((d.detail||'Erreur')+'','error');}
})
.catch(()=>{self.showToast('Échec de l\u2019exécution','error');});
},
pickAutomation(bid){
const self=this;
fetch('/workspace/automations',{credentials:'same-origin'})
.then(r=>r.json())
.then(d=>{
const list=d.automations||[];
if(!list.length){self.showToast('Aucune automation — créez-en dans Paramètres → Automations','error');return;}
const ov=document.createElement('div');ov.id='fdAutomationOverlay';
ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:9999;display:flex;align-items:center;justify-content:center;';
const box=document.createElement('div');box.style.cssText='background:var(--bg-secondary,#1f1f1f);border:1px solid var(--border,#333);border-radius:12px;padding:20px;width:420px;max-width:92vw;max-height:80vh;overflow:auto;';
box.innerHTML='<div style="font-weight:600;font-size:14px;margin-bottom:12px;">Choisir une automation</div>'+
list.filter(a=>a.enabled).map(function(a){
return `<button type="button" data-id="${a.id}" style="display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:9px 10px;background:var(--bg-hover,transparent);border:1px solid var(--border,#333);color:var(--text-primary);border-radius:8px;margin-bottom:8px;cursor:pointer;"><span>⚡</span><span style="flex:1;font-size:13px;">${esc(a.name||'Automation')}</span><span style="font-size:11px;color:var(--text-tertiary);">${esc(a.trigger_type||'event')}</span></button>`;
}).join('')+
'<button type="button" data-cancel="1" style="width:100%;text-align:center;padding:8px;border:none;background:transparent;color:var(--text-secondary);cursor:pointer;font-size:12px;">Annuler</button>';
ov.appendChild(box);
document.body.appendChild(ov);
box.addEventListener('click',function(ev){
const btn=ev.target.closest('[data-id]');if(btn){
const idx=self.getIdx(bid);if(idx>=0){self.blocks[idx].automation_id=parseInt(btn.getAttribute('data-id'),10);const a=list.find(function(x){return x.id===parseInt(btn.getAttribute('data-id'),10);});self.blocks[idx].automation_name=a?a.name:'';self.dirty=true;self.autoSave();self.render();}
ov.remove();
}else if(ev.target.closest('[data-cancel]')){ov.remove();}
});
})
.catch(()=>{self.showToast('Erreur de chargement','error');});
},
md2b(md){const bl=[];const lines=String(md).split('\n');let i=0;for(;i<lines.length;i++){const raw=lines[i];const t=raw.trim();if(!t)continue;
if(t.startsWith('```')){const lang=t.substring(3).trim()||'Plain Text';let code='';i++;for(;i<lines.length;i++){if(lines[i].trim()==='```')break;code+=(code?'\n':'')+lines[i];}bl.push(this.mkB('code',code,{language:lang}));continue;}
if(_tblIsData(t)&&i+1<lines.length&&_tblIsSep(lines[i+1])){const header=_tblSplitLine(t);const rows=[header];let j=i+2;while(j<lines.length&&_tblIsData(lines[j])){rows.push(_tblSplitLine(lines[j]));j++;}let w=0;rows.forEach(function(r){if(r.length>w)w=r.length;});if(w<1)w=1;const rows2=rows.map(function(r){const a=r.slice();while(a.length<w)a.push('');return a;});bl.push(this.mkB('table','',{rows:rows2}));i=j-1;continue;}
if(t.startsWith('# ')&&!t.startsWith('## '))bl.push(this.mkB('heading_1',t.substring(2)));
else if(t.startsWith('## ')&&!t.startsWith('### '))bl.push(this.mkB('heading_2',t.substring(3)));
else if(t.startsWith('### ')&&!t.startsWith('#### '))bl.push(this.mkB('heading_3',t.substring(4)));
else if(t.startsWith('#### '))bl.push(this.mkB('heading_4',t.substring(5)));
else if(t==='---'||t==='***')bl.push(this.mkB('divider',''));
else if(/^[-*+] /.test(t))bl.push(this.mkB('bulleted_list',t.substring(2)));
else if(/^\d+\. /.test(t))bl.push(this.mkB('numbered_list',t.replace(/^\d+\. /,'')));
else if(t.startsWith('- [ ] ')||t.startsWith('* [ ] '))bl.push(this.mkB('to_do',t.substring(6)));
else if(t.startsWith('- [x] ')||t.startsWith('* [x] '))bl.push(this.mkB('to_do',t.substring(6),{checked:true}));
else if(t.startsWith('> '))bl.push(this.mkB('quote',t.substring(2)));
else bl.push(this.mkB('paragraph',t));
}return bl;},
};
}
document.addEventListener('mouseup',()=>{setTimeout(()=>{const s=window.getSelection();if(!s||s.isCollapsed)return;const ed=document.querySelector('.page-editor-wrapper');if(!ed?.__x)return;const d=ed.__x.$data;const ct=document.getElementById('_blocksCt');if(!ct?.contains(s.anchorNode))return;const bel=s.anchorNode.parentElement?.closest('[data-bid]');if(bel){const idx=d.getIdx(bel.dataset.bid);if(idx>=0)d.showFmt(idx);d.showCommentBtn();}else{d.hideCommentBtn();}},80);});
document.addEventListener('mousedown',()=>{setTimeout(()=>{const ed=document.querySelector('.page-editor-wrapper');if(ed?.__x)ed.__x.$data.hideCommentBtn();},10);});
document.addEventListener('keyup',(e)=>{
const ed=document.querySelector('.page-editor-wrapper');if(!ed?.__x)return;
const d=ed.__x.$data;if(!d.mentionOpen){if(e.key!=='@')return;}
const el=document.activeElement;if(!el||!el.hasAttribute('data-bid'))return;
const ct=document.getElementById('_blocksCt');if(!ct||!ct.contains(el))return;
if(d.mentionOpen){
if(e.key==='Escape'){d.closeMention();return;}
if(e.key==='ArrowDown'||e.key==='ArrowUp'){e.preventDefault();const items=document.querySelectorAll('#_mentionMenu .mention-item');if(!items.length)return;let idx=[].indexOf.call(items,document.querySelector('#_mentionMenu .mention-item.hover'));idx+= (e.key==='ArrowDown'?1:-1);if(idx<0)idx=items.length-1;if(idx>=items.length)idx=0;items.forEach(function(x){x.classList.remove('hover');});items[idx].classList.add('hover');return;}
if(e.key==='Enter'){e.preventDefault();const hover=document.querySelector('#_mentionMenu .mention-item.hover')||document.querySelector('#_mentionMenu .mention-item');if(hover)hover.click();return;}
}
d.openMention(el);
},true);
document.addEventListener('click',(e)=>{
const ed=document.querySelector('.page-editor-wrapper');if(!ed?.__x)return;
const d=ed.__x.$data;if(!d.mentionOpen)return;
const menu=document.getElementById('_mentionMenu');
if(menu&&!menu.contains(e.target))d.closeMention();
});
var fdStyle=document.createElement('style');
fdStyle.textContent='#_mentionMenu .mention-item:hover,#_mentionMenu .mention-item.hover{background:rgba(35,131,226,.15);}';
document.head.appendChild(fdStyle);
function blocksToMarkdown(b) {
var c = b.content || '';
switch(b.type) {
case 'heading_1': return '# ' + c;
case 'heading_2': return '## ' + c;
case 'heading_3': return '### ' + c;
case 'heading_4': return '#### ' + c;
case 'bulleted_list': return '- ' + c;
case 'numbered_list': return '1. ' + c;
case 'to_do': return (b.checked ? '- [x] ' : '- [ ] ') + c;
case 'quote': return '> ' + c;
case 'divider': return '---';
case 'code': return '```' + (b.language || '') + '\n' + c + '\n```';
case 'toggle': return (b.children && b.children.length ? '## ' : '## ') + c + (b.children && b.children.length ? '\n\n' + b.children.map(blocksToMarkdown).join('\n\n') : '');
case 'math': return '$$\n' + c + '\n$$';
case 'table_of_contents': return '[TOC]';
case 'columns': return (b.children || []).map(blocksToMarkdown).join('\n\n');
case 'table': { var rows=b.rows||[]; if(!rows.length)return ''; var w=0; rows.forEach(function(r){if(r&&r.length>w)w=r.length;}); var rl=function(r){var cells=[];for(var k=0;k<w;k++)cells.push((r&&k<r.length)?String(r[k]==null?'':r[k]):'');return '| '+cells.join(' | ')+' |';}; var hasH=(b.has_header!==false),fc=!!b.first_col_header; var startRows=[],sep=false; var out=''; var ri0=0; if(hasH){startRows.push(rows[0]);ri0=1;} for(var si=0;si<startRows.length;si++){out+=(out?'\n':'')+rl(startRows[si]);} if(hasH){var seps=[];for(var k2=0;k2<w;k2++)seps.push('---');out+=(out?'\n':'')+'| '+seps.join(' | ')+' |';} for(var ri2=ri0;ri2<rows.length;ri2++)out+='\n'+rl(rows[ri2]); return out; }
default: return c;
}
}
</script>