fix(chatbot): séparation définitive Carte1/Carte2 + markdown inline styles

- ChatbotReseaux.vue : composant standalone, endpoint hardcodé /api/chatbot-reseaux,
  onboarding 120 réseaux AEP, aucun prop partagé avec ChatbotSheet
- ChatbotSheet.vue : restauré état simple, /api/chatbot hardcodé, onboarding Carte 1
- agences.vue : ChatbotReseaux au lieu de ChatbotSheet
- useMarkdown.ts : inline styles (font-weight:700 etc) — zéro dépendance CSS,
  fonctionne dans tout contexte Vue scoped/v-html sans exception

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jules Neny
2026-05-07 02:38:47 +02:00
parent 419071b4c5
commit 5967a5af57
4 changed files with 244 additions and 57 deletions

View File

@@ -1,34 +1,35 @@
/**
* Convertit du Markdown Mistral en HTML sécurisé.
* Gère : **bold**, *italic*, ## titres, listes - et •, retours à la ligne.
* Pas de dépendance externe.
* Convertit du Markdown Mistral en HTML avec inline styles.
* Inline styles = zéro dépendance CSS, fonctionne dans tout contexte Vue (scoped, v-html, etc.)
*/
export function useMarkdown() {
const S = {
p: 'style="margin:0 0 0.45em;line-height:1.6;"',
strong: 'style="font-weight:700;"',
em: 'style="font-style:italic;"',
h2: 'style="font-weight:700;display:block;margin-bottom:0.2em;"',
h3: 'style="font-weight:700;display:block;font-size:0.95em;margin-bottom:0.15em;"',
ul: 'style="margin:0.3em 0 0.3em 1.2em;padding:0;list-style:disc;"',
li: 'style="margin-bottom:0.15em;"',
a: 'style="text-decoration:underline;opacity:0.85;"',
}
function render(text: string): string {
if (!text) return ''
let html = text
// Échappement XSS de base
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
// Titres ## et ###
.replace(/^### (.+)$/gm, '<strong class="md-h3">$1</strong>')
.replace(/^## (.+)$/gm, '<strong class="md-h2">$1</strong>')
.replace(/^# (.+)$/gm, '<strong class="md-h1">$1</strong>')
// Bold et italic
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>')
// Listes (- item ou • item en début de ligne)
.replace(/^[-•]\s+(.+)$/gm, '<li>$1</li>')
// Liens [texte](url)
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener" class="md-link">$1</a>')
// Grouper les <li> consécutifs dans un <ul>
html = html.replace(/(<li>.*<\/li>\n?)+/g, match => `<ul class="md-list">${match}</ul>`)
// Paragraphes : double saut de ligne → séparateur
html = html.replace(/\n{2,}/g, '</p><p>')
// Saut de ligne simple → <br>
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/^### (.+)$/gm, `<strong ${S.h3}>$1</strong>`)
.replace(/^## (.+)$/gm, `<strong ${S.h2}>$1</strong>`)
.replace(/^# (.+)$/gm, `<strong ${S.h2}>$1</strong>`)
.replace(/\*\*(.+?)\*\*/g, `<strong ${S.strong}>$1</strong>`)
.replace(/\*(.+?)\*/g, `<em ${S.em}>$1</em>`)
.replace(/^[-•]\s+(.+)$/gm, `<li ${S.li}>$1</li>`)
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, `<a href="$2" target="_blank" rel="noopener" ${S.a}>$1</a>`)
html = html.replace(/(<li[^>]*>.*<\/li>\n?)+/g, m => `<ul ${S.ul}>${m}</ul>`)
html = html.replace(/\n{2,}/g, `</p><p ${S.p}>`)
html = html.replace(/\n/g, '<br>')
return `<p>${html}</p>`
return `<p ${S.p}>${html}</p>`
}
return { render }