234 lines
6.7 KiB
JavaScript
234 lines
6.7 KiB
JavaScript
|
|
// Blog Management JavaScript
|
||
|
|
|
||
|
|
let postsData = [];
|
||
|
|
let postModal;
|
||
|
|
|
||
|
|
document.addEventListener("DOMContentLoaded", function () {
|
||
|
|
postModal = new bootstrap.Modal(document.getElementById("postModal"));
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
async function loadPosts() {
|
||
|
|
try {
|
||
|
|
const response = await fetch("/api/admin/blog", { credentials: "include" });
|
||
|
|
const data = await response.json();
|
||
|
|
if (data.success) {
|
||
|
|
postsData = data.posts;
|
||
|
|
renderPosts(postsData);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Failed to load posts:", error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderPosts(posts) {
|
||
|
|
const tbody = document.getElementById("postsTableBody");
|
||
|
|
if (posts.length === 0) {
|
||
|
|
tbody.innerHTML = `
|
||
|
|
<tr><td colspan="7" class="text-center p-4">
|
||
|
|
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
||
|
|
<p class="mt-3 text-muted">No blog posts found</p>
|
||
|
|
<button class="btn btn-primary" onclick="showCreatePost()">
|
||
|
|
<i class="bi bi-plus-circle"></i> Create Your First Post
|
||
|
|
</button>
|
||
|
|
</td></tr>`;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
tbody.innerHTML = posts
|
||
|
|
.map(
|
||
|
|
(p) => `
|
||
|
|
<tr>
|
||
|
|
<td>${p.id}</td>
|
||
|
|
<td><strong>${escapeHtml(p.title)}</strong></td>
|
||
|
|
<td><code>${escapeHtml(p.slug)}</code></td>
|
||
|
|
<td>${escapeHtml((p.excerpt || "").substring(0, 40))}...</td>
|
||
|
|
<td><span class="badge ${
|
||
|
|
p.ispublished ? "badge-success" : "badge-warning"
|
||
|
|
}">
|
||
|
|
${p.ispublished ? "Published" : "Draft"}</span></td>
|
||
|
|
<td>${formatDate(p.createdat)}</td>
|
||
|
|
<td>
|
||
|
|
<button class="btn btn-sm btn-info" onclick="editPost(${p.id})">
|
||
|
|
<i class="bi bi-pencil"></i>
|
||
|
|
</button>
|
||
|
|
<button class="btn btn-sm btn-danger" onclick="deletePost(${
|
||
|
|
p.id
|
||
|
|
}, '${escapeHtml(p.title)}')">
|
||
|
|
<i class="bi bi-trash"></i>
|
||
|
|
</button>
|
||
|
|
</td>
|
||
|
|
</tr>`
|
||
|
|
)
|
||
|
|
.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 = false;
|
||
|
|
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 || "";
|
||
|
|
document.getElementById("postContent").value = post.content || "";
|
||
|
|
document.getElementById("postMetaTitle").value = post.metatitle || "";
|
||
|
|
document.getElementById("postMetaDescription").value =
|
||
|
|
post.metadescription || "";
|
||
|
|
document.getElementById("postPublished").checked = post.ispublished;
|
||
|
|
postModal.show();
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Failed to load post:", error);
|
||
|
|
showError("Failed to load post details");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function savePost() {
|
||
|
|
const id = document.getElementById("postId").value;
|
||
|
|
const formData = {
|
||
|
|
title: document.getElementById("postTitle").value,
|
||
|
|
slug: document.getElementById("postSlug").value,
|
||
|
|
excerpt: document.getElementById("postExcerpt").value,
|
||
|
|
content: document.getElementById("postContent").value,
|
||
|
|
metatitle: document.getElementById("postMetaTitle").value,
|
||
|
|
metadescription: document.getElementById("postMetaDescription").value,
|
||
|
|
ispublished: document.getElementById("postPublished").checked,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!formData.title || !formData.slug || !formData.content) {
|
||
|
|
showError("Please fill in all required fields");
|
||
|
|
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) {
|
||
|
|
showSuccess(
|
||
|
|
id ? "Post updated successfully" : "Post created successfully"
|
||
|
|
);
|
||
|
|
postModal.hide();
|
||
|
|
loadPosts();
|
||
|
|
} else {
|
||
|
|
showError(data.message || "Failed to save post");
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Failed to save post:", error);
|
||
|
|
showError("Failed to save post");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function deletePost(id, title) {
|
||
|
|
if (!confirm(`Are you sure you want to delete "${title}"?`)) return;
|
||
|
|
try {
|
||
|
|
const response = await fetch(`/api/admin/blog/${id}`, {
|
||
|
|
method: "DELETE",
|
||
|
|
credentials: "include",
|
||
|
|
});
|
||
|
|
const data = await response.json();
|
||
|
|
if (data.success) {
|
||
|
|
showSuccess("Post deleted successfully");
|
||
|
|
loadPosts();
|
||
|
|
} else {
|
||
|
|
showError(data.message || "Failed to delete post");
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Failed to delete post:", error);
|
||
|
|
showError("Failed to delete post");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function slugify(text) {
|
||
|
|
return text
|
||
|
|
.toLowerCase()
|
||
|
|
.replace(/[^\w\s-]/g, "")
|
||
|
|
.replace(/[\s_-]+/g, "-")
|
||
|
|
.replace(/^-+|-+$/g, "");
|
||
|
|
}
|
||
|
|
|
||
|
|
async function logout() {
|
||
|
|
try {
|
||
|
|
const response = await fetch("/api/admin/logout", {
|
||
|
|
method: "POST",
|
||
|
|
credentials: "include",
|
||
|
|
});
|
||
|
|
if (response.ok) window.location.href = "/admin/login.html";
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Logout failed:", error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function showSuccess(message) {
|
||
|
|
alert(message);
|
||
|
|
}
|
||
|
|
function showError(message) {
|
||
|
|
alert("Error: " + message);
|
||
|
|
}
|