feat(editor): v4.8.0 Bloc Tableau simple (edition Notion via /table)
FlowDeck CI / test (push) Failing after 20s
FlowDeck CI / docker (push) Skipped

- Bloc table editable: slash /table, cellules contenteditable, auto-save
- Colonnes: poignee par colonne -> Insert left/right, Duplicate, Clear, Delete
- Lignes: + Row (bas) + menu contextuel -> Insert above/below, Dup, Clear, Del
- md2b importe les tableaux GFM pipe en bloc table (au lieu de paragraphes)
- Roundtrip markdown (JS + export Python MD/HTML/PDF)
- CSS .ftable-editor + menu flottant (app.css?v=4.8.0)
- VERSION/CHANGELOG/ROADMAP bump 4.8.0 (Collaboration -> v4.9.0, Agent -> v4.10.0)
This commit is contained in:
2026-09-03 14:55:25 -04:00
parent 6e30589133
commit c809a864e6
8 changed files with 222 additions and 17 deletions
+1
View File
@@ -4,6 +4,7 @@ __pycache__/
/data/
.venv/
venv/
.venv*/
*.egg-info/
dist/
.pytest_cache/
+19
View File
@@ -1,5 +1,24 @@
# Changelog — FlowDeck
## 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.
### Added
- **Bloc `table` dans l'éditeur de page.** Nouvelles commandes slash `/table` (menu « BASIC ») → tableau 3×3 avec en-tête ; première ligne stylée en en-tête, cellules `contenteditable` éditées au clic, auto-sauvegarde (format de bloc conservé : `{type:'table', rows:[...]}`).
- **Gestion des colonnes** : bouton de poignée (⋮⋮) au-dessus de chaque colonne → menu contextuel Insert left / Insert right / Duplicate / Clear contents / Delete column / Delete table.
- **Gestion des lignes** : bouton « + Row » sous le tableau (clic = ajouter une ligne) + menu contextuel (clic droit sur une cellule de corps) → Insert above / Insert below / Duplicate / Clear row / Delete row / Delete table.
- **Import Markdown (GFM)** : `md2b` détecte les tableaux pipe (`| a | b |` + ligne de séparateurs) et produit un bloc `table` éditable au lieu de paragraphes aplatis (pages `.md`/markdown).
- **Roundtrip Markdown** : `blocksToMarkdown` (JS) et l'export Python reconstruisent un tableau pipe valide depuis les blocs.
- **Export** : les pages contenant un bloc `table` sortent correctement en Markdown / HTML / PDF (géré côté serveur).
- UX : cliquer sous un tableau en fin de page ajoute un paragraphe pour continuer la rédaction.
### Infra
- `VERSION` → 4.8.0 ; `app/main.py` (log + `version=`) ; cache CSS `app.css?v=4.8.0` ; ROADMAP renuméroté (v4.8.0 Bloc Tableau, Collaboration → v4.9.0, Agent → v4.10.0).
### Tests
- Suite pytest verte (199) ; vérification syntaxe JS du template via `node --check`.
## v4.7.3 (2026-09-03) — Fix export PDF/HTML : tableaux + émojis
### Fixed
+12 -6
View File
@@ -301,12 +301,18 @@ Détails livrés :
- [x] Dockerfile : libs pango/harfbuzz/gdk-pixbuf + polices ; requirements : + weasyprint==69.0
- [x] 4 nouveaux tests → 199/199 ; PDF README.md vérifié : tableau structuré + émojis colorés
### v4.8.0 — Collaboration
### v4.8.0 — Bloc Tableau simple ✅ (2026-09-03)
- [x] **Bloc `table` éditable dans l'éditeur** — insertion via `/table` (commandes slash, section BASIC), tableau 3×3 avec en-tête, cellules éditables au clic, auto-sauvegarde.
- [x] **Lignes** — bouton `+ Row` sous le tableau (clic = ajouter) ; clic droit sur une cellule de corps → Insérer dessus/dessous, Dupliquer, Effacer, Supprimer la ligne, Supprimer le tableau.
- [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.9.0 — Collaboration
- [ ] **Inline comments** — commentaires sur sélection de texte
- [ ] **@mentions** — notifier un utilisateur → page/commentaire
- [ ] **Email notifications** — changements, mentions
### v4.9.0 — FlowDeck Agent (Agent IA natif)
### v4.10.0 — FlowDeck Agent (Agent IA natif)
**Objectif :** Un agent IA intégré à FlowDeck, capable de planifier, rechercher et **agir** directement sur les workspaces — collections, pages, propriétés, vues, issues Gitea. Inspiré de Notion Agent (2026).
@@ -449,8 +455,8 @@ Base + Kanban Éditeur + Gitea UX Pro MVP Onboard
+ UI Notion + Tags + Admin + Sharing COMPLETED
+ GitHub OAuth + Library
v4.0.2 ✅ v4.1.0 ✅ v4.2.0 ✅ v4.3.0 ✅ v4.4.0 ✅ v4.5.0 ✅ v4.6.0 ✅ v4.7.0 ✅ v4.8–4.9 ⬜ v5.x–v6.0 ⬜
Quality Data Sources Templates + 10 Views Tasks & Sprints & Content Export Collab + Pro + Agent
& Tests & Linked DB Dashboards complets Dependencies My Tasks Blocks MD/PDF/HTML Agent IA (futur)
v4.0.2 ✅ v4.1.0 ✅ v4.2.0 ✅ v4.3.0 ✅ v4.4.0 ✅ v4.5.0 ✅ v4.6.0 ✅ v4.7.0 ✅ v4.8.0 ✅ v4.9–4.10 ⬜ v5.x–v6.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.7.3 Export tableaux + émojis ✅*
*Dernière mise à jour: 2026-09-03 — v4.8.0 Bloc Tableau simple (édition Notion) ✅*
+1 -1
View File
@@ -1 +1 @@
4.7.3
4.8.0
+2 -2
View File
@@ -39,13 +39,13 @@ async def lifespan(_app: FastAPI):
(admin_hash,)
)
conn.commit()
logger.info("FlowDeck v4.7.3 started on port %d", settings.app_port)
logger.info("FlowDeck v4.8.0 started on port %d", settings.app_port)
yield
app = FastAPI(
title="FlowDeck",
version="4.7.3",
version="4.8.0",
docs_url="/docs" if settings.log_level == "DEBUG" else None,
redoc_url=None,
lifespan=lifespan,
+142 -7
View File
@@ -9,6 +9,7 @@
{id:'to_do',name:'To-do',icon:'☐'},{id:'toggle',name:'Toggle',icon:'▶'},{id:'quote',name:'Quote',icon:'❝'},
{id:'callout',name:'Callout',icon:'lightbulb'},{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:'⊞'},
]},{name:'MEDIA',items:[{id:'image',name:'Image',icon:'image'}]}];
let _bid=0;function genId(){return 'b'+(++_bid)+'_'+Date.now().toString(36);}
@@ -19,6 +20,120 @@
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,_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 bid=b.id;var h='';
h+='<div class="block-wrapper"><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);var tag=isHead?'th':'td';
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+'>';
}
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></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(!(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 _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);}}
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(document.getElementById('_tblMenu')&&!ev.target.closest('#_tblMenu'))_tblMenuClose();});
}
const SM={_o:false,_i:-1,_s:0,_q:'',_it:[],
init(){const e=document.getElementById('_slashMenu');if(!e)return;e.innerHTML='';
const inp=document.createElement('input');inp.type='text';inp.placeholder='Filter...';
@@ -54,6 +169,7 @@
get isOpen(){return SM._o;}};
function renderBlock(b,idx){
if(b.type==='table')return renderTableBlock(b,idx);
if(b.type==='divider')return `<div class="block-wrapper"><hr class="block-divider"></div>`;
if(b.type==='image')return `<div class="block-wrapper"><div class="block-content block-image" style="text-align:center;padding:12px 0;"><img src="${b.src||''}" alt="${esc(b.alt||'')}" style="max-width:100%;max-height:70vh;border-radius:8px;display:block;margin:0 auto;" onerror="this.style.display='none';this.parentElement.innerHTML='<div style=\\'padding:40px;color:var(--text-tertiary)\\'>Image not found</div>'"></div></div>`;
if(b.type==='table_of_contents')return renderTOC();
@@ -170,7 +286,9 @@
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;SM.init();this.render();
window.E=this;
this.insertTable=function(){this.sync();var nb={id:genId(),type:'table',content:'',rows:_tblNew(3,3)};this.blocks.push(nb);this.dirty=true;this.autoSave();this.render();setTimeout(function(){_tblFocus(nb.id,0,0);},60);};
SM.init();_tblInitDelegate();this.render();
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();});
@@ -205,8 +323,9 @@
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)};},
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];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==='divider'||b.type==='image'||b.type==='embed'||b.type==='table_of_contents')continue;
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')){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')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=el.textContent||'';else if(el)b.content=el.textContent||'';
if(b.type==='toggle'&&b.children)b.children.forEach(function(ch){const che=ct.querySelector(`[data-bid="${ch.id}"]`);if(che)ch.content=che.textContent||'';});
@@ -509,9 +628,10 @@
const idx=SM._i;if(idx<0||idx>=this.blocks.length)return;
const block=this.blocks[idx];this.sync();block.content='';
if(id==='divider'){block.type='divider';this.addAfter(idx,'paragraph');}
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';}
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='';}}
this.render();this.dirty=true;this.autoSave();
if(id==='math'){setTimeout(()=>{const ne=document.querySelector(`[data-math-bid="${block.id}"]`);if(ne)ne.focus();},60);}
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);}
},
@@ -533,13 +653,27 @@
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'].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'].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;});
},
md2b(md){const bl=[];let cb=null;for(const line of md.split('\n')){if(cb){if(line.trim()==='```'){bl.push(cb);cb=null;}else cb.content+=(cb.content?'\n':'')+line;continue;}const t=line.trim();if(!t)continue;if(t.startsWith('```')){cb=this.mkB('code','',{language:t.substring(3).trim()||'Plain Text'});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));}if(cb)bl.push(cb);return bl;},
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);}},80);});
@@ -560,6 +694,7 @@
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 out=rl(rows[0]); var seps=[];for(var k=0;k<w;k++)seps.push('---');out+='\n| '+seps.join(' | ')+' |';for(var ri=1;ri<rows.length;ri++)out+='\n'+rl(rows[ri]);return out; }
default: return c;
}
}
+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.7.1">
<link rel="stylesheet" href="/static/css/app.css?v=4.8.0">
<link rel="stylesheet" href="/static/css/katex.min.css?v=0.16.11">
<style>
/* ── Mobile responsive (v4.0.2) ── */
+44
View File
@@ -4442,3 +4442,47 @@ button.breadcrumb-current { max-width: 260px; }
}
.db-side-peek-close:hover, .db-side-peek-full:hover { background: var(--bg-hover); }
.db-side-peek iframe { flex: 1; }
/* ─── Simple Table block (v4.8.0) ─── */
.ftable-editor { flex: 1; min-width: 0; min-height: 0; padding: 4px 2px; }
.ftable-editor table.ftable-edit { width: 100%; table-layout: fixed; border-collapse: collapse; margin: 0; }
.ftable-edit th.ftable-cell, .ftable-edit td.ftable-cell {
border: 1px solid var(--border); padding: 6px 10px; vertical-align: top;
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 .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-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 {
width: 22px; height: 22px; margin: 0 2px 0 auto; display: flex; align-items: center;
justify-content: center; color: var(--text-dim); cursor: pointer; border-radius: 4px;
background: transparent; border: none; opacity: 0; transition: opacity .12s ease;
}
.ftable-editor .ftable-colcell:hover .ftable-colgrip,
.ftable-editor .ftable-colgrip:hover,
.ftable-editor .ftable-colgrip:focus-visible { opacity: 1; }
.ftable-editor .ftable-colgrip:hover { background: var(--bg-hover); color: var(--text-secondary); }
.ftable-editor .ftable-rowbar { display: flex; justify-content: center; margin-top: 2px; opacity: 0; transition: opacity .12s ease; }
.ftable-editor:hover .ftable-rowbar { opacity: 1; }
.ftable-editor .ftable-addrow {
border: none; background: transparent; color: var(--text-dim); cursor: pointer;
font-size: 13px; padding: 4px 12px; border-radius: 6px;
}
.ftable-editor .ftable-addrow:hover { background: var(--bg-hover); color: var(--text-primary); }
.ftable-menu {
min-width: 190px; background: var(--bg-modal); border: 1px solid var(--border);
border-radius: 8px; box-shadow: var(--shadow-modal); padding: 4px;
font-size: 13.5px; color: var(--text-primary);
}
.ftable-menu-item { padding: 6px 12px; border-radius: 5px; cursor: pointer; user-select: none; }
.ftable-menu-item:hover { background: var(--bg-hover); }
.ftable-menu-item.danger { color: var(--danger); }
.ftable-menu-item.danger:hover { background: rgba(229,72,77,.1); }
.ftable-menu-sep { height: 1px; background: var(--border); margin: 4px 8px; }
@media (max-width: 768px) {
.ftable-editor .ftable-colgrip { opacity: 1; }
.ftable-editor .ftable-rowbar { opacity: 1; }
}