<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>No-Code Constructor</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: #f0f2f5;
overflow: hidden;
}
/* Панель инструментов */
.toolbar {
position: fixed;
top: 20px;
left: 20px;
right: 20px;
background: white;
border-radius: 12px;
padding: 12px 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: flex;
gap: 15px;
z-index: 1000;
flex-wrap: wrap;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-primary:hover {
background: #2563eb;
}
.btn-secondary {
background: #ef4444;
color: white;
}
.btn-secondary:hover {
background: #dc2626;
}
.btn-outline {
background: white;
border: 1px solid #d1d5db;
color: #374151;
}
.btn-outline:hover {
background: #f9fafb;
}
.element-btn {
background: #f3f4f6;
border: 1px solid #e5e7eb;
padding: 6px 12px;
font-size: 13px;
}
.element-btn:hover {
background: #e5e7eb;
}
/* Холст */
.canvas-container {
margin-top: 90px;
margin-left: 20px;
margin-right: 20px;
margin-bottom: 20px;
height: calc(100vh - 110px);
overflow-y: auto;
background: #e5e7eb;
border-radius: 12px;
padding: 20px;
}
#canvas {
min-height: 500px;
background: white;
border-radius: 8px;
padding: 40px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
position: relative;
}
/* Редактируемые элементы */
.editable-element {
position: relative;
margin: 8px 0;
padding: 12px;
border: 2px solid transparent;
transition: all 0.2s;
cursor: move;
}
.editable-element:hover {
border-color: #3b82f6;
background: #eff6ff;
}
.editable-element.selected {
border-color: #3b82f6;
background: #dbeafe;
box-shadow: 0 0 0 3px rgba(59,130,246,0.2);
}
/* Кнопки управления элементом */
.element-controls {
position: absolute;
top: -30px;
right: 0;
display: none;
gap: 5px;
background: white;
padding: 4px;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 10;
}
.editable-element:hover .element-controls {
display: flex;
}
.control-btn {
padding: 4px 8px;
font-size: 12px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.edit-btn {
background: #3b82f6;
color: white;
}
.delete-btn {
background: #ef4444;
color: white;
}
/* Модальное окно редактирования */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 2000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
border-radius: 12px;
padding: 24px;
width: 400px;
max-width: 90%;
}
.modal-content h3 {
margin-bottom: 16px;
}
.modal-content input,
.modal-content textarea,
.modal-content select {
width: 100%;
padding: 8px;
margin: 8px 0;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
}
.modal-content textarea {
min-height: 80px;
}
.modal-buttons {
display: flex;
gap: 10px;
margin-top: 16px;
}
/* Drag and drop placeholder */
.drag-over {
border: 2px dashed #3b82f6;
background: #eff6ff;
}
</style>
</head>
<body>
<div class="toolbar">
<div style="display: flex; gap: 8px;">
<button class="btn btn-outline element-btn" data-type="header">📝 Заголовок</button>
<button class="btn btn-outline element-btn" data-type="text">📄 Текст</button>
<button class="btn btn-outline element-btn" data-type="image">🖼️ Изображение</button>
<button class="btn btn-outline element-btn" data-type="button">🔘 Кнопка</button>
<button class="btn btn-outline element-btn" data-type="divider">➖ Разделитель</button>
</div>
<div style="flex:1"></div>
<button class="btn btn-outline" id="previewBtn">👁️ Предпросмотр</button>
<button class="btn btn-primary" id="exportBtn">💾 Экспорт HTML</button>
<button class="btn btn-secondary" id="clearBtn">🗑️ Очистить</button>
</div>
<div class="canvas-container">
<div id="canvas"></div>
</div>
<!-- Модальное окно редактирования -->
<div id="editModal" class="modal">
<div class="modal-content">
<h3>Редактировать элемент</h3>
<div id="editFields"></div>
<div class="modal-buttons">
<button class="btn btn-primary" id="saveEditBtn">Сохранить</button>
<button class="btn btn-outline" id="closeModalBtn">Отмена</button>
</div>
</div>
</div>
<script>
class PageBuilder {
constructor() {
this.elements = [];
this.nextId = 1;
this.selectedElement = null;
this.currentEditElement = null;
this.canvas = document.getElementById('canvas');
this.init();
this.loadFromLocalStorage();
this.render();
}
init() {
// Добавление элементов через кнопки
document.querySelectorAll('.element-btn').forEach(btn => {
btn.addEventListener('click', () => {
const type = btn.dataset.type;
this.addElement(type);
});
});
// Экспорт
document.getElementById('exportBtn').addEventListener('click', () => this.exportHTML());
// Очистка
document.getElementById('clearBtn').addEventListener('click', () => {
if(confirm('Очистить все?')) {
this.elements = [];
this.saveToLocalStorage();
this.render();
}
});
// Предпросмотр
document.getElementById('previewBtn').addEventListener('click', () => this.preview());
// Модальное окно
document.getElementById('saveEditBtn').addEventListener('click', () => this.saveEdit());
document.getElementById('closeModalBtn').addEventListener('click', () => this.closeModal());
// Drag and drop для перетаскивания
this.setupDragAndDrop();
}
addElement(type, content = null) {
const element = {
id: this.nextId++,
type: type,
content: content || this.getDefaultContent(type)
};
this.elements.push(element);
this.saveToLocalStorage();
this.render();
return element;
}
getDefaultContent(type) {
switch(type) {
case 'header':
return { text: 'Новый заголовок', level: 'h2', align: 'left' };
case 'text':
return { text: 'Текст для вашей страницы. Дважды кликните для редактирования.', align: 'left' };
case 'image':
return { src: 'https://via.placeholder.com/400x200?text=Image', alt: 'Image', width: '100%' };
case 'button':
return { text: 'Нажми меня', url: '#', style: 'primary' };
case 'divider':
return { style: 'solid', color: '#d1d5db' };
default:
return {};
}
}
render() {
if (!this.canvas) return;
this.canvas.innerHTML = '';
this.elements.forEach((element, index) => {
const elementDiv = document.createElement('div');
elementDiv.className = 'editable-element';
if (this.selectedElement === element.id) {
elementDiv.classList.add('selected');
}
elementDiv.setAttribute('data-id', element.id);
elementDiv.setAttribute('draggable', 'true');
// Содержимое элемента
const contentDiv = document.createElement('div');
contentDiv.className = 'element-content';
contentDiv.innerHTML = this.renderElementContent(element);
elementDiv.appendChild(contentDiv);
// Кнопки управления
const controls = document.createElement('div');
controls.className = 'element-controls';
controls.innerHTML = `
<button class="control-btn edit-btn" data-id="${element.id}">✏️</button>
<button class="control-btn delete-btn" data-id="${element.id}">🗑️</button>
`;
elementDiv.appendChild(controls);
// Обработчики
controls.querySelector('.edit-btn').addEventListener('click', (e) => {
e.stopPropagation();
this.openEditModal(element.id);
});
controls.querySelector('.delete-btn').addEventListener('click', (e) => {
e.stopPropagation();
this.deleteElement(element.id);
});
elementDiv.addEventListener('click', (e) => {
e.stopPropagation();
this.selectElement(element.id);
});
// Drag and drop для переупорядочивания
elementDiv.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', element.id);
e.dataTransfer.effectAllowed = 'move';
});
elementDiv.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
});
elementDiv.addEventListener('drop', (e) => {
e.preventDefault();
const draggedId = parseInt(e.dataTransfer.getData('text/plain'));
const targetId = element.id;
if (draggedId !== targetId) {
this.moveElement(draggedId, targetId);
}
});
this.canvas.appendChild(elementDiv);
});
if (this.elements.length === 0) {
this.canvas.innerHTML = '<div style="text-align: center; padding: 60px; color: #9ca3af;">Добавьте элементы на страницу, нажимая кнопки выше</div>';
}
}
renderElementContent(element) {
switch(element.type) {
case 'header':
const HeaderTag = element.content.level || 'h2';
return `<${HeaderTag} style="text-align: ${element.content.align || 'left'}; margin: 0;">${this.escapeHtml(element.content.text)}</${HeaderTag}>`;
case 'text':
return `<p style="text-align: ${element.content.align || 'left'}; margin: 0;">${this.escapeHtml(element.content.text)}</p>`;
case 'image':
return `<img src="${element.content.src}" alt="${this.escapeHtml(element.content.alt)}" style="max-width: 100%; height: auto; width: ${element.content.width || 'auto'}; display: block;">`;
case 'button':
const btnStyle = element.content.style === 'primary'
? 'background: #3b82f6; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer;'
: 'background: white; color: #374151; border: 1px solid #d1d5db; padding: 10px 20px; border-radius: 6px; cursor: pointer;';
return `<a href="${element.content.url}" style="${btnStyle} text-decoration: none; display: inline-block;">${this.escapeHtml(element.content.text)}</a>`;
case 'divider':
return `<hr style="border: none; height: 1px; background: ${element.content.color || '#d1d5db'}; margin: 10px 0;">`;
default:
return '';
}
}
openEditModal(elementId) {
const element = this.elements.find(el => el.id === elementId);
if (!element) return;
this.currentEditElement = element;
const editFields = document.getElementById('editFields');
switch(element.type) {
case 'header':
editFields.innerHTML = `
<label>Текст:</label>
<input type="text" id="edit-text" value="${this.escapeHtml(element.content.text)}">
<label>Уровень:</label>
<select id="edit-level">
<option value="h1" ${element.content.level === 'h1' ? 'selected' : ''}>H1</option>
<option value="h2" ${element.content.level === 'h2' ? 'selected' : ''}>H2</option>
<option value="h3" ${element.content.level === 'h3' ? 'selected' : ''}>H3</option>
</select>
<label>Выравнивание:</label>
<select id="edit-align">
<option value="left" ${element.content.align === 'left' ? 'selected' : ''}>По левому краю</option>
<option value="center" ${element.content.align === 'center' ? 'selected' : ''}>По центру</option>
<option value="right" ${element.content.align === 'right' ? 'selected' : ''}>По правому краю</option>
</select>
`;
break;
case 'text':
editFields.innerHTML = `
<label>Текст:</label>
<textarea id="edit-text">${this.escapeHtml(element.content.text)}</textarea>
<label>Выравнивание:</label>
<select id="edit-align">
<option value="left" ${element.content.align === 'left' ? 'selected' : ''}>По левому краю</option>
<option value="center" ${element.content.align === 'center' ? 'selected' : ''}>По центру</option>
<option value="right" ${element.content.align === 'right' ? 'selected' : ''}>По правому краю</option>
</select>
`;
break;
case 'image':
editFields.innerHTML = `
<label>URL изображения:</label>
<input type="text" id="edit-src" value="${this.escapeHtml(element.content.src)}">
<label>Alt текст:</label>
<input type="text" id="edit-alt" value="${this.escapeHtml(element.content.alt)}">
<label>Ширина:</label>
<input type="text" id="edit-width" value="${element.content.width || 'auto'}" placeholder="auto, 100%, 300px">
`;
break;
case 'button':
editFields.innerHTML = `
<label>Текст:</label>
<input type="text" id="edit-text" value="${this.escapeHtml(element.content.text)}">
<label>Ссылка:</label>
<input type="text" id="edit-url" value="${element.content.url}">
<label>Стиль:</label>
<select id="edit-style">
<option value="primary" ${element.content.style === 'primary' ? 'selected' : ''}>Синий</option>
<option value="secondary" ${element.content.style === 'secondary' ? 'selected' : ''}>Белый</option>
</select>
`;
break;
case 'divider':
editFields.innerHTML = `
<label>Цвет:</label>
<input type="color" id="edit-color" value="${element.content.color || '#d1d5db'}">
`;
break;
}
document.getElementById('editModal').classList.add('active');
}
saveEdit() {
if (!this.currentEditElement) return;
const element = this.currentEditElement;
switch(element.type) {
case 'header':
element.content.text = document.getElementById('edit-text').value;
element.content.level = document.getElementById('edit-level').value;
element.content.align = document.getElementById('edit-align').value;
break;
case 'text':
element.content.text = document.getElementById('edit-text').value;
element.content.align = document.getElementById('edit-align').value;
break;
case 'image':
element.content.src = document.getElementById('edit-src').value;
element.content.alt = document.getElementById('edit-alt').value;
element.content.width = document.getElementById('edit-width').value;
break;
case 'button':
element.content.text = document.getElementById('edit-text').value;
element.content.url = document.getElementById('edit-url').value;
element.content.style = document.getElementById('edit-style').value;
break;
case 'divider':
element.content.color = document.getElementById('edit-color').value;
break;
}
this.saveToLocalStorage();
this.render();
this.closeModal();
}
deleteElement(id) {
this.elements = this.elements.filter(el => el.id !== id);
if (this.selectedElement === id) this.selectedElement = null;
this.saveToLocalStorage();
this.render();
}
moveElement(draggedId, targetId) {
const draggedIndex = this.elements.findIndex(el => el.id === draggedId);
const targetIndex = this.elements.findIndex(el => el.id === targetId);
if (draggedIndex === -1 || targetIndex === -1) return;
const [draggedElement] = this.elements.splice(draggedIndex, 1);
this.elements.splice(targetIndex, 0, draggedElement);
this.saveToLocalStorage();
this.render();
}
selectElement(id) {
this.selectedElement = id;
this.render();
}
exportHTML() {
let htmlContent = `<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Сгенерированная страница</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 40px;
background: #f0f2f5;
}
.page-content {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
img {
max-width: 100%;
height: auto;
}
a {
text-decoration: none;
}
</style>
</head>
<body>
<div class="page-content">`;
this.elements.forEach(element => {
htmlContent += this.renderElementContent(element);
});
htmlContent += `
</div>
</body>
</html>`;
const blob = new Blob([htmlContent], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'landing-page.html';
a.click();
URL.revokeObjectURL(url);
}
preview() {
const previewWindow = window.open();
previewWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>Предпросмотр</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 40px;
background: #f0f2f5;
}
.preview-content {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
img { max-width: 100%; height: auto; }
a { text-decoration: none; }
</style>
</head>
<body>
<div class="preview-content">
${this.elements.map(el => this.renderElementContent(el)).join('')}
</div>
</body>
</html>
`);
previewWindow.document.close();
}
setupDragAndDrop() {
this.canvas.addEventListener('dragover', (e) => {
e.preventDefault();
});
this.canvas.addEventListener('drop', (e) => {
e.preventDefault();
});
}
saveToLocalStorage() {
localStorage.setItem('pageBuilderElements', JSON.stringify(this.elements));
localStorage.setItem('pageBuilderNextId', this.nextId);
}
loadFromLocalStorage() {
const saved = localStorage.getItem('pageBuilderElements');
if (saved) {
this.elements = JSON.parse(saved);
const savedId = localStorage.getItem('pageBuilderNextId');
if (savedId) this.nextId = parseInt(savedId);
}
}
closeModal() {
document.getElementById('editModal').classList.remove('active');
this.currentEditElement = null;
}
escapeHtml(str) {
if (!str) return '';
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
}
// Запуск
const builder = new PageBuilder();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>No-Code Constructor</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: #f0f2f5;
overflow: hidden;
}
/* Панель инструментов */
.toolbar {
position: fixed;
top: 20px;
left: 20px;
right: 20px;
background: white;
border-radius: 12px;
padding: 12px 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: flex;
gap: 15px;
z-index: 1000;
flex-wrap: wrap;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-primary:hover {
background: #2563eb;
}
.btn-secondary {
background: #ef4444;
color: white;
}
.btn-secondary:hover {
background: #dc2626;
}
.btn-outline {
background: white;
border: 1px solid #d1d5db;
color: #374151;
}
.btn-outline:hover {
background: #f9fafb;
}
.element-btn {
background: #f3f4f6;
border: 1px solid #e5e7eb;
padding: 6px 12px;
font-size: 13px;
}
.element-btn:hover {
background: #e5e7eb;
}
/* Холст */
.canvas-container {
margin-top: 90px;
margin-left: 20px;
margin-right: 20px;
margin-bottom: 20px;
height: calc(100vh - 110px);
overflow-y: auto;
background: #e5e7eb;
border-radius: 12px;
padding: 20px;
}
#canvas {
min-height: 500px;
background: white;
border-radius: 8px;
padding: 40px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
position: relative;
}
/* Редактируемые элементы */
.editable-element {
position: relative;
margin: 8px 0;
padding: 12px;
border: 2px solid transparent;
transition: all 0.2s;
cursor: move;
}
.editable-element:hover {
border-color: #3b82f6;
background: #eff6ff;
}
.editable-element.selected {
border-color: #3b82f6;
background: #dbeafe;
box-shadow: 0 0 0 3px rgba(59,130,246,0.2);
}
/* Кнопки управления элементом */
.element-controls {
position: absolute;
top: -30px;
right: 0;
display: none;
gap: 5px;
background: white;
padding: 4px;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 10;
}
.editable-element:hover .element-controls {
display: flex;
}
.control-btn {
padding: 4px 8px;
font-size: 12px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.edit-btn {
background: #3b82f6;
color: white;
}
.delete-btn {
background: #ef4444;
color: white;
}
/* Модальное окно редактирования */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 2000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
border-radius: 12px;
padding: 24px;
width: 400px;
max-width: 90%;
}
.modal-content h3 {
margin-bottom: 16px;
}
.modal-content input,
.modal-content textarea,
.modal-content select {
width: 100%;
padding: 8px;
margin: 8px 0;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
}
.modal-content textarea {
min-height: 80px;
}
.modal-buttons {
display: flex;
gap: 10px;
margin-top: 16px;
}
/* Drag and drop placeholder */
.drag-over {
border: 2px dashed #3b82f6;
background: #eff6ff;
}
</style>
</head>
<body>
<div class="toolbar">
<div style="display: flex; gap: 8px;">
<button class="btn btn-outline element-btn" data-type="header">📝 Заголовок</button>
<button class="btn btn-outline element-btn" data-type="text">📄 Текст</button>
<button class="btn btn-outline element-btn" data-type="image">🖼️ Изображение</button>
<button class="btn btn-outline element-btn" data-type="button">🔘 Кнопка</button>
<button class="btn btn-outline element-btn" data-type="divider">➖ Разделитель</button>
</div>
<div style="flex:1"></div>
<button class="btn btn-outline" id="previewBtn">👁️ Предпросмотр</button>
<button class="btn btn-primary" id="exportBtn">💾 Экспорт HTML</button>
<button class="btn btn-secondary" id="clearBtn">🗑️ Очистить</button>
</div>
<div class="canvas-container">
<div id="canvas"></div>
</div>
<!-- Модальное окно редактирования -->
<div id="editModal" class="modal">
<div class="modal-content">
<h3>Редактировать элемент</h3>
<div id="editFields"></div>
<div class="modal-buttons">
<button class="btn btn-primary" id="saveEditBtn">Сохранить</button>
<button class="btn btn-outline" id="closeModalBtn">Отмена</button>
</div>
</div>
</div>
<script>
class PageBuilder {
constructor() {
this.elements = [];
this.nextId = 1;
this.selectedElement = null;
this.currentEditElement = null;
this.canvas = document.getElementById('canvas');
this.init();
this.loadFromLocalStorage();
this.render();
}
init() {
// Добавление элементов через кнопки
document.querySelectorAll('.element-btn').forEach(btn => {
btn.addEventListener('click', () => {
const type = btn.dataset.type;
this.addElement(type);
});
});
// Экспорт
document.getElementById('exportBtn').addEventListener('click', () => this.exportHTML());
// Очистка
document.getElementById('clearBtn').addEventListener('click', () => {
if(confirm('Очистить все?')) {
this.elements = [];
this.saveToLocalStorage();
this.render();
}
});
// Предпросмотр
document.getElementById('previewBtn').addEventListener('click', () => this.preview());
// Модальное окно
document.getElementById('saveEditBtn').addEventListener('click', () => this.saveEdit());
document.getElementById('closeModalBtn').addEventListener('click', () => this.closeModal());
// Drag and drop для перетаскивания
this.setupDragAndDrop();
}
addElement(type, content = null) {
const element = {
id: this.nextId++,
type: type,
content: content || this.getDefaultContent(type)
};
this.elements.push(element);
this.saveToLocalStorage();
this.render();
return element;
}
getDefaultContent(type) {
switch(type) {
case 'header':
return { text: 'Новый заголовок', level: 'h2', align: 'left' };
case 'text':
return { text: 'Текст для вашей страницы. Дважды кликните для редактирования.', align: 'left' };
case 'image':
return { src: 'https://via.placeholder.com/400x200?text=Image', alt: 'Image', width: '100%' };
case 'button':
return { text: 'Нажми меня', url: '#', style: 'primary' };
case 'divider':
return { style: 'solid', color: '#d1d5db' };
default:
return {};
}
}
render() {
if (!this.canvas) return;
this.canvas.innerHTML = '';
this.elements.forEach((element, index) => {
const elementDiv = document.createElement('div');
elementDiv.className = 'editable-element';
if (this.selectedElement === element.id) {
elementDiv.classList.add('selected');
}
elementDiv.setAttribute('data-id', element.id);
elementDiv.setAttribute('draggable', 'true');
// Содержимое элемента
const contentDiv = document.createElement('div');
contentDiv.className = 'element-content';
contentDiv.innerHTML = this.renderElementContent(element);
elementDiv.appendChild(contentDiv);
// Кнопки управления
const controls = document.createElement('div');
controls.className = 'element-controls';
controls.innerHTML = `
<button class="control-btn edit-btn" data-id="${element.id}">✏️</button>
<button class="control-btn delete-btn" data-id="${element.id}">🗑️</button>
`;
elementDiv.appendChild(controls);
// Обработчики
controls.querySelector('.edit-btn').addEventListener('click', (e) => {
e.stopPropagation();
this.openEditModal(element.id);
});
controls.querySelector('.delete-btn').addEventListener('click', (e) => {
e.stopPropagation();
this.deleteElement(element.id);
});
elementDiv.addEventListener('click', (e) => {
e.stopPropagation();
this.selectElement(element.id);
});
// Drag and drop для переупорядочивания
elementDiv.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', element.id);
e.dataTransfer.effectAllowed = 'move';
});
elementDiv.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
});
elementDiv.addEventListener('drop', (e) => {
e.preventDefault();
const draggedId = parseInt(e.dataTransfer.getData('text/plain'));
const targetId = element.id;
if (draggedId !== targetId) {
this.moveElement(draggedId, targetId);
}
});
this.canvas.appendChild(elementDiv);
});
if (this.elements.length === 0) {
this.canvas.innerHTML = '<div style="text-align: center; padding: 60px; color: #9ca3af;">Добавьте элементы на страницу, нажимая кнопки выше</div>';
}
}
renderElementContent(element) {
switch(element.type) {
case 'header':
const HeaderTag = element.content.level || 'h2';
return `<${HeaderTag} style="text-align: ${element.content.align || 'left'}; margin: 0;">${this.escapeHtml(element.content.text)}</${HeaderTag}>`;
case 'text':
return `<p style="text-align: ${element.content.align || 'left'}; margin: 0;">${this.escapeHtml(element.content.text)}</p>`;
case 'image':
return `<img src="${element.content.src}" alt="${this.escapeHtml(element.content.alt)}" style="max-width: 100%; height: auto; width: ${element.content.width || 'auto'}; display: block;">`;
case 'button':
const btnStyle = element.content.style === 'primary'
? 'background: #3b82f6; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer;'
: 'background: white; color: #374151; border: 1px solid #d1d5db; padding: 10px 20px; border-radius: 6px; cursor: pointer;';
return `<a href="${element.content.url}" style="${btnStyle} text-decoration: none; display: inline-block;">${this.escapeHtml(element.content.text)}</a>`;
case 'divider':
return `<hr style="border: none; height: 1px; background: ${element.content.color || '#d1d5db'}; margin: 10px 0;">`;
default:
return '';
}
}
openEditModal(elementId) {
const element = this.elements.find(el => el.id === elementId);
if (!element) return;
this.currentEditElement = element;
const editFields = document.getElementById('editFields');
switch(element.type) {
case 'header':
editFields.innerHTML = `
<label>Текст:</label>
<input type="text" id="edit-text" value="${this.escapeHtml(element.content.text)}">
<label>Уровень:</label>
<select id="edit-level">
<option value="h1" ${element.content.level === 'h1' ? 'selected' : ''}>H1</option>
<option value="h2" ${element.content.level === 'h2' ? 'selected' : ''}>H2</option>
<option value="h3" ${element.content.level === 'h3' ? 'selected' : ''}>H3</option>
</select>
<label>Выравнивание:</label>
<select id="edit-align">
<option value="left" ${element.content.align === 'left' ? 'selected' : ''}>По левому краю</option>
<option value="center" ${element.content.align === 'center' ? 'selected' : ''}>По центру</option>
<option value="right" ${element.content.align === 'right' ? 'selected' : ''}>По правому краю</option>
</select>
`;
break;
case 'text':
editFields.innerHTML = `
<label>Текст:</label>
<textarea id="edit-text">${this.escapeHtml(element.content.text)}</textarea>
<label>Выравнивание:</label>
<select id="edit-align">
<option value="left" ${element.content.align === 'left' ? 'selected' : ''}>По левому краю</option>
<option value="center" ${element.content.align === 'center' ? 'selected' : ''}>По центру</option>
<option value="right" ${element.content.align === 'right' ? 'selected' : ''}>По правому краю</option>
</select>
`;
break;
case 'image':
editFields.innerHTML = `
<label>URL изображения:</label>
<input type="text" id="edit-src" value="${this.escapeHtml(element.content.src)}">
<label>Alt текст:</label>
<input type="text" id="edit-alt" value="${this.escapeHtml(element.content.alt)}">
<label>Ширина:</label>
<input type="text" id="edit-width" value="${element.content.width || 'auto'}" placeholder="auto, 100%, 300px">
`;
break;
case 'button':
editFields.innerHTML = `
<label>Текст:</label>
<input type="text" id="edit-text" value="${this.escapeHtml(element.content.text)}">
<label>Ссылка:</label>
<input type="text" id="edit-url" value="${element.content.url}">
<label>Стиль:</label>
<select id="edit-style">
<option value="primary" ${element.content.style === 'primary' ? 'selected' : ''}>Синий</option>
<option value="secondary" ${element.content.style === 'secondary' ? 'selected' : ''}>Белый</option>
</select>
`;
break;
case 'divider':
editFields.innerHTML = `
<label>Цвет:</label>
<input type="color" id="edit-color" value="${element.content.color || '#d1d5db'}">
`;
break;
}
document.getElementById('editModal').classList.add('active');
}
saveEdit() {
if (!this.currentEditElement) return;
const element = this.currentEditElement;
switch(element.type) {
case 'header':
element.content.text = document.getElementById('edit-text').value;
element.content.level = document.getElementById('edit-level').value;
element.content.align = document.getElementById('edit-align').value;
break;
case 'text':
element.content.text = document.getElementById('edit-text').value;
element.content.align = document.getElementById('edit-align').value;
break;
case 'image':
element.content.src = document.getElementById('edit-src').value;
element.content.alt = document.getElementById('edit-alt').value;
element.content.width = document.getElementById('edit-width').value;
break;
case 'button':
element.content.text = document.getElementById('edit-text').value;
element.content.url = document.getElementById('edit-url').value;
element.content.style = document.getElementById('edit-style').value;
break;
case 'divider':
element.content.color = document.getElementById('edit-color').value;
break;
}
this.saveToLocalStorage();
this.render();
this.closeModal();
}
deleteElement(id) {
this.elements = this.elements.filter(el => el.id !== id);
if (this.selectedElement === id) this.selectedElement = null;
this.saveToLocalStorage();
this.render();
}
moveElement(draggedId, targetId) {
const draggedIndex = this.elements.findIndex(el => el.id === draggedId);
const targetIndex = this.elements.findIndex(el => el.id === targetId);
if (draggedIndex === -1 || targetIndex === -1) return;
const [draggedElement] = this.elements.splice(draggedIndex, 1);
this.elements.splice(targetIndex, 0, draggedElement);
this.saveToLocalStorage();
this.render();
}
selectElement(id) {
this.selectedElement = id;
this.render();
}
exportHTML() {
let htmlContent = `<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Сгенерированная страница</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 40px;
background: #f0f2f5;
}
.page-content {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
img {
max-width: 100%;
height: auto;
}
a {
text-decoration: none;
}
</style>
</head>
<body>
<div class="page-content">`;
this.elements.forEach(element => {
htmlContent += this.renderElementContent(element);
});
htmlContent += `
</div>
</body>
</html>`;
const blob = new Blob([htmlContent], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'landing-page.html';
a.click();
URL.revokeObjectURL(url);
}
preview() {
const previewWindow = window.open();
previewWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>Предпросмотр</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 40px;
background: #f0f2f5;
}
.preview-content {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
img { max-width: 100%; height: auto; }
a { text-decoration: none; }
</style>
</head>
<body>
<div class="preview-content">
${this.elements.map(el => this.renderElementContent(el)).join('')}
</div>
</body>
</html>
`);
previewWindow.document.close();
}
setupDragAndDrop() {
this.canvas.addEventListener('dragover', (e) => {
e.preventDefault();
});
this.canvas.addEventListener('drop', (e) => {
e.preventDefault();
});
}
saveToLocalStorage() {
localStorage.setItem('pageBuilderElements', JSON.stringify(this.elements));
localStorage.setItem('pageBuilderNextId', this.nextId);
}
loadFromLocalStorage() {
const saved = localStorage.getItem('pageBuilderElements');
if (saved) {
this.elements = JSON.parse(saved);
const savedId = localStorage.getItem('pageBuilderNextId');
if (savedId) this.nextId = parseInt(savedId);
}
}
closeModal() {
document.getElementById('editModal').classList.remove('active');
this.currentEditElement = null;
}
escapeHtml(str) {
if (!str) return '';
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
}
// Запуск
const builder = new PageBuilder();
</script>
</body>
</html>