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'; ?>