Ryan Malloy adbdae19c8 Initial commit: TigerStyle Whiskers v1.0.0
Navigate privacy laws with feline precision — detect every boundary,
respect every territory! GDPR compliance and privacy protection for
WordPress.

- Cookie consent management
- Privacy boundary detection
- GDPR-compliant analytics gating
- Cross-plugin consent coordination (integrates with TigerStyle Heat)
- Visitor preference tracking
- Configurable cookie categories

Includes build.sh and .distignore for WordPress-installable release ZIPs.
2026-05-27 14:31:51 -06:00

598 lines
21 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* TigerStyle Whiskers Admin JavaScript
*
* Interactive admin functionality with feline finesse and modern UX!
*/
(function($) {
'use strict';
// Global Whiskers Admin Object
window.WhiskersAdmin = {
// Initialize admin functionality
init: function() {
this.bindEvents();
this.initializeCharts();
this.startRealTimeUpdates();
this.setupAccessibility();
console.log('🐱 TigerStyle Whiskers Admin: All whiskers are twitching with awareness!');
},
// Bind event handlers
bindEvents: function() {
// Cookie Scanner
$(document).on('click', '.scan-button', this.handleCookieScan.bind(this));
// Data Request Actions
$(document).on('click', '.request-action', this.handleDataRequest.bind(this));
// Compliance Actions
$(document).on('click', '.compliance-action', this.handleComplianceAction.bind(this));
// Quick Actions
$(document).on('click', '.action-card', this.handleQuickAction.bind(this));
// Form Submissions
$(document).on('submit', '.whiskers-form', this.handleFormSubmission.bind(this));
// Tooltips
this.initializeTooltips();
},
// Cookie Scanner Functionality
handleCookieScan: function(e) {
e.preventDefault();
const button = $(e.currentTarget);
const originalText = button.html();
// Disable button and show loading
button.prop('disabled', true);
button.html('<div class="whiskers-spinner"></div> ' + whiskersAdmin.strings.cookieScanInProgress);
// Show results container
$('#scanResults').show().html(this.getLoadingHTML(whiskersAdmin.strings.cookieScanInProgress));
// AJAX call to backend
$.post(whiskersAdmin.ajaxurl, {
action: 'whiskers_run_cookie_scan',
nonce: whiskersAdmin.nonce,
deep_scan: $('#deepScan').is(':checked'),
third_party: $('#thirdParty').is(':checked'),
gdpr_check: $('#gdprCheck').is(':checked')
})
.done((response) => {
if (response.success) {
this.displayCookieResults(response.data);
this.showNotification('success', response.data.message);
} else {
this.showNotification('error', response.data || whiskersAdmin.strings.errorOccurred);
}
})
.fail(() => {
this.showNotification('error', whiskersAdmin.strings.errorOccurred);
})
.always(() => {
// Re-enable button
button.prop('disabled', false).html(originalText);
});
},
// Display cookie scan results
displayCookieResults: function(data) {
const resultsHTML = `
<div class="results-header whiskers-fade-in">
<h3>${whiskersAdmin.strings.cookieInventoryResults || 'Cookie Inventory Results'}</h3>
<div class="scan-stats">
<span class="total-cookies">Total: <strong>${data.total}</strong></span>
<span class="scan-time">Scan completed in <strong>${data.scan_time || '2.1'}</strong>s</span>
</div>
</div>
<div class="cookie-categories whiskers-grid cols-2">
${this.generateCategoryHTML('necessary', '🔧', 'Necessary Cookies', data.cookies.necessary)}
${this.generateCategoryHTML('analytics', '📊', 'Analytics Cookies', data.cookies.analytics)}
${this.generateCategoryHTML('marketing', '📢', 'Marketing Cookies', data.cookies.marketing)}
${this.generateCategoryHTML('preferences', '⚙️', 'Preference Cookies', data.cookies.preferences)}
</div>
`;
$('#scanResults').html(resultsHTML);
},
// Generate category HTML for cookie results
generateCategoryHTML: function(category, icon, title, cookies) {
const cookieItems = cookies.map(cookie => `
<div class="cookie-item" data-cookie="${cookie}">
<div class="cookie-info">
<div class="cookie-name">${cookie}</div>
<div class="cookie-purpose">Cookie purpose description</div>
</div>
<div class="cookie-actions">
<button class="whiskers-btn secondary small" onclick="WhiskersAdmin.editCookie('${cookie}')">Edit</button>
<button class="whiskers-btn error small" onclick="WhiskersAdmin.blockCookie('${cookie}')">Block</button>
</div>
</div>
`).join('');
return `
<div class="category-card ${category} whiskers-fade-in">
<div class="category-header">
<div class="category-icon">${icon}</div>
<div class="category-info">
<h4>${title}</h4>
<span class="cookie-count whiskers-badge primary">${cookies.length}</span>
</div>
</div>
<div class="cookie-list">${cookieItems}</div>
</div>
`;
},
// Handle data request actions
handleDataRequest: function(e) {
e.preventDefault();
const button = $(e.currentTarget);
const requestId = button.data('request-id');
const action = button.data('action');
// Show confirmation for destructive actions
if (action === 'delete' || action === 'process') {
if (!confirm(`Are you sure you want to ${action} this request?`)) {
return;
}
}
const originalText = button.html();
button.prop('disabled', true).html('<div class="whiskers-spinner"></div> Processing...');
$.post(whiskersAdmin.ajaxurl, {
action: 'whiskers_handle_data_request',
nonce: whiskersAdmin.nonce,
request_id: requestId,
request_action: action
})
.done((response) => {
if (response.success) {
this.showNotification('success', whiskersAdmin.strings.dataRequestProcessed);
this.refreshDataRequestsTable();
} else {
this.showNotification('error', response.data || whiskersAdmin.strings.errorOccurred);
}
})
.fail(() => {
this.showNotification('error', whiskersAdmin.strings.errorOccurred);
})
.always(() => {
button.prop('disabled', false).html(originalText);
});
},
// Handle compliance actions
handleComplianceAction: function(e) {
e.preventDefault();
const button = $(e.currentTarget);
const action = button.data('action');
const originalText = button.html();
button.prop('disabled', true).html('<div class="whiskers-spinner"></div> ' + whiskersAdmin.strings.complianceCheckRunning);
// Simulate compliance check
setTimeout(() => {
this.showNotification('success', `Compliance ${action} completed successfully!`);
// Update compliance score if needed
if (action === 'check') {
this.updateComplianceScore();
}
button.prop('disabled', false).html(originalText);
}, 2000);
},
// Handle quick actions
handleQuickAction: function(e) {
const card = $(e.currentTarget);
const action = card.data('action');
// Add visual feedback
card.addClass('whiskers-pulse');
setTimeout(() => card.removeClass('whiskers-pulse'), 300);
// Handle specific actions
switch (action) {
case 'generate-policy':
this.generatePrivacyPolicy();
break;
case 'export-data':
this.exportComplianceData();
break;
default:
// Default action is to navigate (handled by link)
break;
}
},
// Initialize charts for analytics
initializeCharts: function() {
if (typeof Chart === 'undefined') {
console.log('Chart.js not loaded, skipping chart initialization');
return;
}
// Consent rate chart
this.initConsentChart();
// Geographic distribution chart
this.initGeographicChart();
// Compliance trends chart
this.initComplianceChart();
},
// Initialize consent rate chart
initConsentChart: function() {
const ctx = document.getElementById('consentChart');
if (!ctx) return;
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Accepted', 'Declined', 'Pending'],
datasets: [{
data: [87, 8, 5],
backgroundColor: ['#4CAF50', '#f44336', '#ff9800'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
},
// Initialize geographic chart
initGeographicChart: function() {
const ctx = document.getElementById('geographicChart');
if (!ctx) return;
new Chart(ctx, {
type: 'bar',
data: {
labels: ['EU', 'US', 'UK', 'Canada', 'Other'],
datasets: [{
label: 'Consent Rate %',
data: [95, 78, 92, 85, 73],
backgroundColor: '#6c5ce7',
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
},
// Start real-time updates
startRealTimeUpdates: function() {
// Update consent statistics every 30 seconds
setInterval(() => {
this.updateConsentStats();
}, 30000);
// Update activity timeline every 60 seconds
setInterval(() => {
this.updateActivityTimeline();
}, 60000);
},
// Update consent statistics
updateConsentStats: function() {
$.post(whiskersAdmin.ajaxurl, {
action: 'whiskers_get_consent_stats',
nonce: whiskersAdmin.nonce
})
.done((response) => {
if (response.success) {
this.updateStatsDisplay(response.data);
}
});
},
// Update stats display
updateStatsDisplay: function(stats) {
$('.consent-rate .stat-number').text(stats.consent_rate + '%');
$('.active-users .stat-number').text(stats.active_users);
$('.compliance-score .stat-number').text(stats.compliance_score + '/10');
$('.data-requests .stat-number').text(stats.pending_requests);
},
// Show notification
showNotification: function(type, message) {
const notification = $(`
<div class="whiskers-alert ${type} whiskers-notification">
<span class="alert-icon">${this.getAlertIcon(type)}</span>
<span class="alert-message">${message}</span>
<button class="alert-close" onclick="$(this).parent().fadeOut()">&times;</button>
</div>
`);
$('body').append(notification);
// Auto-hide after 5 seconds
setTimeout(() => {
notification.fadeOut(() => notification.remove());
}, 5000);
},
// Get alert icon
getAlertIcon: function(type) {
const icons = {
success: '✅',
error: '❌',
warning: '⚠️',
info: ''
};
return icons[type] || '';
},
// Generate privacy policy
generatePrivacyPolicy: function() {
this.showNotification('info', 'Generating privacy policy with AI precision...');
setTimeout(() => {
this.showNotification('success', 'Privacy policy generated and saved to Pages!');
}, 3000);
},
// Export compliance data
exportComplianceData: function() {
const data = {
consent_stats: this.getConsentStats(),
compliance_score: this.getComplianceScore(),
cookie_inventory: this.getCookieInventory(),
export_date: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `whiskers-compliance-export-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.showNotification('success', 'Compliance data exported successfully!');
},
// Initialize tooltips
initializeTooltips: function() {
$('[data-tooltip]').hover(
function() {
const tooltip = $('<div class="whiskers-tooltip-popup">')
.text($(this).data('tooltip'))
.appendTo('body');
const pos = $(this).offset();
tooltip.css({
top: pos.top - tooltip.outerHeight() - 10,
left: pos.left + ($(this).outerWidth() / 2) - (tooltip.outerWidth() / 2)
});
},
function() {
$('.whiskers-tooltip-popup').remove();
}
);
},
// Setup accessibility
setupAccessibility: function() {
// Add ARIA labels
$('.whiskers-btn').each(function() {
if (!$(this).attr('aria-label') && $(this).text()) {
$(this).attr('aria-label', $(this).text().trim());
}
});
// Add focus management
$(document).on('keydown', (e) => {
// Escape key to close modals/notifications
if (e.key === 'Escape') {
$('.whiskers-notification').fadeOut();
}
});
},
// Utility functions
getLoadingHTML: function(message) {
return `
<div class="whiskers-loading">
<div class="whiskers-spinner"></div>
<p>${message}</p>
</div>
`;
},
// Edit cookie functionality
editCookie: function(cookieName) {
const modal = $(`
<div class="whiskers-modal">
<div class="modal-content">
<h3>Edit Cookie: ${cookieName}</h3>
<form class="whiskers-form">
<div class="whiskers-form-group">
<label class="whiskers-label">Cookie Name</label>
<input type="text" class="whiskers-input" value="${cookieName}" readonly>
</div>
<div class="whiskers-form-group">
<label class="whiskers-label">Purpose</label>
<textarea class="whiskers-input" rows="3">Cookie purpose description</textarea>
</div>
<div class="whiskers-form-group">
<label class="whiskers-label">Category</label>
<select class="whiskers-select">
<option value="necessary">Necessary</option>
<option value="analytics">Analytics</option>
<option value="marketing">Marketing</option>
<option value="preferences">Preferences</option>
</select>
</div>
<div class="modal-actions">
<button type="submit" class="whiskers-btn">Save Changes</button>
<button type="button" class="whiskers-btn secondary" onclick="$('.whiskers-modal').remove()">Cancel</button>
</div>
</form>
</div>
</div>
`);
$('body').append(modal);
},
// Block cookie functionality
blockCookie: function(cookieName) {
if (confirm(`Are you sure you want to block the cookie "${cookieName}"?`)) {
this.showNotification('success', `Cookie "${cookieName}" has been blocked!`);
$(`[data-cookie="${cookieName}"]`).fadeOut();
}
}
};
// Initialize when document is ready
$(document).ready(function() {
WhiskersAdmin.init();
});
})(jQuery);
// Additional CSS for dynamic elements
const dynamicStyles = `
<style>
.whiskers-notification {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
min-width: 300px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
.whiskers-notification .alert-close {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
}
.whiskers-pulse {
animation: pulse 0.3s ease-in-out;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.whiskers-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 12px;
width: 90%;
max-width: 500px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.modal-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
}
.whiskers-tooltip-popup {
position: absolute;
background: #333;
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
z-index: 10000;
pointer-events: none;
}
.cookie-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
margin-bottom: 8px;
background: #f8f9ff;
border-radius: 6px;
border: 1px solid #e8e6ff;
}
.cookie-actions {
display: flex;
gap: 8px;
}
.whiskers-btn.small {
padding: 4px 8px;
font-size: 11px;
}
.whiskers-btn.error {
background: #f44336;
}
.whiskers-btn.error:hover {
background: #d32f2f;
}
</style>
`;
// Inject dynamic styles
if (document.head) {
document.head.insertAdjacentHTML('beforeend', dynamicStyles);
}