- Eltern-Ordner ist jetzt EIN Git-Repo (statt getrennter Repos). - root .gitignore haelt Secrets (.env), node_modules, DB und Build-Artefakte raus. - release.ps1: manueller Release (ZIP bauen + ans Backend laden). - root README mit Struktur und Release-Ablauf. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
116 lines
3.4 KiB
JavaScript
116 lines
3.4 KiB
JavaScript
/**
|
||
* Content Blocker – frontend.js
|
||
* Click-to-load: the real iframe is CREATED only after the user actively
|
||
* consents per service. Consent is stored in localStorage per service id.
|
||
*/
|
||
|
||
( function () {
|
||
'use strict';
|
||
|
||
const STORAGE_PREFIX = 'cb_consent_';
|
||
|
||
/* ───────────────────────── consent storage ───────────────────────── */
|
||
|
||
function hasConsent( serviceId ) {
|
||
try {
|
||
return localStorage.getItem( STORAGE_PREFIX + serviceId ) === '1';
|
||
} catch ( e ) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function grantConsent( serviceId ) {
|
||
try {
|
||
localStorage.setItem( STORAGE_PREFIX + serviceId, '1' );
|
||
} catch ( e ) {
|
||
// localStorage unavailable; allow the load for this session only.
|
||
}
|
||
}
|
||
|
||
/* ───────────────────────── iframe loading ────────────────────────── */
|
||
|
||
/** Replace a .cb-blocker element with the real iframe. src comes from data-src. */
|
||
function loadContent( blockerEl ) {
|
||
const src = blockerEl.dataset.src;
|
||
if ( ! src ) {
|
||
blockerEl.remove();
|
||
return;
|
||
}
|
||
|
||
const iframe = document.createElement( 'iframe' );
|
||
|
||
if ( blockerEl.dataset.width ) iframe.width = blockerEl.dataset.width;
|
||
if ( blockerEl.dataset.height ) iframe.height = blockerEl.dataset.height;
|
||
|
||
iframe.setAttribute( 'loading', 'lazy' );
|
||
iframe.setAttribute( 'allowfullscreen', '' );
|
||
iframe.setAttribute( 'referrerpolicy', 'no-referrer-when-downgrade' );
|
||
|
||
// Set src last — this is the moment the network request is made.
|
||
iframe.src = src;
|
||
|
||
blockerEl.parentNode.replaceChild( iframe, blockerEl );
|
||
}
|
||
|
||
function loadPreConsented() {
|
||
document.querySelectorAll( '.cb-blocker[data-cb-id]' ).forEach( function ( el ) {
|
||
const id = el.dataset.cbId;
|
||
if ( id && hasConsent( id ) ) {
|
||
loadContent( el );
|
||
}
|
||
} );
|
||
}
|
||
|
||
/* ───────────────────────── consent buttons ───────────────────────── */
|
||
|
||
function attachButtons() {
|
||
document.addEventListener( 'click', function ( e ) {
|
||
const btn = e.target.closest( '.cb-blocker__button' );
|
||
if ( ! btn ) return;
|
||
|
||
const serviceId = btn.dataset.cbId;
|
||
if ( ! serviceId ) return;
|
||
|
||
grantConsent( serviceId );
|
||
|
||
const wrapper = btn.closest( '.cb-blocker' );
|
||
if ( wrapper ) {
|
||
loadContent( wrapper );
|
||
}
|
||
} );
|
||
}
|
||
|
||
/* ───────────────────────── revoke (Art. 7 (3)) ───────────────────── */
|
||
|
||
window.cbRevokeAll = function () {
|
||
try {
|
||
const keysToRemove = [];
|
||
for ( let i = 0; i < localStorage.length; i++ ) {
|
||
const key = localStorage.key( i );
|
||
if ( key && key.indexOf( STORAGE_PREFIX ) === 0 ) {
|
||
keysToRemove.push( key );
|
||
}
|
||
}
|
||
keysToRemove.forEach( function ( k ) {
|
||
localStorage.removeItem( k );
|
||
} );
|
||
} catch ( e ) {
|
||
// Silently ignore if localStorage is unavailable.
|
||
}
|
||
window.location.reload();
|
||
};
|
||
|
||
/* ───────────────────────── bootstrap ─────────────────────────────── */
|
||
|
||
function init() {
|
||
loadPreConsented();
|
||
attachButtons();
|
||
}
|
||
|
||
if ( document.readyState === 'loading' ) {
|
||
document.addEventListener( 'DOMContentLoaded', init );
|
||
} else {
|
||
init();
|
||
}
|
||
} )();
|