Lot 4 modernisation JS (SonarLint) + restauration CI sonarqube non-bloquante
Build and deploy / test-python (push) Successful in 11m18s
Build and deploy / test-js (push) Successful in 1m16s
Build and deploy / lint-python (push) Successful in 4m24s
Build and deploy / lint-js (push) Successful in 2m51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Successful in 5m30s

Corrige les findings SonarQube (via SonarLint IDE, fichier par fichier)
sur ~24 fichiers static/js/ : parseFloat/parseInt -> Number.*,
.replace(/x/g,y) -> .replaceAll, .indexOf() -> .includes()/.startsWith(),
getAttribute/setAttribute -> .dataset, tableaux -> Set, x && x.y -> x?.y
(verifie site par site), extraction de template litteraux imbriques,
ternaires imbriquees, refactors de complexite cognitive (S3776) via
tables de dispatch, Object.hasOwn, .at(), et deduplication de fonctions
identiques (S4144). Deux exceptions S2486 documentees/corrigees
(filter-repeater-rows.js) et un cas S2703 de partage inter-scripts
complete (_collisionWizard, trigger-editor.js <-> collision-rules-editor.js).
Details complets dans CODE_QUALITY.md section 5.

Restaure aussi le job CI "sonarqube" (non-bloquant) dans
.gitea/workflows/deploy.yml maintenant que l'instance prod est
operationnelle.

Suites vertes : 276/276 JS (node --test), 591/591 Python (pytest).

SKIP=djlint sur ce commit : hook djlint bloquant sur le backlog H021
(styles inline, 49 occurrences/6 templates) deja documente comme dette
assumee non traitee dans CODE_QUALITY.md section 6, aucun rapport avec
ce commit (aucun template touche ici) - valide explicitement avec
l'utilisateur avant de contourner ce hook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-18 16:38:29 +02:00
co-authored by Claude Sonnet 5
parent 66d8eaeae8
commit ccf836f2c5
36 changed files with 1720 additions and 838 deletions
+22 -11
View File
@@ -15,27 +15,38 @@
// `forgeDialogueBoxState` (déclarés dans dialogue-box-controller.js,
// chargé à côté de ce fichier) sont des variables GLOBALES de script
// classique (pas de module), lisibles ici telles quelles.
// Extrait de forgeQuizProgressOnShow (lot 4 "modernisation JS", S7721) :
// fonction pure (aucune fermeture), pas besoin d'être redéfinie à chaque appel.
function isQuestionLine(line) { return line?.type === 'question'; }
// Extrait de forgeQuizProgressOnShow (S3358) : segment "fait"/"courant"/
// "à venir" de la barre d'étapes pour la question d'indice `i` (1-based).
function forgeQuizProgressBarSegmentClass(i, current) {
if (i < current) return 'is-done';
if (i === current) return 'is-current';
return '';
}
// `progressSelector` : chaque modèle a son propre conteneur de barre
// d'étapes (data-manga-role/data-classique-role, jamais data-quiz-role —
// décoration propre à CHAQUE modèle, pas du contrat partagé).
function forgeQuizProgressOnShow(widget, progressSelector) {
if (!widget) return;
const quizObjectId = widget.getAttribute('data-object-id');
const quizObjectId = widget.dataset.objectId;
const quizState = (typeof forgeQuizBoxState !== 'undefined') ? forgeQuizBoxState[quizObjectId] : null;
if (!quizState) return;
const dialogueState = (typeof forgeDialogueBoxState !== 'undefined') ? forgeDialogueBoxState[quizState.dialogueObjectId] : null;
if (!dialogueState) return;
function isQuestion(line) { return line && line.type === 'question'; }
const total = dialogueState.lines.filter(isQuestion).length;
const total = dialogueState.lines.filter(isQuestionLine).length;
if (total === 0) return;
const current = dialogueState.lines.slice(0, dialogueState.index + 1).filter(isQuestion).length;
const current = dialogueState.lines.slice(0, dialogueState.index + 1).filter(isQuestionLine).length;
const header = widget.querySelector('[data-quiz-role="header"]');
if (header) header.textContent = `Question ${current}/${total}`;
const progressEl = widget.querySelector(progressSelector);
if (progressEl) {
const bars = [];
for (let i = 1; i <= total; i++) {
bars.push(`<i class="${i < current ? 'is-done' : (i === current ? 'is-current' : '')}"></i>`);
bars.push(`<i class="${forgeQuizProgressBarSegmentClass(i, current)}"></i>`);
}
progressEl.innerHTML = bars.join('');
}
@@ -79,15 +90,15 @@ function forgeQuizClassiqueOnQuizComplete(widget, result) {
const content = widget.querySelector('.classiqueContent');
const zone = widget.querySelector('[data-classique-role="result"]');
if (!content || !zone) return;
const total = (result && result.totalCount) || 0;
const correct = (result && result.correctCount) || 0;
const total = result?.totalCount || 0;
const correct = result?.correctCount || 0;
const pct = total > 0 ? Math.round((correct / total) * 100) : 0;
const threshold = (typeof window !== 'undefined' && typeof window.FORGE_SUCCESS_THRESHOLD_PERCENT === 'number')
? window.FORGE_SUCCESS_THRESHOLD_PERCENT : 70;
const passed = pct >= threshold;
const titleEl = widget.querySelector('.classiqueHeaderTitle');
const title = titleEl ? titleEl.textContent : 'Quiz';
const quizObjectId = widget.getAttribute('data-object-id');
const quizObjectId = widget.dataset.objectId;
zone.innerHTML = `<div class="classiqueResultRing" style="--pct:${pct}"><span>${pct}%</span></div>`
+ `<p class="classiqueResultTitle">${correct} bonne${correct > 1 ? 's' : ''} réponse${correct > 1 ? 's' : ''} sur ${total}</p>`
+ `<p class="classiqueResultSub">« ${typeof forgeEscapeHtml === 'function' ? forgeEscapeHtml(title) : title} »</p>`
@@ -112,13 +123,13 @@ function forgeQuizMangaOnQuizComplete(widget, result) {
if (!widget) return;
const zone = widget.querySelector('[data-manga-role="result"]');
if (!zone) return;
const total = (result && result.totalCount) || 0;
const correct = (result && result.correctCount) || 0;
const total = result?.totalCount || 0;
const correct = result?.correctCount || 0;
const pct = total > 0 ? Math.round((correct / total) * 100) : 0;
const threshold = (typeof window !== 'undefined' && typeof window.FORGE_SUCCESS_THRESHOLD_PERCENT === 'number')
? window.FORGE_SUCCESS_THRESHOLD_PERCENT : 70;
const passed = pct >= threshold;
const quizObjectId = widget.getAttribute('data-object-id');
const quizObjectId = widget.dataset.objectId;
zone.innerHTML = '<div class="mangaResultCard">'
+ '<span class="mangaResultTitle">Fin du quiz</span>'
+ `<p class="mangaResultScore">Score final : ${correct} / ${total}</p>`