505 lines
26 KiB
HTML
505 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>{% if post_id %}Редактирование поста{% else %}Добавление поста{% endif %}</title>
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
<style>
|
||
.form-container {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
}
|
||
.preview-image {
|
||
max-width: 200px;
|
||
max-height: 200px;
|
||
object-fit: contain;
|
||
}
|
||
.required:after {
|
||
content: " *";
|
||
color: red;
|
||
}
|
||
.toast-container {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 1050;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container py-4">
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<h2>{% if post_id %}Редактирование поста{% else %}Добавление поста{% endif %}</h2>
|
||
<a href="#" id="back-button" class="btn btn-outline-secondary">
|
||
<i class="fas fa-arrow-left me-1"></i> Назад
|
||
</a>
|
||
</div>
|
||
|
||
<div class="card form-container">
|
||
<div class="card-body">
|
||
<form id="post-form">
|
||
<input type="hidden" id="post-id" value="{{ post_id if post_id else '' }}">
|
||
|
||
<!-- Заголовок и поле Название в одну строку -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-2">
|
||
<label for="shortname" class="form-label">Название:</label>
|
||
</div>
|
||
<div class="col-md-10">
|
||
<input type="text" class="form-control" id="shortname" maxlength="100">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Секция с площадкой слева и изображением справа -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-6">
|
||
<div class="row">
|
||
<div class="col-md-3">
|
||
<label for="auto_unit" class="form-label">Площадка:</label>
|
||
</div>
|
||
<div class="col-md-9">
|
||
<select class="form-control" id="auto_unit">
|
||
<option value="">Выберите площадку</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 text-end">
|
||
<div id="image-preview-container" style="display: none;">
|
||
<img id="image-preview" class="preview-image img-thumbnail" style="max-width: 200px; max-height: 200px; object-fit: contain;">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- VK ID слева от изображения -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-6">
|
||
<div class="row">
|
||
<div class="col-md-3">
|
||
<label for="vk_post_id" class="form-label">VK ID:</label>
|
||
</div>
|
||
<div class="col-md-9">
|
||
<input type="text" class="form-control" id="vk_post_id" required>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Переключатели -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-3">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="is_event">
|
||
<label class="form-check-label" for="is_event">Событие</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="is_poll">
|
||
<label class="form-check-label" for="is_poll">Опрос</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="marked_for_publication">
|
||
<label class="form-check-label" for="marked_for_publication">К публикации</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="published_in_tg">
|
||
<label class="form-check-label" for="published_in_tg">Опубликовано в TG</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Текстовое поле поста -->
|
||
<div class="mb-3">
|
||
<label for="text" class="form-label required">Текст поста</label>
|
||
<textarea class="form-control" id="text" rows="4" required></textarea>
|
||
</div>
|
||
|
||
<!-- URL изображения -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-2">
|
||
<label for="image_url" class="form-label">Картинка:</label>
|
||
</div>
|
||
<div class="col-md-10">
|
||
<input type="url" class="form-control" id="image_url">
|
||
<div class="form-text">Ссылка на изображение для поста</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- URL поста ВК с датой -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-2">
|
||
<label for="vk_post_url" class="form-label">Пост в ВК:</label>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<input type="url" class="form-control" id="vk_post_url">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<label for="published_at" class="form-label">датирован:</label>
|
||
</div>
|
||
<div class="col-md-2">
|
||
<input type="datetime-local" class="form-control" id="published_at">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Дата публикации в TG -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-4">
|
||
<label for="tg_publication_date" class="form-label">Опубликовано в TG:</label>
|
||
</div>
|
||
<div class="col-md-8">
|
||
<input type="datetime-local" class="form-control" id="tg_publication_date">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ID TG в одну строку -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-2">
|
||
<label for="tg_message_id" class="form-label">ID TG:</label>
|
||
</div>
|
||
<div class="col-md-2">
|
||
<input type="number" class="form-control" id="tg_message_id">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<label for="tg_poll_id" class="form-label">ID опроса TG:</label>
|
||
</div>
|
||
<div class="col-md-2">
|
||
<input type="number" class="form-control" id="tg_poll_id">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<label for="tg_markpost_id" class="form-label">ID отметок TG:</label>
|
||
</div>
|
||
<div class="col-md-2">
|
||
<input type="number" class="form-control" id="tg_markpost_id">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Блок параметров опроса -->
|
||
<div id="poll-fields" style="display: none;">
|
||
<div class="border-top mt-4 pt-3">
|
||
<h5>Параметры опроса</h5>
|
||
|
||
<div class="mb-3">
|
||
<label for="poll_question" class="form-label">Вопрос опроса</label>
|
||
<input type="text" class="form-control" id="poll_question">
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="poll_options" class="form-label">Варианты ответов (JSON)</label>
|
||
<textarea class="form-control" id="poll_options" rows="2"></textarea>
|
||
<div class="form-text">Формат: ["Вариант 1", "Вариант 2", ...]</div>
|
||
</div>
|
||
|
||
<div class="mb-3 form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="poll_multiple">
|
||
<label class="form-check-label" for="poll_multiple">Множественный выбор</label>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="poll_end_date" class="form-label">Дата окончания опроса</label>
|
||
<input type="datetime-local" class="form-control" id="poll_end_date">
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="tg_poll_results" class="form-label">Результаты опроса</label>
|
||
<textarea class="form-control" id="tg_poll_results" rows="4"></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-flex justify-content-end mt-4">
|
||
<button type="button" class="btn btn-secondary me-2" id="cancel-btn">Отмена</button>
|
||
<button type="submit" class="btn btn-primary">
|
||
{% if post_id %}Обновить{% else %}Создать{% endif %}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Контейнер для уведомлений -->
|
||
<div class="toast-container">
|
||
<div id="success-toast" class="toast align-items-center text-white bg-success border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||
<div class="d-flex">
|
||
<div class="toast-body">
|
||
<i class="fas fa-check-circle me-2"></i> Пост успешно сохранен!
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script>
|
||
const PREFIX = "{{ g.prefix }}";
|
||
console.log("Editor Prefix is:", PREFIX); // Должно выводить "/zilant"
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const postId = document.getElementById('post-id').value;
|
||
const form = document.getElementById('post-form');
|
||
const isPollCheckbox = document.getElementById('is_poll');
|
||
const pollFields = document.getElementById('poll-fields');
|
||
const imageUrlInput = document.getElementById('image_url');
|
||
const imagePreviewContainer = document.getElementById('image-preview-container');
|
||
const imagePreview = document.getElementById('image-preview');
|
||
const successToast = new bootstrap.Toast(document.getElementById('success-toast'));
|
||
|
||
// Обработчики кнопок отмены и назад
|
||
const cancelHandler = () => {
|
||
if (postId) {
|
||
window.location.href = `${PREFIX}/?selected_post=${postId}&tab=posts`;
|
||
} else {
|
||
window.location.href = `${PREFIX}/?tab=posts`;
|
||
}
|
||
};
|
||
|
||
document.getElementById('cancel-btn').addEventListener('click', cancelHandler);
|
||
document.getElementById('back-button').addEventListener('click', cancelHandler);
|
||
|
||
// Показать/скрыть поля опроса
|
||
isPollCheckbox.addEventListener('change', () => {
|
||
pollFields.style.display = isPollCheckbox.checked ? 'block' : 'none';
|
||
});
|
||
|
||
// Предпросмотр изображения
|
||
imageUrlInput.addEventListener('change', () => {
|
||
const url = imageUrlInput.value.trim();
|
||
if (url) {
|
||
imagePreview.src = `${PREFIX}/image_proxy?url=${encodeURIComponent(url)}`;
|
||
imagePreviewContainer.style.display = 'block';
|
||
} else {
|
||
imagePreviewContainer.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
// Валидация поля названия
|
||
const shortnameInput = document.getElementById('shortname');
|
||
shortnameInput.addEventListener('input', () => {
|
||
const value = shortnameInput.value;
|
||
if (value.length > 100) {
|
||
alert('Название не может превышать 100 символов');
|
||
shortnameInput.value = value.substring(0, 100);
|
||
}
|
||
});
|
||
|
||
// Загрузка списка площадок
|
||
loadVenues();
|
||
|
||
// Загрузка данных поста (если редактирование)
|
||
if (postId) {
|
||
loadPostData(postId);
|
||
}
|
||
|
||
// Обработка отправки формы
|
||
form.addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
savePost();
|
||
});
|
||
});
|
||
|
||
// Загрузка списка площадок
|
||
async function loadVenues() {
|
||
try {
|
||
const response = await fetch(`${PREFIX}/api/venues`);
|
||
if (!response.ok) {
|
||
throw new Error('Ошибка загрузки списка площадок');
|
||
}
|
||
const venues = await response.json();
|
||
|
||
const select = document.getElementById('auto_unit');
|
||
// Очищаем список, оставляя только первую опцию
|
||
select.innerHTML = '<option value="">Выберите площадку</option>';
|
||
|
||
// Добавляем площадки
|
||
venues.forEach(venue => {
|
||
const option = document.createElement('option');
|
||
option.value = venue.name;
|
||
option.textContent = venue.name;
|
||
select.appendChild(option);
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки площадок:', error);
|
||
}
|
||
}
|
||
|
||
// Загрузка данных поста
|
||
async function loadPostData(postId) {
|
||
try {
|
||
const response = await fetch(`${PREFIX}/api/posts/${postId}`);
|
||
if (!response.ok) {
|
||
throw new Error('Пост не найден');
|
||
}
|
||
|
||
const post = await response.json();
|
||
|
||
// Заполняем форму
|
||
document.getElementById('vk_post_id').value = post.vk_post_id || '';
|
||
document.getElementById('shortname').value = post.shortname || '';
|
||
document.getElementById('auto_unit').value = post.auto_unit || '';
|
||
document.getElementById('text').value = post.text || '';
|
||
document.getElementById('image_url').value = post.image_url || '';
|
||
document.getElementById('vk_post_url').value = post.vk_post_url || '';
|
||
|
||
// Форматирование дат для input[type=datetime-local]
|
||
// Конвертация UTC в локальное время с округлением до минут
|
||
const formatForInput = (dateStr) => {
|
||
if (!dateStr) return '';
|
||
const date = new Date(dateStr);
|
||
// Округляем до минут (убираем секунды и миллисекунды)
|
||
date.setSeconds(0, 0);
|
||
// Возвращаем в локальном времени в формате YYYY-MM-DDTHH:MM
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const hours = String(date.getHours()).padStart(2, '0');
|
||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
||
};
|
||
|
||
document.getElementById('published_at').value = formatForInput(post.published_at);
|
||
document.getElementById('is_event').checked = post.is_event; // Новое поле
|
||
document.getElementById('is_poll').checked = post.is_poll;
|
||
document.getElementById('poll_question').value = post.poll_question || '';
|
||
document.getElementById('poll_options').value = post.poll_options || '';
|
||
document.getElementById('poll_multiple').checked = post.poll_multiple;
|
||
document.getElementById('poll_end_date').value = formatForInput(post.poll_end_date);
|
||
|
||
document.getElementById('marked_for_publication').checked = post.marked_for_publication;
|
||
document.getElementById('published_in_tg').checked = post.published_in_tg;
|
||
document.getElementById('tg_publication_date').value = formatForInput(post.tg_publication_date);
|
||
document.getElementById('tg_message_id').value = post.tg_message_id || '';
|
||
document.getElementById('tg_poll_id').value = post.tg_poll_id || '';
|
||
document.getElementById('tg_markpost_id').value = post.tg_markpost_id || '';
|
||
document.getElementById('tg_poll_results').value = post.tg_poll_results || '';
|
||
|
||
// Показываем поля опроса, если нужно
|
||
if (post.is_poll) {
|
||
document.getElementById('poll-fields').style.display = 'block';
|
||
}
|
||
|
||
// Показываем изображение, если есть
|
||
if (post.image_url) {
|
||
document.getElementById('image-preview').src =
|
||
`${PREFIX}/image_proxy?url=${encodeURIComponent(post.image_url)}`;
|
||
document.getElementById('image-preview-container').style.display = 'block';
|
||
}
|
||
|
||
} catch (error) {
|
||
alert(`Ошибка загрузки данных: ${error.message}`);
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
// Функция конвертации локального времени в UTC для отправки на сервер
|
||
const convertLocalToUTC = (localString) => {
|
||
if (!localString) return null;
|
||
const date = new Date(localString);
|
||
// Округляем до минут
|
||
date.setSeconds(0, 0);
|
||
// Возвращаем в UTC формате для сервера
|
||
return date.toISOString().slice(0, 19).replace('T', ' ');
|
||
};
|
||
|
||
// Сохранение поста
|
||
async function savePost() {
|
||
const postId = document.getElementById('post-id').value;
|
||
const form = document.getElementById('post-form');
|
||
const submitBtn = form.querySelector('button[type="submit"]');
|
||
const originalBtnText = submitBtn.innerHTML;
|
||
const isNewPost = !postId;
|
||
|
||
// Показываем индикатор загрузки
|
||
submitBtn.innerHTML = `
|
||
<span class="spinner-border spinner-border-sm" role="status"></span>
|
||
Сохранение...
|
||
`;
|
||
submitBtn.disabled = true;
|
||
|
||
try {
|
||
// Собираем данные формы
|
||
const formData = {
|
||
vk_post_id: document.getElementById('vk_post_id').value,
|
||
shortname: document.getElementById('shortname').value,
|
||
auto_unit: document.getElementById('auto_unit').value,
|
||
text: document.getElementById('text').value,
|
||
image_url: document.getElementById('image_url').value,
|
||
vk_post_url: document.getElementById('vk_post_url').value,
|
||
published_at: convertLocalToUTC(document.getElementById('published_at').value),
|
||
is_event: document.getElementById('is_event').checked, // Новое поле
|
||
is_poll: document.getElementById('is_poll').checked,
|
||
poll_question: document.getElementById('poll_question').value,
|
||
poll_options: document.getElementById('poll_options').value,
|
||
poll_multiple: document.getElementById('poll_multiple').checked,
|
||
poll_end_date: convertLocalToUTC(document.getElementById('poll_end_date').value),
|
||
marked_for_publication: document.getElementById('marked_for_publication').checked,
|
||
published_in_tg: document.getElementById('published_in_tg').checked,
|
||
tg_publication_date: convertLocalToUTC(document.getElementById('tg_publication_date').value),
|
||
tg_message_id: document.getElementById('tg_message_id').value,
|
||
tg_poll_id: document.getElementById('tg_poll_id').value,
|
||
tg_markpost_id: document.getElementById('tg_markpost_id').value,
|
||
tg_poll_results: document.getElementById('tg_poll_results').value
|
||
};
|
||
|
||
// Валидация
|
||
if (!formData.vk_post_id) {
|
||
throw new Error('VK ID поста обязателен для заполнения');
|
||
}
|
||
|
||
let response;
|
||
let result;
|
||
if (postId) {
|
||
// Обновление существующего поста
|
||
response = await fetch(`${PREFIX}/api/posts/${postId}`, {
|
||
method: 'PUT',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(formData)
|
||
});
|
||
result = await response.json();
|
||
} else {
|
||
// Создание нового поста
|
||
response = await fetch(`${PREFIX}/api/posts`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(formData)
|
||
});
|
||
result = await response.json();
|
||
}
|
||
|
||
if (!response.ok) {
|
||
throw new Error(result.error || 'Ошибка сохранения');
|
||
}
|
||
|
||
// Показываем уведомление об успехе
|
||
const successToast = new bootstrap.Toast(document.getElementById('success-toast'));
|
||
successToast.show();
|
||
|
||
// Ждем 1.5 секунды перед перенаправлением
|
||
setTimeout(() => {
|
||
// Перенаправляем на главную страницу с выбранным постом
|
||
window.location.href = `${PREFIX}/?selected_post=${postId || result.post_id}&tab=posts`;
|
||
}, 1500);
|
||
|
||
} catch (error) {
|
||
alert(`Ошибка: ${error.message}`);
|
||
console.error(error);
|
||
} finally {
|
||
// Восстанавливаем кнопку
|
||
submitBtn.innerHTML = originalBtnText;
|
||
submitBtn.disabled = false;
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|