Files
Zilant2025/templates/edit_post.html
T
gitadmin 2df357a8a0
ci/woodpecker/push/woodpecker Pipeline was successful
Ред пост 4
2025-10-12 19:31:05 +03:00

511 lines
26 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>{% 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>
<!-- Секция с площадкой и VK ID слева, изображением справа -->
<div class="row mb-3">
<div class="col-md-6">
<!-- Площадка -->
<div class="row mb-2">
<div class="col-md-2">
<label for="auto_unit" class="form-label">Площадка:</label>
</div>
<div class="col-md-10">
<select class="form-control" id="auto_unit">
<option value="">Выберите площадку</option>
</select>
</div>
</div>
<!-- VK ID -->
<div class="row">
<div class="col-md-2">
<label for="vk_post_id" class="form-label">VK ID:</label>
</div>
<div class="col-md-4">
<input type="text" class="form-control" id="vk_post_id" maxlength="8" required>
</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>
<!-- Переключатели -->
<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);
}
});
// Валидация поля VK ID
const vkPostIdInput = document.getElementById('vk_post_id');
vkPostIdInput.addEventListener('input', () => {
const value = vkPostIdInput.value;
if (value.length > 8) {
alert('VK ID не может превышать 8 символов');
vkPostIdInput.value = value.substring(0, 8);
}
});
// Загрузка списка площадок
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>