Fix: Suppression des gestionnaires inline pour conformité CSP

- Remplacement de tous les onclick/onchange par event delegation
- Utilisation de data attributes (data-todo-id, data-note-todo-id, data-user-id)
- Event listeners attachés aux conteneurs (#todoList, #noteTodosList, #usersTableBody)
- Plus besoin d'exposer les fonctions sur window
- Conforme à la Content Security Policy de Helmet.js
- Résout: 'script-src-attr' violations bloquant les actions de la todo list
This commit is contained in:
Claude committed 2025-11-11 12:12:21 +00:00
1 parent 8c39ead2c6
commit bdd2f7a86b
1 file changed
+59 -14
+59 -14
View File
@@ -420,10 +420,9 @@ function renderNoteTodos(todos) {
const item = document.createElement('div'); const item = document.createElement('div');
item.className = 'todo-item'; item.className = 'todo-item';
item.innerHTML = ` item.innerHTML = `
<input type="checkbox" class="todo-checkbox" ${todo.completed ? 'checked' : ''} <input type="checkbox" class="todo-checkbox" data-note-todo-id="${todo.id}" ${todo.completed ? 'checked' : ''}>
onchange="toggleNoteTodo(${todo.id}, this.checked)">
<span class="todo-text">${escapeHtml(todo.text)}</span> <span class="todo-text">${escapeHtml(todo.text)}</span>
<button class="todo-delete" onclick="deleteNoteTodo(${todo.id})">✕</button> <button class="todo-delete" data-note-todo-id="${todo.id}">✕</button>
`; `;
if (todo.completed) item.classList.add('completed'); if (todo.completed) item.classList.add('completed');
list.appendChild(item); list.appendChild(item);
@@ -573,10 +572,9 @@ function renderTodos() {
const item = document.createElement('div'); const item = document.createElement('div');
item.className = 'todo-item' + (todo.completed ? ' completed' : ''); item.className = 'todo-item' + (todo.completed ? ' completed' : '');
item.innerHTML = ` item.innerHTML = `
<input type="checkbox" class="todo-checkbox" ${todo.completed ? 'checked' : ''} <input type="checkbox" class="todo-checkbox" data-todo-id="${todo.id}" ${todo.completed ? 'checked' : ''}>
onchange="toggleTodo(${todo.id}, this.checked)">
<span class="todo-text">${escapeHtml(todo.text)}</span> <span class="todo-text">${escapeHtml(todo.text)}</span>
<button class="todo-delete" onclick="deleteTodo(${todo.id})">✕</button> <button class="todo-delete" data-todo-id="${todo.id}">✕</button>
`; `;
todoList.appendChild(item); todoList.appendChild(item);
}); });
@@ -599,17 +597,16 @@ async function addTodo() {
} }
} }
// Exposer globalement pour les event handlers inline async function toggleTodo(id, completed) {
window.toggleTodo = async function(id, completed) {
try { try {
await api.put(`/api/todos/${id}`, { completed }); await api.put(`/api/todos/${id}`, { completed });
await loadTodos(); await loadTodos();
} catch (error) { } catch (error) {
console.error('Erreur toggle todo:', error); console.error('Erreur toggle todo:', error);
} }
}; }
window.deleteTodo = async function(id) { async function deleteTodo(id) {
const confirmed = await confirmDialog.show({ const confirmed = await confirmDialog.show({
icon: '🗑️', icon: '🗑️',
title: 'Supprimer cette tâche', title: 'Supprimer cette tâche',
@@ -625,7 +622,7 @@ window.deleteTodo = async function(id) {
} catch (error) { } catch (error) {
console.error('Erreur suppression todo:', error); console.error('Erreur suppression todo:', error);
} }
}; }
function setTodoFilter(filter) { function setTodoFilter(filter) {
state.filter = filter; state.filter = filter;
@@ -696,7 +693,7 @@ function renderUsersTable(users) {
<td>${new Date(user.created_at).toLocaleDateString('fr-FR')}</td> <td>${new Date(user.created_at).toLocaleDateString('fr-FR')}</td>
<td> <td>
${user.id !== state.user.id ? ` ${user.id !== state.user.id ? `
<button class="btn-delete-user" onclick="deleteUser(${user.id}, '${user.username}')"> <button class="btn-delete-user" data-user-id="${user.id}" data-username="${escapeHtml(user.username)}">
🗑️ Supprimer 🗑️ Supprimer
</button> </button>
` : '<span style="color: var(--text-secondary);">Vous-même</span>'} ` : '<span style="color: var(--text-secondary);">Vous-même</span>'}
@@ -759,8 +756,7 @@ async function createUser() {
} }
} }
// Fonction globale pour la suppression d'utilisateur (appelée depuis onclick) async function deleteUser(id, username) {
window.deleteUser = async function(id, username) {
const confirmed = await confirmDialog.show({ const confirmed = await confirmDialog.show({
icon: '⚠️', icon: '⚠️',
title: 'Supprimer cet utilisateur', title: 'Supprimer cet utilisateur',
@@ -867,6 +863,55 @@ async function init() {
btn.addEventListener('click', () => setTodoFilter(btn.dataset.filter)); btn.addEventListener('click', () => setTodoFilter(btn.dataset.filter));
}); });
// Event delegation pour les todos de la sidebar
const todoList = document.getElementById('todoList');
if (todoList) {
todoList.addEventListener('change', (e) => {
if (e.target.classList.contains('todo-checkbox')) {
const todoId = parseInt(e.target.dataset.todoId);
toggleTodo(todoId, e.target.checked);
}
});
todoList.addEventListener('click', (e) => {
if (e.target.classList.contains('todo-delete')) {
const todoId = parseInt(e.target.dataset.todoId);
deleteTodo(todoId);
}
});
}
// Event delegation pour les todos des notes
const noteTodosList = document.getElementById('noteTodosList');
if (noteTodosList) {
noteTodosList.addEventListener('change', (e) => {
if (e.target.classList.contains('todo-checkbox')) {
const todoId = parseInt(e.target.dataset.noteTodoId);
toggleNoteTodo(todoId, e.target.checked);
}
});
noteTodosList.addEventListener('click', (e) => {
if (e.target.classList.contains('todo-delete')) {
const todoId = parseInt(e.target.dataset.noteTodoId);
deleteNoteTodo(todoId);
}
});
}
// Event delegation pour les boutons de suppression d'utilisateurs
const usersTableBody = document.getElementById('usersTableBody');
if (usersTableBody) {
usersTableBody.addEventListener('click', (e) => {
if (e.target.classList.contains('btn-delete-user') || e.target.closest('.btn-delete-user')) {
const btn = e.target.classList.contains('btn-delete-user') ? e.target : e.target.closest('.btn-delete-user');
const userId = parseInt(btn.dataset.userId);
const username = btn.dataset.username;
deleteUser(userId, username);
}
});
}
// Close modal on backdrop click // Close modal on backdrop click
const noteModal = document.getElementById('noteModal'); const noteModal = document.getElementById('noteModal');
if (noteModal) { if (noteModal) {