fix: modal source viewer — data-attributes + nettoyage ellipses

- Remplace onclick inline par data-excerpt/data-page + event delegation
  (élimine les problèmes d'échappement JS dans attributs HTML)
- Nettoie les "..." préfixe/suffixe des extraits avant recherche
- Fallback morceau central si l'extrait complet ne matche pas

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-02-18 21:40:20 +01:00
parent 40934fdc39
commit 5c8c2817ec

View File

@@ -289,7 +289,7 @@
<h3>Diagnostic principal</h3> <h3>Diagnostic principal</h3>
<div style="font-size:0.95rem;margin-bottom:0.5rem;"> <div style="font-size:0.95rem;margin-bottom:0.5rem;">
{{ dp.texte }} {{ dp.texte }}
{% if dp.source_page %}<button class="src-btn" onclick="showSource('{{ dp.source_excerpt|default('',true)|e }}', {{ dp.source_page }})">p.{{ dp.source_page }}</button>{% endif %} {% if dp.source_page %}<button class="src-btn" data-excerpt="{{ dp.source_excerpt|default('',true)|e }}" data-page="{{ dp.source_page }}">p.{{ dp.source_page }}</button>{% endif %}
</div> </div>
{% if dp.cim10_suggestion %} {% if dp.cim10_suggestion %}
<span class="badge" style="background:#dbeafe;color:#1d4ed8;font-size:0.85rem;">{{ dp.cim10_suggestion }}</span> <span class="badge" style="background:#dbeafe;color:#1d4ed8;font-size:0.85rem;">{{ dp.cim10_suggestion }}</span>
@@ -358,7 +358,7 @@
<span class="badge" style="background:#e0e7ff;color:#3730a3;font-size:0.7rem;">{{ das.source }}</span> <span class="badge" style="background:#e0e7ff;color:#3730a3;font-size:0.7rem;">{{ das.source }}</span>
{% endif %} {% endif %}
{% if das.source_page %} {% if das.source_page %}
<button class="src-btn" onclick="showSource('{{ das.source_excerpt|default('',true)|e }}', {{ das.source_page }})">p.{{ das.source_page }}</button> <button class="src-btn" data-excerpt="{{ das.source_excerpt|default('',true)|e }}" data-page="{{ das.source_page }}">p.{{ das.source_page }}</button>
{% endif %} {% endif %}
</td> </td>
<td style="font-size:0.8rem;color:#475569;"> <td style="font-size:0.8rem;color:#475569;">
@@ -430,7 +430,7 @@
<div style="font-size:0.7rem;color:#dc2626;">{{ alerte }}</div> <div style="font-size:0.7rem;color:#dc2626;">{{ alerte }}</div>
{% endfor %} {% endfor %}
</td> </td>
<td>{% if a.source_page %}<button class="src-btn" onclick="showSource('{{ a.source_excerpt|default('',true)|e }}', {{ a.source_page }})">p.{{ a.source_page }}</button>{% endif %}</td> <td>{% if a.source_page %}<button class="src-btn" data-excerpt="{{ a.source_excerpt|default('',true)|e }}" data-page="{{ a.source_page }}">p.{{ a.source_page }}</button>{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -450,7 +450,7 @@
<td>{{ b.test }}</td> <td>{{ b.test }}</td>
<td>{{ b.valeur or '' }}</td> <td>{{ b.valeur or '' }}</td>
<td>{% if b.anomalie %}<span class="badge" style="background:#fee2e2;color:#dc2626;">Oui</span>{% else %}—{% endif %}</td> <td>{% if b.anomalie %}<span class="badge" style="background:#fee2e2;color:#dc2626;">Oui</span>{% else %}—{% endif %}</td>
<td>{% if b.source_page %}<button class="src-btn" onclick="showSource('{{ b.source_excerpt|default('',true)|e }}', {{ b.source_page }})">p.{{ b.source_page }}</button>{% endif %}</td> <td>{% if b.source_page %}<button class="src-btn" data-excerpt="{{ b.source_excerpt|default('',true)|e }}" data-page="{{ b.source_page }}">p.{{ b.source_page }}</button>{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -466,7 +466,7 @@
<div style="margin-bottom:0.5rem;"> <div style="margin-bottom:0.5rem;">
<strong>{{ img.type }}</strong> <strong>{{ img.type }}</strong>
{% if img.score %} — Score : {{ img.score }}{% endif %} {% if img.score %} — Score : {{ img.score }}{% endif %}
{% if img.source_page %}<button class="src-btn" onclick="showSource('{{ img.source_excerpt|default('',true)|e }}', {{ img.source_page }})">p.{{ img.source_page }}</button>{% endif %} {% if img.source_page %}<button class="src-btn" data-excerpt="{{ img.source_excerpt|default('',true)|e }}" data-page="{{ img.source_page }}">p.{{ img.source_page }}</button>{% endif %}
{% if img.conclusion %} {% if img.conclusion %}
<div style="font-size:0.85rem;color:#475569;">{{ img.conclusion }}</div> <div style="font-size:0.85rem;color:#475569;">{{ img.conclusion }}</div>
{% endif %} {% endif %}
@@ -487,7 +487,7 @@
<td>{{ t.medicament }}</td> <td>{{ t.medicament }}</td>
<td>{{ t.posologie or '' }}</td> <td>{{ t.posologie or '' }}</td>
<td>{% if t.code_atc %}<span class="badge" style="background:#e0e7ff;color:#3730a3;">{{ t.code_atc }}</span>{% endif %}</td> <td>{% if t.code_atc %}<span class="badge" style="background:#e0e7ff;color:#3730a3;">{{ t.code_atc }}</span>{% endif %}</td>
<td>{% if t.source_page %}<button class="src-btn" onclick="showSource('{{ t.source_excerpt|default('',true)|e }}', {{ t.source_page }})">p.{{ t.source_page }}</button>{% endif %}</td> <td>{% if t.source_page %}<button class="src-btn" data-excerpt="{{ t.source_excerpt|default('',true)|e }}" data-page="{{ t.source_page }}">p.{{ t.source_page }}</button>{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -501,7 +501,7 @@
<h3>Antécédents ({{ dossier.antecedents|length }})</h3> <h3>Antécédents ({{ dossier.antecedents|length }})</h3>
<ul class="bullet"> <ul class="bullet">
{% for a in dossier.antecedents %} {% for a in dossier.antecedents %}
<li>{{ a.texte }}{% if a.source_page %} <button class="src-btn" onclick="showSource('{{ a.source_excerpt|default('',true)|e }}', {{ a.source_page }})">p.{{ a.source_page }}</button>{% endif %}</li> <li>{{ a.texte }}{% if a.source_page %} <button class="src-btn" data-excerpt="{{ a.source_excerpt|default('',true)|e }}" data-page="{{ a.source_page }}">p.{{ a.source_page }}</button>{% endif %}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@@ -513,7 +513,7 @@
<h3>Complications ({{ dossier.complications|length }})</h3> <h3>Complications ({{ dossier.complications|length }})</h3>
<ul class="bullet"> <ul class="bullet">
{% for c in dossier.complications %} {% for c in dossier.complications %}
<li>{{ c.texte }}{% if c.source_page %} <button class="src-btn" onclick="showSource('{{ c.source_excerpt|default('',true)|e }}', {{ c.source_page }})">p.{{ c.source_page }}</button>{% endif %}</li> <li>{{ c.texte }}{% if c.source_page %} <button class="src-btn" data-excerpt="{{ c.source_excerpt|default('',true)|e }}" data-page="{{ c.source_page }}">p.{{ c.source_page }}</button>{% endif %}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@@ -540,7 +540,7 @@ let _sourceCache = null;
function getDossierId() { function getDossierId() {
// filepath = "103_23056749/103_23056749_fusionne_cim10.json" // filepath = "103_23056749/103_23056749_fusionne_cim10.json"
// dossier_id = "103_23056749" // dossier_id = "103_23056749"
const fp = '{{ filepath }}'; const fp = {{ filepath|tojson }};
const parts = fp.split('/'); const parts = fp.split('/');
return parts.length > 1 ? parts.slice(0, -1).join('/') : ''; return parts.length > 1 ? parts.slice(0, -1).join('/') : '';
} }
@@ -574,13 +574,25 @@ async function showSource(excerpt, page) {
return; return;
} }
// Nettoyer l'extrait : retirer les "..." ajoutés par extract_excerpt()
let searchText = (excerpt || '').trim();
if (searchText.startsWith('...')) searchText = searchText.substring(3);
if (searchText.endsWith('...')) searchText = searchText.slice(0, -3);
searchText = searchText.trim();
// Chercher l'extrait dans le texte et le surligner // Chercher l'extrait dans le texte et le surligner
if (excerpt && excerpt.length > 10) { if (searchText.length > 10) {
const idx = allText.indexOf(excerpt); let idx = allText.indexOf(searchText);
// Fallback : chercher un morceau central (résiste mieux à l'anonymisation)
if (idx < 0 && searchText.length > 60) {
const mid = Math.floor(searchText.length / 2);
searchText = searchText.substring(mid - 30, mid + 30);
idx = allText.indexOf(searchText);
}
if (idx >= 0) { if (idx >= 0) {
const before = allText.substring(0, idx); const before = allText.substring(0, idx);
const match = allText.substring(idx, idx + excerpt.length); const match = allText.substring(idx, idx + searchText.length);
const after = allText.substring(idx + excerpt.length); const after = allText.substring(idx + searchText.length);
content.innerHTML = ''; content.innerHTML = '';
content.appendChild(document.createTextNode(before)); content.appendChild(document.createTextNode(before));
const mark = document.createElement('mark'); const mark = document.createElement('mark');
@@ -615,6 +627,14 @@ document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeSource(); if (e.key === 'Escape') closeSource();
}); });
// Délégation événements pour tous les boutons .src-btn
document.addEventListener('click', function(e) {
const btn = e.target.closest('.src-btn');
if (btn && btn.dataset.page) {
showSource(btn.dataset.excerpt || '', parseInt(btn.dataset.page));
}
});
/* --- Reprocess --- */ /* --- Reprocess --- */
document.getElementById('reprocess-btn').addEventListener('click', async () => { document.getElementById('reprocess-btn').addEventListener('click', async () => {
const btn = document.getElementById('reprocess-btn'); const btn = document.getElementById('reprocess-btn');