chore: monorepo - plugin, backend und hilfsdaten in einem repo
- 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>
This commit is contained in:
273
gdpr-content-blocker/assets/admin.js
Normal file
273
gdpr-content-blocker/assets/admin.js
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* Content Blocker – admin.js
|
||||
* Color-picker init + service repeater (add / remove rows).
|
||||
*/
|
||||
|
||||
( function ( $ ) {
|
||||
'use strict';
|
||||
|
||||
/* ── Color pickers ── */
|
||||
function initColorPickers( ctx ) {
|
||||
$( '.cb-color-picker', ctx ).wpColorPicker();
|
||||
}
|
||||
|
||||
/* ── Live header update: "interner-name — Anbietername" ── */
|
||||
function syncTitle( box ) {
|
||||
const id = ( $( '.cb-input-id', box ).val() || '' ).trim();
|
||||
const name = ( $( '.cb-input-name', box ).val() || '' ).trim();
|
||||
const head = ( id || ( cbAdmin.newServiceLbl || 'neu' ) ) + ( name ? ' — ' + name : '' );
|
||||
$( '.cb-service-title', box ).first().text( head );
|
||||
}
|
||||
function bindNameSync( box ) {
|
||||
$( '.cb-input-name, .cb-input-id', box ).on( 'input', function () {
|
||||
syncTitle( box );
|
||||
} );
|
||||
}
|
||||
|
||||
/* ── Expand / collapse a service box ── */
|
||||
function setExpanded( box, open ) {
|
||||
$( '.cb-service-grid', box ).toggle( open );
|
||||
const $btn = $( '.cb-service-toggle', box );
|
||||
$btn.attr( 'aria-expanded', open ? 'true' : 'false' ).text( open ? '▾' : '▸' );
|
||||
}
|
||||
function bindToggle( box ) {
|
||||
$( '.cb-service-toggle', box ).on( 'click', function () {
|
||||
const open = $( this ).attr( 'aria-expanded' ) !== 'true';
|
||||
setExpanded( box, open );
|
||||
} );
|
||||
// Clicking the title also expands/collapses.
|
||||
$( '.cb-service-title', box ).on( 'click', function () {
|
||||
const open = $( '.cb-service-toggle', box ).attr( 'aria-expanded' ) !== 'true';
|
||||
setExpanded( box, open );
|
||||
} );
|
||||
}
|
||||
|
||||
/* ── Re-index a row's field names after a remove ── */
|
||||
function reindexRows() {
|
||||
$( '#cb-services-list .cb-service-box' ).each( function ( i ) {
|
||||
$( this ).attr( 'data-cb-index', i );
|
||||
$( this ).find( '[name]' ).each( function () {
|
||||
const n = $( this ).attr( 'name' );
|
||||
$( this ).attr( 'name', n.replace( /cb_services\[\d+\]/, 'cb_services[' + i + ']' ) );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
/* ── Add a new (optionally prefilled) service row ── */
|
||||
function addServiceRow( preset ) {
|
||||
const index = $( '#cb-services-list .cb-service-box' ).length;
|
||||
const template = $( '#cb-service-template' ).html();
|
||||
const newHtml = template.replace( /__INDEX__/g, index );
|
||||
const $box = $( newHtml );
|
||||
|
||||
$( '#cb-services-list' ).append( $box );
|
||||
bindNameSync( $box );
|
||||
bindToggle( $box );
|
||||
$box.find( '.cb-remove-service' ).on( 'click', handleRemove );
|
||||
|
||||
if ( preset ) {
|
||||
fillPreset( $box, preset );
|
||||
}
|
||||
setExpanded( $box, true ); // new rows start expanded for editing
|
||||
syncTitle( $box );
|
||||
return $box;
|
||||
}
|
||||
|
||||
/* ── Fill a row from a preset object ── */
|
||||
function fillPreset( $box, p ) {
|
||||
const setText = function ( field, val ) {
|
||||
$box.find( '[name$="[' + field + ']"]' ).val( val != null ? val : '' );
|
||||
};
|
||||
const setBool = function ( field, val ) {
|
||||
$box.find( '[name$="[' + field + ']"]' ).prop( 'checked', !! val );
|
||||
};
|
||||
setText( 'id', p.id );
|
||||
setText( 'name', p.name );
|
||||
setText( 'match_pattern', p.match_pattern );
|
||||
setText( 'recipient', p.recipient );
|
||||
setText( 'privacy_url', p.privacy_url );
|
||||
setText( 'purpose', p.purpose );
|
||||
setBool( 'third_country', p.third_country );
|
||||
setBool( 'sets_cookie', p.sets_cookie );
|
||||
setBool( 'loads_script', p.loads_script );
|
||||
$box.find( '.cb-service-title' ).text( p.name || cbAdmin.newServiceLbl );
|
||||
}
|
||||
|
||||
/* ── Add empty service ── */
|
||||
$( '#cb-add-service' ).on( 'click', function () {
|
||||
addServiceRow( null );
|
||||
} );
|
||||
|
||||
/* ── Insert preset ── */
|
||||
$( '#cb-preset-select' ).on( 'change', function () {
|
||||
const key = $( this ).val();
|
||||
if ( ! key || ! cbAdmin.presets || ! cbAdmin.presets[ key ] ) {
|
||||
return;
|
||||
}
|
||||
addServiceRow( cbAdmin.presets[ key ] );
|
||||
$( this ).val( '' ); // reset so the same preset can be added again
|
||||
} );
|
||||
|
||||
/* ── Remove service ── */
|
||||
function handleRemove() {
|
||||
if ( ! window.confirm( cbAdmin.confirmRemove ) ) {
|
||||
return;
|
||||
}
|
||||
$( this ).closest( '.cb-service-box' ).remove();
|
||||
reindexRows();
|
||||
}
|
||||
|
||||
/* ── Tab switching ── */
|
||||
$( '[data-cb-tab]' ).on( 'click', function ( e ) {
|
||||
e.preventDefault();
|
||||
activateTab( $( this ).data( 'cb-tab' ) );
|
||||
} );
|
||||
|
||||
/* ── Website scan ── */
|
||||
$( '#cb-scan-btn' ).on( 'click', function () {
|
||||
const $btn = $( this ).prop( 'disabled', true );
|
||||
const i18n = cbAdmin.i18n || {};
|
||||
$( '#cb-scan-status' ).text( i18n.scanning || 'Scanning…' );
|
||||
$( '#cb-scan-results' ).empty();
|
||||
|
||||
$.post( cbAdmin.ajaxUrl, { action: 'cb_scan', nonce: cbAdmin.scanNonce } )
|
||||
.done( function ( resp ) {
|
||||
if ( ! resp || ! resp.success ) {
|
||||
const msg = resp && resp.data && resp.data.message ? resp.data.message : 'Error';
|
||||
$( '#cb-scan-status' ).text( ( i18n.scanError || 'Scan failed:' ) + ' ' + msg );
|
||||
return;
|
||||
}
|
||||
$( '#cb-scan-status' ).text( '' );
|
||||
renderScan( resp.data );
|
||||
} )
|
||||
.fail( function ( xhr ) {
|
||||
$( '#cb-scan-status' ).text( ( i18n.scanError || 'Scan failed:' ) + ' ' + xhr.status );
|
||||
} )
|
||||
.always( function () {
|
||||
$btn.prop( 'disabled', false );
|
||||
} );
|
||||
} );
|
||||
|
||||
function renderScan( data ) {
|
||||
const i18n = cbAdmin.i18n || {};
|
||||
const findings = ( data && data.findings ) || [];
|
||||
const $out = $( '#cb-scan-results' ).empty();
|
||||
|
||||
// Scanned pages summary
|
||||
if ( data && data.scanned && data.scanned.length ) {
|
||||
const $p = $( '<p class="description"></p>' ).text( ( i18n.scannedPages || 'Scanned:' ) + ' ' );
|
||||
data.scanned.forEach( function ( s, i ) {
|
||||
if ( i > 0 ) $p.append( ', ' );
|
||||
$p.append( $( '<code></code>' ).text( s.url + ( s.error ? ' (!)' : '' ) ) );
|
||||
} );
|
||||
$out.append( $p );
|
||||
}
|
||||
|
||||
if ( ! findings.length ) {
|
||||
$out.append( $( '<p></p>' ).text( i18n.noFindings || 'No external resources found.' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
const $table = $( '<table class="widefat striped"></table>' );
|
||||
const $head = $( '<tr></tr>' );
|
||||
[ i18n.host || 'Host', i18n.type || 'Type', i18n.count || 'Count', i18n.foundOn || 'Found on', i18n.example || 'Example', i18n.status || 'Status', i18n.action || 'Action' ]
|
||||
.forEach( function ( h ) { $head.append( $( '<th></th>' ).text( h ) ); } );
|
||||
$table.append( $( '<thead></thead>' ).append( $head ) );
|
||||
|
||||
const $body = $( '<tbody></tbody>' );
|
||||
findings.forEach( function ( f ) {
|
||||
const $tr = $( '<tr></tr>' );
|
||||
|
||||
// Host (+ party badge)
|
||||
const $host = $( '<td></td>' );
|
||||
$host.append( $( '<strong></strong>' ).text( f.host ) );
|
||||
$host.append( document.createElement( 'br' ) );
|
||||
$host.append( $( '<span></span>' )
|
||||
.css( { fontSize: '11px', color: f.third_party ? '#b32d2e' : '#1a7f37' } )
|
||||
.text( f.third_party ? ( i18n.thirdParty || 'third-party' ) : ( i18n.firstParty || 'first-party' ) ) );
|
||||
$tr.append( $host );
|
||||
|
||||
// Types
|
||||
$tr.append( $( '<td></td>' ).text( ( f.types || [] ).join( ', ' ) ) );
|
||||
|
||||
// Count
|
||||
$tr.append( $( '<td></td>' ).text( f.count ) );
|
||||
|
||||
// Found on which pages
|
||||
const $pages = $( '<td></td>' ).css( { fontSize: '11px' } );
|
||||
( f.pages || [] ).forEach( function ( pageUrl, i ) {
|
||||
if ( i > 0 ) $pages.append( document.createElement( 'br' ) );
|
||||
let label = pageUrl;
|
||||
try { label = new URL( pageUrl ).pathname || pageUrl; } catch ( e ) {}
|
||||
$pages.append( $( '<a></a>' )
|
||||
.attr( { href: pageUrl, target: '_blank', rel: 'noopener noreferrer', title: pageUrl } )
|
||||
.text( label ) );
|
||||
} );
|
||||
$tr.append( $pages );
|
||||
|
||||
// Example URL
|
||||
const $ex = $( '<td></td>' );
|
||||
const sample = ( f.sample_urls || [] )[ 0 ] || '';
|
||||
$ex.append( $( '<code></code>' ).css( { fontSize: '11px', wordBreak: 'break-all' } ).text( sample ) );
|
||||
$tr.append( $ex );
|
||||
|
||||
// Status
|
||||
const $st = $( '<td></td>' );
|
||||
if ( f.covered ) {
|
||||
$st.append( $( '<span></span>' ).css( { color: '#1a7f37', fontWeight: '600' } ).text( '✓ ' + ( i18n.covered || 'covered' ) ) );
|
||||
}
|
||||
$tr.append( $st );
|
||||
|
||||
// Action: take over as a new service (only useful for uncovered third parties)
|
||||
const $act = $( '<td></td>' );
|
||||
if ( f.third_party && ! f.covered ) {
|
||||
$( '<button type="button" class="button button-small"></button>' )
|
||||
.text( i18n.addService || 'Add as service' )
|
||||
.on( 'click', function () {
|
||||
activateTab( 'services' );
|
||||
const $box = addServiceRow( { name: f.host, match_pattern: f.suggested_pattern || f.host, third_country: true } );
|
||||
if ( $box && $box.length ) {
|
||||
$box[ 0 ].scrollIntoView( { behavior: 'smooth', block: 'center' } );
|
||||
$box.find( '.cb-input-id' ).trigger( 'focus' );
|
||||
}
|
||||
} )
|
||||
.appendTo( $act );
|
||||
}
|
||||
$tr.append( $act );
|
||||
|
||||
$body.append( $tr );
|
||||
} );
|
||||
$table.append( $body );
|
||||
$out.append( $table );
|
||||
}
|
||||
|
||||
/* ── Activate a tab by key ── */
|
||||
function activateTab( tab ) {
|
||||
$( '.cb-tab-content' ).hide();
|
||||
$( '#cb-tab-' + tab ).show();
|
||||
$( '[data-cb-tab]' ).removeClass( 'nav-tab-active' );
|
||||
$( '[data-cb-tab="' + tab + '"]' ).addClass( 'nav-tab-active' );
|
||||
}
|
||||
|
||||
/* ── Bootstrap ── */
|
||||
$( document ).ready( function () {
|
||||
initColorPickers( document );
|
||||
|
||||
$( '#cb-services-list .cb-service-box' ).each( function () {
|
||||
bindNameSync( this );
|
||||
bindToggle( this );
|
||||
$( '.cb-remove-service', this ).on( 'click', handleRemove );
|
||||
} );
|
||||
|
||||
// Honor ?cb_tab=… so license redirects land on the right tab.
|
||||
const params = new URLSearchParams( window.location.search );
|
||||
const tab = params.get( 'cb_tab' );
|
||||
if ( tab && $( '#cb-tab-' + tab ).length ) {
|
||||
activateTab( tab );
|
||||
}
|
||||
} );
|
||||
|
||||
// Expose strings from wp_localize_script if needed.
|
||||
window.cbAdmin = window.cbAdmin || { confirmRemove: 'Dienst wirklich entfernen?' };
|
||||
|
||||
} )( jQuery );
|
||||
198
gdpr-content-blocker/assets/frontend.css
Normal file
198
gdpr-content-blocker/assets/frontend.css
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Content Blocker – frontend.css
|
||||
* Design: minimalist black/white with accent #2043B7
|
||||
* Colors come from CSS custom properties set inline by PHP.
|
||||
*
|
||||
* Note on specificity: every visible rule is scoped under `.cb-blocker`
|
||||
* (specificity 0,2,0) so theme button/link styles (typically 0,1,1) cannot
|
||||
* override the plugin. Your Custom-CSS is injected AFTER this file, so it can
|
||||
* still override everything — use the same `.cb-blocker …` prefix to be safe.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--cb-text: #ffffff;
|
||||
--cb-bg: #111111;
|
||||
--cb-btn-bg: #2043B7;
|
||||
--cb-btn-text: #ffffff;
|
||||
--cb-btn-hover-bg: #1a369a;
|
||||
--cb-btn-hover-text: #ffffff;
|
||||
}
|
||||
|
||||
/* ── Wrapper / placeholder ── */
|
||||
.cb-blocker {
|
||||
display: flex;
|
||||
align-items: flex-start; /* pin content to the top */
|
||||
justify-content: center;
|
||||
min-height: 180px; /* fallback only; real height is set inline */
|
||||
width: 100%;
|
||||
background-color: var(--cb-bg);
|
||||
color: var(--cb-text);
|
||||
box-sizing: border-box;
|
||||
padding: 24px 8px 20px; /* min 20px top/bottom, 8px sides */
|
||||
font-family: inherit;
|
||||
border-radius: 4px;
|
||||
overflow: auto; /* if text is taller than the embed, scroll, don't clip */
|
||||
}
|
||||
|
||||
/* ── Inner content ── */
|
||||
.cb-blocker .cb-blocker__inner {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Text elements ── */
|
||||
.cb-blocker .cb-blocker__text,
|
||||
.cb-blocker .cb-blocker__recipient,
|
||||
.cb-blocker .cb-blocker__purpose {
|
||||
margin: 0 0 0.65em;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
color: var(--cb-text);
|
||||
}
|
||||
|
||||
.cb-blocker .cb-blocker__text {
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.cb-blocker .cb-blocker__third-country {
|
||||
display: inline-block;
|
||||
margin-top: 0.25em;
|
||||
font-size: 0.8rem;
|
||||
color: #f0b429; /* amber — universal caution, brand-neutral */
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Privacy link ── */
|
||||
.cb-blocker .cb-blocker__privacy-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.75em;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.5;
|
||||
color: var(--cb-btn-bg);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
transform: none; /* neutralise theme hover scaling */
|
||||
}
|
||||
|
||||
.cb-blocker .cb-blocker__privacy-link:hover,
|
||||
.cb-blocker .cb-blocker__privacy-link:focus {
|
||||
color: var(--cb-btn-hover-bg);
|
||||
transform: none; /* link must not grow on hover */
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
/* ── Button ── */
|
||||
.cb-blocker .cb-blocker__button {
|
||||
display: inline-block;
|
||||
margin-top: 1em;
|
||||
padding: 0.6em 1.4em;
|
||||
background-color: var(--cb-btn-bg);
|
||||
color: var(--cb-btn-text);
|
||||
border: none; /* never a border/frame */
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
transform: none; /* no size change, even with theme hover styles */
|
||||
border-radius: 3px;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.18s ease, color 0.18s ease;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
/* Hover: only the colour changes — no frame, no resize. */
|
||||
.cb-blocker .cb-blocker__button:hover {
|
||||
background-color: var(--cb-btn-hover-bg);
|
||||
color: var(--cb-btn-hover-text);
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Keyboard focus keeps a subtle, accessible ring (not shown on mouse hover). */
|
||||
.cb-blocker .cb-blocker__button:focus-visible {
|
||||
outline: 2px solid var(--cb-btn-hover-bg);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ── Revoke button ── */
|
||||
.cb-revoke-btn {
|
||||
display: inline-block;
|
||||
padding: 0.5em 1.2em;
|
||||
background-color: #111111;
|
||||
color: #ffffff;
|
||||
border: 2px solid #111111;
|
||||
border-radius: 3px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.18s ease, color 0.18s ease;
|
||||
}
|
||||
|
||||
.cb-revoke-btn:hover,
|
||||
.cb-revoke-btn:focus-visible {
|
||||
background-color: #ffffff;
|
||||
color: #111111;
|
||||
}
|
||||
|
||||
/* ── Revoke as a visible text link (default) ── */
|
||||
.cb-revoke-link {
|
||||
display: inline-block;
|
||||
color: var(--cb-btn-bg);
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cb-revoke-link:hover,
|
||||
.cb-revoke-link:focus-visible {
|
||||
color: var(--cb-btn-hover-bg);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cb-revoke-note {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 0.8125rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ── Services overview (for the privacy policy) ── */
|
||||
.cb-services-list {
|
||||
list-style: none;
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cb-services-list .cb-service-entry {
|
||||
margin: 0 0 1.1em;
|
||||
padding: 0 0 1.1em;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.cb-services-list .cb-service-entry:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.cb-services-list .cb-service-entry__name {
|
||||
font-weight: 700;
|
||||
font-size: 1.02em;
|
||||
}
|
||||
|
||||
.cb-services-list .cb-service-entry dl {
|
||||
margin: 0.4em 0 0;
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
gap: 2px 14px;
|
||||
}
|
||||
|
||||
.cb-services-list .cb-service-entry dt {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.cb-services-list .cb-service-entry dd {
|
||||
margin: 0;
|
||||
}
|
||||
115
gdpr-content-blocker/assets/frontend.js
Normal file
115
gdpr-content-blocker/assets/frontend.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
} )();
|
||||
Reference in New Issue
Block a user