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
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:
co-authored by
Claude Sonnet 5
parent
66d8eaeae8
commit
ccf836f2c5
@@ -43,12 +43,12 @@ function triggerBubbleKind(speaker) {
|
||||
// valeur déjà enregistrée.
|
||||
function triggerSpeakerOptions(currentSpeaker) {
|
||||
const options = ['Joueur'].concat(SCENE_OBJECT_NAMES);
|
||||
if (currentSpeaker && options.indexOf(currentSpeaker) === -1) options.push(currentSpeaker);
|
||||
if (currentSpeaker && !options.includes(currentSpeaker)) options.push(currentSpeaker);
|
||||
return options;
|
||||
}
|
||||
|
||||
function triggerEscAttr(str) {
|
||||
return (str || '').replace(/"/g, '"');
|
||||
return (str || '').replaceAll('"', '"');
|
||||
}
|
||||
|
||||
// ---------- Colonne = un graphe de logique lu par déclencheur ----------
|
||||
@@ -82,11 +82,17 @@ function triggerAudioAssetOptions() {
|
||||
return (typeof USER_ASSETS_OPTIONS !== 'undefined' ? USER_ASSETS_OPTIONS : []).filter((a) => a.kind === 'audio');
|
||||
}
|
||||
|
||||
function triggerAudioOptionHtml(asset, audioUrl) {
|
||||
const selected = asset.url === audioUrl ? ' selected' : '';
|
||||
return `<option value="${asset.url.replaceAll('"', '"')}"${selected}>${asset.name.replaceAll('<', '<')}</option>`;
|
||||
}
|
||||
|
||||
function triggerBubbleAudioControlHtml(dialogueId, index, audioUrl) {
|
||||
const assetOptions = triggerAudioAssetOptions();
|
||||
const selectOptions = `<option value="">Aucune voix off</option>${assetOptions.map((a) => `<option value="${a.url.replace(/"/g, '"')}"${a.url === audioUrl ? ' selected' : ''}>${a.name.replace(/</g, '<')}</option>`).join('')}`;
|
||||
const optionsHtml = assetOptions.map((a) => triggerAudioOptionHtml(a, audioUrl)).join('');
|
||||
const selectOptions = `<option value="">Aucune voix off</option>${optionsHtml}`;
|
||||
const preview = audioUrl
|
||||
? `<audio controls preload="none" src="${audioUrl.replace(/"/g, '"')}" class="questBubbleAudioPreview"></audio>`
|
||||
? `<audio controls preload="none" src="${audioUrl.replaceAll('"', '"')}" class="questBubbleAudioPreview"></audio>`
|
||||
: '';
|
||||
return (
|
||||
'<div class="questBubbleAudioRow">'
|
||||
@@ -101,7 +107,7 @@ function triggerBubbleAudioControlHtml(dialogueId, index, audioUrl) {
|
||||
// optionnelle) — trois zones distinctes, pas un seul bloc.
|
||||
function triggerDialogueBubbleHtml(dialogueId, line, index) {
|
||||
const kind = triggerBubbleKind(line.speaker);
|
||||
const speakerSelect = triggerSpeakerOptions(line.speaker).map((name) => `<option value="${name.replace(/"/g, '"')}"${name === line.speaker ? ' selected' : ''}>${name === 'Joueur' ? 'Joueur' : name}</option>`).join('');
|
||||
const speakerSelect = triggerSpeakerOptions(line.speaker).map((name) => `<option value="${name.replaceAll('"', '"')}"${name === line.speaker ? ' selected' : ''}>${name === 'Joueur' ? 'Joueur' : name}</option>`).join('');
|
||||
return (
|
||||
'<div class="questBubbleRow">'
|
||||
+ `<div class="questBubble questBubble-${kind}">`
|
||||
@@ -112,7 +118,7 @@ function triggerDialogueBubbleHtml(dialogueId, line, index) {
|
||||
+ '<div class="questBubbleBody">'
|
||||
+ '<textarea class="questBubbleText" rows="1" placeholder="Réplique..." '
|
||||
+ 'oninput="this.style.height=\'auto\'; this.style.height=this.scrollHeight+\'px\';" '
|
||||
+ `onchange="triggerInlineUpdateBubbleText('${dialogueId}', ${index}, this.value)">${line.text.replace(/</g, '<')}</textarea>${
|
||||
+ `onchange="triggerInlineUpdateBubbleText('${dialogueId}', ${index}, this.value)">${line.text.replaceAll('<', '<')}</textarea>${
|
||||
triggerBubbleAudioControlHtml(dialogueId, index, line.audio_url)
|
||||
}</div>`
|
||||
+ '</div>'
|
||||
@@ -168,7 +174,7 @@ function triggerQuestionBubbleHtml(dialogueId, line, index) {
|
||||
function triggerVariableActionLabel(item) {
|
||||
const opLabel = (typeof DATA_OPERATION_LABELS_MAP !== 'undefined' && DATA_OPERATION_LABELS_MAP[item.data_operation]) || item.data_operation;
|
||||
const valuePart = (item.data_value !== undefined && item.data_value !== null && item.data_value !== '') ? ` ${item.data_value}` : '';
|
||||
return `${(item.target_variable || '').replace(/</g, '<')} — ${opLabel.replace(/</g, '<')}${valuePart}`;
|
||||
return `${(item.target_variable || '').replaceAll('<', '<')} — ${opLabel.replaceAll('<', '<')}${valuePart}`;
|
||||
}
|
||||
|
||||
// Feuille "Modifier une variable" — un encart compact (même largeur que
|
||||
@@ -201,7 +207,7 @@ const COLLISION_LEAF_PILL_LABELS = {
|
||||
};
|
||||
|
||||
function triggerItemLeafId(item) {
|
||||
return item.type === 'dialogue' ? (item.dialogue && item.dialogue.id) : item.id;
|
||||
return item.type === 'dialogue' ? item.dialogue?.id : item.id;
|
||||
}
|
||||
|
||||
// URL commune retrait/déplacement — objet (routes/triggers/triggers.py)
|
||||
@@ -264,7 +270,7 @@ function triggerLeafPillHtml(item, target, targetId, index, total) {
|
||||
}
|
||||
|
||||
function triggerAssetFilename(url) {
|
||||
return ((url || '').split('/').pop() || url || '').replace(/</g, '<');
|
||||
return ((url || '').split('/').pop() || url || '').replaceAll('<', '<');
|
||||
}
|
||||
|
||||
function triggerGenericLeafBoxHtml(title, text) {
|
||||
@@ -295,7 +301,7 @@ function triggerLeafBoxHtml(item) {
|
||||
return triggerGenericLeafBoxHtml('Vidéo', `${triggerAssetFilename(item.asset_url)} — ${mLabel}`);
|
||||
}
|
||||
if (item.type === 'indication') {
|
||||
return triggerGenericLeafBoxHtml('Indication', `Objet #${item.object_id} — ${(item.texte || '').replace(/</g, '<')}`);
|
||||
return triggerGenericLeafBoxHtml('Indication', `Objet #${item.object_id} — ${(item.texte || '').replaceAll('<', '<')}`);
|
||||
}
|
||||
if (item.type === 'attendre') {
|
||||
return triggerGenericLeafBoxHtml('Attendre', `${item.data_value || '0'} seconde(s)`);
|
||||
@@ -328,7 +334,7 @@ function triggerAppendButtonHtml(target, targetId, afterId) {
|
||||
}
|
||||
|
||||
function triggerActionChainHtml(chain, target, targetId) {
|
||||
if (!chain || !chain.length) return '';
|
||||
if (!chain?.length) return '';
|
||||
return chain.map((item, i) => {
|
||||
const leafId = triggerItemLeafId(item);
|
||||
return (
|
||||
@@ -383,14 +389,14 @@ const _triggerCollapsed = {};
|
||||
let _triggerActiveDialogueId = null;
|
||||
|
||||
function _chainLeafIds(chain) {
|
||||
return (chain || []).map((item) => (item.type === 'dialogue' ? (item.dialogue && item.dialogue.id) : item.id)).filter(Boolean);
|
||||
return (chain || []).map((item) => (item.type === 'dialogue' ? item.dialogue?.id : item.id)).filter(Boolean);
|
||||
}
|
||||
|
||||
function _triggerLeafIds(trigger) {
|
||||
if (trigger.condition) {
|
||||
const vrai = trigger.condition.si_vrai; const
|
||||
faux = trigger.condition.si_faux;
|
||||
return _chainLeafIds(vrai && vrai.chain).concat(_chainLeafIds(faux && faux.chain));
|
||||
return _chainLeafIds(vrai?.chain).concat(_chainLeafIds(faux?.chain));
|
||||
}
|
||||
return _chainLeafIds(trigger.chain);
|
||||
}
|
||||
@@ -398,10 +404,11 @@ function _triggerLeafIds(trigger) {
|
||||
function triggerChainHtml(trigger) {
|
||||
const isCollapsed = !!_triggerCollapsed[trigger.object_id];
|
||||
const leafIds = _triggerLeafIds(trigger);
|
||||
const isActive = _triggerActiveDialogueId && leafIds.indexOf(_triggerActiveDialogueId) !== -1;
|
||||
const cardOnclick = `triggerClickObjectCard(${trigger.object_id}, ${leafIds[0] ? `'${leafIds[0]}'` : 'null'})`;
|
||||
const isActive = _triggerActiveDialogueId && leafIds.includes(_triggerActiveDialogueId);
|
||||
const firstLeafArg = leafIds[0] ? `'${leafIds[0]}'` : 'null';
|
||||
const cardOnclick = `triggerClickObjectCard(${trigger.object_id}, ${firstLeafArg})`;
|
||||
const card = `<div class="questTriggerObjectCard" title="Cliquer pour replier/déplier — replie aussi la logique associée" onclick="${cardOnclick}">${collisionRuleThumbHtml(trigger)
|
||||
}<span class="questTriggerObjectName">${(trigger.object_name || '').replace(/</g, '<')}</span>`
|
||||
}<span class="questTriggerObjectName">${(trigger.object_name || '').replaceAll('<', '<')}</span>`
|
||||
+ '<span class="questTriggerCollapseChevron">▾</span>'
|
||||
+ '</div>';
|
||||
if (isCollapsed) {
|
||||
@@ -465,7 +472,7 @@ function triggerCondArrowHtml(kind, label) {
|
||||
|
||||
function triggerConditionColumnHtml(condition, target, targetId) {
|
||||
const opLabel = CONDITION_OPERATOR_LABELS_MAP[condition.operateur] || condition.operateur;
|
||||
const label = `${(condition.variable || '').replace(/</g, '<')} ${(opLabel || '').replace(/</g, '<')} ${(condition.valeur || '').replace(/</g, '<')}`;
|
||||
const label = `${(condition.variable || '').replaceAll('<', '<')} ${(opLabel || '').replaceAll('<', '<')} ${(condition.valeur || '').replaceAll('<', '<')}`;
|
||||
const branchesHtml = ['si_vrai', 'si_faux'].map((branchKey) => {
|
||||
const kind = branchKey === 'si_vrai' ? 'yes' : 'no';
|
||||
const arrowLabel = branchKey === 'si_vrai' ? '✓ Vrai' : '✕ Faux';
|
||||
@@ -520,7 +527,7 @@ function _registerChainDialogues(chain, owner, dialogueIds) {
|
||||
// l'autre.
|
||||
function _clearRegistryEntriesWithKey(key) {
|
||||
Object.keys(_triggerDialogueRegistry).forEach((id) => {
|
||||
if (Object.prototype.hasOwnProperty.call(_triggerDialogueRegistry[id], key)) delete _triggerDialogueRegistry[id];
|
||||
if (Object.hasOwn(_triggerDialogueRegistry[id], key)) delete _triggerDialogueRegistry[id];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -550,8 +557,8 @@ function triggerLoadAll() {
|
||||
// sauf s'il y en a déjà un actif encore présent, auquel cas on le
|
||||
// garde (cliquer sur une autre carte reste possible, voir
|
||||
// triggerClickObjectCard).
|
||||
if (dialogueIds.indexOf(_triggerActiveDialogueId) === -1) {
|
||||
_triggerActiveDialogueId = dialogueIds[dialogueIds.length - 1] || null;
|
||||
if (!dialogueIds.includes(_triggerActiveDialogueId)) {
|
||||
_triggerActiveDialogueId = dialogueIds.at(-1) || null;
|
||||
}
|
||||
triggerRenderSlot();
|
||||
});
|
||||
@@ -635,11 +642,14 @@ function triggerInlineAddQuestion(dialogueId) {
|
||||
triggerInlineSaveDialogue(dialogueId);
|
||||
}
|
||||
|
||||
// Même effet que triggerInlineUpdateBubbleText ci-dessous (mettre à jour
|
||||
// `.text` d'une ligne puis sauvegarder) — deux noms distincts car
|
||||
// référencés séparément dans le HTML généré (onchange de la question vs.
|
||||
// onchange de la bulle de réplique, voir triggerQuestionBubbleHtml/
|
||||
// triggerDialogueBubbleHtml), mais un seul corps (S4144 : déléguer plutôt
|
||||
// que dupliquer la logique).
|
||||
function triggerInlineUpdateQuestionText(dialogueId, index, text) {
|
||||
const entry = _triggerDialogueRegistry[dialogueId];
|
||||
if (!entry) return;
|
||||
entry.dialogueRef.lines[index].text = text;
|
||||
triggerInlineSaveDialogue(dialogueId);
|
||||
triggerInlineUpdateBubbleText(dialogueId, index, text);
|
||||
}
|
||||
|
||||
// Ajuste le NOMBRE de choix (2 à 4, voir db/dialogue_lines.py::
|
||||
@@ -651,7 +661,7 @@ function triggerInlineSetQuestionChoiceCount(dialogueId, index, value) {
|
||||
const entry = _triggerDialogueRegistry[dialogueId];
|
||||
if (!entry) return;
|
||||
const line = entry.dialogueRef.lines[index];
|
||||
const count = Math.max(2, Math.min(4, parseInt(value, 10) || 2));
|
||||
const count = Math.max(2, Math.min(4, Number.parseInt(value, 10) || 2));
|
||||
while (line.choices.length < count) line.choices.push('');
|
||||
while (line.choices.length > count) line.choices.pop();
|
||||
if (line.correct_index >= line.choices.length) line.correct_index = 0;
|
||||
@@ -676,7 +686,7 @@ function triggerInlineSetQuestionCorrectIndex(dialogueId, index, choiceIndex) {
|
||||
function triggerInlineSetQuestionRewardAmount(dialogueId, index, value) {
|
||||
const entry = _triggerDialogueRegistry[dialogueId];
|
||||
if (!entry) return;
|
||||
entry.dialogueRef.lines[index].reward_amount = Math.max(0, parseInt(value, 10) || 0);
|
||||
entry.dialogueRef.lines[index].reward_amount = Math.max(0, Number.parseInt(value, 10) || 0);
|
||||
triggerInlineSaveDialogue(dialogueId);
|
||||
}
|
||||
|
||||
@@ -755,7 +765,7 @@ function triggerRenderObjectStep() {
|
||||
if (!objects.length) { grid.innerHTML = '<p class="hint">Aucun objet éligible sur ce jeu.</p>'; return; }
|
||||
grid.innerHTML = objects.map((o, i) => `<button type="button" class="collisionWizardChoice questTriggerObjectChoice" style="animation-delay:${i * 40}ms" onclick="triggerSelectObject(${o.id})">`
|
||||
+ `<div class="questTriggerObjectThumb">${collisionRuleThumbHtml(o)}</div>`
|
||||
+ `<span>${(o.name || '').replace(/</g, '<')}</span>`
|
||||
+ `<span>${(o.name || '').replaceAll('<', '<')}</span>`
|
||||
+ '</button>').join('');
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user