// Blog Management JavaScript let postsData = []; let postModal; let quillEditor; let isModalExpanded = false; document.addEventListener("DOMContentLoaded", function () { postModal = new bootstrap.Modal(document.getElementById("postModal")); initializeQuillEditor(); checkAuth().then((authenticated) => { if (authenticated) { loadPosts(); } }); const urlParams = new URLSearchParams(window.location.search); if (urlParams.get("action") === "create") { showCreatePost(); } // Auto-generate slug from title document.getElementById("postTitle")?.addEventListener("input", function () { if (!document.getElementById("postId").value) { document.getElementById("postSlug").value = slugify(this.value); } }); }); function resetModalSize() { const modalDialog = document.querySelector("#postModal .modal-dialog"); const expandIcon = document.getElementById("expandIcon"); const expandText = document.querySelector("#btnExpandModal span"); const editor = document.getElementById("postContentEditor"); if (modalDialog && expandIcon && expandText && editor) { modalDialog.classList.remove("modal-fullscreen"); modalDialog.classList.add("modal-xl"); expandIcon.className = "bi bi-arrows-fullscreen"; expandText.textContent = "Expand"; editor.style.height = "400px"; const container = editor.querySelector(".ql-container"); if (container) { container.style.height = "calc(400px - 42px)"; } isModalExpanded = false; } } function toggleModalSize() { const modalDialog = document.querySelector("#postModal .modal-dialog"); const expandIcon = document.getElementById("expandIcon"); const expandText = document.querySelector("#btnExpandModal span"); const editor = document.getElementById("postContentEditor"); if (!modalDialog || !expandIcon || !expandText || !editor) { console.error("Modal elements not found"); return; } if (isModalExpanded) { // Collapse to normal size modalDialog.classList.remove("modal-fullscreen"); modalDialog.classList.add("modal-xl"); expandIcon.className = "bi bi-arrows-fullscreen"; expandText.textContent = "Expand"; editor.style.height = "400px"; const container = editor.querySelector(".ql-container"); if (container) { container.style.height = "calc(400px - 42px)"; } isModalExpanded = false; } else { // Expand to fullscreen modalDialog.classList.remove("modal-xl"); modalDialog.classList.add("modal-fullscreen"); expandIcon.className = "bi bi-fullscreen-exit"; expandText.textContent = "Collapse"; editor.style.height = "60vh"; const container = editor.querySelector(".ql-container"); if (container) { container.style.height = "calc(60vh - 42px)"; } isModalExpanded = true; } } function initializeQuillEditor() { quillEditor = new Quill("#postContentEditor", { theme: "snow", placeholder: "Write your blog post content here...", modules: { toolbar: [ [{ header: [1, 2, 3, false] }], ["bold", "italic", "underline", "strike"], [{ list: "ordered" }, { list: "bullet" }], [{ color: [] }, { background: [] }], ["link", "image"], ["blockquote", "code-block"], ["clean"], ], }, }); } // Initialize media library let blogMediaLibrary = null; let galleryImages = []; function initBlogMediaLibrary() { blogMediaLibrary = new MediaLibrary({ selectMode: true, multiple: false, onSelect: function (media) { if (media && media.path) { document.getElementById("postFeaturedImage").value = media.path; updateFeaturedImagePreview(media.path); showToast("Featured image selected", "success"); } }, }); } function openMediaLibraryForFeaturedImage() { if (!blogMediaLibrary) { initBlogMediaLibrary(); } blogMediaLibrary.options.multiple = false; blogMediaLibrary.options.onSelect = function (media) { if (media && media.path) { document.getElementById("postFeaturedImage").value = media.path; updateFeaturedImagePreview(media.path); showToast("Featured image selected", "success"); } }; blogMediaLibrary.open(); } function openMediaLibraryForGallery() { if (!blogMediaLibrary) { initBlogMediaLibrary(); } blogMediaLibrary.options.multiple = true; blogMediaLibrary.options.onSelect = function (mediaList) { const items = Array.isArray(mediaList) ? mediaList : [mediaList]; items.forEach((media) => { if (media && media.path && !galleryImages.includes(media.path)) { galleryImages.push(media.path); } }); updateGalleryPreview(); showToast(`${items.length} image(s) added to gallery`, "success"); }; blogMediaLibrary.open(); } function openMediaLibraryForVideo() { if (!blogMediaLibrary) { initBlogMediaLibrary(); } blogMediaLibrary.options.multiple = false; blogMediaLibrary.options.onSelect = function (media) { if (media && media.path) { document.getElementById("postVideoUrl").value = media.path; updateVideoPreview(media.path); showToast("Video selected", "success"); } }; blogMediaLibrary.open(); } function updateFeaturedImagePreview(url) { const preview = document.getElementById("featuredImagePreview"); if (url) { preview.innerHTML = `
`; } else { preview.innerHTML = '

No image selected
'; } } function updateGalleryPreview() { const preview = document.getElementById("galleryImagesPreview"); if (galleryImages.length === 0) { preview.innerHTML = '

No gallery images
'; return; } preview.innerHTML = galleryImages .map( (img, idx) => ` `, ) .join(""); } function removeGalleryImage(index) { galleryImages.splice(index, 1); updateGalleryPreview(); showToast("Image removed from gallery", "info"); } function updateVideoPreview(url) { const preview = document.getElementById("videoPreview"); if (url) { const isVideo = url.match(/\.(mp4|webm|mov|avi|mkv)$/i); if (isVideo) { preview.innerHTML = `
`; } else { preview.innerHTML = `
${url}
`; } } else { preview.innerHTML = '
No video selected
'; } } function removeVideo() { document.getElementById("postVideoUrl").value = ""; document.getElementById("postExternalVideo").value = ""; updateVideoPreview(""); showToast("Video removed", "info"); } function removeFeaturedImage() { document.getElementById("postFeaturedImage").value = ""; updateFeaturedImagePreview(""); showToast("Featured image removed", "info"); } // Poll functions function togglePollSection() { const pollSection = document.getElementById("pollSection"); const enabled = document.getElementById("enablePoll").checked; pollSection.style.display = enabled ? "block" : "none"; } function addPollOption() { const container = document.getElementById("pollOptionsContainer"); const count = container.querySelectorAll(".poll-option-row").length + 1; const row = document.createElement("div"); row.className = "input-group mb-2 poll-option-row"; row.innerHTML = ` ${count} `; container.appendChild(row); } function removePollOption(btn) { const row = btn.closest(".poll-option-row"); row.remove(); // Re-number options const container = document.getElementById("pollOptionsContainer"); container.querySelectorAll(".poll-option-row").forEach((row, idx) => { row.querySelector(".input-group-text").textContent = idx + 1; }); } function getPollData() { if (!document.getElementById("enablePoll").checked) { return null; } const question = document.getElementById("pollQuestion").value.trim(); const options = Array.from(document.querySelectorAll(".poll-option-input")) .map((input) => input.value.trim()) .filter((v) => v); if (!question || options.length < 2) { return null; } return { question, options, votes: options.map(() => 0) }; } function loadPollData(poll) { if (poll && typeof poll === "object") { document.getElementById("enablePoll").checked = true; document.getElementById("pollSection").style.display = "block"; document.getElementById("pollQuestion").value = poll.question || ""; const container = document.getElementById("pollOptionsContainer"); container.innerHTML = ""; (poll.options || []).forEach((opt, idx) => { const row = document.createElement("div"); row.className = "input-group mb-2 poll-option-row"; row.innerHTML = ` ${idx + 1} ${idx >= 2 ? '' : ""} `; container.appendChild(row); }); } else { document.getElementById("enablePoll").checked = false; document.getElementById("pollSection").style.display = "none"; document.getElementById("pollQuestion").value = ""; document.getElementById("pollOptionsContainer").innerHTML = `
1
2
`; } } async function loadPosts() { try { const response = await fetch("/api/admin/blog", { credentials: "include" }); const data = await response.json(); console.log("Blog API Response:", data); if (data.success) { postsData = data.posts; console.log("Loaded posts:", postsData); renderPosts(postsData); } else { console.error("API returned success=false:", data); const tbody = document.getElementById("postsTableBody"); tbody.innerHTML = `

Failed to load posts: ${ data.message || "Unknown error" }

`; } } catch (error) { console.error("Failed to load posts:", error); const tbody = document.getElementById("postsTableBody"); tbody.innerHTML = `

Error loading posts. Please refresh the page.

`; } } function renderPosts(posts) { const tbody = document.getElementById("postsTableBody"); if (posts.length === 0) { tbody.innerHTML = `

No blog posts found

`; return; } tbody.innerHTML = posts .map( (p) => ` ${escapeHtml(String(p.id))} ${escapeHtml(p.title)} ${escapeHtml(p.slug)} ${escapeHtml((p.excerpt || "").substring(0, 40))}... ${p.ispublished ? "Published" : "Draft"} ${formatDate(p.createdat)} `, ) .join(""); } function filterPosts() { const searchTerm = document.getElementById("searchInput").value.toLowerCase(); const filtered = postsData.filter( (p) => p.title.toLowerCase().includes(searchTerm) || p.slug.toLowerCase().includes(searchTerm), ); renderPosts(filtered); } function showCreatePost() { document.getElementById("modalTitle").textContent = "Create Blog Post"; document.getElementById("postForm").reset(); document.getElementById("postId").value = ""; document.getElementById("postPublished").checked = true; // Default to published document.getElementById("postFeaturedImage").value = ""; document.getElementById("postVideoUrl").value = ""; document.getElementById("postExternalVideo").value = ""; galleryImages = []; updateFeaturedImagePreview(""); updateGalleryPreview(); updateVideoPreview(""); loadPollData(null); if (quillEditor) { quillEditor.setContents([]); } resetModalSize(); postModal.show(); } async function editPost(id) { try { const response = await fetch(`/api/admin/blog/${id}`, { credentials: "include", }); const data = await response.json(); if (data.success) { const post = data.post; document.getElementById("modalTitle").textContent = "Edit Blog Post"; document.getElementById("postId").value = post.id; document.getElementById("postTitle").value = post.title; document.getElementById("postSlug").value = post.slug; document.getElementById("postExcerpt").value = post.excerpt || ""; // Set Quill content if (quillEditor) { quillEditor.root.innerHTML = post.content || ""; } // Set featured image const featuredImage = post.featuredimage || post.imageurl || ""; document.getElementById("postFeaturedImage").value = featuredImage; updateFeaturedImagePreview(featuredImage); // Set gallery images try { galleryImages = post.images ? JSON.parse(post.images) : []; } catch (e) { galleryImages = []; } updateGalleryPreview(); // Set video const videoUrl = post.videourl || ""; document.getElementById("postVideoUrl").value = videoUrl; document.getElementById("postExternalVideo").value = ""; updateVideoPreview(videoUrl); // Set poll try { const poll = post.poll ? JSON.parse(post.poll) : null; loadPollData(poll); } catch (e) { loadPollData(null); } document.getElementById("postMetaTitle").value = post.metatitle || ""; document.getElementById("postMetaDescription").value = post.metadescription || ""; document.getElementById("postPublished").checked = post.ispublished; resetModalSize(); postModal.show(); } } catch (error) { console.error("Failed to load post:", error); showToast("Failed to load post details", "error"); } } async function savePost() { const id = document.getElementById("postId").value; // Get content from Quill editor const content = quillEditor ? quillEditor.root.innerHTML : ""; // Get video URL (prefer uploaded, then external) let videoUrl = document.getElementById("postVideoUrl").value; if (!videoUrl) { videoUrl = document.getElementById("postExternalVideo").value; } // Get poll data const poll = getPollData(); const formData = { title: document.getElementById("postTitle").value, slug: document.getElementById("postSlug").value, excerpt: document.getElementById("postExcerpt").value, content: content, featuredimage: document.getElementById("postFeaturedImage").value, images: JSON.stringify(galleryImages), videourl: videoUrl, poll: poll ? JSON.stringify(poll) : null, metatitle: document.getElementById("postMetaTitle").value, metadescription: document.getElementById("postMetaDescription").value, ispublished: document.getElementById("postPublished").checked, }; if (!formData.title || !formData.slug || !formData.content) { showToast("Please fill in all required fields", "error"); return; } try { const url = id ? `/api/admin/blog/${id}` : "/api/admin/blog"; const method = id ? "PUT" : "POST"; const response = await fetch(url, { method: method, headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(formData), }); const data = await response.json(); if (data.success) { showToast( id ? "Post updated successfully" : "Post created successfully", "success", ); postModal.hide(); loadPosts(); } else { showToast(data.message || "Failed to save post", "error"); } } catch (error) { console.error("Failed to save post:", error); showToast("Failed to save post", "error"); } } async function deletePost(id, title) { showDeleteConfirm( `Are you sure you want to delete "${title}"? This action cannot be undone.`, async () => { try { const response = await fetch(`/api/admin/blog/${id}`, { method: "DELETE", credentials: "include", }); const data = await response.json(); if (data.success) { // Immediately remove from local array and re-render postsData = postsData.filter((p) => String(p.id) !== String(id)); renderPosts(postsData); showToast("Post deleted successfully", "success"); } else { showToast(data.message || "Failed to delete post", "error"); } } catch (error) { console.error("Failed to delete post:", error); showToast("Failed to delete post", "error"); } }, { title: "Delete Blog Post", confirmText: "Delete Post" }, ); } function showToast(message, type = "info") { const toastContainer = document.getElementById("toastContainer") || createToastContainer(); const toast = document.createElement("div"); toast.className = `toast toast-${type}`; const icons = { success: "check-circle-fill", error: "exclamation-triangle-fill", warning: "exclamation-circle-fill", info: "info-circle-fill", }; toast.innerHTML = ` ${message} `; toastContainer.appendChild(toast); setTimeout(() => toast.classList.add("show"), 10); setTimeout(() => { toast.classList.remove("show"); setTimeout(() => toast.remove(), 300); }, 3000); } function createToastContainer() { const container = document.createElement("div"); container.id = "toastContainer"; container.style.cssText = "position: fixed; top: 80px; right: 20px; z-index: 9999;"; document.body.appendChild(container); return container; } function slugify(text) { return text .toLowerCase() .replace(/[^\w\s-]/g, "") .replace(/[\s_-]+/g, "-") .replace(/^-+|-+$/g, ""); } function escapeHtml(text) { const map = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", }; return text.replace(/[&<>"']/g, (m) => map[m]); } function formatDate(dateString) { return new Date(dateString).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", }); }