fix(editor): v4.8.1 bloc Tableau - +Row, +Colonne a droite, redim colonnes
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Skipped

- + Row (bas) ne fonctionnait pas: splice(index,ligne) -> splice(index,0,ligne)
- bouton + a droite du tableau = ajoute une colonne a droite
- redimensionnement colonnes a la souris (curseur col-resize) + persistance colsW
- VERSION/CHANGELOG/ROADMAP/css?v bump 4.8.1
This commit is contained in:
2026-09-04 12:10:22 -04:00
parent c809a864e6
commit 23df10732b
7 changed files with 84 additions and 10 deletions
+13
View File
@@ -1,5 +1,18 @@
# Changelog — FlowDeck
## v4.8.1 (2026-09-03) — Fix bloc Tableau : boutons + Ligne / + Colonne, redimensionnement
### Fixed
- **Bouton « + Row » (bas du tableau) inopérant** — le code passait la nouvelle ligne en argument `deleteCount` de `Array.splice` au lieu d'utiliser `splice(index, 0, ligne)` : aucune ligne n'était jamais insérée. Corrigé ; les insertions de lignes (menu contextuel aussi) fonctionnent.
- Coloration/handles alignés sur l'en-tête.
### Added
- **Bouton « + » à droite du tableau** (sur toute la hauteur) → ajoute une colonne à la droite (équivalent d'un « Add column » comme « Add Row »).
- **Redimensionnement des colonnes à la souris** : au survol d'une bordure de colonne (en-tête) le curseur devient `col-resize` ; glisser ajuste la largeur. Les largeurs sont persistées dans le bloc (`colsW`) → conservées au rechargement, à l'ajout/suppression et dans l'auto-save.
### Infra
- `VERSION` → 4.8.1 ; cache CSS `app.css?v=4.8.1`.
## v4.8.0 (2026-09-03) — Bloc Tableau simple (mode édition Notion)
> Insertion via `/table`, cellules éditables au clic, ajout/suppression/modification de lignes et colonnes comme dans Notion.
+6 -1
View File
@@ -307,6 +307,11 @@ Détails livrés :
- [x] **Colonnes** — poignée (⋮⋮) au-dessus de chaque colonne → Insérer à gauche/droite, Dupliquer, Effacer le contenu, Supprimer la colonne, Supprimer le tableau.
- [x] **Import Markdown (GFM)** — `md2b` transforme les tableaux pipe en bloc `table` au lieu de les aplatir en paragraphes ; roundtrip Markdown (JS + export) et sortie MD/HTML/PDF correcte d'un bloc `table`.
### v4.8.1 — Fix bloc Tableau ✅ (2026-09-03)
- [x] **« + Row » (bas) corrigé** — l'insertion de ligne utilisait `splice(index, ligne)` (mauvaise signature) → aucune ligne ajoutée ; désormais `splice(index, 0, ligne)`.
- [x] **Bouton « + » à droite** — ajoute une colonne à droite (équivalent « Add column »).
- [x] **Redimensionnement des colonnes** — curseur `col-resize` au survol des bordures d'en-tête, drag = largeur ; persisté via `colsW`.
### v4.9.0 — Collaboration
- [ ] **Inline comments** — commentaires sur sélection de texte
- [ ] **@mentions** — notifier un utilisateur → page/commentaire
@@ -459,4 +464,4 @@ v4.0.2 ✅ v4.1.0 ✅ v4.2.0 ✅ v4.3.0 ✅ v4.4.0 ✅ v4.5.0 ✅
Quality Data Sources Templates + 10 Views Tasks & Sprints & Content Export Bloc Table Collab + Pro + Agent
& Tests & Linked DB Dashboards complets Dependencies My Tasks Blocks MD/PDF/HTML simple Agent IA (futur)
*Dernière mise à jour: 2026-09-03 — v4.8.0 Bloc Tableau simple (édition Notion) ✅*
*Dernière mise à jour: 2026-09-03 — v4.8.1 Bloc Tableau : + Row / + Col / redimensionnement ✅*
+1 -1
View File
@@ -1 +1 @@
4.8.0
4.8.1
+2 -2
View File
@@ -39,13 +39,13 @@ async def lifespan(_app: FastAPI):
(admin_hash,)
)
conn.commit()
logger.info("FlowDeck v4.8.0 started on port %d", settings.app_port)
logger.info("FlowDeck v4.8.1 started on port %d", settings.app_port)
yield
app = FastAPI(
title="FlowDeck",
version="4.8.0",
version="4.8.1",
docs_url="/docs" if settings.log_level == "DEBUG" else None,
redoc_url=None,
lifespan=lifespan,
+39 -4
View File
@@ -27,7 +27,7 @@
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,_tblNew(1,w)[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]='';});}
@@ -40,8 +40,10 @@
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='';
h+='<div class="block-wrapper"><div class="block-content ftable-editor" data-bid="'+bid+'">';
h+='<div class="ftable-flex"><div class="ftable-flex-main">';
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>';
@@ -53,12 +55,18 @@
h+='<tr class="ftable-row">';
for(var ci=0;ci<w;ci++){
var v=rows[ri]&&ci<rows[ri].length?rows[ri][ci]:'';
h+='<'+tag+' class="ftable-cell'+(isHead?' head':'')+'" contenteditable="true" data-r="'+ri+'" data-c="'+ci+'" spellcheck="false">'+esc(v)+'</'+tag+'>';
var wpx=(widths[ci]&&widths[ci]>0)?(' style="width:'+widths[ci]+'px"'):'';
h+='<'+tag+' class="ftable-cell'+(isHead?' head':'')+'" contenteditable="true" data-r="'+ri+'" data-c="'+ci+'"'+wpx+' spellcheck="false">'+esc(v);
if(isHead)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></div>';
h+='</div>';
h+='<div class="ftable-addcolwrap"><button type="button" class="ftable-addcol" data-tbl="coladdright" data-bid="'+bid+'" title="Add column on the right">+</button></div>';
h+='</div>';
h+='</div></div>';
return h;
}
@@ -108,6 +116,29 @@
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){
@@ -118,6 +149,7 @@
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);}}
return;
}
if(document.getElementById('_tblMenu')&&!(ev.target.closest&&ev.target.closest('[data-tbl]')))_tblMenuClose();
@@ -131,7 +163,10 @@
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(document.getElementById('_tblMenu')&&!ev.target.closest('#_tblMenu'))_tblMenuClose();});
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();
});
}
const SM={_o:false,_i:-1,_s:0,_q:'',_it:[],
@@ -653,7 +688,7 @@
const t=document.getElementById('_titleEl');if(t)this.pageTitle=t.textContent?.trim()||'New page';
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'].forEach(k=>{if(b[k]!=null)c[k]=b[k];});return c;})})})
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','colsW'].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();
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FlowDeck — {% block title_prefix %}Home{% endblock %}</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/css/app.css?v=4.8.0">
<link rel="stylesheet" href="/static/css/app.css?v=4.8.1">
<link rel="stylesheet" href="/static/css/katex.min.css?v=0.16.11">
<style>
/* ── Mobile responsive (v4.0.2) ── */
+22 -1
View File
@@ -4451,9 +4451,29 @@ button.breadcrumb-current { max-width: 260px; }
font-size: 15px; line-height: 1.5; color: var(--text-primary);
outline: none; text-align: left; word-break: break-word; overflow-wrap: break-word;
}
.ftable-edit th.ftable-cell { background: var(--bg-secondary); font-weight: 600; }
.ftable-edit th.ftable-cell { background: var(--bg-secondary); font-weight: 600; position: relative; }
.ftable-edit .ftable-cell:hover { background: var(--bg-hover); }
.ftable-edit .ftable-cell:focus { background: var(--bg-hover); box-shadow: inset 0 0 0 1px var(--blue); }
.ftable-edit .ftable-resize {
position: absolute; top: 0; bottom: 0; right: -3px; width: 7px;
cursor: col-resize; z-index: 3;
}
.ftable-edit .ftable-resize::after {
content: ""; position: absolute; top: 0; bottom: 0; right: 3px; width: 1px;
background: transparent; transition: background .12s ease;
}
.ftable-editor:hover .ftable-resize::after { background: var(--border); }
.ftable-edit .ftable-resize:hover::after { background: var(--accent); }
.ftable-editor .ftable-flex { display: flex; align-items: stretch; gap: 4px; }
.ftable-editor .ftable-flex-main { flex: 1 1 auto; min-width: 0; }
.ftable-editor .ftable-addcolwrap { flex: 0 0 auto; display: flex; align-items: center; }
.ftable-editor .ftable-addcol {
width: 26px; min-height: 64px; border: none; background: transparent;
color: var(--text-dim); font-size: 18px; cursor: pointer; border-radius: 6px;
opacity: 0; transition: opacity .12s ease;
}
.ftable-editor:hover .ftable-addcol { opacity: 1; }
.ftable-editor .ftable-addcol:hover { background: var(--bg-hover); color: var(--accent); }
.ftable-editor .ftable-colbar { display: flex; margin: 0 0 3px; }
.ftable-editor .ftable-colcell { flex: 1 1 0; min-width: 0; display: flex; justify-content: flex-end; }
.ftable-editor .ftable-colgrip {
@@ -4485,4 +4505,5 @@ button.breadcrumb-current { max-width: 260px; }
@media (max-width: 768px) {
.ftable-editor .ftable-colgrip { opacity: 1; }
.ftable-editor .ftable-rowbar { opacity: 1; }
.ftable-editor .ftable-addcol { opacity: 1; }
}