webupdate
This commit is contained in:
@@ -6,6 +6,20 @@ let quillEditor;
|
||||
let portfolioImages = [];
|
||||
let currentMediaPicker = null;
|
||||
let isModalExpanded = false;
|
||||
let portfolioMediaLibrary = null;
|
||||
|
||||
// Initialize portfolio media library
|
||||
function initPortfolioMediaLibrary() {
|
||||
if (typeof MediaLibrary !== "undefined" && !portfolioMediaLibrary) {
|
||||
portfolioMediaLibrary = new MediaLibrary({
|
||||
selectMode: true,
|
||||
multiple: true, // Allow multiple image selection for portfolio gallery
|
||||
onSelect: function (media) {
|
||||
handleMediaSelection(media);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
projectModal = new bootstrap.Modal(document.getElementById("projectModal"));
|
||||
@@ -19,6 +33,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
// Initialize Quill editor
|
||||
initializeQuillEditor();
|
||||
|
||||
// Initialize media library
|
||||
initPortfolioMediaLibrary();
|
||||
|
||||
checkAuth().then((authenticated) => {
|
||||
if (authenticated) {
|
||||
loadProjects();
|
||||
@@ -123,7 +140,7 @@ async function loadProjects() {
|
||||
title: p.title,
|
||||
isactive: p.isactive,
|
||||
isactiveType: typeof p.isactive,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
renderProjects(projectsData);
|
||||
}
|
||||
@@ -152,7 +169,7 @@ function renderProjects(projects) {
|
||||
console.log(
|
||||
`Project ${p.id}: isactive =`,
|
||||
p.isactive,
|
||||
`(type: ${typeof p.isactive})`
|
||||
`(type: ${typeof p.isactive})`,
|
||||
);
|
||||
const isActive =
|
||||
p.isactive === true || p.isactive === "true" || p.isactive === 1;
|
||||
@@ -174,12 +191,12 @@ function renderProjects(projects) {
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editProject('${escapeHtml(
|
||||
String(p.id)
|
||||
String(p.id),
|
||||
)}')">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteProject('${escapeHtml(
|
||||
String(p.id)
|
||||
String(p.id),
|
||||
)}', '${escapeHtml(p.title).replace(/'/g, "'")}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
@@ -192,7 +209,7 @@ function renderProjects(projects) {
|
||||
function filterProjects() {
|
||||
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
||||
const filtered = projectsData.filter((p) =>
|
||||
p.title.toLowerCase().includes(searchTerm)
|
||||
p.title.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
renderProjects(filtered);
|
||||
}
|
||||
@@ -237,10 +254,31 @@ async function editProject(id) {
|
||||
document.getElementById("projectCategory").value = project.category || "";
|
||||
document.getElementById("projectActive").checked = project.isactive;
|
||||
|
||||
// Load images if available (imageurl field or parse from description)
|
||||
// Load images - check images array first, then fall back to imageurl
|
||||
portfolioImages = [];
|
||||
if (project.imageurl) {
|
||||
// If single image URL exists
|
||||
|
||||
// Try to parse images array
|
||||
if (project.images) {
|
||||
try {
|
||||
const imagesArr =
|
||||
typeof project.images === "string"
|
||||
? JSON.parse(project.images)
|
||||
: project.images;
|
||||
if (Array.isArray(imagesArr) && imagesArr.length > 0) {
|
||||
imagesArr.forEach((url) => {
|
||||
portfolioImages.push({
|
||||
url: url,
|
||||
filename: url.split("/").pop(),
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to parse images:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to imageurl if no images array
|
||||
if (portfolioImages.length === 0 && project.imageurl) {
|
||||
portfolioImages.push({
|
||||
url: project.imageurl,
|
||||
filename: project.imageurl.split("/").pop(),
|
||||
@@ -286,6 +324,7 @@ async function saveProject() {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
cache: "no-cache",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
@@ -294,9 +333,15 @@ async function saveProject() {
|
||||
showSuccess(
|
||||
id
|
||||
? "Project updated successfully! 🎉"
|
||||
: "Project created successfully! 🎉"
|
||||
: "Project created successfully! 🎉",
|
||||
);
|
||||
projectModal.hide();
|
||||
// Immediately add to local data and re-render for instant feedback
|
||||
if (!id && data.project) {
|
||||
projectsData.unshift(data.project);
|
||||
renderProjects(projectsData);
|
||||
}
|
||||
// Also reload from server to ensure full sync
|
||||
loadProjects();
|
||||
} else {
|
||||
showError(data.message || "Failed to save project");
|
||||
@@ -308,23 +353,33 @@ async function saveProject() {
|
||||
}
|
||||
|
||||
async function deleteProject(id, name) {
|
||||
if (!confirm(`Are you sure you want to delete "${name}"?`)) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/portfolio/projects/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Project deleted successfully");
|
||||
loadProjects();
|
||||
} else {
|
||||
showError(data.message || "Failed to delete project");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete project:", error);
|
||||
showError("Failed to delete project");
|
||||
}
|
||||
showDeleteConfirm(
|
||||
`Are you sure you want to delete "${name}"? This action cannot be undone.`,
|
||||
async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/portfolio/projects/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
cache: "no-cache",
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess("Project deleted successfully");
|
||||
// Remove immediately from local data and re-render
|
||||
// Compare as strings to handle type mismatches
|
||||
const deletedId = String(id);
|
||||
projectsData = projectsData.filter((p) => String(p.id) !== deletedId);
|
||||
renderProjects(projectsData);
|
||||
} else {
|
||||
showError(data.message || "Failed to delete project");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete project:", error);
|
||||
showError("Failed to delete project");
|
||||
}
|
||||
},
|
||||
{ title: "Delete Project", confirmText: "Delete Project" },
|
||||
);
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
@@ -380,7 +435,7 @@ function renderPortfolioImages() {
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -395,100 +450,30 @@ function removePortfolioImage(index) {
|
||||
function openMediaLibrary(purpose) {
|
||||
currentMediaPicker = { purpose };
|
||||
|
||||
// Create 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: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
// Initialize if not already
|
||||
initPortfolioMediaLibrary();
|
||||
|
||||
// Create modal
|
||||
const modal = document.createElement("div");
|
||||
modal.style.cssText = `
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
// Create close button
|
||||
const closeBtn = document.createElement("button");
|
||||
closeBtn.innerHTML = "×";
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function closeMediaLibrary() {
|
||||
const backdrop = document.getElementById("mediaLibraryBackdrop");
|
||||
if (backdrop) {
|
||||
backdrop.remove();
|
||||
if (portfolioMediaLibrary) {
|
||||
portfolioMediaLibrary.open();
|
||||
}
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
|
||||
function handleMediaSelection(media) {
|
||||
if (!currentMediaPicker) return;
|
||||
|
||||
if (currentMediaPicker.purpose === "portfolioImages") {
|
||||
// Handle multiple images
|
||||
// Handle multiple images - media can be array or single object
|
||||
const mediaArray = Array.isArray(media) ? media : [media];
|
||||
|
||||
// Add all selected images to portfolio images array
|
||||
mediaArray.forEach((item) => {
|
||||
// Check if image already exists
|
||||
if (!portfolioImages.find((img) => img.url === item.url)) {
|
||||
const itemUrl = item.path || item.url;
|
||||
if (!portfolioImages.find((img) => img.url === itemUrl)) {
|
||||
portfolioImages.push({
|
||||
url: item.url,
|
||||
filename: item.filename || item.url.split("/").pop(),
|
||||
url: itemUrl,
|
||||
filename:
|
||||
item.filename || item.originalName || itemUrl.split("/").pop(),
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -497,7 +482,7 @@ function handleMediaSelection(media) {
|
||||
showSuccess(`${mediaArray.length} image(s) added to portfolio gallery`);
|
||||
}
|
||||
|
||||
closeMediaLibrary();
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
|
||||
// Toast Notification System
|
||||
|
||||
Reference in New Issue
Block a user