feat: menu contextuel de bloc refondu façon Notion — 10 actions, sous-menus et raccourcis (v7.61.0)
- panneau de gauche : recherche « Search actions... », section « Text », les 10 actions dans l'ordre Notion (Turn into, Color, Copy link Alt+⇧+L, Duplicate Ctrl+D, Move to Ctrl+⇧+P, Delete Del, Comment Ctrl+⇧+M, Suggest edits Ctrl+⇧+Alt+X, Ask AI Ctrl+J, Skills) + pied métadonnées (auteur / heure de dernière édition / mots-caractères du bloc) - panneau de droite au survol (fermeture différée) : Turn into avec ✓ du type actuel + Heading 1-4, listes, To-do, Toggle, Code, Quote, Callout, Block equation, Synced block, 2/3/4/5 columns (info-bulle « Create N columns of blocks ») ; Color avec Last used / Text color / Background color (10 couleurs nommées, ✓ courante, carré de couleur) ; Skills = compétences réelles de l'espace appliquées au bloc via l'IA - turnInto(idx,type,cols) : crée N colonnes, sélecteur de source pour les blocs synchronisés ; setBlockColor mémorise la dernière couleur (Ctrl+⇧+H) - nouvelles actions éditeur : commentBlock (sélection + tiroir), askAI (composeur sans effacer le bloc), suggestEdit / runSkill (IA, insertion sous le bloc), applyLastColor - raccourcis : handler clavier dédié (Ctrl+J, Ctrl+⇧+H, Ctrl+⇧+M, Ctrl+⇧+Alt+X, Alt+⇧+L, Ctrl+⇧+P, Ctrl+D sans double, Del seulement avec le menu ouvert, Échap) — les entrées Copy/Cut/Paste du menu sont retirées (Ctrl+C/X/V conservés) - styles : .bm-group, .bm-foot, .bm-check, .bm-current, .bm-dot, sous-menu 60vh, recherche focalisée à l'ouverture - tests : tests/test_v761_block_menu.py (9 tests, dont node : colonnes) + test_v7593 adapté ; suite complète 1362 passed / 0 failed, ruff 0, eslint 0 - livraison : VERSION + app/main = 7.61.0, OpenAPI 525 chemins, CHANGELOG
This commit is contained in:
@@ -921,74 +921,214 @@ const PD=(()=>{try{const el=document.getElementById('page-data');return el?JSON.
|
||||
{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'}
|
||||
{id:'code',name:'Code block'},{id:'synced',name:'Synced 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();}
|
||||
// Sous-menu « Turn into » — ordre Notion ; id ∈ _BLOCK_TURN_TYPES, cols = nb de colonnes
|
||||
const _BLOCK_TURN_MENU=[
|
||||
{id:'paragraph',name:'Text',icon:'T'},{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 list',icon:'☑'},
|
||||
{id:'toggle',name:'Toggle list',icon:'▸'},{id:'code',name:'Code',icon:'</>'},
|
||||
{id:'quote',name:'Quote',icon:'”'},{id:'callout',name:'Callout',icon:'ⓘ'},
|
||||
{id:'math',name:'Block equation',icon:'∑'},{id:'synced',name:'Synced block',icon:'⛓'},
|
||||
{id:'columns',name:'2 columns',icon:'▦',cols:2},{id:'columns',name:'3 columns',icon:'▦',cols:3},
|
||||
{id:'columns',name:'4 columns',icon:'▦',cols:4},{id:'columns',name:'5 columns',icon:'▦',cols:5}
|
||||
];
|
||||
// Couleurs texte/fond alignées par index — noms Notion (Dernière utilisée ailleurs)
|
||||
const _BLOCK_COLOR_NAMES=['Default','Gray','Brown','Orange','Yellow','Green','Blue','Purple','Pink','Red'];
|
||||
const _BLOCK_COLORS_TEXT=['default','#94A3B8','#8A6642','#F76B15','#FFC53D','#46A758','#3E63DD','#8E4EC6','#E5539B','#E5484D'];
|
||||
const _BLOCK_COLORS_BG=['default','rgba(148,163,184,.15)','rgba(138,102,66,.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(229,83,155,.15)','rgba(229,72,77,.15)'];
|
||||
// Raccourcis affichés — implémentés par le handler clavier en bas de fichier
|
||||
const _BM_SHORTCUTS={link:'Alt+⇧+L',duplicate:'Ctrl+D',move:'Ctrl+⇧+P',delete:'Del',comment:'Ctrl+⇧+M',suggest:'Ctrl+⇧+Alt+X',ai:'Ctrl+J',color:'Ctrl+⇧+H'};
|
||||
var _bmSubKind='',_bmSubTimer=null;
|
||||
function _blockMenuClose(){var m=document.getElementById('blockMenu');if(m)m.remove();m=document.getElementById('blockMenuSub');if(m)m.remove();clearTimeout(_bmSubTimer);_bmSubKind='';}
|
||||
function _blockAttr(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');}
|
||||
function _blockMenuBid(){var m=document.getElementById('blockMenu');return m?(m.getAttribute('data-bid')||''):'';}
|
||||
// Sous-menu ouvert au survol de l'entrée correspondante (panneau de droite)
|
||||
function _blockSubOpen(kind,bid,anchor){
|
||||
if(!kind||!bid)return;
|
||||
var m=document.getElementById('blockMenu');if(!m)return;
|
||||
if(_bmSubKind===kind&&document.getElementById('blockMenuSub'))return;
|
||||
clearTimeout(_bmSubTimer);_bmSubKind=kind;
|
||||
var r=m.getBoundingClientRect();
|
||||
var a=anchor&&anchor.getBoundingClientRect?anchor.getBoundingClientRect():r;
|
||||
var top=Math.max(8,Math.min(a.top,window.innerHeight-60));
|
||||
if(kind==='turn')_blockTurnMenuOpen(r.right+4,top,bid);
|
||||
else if(kind==='color')_blockColorMenuOpen(r.right+4,top,bid);
|
||||
else if(kind==='skills')_blockSkillsMenuOpen(r.right+4,top,bid);
|
||||
var s=document.getElementById('blockMenuSub');
|
||||
if(s){s.addEventListener('mouseenter',function(){clearTimeout(_bmSubTimer);});
|
||||
s.addEventListener('mouseleave',function(){_blockSubCloseLater();});}
|
||||
}
|
||||
function _blockSubCloseLater(){clearTimeout(_bmSubTimer);_bmSubTimer=setTimeout(function(){var s=document.getElementById('blockMenuSub');if(s&&!s.matches(':hover')){s.remove();_bmSubKind='';}},260);}
|
||||
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');
|
||||
var act=t.getAttribute('data-bm-act');var bid=t.getAttribute('data-bid')||_blockMenuBid();
|
||||
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==='submenu'){_blockSubOpen(t.getAttribute('data-sub'),bid,t);return;}
|
||||
if(act==='duplicate'){E.duplicateBlock(idx);}
|
||||
else if(act==='copy-link'){E.copyBlockLink(bid||'');}
|
||||
else if(act==='copy-blocks'){E.copySelectedBlocks();}
|
||||
else if(act==='cut-blocks'){E.cutSelectedBlocks();}
|
||||
else if(act==='paste-blocks'){E.pasteBlocks();}
|
||||
else if(act==='move-to'){_blockMoveToOpen(idx);}
|
||||
else if(act==='delete'){E.deleteSelected(idx);}
|
||||
else if(act==='comment'){E.commentBlock(idx);}
|
||||
else if(act==='suggest'){E.suggestEdit(idx);}
|
||||
else if(act==='ai'){E.askAI(idx);}
|
||||
else if(act==='skill'){E.runSkill(idx,t.getAttribute('data-prompt')||'');}
|
||||
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();}
|
||||
else if(act==='turn-type'){E.turnInto(idx,t.getAttribute('data-type'),parseInt(t.getAttribute('data-cols')||'0',10)||0);_blockMenuClose();return;}
|
||||
_blockMenuClose();
|
||||
}
|
||||
function _blockMenuItem(act,icon,label,opts){
|
||||
opts=opts||{};
|
||||
var attr=' data-bm-act="'+act+'"';
|
||||
if(opts.bid)attr+=' data-bid="'+_blockAttr(opts.bid)+'"';
|
||||
if(opts.sub)attr+=' data-sub="'+opts.sub+'"';
|
||||
if(opts.colorKey)attr+=' data-color-key="'+opts.colorKey+'" data-color-val="'+_blockAttr(opts.colorVal||'')+'"';
|
||||
if(opts.prompt!=null)attr+=' data-prompt="'+_blockAttr(opts.prompt)+'"';
|
||||
if(opts.type)attr+=' data-type="'+opts.type+'"';
|
||||
if(opts.cols)attr+=' data-cols="'+opts.cols+'"';
|
||||
if(opts.title)attr+=' title="'+_blockAttr(opts.title)+'"';
|
||||
var right=opts.check?'<span class="bm-check">✓</span>'
|
||||
:(opts.sub?'<span class="bm-arrow">▸</span>'
|
||||
:(opts.shortcut?'<span class="bm-hint">'+opts.shortcut+'</span>':''));
|
||||
return '<div class="bm-item'+(opts.danger?' bm-danger':'')+(opts.current?' bm-current':'')+'"'+attr+'>'
|
||||
+(icon!=null?'<span class="bm-ico">'+icon+'</span>':'')+label+right+'</div>';
|
||||
}
|
||||
// Métadonnées du pied de page : auteur / heure de dernière édition / taille du bloc
|
||||
function _blockMenuMeta(b){
|
||||
var E=window.E||{};
|
||||
var who='';
|
||||
try{var n=document.querySelector('.um-name')||document.querySelector('.workspace-name');who=n?(n.textContent||'').trim():'';}catch(e){}
|
||||
var when='';
|
||||
if(E.updatedAt){var d=new Date(E.updatedAt);if(!isNaN(d)){
|
||||
var same=d.toDateString()===new Date().toDateString();
|
||||
var hm=d.toLocaleTimeString([],{hour:'numeric',minute:'2-digit'});
|
||||
when=(same?'Today at ':(d.toLocaleDateString([])+' at '))+hm;}}
|
||||
var content=(b&&b.content)||'';
|
||||
var trimmed=content.trim();
|
||||
var words=trimmed?trimmed.split(/\s+/).length:0;
|
||||
return '<div class="bm-foot"><div>Last edited by '+esc(who||'you')+'</div>'
|
||||
+(when?'<div>'+esc(when)+'</div>':'')
|
||||
+'<div>'+words+' word'+(words===1?'':'s')+', '+content.length+' character'+(content.length===1?'':'s')+'</div></div>';
|
||||
}
|
||||
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-sep"></div>'
|
||||
+'<div class="bm-item" data-bm-act="copy-blocks" data-bid="'+b.id+'"><span class="bm-ico">⎘</span>Copy blocks <span class="bm-hint">'+(_rtMac()?'⌘C':'Ctrl+C')+'</span></div>'
|
||||
+'<div class="bm-item" data-bm-act="cut-blocks" data-bid="'+b.id+'"><span class="bm-ico">✂</span>Cut blocks <span class="bm-hint">'+(_rtMac()?'⌘X':'Ctrl+X')+'</span></div>'
|
||||
+'<div class="bm-item" data-bm-act="paste-blocks" data-bid="'+b.id+'"><span class="bm-ico">⎘</span>Paste blocks <span class="bm-hint">'+(_rtMac()?'⌘V':'Ctrl+V')+'</span></div>'
|
||||
+'<div class="bm-sep"></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>';
|
||||
var d=document.createElement('div');d.id='blockMenu';d.className='block-menu';d.setAttribute('data-bid',b.id);d.style.cssText='position:fixed;z-index:4000;';
|
||||
var S=_BM_SHORTCUTS;
|
||||
d.innerHTML='<input class="bm-search" type="text" placeholder="Search actions..." autocomplete="off" spellcheck="false">'
|
||||
+'<div class="bm-group">Text</div>'
|
||||
+_blockMenuItem('submenu','↩','Turn into',{sub:'turn'})
|
||||
+_blockMenuItem('submenu','🎨','Color',{sub:'color'})
|
||||
+_blockMenuItem('link','🔗','Copy link to block',{shortcut:S.link})
|
||||
+_blockMenuItem('duplicate','▣','Duplicate',{shortcut:S.duplicate})
|
||||
+_blockMenuItem('move-to','↪','Move to…',{shortcut:S.move})
|
||||
+_blockMenuItem('delete','✕','Delete',{shortcut:S.delete,danger:true})
|
||||
+_blockMenuItem('comment','💬','Comment',{shortcut:S.comment})
|
||||
+_blockMenuItem('suggest','✎','Suggest edits',{shortcut:S.suggest})
|
||||
+_blockMenuItem('ai','✨','Ask AI',{shortcut:S.ai})
|
||||
+_blockMenuItem('submenu','📚','Skills',{sub:'skills'})
|
||||
+_blockMenuMeta(b);
|
||||
d.addEventListener('click',_blockMenuClick);
|
||||
d.addEventListener('input',function(ev){
|
||||
if(!ev.target.classList||!ev.target.classList.contains('bm-search'))return;
|
||||
var q=(ev.target.value||'').toLowerCase();
|
||||
d.querySelectorAll('.bm-item').forEach(function(it){
|
||||
it.style.display=(q&&it.textContent.toLowerCase().indexOf(q)<0)?'none':'';
|
||||
});
|
||||
var g=d.querySelector('.bm-group');if(g)g.style.display=q?'none':'';
|
||||
});
|
||||
d.addEventListener('mouseover',function(ev){
|
||||
var it=ev.target&&ev.target.closest?ev.target.closest('[data-sub]'):null;
|
||||
if(it)_blockSubOpen(it.getAttribute('data-sub'),b.id,it);
|
||||
});
|
||||
d.addEventListener('mouseleave',function(){_blockSubCloseLater();});
|
||||
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';
|
||||
var si=d.querySelector('.bm-search');if(si)setTimeout(function(){si.focus();},10);
|
||||
}
|
||||
function _blockTurnMenuOpen(x,y,bid){
|
||||
// Panneau de droite : coquille commune des 3 sous-menus
|
||||
function _blockSubShell(x,y){
|
||||
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';
|
||||
return d;
|
||||
}
|
||||
function _blockTurnMenuOpen(x,y,bid){
|
||||
var d=_blockSubShell(x,y);
|
||||
var E=window.E;var cur='';
|
||||
if(E){var i=E.getIdx(bid);if(i>=0&&E.blocks[i])cur=E.blocks[i].type||'';}
|
||||
var html='';
|
||||
_BLOCK_TURN_MENU.forEach(function(t){
|
||||
var on=(t.id===cur&&!t.cols);
|
||||
html+=_blockMenuItem('turn-type',t.icon,t.name,{bid:bid,type:t.id,cols:t.cols,
|
||||
title:t.cols?('Create '+t.cols+' columns of blocks'):'',
|
||||
check:on,current:on});
|
||||
});
|
||||
d.innerHTML=html;
|
||||
}
|
||||
function _blockColorMenuOpen(x,y,bid){
|
||||
var d=_blockSubShell(x,y);
|
||||
var E=window.E;var idx=E?E.getIdx(bid):-1;var b=idx>=0?E.blocks[idx]:null;
|
||||
var curT=(b&&b.style&&b.style.color)||'default';
|
||||
var curB=(b&&b.style&&b.style.bgColor)||'default';
|
||||
var last='';try{last=localStorage.getItem('fdLastBlockColor')||'';}catch(e){}
|
||||
var html='';
|
||||
if(last){
|
||||
var i=last.indexOf('|');var lk=i<0?'color':last.slice(0,i);var lv=last.slice(i+1);
|
||||
var isBg=(lk==='bgColor');
|
||||
var arr=isBg?_BLOCK_COLORS_BG:_BLOCK_COLORS_TEXT;
|
||||
var ni=arr.indexOf(lv);
|
||||
var nm=(ni>=0?_BLOCK_COLOR_NAMES[ni]:'Custom')+(isBg?' background':' text');
|
||||
html+='<div class="bm-group">Last used</div>'
|
||||
+_blockMenuItem('color','<span style="color:'+(lv==='default'?'var(--text-secondary)':lv)+'">A</span>',
|
||||
nm,{bid:bid,colorKey:lk,colorVal:lv,shortcut:_BM_SHORTCUTS.color});
|
||||
}
|
||||
html+='<div class="bm-group">Text color</div>';
|
||||
_BLOCK_COLORS_TEXT.forEach(function(v,n){
|
||||
html+=_blockMenuItem('color','<span style="color:'+(v==='default'?'var(--text-secondary)':v)+'">A</span>',
|
||||
_BLOCK_COLOR_NAMES[n]+' text',{bid:bid,colorKey:'color',colorVal:v,
|
||||
check:v===curT,current:v===curT});
|
||||
});
|
||||
html+='<div class="bm-group">Background color</div>';
|
||||
_BLOCK_COLORS_BG.forEach(function(v,n){
|
||||
html+=_blockMenuItem('color','<span class="bm-dot" style="background:'+(v==='default'?'var(--text-tertiary)':v)+'"></span>',
|
||||
_BLOCK_COLOR_NAMES[n]+' background',{bid:bid,colorKey:'bgColor',colorVal:v,
|
||||
check:v===curB,current:v===curB});
|
||||
});
|
||||
d.innerHTML=html;
|
||||
}
|
||||
function _blockSkillsMenuOpen(x,y,bid){
|
||||
var d=_blockSubShell(x,y);
|
||||
d.innerHTML='<div class="bm-empty">Chargement…</div>';
|
||||
fetch('/api/agent/skills',{credentials:'same-origin'})
|
||||
.then(function(r){return r.json();})
|
||||
.then(function(res){
|
||||
if(!document.body.contains(d))return;
|
||||
var list=(res&&res.skills)||[];
|
||||
if(!list.length){d.innerHTML='<div class="bm-empty">Aucune compétence enregistrée — créez-en une dans le panneau Agent.</div>';return;}
|
||||
var html='';
|
||||
list.forEach(function(s){
|
||||
html+=_blockMenuItem('skill','📚',esc(String(s.name||'Skill')),{bid:bid,prompt:s.prompt_template||''});
|
||||
});
|
||||
d.innerHTML=html;
|
||||
})
|
||||
.catch(function(){if(document.body.contains(d))d.innerHTML='<div class="bm-empty">Compétences indisponibles.</div>';});
|
||||
}
|
||||
function _rtMac(){return /Mac|iPhone|iPad/.test(navigator.userAgent);}
|
||||
function _blockMoveToOpen(idx){
|
||||
@@ -1526,10 +1666,37 @@ replaceBlock(idx,type){const b=this.blocks[idx];if(!b)return;this.sync();this.pu
|
||||
|
||||
// ── 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.some(t=>t.id===type))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(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='';}});
|
||||
turnInto(idx,type,cols){if(idx<0||idx>=this.blocks.length)return;if(!_BLOCK_TURN_TYPES.some(t=>t.id===type))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(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==='columns'&&cols>=2){b.children=Array.from({length:cols},()=>({id:genId(),type:'paragraph',content:''}));}if(type==='synced'){b.synced_id=0;b._synced_content=[];b._synced_title='';}if(type==='button'){b.automation_id=0;b.automation_name='';}});
|
||||
if(type==='synced')setTimeout(()=>this._pickSyncedBlock(idx),80);
|
||||
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();},
|
||||
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;});if(v!=null){try{localStorage.setItem('fdLastBlockColor',key+'|'+v);}catch(e){}}this.dirty=true;this.autoSave();this.render();_rtSync();_selClear();},
|
||||
// ── v7.61 : actions du menu contextuel de bloc ──
|
||||
commentBlock(idx){
|
||||
const b=this.blocks[idx];if(!b)return;const el=this.getEl(b.id);if(!el)return;
|
||||
const r=document.createRange();r.selectNodeContents(el);
|
||||
const s=window.getSelection();s.removeAllRanges();s.addRange(r);
|
||||
this.showCommentBtn();
|
||||
if(!this._commentSel||!this._commentSel.range){this.showToast('Sélectionnez du texte pour commenter','error');return;}
|
||||
this.commentOnSelection();
|
||||
},
|
||||
askAI(idx){const b=this.blocks[idx];if(!b)return;if(window.AIC)window.AIC.open(idx);},
|
||||
suggestEdit(idx){
|
||||
const b=this.blocks[idx];if(!b||!(b.content||'').trim()){this.showToast('Bloc vide','error');return;}
|
||||
this.showToast('FlowDeck AI : suggestion en cours…');
|
||||
this._aiWritingRun('write',idx,{prompt:'Améliore ce bloc (grammaire, clarté, style) en gardant le sens, la langue et le format. Réponds UNIQUEMENT avec le contenu amélioré, sans préambule.\n\nBloc :\n'+b.content},'insert');
|
||||
},
|
||||
runSkill(idx,tpl){
|
||||
const b=this.blocks[idx];if(!b||!(b.content||'').trim()){this.showToast('Bloc vide','error');return;}
|
||||
this.showToast('FlowDeck AI : compétence en cours…');
|
||||
this._aiWritingRun('write',idx,{prompt:String(tpl||'')+'\n\nContenu du bloc :\n'+b.content},'insert');
|
||||
},
|
||||
applyLastColor(idx){
|
||||
let v='';try{v=localStorage.getItem('fdLastBlockColor')||'';}catch(e){}
|
||||
const i=v.indexOf('|');
|
||||
if(i<0){this.showToast('Aucune couleur récente','error');return;}
|
||||
this.setBlockColor(idx,v.slice(0,i),v.slice(i+1));
|
||||
},
|
||||
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);},
|
||||
// ── v5.11: Copy / Cut / Paste selected blocks ──
|
||||
@@ -2583,6 +2750,43 @@ applyAIBlocks(text){
|
||||
// `window.E` survit aux navigations partielles : on exige donc que
|
||||
// l'editeur soit reellement monte (`#_blocksCt`) avant d'agir, sinon
|
||||
// Ctrl+S sauvegarderait un editeur detache depuis une autre page.
|
||||
// ── v7.61 : raccourcis du menu contextuel de bloc ──
|
||||
document.addEventListener('keydown', function (e) {
|
||||
var E = window.E;
|
||||
if (!E || !document.getElementById('_blocksCt')) return;
|
||||
var k = String(e.key || '').toLowerCase();
|
||||
var ctrl = e.ctrlKey || e.metaKey, sh = e.shiftKey, alt = e.altKey;
|
||||
var menu = document.getElementById('blockMenu');
|
||||
var menuBid = menu ? (menu.getAttribute('data-bid') || '') : '';
|
||||
function curIdx(){
|
||||
if (menuBid) { var mi = E.getIdx(menuBid); if (mi >= 0) return mi; }
|
||||
var ab = E.getActiveBlock();
|
||||
return ab && ab.idx >= 0 ? ab.idx : -1;
|
||||
}
|
||||
if (ctrl && !sh && !alt && k === 'j') { e.preventDefault(); var i1 = curIdx(); if (i1 >= 0) E.askAI(i1); return; }
|
||||
if (ctrl && sh && !alt && k === 'h') { e.preventDefault(); var i2 = curIdx(); if (i2 >= 0) E.applyLastColor(i2); return; }
|
||||
if (ctrl && sh && !alt && k === 'm') { e.preventDefault(); var i3 = curIdx(); if (i3 >= 0) E.commentBlock(i3); return; }
|
||||
if (ctrl && sh && alt && k === 'x') { e.preventDefault(); var i4 = curIdx(); if (i4 >= 0) E.suggestEdit(i4); return; }
|
||||
if (alt && sh && !ctrl && k === 'l') { e.preventDefault(); var i5 = curIdx(); if (i5 >= 0) E.copyBlockLink(E.blocks[i5].id); return; }
|
||||
if (ctrl && sh && !alt && k === 'p') { e.preventDefault(); var i6 = curIdx(); if (i6 >= 0) _blockMoveToOpen(i6); return; }
|
||||
// Ctrl+D : déjà géré par la saisie quand le bloc a le focus (pas de doublon)
|
||||
if (ctrl && !sh && !alt && k === 'd') {
|
||||
var ab = E.getActiveBlock();
|
||||
if (ab && document.activeElement === ab.el) return;
|
||||
var idc = ab && ab.idx >= 0 ? ab.idx : (menuBid ? E.getIdx(menuBid) : -1);
|
||||
if (idc >= 0) { e.preventDefault(); E.duplicateBlock(idc); }
|
||||
return;
|
||||
}
|
||||
// Del : supprime le bloc SEULEMENT avec le menu ouvert (jamais pendant la saisie)
|
||||
if (k === 'delete' && !ctrl && !sh && !alt && menu) {
|
||||
var tgt = e.target;
|
||||
if (tgt && (tgt.tagName === 'INPUT' || tgt.tagName === 'TEXTAREA' || tgt.isContentEditable)) return;
|
||||
var i8 = curIdx();
|
||||
if (i8 >= 0) { e.preventDefault(); E.deleteSelected(i8); }
|
||||
return;
|
||||
}
|
||||
if (k === 'escape' && menu) { _blockMenuClose(); return; }
|
||||
});
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (!(e.ctrlKey || e.metaKey) || e.altKey || e.shiftKey) return;
|
||||
if (String(e.key).toLowerCase() !== 's') return;
|
||||
|
||||
Reference in New Issue
Block a user