' + beacon;
}function renderSandboxed(container, html, size, adId) {
size = size || { w: 336, h: 280 };
applyFixedSize(container, size);
var iframe = document.createElement('iframe');
iframe.className = 'fca-ad__sandbox';
iframe.setAttribute('sandbox', 'allow-scripts allow-popups allow-popups-to-escape-sandbox allow-presentation allow-forms');
iframe.setAttribute('referrerpolicy', 'no-referrer');
iframe.setAttribute('loading', 'lazy');
iframe.setAttribute('title', 'Advertisement');
if (adId) { iframe.setAttribute('data-ad-id', String(adId)); }
iframe.width = String(size.w || 336);
iframe.height = String(size.h || 280);
iframe.style.display = 'block';
iframe.style.width = '100%';
iframe.style.height = (size.h || 280) + 'px';
iframe.style.border = '0';
iframe.style.overflow = 'hidden';
iframe.style.background = 'transparent';
iframe.srcdoc = clippedSrcdoc(html, size, adId);
container.appendChild(iframe);
}// ── member reporting ───────────────────────────────────────
// A small "Report" affordance on served ads. Opens a lightweight
// popover asking for an optional reason, then POSTs to /report.
function closeReportPopover() {
if (window.__fcaReportPop && window.__fcaReportPop.parentNode) {
window.__fcaReportPop.parentNode.removeChild(window.__fcaReportPop);
}
window.__fcaReportPop = null;
document.removeEventListener('click', onDocClickForReport, true);
}
function onDocClickForReport(e) {
var pop = window.__fcaReportPop;
if (pop && !pop.contains(e.target)) { closeReportPopover(); }
}
function positionReportPopover(pop, anchor) {
var r = anchor.getBoundingClientRect();
var w = pop.offsetWidth || 260;
var left = Math.min(Math.max(8, r.right - w), window.innerWidth - w - 8);
var top = r.bottom + 6;
if (top + pop.offsetHeight > window.innerHeight - 8) {
top = Math.max(8, r.top - pop.offsetHeight - 6);
}
pop.style.left = (left + window.scrollX) + 'px';
pop.style.top = (top + window.scrollY) + 'px';
}
function submitReport(adId, reason) {
return fetch(apiUrl('report'), {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': CFG.nonce },
body: JSON.stringify({ ad_id: adId, reason: reason || '' })
}).then(function (r) { return r.ok; }).catch(function () { return false; });
}
function openReportPopover(anchor, ad) {
closeReportPopover();
var pop = document.createElement('div');
pop.className = 'fca-ad-report-pop';
pop.innerHTML =
'
Report this ad
' +
'' +
'
' +
'' +
'' +
'
';
document.body.appendChild(pop);
window.__fcaReportPop = pop;
positionReportPopover(pop, anchor);
var ta = pop.querySelector('.fca-ad-report-pop__reason');
if (ta) { ta.focus(); }
pop.querySelector('.fca-ad-report-pop__cancel').addEventListener('click', closeReportPopover);
pop.querySelector('.fca-ad-report-pop__send').addEventListener('click', function () {
var btn = this;
btn.disabled = true; btn.textContent = 'Sending\u2026';
submitReport(ad.id, ta ? ta.value : '').then(function (ok) {
pop.innerHTML = '
' +
(ok ? 'Thanks \u2014 this ad has been reported.' : 'Could not send your report.') +
'
';
setTimeout(closeReportPopover, ok ? 1600 : 2400);
});
});
setTimeout(function () {
document.addEventListener('click', onDocClickForReport, true);
}, 0);
}
function addReportControl(wrap, ad, serveCfg) {
if (!serveCfg || !serveCfg.reportsEnabled || !serveCfg.canReport) { return; }
if (!ad || !ad.id || ad.demo) { return; }
var rb = document.createElement('button');
rb.type = 'button';
rb.className = 'fca-ad__report';
rb.title = 'Report this ad';
rb.setAttribute('aria-label', 'Report this ad');
rb.innerHTML = '';
rb.addEventListener('click', function (e) {
e.preventDefault(); e.stopPropagation();
openReportPopover(rb, ad);
});
wrap.classList.add('fca-ad--reportable');
wrap.appendChild(rb);
}// ')
: (doc + beaconOnly);
}
}
} catch (e) {}
}
}
} else {
var link = shell.querySelector('.fca-ad__house');
if (link) {
link.addEventListener('click', function () { trackClick(adId, serveCfg); });
}
}
watchImpression(shell, serveCfg);
});
}// ── run ────────────────────────────────────────────────────
function ads(res) { return (res && res.ads) || []; }function run() {
var gen = ++state.gen;
removeAds();
// Suppress all ads in the portal admin area.
if (isAdminArea()) { return; }var feedZone = onSpacePage() ? 'space-feed' : 'global-feed';
var eventSingle = onEventSingle();
var eventList = onEventList();// Single feed-post placements (dedicated page OR overlay popup).
// Driven by syncSinglePost() which is idempotent per element;
// the overlay observer also calls it for popups that open with
// no route change.
syncSinglePost();// Zones fetched every run (cheap, cached server-side). Limit
// 10 = the max per-zone `count`; the server clamps as needed.
// Always request header — injectHeader no-ops until the feed
// column exists (SPA may mount it slightly later).
var jobs = {
feed: fetchServe(feedZone, 10),
sidebar: fetchServe('sidebar', 10),
left: fetchServe('sidebar-left', 10),
header: fetchServe('header', 10)
};
if (eventSingle) {
jobs.eBefore = fetchServe('event-single-before', 10);
jobs.eInside = fetchServe('event-single-inside', 10);
jobs.eAfter = fetchServe('event-single-after', 10);
} else if (eventList) {
jobs.eList = fetchServe('event-list', 10);
}var keys = Object.keys(jobs);
Promise.all(keys.map(function (k) { return jobs[k]; })).then(function (arr) {
if (gen !== state.gen) { return; }
var r = {};
keys.forEach(function (k, i) { r[k] = arr[i]; });
var serveCfg = (r.feed && r.feed.config) || (r.sidebar && r.sidebar.config) || {};
if (serveCfg.zoneSizes) {
CFG.zoneSizes = serveCfg.zoneSizes;
}
if (serveCfg.zoneOptions) {
CFG.zoneOptions = serveCfg.zoneOptions;
}activateServerAds(serveCfg);
injectHeader(r.header);
injectFeed(r.feed);
injectSidebar(r.sidebar);
injectLeftSidebar(r.left);
if (eventSingle || eventList) {
injectEvents({
before: r.eBefore, inside: r.eInside,
after: r.eAfter, list: r.eList
});
}
});
}function waitForFeed(fn, n) {
n = n || 0;
if (feedContainer() || sidebar() || leftSidebar() || mainContent()) { fn(); return; }
if (n >= 60) { return; }
setTimeout(function () { waitForFeed(fn, n + 1); }, 100);
}function debounce(fn, ms) {
var t; return function () { clearTimeout(t); t = setTimeout(fn, ms); };
}
var onNav = debounce(function () { waitForFeed(run); }, 150);// history patch for SPA nav
if (!window.__fcaAdsHistoryPatched) {
window.__fcaAdsHistoryPatched = true;
var op = history.pushState, orp = history.replaceState;
history.pushState = function () { var r = op.apply(this, arguments); window.dispatchEvent(new Event('locationchange')); return r; };
history.replaceState = function () { var r = orp.apply(this, arguments); window.dispatchEvent(new Event('locationchange')); return r; };
}
window.addEventListener('popstate', onNav);
window.addEventListener('hashchange', onNav);
window.addEventListener('locationchange', onNav);
document.addEventListener('fluentComPortalAppReady', onNav);// The overlay popup (el-dialog `.fcom_feed_modal`) is teleported to
// and can open WITHOUT a route change, so route hooks alone
// miss it. Watch the DOM and decorate the single-post element when
// it appears; syncSinglePost() is idempotent per element (guarded by
// a flag) so our own insertions never trigger a re-inject loop.
// Infinite scroll appends posts without a route change. Re-run the
// (idempotent) feed injector against the last feed response so new
// posts get their ad blocks too. No-op when nothing new appeared.
function syncFeed() {
if (isAdminArea() || !state.lastFeed) { return; }
injectFeed(state.lastFeed);
}if ('MutationObserver' in window) {
var onBodyMutation = debounce(function () { syncSinglePost(); syncFeed(); }, 120);
var singleMo = new MutationObserver(onBodyMutation);
var startSingleObserver = function () {
if (document.body) { singleMo.observe(document.body, { childList: true, subtree: true }); }
};
if (document.body) { startSingleObserver(); }
else { document.addEventListener('DOMContentLoaded', startSingleObserver); }
}function registerFcHooks() {
var util = window.FluentCommunityUtil;
if (!util || !util.hooks || typeof util.hooks.addAction !== 'function') { return false; }
util.hooks.addAction('fluent_com_route_changed', 'fca_ads_injector_nav', onNav);
return true;
}
if (!registerFcHooks()) {
document.addEventListener('fluentCommunityUtilReady', function () { registerFcHooks(); onNav(); });
}if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { waitForFeed(run); });
} else {
waitForFeed(run);
}
})();