- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование - Удалены дублирующие настройки navigation для чистой группировки - Добавлены CSS стили для визуального отображения иерархии с отступами - Добавлены эмодзи-иконки для каждого типа ресурсов через CSS - Улучшена навигация с правильной вложенностью элементов
1010 lines
36 KiB
HTML
1010 lines
36 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Профессиональный редактор стилей</title>
|
||
<style>
|
||
* {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background: #f8f9fa;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.editor-container {
|
||
height: 100vh;
|
||
display: flex;
|
||
}
|
||
|
||
/* Sidebar */
|
||
.sidebar {
|
||
width: 320px;
|
||
background: white;
|
||
border-right: 1px solid #e9ecef;
|
||
overflow-y: auto;
|
||
box-shadow: 2px 0 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.sidebar-header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 20px;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
|
||
.sidebar-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.sidebar-subtitle {
|
||
font-size: 14px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
/* Control Sections */
|
||
.control-section {
|
||
border-bottom: 1px solid #e9ecef;
|
||
}
|
||
|
||
.section-header {
|
||
background: #f8f9fa;
|
||
padding: 12px 20px;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
color: #495057;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: between;
|
||
border-bottom: 1px solid #e9ecef;
|
||
}
|
||
|
||
.section-header:hover {
|
||
background: #e9ecef;
|
||
}
|
||
|
||
.section-content {
|
||
padding: 20px;
|
||
}
|
||
|
||
.section-content.collapsed {
|
||
display: none;
|
||
}
|
||
|
||
.section-toggle {
|
||
font-size: 12px;
|
||
margin-left: auto;
|
||
}
|
||
|
||
/* Form Controls */
|
||
.control-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.control-label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
font-size: 14px;
|
||
color: #495057;
|
||
}
|
||
|
||
.control-input {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border: 1px solid #ced4da;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
transition: border-color 0.3s ease;
|
||
}
|
||
|
||
.control-input:focus {
|
||
outline: none;
|
||
border-color: #007bff;
|
||
box-shadow: 0 0 0 3px rgba(0,123,255,0.1);
|
||
}
|
||
|
||
/* Color Controls */
|
||
.color-control {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.color-preview {
|
||
width: 40px;
|
||
height: 32px;
|
||
border: 1px solid #ced4da;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.color-input {
|
||
position: absolute;
|
||
opacity: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.color-value {
|
||
flex: 1;
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
/* Slider Controls */
|
||
.slider-control {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.slider {
|
||
flex: 1;
|
||
height: 6px;
|
||
border-radius: 3px;
|
||
background: #e9ecef;
|
||
outline: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.slider::-webkit-slider-thumb {
|
||
appearance: none;
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 50%;
|
||
background: #007bff;
|
||
cursor: pointer;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||
}
|
||
|
||
.slider-value {
|
||
min-width: 50px;
|
||
text-align: right;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* Image Controls */
|
||
.image-control {
|
||
border: 2px dashed #ced4da;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
}
|
||
|
||
.image-control:hover {
|
||
border-color: #007bff;
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.image-control.has-image {
|
||
border-style: solid;
|
||
border-color: #28a745;
|
||
padding: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.image-preview {
|
||
max-width: 100%;
|
||
max-height: 120px;
|
||
object-fit: cover;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.image-placeholder {
|
||
color: #6c757d;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.image-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.btn-small {
|
||
padding: 4px 8px;
|
||
font-size: 12px;
|
||
border: 1px solid #ced4da;
|
||
background: white;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.btn-small:hover {
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.btn-small.primary {
|
||
background: #007bff;
|
||
color: white;
|
||
border-color: #007bff;
|
||
}
|
||
|
||
.btn-small.primary:hover {
|
||
background: #0056b3;
|
||
}
|
||
|
||
.btn-small.danger {
|
||
background: #dc3545;
|
||
color: white;
|
||
border-color: #dc3545;
|
||
}
|
||
|
||
.btn-small.danger:hover {
|
||
background: #c82333;
|
||
}
|
||
|
||
/* Action Buttons */
|
||
.actions-bar {
|
||
position: sticky;
|
||
bottom: 0;
|
||
background: white;
|
||
border-top: 1px solid #e9ecef;
|
||
padding: 15px 20px;
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
flex: 1;
|
||
}
|
||
|
||
.btn.primary {
|
||
background: #28a745;
|
||
color: white;
|
||
}
|
||
|
||
.btn.primary:hover {
|
||
background: #218838;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.btn.secondary {
|
||
background: #6c757d;
|
||
color: white;
|
||
}
|
||
|
||
.btn.secondary:hover {
|
||
background: #5a6268;
|
||
}
|
||
|
||
/* Preview Panel */
|
||
.preview-panel {
|
||
flex: 1;
|
||
background: white;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
.preview-header {
|
||
background: #f8f9fa;
|
||
padding: 15px 20px;
|
||
border-bottom: 1px solid #e9ecef;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: between;
|
||
}
|
||
|
||
.preview-title {
|
||
font-weight: 600;
|
||
color: #495057;
|
||
}
|
||
|
||
.preview-controls {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.preview-frame {
|
||
width: 100%;
|
||
height: calc(100vh - 80px);
|
||
border: none;
|
||
}
|
||
|
||
/* Media Manager Modal */
|
||
.media-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.8);
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.media-modal.visible {
|
||
display: flex;
|
||
}
|
||
|
||
.media-modal-content {
|
||
width: 90vw;
|
||
height: 90vh;
|
||
background: white;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.media-modal iframe {
|
||
width: 100%;
|
||
height: 100%;
|
||
border: none;
|
||
}
|
||
|
||
/* Status */
|
||
.status-message {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 10px 20px;
|
||
border-radius: 6px;
|
||
color: white;
|
||
font-weight: 500;
|
||
z-index: 2000;
|
||
transform: translateX(100%);
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.status-message.visible {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.status-message.success {
|
||
background: #28a745;
|
||
}
|
||
|
||
.status-message.error {
|
||
background: #dc3545;
|
||
}
|
||
|
||
/* Loading */
|
||
.loading-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(255,255,255,0.9);
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 100;
|
||
}
|
||
|
||
.loading-overlay.visible {
|
||
display: flex;
|
||
}
|
||
|
||
.spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 4px solid #f3f3f3;
|
||
border-top: 4px solid #007bff;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="editor-container">
|
||
<!-- Sidebar -->
|
||
<div class="sidebar">
|
||
<div class="sidebar-header">
|
||
<div class="sidebar-title">🎨 Редактор стилей</div>
|
||
<div class="sidebar-subtitle">Настройка дизайна сайта</div>
|
||
</div>
|
||
|
||
<!-- Colors Section -->
|
||
<div class="control-section">
|
||
<div class="section-header" onclick="toggleSection(this)">
|
||
🎨 Цвета
|
||
<span class="section-toggle">▼</span>
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="control-group">
|
||
<label class="control-label">Основной цвет</label>
|
||
<div class="color-control">
|
||
<div class="color-preview">
|
||
<input type="color" class="color-input" id="primaryColor" value="#007bff">
|
||
</div>
|
||
<input type="text" class="control-input color-value" id="primaryColorValue" value="#007bff">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="control-label">Вторичный цвет</label>
|
||
<div class="color-control">
|
||
<div class="color-preview">
|
||
<input type="color" class="color-input" id="secondaryColor" value="#6c757d">
|
||
</div>
|
||
<input type="text" class="control-input color-value" id="secondaryColorValue" value="#6c757d">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="control-label">Цвет фона</label>
|
||
<div class="color-control">
|
||
<div class="color-preview">
|
||
<input type="color" class="color-input" id="backgroundColor" value="#f8f9fa">
|
||
</div>
|
||
<input type="text" class="control-input color-value" id="backgroundColorValue" value="#f8f9fa">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="control-label">Цвет текста</label>
|
||
<div class="color-control">
|
||
<div class="color-preview">
|
||
<input type="color" class="color-input" id="textColor" value="#333333">
|
||
</div>
|
||
<input type="text" class="control-input color-value" id="textColorValue" value="#333333">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Typography Section -->
|
||
<div class="control-section">
|
||
<div class="section-header" onclick="toggleSection(this)">
|
||
📝 Типографика
|
||
<span class="section-toggle">▼</span>
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="control-group">
|
||
<label class="control-label">Основной шрифт</label>
|
||
<select class="control-input" id="primaryFont">
|
||
<option value="'Inter', sans-serif">Inter</option>
|
||
<option value="'Roboto', sans-serif">Roboto</option>
|
||
<option value="'Open Sans', sans-serif">Open Sans</option>
|
||
<option value="'Lato', sans-serif">Lato</option>
|
||
<option value="'Montserrat', sans-serif">Montserrat</option>
|
||
<option value="'Poppins', sans-serif">Poppins</option>
|
||
<option value="Arial, sans-serif">Arial</option>
|
||
<option value="Georgia, serif">Georgia</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="control-label">Размер шрифта</label>
|
||
<div class="slider-control">
|
||
<input type="range" class="slider" id="fontSize" min="12" max="24" value="16">
|
||
<span class="slider-value">16px</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="control-label">Межстрочный интервал</label>
|
||
<div class="slider-control">
|
||
<input type="range" class="slider" id="lineHeight" min="1.0" max="2.0" step="0.1" value="1.6">
|
||
<span class="slider-value">1.6</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Layout Section -->
|
||
<div class="control-section">
|
||
<div class="section-header" onclick="toggleSection(this)">
|
||
📐 Макет
|
||
<span class="section-toggle">▼</span>
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="control-group">
|
||
<label class="control-label">Максимальная ширина</label>
|
||
<div class="slider-control">
|
||
<input type="range" class="slider" id="maxWidth" min="960" max="1600" step="40" value="1200">
|
||
<span class="slider-value">1200px</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="control-label">Внутренние отступы</label>
|
||
<div class="slider-control">
|
||
<input type="range" class="slider" id="padding" min="10" max="50" step="5" value="20">
|
||
<span class="slider-value">20px</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="control-label">Радиус скругления</label>
|
||
<div class="slider-control">
|
||
<input type="range" class="slider" id="borderRadius" min="0" max="20" step="2" value="6">
|
||
<span class="slider-value">6px</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Background Images Section -->
|
||
<div class="control-section">
|
||
<div class="section-header" onclick="toggleSection(this)">
|
||
🖼️ Фоновые изображения
|
||
<span class="section-toggle">▼</span>
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="control-group">
|
||
<label class="control-label">Фон заголовка</label>
|
||
<div class="image-control" id="headerBgControl" onclick="openMediaManager('headerBg')">
|
||
<div class="image-placeholder">
|
||
📷 Выбрать изображение
|
||
</div>
|
||
</div>
|
||
<input type="hidden" id="headerBg">
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="control-label">Фон страницы</label>
|
||
<div class="image-control" id="pageBgControl" onclick="openMediaManager('pageBg')">
|
||
<div class="image-placeholder">
|
||
📷 Выбрать изображение
|
||
</div>
|
||
</div>
|
||
<input type="hidden" id="pageBg">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Actions Bar -->
|
||
<div class="actions-bar">
|
||
<button class="btn secondary" onclick="resetToDefaults()">Сброс</button>
|
||
<button class="btn primary" onclick="saveSettings()">💾 Сохранить</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Preview Panel -->
|
||
<div class="preview-panel">
|
||
<div class="preview-header">
|
||
<div class="preview-title">📱 Предварительный просмотр</div>
|
||
<div class="preview-controls">
|
||
<button class="btn-small" onclick="refreshPreview()">🔄 Обновить</button>
|
||
<button class="btn-small primary" onclick="openPreviewInNewTab()">🔗 Открыть в новой вкладке</button>
|
||
</div>
|
||
</div>
|
||
<iframe class="preview-frame" id="previewFrame" src="/"></iframe>
|
||
|
||
<div class="loading-overlay" id="loadingOverlay">
|
||
<div class="spinner"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Media Manager Modal -->
|
||
<div class="media-modal" id="mediaModal">
|
||
<div class="media-modal-content">
|
||
<iframe id="mediaManagerFrame" src="/universal-media-manager.html"></iframe>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Status Messages -->
|
||
<div class="status-message" id="statusMessage"></div>
|
||
|
||
<script>
|
||
let currentSettings = {};
|
||
let currentImageField = null;
|
||
|
||
// Инициализация
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initializeControls();
|
||
loadSettings();
|
||
setupPreviewUpdates();
|
||
});
|
||
|
||
// Инициализация контролов
|
||
function initializeControls() {
|
||
// Color controls
|
||
setupColorControl('primaryColor', 'primaryColorValue');
|
||
setupColorControl('secondaryColor', 'secondaryColorValue');
|
||
setupColorControl('backgroundColor', 'backgroundColorValue');
|
||
setupColorControl('textColor', 'textColorValue');
|
||
|
||
// Slider controls
|
||
setupSliderControl('fontSize', 'px');
|
||
setupSliderControl('lineHeight', '');
|
||
setupSliderControl('maxWidth', 'px');
|
||
setupSliderControl('padding', 'px');
|
||
setupSliderControl('borderRadius', 'px');
|
||
|
||
// Other controls
|
||
document.getElementById('primaryFont').addEventListener('change', updatePreview);
|
||
}
|
||
|
||
function setupColorControl(colorId, valueId) {
|
||
const colorInput = document.getElementById(colorId);
|
||
const valueInput = document.getElementById(valueId);
|
||
|
||
colorInput.addEventListener('input', function() {
|
||
valueInput.value = this.value;
|
||
currentSettings[colorId.replace('Color', '-color')] = this.value;
|
||
updatePreview();
|
||
});
|
||
|
||
valueInput.addEventListener('input', function() {
|
||
if (/^#[0-9A-F]{6}$/i.test(this.value)) {
|
||
colorInput.value = this.value;
|
||
currentSettings[colorId.replace('Color', '-color')] = this.value;
|
||
updatePreview();
|
||
}
|
||
});
|
||
}
|
||
|
||
function setupSliderControl(sliderId, unit) {
|
||
const slider = document.getElementById(sliderId);
|
||
const valueSpan = slider.parentElement.querySelector('.slider-value');
|
||
|
||
slider.addEventListener('input', function() {
|
||
valueSpan.textContent = this.value + unit;
|
||
currentSettings[sliderId.replace(/([A-Z])/g, '-$1').toLowerCase()] = this.value + unit;
|
||
updatePreview();
|
||
});
|
||
}
|
||
|
||
// Media Manager
|
||
function openMediaManager(fieldName) {
|
||
currentImageField = fieldName;
|
||
document.getElementById('mediaModal').classList.add('visible');
|
||
}
|
||
|
||
// Получение сообщений от медиа-менеджера
|
||
window.addEventListener('message', function(event) {
|
||
if (event.data.type === 'media-manager-selection' && event.data.files.length > 0) {
|
||
const file = event.data.files[0]; // Берем первый выбранный файл
|
||
setImageForField(currentImageField, file);
|
||
document.getElementById('mediaModal').classList.remove('visible');
|
||
}
|
||
});
|
||
|
||
function setImageForField(fieldName, file) {
|
||
const control = document.getElementById(fieldName + 'Control');
|
||
const input = document.getElementById(fieldName);
|
||
|
||
// Обновляем скрытый input
|
||
input.value = file.url;
|
||
|
||
// Обновляем визуал
|
||
control.classList.add('has-image');
|
||
control.innerHTML = `
|
||
<img class="image-preview" src="${file.url}" alt="${file.name}">
|
||
<div class="image-actions">
|
||
<button class="btn-small primary" onclick="openMediaManager('${fieldName}')">Заменить</button>
|
||
<button class="btn-small danger" onclick="removeImage('${fieldName}')">Удалить</button>
|
||
</div>
|
||
`;
|
||
|
||
// Обновляем настройки
|
||
currentSettings[fieldName.replace(/([A-Z])/g, '-$1').toLowerCase()] = file.url;
|
||
updatePreview();
|
||
}
|
||
|
||
function removeImage(fieldName) {
|
||
const control = document.getElementById(fieldName + 'Control');
|
||
const input = document.getElementById(fieldName);
|
||
|
||
input.value = '';
|
||
control.classList.remove('has-image');
|
||
control.innerHTML = '<div class="image-placeholder">📷 Выбрать изображение</div>';
|
||
|
||
delete currentSettings[fieldName.replace(/([A-Z])/g, '-$1').toLowerCase()];
|
||
updatePreview();
|
||
}
|
||
|
||
// Загрузка настроек
|
||
async function loadSettings() {
|
||
try {
|
||
showLoading(true);
|
||
const response = await fetch('/api/settings/styles');
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
currentSettings = data.styles;
|
||
applySettingsToControls(currentSettings);
|
||
updatePreview();
|
||
} else {
|
||
throw new Error(data.message || 'Ошибка загрузки настроек');
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки настроек:', error);
|
||
showStatus('Ошибка загрузки настроек', 'error');
|
||
} finally {
|
||
showLoading(false);
|
||
}
|
||
}
|
||
|
||
function applySettingsToControls(settings) {
|
||
// Colors
|
||
if (settings['primary-color']) {
|
||
document.getElementById('primaryColor').value = settings['primary-color'];
|
||
document.getElementById('primaryColorValue').value = settings['primary-color'];
|
||
}
|
||
|
||
if (settings['secondary-color']) {
|
||
document.getElementById('secondaryColor').value = settings['secondary-color'];
|
||
document.getElementById('secondaryColorValue').value = settings['secondary-color'];
|
||
}
|
||
|
||
if (settings['background-color']) {
|
||
document.getElementById('backgroundColor').value = settings['background-color'];
|
||
document.getElementById('backgroundColorValue').value = settings['background-color'];
|
||
}
|
||
|
||
if (settings['text-color']) {
|
||
document.getElementById('textColor').value = settings['text-color'];
|
||
document.getElementById('textColorValue').value = settings['text-color'];
|
||
}
|
||
|
||
// Typography
|
||
if (settings['primary-font']) {
|
||
document.getElementById('primaryFont').value = settings['primary-font'];
|
||
}
|
||
|
||
if (settings['base-font-size']) {
|
||
const size = parseInt(settings['base-font-size']);
|
||
document.getElementById('fontSize').value = size;
|
||
document.querySelector('#fontSize').parentElement.querySelector('.slider-value').textContent = size + 'px';
|
||
}
|
||
|
||
if (settings['line-height']) {
|
||
const height = parseFloat(settings['line-height']);
|
||
document.getElementById('lineHeight').value = height;
|
||
document.querySelector('#lineHeight').parentElement.querySelector('.slider-value').textContent = height;
|
||
}
|
||
|
||
// Layout
|
||
if (settings['max-width']) {
|
||
const width = parseInt(settings['max-width']);
|
||
document.getElementById('maxWidth').value = width;
|
||
document.querySelector('#maxWidth').parentElement.querySelector('.slider-value').textContent = width + 'px';
|
||
}
|
||
|
||
if (settings['padding']) {
|
||
const padding = parseInt(settings['padding']);
|
||
document.getElementById('padding').value = padding;
|
||
document.querySelector('#padding').parentElement.querySelector('.slider-value').textContent = padding + 'px';
|
||
}
|
||
|
||
if (settings['border-radius']) {
|
||
const radius = parseInt(settings['border-radius']);
|
||
document.getElementById('borderRadius').value = radius;
|
||
document.querySelector('#borderRadius').parentElement.querySelector('.slider-value').textContent = radius + 'px';
|
||
}
|
||
|
||
// Background images
|
||
if (settings['header-bg']) {
|
||
setImageForField('headerBg', { url: settings['header-bg'], name: 'Header Background' });
|
||
}
|
||
|
||
if (settings['page-bg']) {
|
||
setImageForField('pageBg', { url: settings['page-bg'], name: 'Page Background' });
|
||
}
|
||
}
|
||
|
||
// Обновление превью
|
||
function updatePreview() {
|
||
const css = generateCSS();
|
||
|
||
// Отправляем CSS в iframe (если возможно)
|
||
try {
|
||
const iframe = document.getElementById('previewFrame');
|
||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||
|
||
let styleEl = iframeDoc.getElementById('custom-preview-styles');
|
||
if (!styleEl) {
|
||
styleEl = iframeDoc.createElement('style');
|
||
styleEl.id = 'custom-preview-styles';
|
||
iframeDoc.head.appendChild(styleEl);
|
||
}
|
||
|
||
styleEl.textContent = css;
|
||
} catch (e) {
|
||
// Ошибка доступа к iframe (возможно, другой домен)
|
||
console.log('Не удается обновить превью напрямую');
|
||
}
|
||
}
|
||
|
||
function generateCSS() {
|
||
return `
|
||
:root {
|
||
--primary-color: ${currentSettings['primary-color'] || '#007bff'};
|
||
--secondary-color: ${currentSettings['secondary-color'] || '#6c757d'};
|
||
--background-color: ${currentSettings['background-color'] || '#f8f9fa'};
|
||
--text-color: ${currentSettings['text-color'] || '#333333'};
|
||
--primary-font: ${currentSettings['primary-font'] || "'Inter', sans-serif"};
|
||
--base-font-size: ${currentSettings['base-font-size'] || '16px'};
|
||
--line-height: ${currentSettings['line-height'] || '1.6'};
|
||
--max-width: ${currentSettings['max-width'] || '1200px'};
|
||
--padding: ${currentSettings['padding'] || '20px'};
|
||
--border-radius: ${currentSettings['border-radius'] || '6px'};
|
||
}
|
||
|
||
body {
|
||
font-family: var(--primary-font);
|
||
font-size: var(--base-font-size);
|
||
line-height: var(--line-height);
|
||
color: var(--text-color);
|
||
background-color: var(--background-color);
|
||
${currentSettings['page-bg'] ? `background-image: url('${currentSettings['page-bg']}');` : ''}
|
||
}
|
||
|
||
.container {
|
||
max-width: var(--max-width);
|
||
padding: var(--padding);
|
||
}
|
||
|
||
.header, .hero {
|
||
${currentSettings['header-bg'] ? `background-image: url('${currentSettings['header-bg']}');` : ''}
|
||
background-size: cover;
|
||
background-position: center;
|
||
}
|
||
|
||
.btn-primary, .button, .btn {
|
||
background-color: var(--primary-color);
|
||
border-radius: var(--border-radius);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background-color: var(--secondary-color);
|
||
border-radius: var(--border-radius);
|
||
}
|
||
|
||
.card, .section {
|
||
border-radius: var(--border-radius);
|
||
padding: var(--padding);
|
||
}
|
||
`;
|
||
}
|
||
|
||
// Сохранение настроек
|
||
async function saveSettings() {
|
||
try {
|
||
showLoading(true);
|
||
|
||
const css = generateCSS();
|
||
|
||
const response = await fetch('/api/settings/styles', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
styles: currentSettings,
|
||
css: css
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
showStatus('Настройки успешно сохранены!', 'success');
|
||
refreshPreview();
|
||
} else {
|
||
throw new Error(data.error || 'Ошибка сохранения');
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка сохранения:', error);
|
||
showStatus('Ошибка сохранения настроек', 'error');
|
||
} finally {
|
||
showLoading(false);
|
||
}
|
||
}
|
||
|
||
// Сброс к defaults
|
||
function resetToDefaults() {
|
||
if (!confirm('Сбросить все настройки к значениям по умолчанию?')) return;
|
||
|
||
currentSettings = {
|
||
'primary-color': '#007bff',
|
||
'secondary-color': '#6c757d',
|
||
'background-color': '#f8f9fa',
|
||
'text-color': '#333333',
|
||
'primary-font': "'Inter', sans-serif",
|
||
'base-font-size': '16px',
|
||
'line-height': '1.6',
|
||
'max-width': '1200px',
|
||
'padding': '20px',
|
||
'border-radius': '6px'
|
||
};
|
||
|
||
applySettingsToControls(currentSettings);
|
||
updatePreview();
|
||
showStatus('Настройки сброшены', 'success');
|
||
}
|
||
|
||
// Обновление превью
|
||
function refreshPreview() {
|
||
document.getElementById('previewFrame').src = document.getElementById('previewFrame').src;
|
||
}
|
||
|
||
function openPreviewInNewTab() {
|
||
window.open('/', '_blank');
|
||
}
|
||
|
||
// Утилиты
|
||
function toggleSection(header) {
|
||
const content = header.nextElementSibling;
|
||
const toggle = header.querySelector('.section-toggle');
|
||
|
||
content.classList.toggle('collapsed');
|
||
toggle.textContent = content.classList.contains('collapsed') ? '▶' : '▼';
|
||
}
|
||
|
||
function showLoading(show) {
|
||
document.getElementById('loadingOverlay').classList.toggle('visible', show);
|
||
}
|
||
|
||
function showStatus(message, type) {
|
||
const statusEl = document.getElementById('statusMessage');
|
||
statusEl.textContent = message;
|
||
statusEl.className = `status-message ${type} visible`;
|
||
|
||
setTimeout(() => {
|
||
statusEl.classList.remove('visible');
|
||
}, 3000);
|
||
}
|
||
|
||
function setupPreviewUpdates() {
|
||
// Обновляем превью при изменении размера окна
|
||
window.addEventListener('resize', updatePreview);
|
||
}
|
||
|
||
// Закрытие модальных окон при клике вне их
|
||
document.addEventListener('click', function(event) {
|
||
if (event.target.classList.contains('media-modal')) {
|
||
event.target.classList.remove('visible');
|
||
}
|
||
});
|
||
|
||
// Горячие клавиши
|
||
document.addEventListener('keydown', function(event) {
|
||
if (event.ctrlKey || event.metaKey) {
|
||
switch (event.key) {
|
||
case 's':
|
||
event.preventDefault();
|
||
saveSettings();
|
||
break;
|
||
case 'r':
|
||
event.preventDefault();
|
||
refreshPreview();
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (event.key === 'Escape') {
|
||
document.querySelectorAll('.media-modal').forEach(modal => {
|
||
modal.classList.remove('visible');
|
||
});
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |