Utilizziamo cookie tecnici e, previo consenso, cookie di profilazione e analitici per migliorare la tua esperienza.
Puoi gestire le tue preferenze o accettare tutti i cookie.
Leggi la Privacy Policy.
Gestisci il consenso per le diverse categorie di cookie.
NecessariIndispensabili per il funzionamento del sito. Non possono essere disattivati.
AnaliticiCi aiutano a capire come visiti il sito (es. Google Analytics).
MarketingUsati per inviare pubblicità mirata (es. Facebook Pixel).
`;
document.body.appendChild(modal);
modal.style.display = 'flex';
// Events
document.getElementById('sf-modal-cancel').onclick = () => {
modal.style.display = 'none';
};
document.getElementById('sf-modal-save').onclick = () => {
consent.analytics = document.getElementById('sf-consent-analytics').checked;
consent.marketing = document.getElementById('sf-consent-marketing').checked;
saveConsent();
modal.style.display = 'none';
};
}
function acceptAll() {
consent.analytics = true;
consent.marketing = true;
saveConsent();
}
function rejectAll() {
consent.analytics = false;
consent.marketing = false;
saveConsent();
}
function openSettings() {
injectSettingsModal();
}
function saveConsent() {
consent.timestamp = new Date().toISOString();
localStorage.setItem(STORAGE_KEY, JSON.stringify(consent));
const banner = document.getElementById('sf-privacy-banner');
if (banner) banner.style.display = 'none';
applyConsent();
}
function applyConsent() {
console.log("SiteForge Privacy: Consent Applied", consent);
// Event for other scripts
const event = new CustomEvent('siteforge-consent-update', { detail: consent });
window.dispatchEvent(event);
// Example: Enable Google Analytics if accepted
if (consent.analytics) {
enableAnalytics();
}
}
function enableAnalytics() {
// Here you would typically inject the GA script tag dynamically
// or change types from text/plain to text/javascript
// Example logic needed here based on user specific scripts
}
// Public API
return {
init: init,
openSettings: openSettings,
getConsent: () => consent
};
})();
$(document).ready(function () {
// Configuration
const API_URL = 'blog_manager.php';
// 1. Check if we need to render the Blog Grid
const $gridContainer = $('.siteforge-blog-grid-container');
if ($gridContainer.length) {
loadBlogGrid($gridContainer);
}
// 2. Check if we need to render a Single Post
const $postContainer = $('.siteforge-blog-post-container');
if ($postContainer.length) {
const urlParams = new URLSearchParams(window.location.search);
const slug = urlParams.get('post_slug');
const id = urlParams.get('post_id');
if (slug || id) {
loadSinglePost($postContainer, slug, id);
} else {
$postContainer.html('
');
}
}
// 3. Blog Explorer: elenco completo dei post + calendario/archivio laterale + ricerca
const $blogExplorers = $('.siteforge-blog-explorer');
if ($blogExplorers.length) {
$blogExplorers.each(function () {
loadBlogExplorer($(this));
});
}
// Functions
function loadBlogGrid($container) {
$container.html('
');
$.get(API_URL, { action: 'list_posts' }, function (response) {
if (response.status === 'success') {
$container.empty();
if (response.posts.length === 0) {
$container.html('
Nessun post trovato.
');
return;
}
// Filter only published if on public frontend?
// For now show all or check status. ideal: show only published.
const publicPosts = response.posts.filter(p => p.status === 'published');
if (publicPosts.length === 0) {
$container.html('
Nessun post pubblicato.
');
return;
}
// Quanti post mostrare: attributo data-limit sul container (override
// per-istanza) oppure impostazione globale grid_limit dal backoffice.
// 0 / assente / non valido = mostra tutti.
const settings = response.settings || {};
const attrLimit = parseInt($container.data('limit'), 10);
const cfgLimit = parseInt(settings.grid_limit, 10);
let gridLimit = 0;
if (!isNaN(attrLimit) && attrLimit > 0) gridLimit = attrLimit;
else if (!isNaN(cfgLimit) && cfgLimit > 0) gridLimit = cfgLimit;
const shownPosts = gridLimit > 0 ? publicPosts.slice(0, gridLimit) : publicPosts;
shownPosts.forEach(post => {
const date = new Date(post.created_at).toLocaleDateString();
const excerpt = post.excerpt || (post.content ? post.content.substring(0, 100) + '...' : '');
// Strip HTML from excerpt if it came from content
const cleanExcerpt = excerpt.replace(/<[^>]*>?/gm, '');
const cover = post.cover_image || 'https://via.placeholder.com/400x250?text=Blog';
// Link logic: We need to know the page that acts as "Single Post View".
// For now, we assume the user might link to the SAME page with a query param
// OR user sets up a specific page.
// Let's assume the user will put the Single Post Strip on a page called 'post.htm' or similar.
// BUT, to be generic, we can link to "post.htm?post_slug=..." if it exists,
// or just current page?
// Better: The user should probably configure the "Post Page Base URL".
// For simplicity, we'll assume a standard 'blog-post.htm' or similar,
// OR we just link to "?post_slug=..." and assume the *current* page can handle it?
// No, usually grid is on 'blog.htm' and single is on 'post.htm'.
// We'll use a data attribute on the container if set, else default.
const basePostUrl = $container.data('post-page') || 'post.htm'; // Fallback
const link = `${basePostUrl}?post_slug=${post.slug}`;
const card = `
');
});
}
// Riconosce un file video per estensione (upload diretto, non link esterno)
function isBlogVideoFile(url) {
return /\.(mp4|webm|ogg|mov|m4v)(\?.*)?$/i.test(String(url || ''));
}
// Estensioni "documenti" che, se caricati nella cartella media del post,
// diventano automaticamente allegati scaricabili in fondo al post
// pubblico — nessuna selezione manuale, nessuna colonna DB dedicata.
const BLOG_ATTACHMENT_EXT = /\.(pdf|docx?|xlsx?|pptx?)$/i;
const BLOG_ATTACHMENT_ICONS = {
pdf: 'fa-file-pdf', doc: 'fa-file-word', docx: 'fa-file-word',
xls: 'fa-file-excel', xlsx: 'fa-file-excel',
ppt: 'fa-file-powerpoint', pptx: 'fa-file-powerpoint',
};
// video_url del post -> YouTube/Vimeo embed, file video caricato ->