updateweb
This commit is contained in:
@@ -2,9 +2,12 @@
|
||||
|
||||
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();
|
||||
@@ -24,16 +27,224 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
});
|
||||
});
|
||||
|
||||
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"],
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function openMediaLibraryForFeaturedImage() {
|
||||
// Create modal backdrop
|
||||
const backdrop = document.createElement("div");
|
||||
backdrop.id = "mediaLibraryBackdrop";
|
||||
backdrop.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 9998;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
// Create modal container
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "mediaLibraryModal";
|
||||
modal.style.cssText = `
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
height: 85vh;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
||||
`;
|
||||
|
||||
// Create close button
|
||||
const closeBtn = document.createElement("button");
|
||||
closeBtn.innerHTML = '<i class="bi bi-x-lg"></i>';
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 10000;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
closeBtn.onclick = closeMediaLibrary;
|
||||
|
||||
// Create iframe
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id = "mediaLibraryFrame";
|
||||
iframe.src = "/admin/media-library.html?selectMode=true";
|
||||
iframe.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
modal.appendChild(closeBtn);
|
||||
modal.appendChild(iframe);
|
||||
backdrop.appendChild(modal);
|
||||
document.body.appendChild(backdrop);
|
||||
|
||||
// Close on backdrop click
|
||||
backdrop.onclick = function (e) {
|
||||
if (e.target === backdrop) {
|
||||
closeMediaLibrary();
|
||||
}
|
||||
};
|
||||
|
||||
// Setup media selection handler
|
||||
window.handleMediaSelection = function (media) {
|
||||
const mediaItem = Array.isArray(media) ? media[0] : media;
|
||||
if (mediaItem && mediaItem.url) {
|
||||
document.getElementById("postFeaturedImage").value = mediaItem.url;
|
||||
updateFeaturedImagePreview(mediaItem.url);
|
||||
showToast("Featured image selected", "success");
|
||||
}
|
||||
closeMediaLibrary();
|
||||
};
|
||||
}
|
||||
|
||||
function closeMediaLibrary() {
|
||||
const backdrop = document.getElementById("mediaLibraryBackdrop");
|
||||
if (backdrop) {
|
||||
backdrop.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function updateFeaturedImagePreview(url) {
|
||||
const preview = document.getElementById("featuredImagePreview");
|
||||
if (url) {
|
||||
preview.innerHTML = `
|
||||
<div style="position: relative; display: inline-block;">
|
||||
<img src="${url}" style="max-width: 200px; max-height: 150px; border-radius: 8px; border: 2px solid #e0e0e0;" />
|
||||
<button type="button" onclick="removeFeaturedImage()" style="position: absolute; top: -8px; right: -8px; background: #dc3545; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 14px;">×</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
preview.innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
function removeFeaturedImage() {
|
||||
document.getElementById("postFeaturedImage").value = "";
|
||||
updateFeaturedImagePreview("");
|
||||
showToast("Featured image removed", "info");
|
||||
}
|
||||
|
||||
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 = `
|
||||
<tr><td colspan="7" class="text-center p-4 text-danger">
|
||||
<i class="bi bi-exclamation-triangle" style="font-size: 3rem;"></i>
|
||||
<p class="mt-3">Failed to load posts: ${
|
||||
data.message || "Unknown error"
|
||||
}</p>
|
||||
</td></tr>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load posts:", error);
|
||||
const tbody = document.getElementById("postsTableBody");
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="7" class="text-center p-4 text-danger">
|
||||
<i class="bi bi-exclamation-triangle" style="font-size: 3rem;"></i>
|
||||
<p class="mt-3">Error loading posts. Please refresh the page.</p>
|
||||
</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,22 +266,24 @@ function renderPosts(posts) {
|
||||
.map(
|
||||
(p) => `
|
||||
<tr>
|
||||
<td>${p.id}</td>
|
||||
<td>${escapeHtml(String(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 ? "bg-success text-white" : "bg-warning text-dark"
|
||||
}">
|
||||
${p.ispublished ? "Published" : "Draft"}</span></td>
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editPost(${p.id})">
|
||||
<button class="btn btn-sm btn-info" onclick="editPost('${escapeHtml(
|
||||
String(p.id)
|
||||
)}')">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deletePost(${
|
||||
p.id
|
||||
}, '${escapeHtml(p.title)}')">
|
||||
<button class="btn btn-sm btn-danger" onclick="deletePost('${escapeHtml(
|
||||
String(p.id)
|
||||
)}', '${escapeHtml(p.title).replace(/'/g, "'")}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
@@ -94,6 +307,12 @@ function showCreatePost() {
|
||||
document.getElementById("postForm").reset();
|
||||
document.getElementById("postId").value = "";
|
||||
document.getElementById("postPublished").checked = false;
|
||||
document.getElementById("postFeaturedImage").value = "";
|
||||
updateFeaturedImagePreview("");
|
||||
if (quillEditor) {
|
||||
quillEditor.setContents([]);
|
||||
}
|
||||
resetModalSize();
|
||||
postModal.show();
|
||||
}
|
||||
|
||||
@@ -110,33 +329,49 @@ async function editPost(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 || "";
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
showError("Failed to load post details");
|
||||
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 : "";
|
||||
|
||||
const formData = {
|
||||
title: document.getElementById("postTitle").value,
|
||||
slug: document.getElementById("postSlug").value,
|
||||
excerpt: document.getElementById("postExcerpt").value,
|
||||
content: document.getElementById("postContent").value,
|
||||
content: content,
|
||||
featuredimage: document.getElementById("postFeaturedImage").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");
|
||||
showToast("Please fill in all required fields", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,17 +387,18 @@ async function savePost() {
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Post updated successfully" : "Post created successfully"
|
||||
showToast(
|
||||
id ? "Post updated successfully" : "Post created successfully",
|
||||
"success"
|
||||
);
|
||||
postModal.hide();
|
||||
loadPosts();
|
||||
} else {
|
||||
showError(data.message || "Failed to save post");
|
||||
showToast(data.message || "Failed to save post", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save post:", error);
|
||||
showError("Failed to save post");
|
||||
showToast("Failed to save post", "error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,17 +411,53 @@ async function deletePost(id, title) {
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Post deleted successfully");
|
||||
showToast("Post deleted successfully", "success");
|
||||
loadPosts();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete post");
|
||||
showToast(data.message || "Failed to delete post", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete post:", error);
|
||||
showError("Failed to delete post");
|
||||
showToast("Failed to delete post", "error");
|
||||
}
|
||||
}
|
||||
|
||||
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 = `
|
||||
<i class="bi bi-${icons[type] || icons.info}"></i>
|
||||
<span>${message}</span>
|
||||
`;
|
||||
|
||||
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()
|
||||
@@ -194,18 +466,6 @@ function slugify(text) {
|
||||
.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 = {
|
||||
"&": "&",
|
||||
@@ -224,10 +484,3 @@ function formatDate(dateString) {
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
alert(message);
|
||||
}
|
||||
function showError(message) {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user