Problem: - Only cover image was being saved when creating posts - Section images were shown in preview but not sent to backend - File inputs were not preserved after section saving Solution: - Store section files in global JavaScript storage - Send all section images in FormData during form submission - Update backend to process section_image_* files - Add is_cover flag distinction between cover and section images - Preserve file references throughout the editing process Features: - Multiple images per post section now work correctly - Cover image marked with is_cover=True - Section images marked with is_cover=False - Proper file cleanup when images are removed - Enhanced logging for debugging image uploads
860 lines
39 KiB
HTML
860 lines
39 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Share Your Adventure - Create New Post{% endblock %}
|
|
|
|
{% block head %}
|
|
<style>
|
|
.content-section {
|
|
border: 2px dashed #d1d5db;
|
|
border-radius: 0.75rem;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
transition: all 0.3s ease;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.content-section.editing {
|
|
border-color: #3b82f6;
|
|
background: rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.content-section.saved {
|
|
border-color: #10b981;
|
|
border-style: solid;
|
|
background: rgba(16, 185, 129, 0.1);
|
|
}
|
|
|
|
.highlight-input {
|
|
background: linear-gradient(135deg, #fbbf24, #f59e0b);
|
|
color: white;
|
|
font-weight: 600;
|
|
border: none;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 0.375rem;
|
|
margin: 0 0.25rem;
|
|
}
|
|
|
|
.highlight-display {
|
|
background: linear-gradient(135deg, #fbbf24, #f59e0b);
|
|
color: white;
|
|
font-weight: 600;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 0.375rem;
|
|
margin: 0 0.25rem;
|
|
display: inline-block;
|
|
}
|
|
|
|
.image-preview {
|
|
position: relative;
|
|
display: inline-block;
|
|
margin: 0.5rem;
|
|
}
|
|
|
|
.image-preview img {
|
|
width: 150px;
|
|
height: 100px;
|
|
object-fit: cover;
|
|
border-radius: 0.5rem;
|
|
border: 2px solid #e5e7eb;
|
|
}
|
|
|
|
.image-preview .remove-btn {
|
|
position: absolute;
|
|
top: -8px;
|
|
right: -8px;
|
|
background: #ef4444;
|
|
color: white;
|
|
border-radius: 50%;
|
|
width: 24px;
|
|
height: 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* Cover upload styles */
|
|
.cover-upload-area {
|
|
transition: all 0.3s ease;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cover-upload-area:hover {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
/* Dropdown styling */
|
|
select option {
|
|
background-color: #1f2937;
|
|
color: #ffffff;
|
|
}
|
|
|
|
select option:checked {
|
|
background-color: #0891b2;
|
|
}
|
|
|
|
/* Section styling */
|
|
.content-section {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 0.75rem;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.section-actions-frame {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.add-section-frame {
|
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(147, 51, 234, 0.1));
|
|
border: 1px solid rgba(59, 130, 246, 0.3);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-teal-900 py-12">
|
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<!-- Header -->
|
|
<div class="text-center mb-8">
|
|
<h1 class="text-4xl font-bold text-white mb-4">
|
|
🏍️ Share Your Adventure
|
|
</h1>
|
|
<p class="text-blue-200 text-lg">
|
|
Create a detailed story of your motorcycle journey through Romania
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Main Form -->
|
|
<form id="adventure-form" method="POST" action="{{ url_for('community.new_post') }}" enctype="multipart/form-data" class="space-y-6">
|
|
<!-- Basic Information Section -->
|
|
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-white/20">
|
|
<h2 class="text-2xl font-bold text-white mb-6">📝 Basic Information</h2>
|
|
|
|
<!-- Cover Picture -->
|
|
<div class="mb-6">
|
|
<label for="cover_picture" class="block text-white font-semibold mb-2">Set Cover Picture for the Post</label>
|
|
<div class="cover-upload-area border-2 border-dashed border-white/30 rounded-lg p-6 text-center hover:border-white/50 transition-all duration-300">
|
|
<input type="file" id="cover_picture" name="cover_picture" accept="image/*" class="hidden">
|
|
<div class="cover-upload-content">
|
|
<div class="text-4xl mb-2">📸</div>
|
|
<p class="text-white/80 mb-2">Click to upload cover image</p>
|
|
<p class="text-sm text-white/60">Recommended: 1920x1080 pixels</p>
|
|
</div>
|
|
<div class="cover-preview hidden">
|
|
<img class="cover-preview-image max-h-48 mx-auto rounded-lg" alt="Cover preview">
|
|
<button type="button" class="cover-remove-btn mt-2 px-3 py-1 bg-red-500/80 text-white rounded hover:bg-red-600 transition-colors">Remove</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Title -->
|
|
<div class="mb-6">
|
|
<label for="title" class="block text-white font-semibold mb-2">Adventure Title *</label>
|
|
<input type="text" id="title" name="title" required
|
|
class="w-full px-4 py-3 rounded-lg bg-white/10 backdrop-blur-sm border border-white/20
|
|
text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-cyan-400
|
|
focus:border-transparent transition-all duration-300"
|
|
placeholder="Give your adventure a captivating title...">
|
|
</div>
|
|
|
|
<!-- Subtitle -->
|
|
<div class="mb-6">
|
|
<label for="subtitle" class="block text-white font-semibold mb-2">Subtitle</label>
|
|
<input type="text" id="subtitle" name="subtitle"
|
|
class="w-full px-4 py-3 rounded-lg bg-white/10 backdrop-blur-sm border border-white/20
|
|
text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-cyan-400
|
|
focus:border-transparent transition-all duration-300"
|
|
placeholder="A brief description of your adventure">
|
|
</div>
|
|
|
|
<!-- Difficulty Rating -->
|
|
<div class="mb-6">
|
|
<label for="difficulty" class="block text-white font-semibold mb-2">Route Difficulty *</label>
|
|
<select id="difficulty" name="difficulty" required
|
|
class="w-full px-4 py-3 rounded-lg bg-white/10 backdrop-blur-sm border border-white/20
|
|
text-white focus:outline-none focus:ring-2 focus:ring-cyan-400
|
|
focus:border-transparent transition-all duration-300">
|
|
<option value="" class="bg-gray-800 text-gray-300">Select difficulty level...</option>
|
|
<option value="1" class="bg-gray-800 text-green-400">🟢 Easy - Beginner friendly roads</option>
|
|
<option value="2" class="bg-gray-800 text-yellow-400">🟡 Moderate - Some experience needed</option>
|
|
<option value="3" class="bg-gray-800 text-orange-400">🟠 Challenging - Experienced riders</option>
|
|
<option value="4" class="bg-gray-800 text-red-400">🔴 Difficult - Advanced skills required</option>
|
|
<option value="5" class="bg-gray-800 text-purple-400">🟣 Expert - Only for experts</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content Sections -->
|
|
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-white/20">
|
|
<h2 class="text-2xl font-bold text-white mb-6">📖 Adventure Story</h2>
|
|
<div id="content-sections">
|
|
<!-- Initial content section will be added by JavaScript -->
|
|
</div>
|
|
|
|
<!-- Add New Section Frame -->
|
|
<div class="add-section-frame bg-gradient-to-r from-blue-500/10 to-purple-500/10 border border-blue-400/30 rounded-lg p-6 text-center mt-6">
|
|
<div class="mb-4">
|
|
<h3 class="text-lg font-semibold text-blue-300 mb-2">✨ Expand Your Story</h3>
|
|
<p class="text-white/80 text-sm">Add another section with new text, pictures, and highlights to make your adventure more detailed and engaging</p>
|
|
</div>
|
|
<button type="button" id="add-section-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg transition-all duration-300 transform hover:scale-105 inline-flex items-center">
|
|
<i class="fas fa-plus mr-2"></i>
|
|
Add New Section
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- GPX Upload -->
|
|
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-white/20">
|
|
<h2 class="text-2xl font-bold text-white mb-6">🗺️ Route File</h2>
|
|
<div class="border-2 border-dashed border-white/30 rounded-lg p-8 text-center">
|
|
<i class="fas fa-route text-4xl text-blue-300 mb-4"></i>
|
|
<div class="text-white font-semibold mb-2">Upload GPX Route File</div>
|
|
<div class="text-blue-200 text-sm mb-4">
|
|
Share your exact route so others can follow your adventure
|
|
</div>
|
|
<input type="file" id="gpx-file" name="gpx_file" accept=".gpx" class="hidden">
|
|
<label for="gpx-file" class="inline-flex items-center px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 cursor-pointer transition">
|
|
<i class="fas fa-upload mr-2"></i>
|
|
Choose GPX File
|
|
</label>
|
|
<div id="gpx-filename" class="mt-4 text-green-300 hidden"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit -->
|
|
<div class="text-center space-x-4">
|
|
<button type="button" onclick="previewPost()" class="bg-gradient-to-r from-blue-500 to-indigo-600 text-white px-8 py-4 rounded-lg font-bold text-lg hover:from-blue-600 hover:to-indigo-700 transform hover:scale-105 transition-all duration-200 shadow-lg">
|
|
<i class="fas fa-eye mr-3"></i>
|
|
Preview Adventure
|
|
</button>
|
|
<button type="submit" class="bg-gradient-to-r from-orange-500 to-red-600 text-white px-12 py-4 rounded-lg font-bold text-lg hover:from-orange-600 hover:to-red-700 transform hover:scale-105 transition-all duration-200 shadow-lg">
|
|
<i class="fas fa-paper-plane mr-3"></i>
|
|
Share Adventure
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Global storage for section files
|
|
window.sectionFiles = {};
|
|
let sectionCounter = 0;
|
|
|
|
// Populate form from URL parameters
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
if (urlParams.get('title')) {
|
|
document.getElementById('title').value = urlParams.get('title');
|
|
}
|
|
if (urlParams.get('subtitle')) {
|
|
document.getElementById('subtitle').value = urlParams.get('subtitle');
|
|
}
|
|
if (urlParams.get('difficulty')) {
|
|
document.getElementById('difficulty').value = urlParams.get('difficulty');
|
|
}
|
|
if (urlParams.get('cover_picture')) {
|
|
// Note: This would be the filename, but we can't pre-populate file inputs for security reasons
|
|
// We'll show a message instead
|
|
const coverUploadArea = document.querySelector('.cover-upload-area');
|
|
const coverContent = document.querySelector('.cover-upload-content');
|
|
coverContent.innerHTML = `
|
|
<div class="text-4xl mb-2">📸</div>
|
|
<p class="text-yellow-300 mb-2">Cover image suggested: ${urlParams.get('cover_picture')}</p>
|
|
<p class="text-white/80 mb-2">Click to upload this or another cover image</p>
|
|
<p class="text-sm text-white/60">Recommended: 1920x1080 pixels</p>
|
|
`;
|
|
}
|
|
if (urlParams.get('gpx_file')) {
|
|
// Similar note for GPX file
|
|
const gpxLabel = document.querySelector('label[for="gpx-file"]');
|
|
gpxLabel.innerHTML = `
|
|
<i class="fas fa-upload mr-2"></i>
|
|
Choose GPX File (suggested: ${urlParams.get('gpx_file')})
|
|
`;
|
|
}
|
|
|
|
// Cover picture upload
|
|
const coverUploadArea = document.querySelector('.cover-upload-area');
|
|
const coverInput = document.getElementById('cover_picture');
|
|
const coverContent = document.querySelector('.cover-upload-content');
|
|
const coverPreview = document.querySelector('.cover-preview');
|
|
const coverImage = document.querySelector('.cover-preview-image');
|
|
const coverRemoveBtn = document.querySelector('.cover-remove-btn');
|
|
|
|
coverUploadArea.addEventListener('click', () => coverInput.click());
|
|
|
|
coverInput.addEventListener('change', function() {
|
|
const file = this.files[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
coverImage.src = e.target.result;
|
|
coverContent.style.display = 'none';
|
|
coverPreview.classList.remove('hidden');
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
});
|
|
|
|
coverRemoveBtn.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
coverInput.value = '';
|
|
coverContent.style.display = 'block';
|
|
coverPreview.classList.add('hidden');
|
|
});
|
|
|
|
// GPX file input
|
|
document.getElementById('gpx-file').addEventListener('change', function() {
|
|
const filename = this.files[0]?.name;
|
|
const filenameDiv = document.getElementById('gpx-filename');
|
|
if (filename) {
|
|
filenameDiv.textContent = `Selected: ${filename}`;
|
|
filenameDiv.classList.remove('hidden');
|
|
} else {
|
|
filenameDiv.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// Add initial content section
|
|
addContentSection();
|
|
|
|
// Add section button
|
|
document.getElementById('add-section-btn').addEventListener('click', addContentSection);
|
|
|
|
function addContentSection() {
|
|
sectionCounter++;
|
|
const sectionsContainer = document.getElementById('content-sections');
|
|
|
|
const section = document.createElement('div');
|
|
section.className = 'content-section editing';
|
|
section.dataset.sectionId = sectionCounter;
|
|
|
|
section.innerHTML = `
|
|
<div class="section-header mb-4">
|
|
<h3 class="text-lg font-semibold text-white">Section ${sectionCounter}</h3>
|
|
</div>
|
|
|
|
<div class="section-content">
|
|
<!-- Highlights Input -->
|
|
<div class="mb-4">
|
|
<label class="block text-white font-semibold mb-2">Key Highlights (1-5 words)</label>
|
|
<div class="highlights-container mb-2">
|
|
<input type="text" class="highlight-input bg-white/10 backdrop-blur-sm border border-white/20 text-white placeholder-white/60 px-3 py-2 rounded mr-2" placeholder="Highlight 1" maxlength="20">
|
|
<input type="text" class="highlight-input bg-white/10 backdrop-blur-sm border border-white/20 text-white placeholder-white/60 px-3 py-2 rounded mr-2" placeholder="Highlight 2" maxlength="20">
|
|
<input type="text" class="highlight-input bg-white/10 backdrop-blur-sm border border-white/20 text-white placeholder-white/60 px-3 py-2 rounded mr-2" placeholder="Highlight 3" maxlength="20">
|
|
<button type="button" class="add-highlight-btn text-white bg-blue-600 px-3 py-1 rounded ml-2 hover:bg-blue-700 transition">
|
|
<i class="fas fa-plus"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Text Content -->
|
|
<div class="mb-4">
|
|
<label class="block text-white font-semibold mb-2">Section Content</label>
|
|
<textarea class="section-text w-full px-4 py-3 rounded-lg bg-white/10 backdrop-blur-sm border border-white/20 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-cyan-400 focus:border-transparent"
|
|
rows="6" placeholder="Describe this part of your adventure..."></textarea>
|
|
</div>
|
|
|
|
<!-- Image Upload -->
|
|
<div class="mb-6">
|
|
<label class="block text-white font-semibold mb-2">Photos</label>
|
|
<div class="image-upload-area border-2 border-dashed border-white/30 rounded-lg p-4 text-center hover:border-white/50 transition-all duration-300">
|
|
<input type="file" class="section-images" multiple accept="image/*" style="display: none;">
|
|
<button type="button" class="upload-images-btn bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition">
|
|
<i class="fas fa-camera mr-2"></i> Add Photos
|
|
</button>
|
|
<div class="images-preview mt-4"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section Actions Frame -->
|
|
<div class="section-actions-frame bg-white/5 border border-white/20 rounded-lg p-4 mb-4">
|
|
<div class="text-center mb-3">
|
|
<p class="text-yellow-300 font-medium">💡 Section Actions</p>
|
|
<p class="text-white/70 text-sm">Save your content, delete this section, or edit after saving</p>
|
|
</div>
|
|
<div class="section-actions flex justify-center space-x-3">
|
|
<button type="button" class="save-section-btn bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 transition">
|
|
<i class="fas fa-save mr-1"></i> Save
|
|
</button>
|
|
<button type="button" class="delete-section-btn bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition">
|
|
<i class="fas fa-trash mr-1"></i> Delete
|
|
</button>
|
|
<button type="button" class="edit-section-btn bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition hidden">
|
|
<i class="fas fa-edit mr-1"></i> Edit
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Saved Content Display (hidden initially) -->
|
|
<div class="saved-content hidden">
|
|
<div class="saved-highlights mb-4"></div>
|
|
<div class="saved-text mb-4 text-white"></div>
|
|
<div class="saved-images"></div>
|
|
</div>
|
|
`;
|
|
|
|
sectionsContainer.appendChild(section);
|
|
|
|
// Add event listeners for this section
|
|
setupSectionEventListeners(section);
|
|
}
|
|
|
|
function setupSectionEventListeners(section) {
|
|
const sectionId = section.dataset.sectionId;
|
|
|
|
// Image upload
|
|
const uploadBtn = section.querySelector('.upload-images-btn');
|
|
const imageInput = section.querySelector('.section-images');
|
|
const imagesPreview = section.querySelector('.images-preview');
|
|
|
|
uploadBtn.addEventListener('click', () => imageInput.click());
|
|
|
|
imageInput.addEventListener('change', function() {
|
|
const files = Array.from(this.files);
|
|
|
|
// Store files in global storage for this section
|
|
if (!window.sectionFiles[sectionId]) {
|
|
window.sectionFiles[sectionId] = [];
|
|
}
|
|
|
|
files.forEach(file => {
|
|
if (file.type.startsWith('image/')) {
|
|
// Add to global storage
|
|
window.sectionFiles[sectionId].push(file);
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
const imagePreview = document.createElement('div');
|
|
imagePreview.className = 'image-preview';
|
|
imagePreview.dataset.fileIndex = window.sectionFiles[sectionId].length - 1;
|
|
imagePreview.innerHTML = `
|
|
<img src="${e.target.result}" alt="Preview">
|
|
<div class="remove-btn">×</div>
|
|
`;
|
|
|
|
imagePreview.querySelector('.remove-btn').addEventListener('click', function() {
|
|
// Remove from global storage
|
|
const fileIndex = parseInt(imagePreview.dataset.fileIndex);
|
|
window.sectionFiles[sectionId].splice(fileIndex, 1);
|
|
|
|
// Update indices for remaining previews
|
|
const remainingPreviews = imagesPreview.querySelectorAll('.image-preview');
|
|
remainingPreviews.forEach((preview, index) => {
|
|
if (parseInt(preview.dataset.fileIndex) > fileIndex) {
|
|
preview.dataset.fileIndex = parseInt(preview.dataset.fileIndex) - 1;
|
|
}
|
|
});
|
|
|
|
imagePreview.remove();
|
|
});
|
|
|
|
imagesPreview.appendChild(imagePreview);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add highlight button
|
|
section.querySelector('.add-highlight-btn').addEventListener('click', function() {
|
|
const highlightsContainer = section.querySelector('.highlights-container');
|
|
const newHighlight = document.createElement('input');
|
|
newHighlight.type = 'text';
|
|
newHighlight.className = 'highlight-input';
|
|
newHighlight.placeholder = 'New highlight';
|
|
newHighlight.maxLength = 20;
|
|
highlightsContainer.insertBefore(newHighlight, this);
|
|
});
|
|
|
|
// Save section
|
|
section.querySelector('.save-section-btn').addEventListener('click', function() {
|
|
saveSection(section);
|
|
});
|
|
|
|
// Delete section
|
|
section.querySelector('.delete-section-btn').addEventListener('click', function() {
|
|
if (confirm('Are you sure you want to delete this section?')) {
|
|
section.remove();
|
|
}
|
|
});
|
|
|
|
// Edit section
|
|
section.querySelector('.edit-section-btn').addEventListener('click', function() {
|
|
editSection(section);
|
|
});
|
|
}
|
|
|
|
function saveSection(section) {
|
|
const highlights = Array.from(section.querySelectorAll('.highlight-input'))
|
|
.map(input => input.value.trim())
|
|
.filter(value => value);
|
|
|
|
const text = section.querySelector('.section-text').value.trim();
|
|
const images = section.querySelectorAll('.images-preview img');
|
|
|
|
if (!text && highlights.length === 0 && images.length === 0) {
|
|
alert('Please add some content to this section before saving.');
|
|
return;
|
|
}
|
|
|
|
// Show saved content
|
|
const savedContent = section.querySelector('.saved-content');
|
|
const savedHighlights = section.querySelector('.saved-highlights');
|
|
const savedText = section.querySelector('.saved-text');
|
|
const savedImages = section.querySelector('.saved-images');
|
|
|
|
// Display highlights
|
|
savedHighlights.innerHTML = highlights.map(h =>
|
|
`<span class="highlight-display bg-cyan-600/20 text-cyan-300 px-2 py-1 rounded mr-2">${h}</span>`
|
|
).join('');
|
|
|
|
// Display text
|
|
savedText.innerHTML = text.replace(/\n/g, '<br>');
|
|
|
|
// Display images
|
|
savedImages.innerHTML = '';
|
|
images.forEach(img => {
|
|
const imgCopy = img.cloneNode();
|
|
imgCopy.className = 'w-32 h-24 object-cover rounded m-1';
|
|
savedImages.appendChild(imgCopy);
|
|
});
|
|
|
|
// Hide editing interface and show saved content
|
|
section.querySelector('.section-content').style.display = 'none';
|
|
savedContent.classList.remove('hidden');
|
|
|
|
// Update button visibility in actions frame
|
|
section.querySelector('.save-section-btn').style.display = 'none';
|
|
section.querySelector('.delete-section-btn').style.display = 'none';
|
|
section.querySelector('.edit-section-btn').style.display = 'inline-flex';
|
|
|
|
// Update section appearance
|
|
section.classList.remove('editing');
|
|
section.classList.add('saved');
|
|
|
|
// Update header
|
|
section.querySelector('.section-header h3').textContent = `Section ${section.dataset.sectionId} ✓`;
|
|
|
|
// Update actions frame text
|
|
const actionsFrame = section.querySelector('.section-actions-frame');
|
|
actionsFrame.querySelector('p:first-child').innerHTML = '✅ <span class="text-green-300">Section Saved</span>';
|
|
actionsFrame.querySelector('p:last-child').textContent = 'Your content has been saved. You can edit it again if needed.';
|
|
}
|
|
|
|
function editSection(section) {
|
|
// Show editing interface and hide saved content
|
|
section.querySelector('.section-content').style.display = 'block';
|
|
section.querySelector('.saved-content').classList.add('hidden');
|
|
|
|
// Update button visibility in actions frame
|
|
section.querySelector('.save-section-btn').style.display = 'inline-flex';
|
|
section.querySelector('.delete-section-btn').style.display = 'inline-flex';
|
|
section.querySelector('.edit-section-btn').style.display = 'none';
|
|
|
|
// Update section appearance
|
|
section.classList.remove('saved');
|
|
section.classList.add('editing');
|
|
|
|
// Update header
|
|
section.querySelector('.section-header h3').textContent = `Section ${section.dataset.sectionId}`;
|
|
|
|
// Restore actions frame text
|
|
const actionsFrame = section.querySelector('.section-actions-frame');
|
|
actionsFrame.querySelector('p:first-child').innerHTML = '💡 <span class="text-yellow-300">Section Actions</span>';
|
|
actionsFrame.querySelector('p:last-child').textContent = 'Save your content, delete this section, or edit after saving';
|
|
}
|
|
|
|
// Form submission
|
|
document.getElementById('adventure-form').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
// Validate required fields
|
|
const title = document.getElementById('title').value.trim();
|
|
const difficulty = document.getElementById('difficulty').value;
|
|
|
|
if (!title) {
|
|
alert('Please enter an adventure title.');
|
|
return;
|
|
}
|
|
|
|
if (!difficulty) {
|
|
alert('Please select a difficulty level.');
|
|
return;
|
|
}
|
|
|
|
// Check if at least one section is saved or allow empty content for now
|
|
const savedSections = document.querySelectorAll('.content-section.saved');
|
|
|
|
// Collect all form data
|
|
const formData = new FormData();
|
|
formData.append('title', title);
|
|
formData.append('subtitle', document.getElementById('subtitle').value.trim());
|
|
formData.append('difficulty', difficulty);
|
|
|
|
// Collect content from saved sections
|
|
let fullContent = '';
|
|
if (savedSections.length > 0) {
|
|
savedSections.forEach((section, index) => {
|
|
const highlights = Array.from(section.querySelectorAll('.saved-highlights .highlight-display'))
|
|
.map(span => span.textContent);
|
|
const text = section.querySelector('.saved-text').textContent;
|
|
|
|
if (highlights.length > 0) {
|
|
fullContent += highlights.map(h => `**${h}**`).join(' ') + '\n\n';
|
|
}
|
|
if (text) {
|
|
fullContent += text + '\n\n';
|
|
}
|
|
});
|
|
} else {
|
|
// If no sections saved, use a default message
|
|
fullContent = 'Adventure details will be added soon.';
|
|
}
|
|
|
|
formData.append('content', fullContent);
|
|
|
|
// Add cover picture if selected
|
|
const coverFile = document.getElementById('cover_picture').files[0];
|
|
if (coverFile) {
|
|
formData.append('cover_picture', coverFile);
|
|
}
|
|
|
|
// Add GPX file if selected
|
|
const gpxFile = document.getElementById('gpx-file').files[0];
|
|
if (gpxFile) {
|
|
formData.append('gpx_file', gpxFile);
|
|
}
|
|
|
|
// Add section images from all saved sections
|
|
let imageCounter = 0;
|
|
savedSections.forEach((section, sectionIndex) => {
|
|
const sectionId = section.dataset.sectionId;
|
|
if (window.sectionFiles[sectionId] && window.sectionFiles[sectionId].length > 0) {
|
|
window.sectionFiles[sectionId].forEach((file, fileIndex) => {
|
|
formData.append(`section_image_${imageCounter}`, file);
|
|
formData.append(`section_image_${imageCounter}_section`, sectionIndex);
|
|
imageCounter++;
|
|
});
|
|
}
|
|
});
|
|
|
|
// Show loading state
|
|
const submitBtn = document.querySelector('button[type="submit"]');
|
|
const originalText = submitBtn.innerHTML;
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-3"></i>Creating Adventure...';
|
|
submitBtn.disabled = true;
|
|
|
|
// Submit form
|
|
fetch('{{ url_for("community.new_post") }}', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => {
|
|
if (response.redirected) {
|
|
// Follow redirect
|
|
window.location.href = response.url;
|
|
} else if (response.ok) {
|
|
// Check if it's JSON response
|
|
return response.text().then(text => {
|
|
try {
|
|
const data = JSON.parse(text);
|
|
if (data.success) {
|
|
window.location.href = data.redirect_url || '/community/';
|
|
} else {
|
|
throw new Error(data.error || 'Unknown error');
|
|
}
|
|
} catch (e) {
|
|
// Not JSON, probably HTML redirect response
|
|
window.location.href = '/community/';
|
|
}
|
|
});
|
|
} else {
|
|
throw new Error('Server error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('An error occurred while creating your post. Please try again.');
|
|
|
|
// Restore button
|
|
submitBtn.innerHTML = originalText;
|
|
submitBtn.disabled = false;
|
|
});
|
|
});
|
|
|
|
// Preview function
|
|
function previewPost() {
|
|
// Get form data
|
|
const title = document.getElementById('title').value;
|
|
const subtitle = document.getElementById('subtitle').value;
|
|
const content = document.getElementById('content').value;
|
|
const difficulty = document.getElementById('difficulty').value;
|
|
|
|
// Get difficulty stars and labels
|
|
const difficultyStars = '⭐'.repeat(difficulty);
|
|
const difficultyLabels = {
|
|
'1': 'Easy - Beginner friendly',
|
|
'2': 'Moderate - Some experience needed',
|
|
'3': 'Challenging - Good skills required',
|
|
'4': 'Hard - Advanced riders only',
|
|
'5': 'Expert - Extreme difficulty'
|
|
};
|
|
|
|
// Format content (simple markdown-like formatting)
|
|
const formattedContent = content
|
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
.replace(/\n/g, '<br>');
|
|
|
|
// Get cover image preview if available
|
|
const coverInput = document.getElementById('cover_picture');
|
|
let coverImageHtml = '';
|
|
if (coverInput.files && coverInput.files[0]) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
showPreviewModal(title, subtitle, formattedContent, difficulty, difficultyStars, difficultyLabels, e.target.result);
|
|
};
|
|
reader.readAsDataURL(coverInput.files[0]);
|
|
} else {
|
|
showPreviewModal(title, subtitle, formattedContent, difficulty, difficultyStars, difficultyLabels, null);
|
|
}
|
|
}
|
|
|
|
function showPreviewModal(title, subtitle, formattedContent, difficulty, difficultyStars, difficultyLabels, coverImageSrc) {
|
|
// Create cover image section
|
|
let coverImageHtml = '';
|
|
if (coverImageSrc) {
|
|
coverImageHtml = `
|
|
<div class="position-relative mb-4">
|
|
<img src="${coverImageSrc}" alt="Cover preview" class="w-100 rounded" style="max-height: 300px; object-fit: cover;">
|
|
<div class="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-end" style="background: linear-gradient(transparent, rgba(0,0,0,0.6));">
|
|
<div class="p-4 text-white">
|
|
<h1 class="display-4 mb-2">${title || 'Your Adventure Title'}</h1>
|
|
${subtitle ? `<p class="lead mb-0">${subtitle}</p>` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
coverImageHtml = `
|
|
<div class="bg-primary text-white p-4 rounded mb-4">
|
|
<h1 class="display-4 mb-2">${title || 'Your Adventure Title'}</h1>
|
|
${subtitle ? `<p class="lead mb-0">${subtitle}</p>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Generate preview HTML
|
|
const previewHTML = `
|
|
<div class="post-preview">
|
|
${coverImageHtml}
|
|
|
|
<div class="row">
|
|
<div class="col-lg-8">
|
|
<div class="adventure-content">
|
|
<h3><i class="fas fa-book text-primary"></i> Adventure Story</h3>
|
|
<div class="content-text mb-4">
|
|
${formattedContent || '<em class="text-muted">No content provided yet...</em>'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4">
|
|
<div class="card">
|
|
<div class="card-header bg-light">
|
|
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Adventure Details</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<strong>Difficulty:</strong><br>
|
|
<span class="badge bg-warning text-dark fs-6">
|
|
${difficultyStars} ${difficultyLabels[difficulty] || 'Not set'}
|
|
</span>
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Status:</strong><br>
|
|
<span class="badge bg-warning">Pending Review</span>
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Author:</strong><br>
|
|
{{ current_user.nickname }}
|
|
</div>
|
|
<div>
|
|
<strong>Created:</strong><br>
|
|
Today
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mt-3">
|
|
<div class="card-header bg-info text-white">
|
|
<h6 class="mb-0"><i class="fas fa-map-marked-alt"></i> Route Information</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
${document.getElementById('gpx_file').files.length > 0 ?
|
|
'<div class="alert alert-success small"><i class="fas fa-check"></i> GPS track will be displayed on map</div>' :
|
|
'<div class="alert alert-info small"><i class="fas fa-info"></i> No GPS track uploaded</div>'
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Show preview in modal
|
|
showModal('Adventure Preview', previewHTML);
|
|
}
|
|
|
|
function showModal(title, content) {
|
|
// Create modal if it doesn't exist
|
|
let modal = document.getElementById('previewModal');
|
|
if (!modal) {
|
|
modal = document.createElement('div');
|
|
modal.innerHTML = `
|
|
<div class="modal fade" id="previewModal" tabindex="-1" aria-labelledby="previewModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-xl">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="previewModalLabel">
|
|
<i class="fas fa-eye"></i> ${title}
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="previewContent">${content}</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
<i class="fas fa-times"></i> Close Preview
|
|
</button>
|
|
<button type="button" class="btn btn-success" onclick="document.getElementById('createPostForm').submit()">
|
|
<i class="fas fa-paper-plane"></i> Looks Good - Share Adventure
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(modal);
|
|
} else {
|
|
document.getElementById('previewContent').innerHTML = content;
|
|
document.getElementById('previewModalLabel').innerHTML = `<i class="fas fa-eye"></i> ${title}`;
|
|
}
|
|
|
|
// Show the modal
|
|
const bootstrapModal = new bootstrap.Modal(document.getElementById('previewModal'));
|
|
bootstrapModal.show();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|