- crates/ld-engine : formule inertielle (parité avec la maquette, garde C), coquille / changement de fond, assistant d'édition à règles, composition d'un document sous vote permanent (adoptions, aperçus, rivales). - crates/trustin-access : qui voit, qui ouvre, qui gère les accès. - crates/ld-server : API axum + SQLite, connexion Ğ1 (client OAuth2 de connect.monnaie-libre.fr), invitations, entrée en vigueur automatique, signalements de contradiction, traductions éditoriales, publication git, taille de la toile lue sur la chaîne. - seeds/acte-engagement-certification.json : 33 articles, fr + en + es ; en vigueur : préambule, E1, E2 (seed a minima). - maquette/ : frontend, backend et docker v2 déplacés tels quels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
3.2 KiB
Vue
115 lines
3.2 KiB
Vue
<script setup lang="ts">
|
||
// <!-- ld-v2 --> Diff inline +/− entre la version courante et une version
|
||
// proposée — contenu d'abord, calculé localement (LCS ligne à ligne, sans lib).
|
||
const props = defineProps<{
|
||
current: string
|
||
proposed: string
|
||
}>()
|
||
|
||
interface DiffLine {
|
||
text: string
|
||
type: 'kept' | 'removed' | 'added'
|
||
}
|
||
|
||
/** Line-based LCS diff — texts are clause-sized, the DP table stays tiny. */
|
||
function lineDiff(a: string[], b: string[]): DiffLine[] {
|
||
const n = a.length
|
||
const m = b.length
|
||
// lcs[i][j] = LCS length of a[i:] / b[j:]
|
||
const lcs: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0))
|
||
for (let i = n - 1; i >= 0; i--) {
|
||
for (let j = m - 1; j >= 0; j--) {
|
||
lcs[i]![j] = a[i] === b[j]
|
||
? lcs[i + 1]![j + 1]! + 1
|
||
: Math.max(lcs[i + 1]![j]!, lcs[i]![j + 1]!)
|
||
}
|
||
}
|
||
const out: DiffLine[] = []
|
||
let i = 0
|
||
let j = 0
|
||
while (i < n && j < m) {
|
||
if (a[i] === b[j]) {
|
||
out.push({ text: a[i]!, type: 'kept' })
|
||
i++
|
||
j++
|
||
} else if (lcs[i + 1]![j]! >= lcs[i]![j + 1]!) {
|
||
out.push({ text: a[i]!, type: 'removed' })
|
||
i++
|
||
} else {
|
||
out.push({ text: b[j]!, type: 'added' })
|
||
j++
|
||
}
|
||
}
|
||
while (i < n) out.push({ text: a[i++]!, type: 'removed' })
|
||
while (j < m) out.push({ text: b[j++]!, type: 'added' })
|
||
return out
|
||
}
|
||
|
||
const lines = computed(() => {
|
||
const a = props.current.split('\n')
|
||
const b = props.proposed.split('\n')
|
||
return lineDiff(a, b).filter(l => !(l.type === 'kept' && l.text.trim() === ''))
|
||
})
|
||
|
||
const changedCount = computed(() => lines.value.filter(l => l.type !== 'kept').length)
|
||
</script>
|
||
|
||
<template>
|
||
<!-- ld-v2 -->
|
||
<div class="clause-diff">
|
||
<div v-if="changedCount === 0" class="clause-diff__same">
|
||
Aucun changement de texte — les deux versions sont identiques.
|
||
</div>
|
||
<div
|
||
v-for="(line, index) in lines"
|
||
v-else
|
||
:key="index"
|
||
class="clause-diff__line"
|
||
:class="`clause-diff__line--${line.type}`"
|
||
>
|
||
<span class="clause-diff__marker" aria-hidden="true">
|
||
{{ line.type === 'added' ? '+' : line.type === 'removed' ? '−' : '' }}
|
||
</span>
|
||
<span class="clause-diff__text">{{ line.text }}</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.clause-diff {
|
||
border-radius: var(--r-input);
|
||
overflow: hidden;
|
||
background: color-mix(in srgb, var(--mood-text) 3%, var(--mood-surface));
|
||
font-size: 0.8125rem;
|
||
line-height: 1.55;
|
||
}
|
||
.clause-diff__same {
|
||
padding: 0.6rem 0.9rem;
|
||
color: var(--mood-text-muted);
|
||
font-style: italic;
|
||
}
|
||
.clause-diff__line {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
padding: 0.15rem 0.9rem;
|
||
}
|
||
.clause-diff__line--removed {
|
||
background: color-mix(in srgb, var(--mood-error) 10%, transparent);
|
||
color: color-mix(in srgb, var(--mood-error) 80%, var(--mood-text));
|
||
text-decoration: line-through;
|
||
text-decoration-thickness: 1px;
|
||
}
|
||
.clause-diff__line--added {
|
||
background: color-mix(in srgb, var(--mood-success) 11%, transparent);
|
||
color: color-mix(in srgb, var(--mood-success) 75%, var(--mood-text));
|
||
}
|
||
.clause-diff__marker {
|
||
flex-shrink: 0;
|
||
width: 0.9rem;
|
||
font-weight: 800;
|
||
font-family: monospace;
|
||
text-align: center;
|
||
}
|
||
.clause-diff__text { white-space: pre-wrap; word-break: break-word; }
|
||
</style>
|