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:
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
// 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, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.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 }
|
||||
|
||||
Reference in New Issue
Block a user