// content.js - JavaScript for the content management page document.addEventListener('DOMContentLoaded', () => { // =================================================== // Global Variables // =================================================== let currentBotId = null; // Media server configuration (global for all bots) let globalMediaConfig = { primaryService: 'nip94', primaryURL: '', fallbackService: 'none', fallbackURL: '' }; // =================================================== // Element References // =================================================== const botSelect = document.getElementById('botSelect'); const loadContentBtn = document.getElementById('loadContentBtn'); const uploadFileInput = document.getElementById('uploadFileInput'); const uploadPreviewContainer = document.getElementById('uploadPreviewContainer'); const uploadPreview = document.getElementById('uploadPreview'); const uploadButton = document.getElementById('uploadButton'); const contentContainer = document.getElementById('contentContainer'); // Post creation elements const postKindRadios = document.querySelectorAll('input[name="postKind"]'); const titleField = document.getElementById('titleField'); const postTitle = document.getElementById('postTitle'); const manualPostContent = document.getElementById('manualPostContent'); const postHashtags = document.getElementById('postHashtags'); const postMediaInput = document.getElementById('postMediaInput'); const quickUploadBtn = document.getElementById('quickUploadBtn'); const mediaPreviewContainer = document.getElementById('mediaPreviewContainer'); const mediaPreview = document.getElementById('mediaPreview'); const mediaLinkContainer = document.getElementById('mediaLinkContainer'); const submitPostBtn = document.getElementById('submitPostBtn'); // Media server settings const primaryServer = document.getElementById('primaryServer'); const fallbackServer = document.getElementById('fallbackServer'); const saveMediaSettingsBtn = document.getElementById('saveMediaSettingsBtn'); const primaryServerURL = document.getElementById('primaryServerURL'); const fallbackServerURL = document.getElementById('fallbackServerURL'); // =================================================== // Event Listeners // =================================================== // Check auth and load bots first checkAuth(); // Load bot choices after a short delay to ensure auth is checked setTimeout(() => { const token = localStorage.getItem('authToken'); if (token) { loadBotChoices(); } }, 500); // Button click handlers if (loadContentBtn) loadContentBtn.addEventListener('click', handleLoadContent); if (uploadButton) uploadButton.addEventListener('click', handleFileUpload); if (quickUploadBtn) quickUploadBtn.addEventListener('click', handleQuickUpload); if (submitPostBtn) submitPostBtn.addEventListener('click', handleSubmitPost); if (saveMediaSettingsBtn) { saveMediaSettingsBtn.addEventListener('click', handleSaveMediaSettings); } // File input change handlers if (uploadFileInput) { uploadFileInput.addEventListener('change', (e) => { previewFile(e.target.files[0], uploadPreview, uploadPreviewContainer); uploadButton.disabled = !e.target.files.length; }); } if (postMediaInput) { postMediaInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) { mediaPreviewContainer.classList.add('d-none'); return; } previewFile(file, mediaPreview, mediaPreviewContainer); // Show filename if not an image if (!file.type.startsWith('image/')) { mediaPreview.style.display = 'none'; mediaLinkContainer.innerHTML = `

Selected: ${file.name}

Click "Upload" to get media URL`; } else { mediaPreview.style.display = 'block'; mediaLinkContainer.innerHTML = 'Click "Upload" to get media URL'; } }); } // Post kind radio button change handler if (postKindRadios.length) { postKindRadios.forEach(radio => { radio.addEventListener('change', () => { titleField.classList.toggle('d-none', radio.value !== '20'); // Show/hide required media elements for kind 20 const isKind20 = radio.value === '20'; const kind20MediaRequired = document.getElementById('kind20MediaRequired'); if (kind20MediaRequired) { kind20MediaRequired.style.display = isKind20 ? 'inline-block' : 'none'; } // For kind 20, validate that media is provided before posting if (submitPostBtn) { submitPostBtn.title = isKind20 ? 'Picture posts require a title and an image URL' : 'Create a standard text post'; } }); }); } // =================================================== // Event Handler Functions // =================================================== // Load content when button is clicked function handleLoadContent() { if (!botSelect || !botSelect.value) { alert('Please select a bot first!'); return; } currentBotId = botSelect.value; contentContainer.classList.remove('d-none'); loadBotContent(currentBotId); } // Handle file upload button click function handleFileUpload() { if (!currentBotId) { alert('Please select a bot first'); return; } if (!uploadFileInput.files.length) { alert('Please select a file to upload'); return; } uploadBotFile(currentBotId); } // Handle quick upload for post creation function handleQuickUpload() { if (!currentBotId) { alert('Please select a bot first'); return; } if (!postMediaInput.files.length) { alert('Please select a file to upload'); return; } quickUploadMedia(currentBotId, postMediaInput.files[0]); } // Handle post submission function handleSubmitPost() { if (!currentBotId) { alert('Please select a bot first'); return; } createManualPost(currentBotId) .then(data => { alert('Post created successfully!'); console.log('Post success response:', data); // Display event information if present if (data.event) { displayEventInfo(data.event); } // Reset form manualPostContent.value = ''; postHashtags.value = ''; postTitle.value = ''; postMediaInput.value = ''; if (mediaUrlInput) mediaUrlInput.value = ''; if (mediaAltText) mediaAltText.value = ''; mediaPreviewContainer.classList.add('d-none'); // Reset button submitPostBtn.disabled = false; submitPostBtn.textContent = 'Post Now'; }) .catch(error => { console.error('Error creating post:', error); alert('Error creating post: ' + error.message); submitPostBtn.disabled = false; submitPostBtn.textContent = 'Post Now'; }); } // Handle saving media server settings function handleSaveMediaSettings() { globalMediaConfig.primaryService = primaryServer.value; globalMediaConfig.primaryURL = primaryServerURL ? primaryServerURL.value.trim() : ''; globalMediaConfig.fallbackService = fallbackServer.value === 'none' ? '' : fallbackServer.value; globalMediaConfig.fallbackURL = fallbackServerURL ? fallbackServerURL.value.trim() : ''; // Save to localStorage localStorage.setItem('mediaConfig', JSON.stringify(globalMediaConfig)); alert('Media server settings saved!'); } // =================================================== // Utility Functions // =================================================== // Preview a file (image or video) function previewFile(file, previewElement, containerElement) { if (!file) { containerElement.classList.add('d-none'); return; } containerElement.classList.remove('d-none'); // Show preview for images if (file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = function (e) { previewElement.src = e.target.result; previewElement.style.display = 'block'; }; reader.readAsDataURL(file); } else { // For non-images (video, etc) previewElement.style.display = 'none'; } } // Validates post data before submission function validatePostData() { const kind = parseInt(document.querySelector('input[name="postKind"]:checked').value); const content = manualPostContent.value.trim(); // Basic validation for all post types if (!content) { alert('Post content is required'); return false; } // Additional validation for kind 20 posts if (kind === 20) { const title = postTitle.value.trim(); if (!title) { alert('Title is required for Picture Posts (kind: 20)'); return false; } // Check if we have a media URL either in the content or in the mediaUrlInput const mediaUrl = mediaUrlInput.value.trim(); const urlRegex = /(https?:\/\/[^\s]+)/g; const contentContainsUrl = urlRegex.test(content); if (!mediaUrl && !contentContainsUrl) { alert('Picture posts require an image URL. Please upload an image or enter a URL in the Media URL field or in the content.'); return false; } } return true; } // Add this function to content.js function displayEventInfo(event) { // Get the dedicated container const container = document.getElementById('eventInfoContainer'); if (!container) return; // Create HTML for the event info const html = `
Post Published Successfully
`; // Update the container and make it visible container.innerHTML = html; container.classList.remove('d-none'); } // =================================================== // API Functions // =================================================== // Load bot content files function loadBotContent(botId) { const token = localStorage.getItem('authToken'); // Load any saved media config from localStorage const savedConfig = localStorage.getItem('mediaConfig'); if (savedConfig) { try { globalMediaConfig = JSON.parse(savedConfig); if (primaryServer) primaryServer.value = globalMediaConfig.primaryService || 'nip94'; if (primaryServerURL) primaryServerURL.value = globalMediaConfig.primaryURL || ''; if (fallbackServer) { fallbackServer.value = globalMediaConfig.fallbackService || 'none'; } if (fallbackServerURL) { fallbackServerURL.value = globalMediaConfig.fallbackURL || ''; } } catch (e) { console.error('Error parsing saved media config:', e); } } // Fetch content files fetch(`/api/content/${botId}`, { headers: { 'Authorization': token } }) .then(res => { if (!res.ok) throw new Error('Failed to list content'); return res.json(); }) .then(files => { renderContentFiles(botId, files); }) .catch(err => { console.error('Error loading content:', err); alert('Error loading content: ' + err.message); }); } // Updated renderContentFiles function function renderContentFiles(botId, files) { const contentArea = document.getElementById('contentArea'); if (!contentArea) return; // Generate ONLY the file list, no upload form let html = ''; if (!files || files.length === 0) { html = '

No files found. Upload some content!

'; } else { html = ''; } // Set the content area HTML to just the files list contentArea.innerHTML = html; } // Upload media for the manual post function quickUploadMedia(botId, file) { const formData = new FormData(); formData.append('file', file); quickUploadBtn.disabled = true; quickUploadBtn.textContent = 'Uploading...'; // Show loading state in preview mediaLinkContainer.innerHTML = '
Uploading...'; const token = localStorage.getItem('authToken'); // First upload the file to our server fetch(`/api/content/${botId}/upload`, { method: 'POST', headers: { 'Authorization': token }, body: formData }) .then(res => { if (!res.ok) throw new Error('Upload failed'); return res.json(); }) .then(data => { // Now upload to the media server return fetch(`/api/content/${botId}/uploadToMediaServer`, { method: 'POST', headers: { 'Authorization': token, 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: data.filename, service: globalMediaConfig.primaryService, serverURL: globalMediaConfig.primaryURL }) }); }) .then(res => { if (!res.ok) throw new Error('Media server upload failed'); return res.json(); }) .then(data => { // Reset button state quickUploadBtn.disabled = false; quickUploadBtn.textContent = 'Upload'; // Insert the media URL into the post content const textArea = document.getElementById('manualPostContent'); let mediaUrl = data.url; // Also update the media URL input field const mediaUrlInput = document.getElementById('mediaUrlInput'); if (mediaUrlInput) { mediaUrlInput.value = mediaUrl; } // Update preview with media info mediaLinkContainer.innerHTML = `

Media URL:

${mediaUrl}`; // Insert into text area textArea.value += (textArea.value ? '\n\n' : '') + mediaUrl; }) .catch(err => { console.error('Upload error:', err); quickUploadBtn.disabled = false; quickUploadBtn.textContent = 'Upload'; mediaLinkContainer.innerHTML = `

Upload error: ${err.message}

`; alert('Upload error: ' + err.message); }); } // Create a manual post (improved for kind 20 posts) function createManualPost(botId) { // Validate the form data first if (!validatePostData()) { return; } const content = manualPostContent.value.trim(); const hashtagsValue = postHashtags.value.trim(); const hashtags = hashtagsValue ? hashtagsValue.split(',').map(tag => tag.trim()).filter(tag => tag.length > 0) : []; const kind = parseInt(document.querySelector('input[name="postKind"]:checked').value); let title = ''; if (kind === 20) { title = postTitle.value.trim(); } // Extract media URLs and alt text const mediaUrl = mediaUrlInput ? mediaUrlInput.value.trim() : ''; const altText = mediaAltText ? mediaAltText.value.trim() : ''; // Build the post data based on kind const postData = { kind: kind, content: content, hashtags: hashtags }; if (kind === 20) { postData.title = title; // For kind 20, we need to ensure we have a valid URL // If we have a specific media URL field value, add it to the content if not already there if (mediaUrl && !content.includes(mediaUrl)) { postData.content = content + '\n\n' + mediaUrl; } // Add alt text if provided if (altText) { postData.alt = altText; } } // Disable submit button during request submitPostBtn.disabled = true; submitPostBtn.innerHTML = ' Posting...'; const token = localStorage.getItem('authToken'); fetch(`/api/bots/${botId}/post`, { method: 'POST', headers: { 'Authorization': token, 'Content-Type': 'application/json' }, body: JSON.stringify(postData) }) .then(res => { if (!res.ok) { return res.json().then(data => { throw new Error(data.error || 'Failed to create post'); }); } return res.json(); }) .then(data => { alert('Post created successfully!'); console.log('Post success response:', data); // Reset form manualPostContent.value = ''; postHashtags.value = ''; postTitle.value = ''; postMediaInput.value = ''; if (mediaUrlInput) mediaUrlInput.value = ''; if (mediaAltText) mediaAltText.value = ''; mediaPreviewContainer.classList.add('d-none'); // Reset button submitPostBtn.disabled = false; submitPostBtn.textContent = 'Post Now'; }) .catch(err => { console.error('Error creating post:', err); alert('Error creating post: ' + err.message); // Reset button submitPostBtn.disabled = false; submitPostBtn.textContent = 'Post Now'; }); } // =================================================== // Initialize media config // =================================================== // Try to load media config from localStorage const savedConfig = localStorage.getItem('mediaConfig'); if (savedConfig) { try { globalMediaConfig = JSON.parse(savedConfig); if (primaryServer) primaryServer.value = globalMediaConfig.primaryService || 'nip94'; if (primaryServerURL) primaryServerURL.value = globalMediaConfig.primaryURL || ''; if (fallbackServer) { fallbackServer.value = globalMediaConfig.fallbackService || 'none'; } if (fallbackServerURL) { fallbackServerURL.value = globalMediaConfig.fallbackURL || ''; } } catch (e) { console.error('Error parsing saved media config:', e); } } }); document.addEventListener('click', function(e) { if (e.target.closest('.copy-btn')) { const btn = e.target.closest('.copy-btn'); const valueToCopy = btn.getAttribute('data-value'); navigator.clipboard.writeText(valueToCopy) .then(() => { const originalHTML = btn.innerHTML; btn.innerHTML = 'Copied!'; setTimeout(() => { btn.innerHTML = originalHTML; }, 2000); }) .catch(err => { console.error('Failed to copy: ', err); }); } });