🐘
editor.php
Back
📝 Php ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
<?php // Editor for viewing/editing files with ACE Editor, Image Preview, and Play Button // Security: Define the root directory to prevent directory traversal attacks $rootDir = __DIR__; $currentDir = isset($_GET['dir']) ? $_GET['dir'] : ''; // Sanitize the directory path $currentDir = str_replace(['../', '..\\'], '', $currentDir); $fullPath = realpath($rootDir . '/' . $currentDir); // Security check: Ensure we're still within the root directory if (!$fullPath || strpos($fullPath, realpath($rootDir)) !== 0) { header('Location: explorer.php'); exit; } $viewFile = ''; $fileContent = ''; $isEditable = false; $isExecutable = false; $isImage = false; $message = ''; $messageType = ''; if (isset($_GET['view'])) { $viewFile = basename($_GET['view']); $viewFilePath = $fullPath . '/' . $viewFile; if (file_exists($viewFilePath) && is_file($viewFilePath)) { $fileExt = strtolower(pathinfo($viewFile, PATHINFO_EXTENSION)); $editableExts = ['txt', 'php', 'html', 'css', 'js', 'json', 'xml', 'md', 'py', 'java', 'cpp', 'c', 'h', 'sql', 'yml', 'yaml']; $executableExts = ['php', 'html', 'htm', 'js']; $imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']; if (in_array($fileExt, $imageExts)) { $isImage = true; } elseif (in_array($fileExt, $editableExts)) { $isEditable = true; $isExecutable = in_array($fileExt, $executableExts); $fileContent = file_get_contents($viewFilePath); } } else { header('Location: explorer.php?dir=' . urlencode($currentDir)); exit; } } else { header('Location: explorer.php'); exit; } // Handle save submission if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_file') { if (isset($_POST['file_name']) && isset($_POST['file_content'])) { $fileName = basename($_POST['file_name']); $filePath = $fullPath . '/' . $fileName; if (file_put_contents($filePath, $_POST['file_content']) !== false) { setPermissions($filePath); $message = "File '$fileName' saved successfully with 777 permissions!"; $messageType = 'success'; $fileContent = $_POST['file_content']; // Update displayed content } else { $message = "Failed to save file '$fileName'."; $messageType = 'error'; } } } // Handle execute submission if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'execute_file') { if (isset($_POST['file_name']) && isset($_POST['file_content'])) { $fileName = basename($_POST['file_name']); $filePath = $fullPath . '/' . $fileName; // Save the current content first file_put_contents($filePath, $_POST['file_content']); setPermissions($filePath); // Redirect to console monitor with this file $consoleMonitorPath = 'console_monitor.php'; if (file_exists($consoleMonitorPath)) { header('Location: ' . $consoleMonitorPath . '?dir=' . urlencode($currentDir) . '&file=' . urlencode($fileName)); exit; } else { $message = "Console monitor not found. Please ensure console_monitor.php exists."; $messageType = 'error'; } } } // Helper function to ensure proper permissions function setPermissions($path) { if (file_exists($path)) { chmod($path, 0777); umask(0000); return true; } return false; } // Helper function to determine file type for ACE editor function getAceMode($filename) { $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $modes = [ 'php' => 'php', 'js' => 'javascript', 'html' => 'html', 'css' => 'css', 'json' => 'json', 'xml' => 'xml', 'md' => 'markdown', 'py' => 'python', 'java' => 'java', 'cpp' => 'c_cpp', 'c' => 'c_cpp', 'h' => 'c_cpp', 'sql' => 'sql', 'yml' => 'yaml', 'yaml' => 'yaml' ]; return isset($modes[$ext]) ? $modes[$ext] : 'text'; } // Get file extension for icon function getFileIcon($filename) { $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $icons = [ 'php' => '🐘', 'html' => '🌐', 'htm' => '🌐', 'js' => '📜', 'css' => '🎨', 'json' => '📄', 'xml' => '📋', 'md' => '📝', 'py' => '🐍', 'java' => '☕', 'cpp' => '⚙️', 'c' => '⚙️', 'sql' => '🗃️' ]; return $icons[$ext] ?? '📄'; } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Editor - <?= htmlspecialchars($viewFile) ?></title> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ext-language_tools.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #f5f5f5; color: #333; line-height: 1.6; overflow-x: hidden; } .file-viewer { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: white; z-index: 1000; display: flex; flex-direction: column; } .message { padding: 12px 15px; border-radius: 6px; margin: 10px 15px; font-size: 14px; } .message.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .message.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .viewer-header { padding: 12px 15px; border-bottom: 1px solid #ecf0f1; background: #2c3e50; color: white; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; min-height: 50px; } .header-left { display: flex; align-items: center; gap: 10px; flex: 1; } .file-icon { font-size: 20px; } .viewer-title { font-weight: 600; font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 10px; } .header-buttons { display: flex; gap: 8px; align-items: center; } .back-btn, .play-btn { background: none; border: none; color: white; font-size: 16px; cursor: pointer; padding: 8px 12px; border-radius: 4px; transition: background-color 0.2s; text-decoration: none; flex-shrink: 0; display: flex; align-items: center; gap: 5px; } .back-btn:hover { background-color: rgba(255, 255, 255, 0.1); } .play-btn { background-color: #27ae60; } .play-btn:hover { background-color: #219a52; } .play-btn:disabled { background-color: #95a5a6; cursor: not-allowed; } .play-btn.executing { background-color: #f39c12; } .viewer-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; } .image-preview { text-align: center; padding: 15px; overflow: auto; flex: 1; } .image-preview img { max-width: 100%; max-height: 100%; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .editor-toolbar { padding: 10px 15px; background: #f8f9fa; border-bottom: 1px solid #ddd; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; flex-shrink: 0; } .editor-toolbar form { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; width: 100%; } .toolbar-left { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; flex: 1; } .toolbar-info { display: flex; flex-wrap: wrap; gap: 15px; align-items: center; font-size: 13px; color: #666; margin-left: auto; } .toolbar-info span { white-space: nowrap; } .find-group { display: flex; align-items: center; gap: 0; } .find-mode { padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; font-size: 12px; background: #f8f9fa; color: #333; cursor: pointer; border-right: none; min-width: 70px; } .find-mode:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); z-index: 1; position: relative; } .find-input { padding: 6px 10px; border: 1px solid #ddd; border-radius: 0; font-size: 13px; min-width: 120px; flex-shrink: 0; border-right: none; } .find-input:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); z-index: 1; position: relative; } .find-btn { background: #f8f9fa; border: 1px solid #ddd; color: #666; font-size: 12px; line-height: 1; padding: 7px 8px; cursor: pointer; transition: all 0.2s; min-width: 28px; height: 32px; display: flex; align-items: center; justify-content: center; } .find-btn:hover { background: #e9ecef; color: #333; } .find-btn:active { background: #dee2e6; } .find-btn:disabled { background: #f8f9fa; color: #ccc; cursor: not-allowed; } .find-prev { border-right: none; } .find-next { border-radius: 0 4px 4px 0; } #editor { flex: 1; min-height: 0; border: none; border-radius: 0; } .btn { padding: 8px 16px; border: none; border-radius: 4px; font-size: 14px; cursor: pointer; transition: background-color 0.2s; text-decoration: none; display: inline-block; text-align: center; flex-shrink: 0; } .btn-success { background-color: #27ae60; color: white; } .btn-success:hover { background-color: #219a52; } .btn-success:disabled { background-color: #95a5a6; cursor: not-allowed; } .execution-status { position: fixed; top: 20px; right: 20px; background: #2c3e50; color: white; padding: 10px 15px; border-radius: 6px; font-size: 14px; z-index: 1001; transform: translateY(-100px); opacity: 0; transition: all 0.3s ease; } .execution-status.show { transform: translateY(0); opacity: 1; } .execution-status.success { background: #27ae60; } .execution-status.error { background: #e74c3c; } /* Mobile-specific styles */ @media (max-width: 768px) { .viewer-header { padding: 10px 12px; min-height: 45px; } .viewer-title { font-size: 14px; } .back-btn, .play-btn { font-size: 14px; padding: 6px 10px; } .header-buttons { gap: 6px; } .editor-toolbar { padding: 8px 12px; gap: 8px; } .toolbar-left { gap: 8px; } .btn { padding: 6px 12px; font-size: 13px; } .toolbar-info { gap: 10px; font-size: 12px; } .message { padding: 10px 12px; margin: 8px 12px; font-size: 13px; } .find-mode { font-size: 11px; padding: 5px 6px; min-width: 60px; } .find-input { font-size: 12px; min-width: 100px; padding: 5px 8px; } .find-btn { font-size: 11px; padding: 6px 7px; min-width: 26px; height: 30px; } .image-preview { padding: 10px; } .toolbar-info .keyboard-hint { display: none; } } @media (max-width: 480px) { .viewer-header { padding: 8px 10px; min-height: 40px; } .viewer-title { font-size: 13px; } .back-btn, .play-btn { font-size: 13px; padding: 5px 8px; } .editor-toolbar { padding: 6px 10px; } .btn { padding: 5px 10px; font-size: 12px; } .toolbar-info { font-size: 11px; } .message { padding: 8px 10px; margin: 6px 10px; font-size: 12px; } .find-mode { font-size: 10px; padding: 4px 5px; min-width: 55px; } .find-input { font-size: 11px; min-width: 80px; padding: 4px 6px; } .find-btn { font-size: 10px; padding: 5px 6px; min-width: 24px; height: 28px; } } .ace_editor { font-size: 14px !important; } .ace_search { background-color: rgba(255, 193, 7, 0.4) !important; border: 1px solid #ffc107 !important; } .ace_selected-word { background-color: rgba(255, 193, 7, 0.6) !important; border: 1px solid #ffc107 !important; } .ace_selected_function { background-color: rgba(52, 152, 219, 0.2) !important; border: 1px solid rgba(52, 152, 219, 0.4) !important; } .ace_marker-layer .ace_active-line { background: rgba(255, 255, 255, 0.08) !important; } .ace_marker-layer .ace_selection { background: rgba(74, 144, 226, 0.3) !important; } @media (max-width: 768px) { .ace_editor { font-size: 13px !important; } } @media (max-width: 480px) { .ace_editor { font-size: 12px !important; } } </style> </head> <body> <div class="file-viewer"> <div class="viewer-header"> <div class="header-left"> <span class="file-icon"><?= getFileIcon($viewFile) ?></span> <div class="viewer-title"><?= htmlspecialchars($viewFile) ?></div> </div> <div class="header-buttons"> <?php if ($isExecutable): ?> <button onclick="executeFile()" class="play-btn" id="playBtn"> <span>▶</span> <span>Run & Monitor</span> </button> <?php endif; ?> <a href="explorer.php?dir=<?= urlencode($currentDir) ?>" class="back-btn"> <span>←</span> <span>Back</span> </a> </div> </div> <div class="viewer-content"> <?php if ($message): ?> <div class="message <?= $messageType ?>"> <?= htmlspecialchars($message) ?> </div> <?php endif; ?> <?php if ($isImage): ?> <div class="image-preview"> <img src="data:image/<?= pathinfo($viewFile, PATHINFO_EXTENSION) ?>;base64,<?= base64_encode(file_get_contents($fullPath . '/' . $viewFile)) ?>" alt="<?= htmlspecialchars($viewFile) ?>"> </div> <?php elseif ($isEditable): ?> <div class="editor-toolbar"> <form method="post" id="saveFileForm"> <input type="hidden" name="action" value="save_file"> <input type="hidden" name="file_name" value="<?= htmlspecialchars($viewFile) ?>"> <div class="toolbar-left"> <button type="submit" class="btn btn-success"> 💾 Save File </button> <div class="find-group"> <select id="findMode" class="find-mode"> <option value="normal">Normal</option> <option value="function">Function</option> </select> <input type="text" id="findInput" placeholder="Find..." class="find-input"> <button type="button" class="find-btn find-prev" title="Previous (Shift+Enter)">▲</button> <button type="button" class="find-btn find-next" title="Next (Enter)">▼</button> </div> </div> <div class="toolbar-info"> <span>📝 <?= ucfirst(getAceMode($viewFile)) ?></span> <?php if ($isExecutable): ?> <span>⚡ Executable</span> <?php endif; ?> <span class="keyboard-hint">Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find</span> </div> </form> <form method="post" id="executeFileForm" style="display: none;"> <input type="hidden" name="action" value="execute_file"> <input type="hidden" name="file_name" value="<?= htmlspecialchars($viewFile) ?>"> <textarea name="file_content" id="execute_file_content"></textarea> </form> </div> <div id="editor"><?= htmlspecialchars($fileContent) ?></div> <textarea name="file_content" id="file_content" form="saveFileForm" style="display: none;"></textarea> <?php endif; ?> </div> </div> <div id="executionStatus" class="execution-status"> <span id="executionMessage"></span> </div> <script> document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { window.location.href = 'explorer.php?dir=<?= urlencode($currentDir) ?>'; } }); <?php if ($isEditable): ?> const editor = ace.edit("editor"); editor.setTheme("ace/theme/monokai"); editor.session.setMode("ace/mode/<?= getAceMode($viewFile) ?>"); editor.setOptions({ enableBasicAutocompletion: true, enableSnippets: true, enableLiveAutocompletion: true, fontSize: 14, showPrintMargin: false, wrap: true, useSoftTabs: true, tabSize: 4 }); function updateEditorContent() { const content = editor.getValue(); document.getElementById('file_content').value = content; document.getElementById('execute_file_content').value = content; } function showExecutionStatus(message, type = 'info') { const statusEl = document.getElementById('executionStatus'); const messageEl = document.getElementById('executionMessage'); messageEl.textContent = message; statusEl.className = `execution-status ${type} show`; setTimeout(() => { statusEl.classList.remove('show'); }, 3000); } function saveFile() { updateEditorContent(); const form = document.getElementById('saveFileForm'); const saveBtn = form.querySelector('.btn-success'); const originalText = saveBtn.innerHTML; saveBtn.innerHTML = '⏳ Saving...'; saveBtn.disabled = true; const formData = new FormData(form); fetch(window.location.href, { method: 'POST', body: formData }) .then(response => response.text()) .then(html => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const messageEl = doc.querySelector('.message'); const messageText = messageEl ? messageEl.textContent : 'File saved successfully!'; const messageType = messageEl ? messageEl.classList.contains('success') ? 'success' : 'error' : 'success'; showExecutionStatus(messageText, messageType); saveBtn.innerHTML = originalText; saveBtn.disabled = false; }) .catch(error => { console.error('Save error:', error); showExecutionStatus('Failed to save file!', 'error'); saveBtn.innerHTML = originalText; saveBtn.disabled = false; }); } function executeFile() { <?php if (!$isExecutable): ?> showExecutionStatus('This file type is not executable', 'error'); return; <?php endif; ?> updateEditorContent(); const playBtn = document.getElementById('playBtn'); const originalContent = playBtn.innerHTML; playBtn.innerHTML = '<span>⏳</span><span>Executing...</span>'; playBtn.classList.add('executing'); playBtn.disabled = true; showExecutionStatus('Saving and executing file...', 'info'); const form = document.getElementById('executeFileForm'); const formData = new FormData(form); fetch(window.location.href, { method: 'POST', body: formData }) .then(response => { if (response.redirected) { showExecutionStatus('Opening Console Monitor...', 'success'); setTimeout(() => { window.location.href = response.url; }, 1000); } else { return response.text(); } }) .then(html => { if (html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const messageEl = doc.querySelector('.message'); const messageText = messageEl ? messageEl.textContent : 'Execution completed'; const messageType = messageEl ? messageEl.classList.contains('success') ? 'success' : 'error' : 'info'; showExecutionStatus(messageText, messageType); } playBtn.innerHTML = originalContent; playBtn.classList.remove('executing'); playBtn.disabled = false; }) .catch(error => { console.error('Execution error:', error); showExecutionStatus('Failed to execute file!', 'error'); playBtn.innerHTML = originalContent; playBtn.classList.remove('executing'); playBtn.disabled = false; }); } const saveForm = document.getElementById('saveFileForm'); if (saveForm) { saveForm.addEventListener('submit', function(e) { e.preventDefault(); saveFile(); }); } // Keyboard shortcuts editor.commands.addCommand({ name: 'save', bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, exec: function(editor) { saveFile(); } }); <?php if ($isExecutable): ?> editor.commands.addCommand({ name: 'run', bindKey: {win: 'Ctrl-R', mac: 'Command-R'}, exec: function(editor) { executeFile(); } }); <?php endif; ?> const findInput = document.getElementById('findInput'); const findMode = document.getElementById('findMode'); const findPrevBtn = document.querySelector('.find-prev'); const findNextBtn = document.querySelector('.find-next'); let currentSearchTerm = ''; let currentMatches = []; let currentMatchIndex = -1; let searchTimeout = null; function updateFindButtons() { const hasSearch = currentSearchTerm.length > 0; findPrevBtn.disabled = !hasSearch; findNextBtn.disabled = !hasSearch; } function findFunctions(searchTerm) { const content = editor.getValue(); const lines = content.split('\n'); const matches = []; const functionPatterns = [ /^\s*(?:function\s+|const\s+|let\s+|var\s+)?(\w*\s*(?:=\s*)?(?:function\s*)?(?:\([^)]*\)\s*)?(?:=>\s*)?{)/, /^\s*(?:public\s+|private\s+|protected\s+|static\s+)*function\s+(\w+)\s*\([^)]*\)/, /^\s*def\s+(\w+)\s*\([^)]*\):/, /^\s*(?:public\s+|private\s+|protected\s+|static\s+)*(?:\w+\s+)*(\w+)\s*\([^)]*\)\s*{?/, /^\s*([.#]?[\w-]+(?:\s*[,\s]\s*[.#]?[\w-]+)*)\s*{/ ]; for (let i = 0; i < lines.length; i++) { const line = lines[i]; for (let pattern of functionPatterns) { const match = line.match(pattern); if (match && match[1] && match[1].toLowerCase().includes(searchTerm.toLowerCase())) { let braceCount = 0; let endLine = i; let foundOpenBrace = false; for (let j = i; j < lines.length; j++) { const currentLine = lines[j]; for (let char of currentLine) { if (char === '{') { braceCount++; foundOpenBrace = true; } else if (char === '}') { braceCount--; if (foundOpenBrace && braceCount === 0) { endLine = j; break; } } } if (foundOpenBrace && braceCount === 0) { break; } if (pattern.source.includes('def\\s+') && j > i) { const currentIndent = currentLine.match(/^\s*/)[0].length; const funcIndent = lines[i].match(/^\s*/)[0].length; if (currentLine.trim() && currentIndent <= funcIndent) { endLine = j - 1; break; } } } matches.push({ name: match[1], startLine: i, endLine: endLine, startColumn: 0, endColumn: lines[endLine] ? lines[endLine].length : 0 }); break; } } } return matches; } function clearAllHighlights() { editor.findAll(''); if (editor._functionMarkers) { editor._functionMarkers.forEach(id => editor.session.removeMarker(id)); editor._functionMarkers = []; } } function selectFunction(match) { clearAllHighlights(); const Range = ace.require('ace/range').Range; const range = new Range(match.startLine, match.startColumn, match.endLine, match.endColumn); const markerId = editor.session.addMarker(range, "ace_selected_function", "background"); editor._functionMarkers = [markerId]; editor.selection.setRange(range, false); editor.scrollToLine(match.startLine, true, true, function() {}); setTimeout(() => { editor.selection.setRange(range, false); }, 50); } function performSearch() { const searchTerm = findInput.value.trim(); const mode = findMode.value; currentSearchTerm = searchTerm; if (!searchTerm) { clearAllHighlights(); editor.selection.clearSelection(); currentMatches = []; currentMatchIndex = -1; updateFindButtons(); return; } if (mode === 'function') { currentMatches = findFunctions(searchTerm); if (currentMatches.length > 0) { currentMatchIndex = 0; selectFunction(currentMatches[0]); } else { clearAllHighlights(); editor.selection.clearSelection(); } } else { clearAllHighlights(); editor.find(searchTerm, { backwards: false, wrap: true, caseSensitive: false, wholeWord: false, regExp: false }); currentMatches = []; currentMatchIndex = -1; } updateFindButtons(); } function debouncedSearch() { if (searchTimeout) { clearTimeout(searchTimeout); } searchTimeout = setTimeout(performSearch, 300); } findInput.addEventListener('input', function() { if (findMode.value === 'function') { debouncedSearch(); } else { performSearch(); } }); findMode.addEventListener('change', performSearch); findInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); if (currentSearchTerm) { if (e.shiftKey) { findPrevious(); } else { findNext(); } } } else if (e.key === 'Escape') { this.value = ''; currentSearchTerm = ''; currentMatches = []; currentMatchIndex = -1; clearAllHighlights(); editor.selection.clearSelection(); if (searchTimeout) { clearTimeout(searchTimeout); searchTimeout = null; } editor.focus(); updateFindButtons(); } }); function findNext() { if (findMode.value === 'function' && currentMatches.length > 0) { currentMatchIndex = (currentMatchIndex + 1) % currentMatches.length; selectFunction(currentMatches[currentMatchIndex]); } else if (findMode.value === 'normal' && currentSearchTerm) { editor.findNext(); } } function findPrevious() { if (findMode.value === 'function' && currentMatches.length > 0) { currentMatchIndex = currentMatchIndex <= 0 ? currentMatches.length - 1 : currentMatchIndex - 1; selectFunction(currentMatches[currentMatchIndex]); } else if (findMode.value === 'normal' && currentSearchTerm) { editor.findPrevious(); } } findPrevBtn.addEventListener('click', findPrevious); findNextBtn.addEventListener('click', findNext); editor.commands.addCommand({ name: 'find', bindKey: {win: 'Ctrl-F', mac: 'Command-F'}, exec: function(editor) { findInput.focus(); findInput.select(); } }); updateFindButtons(); if (window.innerWidth <= 768) { editor.setOptions({ fontSize: 13, scrollPastEnd: 0.2 }); } if (window.innerWidth <= 480) { editor.setOptions({ fontSize: 12 }); } window.addEventListener('orientationchange', function() { setTimeout(function() { editor.resize(); }, 100); }); // Auto-save functionality (optional) let autoSaveTimeout; editor.session.on('change', function() { if (autoSaveTimeout) { clearTimeout(autoSaveTimeout); } // Auto-save after 3 seconds of inactivity autoSaveTimeout = setTimeout(() => { updateEditorContent(); // Uncomment the next line to enable auto-save // saveFile(); }, 3000); }); <?php endif; ?> // Global keyboard shortcuts document.addEventListener('keydown', function(e) { if (e.ctrlKey || e.metaKey) { switch (e.key) { <?php if ($isExecutable): ?> case 'r': case 'R': e.preventDefault(); executeFile(); break; <?php endif; ?> case 'Enter': if (e.shiftKey) { e.preventDefault(); executeFile(); } break; } } }); // Initialize <?php if ($isEditable): ?> // Focus editor on load setTimeout(() => { editor.focus(); }, 100); <?php endif; ?> </script> </body> </html>