1import { Controller } from "@hotwired/stimulus" 2 3// Connects to data-controller="numiko--turnstile" 4// This controller must be attached to a form element. 5export default class extends Controller { 6 static values = { 7 siteKey: String 8 } 9 10 connect () { 11 this.widgetId = null; 12 this.#disableSubmit(); 13 this.#addScript(); 14 } 15 16 disconnect () { 17 if (this.widgetId !== null && typeof turnstile !== 'undefined') { 18 turnstile.remove(this.widgetId); 19 this.widgetId = null; 20 } 21 } 22 23 challengeCallback (token) { 24 // Remove any existing token input 25 const existingInput = this.element.querySelector('#turnstile_token'); 26 if (existingInput) { 27 existingInput.remove(); 28 } 29 30 // Add new token input 31 const input = document.createElement('input'); 32 input.type = 'hidden'; 33 input.name = 'turnstile_token'; 34 input.id = 'turnstile_token'; 35 input.value = token; 36 this.element.appendChild(input); 37 38 // Enable the submit button 39 this.#enableSubmit(); 40 } 41 42 #disableSubmit () { 43 const submitButton = this.element.querySelector('[type="submit"]'); 44 if (submitButton) { 45 submitButton.disabled = true; 46 } 47 } 48 49 #enableSubmit () { 50 const submitButton = this.element.querySelector('[type="submit"]'); 51 if (submitButton) { 52 submitButton.disabled = false; 53 } 54 } 55 56 #addScript () { 57 const container = this.element.querySelector('.turnstile-container'); 58 if (!container) { 59 console.error('Turnstile container not found'); 60 return; 61 } 62 63 const renderWidget = () => { 64 if (typeof turnstile !== 'undefined') { 65 this.widgetId = turnstile.render(container, { 66 sitekey: this.siteKeyValue, 67 theme: 'light', 68 callback: (token) => this.challengeCallback(token) 69 }); 70 } 71 }; 72 73 if (typeof turnstile !== 'undefined') { 74 renderWidget(); 75 } else if (!document.getElementById('turnstile-script')) { 76 let turnstileScript = document.createElement('script'); 77 turnstileScript.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback'; 78 turnstileScript.async = true; 79 turnstileScript.defer = true; 80 turnstileScript.id = 'turnstile-script'; 81 82 window.onloadTurnstileCallback = () => { 83 document.querySelectorAll('[data-controller*="numiko--turnstile"]').forEach(el => { 84 const event = new CustomEvent('turnstile:loaded'); 85 el.dispatchEvent(event); 86 }); 87 }; 88 89 this.element.addEventListener('turnstile:loaded', () => renderWidget(), { once: true }); 90 document.head.appendChild(turnstileScript); 91 } 92 } 93}
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.