1/** 2 * ============================================================================= 3 * Frontend JS â WTI Google Places (Carousel + Modal Multi-paso) 4 * ============================================================================= 5 * Archivo: wti-google-places/assets/js/wti-gp-front.js 6 * Ubicación: /wp-content/plugins/wti-google-places/assets/js/wti-gp-front.js 7 * Creado: 2026-02-24 8 * Actualizado: 2026-02-24 v1.4.0 â Modal multi-paso con reseñas locales 9 */ 10 11(function() { 12 'use strict'; 13 14 document.addEventListener('DOMContentLoaded', function() { 15 initCarousels(); 16 initReviewModal(); 17 }); 18 19 // âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 20 // CAROUSEL: dots + autoplay + drag/swipe 21 // âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 22 23 function initCarousels() { 24 var carousels = document.querySelectorAll('.wti-gp-reviews__carousel'); 25 26 carousels.forEach(function(carousel) { 27 var track = carousel.querySelector('.wti-gp-reviews__carousel-track'); 28 var dots = carousel.querySelectorAll('.wti-gp-reviews__carousel-dot'); 29 if (!track) return; 30 31 var cards = track.querySelectorAll('.wti-gp-reviews__card'); 32 var current = 0; 33 var total = cards.length; 34 var autoplayMs = parseInt(carousel.getAttribute('data-autoplay')) || 5000; 35 var autoTimer = null; 36 var isPaused = false; 37 38 // ââ Ir a una card por indice ââ 39 function goTo(index) { 40 if (index < 0) index = total - 1; 41 if (index >= total) index = 0; 42 current = index; 43 44 var card = cards[current]; 45 if (card) { 46 track.scrollTo({ 47 left: card.offsetLeft - track.offsetLeft, 48 behavior: 'smooth' 49 }); 50 } 51 updateDots(); 52 } 53 54 // ââ Actualizar dots ââ 55 function updateDots() { 56 dots.forEach(function(dot, i) { 57 if (i === current) { 58 dot.classList.add('wti-gp-reviews__carousel-dot--active'); 59 } else { 60 dot.classList.remove('wti-gp-reviews__carousel-dot--active'); 61 } 62 }); 63 } 64 65 // ââ Click en dots ââ 66 dots.forEach(function(dot) { 67 dot.addEventListener('click', function() { 68 var idx = parseInt(dot.getAttribute('data-index')); 69 goTo(idx); 70 resetAutoplay(); 71 }); 72 }); 73 74 // ââ Autoplay ââ 75 function startAutoplay() { 76 stopAutoplay(); 77 autoTimer = setInterval(function() { 78 if (!isPaused) { 79 goTo(current + 1); 80 } 81 }, autoplayMs); 82 } 83 84 function stopAutoplay() { 85 if (autoTimer) { 86 clearInterval(autoTimer); 87 autoTimer = null; 88 } 89 } 90 91 function resetAutoplay() { 92 stopAutoplay(); 93 startAutoplay(); 94 } 95 96 carousel.addEventListener('mouseenter', function() { isPaused = true; }); 97 carousel.addEventListener('mouseleave', function() { isPaused = false; }); 98 carousel.addEventListener('touchstart', function() { isPaused = true; }, {passive: true}); 99 carousel.addEventListener('touchend', function() { 100 isPaused = false; 101 resetAutoplay(); 102 }, {passive: true}); 103 104 // ââ Detectar scroll manual y actualizar dots ââ 105 var scrollTimeout; 106 track.addEventListener('scroll', function() { 107 clearTimeout(scrollTimeout); 108 scrollTimeout = setTimeout(function() { 109 var trackLeft = track.scrollLeft; 110 var closest = 0; 111 var closestDist = Infinity; 112 cards.forEach(function(card, i) { 113 var dist = Math.abs(card.offsetLeft - track.offsetLeft - trackLeft); 114 if (dist < closestDist) { 115 closestDist = dist; 116 closest = i; 117 } 118 }); 119 if (closest !== current) { 120 current = closest; 121 updateDots(); 122 } 123 }, 80); 124 }); 125 126 // ââ Drag/swipe ââ 127 var isDown = false, startX, scrollLeft; 128 129 track.addEventListener('mousedown', function(e) { 130 isDown = true; 131 track.classList.add('wti-gp-grabbing'); 132 startX = e.pageX - track.offsetLeft; 133 scrollLeft = track.scrollLeft; 134 }); 135 136 track.addEventListener('mouseleave', function() { 137 isDown = false;
138 track.classList.remove('wti-gp-grabbing'); 139 }); 140 141 track.addEventListener('mouseup', function() { 142 isDown = false; 143 track.classList.remove('wti-gp-grabbing'); 144 resetAutoplay(); 145 }); 146 147 track.addEventListener('mousemove', function(e) { 148 if (!isDown) return; 149 e.preventDefault(); 150 var x = e.pageX - track.offsetLeft; 151 var walk = (x - startX) * 1.5; 152 track.scrollLeft = scrollLeft - walk; 153 }); 154 155 updateDots(); 156 startAutoplay(); 157 }); 158 } 159 160 // âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 161 // MODAL MULTI-PASO: Escribir reseña (3 slides) 162 // âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 163 164 function initReviewModal() { 165 var modal = document.getElementById('wti-gp-review-modal'); 166 if (!modal) return; 167 168 var backdrop = modal.querySelector('.wti-gp-modal__backdrop'); 169 var closeBtn = modal.querySelector('.wti-gp-modal__close'); 170 var steps = modal.querySelectorAll('.wti-gp-modal__step'); 171 var progressBar = document.getElementById('wti-gp-progress-bar'); 172 var titleText = document.getElementById('wti-gp-modal-title-text'); 173 var stars = modal.querySelectorAll('.wti-gp-modal__star'); 174 var ratingLabel = document.getElementById('wti-gp-rating-label'); 175 176 // Botones navegacion 177 var step1Next = document.getElementById('wti-gp-step1-next'); 178 var step2Back = document.getElementById('wti-gp-step2-back'); 179 var submitBtn = document.getElementById('wti-gp-submit-review'); 180 var closeThanks = document.getElementById('wti-gp-close-thanks'); 181 182 // Campos paso 2 183 var nameInput = document.getElementById('wti-gp-review-name'); 184 var textArea = document.getElementById('wti-gp-review-text'); 185 var charCount = document.getElementById('wti-gp-char-count'); 186 var selectedStarsDiv = document.getElementById('wti-gp-selected-stars'); 187 188 // Estado 189 var currentStep = 1; 190 var selectedRating = 0; 191 var currentPostId = 0; 192 193 var ratingLabels = ['', 'Malo', 'Regular', 'Bien', 'Muy bien', 'Excelente']; 194 var stepTitles = ['', 'Tu opinion importa', 'Cuentanos mas', 'Gracias']; 195 196 // ââ Abrir modal ââ 197 document.addEventListener('click', function(e) { 198 var btn = e.target.closest('.wti-gp-reviews__write-btn, .wti-gp-summary__opinar'); 199 if (btn) { 200 e.preventDefault(); 201 // Obtener post_id del boton o del wrapper mas cercano 202 currentPostId = parseInt(btn.getAttribute('data-post-id')) || 0; 203 if (!currentPostId) { 204 var wrapper = btn.closest('.wti-gp-reviews, [data-post-id]'); 205 if (wrapper) { 206 currentPostId = parseInt(wrapper.getAttribute('data-post-id')) || 0; 207 } 208 } 209 openModal(); 210 } 211 }); 212 213 // ââ Cerrar modal ââ 214 if (backdrop) backdrop.addEventListener('click', closeModal); 215 if (closeBtn) closeBtn.addEventListener('click', closeModal); 216 if (closeThanks) closeThanks.addEventListener('click', closeModal); 217 218 // Cancelar en paso 1 219 var cancelBtns = modal.querySelectorAll('.wti-gp-modal__cancel'); 220 cancelBtns.forEach(function(btn) { 221 btn.addEventListener('click', closeModal); 222 }); 223 224 // Escape key 225 document.addEventListener('keydown', function(e) { 226 if (e.key === 'Escape' && modal.classList.contains('wti-gp-modal--open')) { 227 closeModal(); 228 } 229 }); 230 231 // ââ Paso 1: Estrellas ââ
232 stars.forEach(function(star, index) { 233 star.addEventListener('mouseenter', function() { 234 highlightStars(index + 1); 235 if (ratingLabel) ratingLabel.textContent = ratingLabels[index + 1] || ''; 236 }); 237 238 star.addEventListener('click', function() { 239 selectedRating = index + 1; 240 var sc = modal.querySelector('.wti-gp-modal__star-select'); 241 if (sc) sc.dataset.selected = selectedRating; 242 highlightStars(selectedRating); 243 if (ratingLabel) { 244 ratingLabel.textContent = ratingLabels[selectedRating] || ''; 245 ratingLabel.classList.add('wti-gp-modal__rating-label--active'); 246 } 247 // Habilitar boton siguiente 248 if (step1Next) step1Next.disabled = false; 249 }); 250 }); 251 252 var starContainer = modal.querySelector('.wti-gp-modal__star-select'); 253 if (starContainer) { 254 starContainer.addEventListener('mouseleave', function() { 255 var selected = parseInt(starContainer.dataset.selected) || 0; 256 highlightStars(selected); 257 if (ratingLabel) { 258 ratingLabel.textContent = selected ? ratingLabels[selected] : ''; 259 } 260 }); 261 } 262 263 // ââ Navegacion entre pasos ââ 264 if (step1Next) { 265 step1Next.addEventListener('click', function() { 266 if (selectedRating < 1) return; 267 goToStep(2); 268 // Mostrar estrellas seleccionadas en paso 2 269 if (selectedStarsDiv) { 270 var starsHtml = ''; 271 for (var i = 1; i <= 5; i++) { 272 var fill = i <= selectedRating ? '#F57C00' : '#E0E0E0'; 273 starsHtml += '<svg viewBox="0 0 24 24" width="20" height="20"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" fill="' + fill + '"/></svg>'; 274 } 275 starsHtml += '<span class="wti-gp-modal__rating-text">' + ratingLabels[selectedRating] + '</span>'; 276 selectedStarsDiv.innerHTML = starsHtml; 277 } 278 // Focus en nombre 279 if (nameInput) { 280 setTimeout(function() { nameInput.focus(); }, 300); 281 } 282 }); 283 } 284 285 if (step2Back) { 286 step2Back.addEventListener('click', function() { 287 goToStep(1); 288 }); 289 } 290 291 // ââ Contador de caracteres ââ 292 if (textArea && charCount) { 293 textArea.addEventListener('input', function() { 294 charCount.textContent = textArea.value.length; 295 }); 296 } 297 298 // ââ Enviar reseña (AJAX) ââ 299 if (submitBtn) { 300 submitBtn.addEventListener('click', function(e) { 301 e.preventDefault(); 302 if (selectedRating < 1 || !currentPostId) return; 303 304 var name = nameInput ? nameInput.value.trim() : ''; 305 var text = textArea ? textArea.value.trim() : ''; 306 307 // Deshabilitar boton 308 submitBtn.disabled = true; 309 submitBtn.innerHTML = '<span class="wti-gp-modal__spinner"></span> Enviando...'; 310 311 // AJAX POST 312 var formData = new FormData(); 313 formData.append('action', 'wti_gp_save_review'); 314 formData.append('nonce', wtiGPFront.nonce); 315 formData.append('post_id', currentPostId); 316 formData.append('rating', selectedRating); 317 formData.append('name', name); 318 formData.append('text', text); 319 320 fetch(wtiGPFront.ajaxUrl, { 321 method: 'POST', 322 body: formData, 323 credentials: 'same-origin' 324 }) 325 .then(function(response) { return response.json(); }) 326 .then(function(result) { 327 submitBtn.disabled = false;
328 submitBtn.innerHTML = 'Publicar <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg>'; 329 330 if (result.success) { 331 goToStep(3); 332 } else { 333 // Mostrar error inline 334 var errorDiv = modal.querySelector('.wti-gp-modal__error'); 335 if (!errorDiv) { 336 errorDiv = document.createElement('div'); 337 errorDiv.className = 'wti-gp-modal__error'; 338 submitBtn.parentNode.insertBefore(errorDiv, submitBtn); 339 } 340 errorDiv.textContent = result.data || 'Error al enviar. Intentalo de nuevo.'; 341 errorDiv.style.display = 'block'; 342 setTimeout(function() { errorDiv.style.display = 'none'; }, 4000); 343 } 344 }) 345 .catch(function() { 346 submitBtn.disabled = false; 347 submitBtn.innerHTML = 'Publicar <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg>'; 348 alert('Error de conexion. Comprueba tu internet e intentalo de nuevo.'); 349 }); 350 }); 351 } 352 353 // ââ Funciones helpers ââ 354 355 function openModal() { 356 // Reset todo 357 selectedRating = 0; 358 currentStep = 1; 359 var sc = modal.querySelector('.wti-gp-modal__star-select'); 360 if (sc) sc.dataset.selected = 0; 361 highlightStars(0); 362 if (step1Next) step1Next.disabled = true; 363 if (nameInput) nameInput.value = ''; 364 if (textArea) textArea.value = ''; 365 if (charCount) charCount.textContent = '0'; 366 if (ratingLabel) { 367 ratingLabel.textContent = ''; 368 ratingLabel.classList.remove('wti-gp-modal__rating-label--active'); 369 } 370 371 // Ocultar errores 372 var errors = modal.querySelectorAll('.wti-gp-modal__error'); 373 errors.forEach(function(el) { el.style.display = 'none'; }); 374 375 // Mostrar paso 1 376 showStep(1); 377 updateProgress(1); 378 379 modal.classList.add('wti-gp-modal--open'); 380 document.body.style.overflow = 'hidden'; 381 } 382 383 function closeModal() { 384 modal.classList.remove('wti-gp-modal--open'); 385 document.body.style.overflow = ''; 386 387 // Si estabamos en paso 3 (gracias), recargar la pagina para mostrar la nueva resena 388 if (currentStep === 3) { 389 window.location.reload(); 390 } 391 } 392 393 function goToStep(step) { 394 var currentEl = modal.querySelector('.wti-gp-modal__step--active'); 395 var nextEl = modal.querySelector('.wti-gp-modal__step[data-step="' + step + '"]'); 396 if (!currentEl || !nextEl) return; 397 398 var direction = step > currentStep ? 'left' : 'right'; 399 400 // Animar salida 401 currentEl.classList.add('wti-gp-modal__step--exit-' + direction); 402 403 setTimeout(function() { 404 currentEl.classList.remove('wti-gp-modal__step--active', 'wti-gp-modal__step--exit-left', 'wti-gp-modal__step--exit-right'); 405 406 // Animar entrada 407 nextEl.classList.add('wti-gp-modal__step--enter-' + direction); 408 409 // Forzar reflow 410 void nextEl.offsetWidth; 411 412 nextEl.classList.add('wti-gp-modal__step--active'); 413 nextEl.classList.remove('wti-gp-modal__step--enter-left', 'wti-gp-modal__step--enter-right'); 414 415 currentStep = step; 416 updateProgress(step); 417 418 // Actualizar titulo 419 if (titleText) titleText.textContent = stepTitles[step] || ''; 420 }, 200); 421 } 422 423 function showStep(step) { 424 steps.forEach(function(s) { 425 s.classList.remove('wti-gp-modal__step--active', 'wti-gp-modal__step--exit-left', 'wti-gp-modal__step--exit-right', 'wti-gp-modal__step--enter-left', 'wti-gp-modal__step--enter-right'); 426 }); 427 var target = modal.querySelector('.wti-gp-modal__step[data-step="' + step + '"]'); 428 if (target) target.classList.add('wti-gp-modal__step--active'); 429 if (titleText) titleText.textContent = stepTitles[step] || ''; 430 } 431 432 function updateProgress(step) { 433 if (progressBar) { 434 var pct = (step / 3) * 100; 435 progressBar.style.width = pct + '%'; 436 } 437 } 438 439 function highlightStars(count) {
440 stars.forEach(function(s, i) { 441 var path = s.querySelector('path'); 442 if (path) { 443 path.setAttribute('fill', i < count ? '#F57C00' : '#E0E0E0'); 444 } 445 }); 446 } 447 } 448 449})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.