From 4dc06eb2038a03244e1d171c83bc0b3a9148048c Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Mon, 7 Sep 2026 10:18:47 -0400 Subject: [PATCH] feat(ui): natural markdown rendering in editor blocks & agent panel - Editor: applyAIBlocks() and fdApplyDocument(replace) now convert agent markdown into real blocks via md2b() (## -> heading_2, GFM tables -> table block, ` -> code, lists, to-do, quote, divider). No more literal '## ' shown. - Editor: inline markdown rendered in blocks via mdEsc() (bold/italic/ inline-code/links/strikethrough) in renderBlock for paragraphs, headings, lists, to-do, toggle, quote, callout, code. - Agent panel: renderMarkdown() renders assistant responses as HTML (H1-H4, GFM tables, code blocks, lists+checkboxes, quotes, hr, inline marks); content escaped before rendering (XSS-safe vs LLM). CSS .fd-md-* added. - VERSION + app.main -> 5.1.1; CHANGELOG updated. - 271 tests pass (no regression); markdown render verified in Node; editor & panel render 200 with new components. --- CHANGELOG.md | 29 ++++- VERSION | 2 +- app/main.py | 2 +- app/templates/_page_editor_scripts.html | 30 +++-- app/templates/agent_panel.html | 147 +++++++++++++++++++++++- 5 files changed, 197 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f140d2..a9fd693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,33 @@ # Changelog — FlowDeck -## v5.1.0 (2026-09-06) — Database Avancée : inline DB, templates prédéfinis & validation +## v5.1.1 (2026-09-07) — Rendu Markdown naturel (éditeur + panneau Agent) + +> L'agent FlowDeck et l'insertion dans les pages affichent désormais le markdown +> rendu (titre, gras, tableaux, code…) au lieu des caractères bruts `##`, `**`… + +### Éditeur de page (blocs) +- **Insertion agent → vrais blocs** : `applyAIBlocks()` (et le mode *replace* de + `fdApplyDocument`) convertit la réponse markdown en blocs réels via `md2b()` : + `## titre` → bloc `heading_2`, tableaux GFM → bloc `table`, ` ``` ` → bloc `code`, + listes `-`/`1.`, to-do, citations, séparateurs. Plus de `## ` affiché littéralement. +- **Rendu inline** : les blocs de texte affichent désormais le markdown inline + (gras `**`, italique `*`, code `` ` ``, lien `[t](url)`, barré `~~`) sans les + caractères, via `mdEsc()` dans `renderBlock` (paragraphes, titres, listes, to-do, + toggle, citation, callout, code). +- Les raccourcis clavier markdown existants (`# ` + espace → H1…) restent inchangés. + +### Panneau FlowDeck Agent +- **`renderMarkdown()`** (agent_panel.html) : les réponses de l'agent s'affichent en + HTML rendu — titres H1–H4, tableaux (GFM), blocs de code, listes à puces/numérotées + (+ cases), citations, séparateurs, gras/italique/code/lien/barré. HTML échappé + avant rendu (sécurité face au contenu LLM). CSS dédié (`.fd-md-*`, `pre`, tableaux). +- Le bouton « Insérer dans la page ouverte » continue d'utiliser `fdApplyDocument` + (maintenant → blocs réels). + +### Tests / validation +- 271 tests pytest verts (aucune régression). Rendu markdown vérifié fonctionnellement + en Node (titres, tableau, code, listes) ; éditeur et panneau rendus 200 avec les + nouveaux composants présents. > Livre la **v5.3.0** du roadmap : databases inline dans les pages (slash `/database`), > galerie de **templates prédéfinis** (CRM, Project tracker, …) et **validation des diff --git a/VERSION b/VERSION index acf69b4..3bff059 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.1.0 \ No newline at end of file +5.1.1 \ No newline at end of file diff --git a/app/main.py b/app/main.py index c07f19e..02c56a4 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.0", + version="5.1.1", 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 92995d3..99eb179 100644 --- a/app/templates/_page_editor_scripts.html +++ b/app/templates/_page_editor_scripts.html @@ -18,6 +18,17 @@ 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,'&').replace(//g,'>');} + // 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,'&').replace(//g,'>') + .replace(/`([^`]+)`/g,'$1') + .replace(/\*\*([^*]+)\*\*/g,'$1') + .replace(/\*([^*]+)\*/g,'$1') + .replace(/~~([^~]+)~~/g,'$1') + .replace(/\[([^\]]+)\]\((https?:[^)\s]+)\)/g,'$1'); + } const CALLOUT_ICONS=['💡','💡','📌','⚠️','ℹ️','✅','❌','🚀','🔔','📣','⭐','❤️','🔥','💬','👀','🧠','🎯','🔑','📅','💰','💡','📚','🧪','🌍']; @@ -223,11 +234,11 @@ } 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...':'Press \'/\' for commands...'; let inner=''; - if(b.type==='to_do')inner=`
${esc(b.content)}
`; - else if(b.type==='toggle')inner=`
${esc(b.content)}
${(b.children||[]).map((ch)=>renderNestedBlock(ch)).join('')}
`; - else if(b.type==='code')inner=`
${b.language||'Plain Text'}
${esc(b.content)}
`; - else if(b.type==='callout')inner=`${b.icon||'💡'}
${esc(b.content)}
`; - else inner=`
${esc(b.content)}
`; + if(b.type==='to_do')inner=`
${mdEsc(b.content)}
`; + else if(b.type==='toggle')inner=`
${mdEsc(b.content)}
${(b.children||[]).map((ch)=>renderNestedBlock(ch)).join('')}
`; + else if(b.type==='code')inner=`
${b.language||'Plain Text'}
${mdEsc(b.content)}
`; + else if(b.type==='callout')inner=`${b.icon||'💡'}
${mdEsc(b.content)}
`; + else inner=`
${mdEsc(b.content)}
`; 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)'}"`:''; @@ -695,12 +706,13 @@ self.showToast('FlowDeck Agent : génération en cours…'); window.fdAgent.ask(prompt); }, - applyAIBlocks(text){ - const lines=(text||'').split(/\r?\n/).map(function(s){return s.trim();}).filter(Boolean); - if(!lines.length) return; +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(); - const nb=lines.map(l=>this.mkB('paragraph',l)); this.blocks = startEmpty ? nb : this.blocks.concat(nb); this.dirty=true; this.autoSave(); this.render(); }, diff --git a/app/templates/agent_panel.html b/app/templates/agent_panel.html index 23be60b..ce2628f 100644 --- a/app/templates/agent_panel.html +++ b/app/templates/agent_panel.html @@ -146,6 +146,28 @@ .fd-ap-toast button{background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:13px;padding:0} [x-cloak]{display:none!important} +.fd-agent-msg-markdown{line-height:1.6} +.fd-agent-msg-markdown p{margin:0 0 8px} +.fd-agent-msg-markdown h1,.fd-agent-msg-markdown h2,.fd-agent-msg-markdown h3,.fd-agent-msg-markdown h4{margin:12px 0 6px;font-weight:700;line-height:1.25} +.fd-agent-msg-markdown h1{font-size:1.4em} +.fd-agent-msg-markdown h2{font-size:1.25em} +.fd-agent-msg-markdown h3{font-size:1.1em} +.fd-agent-msg-markdown h4{font-size:1em} +.fd-agent-msg-markdown ul,.fd-agent-msg-markdown ol{margin:0 0 8px;padding-left:20px} +.fd-agent-msg-markdown li{margin:2px 0} +.fd-agent-msg-markdown code{background:rgba(127,127,127,.18);border-radius:4px;padding:1px 5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em} +.fd-agent-msg-markdown pre{background:var(--bg-code,#111);border:1px solid var(--border,#333);border-radius:8px;padding:12px;overflow-x:auto;margin:8px 0} +.fd-agent-msg-markdown pre code{background:none;padding:0;font-size:12px;line-height:1.5} +.fd-md-table-wrap{overflow-x:auto;margin:8px 0} +.fd-md-table{border-collapse:collapse;width:100%;font-size:13px} +.fd-md-table th,.fd-md-table td{border:1px solid var(--border,#333);padding:6px 10px;text-align:left} +.fd-md-table th{background:rgba(127,127,127,.14);font-weight:600} +.fd-md-table tr:nth-child(even) td{background:rgba(127,127,127,.06)} +.fd-md-quote{border-left:3px solid var(--accent,#2383E2);margin:8px 0;padding:2px 12px;color:var(--text-dim)} +.fd-md-quote p{margin:4px 0} +.fd-md-hr{border:none;border-top:1px solid var(--border,#333);margin:12px 0} +.fd-agent-msg-markdown a{color:var(--accent,#2383E2);text-decoration:none} +.fd-agent-msg-markdown a:hover{text-decoration:underline} {# ── Global Agent API (available before Alpine for the FAB / shortcut) ── #} @@ -230,7 +252,9 @@
-
+
Génération…
+
+