Files
tourrism_site/public/js/admin-image-selector.js
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

300 lines
10 KiB
JavaScript
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.

// JavaScript для интеграции редактора изображений в AdminJS
(function() {
'use strict';
// Функция для открытия редактора изображений
function openImageEditor(inputField, fieldName) {
const currentValue = inputField.value || '';
const editorUrl = `/image-editor-compact.html?field=${fieldName}&current=${encodeURIComponent(currentValue)}`;
// Убираем предыдущие модальные окна
document.querySelectorAll('.image-editor-modal').forEach(modal => modal.remove());
// Создаем модальное окно
const modal = document.createElement('div');
modal.className = 'image-editor-modal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
`;
const content = document.createElement('div');
content.style.cssText = `
background: white;
border-radius: 8px;
width: 90%;
max-width: 700px;
height: 80%;
max-height: 600px;
position: relative;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
`;
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕';
closeBtn.style.cssText = `
position: absolute;
top: 15px;
right: 15px;
background: #ff4757;
color: white;
border: none;
border-radius: 50%;
width: 35px;
height: 35px;
cursor: pointer;
z-index: 1;
font-size: 18px;
font-weight: bold;
`;
const iframe = document.createElement('iframe');
iframe.src = editorUrl;
iframe.style.cssText = `
width: 100%;
height: 100%;
border: none;
border-radius: 8px;
`;
// Обработчик закрытия
const closeModal = () => {
document.body.removeChild(modal);
window.removeEventListener('message', handleMessage);
};
closeBtn.onclick = closeModal;
// Обработчик сообщений от редактора
const handleMessage = (event) => {
if (event.origin !== window.location.origin) return;
if (event.data.type === 'imageSelected' && event.data.targetField === fieldName) {
inputField.value = event.data.path;
// Триггерим события изменения
inputField.dispatchEvent(new Event('input', { bubbles: true }));
inputField.dispatchEvent(new Event('change', { bubbles: true }));
// Обновляем превью если есть
updateImagePreview(inputField, event.data.path);
closeModal();
}
};
window.addEventListener('message', handleMessage);
content.appendChild(closeBtn);
content.appendChild(iframe);
modal.appendChild(content);
document.body.appendChild(modal);
// Закрытие по клику на фон
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal();
}
});
}
// Функция обновления превью изображения
function updateImagePreview(inputField, imagePath) {
const fieldContainer = inputField.closest('.field, .property-edit, div[data-testid]');
if (!fieldContainer) return;
// Находим или создаем превью
let preview = fieldContainer.querySelector('.image-preview');
if (!preview) {
preview = document.createElement('img');
preview.className = 'image-preview';
preview.style.cssText = `
display: block;
max-width: 180px;
max-height: 120px;
object-fit: cover;
border: 1px solid #ddd;
border-radius: 4px;
margin-top: 8px;
margin-bottom: 8px;
`;
// Вставляем превью после кнопки
const button = fieldContainer.querySelector('.image-editor-btn');
if (button && button.nextSibling) {
button.parentNode.insertBefore(preview, button.nextSibling);
} else {
inputField.parentNode.appendChild(preview);
}
}
if (imagePath && imagePath.trim()) {
preview.src = imagePath + '?t=' + Date.now(); // Добавляем timestamp для обновления
preview.style.display = 'block';
preview.onerror = () => {
preview.style.display = 'none';
};
} else {
preview.style.display = 'none';
}
}
// Функция добавления кнопки редактора к полю
function addImageEditorButton(inputField) {
const fieldName = inputField.name || inputField.id || 'image';
// Проверяем, не добавлена ли уже кнопка
const fieldContainer = inputField.closest('.field, .property-edit, div[data-testid]') || inputField.parentNode;
if (fieldContainer.querySelector('.image-editor-btn')) {
return;
}
// Создаем контейнер для кнопки и превью
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = `
margin-top: 8px;
display: flex;
align-items: flex-start;
gap: 10px;
`;
// Создаем кнопку
const button = document.createElement('button');
button.type = 'button';
button.className = 'image-editor-btn';
button.innerHTML = '📷 Выбрать';
button.style.cssText = `
padding: 6px 12px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
white-space: nowrap;
`;
button.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
openImageEditor(inputField, fieldName);
};
buttonContainer.appendChild(button);
// Добавляем контейнер после поля ввода
if (inputField.nextSibling) {
inputField.parentNode.insertBefore(buttonContainer, inputField.nextSibling);
} else {
inputField.parentNode.appendChild(buttonContainer);
}
// Добавляем превью если есть значение
if (inputField.value && inputField.value.trim()) {
updateImagePreview(inputField, inputField.value);
}
// Слушаем изменения в поле для обновления превью
inputField.addEventListener('input', () => {
updateImagePreview(inputField, inputField.value);
});
inputField.addEventListener('change', () => {
updateImagePreview(inputField, inputField.value);
});
}
// Функция проверки, является ли поле полем изображения
function isImageField(inputField) {
const fieldName = (inputField.name || inputField.id || '').toLowerCase();
let labelText = '';
// Ищем label для поля
const fieldContainer = inputField.closest('.field, .property-edit, div[data-testid]');
if (fieldContainer) {
const label = fieldContainer.querySelector('label, .property-label, h3');
if (label) {
labelText = label.textContent.toLowerCase();
}
}
// Проверяем по имени поля или тексту label
return fieldName.includes('image') ||
fieldName.includes('photo') ||
fieldName.includes('avatar') ||
fieldName.includes('picture') ||
fieldName.includes('banner') ||
fieldName.includes('thumbnail') ||
fieldName.includes('url') && (labelText.includes('image') || labelText.includes('изображение')) ||
labelText.includes('изображение') ||
labelText.includes('картинка') ||
labelText.includes('фото') ||
labelText.includes('image') ||
labelText.includes('picture');
}
// Функция сканирования и добавления кнопок к полям изображений
function scanAndAddImageButtons() {
console.log('🔍 Сканирование полей для добавления кнопок редактора изображений...');
// Более широкий поиск полей ввода
const inputFields = document.querySelectorAll('input[type="text"], input[type="url"], input:not([type="hidden"]):not([type="submit"]):not([type="button"])');
console.log(`📋 Найдено ${inputFields.length} полей ввода`);
inputFields.forEach((inputField, index) => {
const fieldName = inputField.name || inputField.id || `field_${index}`;
const isImage = isImageField(inputField);
const hasButton = inputField.parentNode.querySelector('.image-editor-btn');
console.log(`🔸 Поле "${fieldName}": isImage=${isImage}, hasButton=${hasButton}`);
if (isImage && !hasButton) {
console.log(` Добавляем кнопку для поля "${fieldName}"`);
addImageEditorButton(inputField);
}
});
}
// Инициализация при загрузке DOM
function initialize() {
console.log('🚀 Инициализация селектора изображений AdminJS');
scanAndAddImageButtons();
// Наблюдаем за изменениями в DOM для динамически добавляемых полей
const observer = new MutationObserver(() => {
scanAndAddImageButtons();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Периодическое сканирование для надежности
setInterval(scanAndAddImageButtons, 2000);
}
// Ждем загрузки DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
// Также запускаем через задержки для AdminJS
setTimeout(initialize, 1000);
setTimeout(initialize, 3000);
setTimeout(initialize, 5000);
})();