mirror of
https://github.com/Maybe4a6f7365/PwnHub.git
synced 2026-08-25 18:14:15 -05:00
759 lines
25 KiB
JavaScript
759 lines
25 KiB
JavaScript
// PwnHub - Cyberpunk CTF Collaboration Platform
|
|
let currentChallengeId = null;
|
|
|
|
function showNotification(message, type = 'info') {
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification ${type}`;
|
|
notification.textContent = message;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.classList.add('show');
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
notification.classList.remove('show');
|
|
setTimeout(() => {
|
|
document.body.removeChild(notification);
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
function loadChecklistTemplate(category, challengeId) {
|
|
const params = new URLSearchParams();
|
|
params.append('action', 'load_template');
|
|
params.append('category', category);
|
|
params.append('challenge_id', challengeId);
|
|
|
|
fetch('/api/checklist', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
if (html.includes('success')) {
|
|
showNotification(`${category} template loaded successfully!`, 'success');
|
|
refreshChallengeDetail(challengeId);
|
|
} else {
|
|
showNotification('Error loading template', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error loading template', 'error');
|
|
});
|
|
}
|
|
|
|
function addCustomChecklistItem(challengeId) {
|
|
const addItemDiv = document.getElementById(`add-item-${challengeId}`);
|
|
if (!addItemDiv) {
|
|
showNotification('Error: Add item form not found', 'error');
|
|
return;
|
|
}
|
|
addItemDiv.style.display = 'block';
|
|
const inputField = document.getElementById(`new-item-${challengeId}`);
|
|
if (inputField) {
|
|
inputField.focus();
|
|
}
|
|
}
|
|
|
|
function saveCustomChecklistItem(challengeId) {
|
|
const itemText = document.getElementById(`new-item-${challengeId}`).value.trim();
|
|
if (!itemText) {
|
|
showNotification('Please enter an item description', 'error');
|
|
return;
|
|
}
|
|
|
|
const params = new URLSearchParams();
|
|
params.append('action', 'add_item');
|
|
params.append('challenge_id', challengeId);
|
|
params.append('item_text', itemText);
|
|
params.append('assignee', 'Anonymous');
|
|
|
|
fetch('/api/checklist', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
if (html.includes('success')) {
|
|
showNotification('Item added successfully!', 'success');
|
|
cancelAddItem(challengeId);
|
|
refreshChallengeDetail(challengeId);
|
|
} else {
|
|
showNotification('Error adding item', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error adding item', 'error');
|
|
});
|
|
}
|
|
|
|
function cancelAddItem(challengeId) {
|
|
const addItemDiv = document.getElementById(`add-item-${challengeId}`);
|
|
addItemDiv.style.display = 'none';
|
|
document.getElementById(`new-item-${challengeId}`).value = '';
|
|
}
|
|
|
|
function submitChecklistItem(event, challengeId) {
|
|
event.preventDefault();
|
|
|
|
const form = event.target;
|
|
const formData = new FormData(form);
|
|
|
|
const params = new URLSearchParams();
|
|
for (let [key, value] of formData.entries()) {
|
|
params.append(key, value);
|
|
}
|
|
|
|
fetch('/api/checklist', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
if (html.includes('success')) {
|
|
showNotification('Item added successfully!', 'success');
|
|
cancelAddItem(challengeId);
|
|
refreshChallengeDetail(challengeId);
|
|
} else {
|
|
showNotification('Error adding item', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error adding item', 'error');
|
|
});
|
|
}
|
|
|
|
function updateChecklistStatus(challengeId, itemText, status) {
|
|
const params = new URLSearchParams();
|
|
params.append('action', 'update_status');
|
|
params.append('challenge_id', challengeId);
|
|
params.append('item_text', itemText);
|
|
params.append('status', status);
|
|
params.append('assignee', 'Anonymous');
|
|
|
|
fetch('/api/checklist', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
if (html.includes('success')) {
|
|
showNotification(`Marked as ${status}`, 'success');
|
|
refreshChallengeDetail(challengeId);
|
|
} else {
|
|
showNotification('Error updating status', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error updating status', 'error');
|
|
});
|
|
}
|
|
|
|
function uploadFiles(challengeId, files) {
|
|
if (!files || files.length === 0) {
|
|
return;
|
|
}
|
|
|
|
for (let file of files) {
|
|
if (file.size > 10 * 1024 * 1024) {
|
|
showNotification(`File ${file.name} is too large (max 10MB)`, 'error');
|
|
continue;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
const params = new URLSearchParams();
|
|
params.append('challenge_id', challengeId);
|
|
params.append('filename', file.name);
|
|
params.append('content', e.target.result);
|
|
params.append('uploader', 'Anonymous');
|
|
|
|
fetch('/api/upload', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
if (html.includes('success')) {
|
|
showNotification(`File ${file.name} uploaded successfully!`, 'success');
|
|
refreshChallengeDetail(challengeId);
|
|
} else {
|
|
showNotification(`Error uploading ${file.name}`, 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error uploading file', 'error');
|
|
});
|
|
};
|
|
|
|
reader.readAsText(file);
|
|
}
|
|
}
|
|
|
|
function downloadFile(challengeId, filename) {
|
|
const link = document.createElement('a');
|
|
link.href = `/uploads/${challengeId}/${filename}`;
|
|
link.download = filename;
|
|
link.click();
|
|
showNotification(`Downloading ${filename}`, 'info');
|
|
}
|
|
|
|
function viewFile(challengeId, filename) {
|
|
let modal = document.getElementById('code-viewer-modal');
|
|
if (!modal) {
|
|
modal = document.createElement('div');
|
|
modal.id = 'code-viewer-modal';
|
|
modal.className = 'code-modal';
|
|
modal.innerHTML = `
|
|
<div class="code-modal-content">
|
|
<div class="code-modal-header">
|
|
<h3 id="code-modal-title"></h3>
|
|
<button class="code-modal-close" onclick="closeCodeViewer()">×</button>
|
|
</div>
|
|
<div class="code-modal-body">
|
|
<pre id="code-content"><code id="code-text"></code></pre>
|
|
</div>
|
|
<div class="code-modal-footer">
|
|
<button class="btn btn-primary" onclick="downloadFile('${challengeId}', document.getElementById('code-modal-title').dataset.filename)">
|
|
Download File
|
|
</button>
|
|
<button class="btn btn-secondary" onclick="closeCodeViewer()">
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(modal);
|
|
}
|
|
|
|
const titleElement = document.getElementById('code-modal-title');
|
|
titleElement.textContent = filename;
|
|
titleElement.dataset.filename = filename;
|
|
|
|
const codeText = document.getElementById('code-text');
|
|
codeText.textContent = 'Loading...';
|
|
|
|
modal.style.display = 'flex';
|
|
|
|
fetch(`/uploads/${challengeId}/${filename}`)
|
|
.then(response => response.text())
|
|
.then(content => {
|
|
codeText.textContent = content;
|
|
applySyntaxHighlighting(filename, codeText);
|
|
})
|
|
.catch(error => {
|
|
codeText.textContent = 'Error loading file content.';
|
|
showNotification('Error loading file', 'error');
|
|
});
|
|
}
|
|
|
|
function closeCodeViewer() {
|
|
const modal = document.getElementById('code-viewer-modal');
|
|
if (modal) {
|
|
modal.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function applySyntaxHighlighting(filename, codeElement) {
|
|
const extension = filename.split('.').pop().toLowerCase();
|
|
|
|
codeElement.className = '';
|
|
|
|
switch (extension) {
|
|
case 'py':
|
|
codeElement.className = 'language-python';
|
|
break;
|
|
case 'js':
|
|
codeElement.className = 'language-javascript';
|
|
break;
|
|
case 'sh':
|
|
codeElement.className = 'language-bash';
|
|
break;
|
|
case 'php':
|
|
codeElement.className = 'language-php';
|
|
break;
|
|
case 'c':
|
|
case 'cpp':
|
|
codeElement.className = 'language-c';
|
|
break;
|
|
case 'java':
|
|
codeElement.className = 'language-java';
|
|
break;
|
|
case 'rb':
|
|
codeElement.className = 'language-ruby';
|
|
break;
|
|
case 'go':
|
|
codeElement.className = 'language-go';
|
|
break;
|
|
case 'md':
|
|
codeElement.className = 'language-markdown';
|
|
break;
|
|
default:
|
|
codeElement.className = 'language-text';
|
|
}
|
|
}
|
|
|
|
function refreshChallengeDetail(challengeId) {
|
|
if (currentChallengeId === challengeId) {
|
|
loadChallengeDetail(challengeId);
|
|
}
|
|
}
|
|
|
|
function loadChallengeDetail(challengeId) {
|
|
currentChallengeId = challengeId;
|
|
|
|
const challengesGrid = document.querySelector('.challenges-grid');
|
|
const contentHeader = document.querySelector('.content-header');
|
|
|
|
if (challengesGrid) {
|
|
challengesGrid.style.display = 'none';
|
|
}
|
|
if (contentHeader) {
|
|
contentHeader.style.display = 'none';
|
|
}
|
|
|
|
let challengeDetailContainer = document.getElementById('challenge-detail-container');
|
|
if (!challengeDetailContainer) {
|
|
challengeDetailContainer = document.createElement('div');
|
|
challengeDetailContainer.id = 'challenge-detail-container';
|
|
challengeDetailContainer.className = 'challenge-detail-container';
|
|
document.querySelector('.main-content').appendChild(challengeDetailContainer);
|
|
}
|
|
|
|
challengeDetailContainer.innerHTML = `
|
|
<div class="loading-container">
|
|
<div class="loading-spinner"></div>
|
|
<p>Loading challenge details...</p>
|
|
</div>
|
|
`;
|
|
challengeDetailContainer.style.display = 'block';
|
|
|
|
fetch(`/api/challenge-detail?id=${challengeId}`)
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
challengeDetailContainer.innerHTML = html;
|
|
|
|
const loadNotesWithRetry = (attemptNumber = 1, maxAttempts = 10) => {
|
|
const notesTextarea = document.getElementById(`team-notes-${challengeId}`);
|
|
if (notesTextarea) {
|
|
loadTeamNotesForChallenge(challengeId);
|
|
} else if (attemptNumber < maxAttempts) {
|
|
setTimeout(() => {
|
|
loadNotesWithRetry(attemptNumber + 1, maxAttempts);
|
|
}, attemptNumber * 100);
|
|
}
|
|
};
|
|
|
|
loadNotesWithRetry();
|
|
|
|
setTimeout(() => {
|
|
const items = document.querySelectorAll('.checklist-item, .uploaded-file, .flag-entry');
|
|
items.forEach((item, index) => {
|
|
item.style.animationDelay = `${index * 0.05}s`;
|
|
item.classList.add('fade-in-up');
|
|
});
|
|
}, 100);
|
|
})
|
|
.catch(error => {
|
|
challengeDetailContainer.innerHTML = `
|
|
<div class="error-container">
|
|
<h2>Error Loading Challenge</h2>
|
|
<p>Failed to load challenge details. Please try again.</p>
|
|
<button class="btn btn-primary" onclick="backToDashboard()">Back to Dashboard</button>
|
|
</div>
|
|
`;
|
|
showNotification('Error loading challenge details', 'error');
|
|
});
|
|
}
|
|
|
|
function loadTeamNotesForChallenge(challengeId) {
|
|
const notesTextarea = document.getElementById(`team-notes-${challengeId}`);
|
|
|
|
if (!notesTextarea) {
|
|
return;
|
|
}
|
|
|
|
fetch(`/api/team-notes?challenge_id=${challengeId}`)
|
|
.then(response => response.text())
|
|
.then(notes => {
|
|
if (notes && !notes.includes('error') && notes.trim() !== '') {
|
|
notesTextarea.value = notes.trim();
|
|
showNotification('Team notes loaded', 'success');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
});
|
|
}
|
|
|
|
function backToDashboard() {
|
|
const challengeDetailContainer = document.getElementById('challenge-detail-container');
|
|
if (challengeDetailContainer) {
|
|
challengeDetailContainer.style.display = 'none';
|
|
}
|
|
|
|
const challengesGrid = document.querySelector('.challenges-grid');
|
|
const contentHeader = document.querySelector('.content-header');
|
|
|
|
if (challengesGrid) {
|
|
challengesGrid.style.display = 'grid';
|
|
setTimeout(() => {
|
|
const cards = document.querySelectorAll('.challenge-card');
|
|
cards.forEach((card, index) => {
|
|
card.style.animationDelay = `${index * 0.05}s`;
|
|
card.classList.add('slide-in-right');
|
|
});
|
|
}, 100);
|
|
}
|
|
if (contentHeader) {
|
|
contentHeader.style.display = 'flex';
|
|
}
|
|
|
|
currentChallengeId = null;
|
|
|
|
showNotification('Returned to dashboard', 'info');
|
|
}
|
|
|
|
function initializeEnhancedUI() {
|
|
const challengeCards = document.querySelectorAll('.challenge-card');
|
|
challengeCards.forEach(card => {
|
|
card.addEventListener('mouseenter', function() {
|
|
this.style.transform = 'translateY(-5px) scale(1.02)';
|
|
});
|
|
|
|
card.addEventListener('mouseleave', function() {
|
|
this.style.transform = 'translateY(0) scale(1)';
|
|
});
|
|
});
|
|
|
|
const searchInput = document.querySelector('.search-input');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('focus', function() {
|
|
this.style.boxShadow = '0 0 30px rgba(255, 107, 53, 0.4), inset 0 0 20px rgba(255, 107, 53, 0.1)';
|
|
});
|
|
|
|
searchInput.addEventListener('blur', function() {
|
|
this.style.boxShadow = '';
|
|
});
|
|
}
|
|
|
|
const buttons = document.querySelectorAll('.btn');
|
|
buttons.forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
this.style.transform = 'scale(0.95)';
|
|
setTimeout(() => {
|
|
this.style.transform = '';
|
|
}, 150);
|
|
});
|
|
});
|
|
|
|
initializeTooltips();
|
|
}
|
|
|
|
function initializeTooltips() {
|
|
const tooltipElements = document.querySelectorAll('[title]');
|
|
tooltipElements.forEach(element => {
|
|
const title = element.getAttribute('title');
|
|
if (title) {
|
|
element.removeAttribute('title');
|
|
element.classList.add('tooltip');
|
|
|
|
const tooltipText = document.createElement('span');
|
|
tooltipText.className = 'tooltiptext';
|
|
tooltipText.textContent = title;
|
|
element.appendChild(tooltipText);
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateProgress(challengeId) {
|
|
const checklistItems = document.querySelectorAll(`[data-challenge-id="${challengeId}"] .checklist-item`);
|
|
const completedItems = document.querySelectorAll(`[data-challenge-id="${challengeId}"] .checklist-item.status-completed`);
|
|
|
|
if (checklistItems.length > 0) {
|
|
const progress = (completedItems.length / checklistItems.length) * 100;
|
|
const progressBar = document.querySelector(`[data-challenge-id="${challengeId}"] .progress-fill`);
|
|
if (progressBar) {
|
|
progressBar.style.width = `${progress}%`;
|
|
}
|
|
}
|
|
}
|
|
|
|
function enhanceSearch() {
|
|
const searchInput = document.querySelector('.search-input');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', function() {
|
|
const query = this.value.toLowerCase();
|
|
const challengeCards = document.querySelectorAll('.challenge-card');
|
|
|
|
challengeCards.forEach(card => {
|
|
const title = card.querySelector('.challenge-name').textContent.toLowerCase();
|
|
const category = card.querySelector('.challenge-category').textContent.toLowerCase();
|
|
const tags = Array.from(card.querySelectorAll('.tag')).map(tag => tag.textContent.toLowerCase());
|
|
|
|
const matches = title.includes(query) ||
|
|
category.includes(query) ||
|
|
tags.some(tag => tag.includes(query));
|
|
|
|
if (matches || query === '') {
|
|
card.style.display = 'block';
|
|
card.style.animation = 'fadeInUp 0.5s ease';
|
|
} else {
|
|
card.style.display = 'none';
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
function initializeKeyboardShortcuts() {
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
if (currentChallengeId) {
|
|
backToDashboard();
|
|
}
|
|
}
|
|
|
|
if (e.ctrlKey && e.key === 'f') {
|
|
e.preventDefault();
|
|
if (!currentChallengeId) {
|
|
const searchInput = document.querySelector('.search-input');
|
|
if (searchInput) {
|
|
searchInput.focus();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeEnhancedUI();
|
|
enhanceSearch();
|
|
initializeKeyboardShortcuts();
|
|
|
|
setTimeout(() => {
|
|
const elements = document.querySelectorAll('.challenge-card, .team-member, .nav-link');
|
|
elements.forEach((element, index) => {
|
|
element.style.animationDelay = `${index * 0.1}s`;
|
|
element.classList.add('slide-in-right');
|
|
});
|
|
}, 500);
|
|
|
|
showNotification('Welcome to PwnHub! CTF collaboration platform loaded.', 'success');
|
|
});
|
|
|
|
window.loadChecklistTemplate = loadChecklistTemplate;
|
|
window.addCustomChecklistItem = addCustomChecklistItem;
|
|
window.saveCustomChecklistItem = saveCustomChecklistItem;
|
|
window.submitChecklistItem = submitChecklistItem;
|
|
window.cancelAddItem = cancelAddItem;
|
|
window.updateChecklistStatus = updateChecklistStatus;
|
|
window.uploadFiles = uploadFiles;
|
|
window.downloadFile = downloadFile;
|
|
window.viewFile = viewFile;
|
|
window.loadChallengeDetail = loadChallengeDetail;
|
|
window.backToDashboard = backToDashboard;
|
|
window.saveTeamNotes = saveTeamNotes;
|
|
window.submitFlag = submitFlag;
|
|
window.closeCodeViewer = closeCodeViewer;
|
|
|
|
function saveTeamNotes(challengeId) {
|
|
const notesTextarea = document.getElementById(`team-notes-${challengeId}`);
|
|
|
|
if (!notesTextarea) {
|
|
return;
|
|
}
|
|
|
|
const notes = notesTextarea.value.trim();
|
|
|
|
const params = new URLSearchParams();
|
|
params.append('challenge_id', challengeId);
|
|
params.append('notes', notes);
|
|
params.append('editor', 'Anonymous');
|
|
|
|
fetch('/api/team-notes', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
if (html.includes('success')) {
|
|
showNotification('Team notes saved successfully!', 'success');
|
|
} else {
|
|
showNotification('Error saving notes', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error saving notes', 'error');
|
|
});
|
|
}
|
|
|
|
function submitFlag(event, challengeId) {
|
|
event.preventDefault();
|
|
|
|
const form = event.target;
|
|
const formData = new FormData(form);
|
|
|
|
const params = new URLSearchParams();
|
|
for (let [key, value] of formData.entries()) {
|
|
params.append(key, value);
|
|
}
|
|
|
|
fetch('/api/submit-flag', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
const resultDiv = document.getElementById(`flag-result-${challengeId}`);
|
|
resultDiv.innerHTML = html;
|
|
|
|
if (html.includes('success')) {
|
|
showNotification('Flag submitted successfully!', 'success');
|
|
form.reset();
|
|
setTimeout(() => {
|
|
refreshChallengeDetail(challengeId);
|
|
}, 1000);
|
|
} else {
|
|
showNotification('Error submitting flag', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error submitting flag', 'error');
|
|
});
|
|
}
|
|
|
|
function openAddChallengeModal() {
|
|
const modal = document.getElementById('add-challenge-modal');
|
|
if (modal) {
|
|
modal.classList.add('open');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
}
|
|
|
|
function closeAddChallengeModal() {
|
|
const modal = document.getElementById('add-challenge-modal');
|
|
if (modal) {
|
|
modal.classList.remove('open');
|
|
document.body.style.overflow = 'auto';
|
|
const form = modal.querySelector('form');
|
|
if (form) form.reset();
|
|
const response = document.getElementById('challenge-response');
|
|
if (response) response.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
window.openAddChallengeModal = openAddChallengeModal;
|
|
window.closeAddChallengeModal = closeAddChallengeModal;
|
|
|
|
function openSettingsModal() {
|
|
const modal = document.getElementById('settings-modal');
|
|
if (modal) {
|
|
modal.classList.add('open');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
}
|
|
|
|
function closeSettingsModal() {
|
|
const modal = document.getElementById('settings-modal');
|
|
if (modal) {
|
|
modal.classList.remove('open');
|
|
document.body.style.overflow = 'auto';
|
|
const form = modal.querySelector('form');
|
|
if (form) form.reset();
|
|
const response = document.getElementById('settings-response');
|
|
if (response) response.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
window.openSettingsModal = openSettingsModal;
|
|
window.closeSettingsModal = closeSettingsModal;
|
|
|
|
document.addEventListener('click', function(event) {
|
|
const challengeModal = document.getElementById('add-challenge-modal');
|
|
const settingsModal = document.getElementById('settings-modal');
|
|
|
|
if (event.target === challengeModal) {
|
|
closeAddChallengeModal();
|
|
}
|
|
|
|
if (event.target === settingsModal) {
|
|
closeSettingsModal();
|
|
}
|
|
});
|
|
|
|
function deleteChallenge(challengeId, event) {
|
|
event.stopPropagation();
|
|
|
|
if (confirm('Are you sure you want to delete this challenge? This action cannot be undone.')) {
|
|
const params = new URLSearchParams();
|
|
params.append('challenge_id', challengeId);
|
|
|
|
fetch('/api/delete-challenge', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
if (html.includes('success')) {
|
|
showNotification('Challenge deleted successfully!', 'success');
|
|
htmx.ajax('GET', '/api/content?tab=challenges', {target: '#main-content'});
|
|
} else {
|
|
showNotification('Error deleting challenge', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error deleting challenge', 'error');
|
|
});
|
|
}
|
|
}
|
|
|
|
function searchChallenges(query) {
|
|
const cards = document.querySelectorAll('.challenge-card');
|
|
const lowercaseQuery = query.toLowerCase();
|
|
|
|
cards.forEach(card => {
|
|
const name = card.querySelector('.challenge-name').textContent.toLowerCase();
|
|
const category = card.querySelector('.challenge-category span:last-child').textContent.toLowerCase();
|
|
const tags = Array.from(card.querySelectorAll('.tag')).map(tag => tag.textContent.toLowerCase()).join(' ');
|
|
|
|
const matches = name.includes(lowercaseQuery) ||
|
|
category.includes(lowercaseQuery) ||
|
|
tags.includes(lowercaseQuery);
|
|
|
|
if (matches || query === '') {
|
|
card.style.display = 'block';
|
|
card.style.animation = 'fadeInUp 0.3s ease';
|
|
} else {
|
|
card.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
window.deleteChallenge = deleteChallenge;
|
|
window.searchChallenges = searchChallenges;
|