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 );
|
||||
Reference in New Issue
Block a user