Files
SkyArtShop/website/public/assets/js/dynamic-page.js
Local Server 2a2a3d99e5 webupdate
2026-01-18 02:22:05 -06:00

229 lines
6.7 KiB
JavaScript

/**
* Dynamic Page Content Loader
* Loads page content from the API and renders it with proper formatting
*/
// Convert Quill Delta to HTML - accurate conversion matching backend format
function convertDeltaToHtml(delta) {
if (!delta || !delta.ops) return "";
let html = "";
let currentLine = "";
let inListType = null; // 'bullet' or 'ordered'
const ops = delta.ops;
for (let i = 0; i < ops.length; i++) {
const op = ops[i];
const nextOp = ops[i + 1];
if (typeof op.insert === "string") {
const text = op.insert;
const inlineAttrs = op.attributes || {};
// Check if this is a standalone newline with block attributes
if (text === "\n") {
const blockAttrs = inlineAttrs;
// Handle list transitions
if (blockAttrs.list) {
const newListType = blockAttrs.list;
if (inListType !== newListType) {
if (inListType) {
html += inListType === "ordered" ? "</ol>" : "</ul>";
}
html += newListType === "ordered" ? "<ol>" : "<ul>";
inListType = newListType;
}
html += `<li>${currentLine}</li>`;
} else {
// Close any open list
if (inListType) {
html += inListType === "ordered" ? "</ol>" : "</ul>";
inListType = null;
}
// Apply block formatting
if (blockAttrs.header) {
html += `<h${blockAttrs.header}>${currentLine}</h${blockAttrs.header}>`;
} else if (blockAttrs.blockquote) {
html += `<blockquote>${currentLine}</blockquote>`;
} else if (blockAttrs["code-block"]) {
html += `<pre><code>${currentLine}</code></pre>`;
} else if (currentLine) {
html += `<p>${currentLine}</p>`;
}
}
currentLine = "";
} else {
// Regular text - may contain embedded newlines
const parts = text.split("\n");
for (let j = 0; j < parts.length; j++) {
const part = parts[j];
// Format the text part
if (part.length > 0) {
let formatted = escapeHtml(part);
// Apply inline formatting
if (inlineAttrs.bold) formatted = `<strong>${formatted}</strong>`;
if (inlineAttrs.italic) formatted = `<em>${formatted}</em>`;
if (inlineAttrs.underline) formatted = `<u>${formatted}</u>`;
if (inlineAttrs.strike) formatted = `<s>${formatted}</s>`;
if (inlineAttrs.code) formatted = `<code>${formatted}</code>`;
if (inlineAttrs.link)
formatted = `<a href="${inlineAttrs.link}" target="_blank" rel="noopener">${formatted}</a>`;
currentLine += formatted;
}
// Handle embedded newlines (not the last part)
if (j < parts.length - 1) {
// Close any open list for embedded newlines
if (inListType) {
html += inListType === "ordered" ? "</ol>" : "</ul>";
inListType = null;
}
if (currentLine) {
html += `<p>${currentLine}</p>`;
}
currentLine = "";
}
}
}
} else if (op.insert && op.insert.image) {
// Flush pending content
if (currentLine) {
if (inListType) {
html += `<li>${currentLine}</li>`;
html += inListType === "ordered" ? "</ol>" : "</ul>";
inListType = null;
} else {
html += `<p>${currentLine}</p>`;
}
currentLine = "";
}
html += `<img src="${op.insert.image}" class="content-image" alt="Content image">`;
}
}
// Flush remaining content
if (inListType) {
if (currentLine) html += `<li>${currentLine}</li>`;
html += inListType === "ordered" ? "</ol>" : "</ul>";
} else if (currentLine) {
html += `<p>${currentLine}</p>`;
}
return html;
}
function escapeHtml(text) {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
// Parse and render page content (handles both Delta JSON and raw HTML)
function parsePageContent(content) {
if (!content) return "<p>Content coming soon...</p>";
try {
const delta = JSON.parse(content);
if (delta.ops) {
return convertDeltaToHtml(delta);
}
return content;
} catch (e) {
// Not JSON, return as-is (probably HTML)
return content;
}
}
// Load page content from API
async function loadPageContent(slug, options = {}) {
const {
titleSelector = "#pageTitle",
contentSelector = "#dynamicContent",
staticSelector = "#staticContent",
showLoading = true,
} = options;
const dynamicContent = document.querySelector(contentSelector);
const staticContent = document.querySelector(staticSelector);
const titleElement = document.querySelector(titleSelector);
if (!dynamicContent) {
console.warn("Dynamic content container not found:", contentSelector);
return null;
}
if (showLoading) {
dynamicContent.innerHTML = `
<div style="text-align: center; padding: 40px;">
<div class="loading-spinner" style="width: 40px; height: 40px; border: 3px solid rgba(252,177,216,0.2); border-top-color: #FCB1D8; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto;"></div>
<p style="margin-top: 16px; color: #888;">Loading content...</p>
</div>
`;
}
try {
const response = await fetch(`/api/pages/${slug}`);
const data = await response.json();
if (data.success && data.page) {
const page = data.page;
// Update page title if provided
if (page.title && titleElement) {
titleElement.textContent = page.title;
}
// Parse and render content
const htmlContent = parsePageContent(page.content);
dynamicContent.innerHTML = htmlContent;
// Hide static fallback if exists
if (staticContent) {
staticContent.style.display = "none";
}
return page;
} else {
// Show static fallback
dynamicContent.style.display = "none";
if (staticContent) {
staticContent.style.display = "block";
}
return null;
}
} catch (error) {
console.error("Failed to load page content:", error);
dynamicContent.style.display = "none";
if (staticContent) {
staticContent.style.display = "block";
}
return null;
}
}
// Auto-initialize on DOMContentLoaded if data-page-slug is set
document.addEventListener("DOMContentLoaded", () => {
const pageContainer = document.querySelector("[data-page-slug]");
if (pageContainer) {
const slug = pageContainer.dataset.pageSlug;
loadPageContent(slug);
}
});
// Export for use in other scripts
window.DynamicPage = {
loadPageContent,
parsePageContent,
convertDeltaToHtml,
};