webupdate
This commit is contained in:
228
website/public/assets/js/dynamic-page.js
Normal file
228
website/public/assets/js/dynamic-page.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* 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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
// 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,
|
||||
};
|
||||
Reference in New Issue
Block a user