1import { Controller } from '@hotwired/stimulus'; 2 3export default class extends Controller { 4 static values = { 5 url: String, 6 title: String, 7 copiedLabel: String 8 }; 9 10 connect() { 11 this.element 12 .querySelectorAll('[data-event="single_review_share"]:not([data-action])') 13 .forEach((el) => { 14 el.addEventListener('click', () => this.trackShare(el.dataset.network || '')); 15 }); 16 } 17 18 trackShare(network) { 19 if (window.dataLayer && typeof window.dataLayer.push === 'function') { 20 window.dataLayer.push({ 21 event: 'single_review_share', 22 network, 23 url: this.urlValue 24 }); 25 } 26 } 27 28 async systemShare(event) { 29 event.preventDefault(); 30 if (typeof navigator.share !== 'function') { 31 return; 32 } 33 try { 34 await navigator.share({ title: this.titleValue, url: this.urlValue }); 35 this.trackShare('system'); 36 } catch (e) { 37 38 // User dismissed / aborted; nothing to do. 39 } 40 } 41 42 async copy(event) { 43 event.preventDefault(); 44 const ok = await this.copyUrl(); 45 if (ok) { 46 this.trackShare('copy'); 47 this.flashCopied(event.currentTarget); 48 } 49 } 50 51 // Copies this.urlValue to clipboard. Returns true on success. 52 // Extracted so `shareInstagram` can reuse it without re-handling the event. 53 async copyUrl() { 54 const url = this.urlValue; 55 try { 56 if (navigator.clipboard && window.isSecureContext) { 57 await navigator.clipboard.writeText(url); 58 return true; 59 } 60 if (window.Util && typeof window.Util.copyToClipboard === 'function') { 61 window.Util.copyToClipboard(url); 62 return true; 63 } 64 } catch (e) { 65 66 // fall through 67 } 68 return false; 69 } 70 71 flashCopied(btn) { 72 if (!btn) { 73 return; 74 } 75 const originalLabel = btn.getAttribute('aria-label'); 76 const originalTitle = btn.getAttribute('title'); 77 const label = this.copiedLabelValue || 'Copied'; 78 btn.setAttribute('aria-label', label); 79 btn.setAttribute('title', label); 80 btn.classList.add('sr-copied'); 81 82 // Visible confirmation: floating bubble above the button (matches the 83 // inline-script path in single_review_share.html.twig). Restart cleanly 84 // on rapid re-clicks by replacing any existing bubble. 85 const existing = btn.querySelector('.sr-copied-toast'); 86 if (existing) { 87 existing.remove(); 88 } 89 const toast = document.createElement('span'); 90 toast.className = 'sr-copied-toast'; 91 toast.setAttribute('aria-hidden', 'true'); 92 toast.textContent = label; 93 btn.appendChild(toast); 94 95 window.setTimeout(() => { 96 if (originalLabel !== null) { 97 btn.setAttribute('aria-label', originalLabel); 98 } 99 if (originalTitle !== null) { 100 btn.setAttribute('title', originalTitle); 101 } 102 btn.classList.remove('sr-copied'); 103 if (toast.parentNode === btn) { 104 btn.removeChild(toast); 105 } 106 }, 1500); 107 } 108}
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.