Sprint 2 : moteur de documents + sanctuaire
Backend: - CRUD complet documents/items/versions (update, delete, accept, reject, reorder) - Service IPFS (upload/retrieve/pin via kubo HTTP API) - Service sanctuaire : pipeline SHA-256 + IPFS + on-chain (system.remark) - Verification integrite des entrees sanctuaire - Recherche par reference (document -> entrees sanctuaire) - Serialisation deterministe des documents pour archivage - 14 tests unitaires supplementaires (document service) Frontend: - 9 composants : StatusBadge, MarkdownRenderer, DiffView, ItemCard, ItemVersionDiff, DocumentList, SanctuaryEntry, IPFSLink, ChainAnchor - Page detail item avec historique des versions et diff - Page detail sanctuaire avec verification integrite - Modal de creation de document + proposition de version - Archivage document vers sanctuaire depuis la page detail Documentation: - API reference mise a jour (9 nouveaux endpoints) - Guides utilisateur documents et sanctuaire enrichis Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
67
frontend/app/components/common/MarkdownRenderer.vue
Normal file
67
frontend/app/components/common/MarkdownRenderer.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
content: string
|
||||
}>()
|
||||
|
||||
/**
|
||||
* Basic markdown rendering: bold, line breaks, unordered lists.
|
||||
* Returns sanitized HTML string for v-html usage.
|
||||
*/
|
||||
const renderedHtml = computed(() => {
|
||||
if (!props.content) return ''
|
||||
|
||||
let html = props.content
|
||||
|
||||
// Escape HTML entities to prevent XSS
|
||||
html = html
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
|
||||
// Convert **bold** to <strong>
|
||||
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||
|
||||
// Convert *italic* to <em>
|
||||
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||
|
||||
// Process lines for list items
|
||||
const lines = html.split('\n')
|
||||
const result: string[] = []
|
||||
let inList = false
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim()
|
||||
|
||||
if (trimmed.startsWith('- ')) {
|
||||
if (!inList) {
|
||||
result.push('<ul class="list-disc list-inside space-y-1 my-2">')
|
||||
inList = true
|
||||
}
|
||||
result.push(`<li>${trimmed.slice(2)}</li>`)
|
||||
} else {
|
||||
if (inList) {
|
||||
result.push('</ul>')
|
||||
inList = false
|
||||
}
|
||||
if (trimmed === '') {
|
||||
result.push('<br>')
|
||||
} else {
|
||||
result.push(`<p class="my-1">${line}</p>`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inList) {
|
||||
result.push('</ul>')
|
||||
}
|
||||
|
||||
return result.join('\n')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="prose prose-sm dark:prose-invert max-w-none text-gray-700 dark:text-gray-300 leading-relaxed"
|
||||
v-html="renderedHtml"
|
||||
/>
|
||||
</template>
|
||||
Reference in New Issue
Block a user