diff --git a/podman-compose.yml b/podman-compose.yml index 54bb857..7d602f4 100644 --- a/podman-compose.yml +++ b/podman-compose.yml @@ -4,8 +4,6 @@ services: elemes: build: . container_name: elemes - ports: - - "5000:5000" # Expose port 5000 to the host volumes: - ../content:/app/content - ./static:/app/static diff --git a/templates/lesson.html b/templates/lesson.html index 48ed1b3..d3efec0 100644 --- a/templates/lesson.html +++ b/templates/lesson.html @@ -299,26 +299,209 @@ // Update line numbers on other events that might change content codeEditor.addEventListener('keyup', updateLineNumbers); - codeEditor.addEventListener('paste', function() { - setTimeout(updateLineNumbers, 0); + // Proper ClipboardEvent API implementation + // Store initial code to restore when paste is detected + let originalCodeOnFocus = initialCode; + + // Store the code when editor gains focus + codeEditor.addEventListener('focus', function() { + originalCodeOnFocus = codeEditor.value; }); - // Prevent copy-paste in the code editor with multiple methods + // Prevent paste event using ClipboardEvent API codeEditor.addEventListener('paste', function(e) { + // Store the current value before paste + const currentValue = codeEditor.value; + + // Prevent the default paste behavior e.preventDefault(); e.stopPropagation(); - alert('Copy-paste is not allowed in the code editor. Please type your code manually.'); + + // Use a timeout to ensure the paste operation is fully processed + setTimeout(() => { + // Check if the content has changed (some paste operations might bypass preventDefault) + if (codeEditor.value !== currentValue) { + // Restore the original code before the paste attempt + codeEditor.value = currentValue; + updateLineNumbers(); + } + + // Show warning notification + showCopyPasteNotification('Paste detected and blocked. Copy-paste is not allowed in the code editor. Please type your code manually.'); + }, 10); // Small delay to allow paste to potentially occur + return false; }); - // Additional paste prevention using onpaste attribute + // Prevent copy event using ClipboardEvent API + codeEditor.addEventListener('copy', function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Access the selected text to provide feedback + const selectedText = window.getSelection ? window.getSelection().toString() : ''; + if (selectedText && selectedText.trim() !== '') { + showCopyPasteNotification('Copy detected. Copy is not allowed in the code editor. Please type your code manually.'); + } + }); + + // Prevent cut event using ClipboardEvent API + codeEditor.addEventListener('cut', function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Access the selected text to provide feedback + const selectedText = window.getSelection ? window.getSelection().toString() : ''; + if (selectedText && selectedText.trim() !== '') { + showCopyPasteNotification('Cut detected. Cut is not allowed in the code editor. Please type your code manually.'); + } + }); + + // Additional prevention using onpaste attribute codeEditor.onpaste = function(e) { e.preventDefault(); e.stopPropagation(); - alert('Copy-paste is not allowed in the code editor. Please type your code manually.'); + showCopyPasteNotification('Paste detected. Copy-paste is not allowed in the code editor. Please type your code manually.'); return false; }; + // Additional prevention using oncopy attribute + codeEditor.oncopy = function(e) { + e.preventDefault(); + e.stopPropagation(); + showCopyPasteNotification('Copy detected. Copy is not allowed in the code editor. Please type your code manually.'); + }; + + // Additional prevention using oncut attribute + codeEditor.oncut = function(e) { + e.preventDefault(); + e.stopPropagation(); + showCopyPasteNotification('Cut detected. Cut is not allowed in the code editor. Please type your code manually.'); + }; + + // Allow context menu (right-click) but prevent paste actions + // We'll handle paste operations separately in the paste event + + // Device detection function + function isMobileDevice() { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); + } + + // Function to show a temporary notification about copy-paste detection + function showCopyPasteNotification(message) { + // Create or update notification element + let notificationEl = document.getElementById('copy-paste-notification'); + if (!notificationEl) { + notificationEl = document.createElement('div'); + notificationEl.id = 'copy-paste-notification'; + notificationEl.style.position = 'fixed'; + notificationEl.style.top = '20px'; + notificationEl.style.right = '20px'; + notificationEl.style.padding = '10px 15px'; + notificationEl.style.backgroundColor = '#dc3545'; + notificationEl.style.color = 'white'; + notificationEl.style.borderRadius = '4px'; + notificationEl.style.zIndex = '10000'; + notificationEl.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)'; + document.body.appendChild(notificationEl); + } + + notificationEl.textContent = message; + notificationEl.style.display = 'block'; + + // Hide notification after 3 seconds + setTimeout(() => { + notificationEl.style.display = 'none'; + }, 3000); + } + + // Enhanced mobile clipboard detection algorithm + // Reference: Based on techniques to detect rapid input to distinguish typing from paste + // Technique: Track timestamps of input events to detect rapid pasting (especially from GBoard) + let lastInputTime = Date.now(); + const inputTimeThreshold = 100; // milliseconds - inputs faster than this threshold suggest paste + let rapidInputs = 0; + const rapidInputThreshold = 3; // number of rapid inputs to trigger detection + + // Track previous value to detect large changes that might indicate pasting + let previousValue = codeEditor.value; + + // Listen for input events to detect rapid typing that might indicate pasting + let lastValidValue = initialCode; // Track the last valid (non-pasted) value + codeEditor.addEventListener('input', function(e) { + const currentTime = Date.now(); + const timeDiff = currentTime - lastInputTime; + + // Calculate how much text was added + const currentValue = codeEditor.value; + const addedText = currentValue.length - previousValue.length; + + // On mobile devices, check for both rapid inputs and large text additions + if (isMobileDevice()) { + // Check for rapid inputs - technique to detect paste events via typing speed + if (timeDiff < inputTimeThreshold) { + rapidInputs++; + + if (rapidInputs >= rapidInputThreshold) { + // Likely a paste or snippet insertion on mobile (reference: detect rapid input to distinguish typing from paste) + // Restore to the last known valid value + codeEditor.value = lastValidValue; + updateLineNumbers(); + showCopyPasteNotification('Rapid input detected and removed. Please type your code manually.'); + rapidInputs = 0; // Reset counter + } + } else { + // Reset counter if normal typing pace resumes + rapidInputs = 0; + } + + // Check for large text additions (another sign of paste) + if (addedText > 10) { // If more than 10 characters were added at once + // Restore to the last known valid value + codeEditor.value = lastValidValue; + updateLineNumbers(); + showCopyPasteNotification('Large text addition detected and removed. Please type your code manually.'); + } + } + + // Update tracking variables + lastInputTime = currentTime; + previousValue = currentValue; + + // Update the last valid value (but only if we're not in a rapid input situation) + if (isMobileDevice() && rapidInputs === 0 && addedText <= 10) { + // Consider it a valid manual input if it's not rapid or large + lastValidValue = currentValue; + } else if (!isMobileDevice()) { + // For non-mobile, always update the valid value + lastValidValue = currentValue; + } + }); + + // Additional detection for composition events (used by many mobile keyboards) + // Reference: Some mobile keyboards use composition events when inserting text from suggestions/clipboard + codeEditor.addEventListener('compositionstart', function(e) { + if (isMobileDevice()) { + // Store the current value before composition starts + const beforeCompositionValue = codeEditor.value; + + // Some keyboards use composition events when inserting text from suggestions/clipboard + setTimeout(() => { + const currentValue = codeEditor.value; + const addedText = currentValue.length - beforeCompositionValue.length; + + if (addedText > 5) { // If more than 5 characters were added via composition + // Restore to the last known valid value + codeEditor.value = lastValidValue; + updateLineNumbers(); + showCopyPasteNotification('Text composition detected and removed. Please type your code manually.'); + } + + previousValue = codeEditor.value; // Update with current value after potential restoration + }, 10); // Small delay to allow composition to complete + } + }); + codeEditor.addEventListener('copy', function(e) { e.preventDefault(); e.stopPropagation();