webupdate

This commit is contained in:
Local Server
2026-01-18 02:22:05 -06:00
parent 6fc159051a
commit 2a2a3d99e5
135 changed files with 54897 additions and 9825 deletions

View File

@@ -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, "&#39;")}')">
<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