🚀 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
This commit is contained in:
@@ -1,5 +1,96 @@
|
||||
/* Korea Tourism Agency Admin Panel Custom Scripts */
|
||||
|
||||
// Функция для открытия редактора изображений
|
||||
function openImageEditor(fieldName, currentValue) {
|
||||
const editorUrl = `/image-editor.html?field=${fieldName}¤t=${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.querySelector(`input[name="${fieldName}"], input[id="${fieldName}"]`);
|
||||
if (field) {
|
||||
field.value = event.data.path;
|
||||
field.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
|
||||
// Обновляем превью если есть
|
||||
updateImagePreview(fieldName, event.data.path);
|
||||
}
|
||||
|
||||
window.removeEventListener('message', messageHandler);
|
||||
editorWindow.close();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', messageHandler);
|
||||
|
||||
// Очистка обработчика при закрытии окна
|
||||
const checkClosed = setInterval(() => {
|
||||
if (editorWindow.closed) {
|
||||
window.removeEventListener('message', messageHandler);
|
||||
clearInterval(checkClosed);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Функция обновления превью изображения
|
||||
function updateImagePreview(fieldName, imagePath) {
|
||||
const previewId = `${fieldName}_preview`;
|
||||
let preview = document.getElementById(previewId);
|
||||
|
||||
if (!preview) {
|
||||
// Создаем превью если его нет
|
||||
const field = document.querySelector(`input[name="${fieldName}"], input[id="${fieldName}"]`);
|
||||
if (field) {
|
||||
preview = document.createElement('img');
|
||||
preview.id = previewId;
|
||||
preview.className = 'img-thumbnail mt-2';
|
||||
preview.style.maxWidth = '200px';
|
||||
preview.style.maxHeight = '200px';
|
||||
field.parentNode.appendChild(preview);
|
||||
}
|
||||
}
|
||||
|
||||
if (preview) {
|
||||
preview.src = imagePath || '/images/placeholders/no-image.png';
|
||||
preview.alt = 'Preview';
|
||||
}
|
||||
}
|
||||
|
||||
// Функция добавления кнопки редактора к полю
|
||||
function addImageEditorButton(field) {
|
||||
const fieldName = field.name || field.id;
|
||||
if (!fieldName) return;
|
||||
|
||||
// Проверяем, не добавлена ли уже кнопка
|
||||
if (field.parentNode.querySelector('.image-editor-btn')) return;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'input-group';
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'btn btn-outline-secondary image-editor-btn';
|
||||
button.innerHTML = '<i class="fas fa-images"></i> Выбрать';
|
||||
button.onclick = () => openImageEditor(fieldName, field.value);
|
||||
|
||||
const buttonWrapper = document.createElement('div');
|
||||
buttonWrapper.className = 'input-group-append';
|
||||
buttonWrapper.appendChild(button);
|
||||
|
||||
// Перестраиваем структуру
|
||||
field.parentNode.insertBefore(wrapper, field);
|
||||
wrapper.appendChild(field);
|
||||
wrapper.appendChild(buttonWrapper);
|
||||
|
||||
// Добавляем превью если есть значение
|
||||
if (field.value) {
|
||||
updateImagePreview(fieldName, field.value);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// Initialize tooltips
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
@@ -11,6 +102,33 @@ $(document).ready(function() {
|
||||
setTimeout(function() {
|
||||
$('.alert').fadeOut('slow');
|
||||
}, 5000);
|
||||
|
||||
// Добавляем кнопки редактора к полям изображений
|
||||
$('input[type="text"], input[type="url"]').each(function() {
|
||||
const field = this;
|
||||
const fieldName = field.name || field.id || '';
|
||||
|
||||
// Проверяем, относится ли поле к изображениям
|
||||
if (fieldName.includes('image') || fieldName.includes('photo') || fieldName.includes('avatar') ||
|
||||
fieldName.includes('picture') || fieldName.includes('thumbnail') || fieldName.includes('banner') ||
|
||||
$(field).closest('label').text().toLowerCase().includes('изображение') ||
|
||||
$(field).closest('label').text().toLowerCase().includes('картинка') ||
|
||||
$(field).closest('label').text().toLowerCase().includes('фото')) {
|
||||
addImageEditorButton(field);
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик для динамически добавляемых полей
|
||||
$(document).on('focus', 'input[type="text"], input[type="url"]', function() {
|
||||
const field = this;
|
||||
const fieldName = field.name || field.id || '';
|
||||
|
||||
if ((fieldName.includes('image') || fieldName.includes('photo') || fieldName.includes('avatar') ||
|
||||
fieldName.includes('picture') || fieldName.includes('thumbnail') || fieldName.includes('banner')) &&
|
||||
!field.parentNode.querySelector('.image-editor-btn')) {
|
||||
addImageEditorButton(field);
|
||||
}
|
||||
});
|
||||
|
||||
// Confirm delete actions
|
||||
$('.btn-delete').on('click', function(e) {
|
||||
|
||||
Reference in New Issue
Block a user