From ddf3f79e7aa214473095d81e65e07ca7f5c4694b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9drix?=
Date: Wed, 23 Sep 2026 12:42:22 +0200
Subject: [PATCH] =?UTF-8?q?Rendre=20cliquables=20les=20adresses=20=C3=A9cr?=
=?UTF-8?q?ites=20dans=20l'agenda?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
« infos et inscriptions sur https://installparty.alpinux.org » s'affichait
en texte mort dans la fiche d'un événement : à recopier à la main. Les
adresses du descriptif et du lieu deviennent des liens, la ponctuation
qui termine la phrase restant en dehors.
Le texte vient du calendrier, donc de quelqu'un qui écrit du texte et
non du HTML : il est échappé avant d'être lié, et les titres et lieux
des cartes le sont aussi, ce qui n'était pas le cas jusqu'ici.
Co-Authored-By: Claude Opus 5 (1M context)
Claude-Session: https://claude.ai/code/session_01JYfYUZRcJmmEWJwAxZb4o2
---
site/index.html | 42 ++++++++++++++++++++++++++++++++++--------
1 file changed, 34 insertions(+), 8 deletions(-)
diff --git a/site/index.html b/site/index.html
index 2264ee6..60e9c26 100644
--- a/site/index.html
+++ b/site/index.html
@@ -437,6 +437,8 @@
}
.ev-modal-body { font-size: .92rem; line-height: 1.6; color: #444; }
+ .ev-modal-body a { color: #1a6bbf; word-break: break-word; }
+ .ev-modal-body a:hover { text-decoration: none; }
.event-date {
font-size: .82rem;
@@ -1088,6 +1090,30 @@
}
}
+ /* Le calendrier est saisi par des humains : son texte est du texte, jamais du HTML. */
+ function echapper(texte) {
+ return String(texte == null ? '' : texte)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"');
+ }
+
+ /* Rend cliquables les adresses écrites dans un descriptif. Le texte reçu est déjà
+ échappé — le motif s'arrête donc avant une balise — et la ponctuation qui termine
+ une phrase reste en dehors du lien. */
+ function avecLiens(texteEchappe) {
+ return texteEchappe.replace(/https?:\/\/[^\s<]+/g, function(url) {
+ var queue = '';
+ var fin = /[.,;:!?»)\]]+$/.exec(url);
+ if (fin) {
+ queue = fin[0];
+ url = url.slice(0, url.length - queue.length);
+ }
+ return '' + url + '' + queue;
+ });
+ }
+
function showDetail(date, evs, el) {
el.hidden = false;
el.innerHTML = '' +
@@ -1097,9 +1123,9 @@
var time = e.allDay ? '' :
' ' +
e.start.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' }) + '';
- return '' + e.title + time +
+ return '' + echapper(e.title) + time +
(e.desc ? ' ℹ' : '') +
- (e.location ? ' — ' + e.location + '' : '') +
+ (e.location ? ' — ' + echapper(e.location) + '' : '') +
'';
}).join('') + '';
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
@@ -1120,11 +1146,11 @@
var descHtml = '';
if (ev.desc) {
descHtml = '' +
- ev.desc.replace(/&/g,'&').replace(//g,'>') + '
';
+ avecLiens(echapper(ev.desc)) + '
';
}
body.innerHTML =
'📅 ' + dateStr + '
' +
- (ev.location ? '📍 ' + ev.location + '
' : '') +
+ (ev.location ? '📍 ' + avecLiens(echapper(ev.location)) + '
' : '') +
descHtml;
modal.hidden = false;
document.getElementById('ev-modal').querySelector('.ev-modal-close').focus();
@@ -1156,8 +1182,8 @@
: ev.start.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' }) +
(ev.end ? ' – ' + ev.end.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' }) : '')) +
'' +
- '' + ev.title + '
' +
- (ev.location ? '📍 ' + ev.location + '
' : '');
+ '' + echapper(ev.title) + '
' +
+ (ev.location ? '📍 ' + echapper(ev.location) + '
' : '');
card.addEventListener('click', function() { openModal(ev); });
container.appendChild(card);
});
@@ -1179,8 +1205,8 @@
card.innerHTML =
(ev.desc ? 'ℹ' : '') +
'' + fmtDate(ev.start, ev.allDay) + '
' +
- '' + ev.title + '
' +
- (ev.location ? '📍 ' + ev.location + '
' : '');
+ '' + echapper(ev.title) + '
' +
+ (ev.location ? '📍 ' + echapper(ev.location) + '
' : '');
card.addEventListener('click', function() { openModal(ev); });
container.appendChild(card);
});