webupdate
This commit is contained in:
@@ -5,12 +5,29 @@ 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();
|
||||
@@ -30,13 +47,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Media Library Selection Handler
|
||||
window.addEventListener("message", function (event) {
|
||||
if (event.data.type === "mediaSelected" && currentMediaPicker) {
|
||||
handleMediaSelection(event.data.media);
|
||||
}
|
||||
});
|
||||
|
||||
function initializeQuillEditor() {
|
||||
quillEditor = new Quill("#pageContentEditor", {
|
||||
theme: "snow",
|
||||
@@ -57,6 +67,31 @@ function initializeQuillEditor() {
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// 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() {
|
||||
@@ -75,6 +110,31 @@ function initializeAboutEditor() {
|
||||
},
|
||||
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() {
|
||||
@@ -120,17 +180,17 @@ function renderPages(pages) {
|
||||
<td>${formatDate(p.createdat)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="editPage('${escapeHtml(
|
||||
p.id
|
||||
p.id,
|
||||
)}')">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deletePage('${escapeHtml(
|
||||
p.id
|
||||
p.id,
|
||||
)}', '${escapeHtml(p.title).replace(/'/g, "\\'")}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`
|
||||
</tr>`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -140,7 +200,7 @@ function filterPages() {
|
||||
const filtered = pagesData.filter(
|
||||
(p) =>
|
||||
p.title.toLowerCase().includes(searchTerm) ||
|
||||
p.slug.toLowerCase().includes(searchTerm)
|
||||
p.slug.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
renderPages(filtered);
|
||||
}
|
||||
@@ -155,6 +215,7 @@ function showCreatePage() {
|
||||
// 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();
|
||||
@@ -203,6 +264,40 @@ async function editPage(id) {
|
||||
) {
|
||||
// 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 =
|
||||
@@ -286,7 +381,7 @@ function renderBusinessHours(hours) {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -321,6 +416,124 @@ function removeBusinessHour(index) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -346,7 +559,12 @@ async function showAboutWithTeamFields(page) {
|
||||
|
||||
async function loadTeamMembersForAbout() {
|
||||
try {
|
||||
const response = await fetch("/api/admin/team-members");
|
||||
// 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;
|
||||
@@ -387,7 +605,7 @@ function displayTeamMembersInEditor() {
|
||||
${
|
||||
member.image_url
|
||||
? `<img src="${member.image_url}" alt="${escapeHtml(
|
||||
member.name
|
||||
member.name,
|
||||
)}" />`
|
||||
: `<i class="bi bi-person-circle"></i>`
|
||||
}
|
||||
@@ -409,8 +627,8 @@ function displayTeamMembersInEditor() {
|
||||
rows="2"
|
||||
placeholder="Bio"
|
||||
onchange="updateTeamMember(${index}, 'bio', this.value)">${escapeHtml(
|
||||
member.bio || ""
|
||||
)}</textarea>
|
||||
member.bio || "",
|
||||
)}</textarea>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="input-group input-group-sm">
|
||||
@@ -431,7 +649,7 @@ function displayTeamMembersInEditor() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
@@ -456,117 +674,71 @@ function updateTeamMember(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 };
|
||||
openMediaLibraryModal();
|
||||
}
|
||||
|
||||
function openMediaLibraryModal() {
|
||||
// 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;
|
||||
`;
|
||||
// Initialize if not already
|
||||
initPagesMediaLibrary();
|
||||
|
||||
// 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 = closeMediaLibraryModal;
|
||||
|
||||
// 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) {
|
||||
closeMediaLibraryModal();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function closeMediaLibraryModal() {
|
||||
const backdrop = document.getElementById("mediaLibraryBackdrop");
|
||||
if (backdrop) {
|
||||
backdrop.remove();
|
||||
if (pagesMediaLibrary) {
|
||||
pagesMediaLibrary.open();
|
||||
}
|
||||
currentMediaPicker = null;
|
||||
}
|
||||
|
||||
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]) {
|
||||
// Media is an array, get the first item's URL
|
||||
const selectedMedia = Array.isArray(media) ? media[0] : media;
|
||||
aboutTeamMembers[index].image_url = selectedMedia.url;
|
||||
aboutTeamMembers[index].image_url = media.path;
|
||||
displayTeamMembersInEditor();
|
||||
}
|
||||
}
|
||||
|
||||
closeMediaLibraryModal();
|
||||
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
|
||||
@@ -584,6 +756,7 @@ async function saveTeamMembers() {
|
||||
await fetch(`/api/admin/team-members/${member.id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
} else {
|
||||
@@ -591,6 +764,7 @@ async function saveTeamMembers() {
|
||||
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();
|
||||
@@ -599,6 +773,7 @@ async function saveTeamMembers() {
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("Team members saved successfully");
|
||||
} catch (error) {
|
||||
console.error("Error saving team members:", error);
|
||||
}
|
||||
@@ -663,6 +838,52 @@ async function savePage() {
|
||||
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();
|
||||
@@ -695,7 +916,7 @@ async function savePage() {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSuccess(
|
||||
id ? "Page updated successfully" : "Page created successfully"
|
||||
id ? "Page updated successfully" : "Page created successfully",
|
||||
);
|
||||
pageModal.hide();
|
||||
loadPages();
|
||||
@@ -717,11 +938,11 @@ function generateContactHTML(pagedata) {
|
||||
(hour) => `
|
||||
<div>
|
||||
<p style="font-weight: 600; margin-bottom: 8px;">${escapeHtml(
|
||||
hour.days
|
||||
hour.days,
|
||||
)}</p>
|
||||
<p style="opacity: 0.95; margin: 0;">${escapeHtml(hour.hours)}</p>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
|
||||
@@ -743,7 +964,7 @@ function generateContactHTML(pagedata) {
|
||||
</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
|
||||
contactInfo.phone,
|
||||
)}</p>
|
||||
</div>
|
||||
|
||||
@@ -754,7 +975,7 @@ function generateContactHTML(pagedata) {
|
||||
</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
|
||||
contactInfo.email,
|
||||
)}</p>
|
||||
</div>
|
||||
|
||||
@@ -765,7 +986,7 @@ function generateContactHTML(pagedata) {
|
||||
</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
|
||||
contactInfo.address,
|
||||
)}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -780,11 +1001,43 @@ function generateContactHTML(pagedata) {
|
||||
`;
|
||||
}
|
||||
|
||||
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
|
||||
title,
|
||||
)}</strong>"?<br><br>` +
|
||||
`<small class="text-muted">This action cannot be undone.</small>`,
|
||||
async () => {
|
||||
@@ -804,7 +1057,7 @@ async function deletePage(id, title) {
|
||||
console.error("Failed to delete page:", error);
|
||||
showError("Failed to delete page");
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -845,7 +1098,7 @@ function showError(message) {
|
||||
|
||||
function showNotification(message, type) {
|
||||
const modal = new bootstrap.Modal(
|
||||
document.getElementById("notificationModal")
|
||||
document.getElementById("notificationModal"),
|
||||
);
|
||||
const modalContent = document.getElementById("notificationModalContent");
|
||||
const modalHeader = document.getElementById("notificationModalHeader");
|
||||
@@ -863,7 +1116,7 @@ function showNotification(message, type) {
|
||||
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
|
||||
message,
|
||||
)}</p>`;
|
||||
} else {
|
||||
modalContent.classList.remove("border-success");
|
||||
@@ -874,7 +1127,7 @@ function showNotification(message, type) {
|
||||
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
|
||||
message,
|
||||
)}</p>`;
|
||||
}
|
||||
|
||||
@@ -1061,7 +1314,7 @@ function toggleContentExpand(editorType) {
|
||||
"Resize handle clicked! Target:",
|
||||
targetId,
|
||||
"Element found:",
|
||||
!!targetElement
|
||||
!!targetElement,
|
||||
);
|
||||
|
||||
if (!targetElement) {
|
||||
@@ -1092,7 +1345,7 @@ function toggleContentExpand(editorType) {
|
||||
const deltaY = e.clientY - resizeState.startY;
|
||||
const newHeight = Math.max(
|
||||
200,
|
||||
Math.min(1200, resizeState.startHeight + deltaY)
|
||||
Math.min(1200, resizeState.startHeight + deltaY),
|
||||
);
|
||||
|
||||
// Update target element height
|
||||
@@ -1110,7 +1363,7 @@ function toggleContentExpand(editorType) {
|
||||
"pageContentEditor resize - editor:",
|
||||
!!editor,
|
||||
"toolbar:",
|
||||
!!toolbar
|
||||
!!toolbar,
|
||||
);
|
||||
|
||||
if (editor && toolbar) {
|
||||
@@ -1123,7 +1376,7 @@ function toggleContentExpand(editorType) {
|
||||
"editor:",
|
||||
editorHeight,
|
||||
"total:",
|
||||
newHeight
|
||||
newHeight,
|
||||
);
|
||||
|
||||
resizeState.target.style.height = editorHeight + "px";
|
||||
@@ -1147,7 +1400,7 @@ function toggleContentExpand(editorType) {
|
||||
"aboutContentEditor resize - editor:",
|
||||
!!editor,
|
||||
"toolbar:",
|
||||
!!toolbar
|
||||
!!toolbar,
|
||||
);
|
||||
|
||||
if (editor && toolbar) {
|
||||
@@ -1160,7 +1413,7 @@ function toggleContentExpand(editorType) {
|
||||
"editor:",
|
||||
editorHeight,
|
||||
"total:",
|
||||
newHeight
|
||||
newHeight,
|
||||
);
|
||||
|
||||
resizeState.target.style.height = editorHeight + "px";
|
||||
|
||||
Reference in New Issue
Block a user