diff --git a/public/js/admin/audit-analytics.js b/public/js/admin/audit-analytics.js index 7ef8def6..fddb7285 100644 --- a/public/js/admin/audit-analytics.js +++ b/public/js/admin/audit-analytics.js @@ -247,3 +247,15 @@ init(); }); } +// Event delegation for data-action buttons (CSP compliance) +document.addEventListener('click', (e) => { + const button = e.target.closest('[data-action]'); + if (!button) return; + + const action = button.dataset.action; + const arg0 = button.dataset.arg0; + + if (action === 'showDecisionDetails') { + showDecisionDetails(arg0); + } +}); diff --git a/public/js/admin/claude-md-migrator.js b/public/js/admin/claude-md-migrator.js index 9239eff4..eee97ab2 100644 --- a/public/js/admin/claude-md-migrator.js +++ b/public/js/admin/claude-md-migrator.js @@ -480,3 +480,19 @@ function getPersistenceColor(persistence) { }; return colors[persistence] || 'bg-gray-100 text-gray-800'; } + +// Event delegation for data-change-action checkboxes (CSP compliance) +document.addEventListener('change', (e) => { + const checkbox = e.target.closest('[data-change-action]'); + if (!checkbox) return; + + const action = checkbox.dataset.changeAction; + const index = parseInt(checkbox.dataset.index); + + if (action === 'toggleCandidate') { + // Need to get the candidate from the analysis based on index + if (window.currentAnalysis && window.currentAnalysis.candidates[index]) { + toggleCandidate(window.currentAnalysis.candidates[index], checkbox.checked); + } + } +}); diff --git a/public/js/admin/dashboard.js b/public/js/admin/dashboard.js index 18f88e48..a45f67af 100644 --- a/public/js/admin/dashboard.js +++ b/public/js/admin/dashboard.js @@ -396,8 +396,26 @@ document.getElementById('queue-filter')?.addEventListener('change', (e) => { loadStatistics(); loadRecentActivity(); -// Make functions global for onclick handlers -window.approveItem = approveItem; -window.rejectItem = rejectItem; -window.deleteUser = deleteUser; -window.deleteDocument = deleteDocument; +// Event delegation for data-action buttons (CSP compliance) +document.addEventListener('click', (e) => { + const button = e.target.closest('[data-action]'); + if (!button) return; + + const action = button.dataset.action; + const arg0 = button.dataset.arg0; + + switch (action) { + case 'approveItem': + approveItem(arg0); + break; + case 'rejectItem': + rejectItem(arg0); + break; + case 'deleteUser': + deleteUser(arg0); + break; + case 'deleteDocument': + deleteDocument(arg0); + break; + } +}); diff --git a/public/js/admin/project-editor.js b/public/js/admin/project-editor.js index a2f2897c..2ecdb539 100644 --- a/public/js/admin/project-editor.js +++ b/public/js/admin/project-editor.js @@ -766,3 +766,18 @@ function escapeHtml(text) { // Create global instance window.projectEditor = new ProjectEditor(); + +// Event delegation for data-action buttons (CSP compliance) +document.addEventListener('click', (e) => { + const button = e.target.closest('[data-action]'); + if (!button) return; + + const action = button.dataset.action; + const arg0 = button.dataset.arg0; + + if (action === 'editVariable') { + window.projectEditor.editVariable(arg0); + } else if (action === 'deleteVariable') { + window.projectEditor.deleteVariable(arg0); + } +}); diff --git a/public/js/admin/project-manager.js b/public/js/admin/project-manager.js index 526ae5c4..11b1334f 100644 --- a/public/js/admin/project-manager.js +++ b/public/js/admin/project-manager.js @@ -386,11 +386,33 @@ function escapeHtml(text) { return div.innerHTML; } -// Make functions global for onclick handlers -window.viewProject = viewProject; -window.editProject = editProject; -window.manageVariables = manageVariables; -window.deleteProject = deleteProject; +// Event delegation for data-action buttons (CSP compliance) +document.addEventListener('click', (e) => { + const button = e.target.closest('[data-action]'); + if (!button) return; + + const action = button.dataset.action; + const arg0 = button.dataset.arg0; + const arg1 = button.dataset.arg1; + + switch (action) { + case 'viewProject': + viewProject(arg0); + break; + case 'manageVariables': + manageVariables(arg0); + break; + case 'editProject': + editProject(arg0); + break; + case 'deleteProject': + deleteProject(arg0, arg1); + break; + case 'remove-parent': + button.parentElement.remove(); + break; + } +}); // Initialize on page load loadStatistics(); diff --git a/public/js/admin/rule-editor.js b/public/js/admin/rule-editor.js index 0b144c0b..b4138d2f 100644 --- a/public/js/admin/rule-editor.js +++ b/public/js/admin/rule-editor.js @@ -1092,3 +1092,20 @@ window.ruleEditor = new RuleEditor(); }); } +// Event delegation for data-action buttons (CSP compliance) +document.addEventListener('click', (e) => { + const button = e.target.closest('[data-action]'); + if (!button) return; + + const action = button.dataset.action; + const arg0 = button.dataset.arg0; + + switch (action) { + case 'editRule': + editRule(arg0); + break; + case 'remove-parent': + button.parentElement.remove(); + break; + } +}); diff --git a/public/js/admin/rule-manager.js b/public/js/admin/rule-manager.js index 090efc8a..77d8125d 100644 --- a/public/js/admin/rule-manager.js +++ b/public/js/admin/rule-manager.js @@ -676,3 +676,31 @@ loadRules(); }); } +// Event delegation for data-action buttons (CSP compliance) +document.addEventListener('click', (e) => { + const button = e.target.closest('[data-action]'); + if (!button) return; + + const action = button.dataset.action; + const arg0 = button.dataset.arg0; + const arg1 = button.dataset.arg1; + + switch (action) { + case 'viewRule': + viewRule(arg0); + break; + case 'editRule': + editRule(arg0); + break; + case 'deleteRule': + deleteRule(arg0, arg1); + break; + case 'goToPage': + goToPage(parseInt(arg0)); + break; + case 'remove-parent': + button.parentElement.remove(); + break; + } +}); + diff --git a/scripts/add-event-delegation.js b/scripts/add-event-delegation.js new file mode 100644 index 00000000..ec0fd1f1 --- /dev/null +++ b/scripts/add-event-delegation.js @@ -0,0 +1,121 @@ +#!/usr/bin/env node + +/** + * Add event delegation to remaining admin files + */ + +const fs = require('fs'); +const path = require('path'); + +// project-editor.js +const projectEditorFile = path.join(__dirname, '../public/js/admin/project-editor.js'); +let projectEditorContent = fs.readFileSync(projectEditorFile, 'utf8'); + +const projectEditorDelegation = ` +// Event delegation for data-action buttons (CSP compliance) +document.addEventListener('click', (e) => { + const button = e.target.closest('[data-action]'); + if (!button) return; + + const action = button.dataset.action; + const arg0 = button.dataset.arg0; + + if (action === 'editVariable') { + window.projectEditor.editVariable(arg0); + } else if (action === 'deleteVariable') { + window.projectEditor.deleteVariable(arg0); + } +}); +`; + +if (!projectEditorContent.includes('Event delegation for data-action')) { + // Add before the end + projectEditorContent = projectEditorContent.trim() + '\n' + projectEditorDelegation; + fs.writeFileSync(projectEditorFile, projectEditorContent); + console.log('✓ Added event delegation to project-editor.js'); +} + +// rule-editor.js +const ruleEditorFile = path.join(__dirname, '../public/js/admin/rule-editor.js'); +let ruleEditorContent = fs.readFileSync(ruleEditorFile, 'utf8'); + +const ruleEditorDelegation = ` +// Event delegation for data-action buttons (CSP compliance) +document.addEventListener('click', (e) => { + const button = e.target.closest('[data-action]'); + if (!button) return; + + const action = button.dataset.action; + const arg0 = button.dataset.arg0; + + switch (action) { + case 'editRule': + editRule(arg0); + break; + case 'remove-parent': + button.parentElement.remove(); + break; + } +}); +`; + +if (!ruleEditorContent.includes('Event delegation for data-action')) { + ruleEditorContent = ruleEditorContent.trim() + '\n' + ruleEditorDelegation; + fs.writeFileSync(ruleEditorFile, ruleEditorContent); + console.log('✓ Added event delegation to rule-editor.js'); +} + +// audit-analytics.js +const auditFile = path.join(__dirname, '../public/js/admin/audit-analytics.js'); +let auditContent = fs.readFileSync(auditFile, 'utf8'); + +const auditDelegation = ` +// Event delegation for data-action buttons (CSP compliance) +document.addEventListener('click', (e) => { + const button = e.target.closest('[data-action]'); + if (!button) return; + + const action = button.dataset.action; + const arg0 = button.dataset.arg0; + + if (action === 'showDecisionDetails') { + showDecisionDetails(arg0); + } +}); +`; + +if (!auditContent.includes('Event delegation for data-action')) { + auditContent = auditContent.trim() + '\n' + auditDelegation; + fs.writeFileSync(auditFile, auditContent); + console.log('✓ Added event delegation to audit-analytics.js'); +} + +// claude-md-migrator.js +const migratorFile = path.join(__dirname, '../public/js/admin/claude-md-migrator.js'); +let migratorContent = fs.readFileSync(migratorFile, 'utf8'); + +const migratorDelegation = ` +// Event delegation for data-change-action checkboxes (CSP compliance) +document.addEventListener('change', (e) => { + const checkbox = e.target.closest('[data-change-action]'); + if (!checkbox) return; + + const action = checkbox.dataset.changeAction; + const index = parseInt(checkbox.dataset.index); + + if (action === 'toggleCandidate') { + // Need to get the candidate from the analysis based on index + if (window.currentAnalysis && window.currentAnalysis.candidates[index]) { + toggleCandidate(window.currentAnalysis.candidates[index], checkbox.checked); + } + } +}); +`; + +if (!migratorContent.includes('Event delegation for data-change-action')) { + migratorContent = migratorContent.trim() + '\n' + migratorDelegation; + fs.writeFileSync(migratorFile, migratorContent); + console.log('✓ Added event delegation to claude-md-migrator.js'); +} + +console.log('\n✅ Event delegation added to all remaining admin files\n');