1439 lines
46 KiB
JavaScript
1439 lines
46 KiB
JavaScript
// Pages Management JavaScript
|
|
|
|
let pagesData = [];
|
|
let pageModal;
|
|
let quillEditor;
|
|
let aboutContentEditor;
|
|
let aboutTeamMembers = [];
|
|
let deletedTeamMemberIds = []; // Track deleted team members for database deletion
|
|
let currentMediaPicker = null;
|
|
let pagesMediaLibrary = null;
|
|
|
|
// Initialize pages media library
|
|
function initPagesMediaLibrary() {
|
|
if (typeof MediaLibrary !== "undefined" && !pagesMediaLibrary) {
|
|
pagesMediaLibrary = new MediaLibrary({
|
|
selectMode: true,
|
|
multiple: false,
|
|
onSelect: function (media) {
|
|
handleMediaSelection(media);
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
pageModal = new bootstrap.Modal(document.getElementById("pageModal"));
|
|
initializeQuillEditor();
|
|
initializeAboutEditor();
|
|
initPagesMediaLibrary();
|
|
|
|
checkAuth().then((authenticated) => {
|
|
if (authenticated) {
|
|
loadPages();
|
|
}
|
|
});
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
if (urlParams.get("action") === "create") {
|
|
showCreatePage();
|
|
}
|
|
|
|
// Auto-generate slug from title
|
|
document.getElementById("pageTitle")?.addEventListener("input", function () {
|
|
if (!document.getElementById("pageId").value) {
|
|
document.getElementById("pageSlug").value = slugify(this.value);
|
|
}
|
|
});
|
|
});
|
|
|
|
function initializeQuillEditor() {
|
|
quillEditor = new Quill("#pageContentEditor", {
|
|
theme: "snow",
|
|
modules: {
|
|
toolbar: [
|
|
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
|
[{ font: [] }],
|
|
[{ size: ["small", false, "large", "huge"] }],
|
|
["bold", "italic", "underline", "strike"],
|
|
[{ color: [] }, { background: [] }],
|
|
[{ script: "sub" }, { script: "super" }],
|
|
[{ list: "ordered" }, { list: "bullet" }],
|
|
[{ indent: "-1" }, { indent: "+1" }],
|
|
[{ align: [] }],
|
|
["blockquote", "code-block"],
|
|
["link", "image", "video"],
|
|
["clean"],
|
|
],
|
|
},
|
|
});
|
|
|
|
// Custom image handler to use media library
|
|
const toolbar = quillEditor.getModule("toolbar");
|
|
toolbar.addHandler("image", function () {
|
|
openMediaLibraryForPageEditor();
|
|
});
|
|
}
|
|
|
|
// Open media library for main page editor image insertion
|
|
function openMediaLibraryForPageEditor() {
|
|
if (!pagesMediaLibrary) {
|
|
initPagesMediaLibrary();
|
|
}
|
|
|
|
currentMediaPicker = "pageEditorImage";
|
|
pagesMediaLibrary.show();
|
|
}
|
|
|
|
// Handle media selection for main page editor
|
|
function handlePageEditorImageSelection(media) {
|
|
if (quillEditor && media && media.path) {
|
|
const range = quillEditor.getSelection(true);
|
|
quillEditor.insertEmbed(range.index, "image", media.path);
|
|
quillEditor.setSelection(range.index + 1);
|
|
}
|
|
}
|
|
|
|
function initializeAboutEditor() {
|
|
aboutContentEditor = new Quill("#aboutContentEditor", {
|
|
theme: "snow",
|
|
modules: {
|
|
toolbar: [
|
|
[{ header: [1, 2, 3, false] }],
|
|
["bold", "italic", "underline"],
|
|
[{ color: [] }, { background: [] }],
|
|
[{ list: "ordered" }, { list: "bullet" }],
|
|
[{ align: [] }],
|
|
["link", "image"],
|
|
["clean"],
|
|
],
|
|
},
|
|
placeholder: "Write your page content here...",
|
|
});
|
|
|
|
// Custom image handler to use media library
|
|
const toolbar = aboutContentEditor.getModule("toolbar");
|
|
toolbar.addHandler("image", function () {
|
|
openMediaLibraryForAboutEditor();
|
|
});
|
|
}
|
|
|
|
// Open media library for About editor image insertion
|
|
function openMediaLibraryForAboutEditor() {
|
|
if (!pagesMediaLibrary) {
|
|
initPagesMediaLibrary();
|
|
}
|
|
|
|
currentMediaPicker = "aboutEditorImage";
|
|
pagesMediaLibrary.show();
|
|
}
|
|
|
|
// Handle media selection for About editor
|
|
function handleAboutEditorImageSelection(media) {
|
|
if (aboutContentEditor && media && media.path) {
|
|
const range = aboutContentEditor.getSelection(true);
|
|
aboutContentEditor.insertEmbed(range.index, "image", media.path);
|
|
aboutContentEditor.setSelection(range.index + 1);
|
|
}
|
|
}
|
|
|
|
async function loadPages() {
|
|
try {
|
|
const response = await fetch("/api/admin/pages", {
|
|
credentials: "include",
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
pagesData = data.pages;
|
|
renderPages(pagesData);
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load pages:", error);
|
|
}
|
|
}
|
|
|
|
function renderPages(pages) {
|
|
const tbody = document.getElementById("pagesTableBody");
|
|
if (pages.length === 0) {
|
|
tbody.innerHTML = `
|
|
<tr><td colspan="6" class="text-center p-4">
|
|
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
|
<p class="mt-3 text-muted">No custom pages found</p>
|
|
<button class="btn btn-primary" onclick="showCreatePage()">
|
|
<i class="bi bi-plus-circle"></i> Create Your First Page
|
|
</button>
|
|
</td></tr>`;
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = pages
|
|
.map(
|
|
(p) => `
|
|
<tr>
|
|
<td>${escapeHtml(p.id)}</td>
|
|
<td><strong>${escapeHtml(p.title)}</strong></td>
|
|
<td><code>${escapeHtml(p.slug)}</code></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="editPage('${escapeHtml(
|
|
p.id,
|
|
)}')">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="deletePage('${escapeHtml(
|
|
p.id,
|
|
)}', '${escapeHtml(p.title).replace(/'/g, "\\'")}')">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>`,
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
function filterPages() {
|
|
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
|
|
const filtered = pagesData.filter(
|
|
(p) =>
|
|
p.title.toLowerCase().includes(searchTerm) ||
|
|
p.slug.toLowerCase().includes(searchTerm),
|
|
);
|
|
renderPages(filtered);
|
|
}
|
|
|
|
function showCreatePage() {
|
|
document.getElementById("modalTitle").textContent = "Create Custom Page";
|
|
document.getElementById("pageForm").reset();
|
|
document.getElementById("pageId").value = "";
|
|
document.getElementById("pagePublished").checked = true;
|
|
quillEditor.setContents([]);
|
|
|
|
// Show regular editor by default
|
|
document.getElementById("contactStructuredFields").style.display = "none";
|
|
document.getElementById("aboutWithTeamFields").style.display = "none";
|
|
document.getElementById("privacyContentSection").style.display = "none";
|
|
document.getElementById("regularContentEditor").style.display = "block";
|
|
|
|
pageModal.show();
|
|
}
|
|
|
|
async function editPage(id) {
|
|
try {
|
|
const response = await fetch(`/api/admin/pages/${id}`, {
|
|
credentials: "include",
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
const page = data.page;
|
|
document.getElementById("modalTitle").textContent = "Edit Custom Page";
|
|
document.getElementById("pageId").value = page.id;
|
|
document.getElementById("pageTitle").value = page.title;
|
|
document.getElementById("pageSlug").value = page.slug;
|
|
|
|
// Check if this is the contact page - use structured fields
|
|
if (page.slug === "contact" || page.slug === "page-contact") {
|
|
if (page.pagedata) {
|
|
showContactStructuredFields(page.pagedata);
|
|
} else {
|
|
// Contact page without pagedata, use regular editor
|
|
document.getElementById("contactStructuredFields").style.display =
|
|
"none";
|
|
document.getElementById("aboutWithTeamFields").style.display = "none";
|
|
document.getElementById("regularContentEditor").style.display =
|
|
"block";
|
|
|
|
if (page.content) {
|
|
try {
|
|
const delta = JSON.parse(page.content);
|
|
quillEditor.setContents(delta);
|
|
} catch {
|
|
quillEditor.clipboard.dangerouslyPasteHTML(page.content);
|
|
}
|
|
} else {
|
|
quillEditor.setContents([]);
|
|
}
|
|
}
|
|
} else if (
|
|
page.slug === "about" ||
|
|
page.slug === "page-about" ||
|
|
page.slug.includes("about")
|
|
) {
|
|
// Show About page with team members
|
|
await showAboutWithTeamFields(page);
|
|
} else if (
|
|
page.slug === "privacy" ||
|
|
page.slug === "page-privacy" ||
|
|
page.slug.includes("privacy") ||
|
|
page.slug === "shipping" ||
|
|
page.slug === "shipping-info" ||
|
|
page.slug.includes("shipping") ||
|
|
page.slug === "returns" ||
|
|
page.slug.includes("return")
|
|
) {
|
|
// Show Privacy/Shipping/Returns page with structured fields
|
|
if (page.pagedata) {
|
|
showPrivacyStructuredFields(page.pagedata);
|
|
} else {
|
|
// No pagedata, use regular editor
|
|
document.getElementById("contactStructuredFields").style.display =
|
|
"none";
|
|
document.getElementById("aboutWithTeamFields").style.display = "none";
|
|
document.getElementById("privacyContentSection").style.display =
|
|
"none";
|
|
document.getElementById("regularContentEditor").style.display =
|
|
"block";
|
|
|
|
if (page.content) {
|
|
try {
|
|
const delta = JSON.parse(page.content);
|
|
quillEditor.setContents(delta);
|
|
} catch {
|
|
quillEditor.clipboard.dangerouslyPasteHTML(page.content);
|
|
}
|
|
} else {
|
|
quillEditor.setContents([]);
|
|
}
|
|
}
|
|
} else {
|
|
// Use regular Quill editor for all other pages (privacy, etc)
|
|
document.getElementById("contactStructuredFields").style.display =
|
|
"none";
|
|
document.getElementById("aboutWithTeamFields").style.display = "none";
|
|
document.getElementById("regularContentEditor").style.display = "block";
|
|
|
|
// Set Quill editor content
|
|
if (page.content) {
|
|
try {
|
|
const delta = JSON.parse(page.content);
|
|
quillEditor.setContents(delta);
|
|
} catch {
|
|
// If content is plain HTML/text, set it directly
|
|
quillEditor.clipboard.dangerouslyPasteHTML(page.content);
|
|
}
|
|
} else {
|
|
quillEditor.setContents([]);
|
|
}
|
|
}
|
|
|
|
document.getElementById("pageMetaTitle").value = page.metatitle || "";
|
|
document.getElementById("pageMetaDescription").value =
|
|
page.metadescription || "";
|
|
document.getElementById("pagePublished").checked = page.ispublished;
|
|
pageModal.show();
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load page:", error);
|
|
showError("Failed to load page details");
|
|
}
|
|
}
|
|
|
|
function showContactStructuredFields(pagedata) {
|
|
// Hide regular editor, show structured fields
|
|
document.getElementById("regularContentEditor").style.display = "none";
|
|
document.getElementById("aboutWithTeamFields").style.display = "none";
|
|
document.getElementById("contactStructuredFields").style.display = "block";
|
|
|
|
// Populate header fields
|
|
if (pagedata.header) {
|
|
document.getElementById("contactHeaderTitle").value =
|
|
pagedata.header.title || "";
|
|
document.getElementById("contactHeaderSubtitle").value =
|
|
pagedata.header.subtitle || "";
|
|
}
|
|
|
|
// Populate contact info
|
|
if (pagedata.contactInfo) {
|
|
document.getElementById("contactPhone").value =
|
|
pagedata.contactInfo.phone || "";
|
|
document.getElementById("contactEmail").value =
|
|
pagedata.contactInfo.email || "";
|
|
document.getElementById("contactAddress").value =
|
|
pagedata.contactInfo.address || "";
|
|
}
|
|
|
|
// Populate business hours
|
|
if (pagedata.businessHours) {
|
|
renderBusinessHours(pagedata.businessHours);
|
|
}
|
|
}
|
|
|
|
function renderBusinessHours(hours) {
|
|
const container = document.getElementById("businessHoursList");
|
|
container.innerHTML = hours
|
|
.map(
|
|
(hour, index) => `
|
|
<div class="row mb-2" data-hour-index="${index}">
|
|
<div class="col-md-5">
|
|
<input type="text" class="form-control" placeholder="Days (e.g., Monday - Friday)"
|
|
value="${escapeHtml(hour.days)}" data-field="days">
|
|
</div>
|
|
<div class="col-md-5">
|
|
<input type="text" class="form-control" placeholder="Hours (e.g., 9:00 AM - 6:00 PM)"
|
|
value="${escapeHtml(hour.hours)}" data-field="hours">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="button" class="btn btn-sm btn-danger" onclick="removeBusinessHour(${index})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`,
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
function addBusinessHour() {
|
|
const container = document.getElementById("businessHoursList");
|
|
const index = container.children.length;
|
|
const newHour = document.createElement("div");
|
|
newHour.className = "row mb-2";
|
|
newHour.setAttribute("data-hour-index", index);
|
|
newHour.innerHTML = `
|
|
<div class="col-md-5">
|
|
<input type="text" class="form-control" placeholder="Days (e.g., Monday - Friday)" data-field="days">
|
|
</div>
|
|
<div class="col-md-5">
|
|
<input type="text" class="form-control" placeholder="Hours (e.g., 9:00 AM - 6:00 PM)" data-field="hours">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="button" class="btn btn-sm btn-danger" onclick="removeBusinessHour(${index})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
`;
|
|
container.appendChild(newHour);
|
|
}
|
|
|
|
function removeBusinessHour(index) {
|
|
const container = document.getElementById("businessHoursList");
|
|
const hourRow = container.querySelector(`[data-hour-index="${index}"]`);
|
|
if (hourRow) {
|
|
hourRow.remove();
|
|
}
|
|
}
|
|
|
|
// Privacy Policy Page Functions
|
|
let privacySectionEditors = []; // Array to store Quill editors for each section
|
|
|
|
function showPrivacyStructuredFields(pagedata) {
|
|
// Hide regular editor, show privacy fields
|
|
document.getElementById("regularContentEditor").style.display = "none";
|
|
document.getElementById("aboutWithTeamFields").style.display = "none";
|
|
document.getElementById("contactStructuredFields").style.display = "none";
|
|
document.getElementById("privacyContentSection").style.display = "block";
|
|
|
|
// Populate header fields
|
|
if (pagedata.header) {
|
|
document.getElementById("privacyHeaderTitle").value =
|
|
pagedata.header.title || "";
|
|
}
|
|
|
|
// Populate last updated
|
|
document.getElementById("privacyLastUpdated").value =
|
|
pagedata.lastUpdated || "";
|
|
|
|
// Populate sections
|
|
if (pagedata.sections && pagedata.sections.length > 0) {
|
|
renderPrivacySections(pagedata.sections);
|
|
} else {
|
|
// Start with one empty section
|
|
privacySectionEditors = [];
|
|
document.getElementById("privacySectionsContainer").innerHTML = "";
|
|
}
|
|
}
|
|
|
|
function renderPrivacySections(sections) {
|
|
const container = document.getElementById("privacySectionsContainer");
|
|
privacySectionEditors = []; // Reset editors array
|
|
container.innerHTML = "";
|
|
|
|
sections.forEach((section, index) => {
|
|
addPrivacySectionWithData(section, index);
|
|
});
|
|
}
|
|
|
|
function addPrivacySection() {
|
|
addPrivacySectionWithData(
|
|
{ title: "", content: "" },
|
|
privacySectionEditors.length,
|
|
);
|
|
}
|
|
|
|
function addPrivacySectionWithData(section, index) {
|
|
const container = document.getElementById("privacySectionsContainer");
|
|
const sectionDiv = document.createElement("div");
|
|
sectionDiv.className = "contact-field-group mb-4";
|
|
sectionDiv.setAttribute("data-section-index", index);
|
|
|
|
// Create unique IDs for this section's editor
|
|
const editorId = `privacySectionContent_${index}`;
|
|
|
|
sectionDiv.innerHTML = `
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h6><i class="bi bi-file-text"></i> Section ${index + 1}</h6>
|
|
<button type="button" class="btn btn-sm btn-danger" onclick="removePrivacySection(${index})">
|
|
<i class="bi bi-trash"></i> Remove
|
|
</button>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Section Title</label>
|
|
<input type="text" class="form-control"
|
|
value="${escapeHtml(section.title || "")}"
|
|
data-field="title"
|
|
placeholder="e.g., Information We Collect">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Content</label>
|
|
<div class="editor-wrapper">
|
|
<div id="${editorId}" style="min-height: 200px;"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(sectionDiv);
|
|
|
|
// Initialize Quill editor for this section's content
|
|
const editor = new Quill(`#${editorId}`, {
|
|
theme: "snow",
|
|
modules: {
|
|
toolbar: [
|
|
[{ header: [2, 3, false] }],
|
|
["bold", "italic", "underline"],
|
|
[{ list: "ordered" }, { list: "bullet" }],
|
|
["link"],
|
|
["clean"],
|
|
],
|
|
},
|
|
});
|
|
|
|
// Set the content if provided
|
|
if (section.content) {
|
|
try {
|
|
const delta = JSON.parse(section.content);
|
|
editor.setContents(delta);
|
|
} catch {
|
|
// If it's plain text/HTML, set it directly
|
|
editor.clipboard.dangerouslyPasteHTML(section.content);
|
|
}
|
|
}
|
|
|
|
privacySectionEditors[index] = editor;
|
|
}
|
|
|
|
function removePrivacySection(index) {
|
|
const container = document.getElementById("privacySectionsContainer");
|
|
const sectionDiv = container.querySelector(`[data-section-index="${index}"]`);
|
|
if (sectionDiv) {
|
|
sectionDiv.remove();
|
|
// Remove editor from array (set to null to keep indices)
|
|
privacySectionEditors[index] = null;
|
|
}
|
|
}
|
|
|
|
// About Page with Team Members Functions
|
|
async function showAboutWithTeamFields(page) {
|
|
// Hide other editors
|
|
document.getElementById("contactStructuredFields").style.display = "none";
|
|
document.getElementById("regularContentEditor").style.display = "none";
|
|
document.getElementById("aboutWithTeamFields").style.display = "block";
|
|
|
|
// Set About content
|
|
if (page.content) {
|
|
try {
|
|
const delta = JSON.parse(page.content);
|
|
aboutContentEditor.setContents(delta);
|
|
} catch {
|
|
aboutContentEditor.clipboard.dangerouslyPasteHTML(page.content);
|
|
}
|
|
} else {
|
|
aboutContentEditor.setContents([]);
|
|
}
|
|
|
|
// Load team members from database
|
|
await loadTeamMembersForAbout();
|
|
}
|
|
|
|
async function loadTeamMembersForAbout() {
|
|
try {
|
|
// Reset the deleted IDs when loading fresh data
|
|
deletedTeamMemberIds = [];
|
|
|
|
const response = await fetch("/api/admin/team-members", {
|
|
credentials: "include",
|
|
});
|
|
const data = await response.json();
|
|
if (data.success && data.teamMembers) {
|
|
aboutTeamMembers = data.teamMembers;
|
|
displayTeamMembersInEditor();
|
|
}
|
|
} catch (error) {
|
|
console.error("Error loading team members:", error);
|
|
}
|
|
}
|
|
|
|
function displayTeamMembersInEditor() {
|
|
const container = document.getElementById("teamMembersList");
|
|
|
|
if (aboutTeamMembers.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="col-12 text-center text-muted py-3">
|
|
<i class="bi bi-people" style="font-size: 3rem;"></i>
|
|
<p class="mt-2">No team members yet. Click "Add Member" to get started.</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = aboutTeamMembers
|
|
.map(
|
|
(member, index) => `
|
|
<div class="col-md-6 col-lg-4">
|
|
<div class="team-member-card">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<span class="team-member-handle">
|
|
<i class="bi bi-grip-vertical"></i>
|
|
</span>
|
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeTeamMemberFromAbout(${index})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
<div class="team-member-preview">
|
|
${
|
|
member.image_url
|
|
? `<img src="${member.image_url}" alt="${escapeHtml(
|
|
member.name,
|
|
)}" />`
|
|
: `<i class="bi bi-person-circle"></i>`
|
|
}
|
|
</div>
|
|
<div class="mb-2">
|
|
<input type="text" class="form-control form-control-sm mb-2"
|
|
value="${escapeHtml(member.name)}"
|
|
placeholder="Name"
|
|
onchange="updateTeamMember(${index}, 'name', this.value)">
|
|
</div>
|
|
<div class="mb-2">
|
|
<input type="text" class="form-control form-control-sm mb-2"
|
|
value="${escapeHtml(member.position)}"
|
|
placeholder="Position"
|
|
onchange="updateTeamMember(${index}, 'position', this.value)">
|
|
</div>
|
|
<div class="mb-2">
|
|
<textarea class="form-control form-control-sm mb-2"
|
|
rows="2"
|
|
placeholder="Bio"
|
|
onchange="updateTeamMember(${index}, 'bio', this.value)">${escapeHtml(
|
|
member.bio || "",
|
|
)}</textarea>
|
|
</div>
|
|
<div class="mb-2">
|
|
<div class="input-group input-group-sm">
|
|
<input type="text" class="form-control"
|
|
value="${member.image_url || ""}"
|
|
placeholder="Image URL"
|
|
onchange="updateTeamMember(${index}, 'image_url', this.value)">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="selectImageForMember(${index})">
|
|
<i class="bi bi-image"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="mb-2">
|
|
<input type="number" class="form-control form-control-sm"
|
|
value="${member.display_order || 0}"
|
|
placeholder="Order"
|
|
onchange="updateTeamMember(${index}, 'display_order', this.value)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`,
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
function addTeamMember() {
|
|
const newMember = {
|
|
id: null,
|
|
name: "",
|
|
position: "",
|
|
bio: "",
|
|
image_url: "",
|
|
display_order: aboutTeamMembers.length,
|
|
};
|
|
aboutTeamMembers.push(newMember);
|
|
displayTeamMembersInEditor();
|
|
}
|
|
|
|
function updateTeamMember(index, field, value) {
|
|
if (aboutTeamMembers[index]) {
|
|
aboutTeamMembers[index][field] = value;
|
|
}
|
|
}
|
|
|
|
function removeTeamMemberFromAbout(index) {
|
|
const member = aboutTeamMembers[index];
|
|
// If member has an ID, track it for deletion from database
|
|
if (member && member.id) {
|
|
deletedTeamMemberIds.push(member.id);
|
|
}
|
|
aboutTeamMembers.splice(index, 1);
|
|
displayTeamMembersInEditor();
|
|
}
|
|
|
|
function selectImageForMember(index) {
|
|
currentMediaPicker = { purpose: "teamMember", index };
|
|
|
|
// Initialize if not already
|
|
initPagesMediaLibrary();
|
|
|
|
if (pagesMediaLibrary) {
|
|
pagesMediaLibrary.open();
|
|
}
|
|
}
|
|
|
|
function handleMediaSelection(media) {
|
|
if (!currentMediaPicker) return;
|
|
|
|
// Handle About editor image insertion
|
|
if (currentMediaPicker === "aboutEditorImage") {
|
|
handleAboutEditorImageSelection(media);
|
|
currentMediaPicker = null;
|
|
return;
|
|
}
|
|
|
|
// Handle main page editor image insertion
|
|
if (currentMediaPicker === "pageEditorImage") {
|
|
handlePageEditorImageSelection(media);
|
|
currentMediaPicker = null;
|
|
return;
|
|
}
|
|
|
|
if (currentMediaPicker.purpose === "teamMember") {
|
|
const index = currentMediaPicker.index;
|
|
if (aboutTeamMembers[index]) {
|
|
aboutTeamMembers[index].image_url = media.path;
|
|
displayTeamMembersInEditor();
|
|
}
|
|
}
|
|
|
|
currentMediaPicker = null;
|
|
}
|
|
|
|
async function saveTeamMembers() {
|
|
try {
|
|
// First, delete any removed team members from the database
|
|
for (const memberId of deletedTeamMemberIds) {
|
|
try {
|
|
await fetch(`/api/admin/team-members/${memberId}`, {
|
|
method: "DELETE",
|
|
credentials: "include",
|
|
});
|
|
console.log(`Deleted team member ${memberId}`);
|
|
} catch (err) {
|
|
console.error(`Failed to delete team member ${memberId}:`, err);
|
|
}
|
|
}
|
|
// Clear the deleted IDs array after processing
|
|
deletedTeamMemberIds = [];
|
|
|
|
// Save or update each team member
|
|
for (const member of aboutTeamMembers) {
|
|
if (!member.name || !member.position) continue; // Skip incomplete members
|
|
|
|
const payload = {
|
|
name: member.name,
|
|
position: member.position,
|
|
bio: member.bio || "",
|
|
image_url: member.image_url || "",
|
|
display_order: parseInt(member.display_order) || 0,
|
|
};
|
|
|
|
if (member.id) {
|
|
// Update existing
|
|
await fetch(`/api/admin/team-members/${member.id}`, {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
} else {
|
|
// Create new
|
|
const response = await fetch("/api/admin/team-members", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const data = await response.json();
|
|
if (data.success && data.teamMember) {
|
|
member.id = data.teamMember.id; // Update with new ID
|
|
}
|
|
}
|
|
}
|
|
console.log("Team members saved successfully");
|
|
} catch (error) {
|
|
console.error("Error saving team members:", error);
|
|
}
|
|
}
|
|
|
|
async function savePage() {
|
|
const id = document.getElementById("pageId").value;
|
|
const slug = document.getElementById("pageSlug").value;
|
|
|
|
let formData = {
|
|
title: document.getElementById("pageTitle").value,
|
|
slug: slug,
|
|
metatitle: document.getElementById("pageMetaTitle").value,
|
|
metadescription: document.getElementById("pageMetaDescription").value,
|
|
ispublished: document.getElementById("pagePublished").checked,
|
|
};
|
|
|
|
// Check if this is About page with team members
|
|
if (
|
|
(slug === "about" || slug === "page-about") &&
|
|
document.getElementById("aboutWithTeamFields").style.display !== "none"
|
|
) {
|
|
// Get About content from editor
|
|
const contentDelta = aboutContentEditor.getContents();
|
|
formData.content = JSON.stringify(contentDelta);
|
|
|
|
// Save team members separately
|
|
await saveTeamMembers();
|
|
}
|
|
// Check if this is contact page with structured fields visible
|
|
else if (
|
|
slug === "contact" &&
|
|
document.getElementById("contactStructuredFields").style.display !== "none"
|
|
) {
|
|
// Collect structured data
|
|
const pagedata = {
|
|
header: {
|
|
title: document.getElementById("contactHeaderTitle").value,
|
|
subtitle: document.getElementById("contactHeaderSubtitle").value,
|
|
},
|
|
contactInfo: {
|
|
phone: document.getElementById("contactPhone").value,
|
|
email: document.getElementById("contactEmail").value,
|
|
address: document.getElementById("contactAddress").value,
|
|
},
|
|
businessHours: [],
|
|
};
|
|
|
|
// Collect business hours
|
|
const hourRows = document.getElementById("businessHoursList").children;
|
|
for (let row of hourRows) {
|
|
const days = row.querySelector('[data-field="days"]').value;
|
|
const hours = row.querySelector('[data-field="hours"]').value;
|
|
if (days && hours) {
|
|
pagedata.businessHours.push({ days, hours });
|
|
}
|
|
}
|
|
|
|
// Generate HTML from structured data
|
|
const generatedHTML = generateContactHTML(pagedata);
|
|
|
|
formData.pagedata = pagedata;
|
|
formData.content = generatedHTML; // Store HTML in content field
|
|
formData.contenthtml = generatedHTML; // Also in contenthtml
|
|
}
|
|
// Check if this is privacy/shipping/returns page with structured fields
|
|
else if (
|
|
(slug.includes("privacy") ||
|
|
slug.includes("shipping") ||
|
|
slug.includes("return")) &&
|
|
document.getElementById("privacyContentSection").style.display !== "none"
|
|
) {
|
|
// Collect structured privacy data
|
|
const pagedata = {
|
|
header: {
|
|
title: document.getElementById("privacyHeaderTitle").value,
|
|
},
|
|
lastUpdated: document.getElementById("privacyLastUpdated").value,
|
|
sections: [],
|
|
};
|
|
|
|
// Collect sections
|
|
const sectionDivs = document.getElementById(
|
|
"privacySectionsContainer",
|
|
).children;
|
|
for (let i = 0; i < sectionDivs.length; i++) {
|
|
const sectionDiv = sectionDivs[i];
|
|
const title = sectionDiv.querySelector('[data-field="title"]').value;
|
|
const editor = privacySectionEditors[i];
|
|
|
|
if (editor && title) {
|
|
// Get content as Delta JSON
|
|
const contentDelta = editor.getContents();
|
|
const contentHTML = editor.root.innerHTML;
|
|
|
|
pagedata.sections.push({
|
|
title: title,
|
|
content: JSON.stringify(contentDelta), // Store as Delta
|
|
contentHTML: contentHTML, // Also store rendered HTML
|
|
});
|
|
}
|
|
}
|
|
|
|
// Store the structured data
|
|
formData.pagedata = pagedata;
|
|
|
|
// Also generate and store as content for backwards compatibility
|
|
const generatedHTML = generatePrivacyHTML(pagedata);
|
|
formData.content = generatedHTML;
|
|
formData.contenthtml = generatedHTML;
|
|
} else {
|
|
// Use Quill editor content for other pages
|
|
const contentDelta = quillEditor.getContents();
|
|
const contentHTML = quillEditor.root.innerHTML;
|
|
|
|
formData.content = JSON.stringify(contentDelta); // Store as Delta for editing
|
|
formData.contenthtml = contentHTML; // Store rendered HTML for display
|
|
|
|
if (contentDelta.ops.length === 0) {
|
|
showError("Please fill in all required fields");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!formData.title || !formData.slug) {
|
|
showError("Please fill in all required fields");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const url = id ? `/api/admin/pages/${id}` : "/api/admin/pages";
|
|
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 ? "Page updated successfully" : "Page created successfully",
|
|
);
|
|
pageModal.hide();
|
|
loadPages();
|
|
} else {
|
|
showError(data.message || "Failed to save page");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to save page:", error);
|
|
showError("Failed to save page");
|
|
}
|
|
}
|
|
|
|
function generateContactHTML(pagedata) {
|
|
const { header, contactInfo, businessHours } = pagedata;
|
|
|
|
// Generate business hours HTML
|
|
const businessHoursHTML = businessHours
|
|
.map(
|
|
(hour) => `
|
|
<div>
|
|
<p style="font-weight: 600; margin-bottom: 8px;">${escapeHtml(
|
|
hour.days,
|
|
)}</p>
|
|
<p style="opacity: 0.95; margin: 0;">${escapeHtml(hour.hours)}</p>
|
|
</div>
|
|
`,
|
|
)
|
|
.join("");
|
|
|
|
return `
|
|
<div style="text-align: center; margin-bottom: 48px;">
|
|
<h2 style="font-size: 2rem; font-weight: 700; color: #2d3436; margin-bottom: 12px;">
|
|
${escapeHtml(header.title)}
|
|
</h2>
|
|
<p style="font-size: 1rem; color: #636e72">
|
|
${escapeHtml(header.subtitle)}
|
|
</p>
|
|
</div>
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px; margin-bottom: 48px;">
|
|
<!-- Phone Card -->
|
|
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 32px; border-radius: 16px; text-align: center; color: white; box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);">
|
|
<div style="font-size: 48px; margin-bottom: 16px;">
|
|
<i class="bi bi-telephone-fill"></i>
|
|
</div>
|
|
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;">Phone</h3>
|
|
<p style="font-size: 1rem; opacity: 0.9; margin: 0;">${escapeHtml(
|
|
contactInfo.phone,
|
|
)}</p>
|
|
</div>
|
|
|
|
<!-- Email Card -->
|
|
<div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 32px; border-radius: 16px; text-align: center; color: white; box-shadow: 0 8px 24px rgba(240, 147, 251, 0.3);">
|
|
<div style="font-size: 48px; margin-bottom: 16px;">
|
|
<i class="bi bi-envelope-fill"></i>
|
|
</div>
|
|
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;">Email</h3>
|
|
<p style="font-size: 1rem; opacity: 0.9; margin: 0;">${escapeHtml(
|
|
contactInfo.email,
|
|
)}</p>
|
|
</div>
|
|
|
|
<!-- Location Card -->
|
|
<div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); padding: 32px; border-radius: 16px; text-align: center; color: white; box-shadow: 0 8px 24px rgba(79, 172, 254, 0.3);">
|
|
<div style="font-size: 48px; margin-bottom: 16px;">
|
|
<i class="bi bi-geo-alt-fill"></i>
|
|
</div>
|
|
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;">Location</h3>
|
|
<p style="font-size: 1rem; opacity: 0.9; margin: 0;">${escapeHtml(
|
|
contactInfo.address,
|
|
)}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Business Hours -->
|
|
<div style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); padding: 40px; border-radius: 16px; text-align: center; color: white; box-shadow: 0 8px 24px rgba(250, 112, 154, 0.3);">
|
|
<h3 style="font-size: 1.5rem; font-weight: 700; margin-bottom: 24px;">Business Hours</h3>
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; max-width: 800px; margin: 0 auto;">
|
|
${businessHoursHTML}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function generatePrivacyHTML(pagedata) {
|
|
const { header, lastUpdated, sections } = pagedata;
|
|
|
|
// Generate sections HTML
|
|
const sectionsHTML = sections
|
|
.map((section) => {
|
|
// Parse the content Delta if it's stored as JSON
|
|
let contentHTML = section.contentHTML;
|
|
if (!contentHTML && section.content) {
|
|
try {
|
|
// If we don't have contentHTML, try to get it from the content field
|
|
contentHTML = section.content;
|
|
} catch {
|
|
contentHTML = section.content;
|
|
}
|
|
}
|
|
|
|
return `
|
|
<h2>${escapeHtml(section.title)}</h2>
|
|
<div class="section-content">${contentHTML}</div>
|
|
`;
|
|
})
|
|
.join("");
|
|
|
|
return `
|
|
<div style="max-width: 900px; margin: 0 auto;">
|
|
${lastUpdated ? `<p class="policy-meta" style="color: #636e72; font-style: italic; margin-bottom: 24px;">Last updated: ${escapeHtml(lastUpdated)}</p>` : ""}
|
|
${sectionsHTML}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
async function deletePage(id, title) {
|
|
// Show custom confirmation modal instead of browser confirm
|
|
showConfirmation(
|
|
`Are you sure you want to delete "<strong>${escapeHtml(
|
|
title,
|
|
)}</strong>"?<br><br>` +
|
|
`<small class="text-muted">This action cannot be undone.</small>`,
|
|
async () => {
|
|
try {
|
|
const response = await fetch(`/api/admin/pages/${id}`, {
|
|
method: "DELETE",
|
|
credentials: "include",
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
showSuccess("Page deleted successfully");
|
|
loadPages();
|
|
} else {
|
|
showError(data.message || "Failed to delete page");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to delete page:", error);
|
|
showError("Failed to delete page");
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
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",
|
|
});
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
showNotification(message, "success");
|
|
}
|
|
|
|
function showError(message) {
|
|
showNotification(message, "error");
|
|
}
|
|
|
|
function showNotification(message, type) {
|
|
const modal = new bootstrap.Modal(
|
|
document.getElementById("notificationModal"),
|
|
);
|
|
const modalContent = document.getElementById("notificationModalContent");
|
|
const modalHeader = document.getElementById("notificationModalHeader");
|
|
const modalIcon = document.getElementById("notificationModalIcon");
|
|
const modalTitleText = document.getElementById("notificationModalTitleText");
|
|
const modalBody = document.getElementById("notificationModalBody");
|
|
|
|
// Configure based on type
|
|
if (type === "success") {
|
|
modalContent.classList.remove("border-danger");
|
|
modalContent.classList.add("border-success");
|
|
modalContent.style.borderWidth = "3px";
|
|
modalHeader.classList.remove("bg-danger", "text-white");
|
|
modalHeader.classList.add("bg-success", "text-white");
|
|
modalIcon.className = "bi bi-check-circle-fill me-2";
|
|
modalTitleText.textContent = "Success";
|
|
modalBody.innerHTML = `<p class="mb-0"><i class="bi bi-check-circle text-success me-2"></i>${escapeHtml(
|
|
message,
|
|
)}</p>`;
|
|
} else {
|
|
modalContent.classList.remove("border-success");
|
|
modalContent.classList.add("border-danger");
|
|
modalContent.style.borderWidth = "3px";
|
|
modalHeader.classList.remove("bg-success", "text-white");
|
|
modalHeader.classList.add("bg-danger", "text-white");
|
|
modalIcon.className = "bi bi-exclamation-triangle-fill me-2";
|
|
modalTitleText.textContent = "Error";
|
|
modalBody.innerHTML = `<p class="mb-0"><i class="bi bi-x-circle text-danger me-2"></i>${escapeHtml(
|
|
message,
|
|
)}</p>`;
|
|
}
|
|
|
|
modal.show();
|
|
}
|
|
|
|
function showConfirmation(message, onConfirm) {
|
|
const modal = new bootstrap.Modal(document.getElementById("confirmModal"));
|
|
const modalBody = document.getElementById("confirmModalBody");
|
|
const confirmButton = document.getElementById("confirmModalButton");
|
|
|
|
// Set message
|
|
modalBody.innerHTML = `<p class="mb-0">${message}</p>`;
|
|
|
|
// Remove old event listeners by cloning button
|
|
const newConfirmButton = confirmButton.cloneNode(true);
|
|
confirmButton.parentNode.replaceChild(newConfirmButton, confirmButton);
|
|
|
|
// Add new click handler
|
|
newConfirmButton.addEventListener("click", () => {
|
|
modal.hide();
|
|
onConfirm();
|
|
});
|
|
|
|
modal.show();
|
|
}
|
|
|
|
// Modal Drag and Resize Functionality - DISABLED
|
|
// function initializeModalDragResize() {
|
|
// const modal = document.getElementById("pageModal");
|
|
// const modalDialog = modal.querySelector(".modal-dialog");
|
|
// const modalContent = modal.querySelector(".modal-content");
|
|
// const modalHeader = modal.querySelector(".modal-header");
|
|
// const resizeHandle = modal.querySelector(".modal-resize-handle");
|
|
//
|
|
// let isDragging = false;
|
|
// let isResizing = false;
|
|
// let startX, startY, startWidth, startHeight, startLeft, startTop;
|
|
//
|
|
// // Dragging functionality
|
|
// modalHeader.addEventListener("mousedown", function (e) {
|
|
// // Don't drag if clicking on buttons
|
|
// if (
|
|
// e.target.classList.contains("btn-close") ||
|
|
// e.target.classList.contains("btn-fullscreen") ||
|
|
// e.target.closest(".btn-close") ||
|
|
// e.target.closest(".btn-fullscreen")
|
|
// ) {
|
|
// return;
|
|
// }
|
|
//
|
|
// isDragging = true;
|
|
// const rect = modalDialog.getBoundingClientRect();
|
|
// startX = e.clientX - rect.left;
|
|
// startY = e.clientY - rect.top;
|
|
// modalDialog.style.margin = "0";
|
|
// modalDialog.style.position = "fixed";
|
|
// e.preventDefault();
|
|
// });
|
|
//
|
|
// document.addEventListener("mousemove", function (e) {
|
|
// if (isDragging) {
|
|
// const newLeft = e.clientX - startX;
|
|
// const newTop = e.clientY - startY;
|
|
//
|
|
// // Keep within viewport bounds
|
|
// const maxLeft = window.innerWidth - modalDialog.offsetWidth;
|
|
// const maxTop = window.innerHeight - modalDialog.offsetHeight;
|
|
//
|
|
// modalDialog.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
|
|
// modalDialog.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
|
|
// }
|
|
//
|
|
// if (isResizing) {
|
|
// const newWidth = startWidth + (e.clientX - startX);
|
|
// const newHeight = startHeight + (e.clientY - startY);
|
|
//
|
|
// // Set minimum and maximum sizes
|
|
// const minWidth = 400;
|
|
// const maxWidth = window.innerWidth - 40;
|
|
// const minHeight = 300;
|
|
// const maxHeight = window.innerHeight - 40;
|
|
//
|
|
// modalDialog.style.maxWidth =
|
|
// Math.max(minWidth, Math.min(newWidth, maxWidth)) + "px";
|
|
// modalContent.style.maxHeight =
|
|
// Math.max(minHeight, Math.min(newHeight, maxHeight)) + "px";
|
|
// modalContent.style.height = modalContent.style.maxHeight;
|
|
//
|
|
// // Adjust body height
|
|
// const bodyMaxHeight = parseInt(modalContent.style.maxHeight) - 140 + "px";
|
|
// modal.querySelector(".modal-body").style.maxHeight = bodyMaxHeight;
|
|
// }
|
|
// });
|
|
//
|
|
// document.addEventListener("mouseup", function () {
|
|
// isDragging = false;
|
|
// isResizing = false;
|
|
// });
|
|
//
|
|
// // Resizing functionality
|
|
// if (resizeHandle) {
|
|
// resizeHandle.addEventListener("mousedown", function (e) {
|
|
// isResizing = true;
|
|
// const rect = modalDialog.getBoundingClientRect();
|
|
// startX = e.clientX;
|
|
// startY = e.clientY;
|
|
// startWidth = rect.width;
|
|
// startHeight = modalContent.offsetHeight;
|
|
// e.preventDefault();
|
|
// e.stopPropagation();
|
|
// });
|
|
// }
|
|
//
|
|
// // Reset position when modal is closed
|
|
// modal.addEventListener("hidden.bs.modal", function () {
|
|
// modalDialog.style.position = "";
|
|
// modalDialog.style.left = "";
|
|
// modalDialog.style.top = "";
|
|
// modalDialog.style.margin = "1.75rem auto";
|
|
// });
|
|
// }
|
|
|
|
// Toggle Fullscreen
|
|
function toggleFullscreen() {
|
|
const modal = document.getElementById("pageModal");
|
|
const icon = document.getElementById("fullscreenIcon");
|
|
|
|
if (modal.classList.contains("modal-fullscreen")) {
|
|
modal.classList.remove("modal-fullscreen");
|
|
icon.classList.remove("bi-fullscreen-exit");
|
|
icon.classList.add("bi-arrows-fullscreen");
|
|
} else {
|
|
modal.classList.add("modal-fullscreen");
|
|
icon.classList.remove("bi-arrows-fullscreen");
|
|
icon.classList.add("bi-fullscreen-exit");
|
|
}
|
|
}
|
|
|
|
// Toggle Content Expand/Collapse
|
|
function toggleContentExpand(editorType) {
|
|
if (editorType === "quillEditor") {
|
|
const container = document.querySelector(".ql-container");
|
|
const icon = document.getElementById("quillExpandIcon");
|
|
|
|
if (container.classList.contains("expanded")) {
|
|
container.classList.remove("expanded");
|
|
icon.classList.remove("bi-arrows-collapse");
|
|
icon.classList.add("bi-arrows-expand");
|
|
} else {
|
|
container.classList.add("expanded");
|
|
icon.classList.remove("bi-arrows-expand");
|
|
icon.classList.add("bi-arrows-collapse");
|
|
}
|
|
} else if (editorType === "contactStructuredFields") {
|
|
const container = document.getElementById("contactStructuredFields");
|
|
const icon = document.getElementById("contactExpandIcon");
|
|
|
|
if (container.classList.contains("expanded")) {
|
|
container.classList.remove("expanded");
|
|
icon.classList.remove("bi-arrows-collapse");
|
|
icon.classList.add("bi-arrows-expand");
|
|
} else {
|
|
container.classList.add("expanded");
|
|
icon.classList.remove("bi-arrows-expand");
|
|
icon.classList.add("bi-arrows-collapse");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Simple Drag-to-Resize for Editor Content
|
|
(function () {
|
|
let resizeState = null;
|
|
|
|
document.addEventListener("mousedown", function (e) {
|
|
if (e.target.classList.contains("editor-resize-handle")) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const targetId = e.target.getAttribute("data-target");
|
|
const targetElement = document.getElementById(targetId);
|
|
|
|
console.log(
|
|
"Resize handle clicked! Target:",
|
|
targetId,
|
|
"Element found:",
|
|
!!targetElement,
|
|
);
|
|
|
|
if (!targetElement) {
|
|
console.error("Target element not found:", targetId);
|
|
return;
|
|
}
|
|
|
|
resizeState = {
|
|
target: targetElement,
|
|
handle: e.target,
|
|
startY: e.clientY,
|
|
startHeight: targetElement.offsetHeight,
|
|
};
|
|
|
|
console.log("Starting resize from height:", resizeState.startHeight);
|
|
|
|
document.body.style.cursor = "nwse-resize";
|
|
document.body.style.userSelect = "none";
|
|
e.target.style.pointerEvents = "none";
|
|
}
|
|
});
|
|
|
|
document.addEventListener("mousemove", function (e) {
|
|
if (resizeState) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const deltaY = e.clientY - resizeState.startY;
|
|
const newHeight = Math.max(
|
|
200,
|
|
Math.min(1200, resizeState.startHeight + deltaY),
|
|
);
|
|
|
|
// Update target element height
|
|
resizeState.target.style.height = newHeight + "px";
|
|
|
|
// For Quill editor (pageContentEditor), update the internal structure
|
|
if (resizeState.target.id === "pageContentEditor") {
|
|
// The target div BECOMES .ql-container when Quill initializes
|
|
const editor = resizeState.target.querySelector(".ql-editor");
|
|
// Toolbar is a SIBLING, not a child - look in parent
|
|
const parent = resizeState.target.parentElement;
|
|
const toolbar = parent ? parent.querySelector(".ql-toolbar") : null;
|
|
|
|
console.log(
|
|
"pageContentEditor resize - editor:",
|
|
!!editor,
|
|
"toolbar:",
|
|
!!toolbar,
|
|
);
|
|
|
|
if (editor && toolbar) {
|
|
const toolbarHeight = toolbar.offsetHeight;
|
|
const editorHeight = newHeight - toolbarHeight;
|
|
|
|
console.log(
|
|
"Setting heights - toolbar:",
|
|
toolbarHeight,
|
|
"editor:",
|
|
editorHeight,
|
|
"total:",
|
|
newHeight,
|
|
);
|
|
|
|
resizeState.target.style.height = editorHeight + "px";
|
|
editor.style.height = editorHeight + "px";
|
|
} else if (editor) {
|
|
// Fallback: just resize the editor if no toolbar found
|
|
console.log("Toolbar not found, resizing editor directly");
|
|
editor.style.height = newHeight + "px";
|
|
}
|
|
}
|
|
|
|
// For About editor (aboutContentEditor)
|
|
if (resizeState.target.id === "aboutContentEditor") {
|
|
// The target div BECOMES .ql-container when Quill initializes
|
|
const editor = resizeState.target.querySelector(".ql-editor");
|
|
// Toolbar is a SIBLING, not a child - look in parent
|
|
const parent = resizeState.target.parentElement;
|
|
const toolbar = parent ? parent.querySelector(".ql-toolbar") : null;
|
|
|
|
console.log(
|
|
"aboutContentEditor resize - editor:",
|
|
!!editor,
|
|
"toolbar:",
|
|
!!toolbar,
|
|
);
|
|
|
|
if (editor && toolbar) {
|
|
const toolbarHeight = toolbar.offsetHeight;
|
|
const editorHeight = newHeight - toolbarHeight;
|
|
|
|
console.log(
|
|
"Setting heights - toolbar:",
|
|
toolbarHeight,
|
|
"editor:",
|
|
editorHeight,
|
|
"total:",
|
|
newHeight,
|
|
);
|
|
|
|
resizeState.target.style.height = editorHeight + "px";
|
|
editor.style.height = editorHeight + "px";
|
|
} else if (editor) {
|
|
// Fallback: just resize the editor if no toolbar found
|
|
console.log("Toolbar not found, resizing editor directly");
|
|
editor.style.height = newHeight + "px";
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
document.addEventListener("mouseup", function () {
|
|
if (resizeState) {
|
|
resizeState.handle.style.pointerEvents = "";
|
|
resizeState = null;
|
|
document.body.style.cursor = "";
|
|
document.body.style.userSelect = "";
|
|
}
|
|
});
|
|
})();
|