Files
tourrism_site/public/test-image-editor.html
Andrey K. Choi b4e513e996 🚀 Korea Tourism Agency - Complete implementation
 Features:
- Modern tourism website with responsive design
- AdminJS admin panel with image editor integration
- PostgreSQL database with comprehensive schema
- Docker containerization
- Image upload and gallery management

🛠 Tech Stack:
- Backend: Node.js + Express.js
- Database: PostgreSQL 13+
- Frontend: HTML/CSS/JS with responsive design
- Admin: AdminJS with custom components
- Deployment: Docker + Docker Compose
- Image Processing: Sharp with optimization

📱 Admin Features:
- Routes/Tours management (city, mountain, fishing)
- Guides profiles with specializations
- Articles and blog system
- Image editor with upload/gallery/URL options
- User management and authentication
- Responsive admin interface

🎨 Design:
- Korean tourism focused branding
- Mobile-first responsive design
- Custom CSS with modern aesthetics
- Image optimization and gallery
- SEO-friendly structure

🔒 Security:
- Helmet.js security headers
- bcrypt password hashing
- Input validation and sanitization
- CORS protection
- Environment variables
2025-11-30 00:53:15 +09:00

191 lines
9.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест редактора изображений</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1>Тест интеграции редактора изображений</h1>
<p>Этот файл демонстрирует, как редактор изображений будет работать с AdminJS.</p>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>Поле изображения маршрута</h5>
</div>
<div class="card-body">
<label for="route_image_url" class="form-label">Изображение маршрута</label>
<div class="input-group">
<input type="text" id="route_image_url" name="route_image_url" class="form-control"
placeholder="/uploads/routes/example.jpg" value="/uploads/routes/seoul-city-tour.jpg">
<button type="button" class="btn btn-outline-secondary" onclick="openImageEditor('route_image_url', document.getElementById('route_image_url').value)">
<i class="fas fa-images"></i> Выбрать
</button>
</div>
<img id="route_image_url_preview" src="/uploads/routes/seoul-city-tour.jpg"
class="img-thumbnail mt-2" style="max-width: 200px; max-height: 200px;">
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>Поле изображения гида</h5>
</div>
<div class="card-body">
<label for="guide_image_url" class="form-label">Фотография гида</label>
<div class="input-group">
<input type="text" id="guide_image_url" name="guide_image_url" class="form-control"
placeholder="/uploads/guides/example.jpg" value="/uploads/guides/guide-profile.jpg">
<button type="button" class="btn btn-outline-secondary" onclick="openImageEditor('guide_image_url', document.getElementById('guide_image_url').value)">
<i class="fas fa-images"></i> Выбрать
</button>
</div>
<img id="guide_image_url_preview" src="/uploads/guides/guide-profile.jpg"
class="img-thumbnail mt-2" style="max-width: 200px; max-height: 200px;">
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5>Доступные изображения</h5>
</div>
<div class="card-body">
<div id="imageList" class="row">
<!-- Будет заполнено динамически -->
</div>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="alert alert-info">
<h6>Как использовать:</h6>
<ol>
<li>Нажмите кнопку "Выбрать" рядом с полем изображения</li>
<li>Откроется редактор изображений в новом окне</li>
<li>Выберите изображение из галереи, загрузите новое или укажите URL</li>
<li>Нажмите "Выбрать" в редакторе</li>
<li>Поле автоматически обновится с выбранным путем</li>
</ol>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
<script>
// Функция для открытия редактора изображений
function openImageEditor(fieldName, currentValue) {
const editorUrl = `/image-editor.html?field=${fieldName}&current=${encodeURIComponent(currentValue || '')}`;
const editorWindow = window.open(editorUrl, 'imageEditor', 'width=1200,height=800,scrollbars=yes,resizable=yes');
// Слушаем сообщения от редактора
const messageHandler = (event) => {
if (event.origin !== window.location.origin) return;
if (event.data.type === 'imageSelected' && event.data.targetField === fieldName) {
const field = document.getElementById(fieldName);
const preview = document.getElementById(fieldName + '_preview');
if (field) {
field.value = event.data.path;
field.dispatchEvent(new Event('change', { bubbles: true }));
}
if (preview) {
preview.src = event.data.path;
}
window.removeEventListener('message', messageHandler);
editorWindow.close();
showSuccess(`Изображение обновлено: ${event.data.path}`);
}
};
window.addEventListener('message', messageHandler);
// Очистка обработчика при закрытии окна
const checkClosed = setInterval(() => {
if (editorWindow.closed) {
window.removeEventListener('message', messageHandler);
clearInterval(checkClosed);
}
}, 1000);
}
// Функция показа уведомления об успехе
function showSuccess(message) {
const alert = document.createElement('div');
alert.className = 'alert alert-success alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-3';
alert.style.zIndex = '9999';
alert.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alert);
setTimeout(() => {
if (alert.parentNode) {
alert.parentNode.removeChild(alert);
}
}, 3000);
}
// Загрузка списка изображений
async function loadImageList() {
try {
const response = await fetch('/api/images/gallery?folder=all');
const result = await response.json();
if (result.success) {
renderImageList(result.data);
} else {
document.getElementById('imageList').innerHTML = '<p class="text-muted">Ошибка загрузки изображений</p>';
}
} catch (error) {
document.getElementById('imageList').innerHTML = '<p class="text-muted">Ошибка: ' + error.message + '</p>';
}
}
// Отображение списка изображений
function renderImageList(images) {
const container = document.getElementById('imageList');
if (images.length === 0) {
container.innerHTML = '<p class="text-muted">Изображения не найдены</p>';
return;
}
container.innerHTML = images.slice(0, 12).map(img => `
<div class="col-md-2 col-sm-3 col-4 mb-3">
<div class="card h-100">
<img src="${img.path}" class="card-img-top" style="height: 100px; object-fit: cover;" alt="${img.name}">
<div class="card-body p-2">
<small class="card-title text-truncate d-block">${img.name}</small>
<small class="text-muted">${img.folder}</small>
</div>
</div>
</div>
`).join('');
}
// Загружаем изображения при загрузке страницы
document.addEventListener('DOMContentLoaded', loadImageList);
</script>
</body>
</html>