diff --git a/JavaScript/script.js b/JavaScript/script.js new file mode 100644 index 0000000..f95ba11 --- /dev/null +++ b/JavaScript/script.js @@ -0,0 +1,278 @@ +// public/script.js +document.addEventListener('DOMContentLoaded', () => { + // --- Password Strength Checker for Registration --- + const passwordInput = document.getElementById('password'); + const passwordStrengthDiv = document.getElementById('password-strength'); + const passwordCriteriaUl = passwordStrengthDiv ? passwordStrengthDiv.querySelector('ul') : null; + + + if (passwordInput && passwordStrengthDiv && passwordCriteriaUl) { + const criteria = [ + { regex: /.{8,}/, message: "At least 8 characters", id: "len" }, + { regex: /[A-Z]/, message: "An uppercase letter", id: "upper" }, + { regex: /[a-z]/, message: "A lowercase letter", id: "lower" }, + { regex: /[0-9]/, message: "A number", id: "num" }, + { regex: /[^A-Za-z0-9]/, message: "A special character", id: "special" } + ]; + + // Populate criteria list + criteria.forEach(c => { + const li = document.createElement('li'); + li.id = `crit-${c.id}`; + li.textContent = `✗ ${c.message}`; + li.classList.add('invalid'); + passwordCriteriaUl.appendChild(li); + }); + + passwordInput.addEventListener('input', () => { + const password = passwordInput.value; + let strengthScore = 0; + + criteria.forEach(c => { + const li = document.getElementById(`crit-${c.id}`); + if (c.regex.test(password)) { + li.textContent = `✓ ${c.message}`; + li.classList.remove('invalid'); + li.classList.add('valid'); + strengthScore++; + } else { + li.textContent = `✗ ${c.message}`; + li.classList.remove('valid'); + li.classList.add('invalid'); + } + }); + + const strengthBar = document.querySelector('.password-strength-meter div'); + if (strengthBar) { + strengthBar.style.width = (strengthScore / criteria.length * 100) + '%'; + if (strengthScore < 2) strengthBar.style.backgroundColor = 'red'; + else if (strengthScore < 4) strengthBar.style.backgroundColor = 'orange'; + else strengthBar.style.backgroundColor = 'green'; + } + + if (password.length === 0) { + if (strengthBar) strengthBar.style.width = '0%'; + } + }); + // Trigger initial check if there's a value (e.g. browser autofill) + if(passwordInput.value) passwordInput.dispatchEvent(new Event('input')); + } + + // --- AJAX Form Submissions --- + handleAjaxForm('login-form'); + handleAjaxForm('register-form'); + handleAjaxForm('note-form'); + handleAjaxForm('logout-form'); // Added for logout + + // --- Delete Note Confirmation and AJAX --- + document.querySelectorAll('.delete-note-btn').forEach(button => { + button.addEventListener('click', function(e) { + e.preventDefault(); + if (confirm('Are you sure you want to delete this note?')) { + const noteId = this.dataset.noteId; + + // Create a temporary form to submit for delete + const tempForm = document.createElement('form'); + tempForm.method = 'POST'; + tempForm.style.display = 'none'; + + const actionInput = document.createElement('input'); + actionInput.type = 'hidden'; + actionInput.name = 'action'; + actionInput.value = 'delete_note'; + tempForm.appendChild(actionInput); + + const noteIdInput = document.createElement('input'); + noteIdInput.type = 'hidden'; + noteIdInput.name = 'note_id'; + noteIdInput.value = noteId; + tempForm.appendChild(noteIdInput); + + document.body.appendChild(tempForm); // Form must be in DOM to submit + + // Use handleAjaxForm for consistency + const formData = new FormData(tempForm); + fetch('index.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + displayMessage(data.message, data.success ? 'success' : 'danger'); + if (data.success) { + // Refresh page or remove row dynamically for better UX + // For simplicity, reload. A more advanced way is to find and remove table row. + setTimeout(() => { window.location.reload(); }, 1000); + } + }) + .catch(error => { + console.error('Error:', error); + displayMessage('An error occurred during deletion.', 'danger'); + }) + .finally(() => { + document.body.removeChild(tempForm); // Clean up + }); + } + }); + }); + + // --- Drag and Drop File Upload --- + // ... (no changes from previous, ensure it's present) + const dropZone = document.getElementById('drop-zone'); + const noteTitleInput = document.getElementById('title'); + const noteContentInput = document.getElementById('content'); + const markdownPreview = document.getElementById('markdown-preview'); + + if (dropZone && noteTitleInput && noteContentInput) { + dropZone.addEventListener('dragover', (event) => { + event.preventDefault(); + dropZone.classList.add('drag-over'); + }); + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('drag-over'); + }); + dropZone.addEventListener('drop', (event) => { + event.preventDefault(); + dropZone.classList.remove('drag-over'); + const files = event.dataTransfer.files; + if (files.length > 0) { + const file = files[0]; + if (file.type === 'text/plain' || file.type === 'text/markdown' || file.name.endsWith('.md') || file.name.endsWith('.txt')) { + const reader = new FileReader(); + reader.onload = (e) => { + noteTitleInput.value = file.name.replace(/\.(md|txt)$/i, ''); + noteContentInput.value = e.target.result; + if (window.updateMarkdownPreview) updateMarkdownPreview(); + }; + reader.readAsText(file); + } else { + displayMessage('Please drop a .txt or .markdown file.', 'info'); + } + } + }); + } + + if (noteContentInput && markdownPreview && typeof showdown !== 'undefined') { + const converter = new showdown.Converter({sanitize: true}); + window.updateMarkdownPreview = function() { + markdownPreview.innerHTML = converter.makeHtml(noteContentInput.value); + } + noteContentInput.addEventListener('input', window.updateMarkdownPreview); + if(noteContentInput.value) window.updateMarkdownPreview(); + } else if (noteContentInput && markdownPreview) { + noteContentInput.addEventListener('input', () => { + markdownPreview.textContent = "Preview updates on save (or include Showdown.js library for live preview)."; + }); + if(noteContentInput.value) markdownPreview.textContent = "Preview updates on save (or include Showdown.js library for live preview)."; + } + + + // --- Table Sorting --- + document.querySelectorAll('.notes-table th[data-sort]').forEach(headerCell => { + headerCell.addEventListener('click', () => { + const sortBy = headerCell.dataset.sort; + const currentUrl = new URL(window.location.href); + const currentSortBy = currentUrl.searchParams.get('sort_by'); + const currentSortOrder = currentUrl.searchParams.get('sort_order') || 'asc'; // Default to asc if not set + + let newSortOrder = 'asc'; + if (sortBy === currentSortBy && currentSortOrder.toLowerCase() === 'asc') { + newSortOrder = 'desc'; + } + // If different column, or current is desc, start with asc for the new column + + currentUrl.searchParams.set('sort_by', sortBy); + currentUrl.searchParams.set('sort_order', newSortOrder); + window.location.href = currentUrl.toString(); + }); + }); +}); // End DOMContentLoaded + +function handleAjaxForm(formId) { // Removed successRedirectPage, rely on JSON + const form = document.getElementById(formId); + if (form) { + form.addEventListener('submit', function(e) { + e.preventDefault(); + const formData = new FormData(form); + const action = formData.get('action'); // Get action from FormData + + if (action === 'register') { + const password = formData.get('password'); + const confirmPassword = formData.get('confirm_password'); + if (password !== confirmPassword) { + displayMessage('Passwords do not match.', 'danger'); + return; + } + + let criteriaMet = 0; + const criteriaElements = document.querySelectorAll('#password-strength ul li'); + criteriaElements.forEach(li => { + if (li.classList.contains('valid')) { + criteriaMet++; + } + }); + + if (criteriaMet < criteriaElements.length) { // Check if all criteria are met + displayMessage('Password does not meet all requirements.', 'danger'); + return; + } + } + + fetch('index.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.message) { // Display message if provided + displayMessage(data.message, data.success ? 'success' : 'danger'); + } + if (data.success && data.redirect) { + setTimeout(() => { window.location.href = data.redirect; }, data.message ? 1000 : 0); // Delay if message shown + } else if (data.success && (action === 'create_note' || action === 'update_note')) { + // Specific redirect for note actions if not overridden by data.redirect + setTimeout(() => { window.location.href = 'index.php?page=dashboard'; }, data.message ? 1000 : 0); + } + }) + .catch(error => { + console.error('Error:', error); + displayMessage('A network error occurred. Please try again.', 'danger'); + }); + }); + } +} + +function displayMessage(message, type = 'info') { + const existingAlert = document.querySelector('#alert-container .alert'); + if (existingAlert) { + existingAlert.remove(); + } + + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${type}`; + alertDiv.innerHTML = message; // Use innerHTML if message contains HTML (e.g. list from server) + + let alertContainer = document.getElementById('alert-container'); + if (!alertContainer) { + alertContainer = document.createElement('div'); + alertContainer.id = 'alert-container'; + alertContainer.style.position = 'fixed'; + alertContainer.style.top = '70px'; + alertContainer.style.left = '50%'; + alertContainer.style.transform = 'translateX(-50%)'; + alertContainer.style.zIndex = '1000'; + alertContainer.style.width = 'auto'; // Fit content + alertContainer.style.minWidth = '300px'; + alertContainer.style.maxWidth = '90%'; + document.body.prepend(alertContainer); + } + alertContainer.appendChild(alertDiv); + + setTimeout(() => { + alertDiv.style.opacity = '0'; + alertDiv.style.transition = 'opacity 0.5s ease-out'; + setTimeout(() => { + if (alertDiv.parentNode) alertDiv.remove(); + }, 500); + }, 5000); +} \ No newline at end of file diff --git a/Model/NotesModel.php b/Model/NotesModel.php index 4e7e9f1..e6a1d65 100644 --- a/Model/NotesModel.php +++ b/Model/NotesModel.php @@ -32,10 +32,6 @@ class NotesModel extends Database $erg=$stmt->fetchAll(\PDO::FETCH_ASSOC); - foreach($erg as $key=>$row) { - $erg[$key]['id']+=0; - } - return $erg; } } \ No newline at end of file diff --git a/Views/Notes/showNotes.phtml b/Views/Notes/showNotes.phtml index 5bf9ce5..1cfa9d5 100644 --- a/Views/Notes/showNotes.phtml +++ b/Views/Notes/showNotes.phtml @@ -1,25 +1,5 @@ - - - + +