| 1 |
/** |
| 2 |
* Dashboard-inspired JavaScript enhancements for Metasync plugin |
| 3 |
* Adds modern interactions and animations matching the dashboard design |
| 4 |
*/ |
| 5 |
|
| 6 |
(function ($) { |
| 7 |
'use strict'; |
| 8 |
|
| 9 |
$(document).ready(function () { |
| 10 |
|
| 11 |
// Only run on plugin pages |
| 12 |
if (!$('.metasync-dashboard-wrap').length) { |
| 13 |
return; |
| 14 |
} |
| 15 |
|
| 16 |
// Fix plugin admin styles (scoped) |
| 17 |
fixPluginAdminStyles(); |
| 18 |
|
| 19 |
// Add loading states to buttons |
| 20 |
enhanceButtonInteractions(); |
| 21 |
|
| 22 |
// Add smooth transitions to cards |
| 23 |
// enhanceCardAnimations(); // Disabled - was too distracting in settings pages |
| 24 |
|
| 25 |
// Enhance form validation |
| 26 |
enhanceFormValidation(); |
| 27 |
|
| 28 |
// Add dashboard-style notifications |
| 29 |
enhanceDashboardNotifications(); |
| 30 |
|
| 31 |
// Enhanced API response handling for Google Console |
| 32 |
enhanceGoogleConsoleResponse(); |
| 33 |
|
| 34 |
// Enhanced Search Atlas Connect handling |
| 35 |
enhanceSearchAtlasConnect(); |
| 36 |
|
| 37 |
// Integrate with existing connect functions |
| 38 |
integrateConnectWithDashboard(); |
| 39 |
|
| 40 |
// Add tooltips to stat cards |
| 41 |
addStatCardTooltips(); |
| 42 |
|
| 43 |
// Initialize progress bars |
| 44 |
initializeProgressBars(); |
| 45 |
|
| 46 |
// Fix stat card text overflow |
| 47 |
fixStatCardTextOverflow(); |
| 48 |
|
| 49 |
// Handle responsive layout |
| 50 |
handleResponsiveLayout(); |
| 51 |
|
| 52 |
}); |
| 53 |
|
| 54 |
/** |
| 55 |
* Enhance button interactions with loading states - SCOPED TO PLUGIN |
| 56 |
*/ |
| 57 |
function enhanceButtonInteractions() { |
| 58 |
$('.metasync-dashboard-wrap .button-primary, .metasync-dashboard-wrap .button-secondary').on('click', function (e) { |
| 59 |
const $button = $(this); |
| 60 |
|
| 61 |
// Don't add loading to certain buttons |
| 62 |
if ($button.hasClass('no-loading') || $button.attr('type') === 'submit') { |
| 63 |
return; |
| 64 |
} |
| 65 |
|
| 66 |
// Add loading state |
| 67 |
$button.addClass('dashboard-loading'); |
| 68 |
$button.prop('disabled', true); |
| 69 |
|
| 70 |
// Remove loading state after animation |
| 71 |
setTimeout(() => { |
| 72 |
$button.removeClass('dashboard-loading'); |
| 73 |
$button.prop('disabled', false); |
| 74 |
}, 2000); |
| 75 |
}); |
| 76 |
} |
| 77 |
|
| 78 |
/** |
| 79 |
* Add hover animations to dashboard cards - SCOPED TO PLUGIN |
| 80 |
*/ |
| 81 |
function enhanceCardAnimations() { |
| 82 |
$('.metasync-dashboard-wrap .dashboard-card').hover( |
| 83 |
function () { |
| 84 |
$(this).addClass('card-hover'); |
| 85 |
}, |
| 86 |
function () { |
| 87 |
$(this).removeClass('card-hover'); |
| 88 |
} |
| 89 |
); |
| 90 |
|
| 91 |
// Add stagger animation to stat cards |
| 92 |
$('.metasync-dashboard-wrap .dashboard-stat-card').each(function (index) { |
| 93 |
$(this).css('animation-delay', (index * 0.1) + 's'); |
| 94 |
$(this).addClass('fade-in-up'); |
| 95 |
}); |
| 96 |
} |
| 97 |
|
| 98 |
/** |
| 99 |
* Enhance form validation with better UX - SCOPED TO PLUGIN |
| 100 |
*/ |
| 101 |
function enhanceFormValidation() { |
| 102 |
$('.metasync-dashboard-wrap input, .metasync-dashboard-wrap textarea, .metasync-dashboard-wrap select').on('blur', function () { |
| 103 |
const $input = $(this); |
| 104 |
|
| 105 |
if ($input.is(':invalid')) { |
| 106 |
$input.addClass('input-error'); |
| 107 |
showInputError($input, 'Please check this field'); |
| 108 |
} else { |
| 109 |
$input.removeClass('input-error'); |
| 110 |
hideInputError($input); |
| 111 |
} |
| 112 |
}); |
| 113 |
|
| 114 |
$('.metasync-dashboard-wrap input, .metasync-dashboard-wrap textarea, .metasync-dashboard-wrap select').on('input', function () { |
| 115 |
const $input = $(this); |
| 116 |
if ($input.hasClass('input-error') && $input.is(':valid')) { |
| 117 |
$input.removeClass('input-error'); |
| 118 |
hideInputError($input); |
| 119 |
} |
| 120 |
}); |
| 121 |
} |
| 122 |
|
| 123 |
/** |
| 124 |
* Show input error with dashboard styling |
| 125 |
*/ |
| 126 |
function showInputError($input, message) { |
| 127 |
const errorId = 'error-' + Math.random().toString(36).substr(2, 9); |
| 128 |
|
| 129 |
if ($input.siblings('.input-error-message').length === 0) { |
| 130 |
$input.after(` |
| 131 |
<div class="input-error-message" id="${errorId}" style=" |
| 132 |
color: var(--dashboard-error); |
| 133 |
font-size: 12px; |
| 134 |
margin-top: 4px; |
| 135 |
opacity: 0; |
| 136 |
transform: translateY(-10px); |
| 137 |
transition: all 0.3s ease; |
| 138 |
">${message}</div> |
| 139 |
`); |
| 140 |
|
| 141 |
setTimeout(() => { |
| 142 |
$(`#${errorId}`).css({ |
| 143 |
opacity: 1, |
| 144 |
transform: 'translateY(0)' |
| 145 |
}); |
| 146 |
}, 10); |
| 147 |
} |
| 148 |
} |
| 149 |
|
| 150 |
/** |
| 151 |
* Hide input error |
| 152 |
*/ |
| 153 |
function hideInputError($input) { |
| 154 |
const $error = $input.siblings('.input-error-message'); |
| 155 |
$error.css({ |
| 156 |
opacity: 0, |
| 157 |
transform: 'translateY(-10px)' |
| 158 |
}); |
| 159 |
|
| 160 |
setTimeout(() => { |
| 161 |
$error.remove(); |
| 162 |
}, 300); |
| 163 |
} |
| 164 |
|
| 165 |
/** |
| 166 |
* Enhance dashboard notifications - SCOPED TO PLUGIN |
| 167 |
*/ |
| 168 |
function enhanceDashboardNotifications() { |
| 169 |
$('.metasync-dashboard-wrap .notice').each(function () { |
| 170 |
const $notice = $(this); |
| 171 |
$notice.addClass('dashboard-notice-enhanced'); |
| 172 |
|
| 173 |
// Add close button |
| 174 |
if (!$notice.find('.notice-dismiss').length) { |
| 175 |
$notice.append(` |
| 176 |
<button type="button" class="notice-dismiss"> |
| 177 |
<span class="screen-reader-text">Dismiss this notice.</span> |
| 178 |
</button> |
| 179 |
`); |
| 180 |
} |
| 181 |
}); |
| 182 |
|
| 183 |
// Handle notice dismiss ONLY within plugin pages |
| 184 |
$('.metasync-dashboard-wrap').on('click', '.notice-dismiss', function () { |
| 185 |
$(this).closest('.notice').fadeOut(300); |
| 186 |
}); |
| 187 |
} |
| 188 |
|
| 189 |
/** |
| 190 |
* Enhanced Google Console API response handling |
| 191 |
*/ |
| 192 |
function enhanceGoogleConsoleResponse() { |
| 193 |
const $responseDiv = $('#metasync-giapi-response'); |
| 194 |
|
| 195 |
if ($responseDiv.length === 0) { |
| 196 |
return; |
| 197 |
} |
| 198 |
|
| 199 |
// Show response with animation when data is received |
| 200 |
const originalShow = $responseDiv.show; |
| 201 |
$responseDiv.show = function () { |
| 202 |
$responseDiv.css('opacity', 0).slideDown(300).animate({ opacity: 1 }, 300); |
| 203 |
return this; |
| 204 |
}; |
| 205 |
|
| 206 |
// Enhanced send button for Google Console |
| 207 |
$('#metasync-btn-send').on('click', function () { |
| 208 |
const $button = $(this); |
| 209 |
const originalText = $button.text(); |
| 210 |
|
| 211 |
$button.html('🔄 Sending...').prop('disabled', true); |
| 212 |
|
| 213 |
// Simulate API call progress (replace with actual API integration) |
| 214 |
let progress = 0; |
| 215 |
const progressInterval = setInterval(() => { |
| 216 |
progress += 10; |
| 217 |
if (progress >= 100) { |
| 218 |
clearInterval(progressInterval); |
| 219 |
$button.html(originalText).prop('disabled', false); |
| 220 |
} |
| 221 |
}, 200); |
| 222 |
}); |
| 223 |
} |
| 224 |
|
| 225 |
/** |
| 226 |
* Add tooltips to stat cards - SCOPED TO PLUGIN |
| 227 |
*/ |
| 228 |
function addStatCardTooltips() { |
| 229 |
$('.metasync-dashboard-wrap .dashboard-stat-card').each(function () { |
| 230 |
const $card = $(this); |
| 231 |
const label = $card.find('.dashboard-stat-label').text(); |
| 232 |
const value = $card.find('.dashboard-stat-value').text(); |
| 233 |
|
| 234 |
$card.attr('title', `${label}: ${value}`) |
| 235 |
.addClass('dashboard-tooltip'); |
| 236 |
}); |
| 237 |
} |
| 238 |
|
| 239 |
/** |
| 240 |
* Initialize animated progress bars - SCOPED TO PLUGIN |
| 241 |
*/ |
| 242 |
function initializeProgressBars() { |
| 243 |
$('.metasync-dashboard-wrap .dashboard-progress-bar').each(function () { |
| 244 |
const $bar = $(this); |
| 245 |
const width = $bar.data('width') || '0%'; |
| 246 |
|
| 247 |
// Animate progress bar on scroll into view |
| 248 |
const observer = new IntersectionObserver((entries) => { |
| 249 |
entries.forEach(entry => { |
| 250 |
if (entry.isIntersecting) { |
| 251 |
setTimeout(() => { |
| 252 |
$bar.css('width', width); |
| 253 |
}, 500); |
| 254 |
observer.unobserve(entry.target); |
| 255 |
} |
| 256 |
}); |
| 257 |
}); |
| 258 |
|
| 259 |
observer.observe($bar[0]); |
| 260 |
}); |
| 261 |
} |
| 262 |
|
| 263 |
/** |
| 264 |
* Add success animation to forms after submission |
| 265 |
*/ |
| 266 |
function addSuccessAnimation($element) { |
| 267 |
$element.addClass('metasync-sa-connect-success-animation'); |
| 268 |
setTimeout(() => { |
| 269 |
$element.removeClass('metasync-sa-connect-success-animation'); |
| 270 |
}, 600); |
| 271 |
} |
| 272 |
|
| 273 |
/** |
| 274 |
* Enhanced tab navigation - SCOPED TO PLUGIN |
| 275 |
*/ |
| 276 |
$('.metasync-dashboard-wrap .metasync-nav-tab, .metasync-dashboard-wrap .nav-tab').on('click', function (e) { |
| 277 |
const $tab = $(this); |
| 278 |
|
| 279 |
// Add loading effect |
| 280 |
$tab.addClass('tab-loading'); |
| 281 |
|
| 282 |
// Remove loading after page load |
| 283 |
setTimeout(() => { |
| 284 |
$tab.removeClass('tab-loading'); |
| 285 |
}, 1000); |
| 286 |
}); |
| 287 |
|
| 288 |
/** |
| 289 |
* Set active navigation tab based on current page |
| 290 |
*/ |
| 291 |
function setActiveNavTab() { |
| 292 |
const currentUrl = window.location.href; |
| 293 |
const $navTabs = $('.metasync-dashboard-wrap .metasync-nav-tab'); |
| 294 |
|
| 295 |
$navTabs.each(function () { |
| 296 |
const $tab = $(this); |
| 297 |
const tabHref = $tab.attr('href'); |
| 298 |
|
| 299 |
if (tabHref && currentUrl.includes(tabHref.split('?')[1])) { |
| 300 |
$navTabs.removeClass('active'); |
| 301 |
$tab.addClass('active'); |
| 302 |
} |
| 303 |
}); |
| 304 |
} |
| 305 |
|
| 306 |
// Set active tab on page load |
| 307 |
setActiveNavTab(); |
| 308 |
|
| 309 |
/** |
| 310 |
* Add keyboard navigation for accessibility - SCOPED TO PLUGIN |
| 311 |
*/ |
| 312 |
$('.metasync-dashboard-wrap .dashboard-card').attr('tabindex', '0').on('keydown', function (e) { |
| 313 |
if (e.key === 'Enter' || e.key === ' ') { |
| 314 |
$(this).click(); |
| 315 |
} |
| 316 |
}); |
| 317 |
|
| 318 |
/** |
| 319 |
* Dark mode toggle functionality (if needed) |
| 320 |
*/ |
| 321 |
function addDarkModeToggle() { |
| 322 |
// This can be expanded if manual dark mode toggle is needed |
| 323 |
// Currently the dashboard is always dark themed |
| 324 |
} |
| 325 |
|
| 326 |
/** |
| 327 |
* Responsive navigation improvements |
| 328 |
*/ |
| 329 |
function enhanceResponsiveNavigation() { |
| 330 |
const $navTabs = $('.nav-tab-wrapper'); |
| 331 |
|
| 332 |
if ($navTabs.length && window.innerWidth < 768) { |
| 333 |
$navTabs.addClass('nav-mobile'); |
| 334 |
} |
| 335 |
|
| 336 |
$(window).on('resize', function () { |
| 337 |
if (window.innerWidth < 768) { |
| 338 |
$navTabs.addClass('nav-mobile'); |
| 339 |
} else { |
| 340 |
$navTabs.removeClass('nav-mobile'); |
| 341 |
} |
| 342 |
}); |
| 343 |
} |
| 344 |
|
| 345 |
// Initialize responsive enhancements |
| 346 |
enhanceResponsiveNavigation(); |
| 347 |
|
| 348 |
/** |
| 349 |
* Fix WordPress admin styles - SCOPED TO PLUGIN ONLY |
| 350 |
*/ |
| 351 |
function fixPluginAdminStyles() { |
| 352 |
const $pluginWrap = $('.metasync-dashboard-wrap'); |
| 353 |
|
| 354 |
if ($pluginWrap.length === 0) { |
| 355 |
return; |
| 356 |
} |
| 357 |
|
| 358 |
// Handle WordPress notices ONLY within plugin pages |
| 359 |
$pluginWrap.find('.notice, .error, .updated').each(function () { |
| 360 |
$(this).addClass('dashboard-notice'); |
| 361 |
}); |
| 362 |
|
| 363 |
// Fix nav tabs background ONLY within plugin pages |
| 364 |
$pluginWrap.find('.nav-tab-wrapper').css('background', 'transparent'); |
| 365 |
} |
| 366 |
|
| 367 |
/** |
| 368 |
* Fix stat card text overflow issues - SCOPED TO PLUGIN |
| 369 |
*/ |
| 370 |
function fixStatCardTextOverflow() { |
| 371 |
$('.metasync-dashboard-wrap .dashboard-stat-card').each(function () { |
| 372 |
const $card = $(this); |
| 373 |
const $value = $card.find('.dashboard-stat-value'); |
| 374 |
const $label = $card.find('.dashboard-stat-label'); |
| 375 |
|
| 376 |
// Handle long theme names or values |
| 377 |
if ($value.text().length > 20) { |
| 378 |
$value.addClass('long-text'); |
| 379 |
$card.attr('title', $value.text()); |
| 380 |
} |
| 381 |
|
| 382 |
// Adjust font size based on text length |
| 383 |
if ($value.text().length > 15) { |
| 384 |
$value.css('font-size', '1.4rem'); |
| 385 |
} |
| 386 |
if ($value.text().length > 25) { |
| 387 |
$value.css('font-size', '1.2rem'); |
| 388 |
} |
| 389 |
}); |
| 390 |
} |
| 391 |
|
| 392 |
/** |
| 393 |
* Handle responsive layout changes - SCOPED TO PLUGIN |
| 394 |
*/ |
| 395 |
function handleResponsiveLayout() { |
| 396 |
function adjustLayout() { |
| 397 |
const $pluginWrap = $('.metasync-dashboard-wrap'); |
| 398 |
if ($pluginWrap.length === 0) { |
| 399 |
return; |
| 400 |
} |
| 401 |
|
| 402 |
const windowWidth = $(window).width(); |
| 403 |
|
| 404 |
if (windowWidth < 768) { |
| 405 |
$pluginWrap.find('.dashboard-stats').addClass('mobile-layout'); |
| 406 |
$pluginWrap.find('.metasync-sa-connect-buttons').addClass('mobile-buttons'); |
| 407 |
} else { |
| 408 |
$pluginWrap.find('.dashboard-stats').removeClass('mobile-layout'); |
| 409 |
$pluginWrap.find('.metasync-sa-connect-buttons').removeClass('mobile-buttons'); |
| 410 |
} |
| 411 |
|
| 412 |
if (windowWidth < 1200) { |
| 413 |
$pluginWrap.find('.dashboard-card').addClass('compact-layout'); |
| 414 |
} else { |
| 415 |
$pluginWrap.find('.dashboard-card').removeClass('compact-layout'); |
| 416 |
} |
| 417 |
} |
| 418 |
|
| 419 |
// Run on load |
| 420 |
adjustLayout(); |
| 421 |
|
| 422 |
// Run on resize |
| 423 |
$(window).on('resize', debounce(adjustLayout, 250)); |
| 424 |
} |
| 425 |
|
| 426 |
/** |
| 427 |
* Debounce function for performance |
| 428 |
*/ |
| 429 |
function debounce(func, wait) { |
| 430 |
let timeout; |
| 431 |
return function executedFunction(...args) { |
| 432 |
const later = () => { |
| 433 |
clearTimeout(timeout); |
| 434 |
func(...args); |
| 435 |
}; |
| 436 |
clearTimeout(timeout); |
| 437 |
timeout = setTimeout(later, wait); |
| 438 |
}; |
| 439 |
} |
| 440 |
|
| 441 |
/** |
| 442 |
* Enhanced Search Atlas Connect handling - SCOPED TO PLUGIN |
| 443 |
* |
| 444 |
* Handles 1-click connect to retrieve Search Atlas API key and Otto UUID. |
| 445 |
* Does NOT create WordPress login sessions. |
| 446 |
*/ |
| 447 |
function enhanceSearchAtlasConnect() { |
| 448 |
const $connectContainer = $('.metasync-dashboard-wrap .metasync-sa-connect-container'); |
| 449 |
const $connectBtn = $('.metasync-dashboard-wrap #connect-searchatlas-btn'); |
| 450 |
const $retryBtn = $('.metasync-dashboard-wrap .metasync-sa-connect-retry-btn'); |
| 451 |
|
| 452 |
if ($connectContainer.length === 0) { |
| 453 |
return; |
| 454 |
} |
| 455 |
|
| 456 |
// Handle connect button clicks |
| 457 |
$connectBtn.on('click', function () { |
| 458 |
const $btn = $(this); |
| 459 |
const originalText = $btn.text(); |
| 460 |
|
| 461 |
// Add loading state |
| 462 |
$btn.prop('disabled', true) |
| 463 |
.html('<span class="metasync-sa-connect-loading"></span> Opening Authentication...') |
| 464 |
.addClass('dashboard-loading'); |
| 465 |
|
| 466 |
// Show progress indicator |
| 467 |
showConnectProgress(); |
| 468 |
|
| 469 |
// Start progress timer |
| 470 |
startConnectTimer(); |
| 471 |
|
| 472 |
// Remove any existing status messages |
| 473 |
$('.metasync-sa-connect-status').remove(); |
| 474 |
}); |
| 475 |
|
| 476 |
// Handle retry button clicks |
| 477 |
$(document).on('click', '.metasync-sa-connect-retry-btn', function () { |
| 478 |
const $btn = $(this); |
| 479 |
|
| 480 |
// Remove status messages |
| 481 |
$('.metasync-sa-connect-status').remove(); |
| 482 |
|
| 483 |
// Re-enable connect button |
| 484 |
$connectBtn.prop('disabled', false) |
| 485 |
.html('🔄 Re-authenticate with ' + (window.MetasyncConfig && window.MetasyncConfig.pluginName ? window.MetasyncConfig.pluginName : 'Search Atlas')) |
| 486 |
.removeClass('dashboard-loading'); |
| 487 |
|
| 488 |
// Hide progress |
| 489 |
$('.metasync-sa-connect-progress').fadeOut(300); |
| 490 |
}); |
| 491 |
|
| 492 |
// Handle authentication tips toggle for custom elements only (exclude summary elements) |
| 493 |
$(document).on('click', '.metasync-sa-connect-tips-toggle:not(summary)', function (e) { |
| 494 |
const $toggle = $(this); |
| 495 |
const $content = $toggle.siblings('.metasync-sa-connect-tips-content'); |
| 496 |
const $icon = $toggle.find('.tips-icon'); |
| 497 |
|
| 498 |
$content.slideToggle(300); |
| 499 |
$icon.text($content.is(':visible') ? '▼' : '▶'); |
| 500 |
}); |
| 501 |
|
| 502 |
// Handle details element with custom animation |
| 503 |
$(document).on('click', 'details.metasync-sa-connect-tips summary', function (e) { |
| 504 |
|
| 505 |
const $details = $(this).closest('details'); |
| 506 |
if ($details.prop('open')) { |
| 507 |
// Is open, about to close |
| 508 |
e.preventDefault(); |
| 509 |
$details.find('> div').slideUp(400, () => { |
| 510 |
$details.removeAttr('open'); |
| 511 |
}); |
| 512 |
} else { |
| 513 |
// Is closed, about to open. Let it open, then animate. |
| 514 |
// The 'toggle' event is another way to handle this. |
| 515 |
e.preventDefault(); |
| 516 |
$details.attr('open', 'open'); |
| 517 |
$details.find('> div').hide(); |
| 518 |
$details.find('> div').slideDown(400); |
| 519 |
|
| 520 |
} |
| 521 |
}); |
| 522 |
} |
| 523 |
|
| 524 |
/** |
| 525 |
* Show connect progress indicator |
| 526 |
*/ |
| 527 |
function showConnectProgress() { |
| 528 |
const progressHTML = ` |
| 529 |
<div class="metasync-sa-connect-progress"> |
| 530 |
<div class="metasync-sa-connect-progress-header"> |
| 531 |
<span>🔒 Authentication in Progress</span> |
| 532 |
<span class="metasync-sa-connect-progress-time">0min elapsed, 5min remaining</span> |
| 533 |
</div> |
| 534 |
<div class="metasync-sa-connect-progress-bar"> |
| 535 |
<div class="metasync-sa-connect-progress-fill" style="width: 0%"></div> |
| 536 |
</div> |
| 537 |
<div class="metasync-sa-connect-progress-text">Please complete the connect flow in the popup window...</div> |
| 538 |
</div> |
| 539 |
`; |
| 540 |
|
| 541 |
$('.metasync-sa-connect-container').append(progressHTML); |
| 542 |
} |
| 543 |
|
| 544 |
/** |
| 545 |
* Start connect timer and progress animation |
| 546 |
*/ |
| 547 |
function startConnectTimer() { |
| 548 |
let elapsed = 0; |
| 549 |
const maxTime = 300; // 5 minutes in seconds |
| 550 |
|
| 551 |
const timer = setInterval(() => { |
| 552 |
elapsed += 1; |
| 553 |
const remaining = maxTime - elapsed; |
| 554 |
const progressPercent = (elapsed / maxTime) * 100; |
| 555 |
|
| 556 |
// Update progress bar |
| 557 |
$('.metasync-sa-connect-progress-fill').css('width', progressPercent + '%'); |
| 558 |
|
| 559 |
// Update time display |
| 560 |
const elapsedMin = Math.floor(elapsed / 60); |
| 561 |
const remainingMin = Math.floor(remaining / 60); |
| 562 |
$('.metasync-sa-connect-progress-time').text(`${elapsedMin}min elapsed, ${remainingMin}min remaining`); |
| 563 |
|
| 564 |
// Check if time is up |
| 565 |
if (elapsed >= maxTime) { |
| 566 |
clearInterval(timer); |
| 567 |
showConnectTimeout(); |
| 568 |
} |
| 569 |
}, 1000); |
| 570 |
|
| 571 |
// Store timer reference for cleanup |
| 572 |
$('.metasync-sa-connect-container').data('connect-timer', timer); |
| 573 |
} |
| 574 |
|
| 575 |
/** |
| 576 |
* Show connect timeout message |
| 577 |
*/ |
| 578 |
function showConnectTimeout() { |
| 579 |
showConnectStatus('error', 'Authentication Timeout', 'The authentication window timed out. You can try again when you\'re ready.'); |
| 580 |
} |
| 581 |
|
| 582 |
/** |
| 583 |
* Show connect status message |
| 584 |
*/ |
| 585 |
function showConnectStatus(type, title, message) { |
| 586 |
const iconMap = { |
| 587 |
success: '� |
| 588 |
', |
| 589 |
error: '⚠️', |
| 590 |
info: 'ℹ️', |
| 591 |
warning: '⚠️' |
| 592 |
}; |
| 593 |
|
| 594 |
const statusHTML = ` |
| 595 |
<div class="metasync-sa-connect-status ${type}"> |
| 596 |
<div class="metasync-sa-connect-status-content"> |
| 597 |
<div class="metasync-sa-connect-status-title"> |
| 598 |
${iconMap[type]} ${title} |
| 599 |
</div> |
| 600 |
<div class="metasync-sa-connect-status-message">${message}</div> |
| 601 |
${type === 'error' ? '<button class="metasync-sa-connect-retry-btn">🔄 Try Again</button>' : ''} |
| 602 |
</div> |
| 603 |
</div> |
| 604 |
`; |
| 605 |
|
| 606 |
// Remove existing status |
| 607 |
$('.metasync-sa-connect-status').remove(); |
| 608 |
|
| 609 |
// Add new status |
| 610 |
$('.metasync-sa-connect-container').append(statusHTML); |
| 611 |
|
| 612 |
// Clear timer and progress |
| 613 |
const timer = $('.metasync-sa-connect-container').data('connect-timer'); |
| 614 |
if (timer) { |
| 615 |
clearInterval(timer); |
| 616 |
} |
| 617 |
|
| 618 |
// Reset button state |
| 619 |
$('#connect-searchatlas-btn').prop('disabled', false) |
| 620 |
.html('🔄 Re-authenticate with ' + (window.MetasyncConfig && window.MetasyncConfig.pluginName ? window.MetasyncConfig.pluginName : 'Search Atlas')) |
| 621 |
.removeClass('dashboard-loading'); |
| 622 |
|
| 623 |
// Hide progress after delay |
| 624 |
setTimeout(() => { |
| 625 |
$('.metasync-sa-connect-progress').fadeOut(300); |
| 626 |
}, 1000); |
| 627 |
} |
| 628 |
|
| 629 |
/** |
| 630 |
* Integrate with existing Search Atlas connect functions |
| 631 |
*/ |
| 632 |
function integrateConnectWithDashboard() { |
| 633 |
// Override existing SSO status display to use dashboard styling |
| 634 |
if (typeof window.showConnectStatus !== 'undefined') { |
| 635 |
const originalShowConnectStatus = window.showConnectStatus; |
| 636 |
|
| 637 |
window.showConnectStatus = function (type, title, message, actions) { |
| 638 |
// If we're on a dashboard page, use our enhanced styling |
| 639 |
if ($('.metasync-dashboard-wrap').length > 0) { |
| 640 |
showConnectStatus(type, title, message); |
| 641 |
} else { |
| 642 |
// Fallback to original function for non-dashboard pages |
| 643 |
originalShowConnectStatus.call(this, type, title, message, actions); |
| 644 |
} |
| 645 |
}; |
| 646 |
} |
| 647 |
|
| 648 |
// Enhance existing SSO containers with dashboard styling |
| 649 |
$('.metasync-sa-connect-container').each(function () { |
| 650 |
if (!$(this).closest('.metasync-dashboard-wrap').length) { |
| 651 |
return; // Only enhance containers within dashboard pages |
| 652 |
} |
| 653 |
|
| 654 |
// Add dashboard classes to existing elements |
| 655 |
$(this).addClass('dashboard-enhanced'); |
| 656 |
|
| 657 |
// Style existing status messages |
| 658 |
$(this).find('.metasync-sa-connect-status').each(function () { |
| 659 |
$(this).addClass('dashboard-styled'); |
| 660 |
}); |
| 661 |
|
| 662 |
// Style existing progress elements |
| 663 |
$(this).find('.metasync-sa-connect-progress').each(function () { |
| 664 |
$(this).addClass('dashboard-styled'); |
| 665 |
}); |
| 666 |
|
| 667 |
// Style existing authentication tips (PHP-generated details element) |
| 668 |
// Check for both class-based tips AND details elements to prevent duplication |
| 669 |
const hasExistingTips = $(this).find('.metasync-sa-connect-tips').length > 0 || |
| 670 |
$(this).find('details summary').filter(function () { |
| 671 |
return $(this).text().includes('Authentication Tips'); |
| 672 |
}).length > 0; |
| 673 |
|
| 674 |
if (!hasExistingTips) { |
| 675 |
// No existing tips found, this shouldn't happen but just in case |
| 676 |
console.log('No authentication tips found in connect container'); |
| 677 |
} else { |
| 678 |
// Style existing PHP-generated details element |
| 679 |
$(this).find('details').each(function () { |
| 680 |
const $details = $(this); |
| 681 |
if ($details.find('summary').text().includes('Authentication Tips')) { |
| 682 |
// Add dashboard styling to existing tips |
| 683 |
$details.addClass('metasync-sa-connect-tips dashboard-styled'); |
| 684 |
$details.find('div').addClass('metasync-sa-connect-tips-content'); |
| 685 |
} |
| 686 |
}); |
| 687 |
} |
| 688 |
}); |
| 689 |
|
| 690 |
// Monitor for dynamically added SSO elements |
| 691 |
const observer = new MutationObserver(function (mutations) { |
| 692 |
mutations.forEach(function (mutation) { |
| 693 |
mutation.addedNodes.forEach(function (node) { |
| 694 |
if (node.nodeType === 1) { // Element node |
| 695 |
const $node = $(node); |
| 696 |
|
| 697 |
// Style new SSO status messages |
| 698 |
if ($node.hasClass('metasync-sa-connect-status')) { |
| 699 |
$node.addClass('dashboard-styled'); |
| 700 |
} |
| 701 |
|
| 702 |
// Style new progress indicators |
| 703 |
if ($node.hasClass('metasync-sa-connect-progress')) { |
| 704 |
$node.addClass('dashboard-styled'); |
| 705 |
} |
| 706 |
|
| 707 |
// Check for nested SSO elements |
| 708 |
$node.find('.metasync-sa-connect-status, .metasync-sa-connect-progress').addClass('dashboard-styled'); |
| 709 |
} |
| 710 |
}); |
| 711 |
}); |
| 712 |
}); |
| 713 |
|
| 714 |
// Start observing |
| 715 |
$('.metasync-dashboard-wrap').each(function () { |
| 716 |
observer.observe(this, { |
| 717 |
childList: true, |
| 718 |
subtree: true |
| 719 |
}); |
| 720 |
}); |
| 721 |
} |
| 722 |
|
| 723 |
// Expose functions globally for SSO integration |
| 724 |
window.metasyncDashboard = { |
| 725 |
showConnectStatus: showConnectStatus, |
| 726 |
showConnectProgress: showConnectProgress, |
| 727 |
startConnectTimer: startConnectTimer, |
| 728 |
integrateConnectWithDashboard: integrateConnectWithDashboard |
| 729 |
}; |
| 730 |
|
| 731 |
})(jQuery); |
| 732 |
|
| 733 |
// CSS animations for JavaScript enhancements |
| 734 |
const additionalCSS = ` |
| 735 |
<style> |
| 736 |
.fade-in-up { |
| 737 |
animation: fadeInUp 0.6s ease forwards; |
| 738 |
} |
| 739 |
|
| 740 |
@keyframes fadeInUp { |
| 741 |
from { |
| 742 |
opacity: 0; |
| 743 |
transform: translateY(20px); |
| 744 |
} |
| 745 |
to { |
| 746 |
opacity: 1; |
| 747 |
transform: translateY(0); |
| 748 |
} |
| 749 |
} |
| 750 |
|
| 751 |
.card-hover { |
| 752 |
/* Removed translateY animation - was too distracting in settings pages */ |
| 753 |
/* transform: translateY(-4px) !important; */ |
| 754 |
/* transition: transform 0.3s ease !important; */ |
| 755 |
} |
| 756 |
|
| 757 |
.input-error { |
| 758 |
border-color: var(--dashboard-error) !important; |
| 759 |
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important; |
| 760 |
} |
| 761 |
|
| 762 |
.dashboard-notice-enhanced { |
| 763 |
backdrop-filter: blur(10px); |
| 764 |
animation: slideInRight 0.3s ease; |
| 765 |
} |
| 766 |
|
| 767 |
@keyframes slideInRight { |
| 768 |
from { |
| 769 |
opacity: 0; |
| 770 |
transform: translateX(20px); |
| 771 |
} |
| 772 |
to { |
| 773 |
opacity: 1; |
| 774 |
transform: translateX(0); |
| 775 |
} |
| 776 |
} |
| 777 |
|
| 778 |
.tab-loading { |
| 779 |
position: relative; |
| 780 |
overflow: hidden; |
| 781 |
} |
| 782 |
|
| 783 |
.tab-loading::after { |
| 784 |
content: ''; |
| 785 |
position: absolute; |
| 786 |
top: 0; |
| 787 |
left: -100%; |
| 788 |
width: 100%; |
| 789 |
height: 100%; |
| 790 |
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); |
| 791 |
animation: shimmer 1s ease-in-out; |
| 792 |
} |
| 793 |
|
| 794 |
/* Enhanced loading effect for new nav tabs */ |
| 795 |
.metasync-nav-tab.tab-loading { |
| 796 |
opacity: 0.7; |
| 797 |
} |
| 798 |
|
| 799 |
.metasync-nav-tab.tab-loading .tab-icon { |
| 800 |
animation: spin 1s linear infinite; |
| 801 |
display: inline-flex; |
| 802 |
align-items: center; |
| 803 |
justify-content: center; |
| 804 |
transform-origin: center; |
| 805 |
} |
| 806 |
|
| 807 |
/* Animation for existing authentication tips */ |
| 808 |
.tips-animated { |
| 809 |
animation: slideInTips 0.3s ease; |
| 810 |
} |
| 811 |
|
| 812 |
@keyframes slideInTips { |
| 813 |
from { |
| 814 |
opacity: 0; |
| 815 |
transform: translateY(-10px); |
| 816 |
} |
| 817 |
to { |
| 818 |
opacity: 1; |
| 819 |
transform: translateY(0); |
| 820 |
} |
| 821 |
} |
| 822 |
|
| 823 |
@keyframes shimmer { |
| 824 |
0% { left: -100%; } |
| 825 |
100% { left: 100%; } |
| 826 |
} |
| 827 |
|
| 828 |
.nav-mobile { |
| 829 |
flex-direction: column !important; |
| 830 |
gap: 8px !important; |
| 831 |
} |
| 832 |
|
| 833 |
.nav-mobile .nav-tab { |
| 834 |
text-align: center; |
| 835 |
width: 100%; |
| 836 |
} |
| 837 |
|
| 838 |
/* Additional responsive classes */ |
| 839 |
.mobile-layout .dashboard-stat-card { |
| 840 |
margin-bottom: 12px; |
| 841 |
} |
| 842 |
|
| 843 |
.mobile-buttons { |
| 844 |
flex-direction: column; |
| 845 |
align-items: stretch; |
| 846 |
} |
| 847 |
|
| 848 |
.mobile-buttons button { |
| 849 |
width: 100%; |
| 850 |
margin: 4px 0 !important; |
| 851 |
} |
| 852 |
|
| 853 |
.compact-layout { |
| 854 |
padding: 16px !important; |
| 855 |
} |
| 856 |
|
| 857 |
.long-text { |
| 858 |
word-break: break-word; |
| 859 |
hyphens: auto; |
| 860 |
} |
| 861 |
|
| 862 |
/* Dashboard notice styling */ |
| 863 |
.dashboard-notice { |
| 864 |
backdrop-filter: blur(10px); |
| 865 |
animation: slideInNotice 0.3s ease; |
| 866 |
} |
| 867 |
|
| 868 |
@keyframes slideInNotice { |
| 869 |
from { |
| 870 |
opacity: 0; |
| 871 |
transform: translateX(-20px); |
| 872 |
} |
| 873 |
to { |
| 874 |
opacity: 1; |
| 875 |
transform: translateX(0); |
| 876 |
} |
| 877 |
} |
| 878 |
</style> |
| 879 |
`; |
| 880 |
|
| 881 |
// Inject additional CSS |
| 882 |
document.head.insertAdjacentHTML('beforeend', additionalCSS); |
| 883 |
|