View file admin/editor.php

File size: 6.96Kb
<?php
/**
 * CMS: LaiCMS (v1.0 Edition 2026)
 * File: admin/editor.php
 * Оптимизация: Ultra-Responsive Cloud IDE
 */

require_once '../system/db.php';
require_once '../system/functions.php';

if (!isAdmin()) { die("403 Forbidden"); }

$base_dir = dirname(__DIR__);
$allowed_files = [
    'style.css'  => $base_dir . '/theme/style.css',
    'app.js'     => $base_dir . '/theme/app.js',
    'footer.php' => $base_dir . '/system/footer.php',
    'header.php' => $base_dir . '/system/header.php'
];

$file_key = $_GET['file'] ?? 'style.css';
if (!array_key_exists($file_key, $allowed_files)) { $file_key = 'style.css'; }
$path = $allowed_files[$file_key];
$content = (file_exists($path) && is_readable($path)) ? file_get_contents($path) : "Error: File inaccessible.";

$page_title = "IDE - $file_key";
include '../system/header.php';
?>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/monokai.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/php/php.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/css/css.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/javascript/javascript.min.js"></script>

<style>
    .ide-container { display: flex; flex-direction: column; height: calc(100vh - 120px); gap: 10px; padding: 5px; }
    
    /* Файловый менеджер (Desktop) */
    .ide-sidebar { width: 220px; background: var(--pico-card-background-color); border-radius: 12px; padding: 10px; border: 1px solid var(--pico-muted-border-color); }
    .file-link { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 8px; font-size: 0.75rem; transition: 0.2s; }
    .file-link.active { background: var(--pico-primary-background); color: white; }

    /* Редактор */
    .ide-main { flex: 1; position: relative; border-radius: 12px; overflow: hidden; border: 1px solid var(--pico-muted-border-color); display: flex; flex-direction: column; }
    .CodeMirror { flex: 1; font-family: 'Fira Code', monospace; font-size: 13px; }
    
    /* Мобильный селектор */
    .mobile-file-nav { display: none; margin-bottom: 10px; }

    @media (max-width: 992px) {
        .ide-sidebar { display: none; }
        .mobile-file-nav { display: block; }
        .ide-container { height: auto; }
        .CodeMirror { height: 65vh !important; }
        .hide-mobile { display: none; }
    }

    .toolbar { display: flex; justify-content: space-between; align-items: center; padding-bottom: 10px; }
    .status-bar { background: #1a1a1a; color: #777; font-size: 10px; padding: 4px 12px; display: flex; justify-content: space-between; }
</style>

<div class="ide-container">
    <div class="toolbar">
        <hgroup style="margin:0;">
            <h4 style="margin:0;"><i class="fa-solid fa-code"></i> IDE 2026</h4>
            <code style="font-size:10px;"><?= $file_key ?></code>
        </hgroup>
        <button id="save-btn" class="primary btn-sm" style="width:auto; margin:0; padding: 5px 20px;">
            <i class="fa-solid fa-cloud-arrow-up"></i> <span class="hide-mobile">Сохранить</span>
        </button>
    </div>

    <select class="mobile-file-nav" onchange="location.href='?file='+this.value">
        <?php foreach($allowed_files as $name => $p): ?>
            <option value="<?= $name ?>" <?= ($file_key == $name) ? 'selected' : '' ?>><?= $name ?></option>
        <?php endforeach; ?>
    </select>

    <div style="display: flex; flex: 1; gap: 10px; overflow: hidden;">
        <aside class="ide-sidebar">
            <small style="opacity: 0.5; text-transform: uppercase; font-size: 9px; letter-spacing: 1px;">Project Files</small>
            <nav style="margin-top: 10px;">
                <?php foreach($allowed_files as $name => $p): 
                    $icon = strpos($name, '.css') ? 'fa-css3' : (strpos($name, '.js') ? 'fa-js' : 'fa-php');
                ?>
                <a href="?file=<?= $name ?>" class="file-link <?= ($file_key == $name) ? 'active' : 'secondary' ?>">
                    <i class="fa-brands <?= $icon ?>"></i> <?= $name ?>
                </a>
                <?php endforeach; ?>
            </nav>
        </aside>

        <main class="ide-main">
            <textarea id="code-editor"><?= htmlspecialchars($content) ?></textarea>
            <div class="status-bar">
                <span>Ln <span id="ln">1</span>, Col <span id="col">1</span></span>
                <span id="save-status">Ready</span>
                <span class="hide-mobile">UTF-8 / <?= strtoupper(pathinfo($file_key, PATHINFO_EXTENSION)) ?></span>
            </div>
        </main>
    </div>
</div>

<script>
    const modeMap = { 'css': 'css', 'js': 'javascript', 'php': 'application/x-httpd-php' };
    const ext = '<?= pathinfo($file_key, PATHINFO_EXTENSION) ?>';

    const editor = CodeMirror.fromTextArea(document.getElementById("code-editor"), {
        lineNumbers: true,
        theme: "monokai",
        mode: modeMap[ext] || 'application/x-httpd-php',
        indentUnit: 4,
        smartIndent: true,
        lineWrapping: true,
        autoCloseBrackets: true,
        matchBrackets: true
    });

    // Отслеживание позиции курсора
    editor.on("cursorActivity", () => {
        const pos = editor.getCursor();
        document.getElementById("ln").innerText = pos.line + 1;
        document.getElementById("col").innerText = pos.ch + 1;
    });

    // Функция сохранения
    async function saveCode() {
        const btn = document.getElementById('save-btn');
        const status = document.getElementById('save-status');
        btn.setAttribute('aria-busy', 'true');
        status.innerText = "Saving...";

        try {
            const response = await fetch('save_handler.php', {
                method: 'POST',
                body: JSON.stringify({
                    file: '<?= $file_key ?>',
                    content: editor.getValue()
                })
            });
            status.innerText = "✓ Saved";
            setTimeout(() => status.innerText = "Ready", 2000);
        } catch (e) {
            alert("Ошибка сети");
            status.innerText = "Error";
        } finally {
            btn.setAttribute('aria-busy', 'false');
        }
    }

    // Ctrl+S
    document.addEventListener('keydown', e => {
        if ((e.ctrlKey || e.metaKey) && e.key === 's') {
            e.preventDefault();
            saveCode();
        }
    });

    document.getElementById('save-btn').onclick = saveCode;

    // Фикс для мобильной клавиатуры
    window.visualViewport.addEventListener('resize', () => {
        editor.setSize(null, window.visualViewport.height - 200);
    });
</script>

<?php include '../system/footer.php'; ?>