diff --git a/CHANGELOG.md b/CHANGELOG.md index 9995ac0..93258c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog — FlowDeck +## v5.1.11 (2026-09-07) — Éditeur : collage intelligent (découpe en blocs) + +> Coller un texte dans un bloc le plaçait en entier dans ce bloc, même s'il +> contenait plusieurs paragraphes, des listes, des titres markdown, du code ou +> un tableau. + +- **Découpe automatique au collage** : le contenu collé est analysé + (`paste2b`) et réparti en plusieurs blocs selon son format (markdown ou + texte simple). +- Pris en charge : titres `#`→`####`, listes à puces (`-`, `*`, `+`), listes + numérotées, cases à cocher `- [ ]` / `- [x]` (y compris `[X]`), citations + `>` , séparateurs `---`/`***`, blocs de code (fences ```), tableaux + markdown et paragraphes. +- Texte simple : les lignes courtes (≤ 55 caractères) deviennent chacune un + bloc ; les blocs de texte longs (paragraphes) restent groupés ; une ligne + vide crée une nouvelle section. +- Comportement au curseur : le texte avant le curseur reste dans le bloc + courant (qui est réutilisé comme premier bloc si vide), puis les blocs + analysés sont insérés, et le texte après le curseur conserve sa propre + place (refocus sur le dernier bloc inséré). +- Les blocs non textuels (code, tableau, image, formules…) reçoivent toujours + le collage en texte brut, sans découpe. + ## v5.1.10 (2026-09-07) — Espace local : boîte de renommage à la taille du nom > Renommer un fichier en cliquant sur son nom ouvrait une boîte d'édition qui diff --git a/VERSION b/VERSION index ed45e0a..831c6cd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.1.10 \ No newline at end of file +5.1.11 \ No newline at end of file diff --git a/app/main.py b/app/main.py index 7bdb010..0b42f5c 100644 --- a/app/main.py +++ b/app/main.py @@ -60,7 +60,7 @@ async def lifespan(_app: FastAPI): app = FastAPI( title="FlowDeck", - version="5.1.10", + version="5.1.11", docs_url="/docs" if settings.log_level == "DEBUG" else None, redoc_url=None, lifespan=lifespan, diff --git a/app/templates/_page_editor_scripts.html b/app/templates/_page_editor_scripts.html index 8ec2367..54f8fb2 100644 --- a/app/templates/_page_editor_scripts.html +++ b/app/templates/_page_editor_scripts.html @@ -715,7 +715,7 @@ if(tEl){const syncT=()=>{tEl.classList.toggle('empty',!(tEl.textContent||'').trim());};syncT();tEl.addEventListener('input',syncT);tEl.addEventListener('blur',syncT);} setTimeout(()=>{this.focusBlock();const ct=document.getElementById('_blocksCt');if(!ct)return; ct.addEventListener('keydown',e=>this.onKd(e)); - ct.addEventListener('paste',e=>{e.preventDefault();document.execCommand('insertText',false,(e.clipboardData||window.clipboardData).getData('text/plain'));this.dirty=true;this.autoSave();}); + 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=>{if(evt.oldIndex===evt.newIndex)return;const[item]=this.blocks.splice(evt.oldIndex,1);this.blocks.splice(evt.newIndex,0,item);this.dirty=true;this.autoSave();this.render();}}); },100); }, @@ -1285,6 +1285,92 @@ applyAIBlocks(text){ 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{if(r.length>w)w=r.length;});if(w<1)w=1; + const rows2=rows.map(r=>{const a=r.slice();while(a.length '){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(); + 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;