Move plugins to manifest, pin Docker version, add Makefile
- Add plugins.txt listing all plugins for reproducible installs - Add Makefile with setup/start/stop/install-plugins targets - Remove user/plugins/ from git tracking - Pin Docker image to 1.7.49.5-ls244 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,128 +0,0 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Function to refresh a captcha image
|
||||
const refreshCaptchaImage = function(container) {
|
||||
const img = container.querySelector('img');
|
||||
if (!img) {
|
||||
console.warn('Cannot find captcha image in container');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the base URL and field ID
|
||||
const baseUrl = img.dataset.baseUrl || img.src.split('?')[0];
|
||||
const fieldId = img.dataset.fieldId || container.dataset.fieldId;
|
||||
|
||||
// Force reload by adding/updating timestamp and field ID
|
||||
const timestamp = new Date().getTime();
|
||||
let newUrl = baseUrl + '?t=' + timestamp;
|
||||
if (fieldId) {
|
||||
newUrl += '&field=' + fieldId;
|
||||
}
|
||||
img.src = newUrl;
|
||||
|
||||
// Also clear the input field if we can find it
|
||||
const formField = container.closest('.form-field');
|
||||
if (formField) {
|
||||
const input = formField.querySelector('input[type="text"]');
|
||||
if (input) {
|
||||
input.value = '';
|
||||
// Try to focus the input
|
||||
try { input.focus(); } catch(e) {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function to set up click handlers for refresh buttons
|
||||
const setupRefreshButtons = function() {
|
||||
// Find all captcha containers
|
||||
const containers = document.querySelectorAll('[data-captcha-provider="basic-captcha"]');
|
||||
|
||||
containers.forEach(function(container) {
|
||||
// Find the refresh button within this container
|
||||
const button = container.querySelector('button');
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any existing listeners (just in case)
|
||||
button.removeEventListener('click', handleRefreshClick);
|
||||
|
||||
// Add the click handler
|
||||
button.addEventListener('click', handleRefreshClick);
|
||||
});
|
||||
};
|
||||
|
||||
// Click handler function
|
||||
const handleRefreshClick = function(event) {
|
||||
// Prevent default behavior and stop propagation
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Find the container
|
||||
const container = this.closest('[data-captcha-provider="basic-captcha"]');
|
||||
if (!container) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Refresh the image
|
||||
refreshCaptchaImage(container);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Set up a mutation observer to handle dynamically added captchas
|
||||
const setupMutationObserver = function() {
|
||||
// Check if MutationObserver is available
|
||||
if (typeof MutationObserver === 'undefined') return;
|
||||
|
||||
// Create a mutation observer to watch for new captcha elements
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
let needsSetup = false;
|
||||
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.type === 'childList' && mutation.addedNodes.length) {
|
||||
// Check if any of the added nodes contain our captcha containers
|
||||
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
||||
const node = mutation.addedNodes[i];
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
// Check if this element has or contains captcha containers
|
||||
if (node.querySelector && (
|
||||
node.matches('[data-captcha-provider="basic-captcha"]') ||
|
||||
node.querySelector('[data-captcha-provider="basic-captcha"]')
|
||||
)) {
|
||||
needsSetup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (needsSetup) {
|
||||
setupRefreshButtons();
|
||||
}
|
||||
});
|
||||
|
||||
// Start observing the document
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
};
|
||||
|
||||
// Initialize on DOM ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setupRefreshButtons();
|
||||
setupMutationObserver();
|
||||
|
||||
// Also connect to XHR system if available (for best of both worlds)
|
||||
if (window.GravFormXHR && window.GravFormXHR.captcha) {
|
||||
window.GravFormXHR.captcha.register('basic-captcha', {
|
||||
reset: function(container, form) {
|
||||
refreshCaptchaImage(container);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -1,166 +0,0 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Register the handler with the form system when it's ready
|
||||
const registerRecaptchaHandler = function() {
|
||||
if (window.GravFormXHR && window.GravFormXHR.captcha) {
|
||||
window.GravFormXHR.captcha.register('recaptcha', {
|
||||
reset: function(container, form) {
|
||||
if (!form || !form.id) {
|
||||
console.warn('Cannot reset reCAPTCHA: form is invalid or missing ID');
|
||||
return;
|
||||
}
|
||||
|
||||
const formId = form.id;
|
||||
console.log(`Attempting to reset reCAPTCHA for form: ${formId}`);
|
||||
|
||||
// First try the expected ID pattern from the Twig template
|
||||
const recaptchaId = `g-recaptcha-${formId}`;
|
||||
// We need to look more flexibly for the container
|
||||
let widgetContainer = document.getElementById(recaptchaId);
|
||||
|
||||
// If not found by ID, look for the div inside the captcha provider container
|
||||
if (!widgetContainer) {
|
||||
// Try to find it inside the captcha provider container
|
||||
widgetContainer = container.querySelector('.g-recaptcha');
|
||||
|
||||
if (!widgetContainer) {
|
||||
// If that fails, look more broadly in the form
|
||||
widgetContainer = form.querySelector('.g-recaptcha');
|
||||
|
||||
if (!widgetContainer) {
|
||||
// Last resort - create a new container if needed
|
||||
console.warn(`reCAPTCHA container #${recaptchaId} not found. Creating a new one.`);
|
||||
widgetContainer = document.createElement('div');
|
||||
widgetContainer.id = recaptchaId;
|
||||
widgetContainer.className = 'g-recaptcha';
|
||||
container.appendChild(widgetContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Found reCAPTCHA container for form: ${formId}`);
|
||||
|
||||
// Get configuration from data attributes
|
||||
const parentContainer = container.closest('[data-captcha-provider="recaptcha"]');
|
||||
if (!parentContainer) {
|
||||
console.warn('Cannot find reCAPTCHA parent container with data-captcha-provider attribute.');
|
||||
return;
|
||||
}
|
||||
|
||||
const sitekey = parentContainer.dataset.sitekey;
|
||||
const version = parentContainer.dataset.version || '2-checkbox';
|
||||
const isV3 = version.startsWith('3');
|
||||
const isInvisible = version === '2-invisible';
|
||||
|
||||
if (!sitekey) {
|
||||
console.warn('Cannot reinitialize reCAPTCHA - missing sitekey attribute');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Re-rendering reCAPTCHA widget for form: ${formId}, version: ${version}`);
|
||||
|
||||
// Handle V3 reCAPTCHA differently
|
||||
if (isV3) {
|
||||
try {
|
||||
// For v3, we don't need to reset anything visible, just make sure we have the API
|
||||
if (typeof grecaptcha !== 'undefined' && typeof grecaptcha.execute === 'function') {
|
||||
// Create a new execution context for the form
|
||||
const actionName = `form_${formId}`;
|
||||
const tokenInput = form.querySelector('input[name="token"]') ||
|
||||
form.querySelector('input[name="data[token]"]');
|
||||
const actionInput = form.querySelector('input[name="action"]') ||
|
||||
form.querySelector('input[name="data[action]"]');
|
||||
|
||||
if (tokenInput && actionInput) {
|
||||
// Clear previous token
|
||||
tokenInput.value = '';
|
||||
|
||||
// Set the action name
|
||||
actionInput.value = actionName;
|
||||
|
||||
console.log(`reCAPTCHA v3 ready for execution on form: ${formId}`);
|
||||
} else {
|
||||
console.warn(`Cannot find token or action inputs for reCAPTCHA v3 in form: ${formId}`);
|
||||
}
|
||||
} else {
|
||||
console.warn('reCAPTCHA v3 API not properly loaded.');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error setting up reCAPTCHA v3: ${e.message}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For v2, handle visible widget reset
|
||||
// Clear the container to ensure fresh rendering
|
||||
widgetContainer.innerHTML = '';
|
||||
|
||||
// Check if reCAPTCHA API is available
|
||||
if (typeof grecaptcha !== 'undefined' && typeof grecaptcha.render === 'function') {
|
||||
try {
|
||||
// Render with a slight delay to ensure DOM is settled
|
||||
setTimeout(() => {
|
||||
grecaptcha.render(widgetContainer.id || widgetContainer, {
|
||||
'sitekey': sitekey,
|
||||
'theme': parentContainer.dataset.theme || 'light',
|
||||
'size': isInvisible ? 'invisible' : 'normal',
|
||||
'callback': function(token) {
|
||||
console.log(`reCAPTCHA verification completed for form: ${formId}`);
|
||||
|
||||
// If it's invisible reCAPTCHA, submit the form automatically
|
||||
if (isInvisible && window.GravFormXHR && typeof window.GravFormXHR.submit === 'function') {
|
||||
window.GravFormXHR.submit(form);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log(`Successfully rendered reCAPTCHA for form: ${formId}`);
|
||||
}, 100);
|
||||
} catch (e) {
|
||||
console.error(`Error rendering reCAPTCHA widget: ${e.message}`);
|
||||
widgetContainer.innerHTML = '<p style="color:red;">Error initializing reCAPTCHA.</p>';
|
||||
}
|
||||
} else {
|
||||
console.warn('reCAPTCHA API not available. Attempting to reload...');
|
||||
|
||||
// Remove existing script if any
|
||||
const existingScript = document.querySelector('script[src*="google.com/recaptcha/api.js"]');
|
||||
if (existingScript) {
|
||||
existingScript.parentNode.removeChild(existingScript);
|
||||
}
|
||||
|
||||
// Create new script element
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://www.google.com/recaptcha/api.js${isV3 ? '?render=' + sitekey : ''}`;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.onload = function() {
|
||||
console.log('reCAPTCHA API loaded, retrying widget render...');
|
||||
setTimeout(() => {
|
||||
const retryContainer = document.querySelector(`[data-captcha-provider="recaptcha"]`);
|
||||
if (retryContainer && form) {
|
||||
window.GravFormXHR.captcha.getProvider('recaptcha').reset(retryContainer, form);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log('reCAPTCHA XHR handler registered successfully');
|
||||
} else {
|
||||
console.error('GravFormXHR.captcha not found. Make sure the Form plugin is loaded correctly.');
|
||||
}
|
||||
};
|
||||
|
||||
// Try to register the handler immediately if GravFormXHR is already available
|
||||
if (window.GravFormXHR && window.GravFormXHR.captcha) {
|
||||
registerRecaptchaHandler();
|
||||
} else {
|
||||
// Otherwise, wait for the DOM to be fully loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Give a small delay to ensure GravFormXHR is initialized
|
||||
setTimeout(registerRecaptchaHandler, 100);
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -1,121 +0,0 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Register the handler with the form system when it's ready
|
||||
const registerTurnstileHandler = function() {
|
||||
if (window.GravFormXHR && window.GravFormXHR.captcha) {
|
||||
window.GravFormXHR.captcha.register('turnstile', {
|
||||
reset: function(container, form) {
|
||||
const formId = form.id;
|
||||
const containerId = `cf-turnstile-${formId}`;
|
||||
const widgetContainer = document.getElementById(containerId);
|
||||
|
||||
if (!widgetContainer) {
|
||||
console.warn(`Turnstile container #${containerId} not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get configuration from data attributes
|
||||
const parentContainer = container.closest('[data-captcha-provider="turnstile"]');
|
||||
const sitekey = parentContainer ? parentContainer.dataset.sitekey : null;
|
||||
|
||||
if (!sitekey) {
|
||||
console.warn('Cannot reinitialize Turnstile - missing sitekey attribute');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the container to ensure fresh rendering
|
||||
widgetContainer.innerHTML = '';
|
||||
|
||||
console.log(`Re-rendering Turnstile widget for form: ${formId}`);
|
||||
|
||||
// Check if Turnstile API is available
|
||||
if (typeof window.turnstile !== 'undefined') {
|
||||
try {
|
||||
// Reset any existing widgets
|
||||
try {
|
||||
window.turnstile.reset(containerId);
|
||||
} catch (e) {
|
||||
// Ignore reset errors, we'll re-render anyway
|
||||
}
|
||||
|
||||
// Render with a slight delay to ensure DOM is settled
|
||||
setTimeout(() => {
|
||||
window.turnstile.render(`#${containerId}`, {
|
||||
sitekey: sitekey,
|
||||
theme: parentContainer ? (parentContainer.dataset.theme || 'light') : 'light',
|
||||
callback: function(token) {
|
||||
console.log(`Turnstile verification completed for form: ${formId} with token:`, token.substring(0, 10) + '...');
|
||||
|
||||
// Create or update hidden input for token
|
||||
let tokenInput = form.querySelector('input[name="cf-turnstile-response"]');
|
||||
if (!tokenInput) {
|
||||
console.log('Creating new hidden input for Turnstile token');
|
||||
tokenInput = document.createElement('input');
|
||||
tokenInput.type = 'hidden';
|
||||
tokenInput.name = 'cf-turnstile-response';
|
||||
form.appendChild(tokenInput);
|
||||
} else {
|
||||
console.log('Updating existing hidden input for Turnstile token');
|
||||
}
|
||||
tokenInput.value = token;
|
||||
|
||||
// Also add a debug attribute
|
||||
form.setAttribute('data-turnstile-verified', 'true');
|
||||
},
|
||||
'expired-callback': function() {
|
||||
console.log(`Turnstile token expired for form: ${formId}`);
|
||||
},
|
||||
'error-callback': function(error) {
|
||||
console.error(`Turnstile error for form ${formId}: ${error}`);
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
} catch (e) {
|
||||
console.error(`Error rendering Turnstile widget: ${e.message}`);
|
||||
widgetContainer.innerHTML = '<p style="color:red;">Error initializing Turnstile.</p>';
|
||||
}
|
||||
} else {
|
||||
console.warn('Turnstile API not available. Attempting to reload...');
|
||||
|
||||
// Remove existing script if any
|
||||
const existingScript = document.querySelector('script[src*="challenges.cloudflare.com/turnstile/v0/api.js"]');
|
||||
if (existingScript) {
|
||||
existingScript.parentNode.removeChild(existingScript);
|
||||
}
|
||||
|
||||
// Create new script element
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.onload = function() {
|
||||
console.log('Turnstile API loaded, retrying widget render...');
|
||||
setTimeout(() => {
|
||||
const retryContainer = document.querySelector('[data-captcha-provider="turnstile"]');
|
||||
if (retryContainer && form) {
|
||||
window.GravFormXHR.captcha.getProvider('turnstile').reset(retryContainer, form);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log('Turnstile XHR handler registered successfully');
|
||||
} else {
|
||||
console.error('GravFormXHR.captcha not found. Make sure the Form plugin is loaded correctly.');
|
||||
}
|
||||
};
|
||||
|
||||
// Try to register the handler immediately if GravFormXHR is already available
|
||||
if (window.GravFormXHR && window.GravFormXHR.captcha) {
|
||||
registerTurnstileHandler();
|
||||
} else {
|
||||
// Otherwise, wait for the DOM to be fully loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Give a small delay to ensure GravFormXHR is initialized
|
||||
setTimeout(registerTurnstileHandler, 100);
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -1,311 +0,0 @@
|
||||
/**
|
||||
* Direct Dropzone Initialization for XHR Forms
|
||||
*
|
||||
* This script directly targets Form plugin's Dropzone initialization mechanisms
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Enable debugging logs
|
||||
const DEBUG = false;
|
||||
|
||||
// Helper function for logging
|
||||
function log(message, type = 'log') {
|
||||
if (!DEBUG) return;
|
||||
|
||||
const prefix = '[Dropzone Direct Init]';
|
||||
|
||||
if (type === 'error') {
|
||||
console.error(prefix, message);
|
||||
} else if (type === 'warn') {
|
||||
console.warn(prefix, message);
|
||||
} else {
|
||||
console.log(prefix, message);
|
||||
}
|
||||
}
|
||||
|
||||
// Flag to prevent multiple initializations
|
||||
let isInitializing = false;
|
||||
|
||||
// Function to directly initialize Dropzone
|
||||
function initializeDropzone(element) {
|
||||
if (isInitializing) {
|
||||
log('Initialization already in progress, skipping');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!element || element.classList.contains('dz-clickable')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log('Starting direct Dropzone initialization for element:', element);
|
||||
isInitializing = true;
|
||||
|
||||
// First, let's try to find the FilesField constructor in the global scope
|
||||
if (typeof FilesField === 'function') {
|
||||
log('Found FilesField constructor, trying direct instantiation');
|
||||
|
||||
try {
|
||||
new FilesField({
|
||||
container: element,
|
||||
options: {}
|
||||
});
|
||||
|
||||
log('Successfully initialized Dropzone using FilesField constructor');
|
||||
isInitializing = false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(`Error using FilesField constructor: ${e.message}`, 'error');
|
||||
// Continue with other methods
|
||||
}
|
||||
}
|
||||
|
||||
// Second approach: Look for the Form plugin's initialization code in the page
|
||||
const dropzoneInit = findFunctionOnWindow('addNode') ||
|
||||
window.addNode ||
|
||||
findFunctionOnWindow('initDropzone');
|
||||
|
||||
if (dropzoneInit) {
|
||||
log('Found Form plugin initialization function, calling it directly');
|
||||
|
||||
try {
|
||||
dropzoneInit(element);
|
||||
log('Successfully called Form plugin initialization function');
|
||||
isInitializing = false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(`Error calling Form plugin initialization function: ${e.message}`, 'error');
|
||||
// Continue with other methods
|
||||
}
|
||||
}
|
||||
|
||||
// Third approach: Try to invoke Dropzone directly if it's globally available
|
||||
if (typeof Dropzone === 'function') {
|
||||
log('Found global Dropzone constructor, trying direct instantiation');
|
||||
|
||||
try {
|
||||
// Extract settings from the element
|
||||
const settingsAttr = element.getAttribute('data-grav-file-settings');
|
||||
if (!settingsAttr) {
|
||||
log('No settings found for element', 'warn');
|
||||
isInitializing = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const settings = JSON.parse(settingsAttr);
|
||||
const optionsAttr = element.getAttribute('data-dropzone-options');
|
||||
const options = optionsAttr ? JSON.parse(optionsAttr) : {};
|
||||
|
||||
// Configure Dropzone options
|
||||
const dropzoneOptions = {
|
||||
url: element.getAttribute('data-file-url-add') || window.location.href,
|
||||
maxFiles: settings.limit || null,
|
||||
maxFilesize: settings.filesize || 10,
|
||||
acceptedFiles: settings.accept ? settings.accept.join(',') : null
|
||||
};
|
||||
|
||||
// Merge with any provided options
|
||||
Object.assign(dropzoneOptions, options);
|
||||
|
||||
// Create new Dropzone instance
|
||||
new Dropzone(element, dropzoneOptions);
|
||||
|
||||
log('Successfully initialized Dropzone using global constructor');
|
||||
isInitializing = false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(`Error using global Dropzone constructor: ${e.message}`, 'error');
|
||||
// Continue to final approach
|
||||
}
|
||||
}
|
||||
|
||||
// Final approach: Force reloading of Form plugin's JavaScript
|
||||
log('Attempting to force reload Form plugin JavaScript');
|
||||
|
||||
// Look for Form plugin's JS files
|
||||
const formVendorScript = document.querySelector('script[src*="form.vendor.js"]');
|
||||
const formScript = document.querySelector('script[src*="form.min.js"]');
|
||||
|
||||
if (formVendorScript || formScript) {
|
||||
log('Found Form plugin scripts, attempting to reload them');
|
||||
|
||||
// Create new script elements
|
||||
if (formVendorScript) {
|
||||
const newVendorScript = document.createElement('script');
|
||||
newVendorScript.src = formVendorScript.src.split('?')[0] + '?t=' + new Date().getTime();
|
||||
newVendorScript.async = true;
|
||||
newVendorScript.onload = function() {
|
||||
log('Reloaded Form vendor script');
|
||||
|
||||
// Trigger event after script loads
|
||||
setTimeout(function() {
|
||||
const event = new CustomEvent('mutation._grav', {
|
||||
detail: { target: element }
|
||||
});
|
||||
document.body.dispatchEvent(event);
|
||||
}, 100);
|
||||
};
|
||||
document.head.appendChild(newVendorScript);
|
||||
}
|
||||
|
||||
if (formScript) {
|
||||
const newFormScript = document.createElement('script');
|
||||
newFormScript.src = formScript.src.split('?')[0] + '?t=' + new Date().getTime();
|
||||
newFormScript.async = true;
|
||||
newFormScript.onload = function() {
|
||||
log('Reloaded Form script');
|
||||
|
||||
// Trigger event after script loads
|
||||
setTimeout(function() {
|
||||
const event = new CustomEvent('mutation._grav', {
|
||||
detail: { target: element }
|
||||
});
|
||||
document.body.dispatchEvent(event);
|
||||
}, 100);
|
||||
};
|
||||
document.head.appendChild(newFormScript);
|
||||
}
|
||||
}
|
||||
|
||||
// As a final resort, trigger the mutation event
|
||||
log('Triggering mutation._grav event as final resort');
|
||||
const event = new CustomEvent('mutation._grav', {
|
||||
detail: { target: element }
|
||||
});
|
||||
document.body.dispatchEvent(event);
|
||||
|
||||
isInitializing = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper function to find a function on the window object by name pattern
|
||||
function findFunctionOnWindow(pattern) {
|
||||
for (const key in window) {
|
||||
if (typeof window[key] === 'function' && key.includes(pattern)) {
|
||||
return window[key];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Function to check all Dropzone elements
|
||||
function checkAllDropzones() {
|
||||
const dropzones = document.querySelectorAll('.dropzone.files-upload:not(.dz-clickable)');
|
||||
|
||||
if (dropzones.length === 0) {
|
||||
log('No uninitialized Dropzone elements found');
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Found ${dropzones.length} uninitialized Dropzone elements`);
|
||||
|
||||
// Try to initialize each one
|
||||
dropzones.forEach(function(element) {
|
||||
initializeDropzone(element);
|
||||
});
|
||||
}
|
||||
|
||||
// Hook into form submission to reinitialize after XHR updates
|
||||
function setupFormSubmissionHook() {
|
||||
// First check if the XHR submit function is available
|
||||
if (window.GravFormXHR && typeof window.GravFormXHR.submit === 'function') {
|
||||
log('Found GravFormXHR.submit, attaching hook');
|
||||
|
||||
// Store the original function
|
||||
const originalSubmit = window.GravFormXHR.submit;
|
||||
|
||||
// Override it with our version
|
||||
window.GravFormXHR.submit = function(form) {
|
||||
log(`XHR form submission detected for form: ${form?.id || 'unknown'}`);
|
||||
|
||||
// Call the original function
|
||||
const result = originalSubmit.apply(this, arguments);
|
||||
|
||||
// Set up checks for after the submission completes
|
||||
[500, 1000, 2000, 3000].forEach(function(delay) {
|
||||
setTimeout(checkAllDropzones, delay);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
log('Successfully hooked into GravFormXHR.submit');
|
||||
}
|
||||
|
||||
// Also add a direct event listener for standard form submissions
|
||||
document.addEventListener('submit', function(event) {
|
||||
if (event.target.tagName === 'FORM') {
|
||||
log(`Standard form submission detected for form: ${event.target.id || 'unknown'}`);
|
||||
|
||||
// Schedule checks after submission
|
||||
[1000, 2000, 3000].forEach(function(delay) {
|
||||
setTimeout(checkAllDropzones, delay);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
log('Form submission hooks set up');
|
||||
}
|
||||
|
||||
// Monitor for AJAX responses
|
||||
function setupAjaxMonitoring() {
|
||||
if (window.jQuery) {
|
||||
log('Setting up jQuery AJAX response monitoring');
|
||||
|
||||
jQuery(document).ajaxComplete(function(event, xhr, settings) {
|
||||
log('AJAX request completed, checking if form-related');
|
||||
|
||||
// Check if this looks like a form request
|
||||
const url = settings.url || '';
|
||||
if (url.includes('form') ||
|
||||
url.includes('task=') ||
|
||||
url.includes('file-upload') ||
|
||||
url.includes('file-uploader')) {
|
||||
|
||||
log('Form-related AJAX request detected, will check for Dropzones');
|
||||
|
||||
// Schedule checks with delays
|
||||
[300, 800, 1500].forEach(function(delay) {
|
||||
setTimeout(checkAllDropzones, delay);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
log('jQuery AJAX monitoring set up');
|
||||
}
|
||||
}
|
||||
|
||||
// Create global function for manual reinitialization
|
||||
window.reinitializeDropzones = function() {
|
||||
log('Manual reinitialization triggered');
|
||||
checkAllDropzones();
|
||||
return 'Reinitialization check triggered. See console for details.';
|
||||
};
|
||||
|
||||
// Main initialization function
|
||||
function initialize() {
|
||||
log('Initializing Dropzone direct initialization system');
|
||||
|
||||
// Set up submission hook
|
||||
setupFormSubmissionHook();
|
||||
|
||||
// Set up AJAX monitoring
|
||||
setupAjaxMonitoring();
|
||||
|
||||
// Do an initial check for any uninitialized Dropzones
|
||||
setTimeout(checkAllDropzones, 500);
|
||||
|
||||
log('Initialization complete. Use window.reinitializeDropzones() for manual reinitialization.');
|
||||
}
|
||||
|
||||
// Start when the DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Delay to allow other scripts to load
|
||||
setTimeout(initialize, 100);
|
||||
});
|
||||
} else {
|
||||
// DOM already loaded, delay slightly
|
||||
setTimeout(initialize, 100);
|
||||
}
|
||||
})();
|
||||
Vendored
-1
File diff suppressed because one or more lines are too long
@@ -1,662 +0,0 @@
|
||||
/**
|
||||
* Unified Grav Form FilePond Handler
|
||||
*
|
||||
* This script initializes and configures FilePond instances for file uploads
|
||||
* within Grav forms. It works with both normal and XHR form submissions.
|
||||
* It also handles reinitializing FilePond instances after XHR form submissions.
|
||||
*/
|
||||
|
||||
// Immediately-Invoked Function Expression for scoping
|
||||
(function () {
|
||||
// Check if script already loaded
|
||||
if (window.gravFilepondHandlerLoaded) {
|
||||
console.log('FilePond unified handler already loaded, skipping.');
|
||||
return;
|
||||
}
|
||||
window.gravFilepondHandlerLoaded = true;
|
||||
|
||||
// Debugging - set to false for production
|
||||
const debug = true;
|
||||
|
||||
// Helper function for logging
|
||||
function log(message, type = 'log') {
|
||||
if (!debug && type !== 'error') return;
|
||||
|
||||
const prefix = '[FilePond Handler]';
|
||||
if (type === 'error') {
|
||||
console.error(prefix, message);
|
||||
} else if (type === 'warn') {
|
||||
console.warn(prefix, message);
|
||||
} else {
|
||||
console.log(prefix, message);
|
||||
}
|
||||
}
|
||||
|
||||
// Track FilePond instances with their configuration
|
||||
const pondInstances = new Map();
|
||||
|
||||
// Get translations from global object if available
|
||||
const translations = window.GravForm?.translations?.PLUGIN_FORM || {
|
||||
FILEPOND_REMOVE_FILE: 'Remove file',
|
||||
FILEPOND_REMOVE_FILE_CONFIRMATION: 'Are you sure you want to remove this file?',
|
||||
FILEPOND_CANCEL_UPLOAD: 'Cancel upload',
|
||||
FILEPOND_ERROR_FILESIZE: 'File is too large',
|
||||
FILEPOND_ERROR_FILETYPE: 'Invalid file type'
|
||||
};
|
||||
|
||||
// Track initialization state
|
||||
let initialized = false;
|
||||
|
||||
/**
|
||||
* Get standard FilePond configuration for an element
|
||||
* This is used for both initial setup and reinit after XHR
|
||||
* @param {HTMLElement} element - The file input element
|
||||
* @param {HTMLElement} container - The container element
|
||||
* @returns {Object} Configuration object for FilePond
|
||||
*/
|
||||
function getFilepondConfig(element, container) {
|
||||
if (!container) {
|
||||
log('Container not provided for config extraction', 'error');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the field is required - this is correct location
|
||||
const isRequired = element.hasAttribute('required') ||
|
||||
container.hasAttribute('required') ||
|
||||
container.getAttribute('data-required') === 'true';
|
||||
|
||||
// Then, add this code to remove the required attribute from the actual input
|
||||
// to prevent browser validation errors, but keep track of the requirement
|
||||
if (isRequired) {
|
||||
// Store the required state on the container for our custom validation
|
||||
container.setAttribute('data-required', 'true');
|
||||
// Remove the required attribute from the input to avoid browser validation errors
|
||||
element.removeAttribute('required');
|
||||
}
|
||||
|
||||
try {
|
||||
// Get settings from data attributes
|
||||
const settingsAttr = container.getAttribute('data-grav-file-settings');
|
||||
if (!settingsAttr) {
|
||||
log('No file settings found for FilePond element', 'warn');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse settings
|
||||
let settings;
|
||||
try {
|
||||
settings = JSON.parse(settingsAttr);
|
||||
log('Parsed settings:', settings);
|
||||
} catch (e) {
|
||||
log(`Error parsing file settings: ${e.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse FilePond options
|
||||
const filepondOptionsAttr = container.getAttribute('data-filepond-options') || '{}';
|
||||
let filepondOptions;
|
||||
try {
|
||||
filepondOptions = JSON.parse(filepondOptionsAttr);
|
||||
log('Parsed FilePond options:', filepondOptions);
|
||||
} catch (e) {
|
||||
log(`Error parsing FilePond options: ${e.message}`, 'error');
|
||||
filepondOptions = {};
|
||||
}
|
||||
|
||||
// Get URLs for upload and remove
|
||||
const uploadUrl = container.getAttribute('data-file-url-add');
|
||||
const removeUrl = container.getAttribute('data-file-url-remove');
|
||||
|
||||
if (!uploadUrl) {
|
||||
log('Upload URL not found for FilePond element', 'warn');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse previously uploaded files
|
||||
const existingFiles = [];
|
||||
const fileDataElements = container.querySelectorAll('[data-file]');
|
||||
log(`Found ${fileDataElements.length} existing file data elements`);
|
||||
|
||||
fileDataElements.forEach(fileData => {
|
||||
try {
|
||||
const fileAttr = fileData.getAttribute('data-file');
|
||||
log('File data attribute:', fileAttr);
|
||||
|
||||
const fileJson = JSON.parse(fileAttr);
|
||||
|
||||
if (fileJson && fileJson.name) {
|
||||
existingFiles.push({
|
||||
source: fileJson.name,
|
||||
options: {
|
||||
type: 'local',
|
||||
file: {
|
||||
name: fileJson.name,
|
||||
size: fileJson.size,
|
||||
type: fileJson.type
|
||||
},
|
||||
metadata: {
|
||||
poster: fileJson.thumb_url || fileJson.path
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(`Error parsing file data: ${e.message}`, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
log('Existing files:', existingFiles);
|
||||
|
||||
// Get form elements for Grav integration
|
||||
const fieldName = container.getAttribute('data-file-field-name');
|
||||
const form = element.closest('form');
|
||||
const formNameInput = form ? form.querySelector('[name="__form-name__"]') : document.querySelector('[name="__form-name__"]');
|
||||
const formIdInput = form ? form.querySelector('[name="__unique_form_id__"]') : document.querySelector('[name="__unique_form_id__"]');
|
||||
const formNonceInput = form ? form.querySelector('[name="form-nonce"]') : document.querySelector('[name="form-nonce"]');
|
||||
|
||||
if (!formNameInput || !formIdInput || !formNonceInput) {
|
||||
log('Missing required form inputs for proper Grav integration', 'warn');
|
||||
}
|
||||
|
||||
// Configure FilePond
|
||||
const options = {
|
||||
// Core settings
|
||||
name: settings.paramName,
|
||||
maxFiles: settings.limit || null,
|
||||
maxFileSize: `${settings.filesize}MB`,
|
||||
acceptedFileTypes: settings.accept,
|
||||
files: existingFiles,
|
||||
|
||||
// Server configuration - modified for Grav
|
||||
server: {
|
||||
process: {
|
||||
url: uploadUrl,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
ondata: (formData) => {
|
||||
// Safety check - ensure formData is valid
|
||||
if (!formData) {
|
||||
console.error('FormData is undefined in ondata');
|
||||
return new FormData(); // Return empty FormData as fallback
|
||||
}
|
||||
|
||||
// Add all required Grav form fields
|
||||
if (formNameInput) formData.append('__form-name__', formNameInput.value);
|
||||
if (formIdInput) formData.append('__unique_form_id__', formIdInput.value);
|
||||
formData.append('__form-file-uploader__', '1');
|
||||
if (formNonceInput) formData.append('form-nonce', formNonceInput.value);
|
||||
formData.append('task', 'filesupload');
|
||||
|
||||
// Use fieldName from the outer scope
|
||||
if (fieldName) {
|
||||
formData.append('name', fieldName);
|
||||
} else {
|
||||
console.error('Field name is undefined, falling back to default');
|
||||
formData.append('name', 'files');
|
||||
}
|
||||
|
||||
// Add URI if needed
|
||||
const uriInput = document.querySelector('[name="uri"]');
|
||||
if (uriInput) {
|
||||
formData.append('uri', uriInput.value);
|
||||
}
|
||||
|
||||
// Note: Don't try to append file here, FilePond will do that based on the name parameter
|
||||
// Just return the modified formData
|
||||
log('Prepared form data for Grav upload');
|
||||
return formData;
|
||||
}
|
||||
},
|
||||
revert: removeUrl ? {
|
||||
url: removeUrl,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
ondata: (formData, file) => {
|
||||
// Add all required Grav form fields
|
||||
if (formNameInput) formData.append('__form-name__', formNameInput.value);
|
||||
if (formIdInput) formData.append('__unique_form_id__', formIdInput.value);
|
||||
formData.append('__form-file-remover__', '1');
|
||||
if (formNonceInput) formData.append('form-nonce', formNonceInput.value);
|
||||
formData.append('name', fieldName);
|
||||
|
||||
// Add filename
|
||||
formData.append('filename', file.filename);
|
||||
|
||||
log('Prepared form data for file removal');
|
||||
return formData;
|
||||
}
|
||||
} : null
|
||||
},
|
||||
|
||||
// Image Transform settings - both FilePond native settings and our custom ones
|
||||
// Native settings
|
||||
allowImagePreview: true,
|
||||
allowImageResize: true,
|
||||
allowImageTransform: true,
|
||||
imagePreviewHeight: filepondOptions.imagePreviewHeight || 256,
|
||||
|
||||
// Transform settings
|
||||
imageTransformOutputMimeType: filepondOptions.imageTransformOutputMimeType || 'image/jpeg',
|
||||
imageTransformOutputQuality: filepondOptions.imageTransformOutputQuality || settings.resizeQuality || 90,
|
||||
imageTransformOutputStripImageHead: filepondOptions.imageTransformOutputStripImageHead !== false,
|
||||
|
||||
// Resize settings
|
||||
imageResizeTargetWidth: filepondOptions.imageResizeTargetWidth || settings.resizeWidth || null,
|
||||
imageResizeTargetHeight: filepondOptions.imageResizeTargetHeight || settings.resizeHeight || null,
|
||||
imageResizeMode: filepondOptions.imageResizeMode || 'cover',
|
||||
imageResizeUpscale: filepondOptions.imageResizeUpscale || false,
|
||||
|
||||
// Crop settings
|
||||
allowImageCrop: filepondOptions.allowImageCrop || false,
|
||||
imageCropAspectRatio: filepondOptions.imageCropAspectRatio || null,
|
||||
|
||||
// Labels and translations
|
||||
labelIdle: filepondOptions.labelIdle || '<span class="filepond--label-action">Browse</span> or drop files',
|
||||
labelFileTypeNotAllowed: translations.FILEPOND_ERROR_FILETYPE || 'Invalid file type',
|
||||
labelFileSizeNotAllowed: translations.FILEPOND_ERROR_FILESIZE || 'File is too large',
|
||||
labelFileLoading: 'Loading',
|
||||
labelFileProcessing: 'Uploading',
|
||||
labelFileProcessingComplete: 'Upload complete',
|
||||
labelFileProcessingAborted: 'Upload cancelled',
|
||||
labelTapToCancel: translations.FILEPOND_CANCEL_UPLOAD || 'Cancel upload',
|
||||
labelTapToRetry: 'Retry',
|
||||
labelTapToUndo: 'Undo',
|
||||
labelButtonRemoveItem: translations.FILEPOND_REMOVE_FILE || 'Remove',
|
||||
|
||||
// Style settings
|
||||
stylePanelLayout: filepondOptions.stylePanelLayout || 'compact',
|
||||
styleLoadIndicatorPosition: filepondOptions.styleLoadIndicatorPosition || 'center bottom',
|
||||
styleProgressIndicatorPosition: filepondOptions.styleProgressIndicatorPosition || 'center bottom',
|
||||
styleButtonRemoveItemPosition: filepondOptions.styleButtonRemoveItemPosition || 'right',
|
||||
|
||||
// Override with any remaining user-provided options
|
||||
...filepondOptions
|
||||
};
|
||||
|
||||
log('Prepared FilePond configuration:', options);
|
||||
|
||||
return options;
|
||||
} catch (e) {
|
||||
log(`Error creating FilePond configuration: ${e.message}`, 'error');
|
||||
console.error(e); // Full error in console
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a single FilePond instance
|
||||
* @param {HTMLElement} element - The file input element to initialize
|
||||
* @returns {FilePond|null} The created FilePond instance, or null if creation failed
|
||||
*/
|
||||
function initializeSingleFilePond(element) {
|
||||
const container = element.closest('.filepond-root');
|
||||
|
||||
if (!container) {
|
||||
log('FilePond container not found for input element', 'error');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Don't initialize twice
|
||||
if (container.classList.contains('filepond--hopper') || container.querySelector('.filepond--hopper')) {
|
||||
log('FilePond already initialized for this element, skipping');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the element ID or create a unique one for tracking
|
||||
const elementId = element.id || `filepond-${Math.random().toString(36).substring(2, 15)}`;
|
||||
|
||||
// Get configuration
|
||||
const config = getFilepondConfig(element, container);
|
||||
if (!config) {
|
||||
log('Failed to get configuration, cannot initialize FilePond', 'error');
|
||||
return null;
|
||||
}
|
||||
|
||||
log(`Initializing FilePond element ${elementId} with config`, config);
|
||||
|
||||
try {
|
||||
// Create FilePond instance
|
||||
const pond = FilePond.create(element, config);
|
||||
log(`FilePond instance created successfully for element ${elementId}`);
|
||||
|
||||
// Store the instance and its configuration for potential reinit
|
||||
pondInstances.set(elementId, {
|
||||
instance: pond,
|
||||
config: config,
|
||||
container: container
|
||||
});
|
||||
|
||||
// Add a reference to the element for easier lookup
|
||||
element.filepondId = elementId;
|
||||
container.filepondId = elementId;
|
||||
|
||||
// Handle form submission to ensure files are processed before submit
|
||||
const form = element.closest('form');
|
||||
if (form && !form._filepond_handler_attached) {
|
||||
form._filepond_handler_attached = true;
|
||||
|
||||
form.addEventListener('submit', function (e) {
|
||||
// Check for all FilePond instances in this form
|
||||
const formPonds = Array.from(pondInstances.values())
|
||||
.filter(info => info.instance && info.container.closest('form') === form);
|
||||
|
||||
const processingFiles = formPonds.reduce((total, info) => {
|
||||
return total + info.instance.getFiles().filter(file =>
|
||||
file.status === FilePond.FileStatus.PROCESSING_QUEUED ||
|
||||
file.status === FilePond.FileStatus.PROCESSING
|
||||
).length;
|
||||
}, 0);
|
||||
|
||||
if (processingFiles > 0) {
|
||||
e.preventDefault();
|
||||
alert('Please wait for all files to finish uploading before submitting the form.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return pond;
|
||||
} catch (e) {
|
||||
log(`Error creating FilePond instance: ${e.message}`, 'error');
|
||||
console.error(e); // Full error in console
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main FilePond initialization function
|
||||
* This will find and initialize all uninitialized FilePond elements
|
||||
*/
|
||||
function initializeFilePond() {
|
||||
log('Starting FilePond initialization');
|
||||
|
||||
// Make sure we have the libraries loaded
|
||||
if (typeof window.FilePond === 'undefined') {
|
||||
log('FilePond library not found. Will retry in 500ms...', 'warn');
|
||||
setTimeout(initializeFilePond, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
log('FilePond library found, continuing initialization');
|
||||
|
||||
// Register plugins if available
|
||||
try {
|
||||
if (window.FilePondPluginFileValidateSize) {
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateSize);
|
||||
log('Registered FileValidateSize plugin');
|
||||
}
|
||||
|
||||
if (window.FilePondPluginFileValidateType) {
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateType);
|
||||
log('Registered FileValidateType plugin');
|
||||
}
|
||||
|
||||
if (window.FilePondPluginImagePreview) {
|
||||
FilePond.registerPlugin(FilePondPluginImagePreview);
|
||||
log('Registered ImagePreview plugin');
|
||||
}
|
||||
|
||||
if (window.FilePondPluginImageResize) {
|
||||
FilePond.registerPlugin(FilePondPluginImageResize);
|
||||
log('Registered ImageResize plugin');
|
||||
}
|
||||
|
||||
if (window.FilePondPluginImageTransform) {
|
||||
FilePond.registerPlugin(FilePondPluginImageTransform);
|
||||
log('Registered ImageTransform plugin');
|
||||
}
|
||||
} catch (e) {
|
||||
log(`Error registering plugins: ${e.message}`, 'error');
|
||||
}
|
||||
|
||||
// Find all FilePond elements
|
||||
const elements = document.querySelectorAll('.filepond-root input[type="file"]:not(.filepond--browser)');
|
||||
|
||||
if (elements.length === 0) {
|
||||
log('No FilePond form elements found on the page');
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Found ${elements.length} FilePond element(s)`);
|
||||
|
||||
// Process each FilePond element
|
||||
elements.forEach((element, index) => {
|
||||
log(`Initializing FilePond element #${index + 1}`);
|
||||
initializeSingleFilePond(element);
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
log('FilePond initialization complete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize a specific FilePond instance
|
||||
* @param {HTMLElement} container - The FilePond container element
|
||||
* @returns {FilePond|null} The reinitialized FilePond instance, or null if reinitialization failed
|
||||
*/
|
||||
function reinitializeSingleFilePond(container) {
|
||||
if (!container) {
|
||||
log('No container provided for reinitialization', 'error');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if this is a FilePond container
|
||||
if (!container.classList.contains('filepond-root')) {
|
||||
log('Container is not a FilePond container', 'warn');
|
||||
return null;
|
||||
}
|
||||
|
||||
log(`Reinitializing FilePond container: ${container.id || 'unnamed'}`);
|
||||
|
||||
// If already initialized, destroy first
|
||||
if (container.classList.contains('filepond--hopper') || container.querySelector('.filepond--hopper')) {
|
||||
log('Container already has an active FilePond instance, destroying it first');
|
||||
|
||||
// Try to find and destroy through our internal tracking
|
||||
const elementId = container.filepondId;
|
||||
if (elementId && pondInstances.has(elementId)) {
|
||||
const info = pondInstances.get(elementId);
|
||||
if (info.instance) {
|
||||
log(`Destroying tracked FilePond instance for element ${elementId}`);
|
||||
info.instance.destroy();
|
||||
pondInstances.delete(elementId);
|
||||
}
|
||||
} else {
|
||||
// Fallback: Try to find via child element with class
|
||||
const pondElement = container.querySelector('.filepond--root');
|
||||
if (pondElement && pondElement._pond) {
|
||||
log('Destroying FilePond instance via DOM reference');
|
||||
pondElement._pond.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the file input
|
||||
const input = container.querySelector('input[type="file"]:not(.filepond--browser)');
|
||||
if (!input) {
|
||||
log('No file input found in container for reinitialization', 'error');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a new instance
|
||||
return initializeSingleFilePond(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize all FilePond instances
|
||||
* This is used after XHR form submissions
|
||||
*/
|
||||
function reinitializeFilePond() {
|
||||
log('Reinitializing all FilePond instances');
|
||||
|
||||
// Find all FilePond containers
|
||||
const containers = document.querySelectorAll('.filepond-root');
|
||||
if (containers.length === 0) {
|
||||
log('No FilePond containers found for reinitialization');
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Found ${containers.length} FilePond container(s) for reinitialization`);
|
||||
|
||||
// Process each container
|
||||
containers.forEach((container, index) => {
|
||||
log(`Reinitializing FilePond container #${index + 1}`);
|
||||
reinitializeSingleFilePond(container);
|
||||
});
|
||||
|
||||
log('FilePond reinitialization complete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to support XHR form interaction
|
||||
* This hooks into the GravFormXHR system if available
|
||||
*/
|
||||
function setupXHRIntegration() {
|
||||
// Only run if GravFormXHR is available
|
||||
if (window.GravFormXHR) {
|
||||
log('Setting up XHR integration for FilePond');
|
||||
|
||||
// Store original submit function
|
||||
const originalSubmit = window.GravFormXHR.submit;
|
||||
|
||||
// Override to handle FilePond files
|
||||
window.GravFormXHR.submit = function (form) {
|
||||
if (!form) {
|
||||
return originalSubmit.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Check for any FilePond instances in the form
|
||||
let hasPendingUploads = false;
|
||||
|
||||
// First check via our tracking
|
||||
Array.from(pondInstances.values()).forEach(info => {
|
||||
if (info.container.closest('form') === form) {
|
||||
const processingFiles = info.instance.getFiles().filter(file =>
|
||||
file.status === FilePond.FileStatus.PROCESSING_QUEUED ||
|
||||
file.status === FilePond.FileStatus.PROCESSING);
|
||||
|
||||
if (processingFiles.length > 0) {
|
||||
hasPendingUploads = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fallback check for any untracked instances
|
||||
if (!hasPendingUploads) {
|
||||
const filepondContainers = form.querySelectorAll('.filepond-root');
|
||||
filepondContainers.forEach(container => {
|
||||
const pondElement = container.querySelector('.filepond--root');
|
||||
if (pondElement && pondElement._pond) {
|
||||
const pond = pondElement._pond;
|
||||
const processingFiles = pond.getFiles().filter(file =>
|
||||
file.status === FilePond.FileStatus.PROCESSING_QUEUED ||
|
||||
file.status === FilePond.FileStatus.PROCESSING);
|
||||
|
||||
if (processingFiles.length > 0) {
|
||||
hasPendingUploads = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (hasPendingUploads) {
|
||||
alert('Please wait for all files to finish uploading before submitting the form.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Call the original submit function
|
||||
return originalSubmit.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Set up listeners for form updates
|
||||
document.addEventListener('grav-form-updated', function (e) {
|
||||
log('Detected form update event, reinitializing FilePond instances');
|
||||
setTimeout(reinitializeFilePond, 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup mutation observer to detect dynamically added FilePond elements
|
||||
*/
|
||||
function setupMutationObserver() {
|
||||
if (window.MutationObserver) {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
let shouldCheck = false;
|
||||
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === 'childList' && mutation.addedNodes.length) {
|
||||
for (const node of mutation.addedNodes) {
|
||||
if (node.nodeType === 1) {
|
||||
if (node.classList && node.classList.contains('filepond-root') ||
|
||||
node.querySelector && node.querySelector('.filepond-root')) {
|
||||
shouldCheck = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCheck) break;
|
||||
}
|
||||
|
||||
if (shouldCheck) {
|
||||
log('DOM changes detected that might include FilePond elements');
|
||||
// Delay to ensure DOM is fully updated
|
||||
setTimeout(initializeFilePond, 50);
|
||||
}
|
||||
});
|
||||
|
||||
// Start observing
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
log('MutationObserver set up for FilePond elements');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize when DOM is ready
|
||||
*/
|
||||
function domReadyInit() {
|
||||
log('DOM ready, initializing FilePond');
|
||||
initializeFilePond();
|
||||
setupXHRIntegration();
|
||||
setupMutationObserver();
|
||||
}
|
||||
|
||||
// Handle different document ready states
|
||||
if (document.readyState === 'loading') {
|
||||
log('Document still loading, adding DOMContentLoaded listener');
|
||||
document.addEventListener('DOMContentLoaded', domReadyInit);
|
||||
} else {
|
||||
log('Document already loaded, initializing now');
|
||||
setTimeout(domReadyInit, 0);
|
||||
}
|
||||
|
||||
// Also support initialization via window load event as a fallback
|
||||
window.addEventListener('load', function () {
|
||||
log('Window load event fired');
|
||||
if (!initialized) {
|
||||
log('FilePond not yet initialized, initializing now');
|
||||
initializeFilePond();
|
||||
}
|
||||
});
|
||||
|
||||
// Expose functions to global scope for external usage
|
||||
window.GravFilePond = {
|
||||
initialize: initializeFilePond,
|
||||
reinitialize: reinitializeFilePond,
|
||||
reinitializeContainer: reinitializeSingleFilePond,
|
||||
getInstances: () => Array.from(pondInstances.values()).map(info => info.instance)
|
||||
};
|
||||
|
||||
// Log initialization start
|
||||
log('FilePond unified handler script loaded and ready');
|
||||
})();
|
||||
@@ -1,141 +0,0 @@
|
||||
/**
|
||||
* FilePond Direct Fix - Emergency fix for XHR forms
|
||||
*/
|
||||
(function() {
|
||||
// Directly attempt to initialize uninitialized FilePond elements
|
||||
// without relying on any existing logic
|
||||
|
||||
console.log('FilePond Direct Fix loaded');
|
||||
|
||||
// Function to directly create FilePond instances
|
||||
function initializeFilePondElements() {
|
||||
console.log('Direct FilePond initialization attempt');
|
||||
|
||||
// Find uninitialized FilePond elements
|
||||
const elements = document.querySelectorAll('.filepond-root:not(.filepond--hopper)');
|
||||
if (elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${elements.length} uninitialized FilePond elements`);
|
||||
|
||||
// Process each element
|
||||
elements.forEach((element, index) => {
|
||||
const input = element.querySelector('input[type="file"]:not(.filepond--browser)');
|
||||
if (!input) {
|
||||
console.log(`Element #${index + 1}: No suitable file input found`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Element #${index + 1}: Found file input:`, input);
|
||||
|
||||
// Get settings
|
||||
let settings = {};
|
||||
try {
|
||||
const settingsAttr = element.getAttribute('data-grav-file-settings');
|
||||
if (settingsAttr) {
|
||||
settings = JSON.parse(settingsAttr);
|
||||
console.log('Parsed settings:', settings);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse settings:', e);
|
||||
}
|
||||
|
||||
// Get URLS
|
||||
const uploadUrl = element.getAttribute('data-file-url-add');
|
||||
const removeUrl = element.getAttribute('data-file-url-remove');
|
||||
|
||||
console.log('Upload URL:', uploadUrl);
|
||||
console.log('Remove URL:', removeUrl);
|
||||
|
||||
try {
|
||||
// Create FilePond instance directly
|
||||
const pond = FilePond.create(input);
|
||||
|
||||
// Apply minimal configuration to make uploads work
|
||||
if (pond) {
|
||||
console.log(`Successfully created FilePond on element #${index + 1}`);
|
||||
|
||||
// Basic configuration to make it functional
|
||||
pond.setOptions({
|
||||
name: settings.paramName || input.name || 'files',
|
||||
server: {
|
||||
process: uploadUrl,
|
||||
revert: removeUrl
|
||||
},
|
||||
// Transform options
|
||||
imageTransformOutputMimeType: 'image/jpeg',
|
||||
imageTransformOutputQuality: settings.resizeQuality || 90,
|
||||
imageTransformOutputStripImageHead: true,
|
||||
// Resize options
|
||||
imageResizeTargetWidth: settings.resizeWidth || null,
|
||||
imageResizeTargetHeight: settings.resizeHeight || null,
|
||||
imageResizeMode: 'cover',
|
||||
imageResizeUpscale: false
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to create FilePond on element #${index + 1}:`, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Monitor form submissions and DOM changes
|
||||
function setupMonitoring() {
|
||||
// Create MutationObserver to watch for DOM changes
|
||||
if (window.MutationObserver) {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
let shouldCheck = false;
|
||||
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === 'childList' && mutation.addedNodes.length) {
|
||||
for (const node of mutation.addedNodes) {
|
||||
if (node.nodeType === 1) {
|
||||
if (node.classList && node.classList.contains('filepond-root') ||
|
||||
node.querySelector && node.querySelector('.filepond-root')) {
|
||||
shouldCheck = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCheck) break;
|
||||
}
|
||||
|
||||
if (shouldCheck) {
|
||||
console.log('DOM changes detected that might include FilePond elements');
|
||||
// Delay to ensure DOM is fully updated
|
||||
setTimeout(initializeFilePondElements, 50);
|
||||
}
|
||||
});
|
||||
|
||||
// Start observing
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
console.log('MutationObserver set up for FilePond elements');
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the emergency fix
|
||||
function init() {
|
||||
// Set up monitoring
|
||||
setupMonitoring();
|
||||
|
||||
// Expose global function for manual reinit
|
||||
window.directFilePondInit = initializeFilePondElements;
|
||||
|
||||
// Initial check
|
||||
setTimeout(initializeFilePondElements, 500);
|
||||
}
|
||||
|
||||
// Start when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
setTimeout(init, 0);
|
||||
}
|
||||
})();
|
||||
@@ -1,9 +0,0 @@
|
||||
/*!
|
||||
* FilePondPluginFileValidateSize 2.2.8
|
||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||
* Please visit https://pqina.nl/filepond/ for details.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
!function(e,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(e=e||self).FilePondPluginFileValidateSize=i()}(this,function(){"use strict";var e=function(e){var i=e.addFilter,E=e.utils,l=E.Type,_=E.replaceInString,n=E.toNaturalFileSize;return i("ALLOW_HOPPER_ITEM",function(e,i){var E=i.query;if(!E("GET_ALLOW_FILE_SIZE_VALIDATION"))return!0;var l=E("GET_MAX_FILE_SIZE");if(null!==l&&e.size>l)return!1;var _=E("GET_MIN_FILE_SIZE");return!(null!==_&&e.size<_)}),i("LOAD_FILE",function(e,i){var E=i.query;return new Promise(function(i,l){if(!E("GET_ALLOW_FILE_SIZE_VALIDATION"))return i(e);var I=E("GET_FILE_VALIDATE_SIZE_FILTER");if(I&&!I(e))return i(e);var t=E("GET_MAX_FILE_SIZE");if(null!==t&&e.size>t)l({status:{main:E("GET_LABEL_MAX_FILE_SIZE_EXCEEDED"),sub:_(E("GET_LABEL_MAX_FILE_SIZE"),{filesize:n(t,".",E("GET_FILE_SIZE_BASE"),E("GET_FILE_SIZE_LABELS",E))})}});else{var L=E("GET_MIN_FILE_SIZE");if(null!==L&&e.size<L)l({status:{main:E("GET_LABEL_MIN_FILE_SIZE_EXCEEDED"),sub:_(E("GET_LABEL_MIN_FILE_SIZE"),{filesize:n(L,".",E("GET_FILE_SIZE_BASE"),E("GET_FILE_SIZE_LABELS",E))})}});else{var a=E("GET_MAX_TOTAL_FILE_SIZE");if(null!==a)if(E("GET_ACTIVE_ITEMS").reduce(function(e,i){return e+i.fileSize},0)>a)return void l({status:{main:E("GET_LABEL_MAX_TOTAL_FILE_SIZE_EXCEEDED"),sub:_(E("GET_LABEL_MAX_TOTAL_FILE_SIZE"),{filesize:n(a,".",E("GET_FILE_SIZE_BASE"),E("GET_FILE_SIZE_LABELS",E))})}});i(e)}}})}),{options:{allowFileSizeValidation:[!0,l.BOOLEAN],maxFileSize:[null,l.INT],minFileSize:[null,l.INT],maxTotalFileSize:[null,l.INT],fileValidateSizeFilter:[null,l.FUNCTION],labelMinFileSizeExceeded:["File is too small",l.STRING],labelMinFileSize:["Minimum file size is {filesize}",l.STRING],labelMaxFileSizeExceeded:["File is too large",l.STRING],labelMaxFileSize:["Maximum file size is {filesize}",l.STRING],labelMaxTotalFileSizeExceeded:["Maximum total size exceeded",l.STRING],labelMaxTotalFileSize:["Maximum total file size is {filesize}",l.STRING]}}};return"undefined"!=typeof window&&void 0!==window.document&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:e})),e});
|
||||
@@ -1,9 +0,0 @@
|
||||
/*!
|
||||
* FilePondPluginFileValidateType 1.2.9
|
||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||
* Please visit https://pqina.nl/filepond/ for details.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).FilePondPluginFileValidateType=t()}(this,function(){"use strict";var e=function(e){var t=e.addFilter,n=e.utils,i=n.Type,T=n.isString,E=n.replaceInString,l=n.guesstimateMimeType,o=n.getExtensionFromFilename,r=n.getFilenameFromURL,u=function(e,t){return e.some(function(e){return/\*$/.test(e)?(n=e,(/^[^/]+/.exec(t)||[]).pop()===n.slice(0,-2)):e===t;var n})},a=function(e,t,n){if(0===t.length)return!0;var i=function(e){var t="";if(T(e)){var n=r(e),i=o(n);i&&(t=l(i))}else t=e.type;return t}(e);return n?new Promise(function(T,E){n(e,i).then(function(e){u(t,e)?T():E()}).catch(E)}):u(t,i)};return t("SET_ATTRIBUTE_TO_OPTION_MAP",function(e){return Object.assign(e,{accept:"acceptedFileTypes"})}),t("ALLOW_HOPPER_ITEM",function(e,t){var n=t.query;return!n("GET_ALLOW_FILE_TYPE_VALIDATION")||a(e,n("GET_ACCEPTED_FILE_TYPES"))}),t("LOAD_FILE",function(e,t){var n=t.query;return new Promise(function(t,i){if(n("GET_ALLOW_FILE_TYPE_VALIDATION")){var T=n("GET_ACCEPTED_FILE_TYPES"),l=n("GET_FILE_VALIDATE_TYPE_DETECT_TYPE"),o=a(e,T,l),r=function(){var e,t=T.map((e=n("GET_FILE_VALIDATE_TYPE_LABEL_EXPECTED_TYPES_MAP"),function(t){return null!==e[t]&&(e[t]||t)})).filter(function(e){return!1!==e}),l=t.filter(function(e,n){return t.indexOf(e)===n});i({status:{main:n("GET_LABEL_FILE_TYPE_NOT_ALLOWED"),sub:E(n("GET_FILE_VALIDATE_TYPE_LABEL_EXPECTED_TYPES"),{allTypes:l.join(", "),allButLastType:l.slice(0,-1).join(", "),lastType:l[l.length-1]})}})};if("boolean"==typeof o)return o?t(e):r();o.then(function(){t(e)}).catch(r)}else t(e)})}),{options:{allowFileTypeValidation:[!0,i.BOOLEAN],acceptedFileTypes:[[],i.ARRAY],labelFileTypeNotAllowed:["File is of invalid type",i.STRING],fileValidateTypeLabelExpectedTypes:["Expects {allButLastType} or {lastType}",i.STRING],fileValidateTypeLabelExpectedTypesMap:[{},i.OBJECT],fileValidateTypeDetectType:[null,i.FUNCTION]}}};return"undefined"!=typeof window&&void 0!==window.document&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:e})),e});
|
||||
@@ -1,8 +0,0 @@
|
||||
/*!
|
||||
* FilePondPluginImagePreview 4.6.12
|
||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||
* Please visit https://pqina.nl/filepond/ for details.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
.filepond--image-preview-markup{position:absolute;left:0;top:0}.filepond--image-preview-wrapper{z-index:2}.filepond--image-preview-overlay{display:block;position:absolute;left:0;top:0;width:100%;min-height:5rem;max-height:7rem;margin:0;opacity:0;z-index:2;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.filepond--image-preview-overlay svg{width:100%;height:auto;color:inherit;max-height:inherit}.filepond--image-preview-overlay-idle{mix-blend-mode:multiply;color:rgba(40,40,40,.85)}.filepond--image-preview-overlay-success{mix-blend-mode:normal;color:#369763}.filepond--image-preview-overlay-failure{mix-blend-mode:normal;color:#c44e47}@supports (-webkit-marquee-repetition:infinite) and ((-o-object-fit:fill) or (object-fit:fill)){.filepond--image-preview-overlay-idle{mix-blend-mode:normal}}.filepond--image-preview-wrapper{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:absolute;left:0;top:0;right:0;height:100%;margin:0;border-radius:.45em;overflow:hidden;background:rgba(0,0,0,.01)}.filepond--image-preview{position:absolute;left:0;top:0;z-index:1;display:flex;align-items:center;height:100%;width:100%;pointer-events:none;background:#222;will-change:transform,opacity}.filepond--image-clip{position:relative;overflow:hidden;margin:0 auto}.filepond--image-clip[data-transparency-indicator=grid] canvas,.filepond--image-clip[data-transparency-indicator=grid] img{background-color:#fff;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' fill='%23eee'%3E%3Cpath d='M0 0h50v50H0M50 50h50v50H50'/%3E%3C/svg%3E");background-size:1.25em 1.25em}.filepond--image-bitmap,.filepond--image-vector{position:absolute;left:0;top:0;will-change:transform}.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview-wrapper{border-radius:0}.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview{height:100%;display:flex;justify-content:center;align-items:center}.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-wrapper{border-radius:99999rem}.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-overlay{top:auto;bottom:0;-webkit-transform:scaleY(-1);transform:scaleY(-1)}.filepond--root[data-style-panel-layout~=circle] .filepond--file .filepond--file-action-button[data-align*=bottom]:not([data-align*=center]){margin-bottom:.325em}.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=left]{left:calc(50% - 3em)}.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=right]{right:calc(50% - 3em)}.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=left],.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=right]{margin-bottom:.5125em}.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=center]{margin-top:0;margin-bottom:.1875em;margin-left:.1875em}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,9 +0,0 @@
|
||||
/*!
|
||||
* FilePondPluginImageResize 2.0.10
|
||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||
* Please visit https://pqina.nl/filepond/ for details.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).FilePondPluginImageResize=t()}(this,function(){"use strict";var e=function(e){var t=e.addFilter,i=e.utils.Type;return t("DID_LOAD_ITEM",function(e,t){var i=t.query;return new Promise(function(t,n){var r=e.file;if(!function(e){return/^image/.test(e.type)}(r)||!i("GET_ALLOW_IMAGE_RESIZE"))return t(e);var u=i("GET_IMAGE_RESIZE_MODE"),o=i("GET_IMAGE_RESIZE_TARGET_WIDTH"),a=i("GET_IMAGE_RESIZE_TARGET_HEIGHT"),l=i("GET_IMAGE_RESIZE_UPSCALE");if(null===o&&null===a)return t(e);var d,f,E,s=null===o?a:o,c=null===a?s:a,I=URL.createObjectURL(r);d=I,f=function(i){if(URL.revokeObjectURL(I),!i)return t(e);var n=i.width,r=i.height,o=(e.getMetadata("exif")||{}).orientation||-1;if(o>=5&&o<=8){var a=[r,n];n=a[0],r=a[1]}if(n===s&&r===c)return t(e);if(!l)if("cover"===u){if(n<=s||r<=c)return t(e)}else if(n<=s&&r<=s)return t(e);e.setMetadata("resize",{mode:u,upscale:l,size:{width:s,height:c}}),t(e)},(E=new Image).onload=function(){var e=E.naturalWidth,t=E.naturalHeight;E=null,f({width:e,height:t})},E.onerror=function(){return f(null)},E.src=d})}),{options:{allowImageResize:[!0,i.BOOLEAN],imageResizeMode:["cover",i.STRING],imageResizeUpscale:[!0,i.BOOLEAN],imageResizeTargetWidth:[null,i.INT],imageResizeTargetHeight:[null,i.INT]}}};return"undefined"!=typeof window&&void 0!==window.document&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:e})),e});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,62 +0,0 @@
|
||||
(function() {
|
||||
function refreshNonces() {
|
||||
var nonces = document.querySelectorAll('.form-nonce-field');
|
||||
if (nonces.length === 0) return;
|
||||
|
||||
var actions = {};
|
||||
nonces.forEach(function(field) {
|
||||
var action = field.getAttribute('data-nonce-action');
|
||||
if (!actions[action]) actions[action] = [];
|
||||
actions[action].push(field);
|
||||
});
|
||||
|
||||
Object.keys(actions).forEach(function(action) {
|
||||
try {
|
||||
var urlObj = new URL(window.location.href);
|
||||
urlObj.searchParams.set('task', 'get-nonce');
|
||||
urlObj.searchParams.set('action', action);
|
||||
var fetchUrl = urlObj.toString();
|
||||
|
||||
fetch(fetchUrl, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(data) {
|
||||
if (data.nonce) {
|
||||
actions[action].forEach(function(field) {
|
||||
field.value = data.nonce;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function(e) {
|
||||
console.error('Grav Form Plugin: Failed to refresh nonce', e);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Grav Form Plugin: URL parsing failed', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh based on configured interval (default to 15 minutes)
|
||||
var interval = (window.GravForm && window.GravForm.refresh_nonce_interval) || 900000;
|
||||
|
||||
function scheduleRefresh() {
|
||||
var nonces = document.querySelectorAll('.form-nonce-field');
|
||||
if (nonces.length === 0) return;
|
||||
|
||||
var parsed = Number(interval);
|
||||
var delay = !isNaN(parsed) && parsed > 0 ? parsed : 900000;
|
||||
delay = Math.max(delay, 1000);
|
||||
setTimeout(function() {
|
||||
refreshNonces();
|
||||
setInterval(refreshNonces, delay);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', scheduleRefresh);
|
||||
} else {
|
||||
scheduleRefresh();
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -1 +0,0 @@
|
||||
.form-group.has-errors{background:rgba(255,0,0,.05);border:1px solid rgba(255,0,0,.2);border-radius:3px;margin:0 -5px;padding:0 5px}.form-errors{color:#b52b27}.form-honeybear{display:none;position:absolute !important;height:1px;width:1px;overflow:hidden;clip-path:rect(0px, 1px, 1px, 0px)}.form-errors p{margin:0}.form-input-file input{display:none}.form-input-file .dz-default.dz-message{position:absolute;text-align:center;left:0;right:0;top:50%;transform:translateY(-50%);margin:0}.form-input-file.dropzone{position:relative;min-height:70px;border-radius:3px;margin-bottom:.85rem;border:2px dashed #ccc;color:#aaa;padding:.5rem}.form-input-file.dropzone .dz-preview{margin:.5rem}.form-input-file.dropzone .dz-preview:hover{z-index:2}.form-input-file.dropzone .dz-preview .dz-image img{margin:0}.form-input-file.dropzone .dz-preview .dz-remove{font-size:16px;position:absolute;top:3px;right:3px;display:inline-flex;height:20px;width:20px;background-color:red;justify-content:center;align-items:center;color:#fff;font-weight:bold;border-radius:50%;cursor:pointer;z-index:20}.form-input-file.dropzone .dz-preview .dz-remove:hover{background-color:darkred;text-decoration:none}.form-input-file.dropzone .dz-preview .dz-error-message{min-width:140px;width:auto}.form-input-file.dropzone .dz-preview .dz-image,.form-input-file.dropzone .dz-preview.dz-file-preview .dz-image{border-radius:3px;z-index:1}.filepond--root.form-input{min-height:7rem;height:auto;overflow:hidden;border:0}.form-tabs .tabs-nav{display:flex;padding-top:1px;margin-bottom:-1px}.form-tabs .tabs-nav a{flex:1;transition:color .5s ease,background .5s ease;cursor:pointer;text-align:center;padding:10px;display:flex;align-items:center;justify-content:center;border-bottom:1px solid #ccc;border-radius:5px 5px 0 0}.form-tabs .tabs-nav a.active{border:1px solid #ccc;border-bottom:1px solid rgba(0,0,0,0);margin:0 -1px}.form-tabs .tabs-nav a.active span{color:#000}.form-tabs .tabs-nav span{display:inline-block;line-height:1.1}.form-tabs.subtle .tabs-nav{margin-right:0 !important}.form-tabs .tabs-content .tab__content{display:none;padding-top:2rem}.form-tabs .tabs-content .tab__content.active{display:block}.checkboxes{display:inline-block}.checkboxes label{display:inline;cursor:pointer;position:relative;padding:0 0 0 20px;margin-right:15px}.checkboxes label:before{content:"";display:inline-block;width:20px;height:20px;left:0;margin-top:0;margin-right:10px;position:absolute;border-radius:3px;border:1px solid #e6e6e6}.checkboxes input[type=checkbox]{display:none}.checkboxes input[type=checkbox]:checked+label:before{content:"✓";font-size:20px;line-height:1;text-align:center}.checkboxes.toggleable label{margin-right:0}.form-field-toggleable .checkboxes.toggleable{margin-right:5px;vertical-align:middle}.form-field-toggleable .checkboxes+label{display:inline-block}.switch-toggle{display:inline-flex;overflow:hidden;border-radius:3px;line-height:35px;border:1px solid #ccc}.switch-toggle input[type=radio]{position:absolute;visibility:hidden;display:none}.switch-toggle label{display:inline-block;cursor:pointer;padding:0 15px;margin:0;white-space:nowrap;color:inherit;transition:background-color .5s ease}.switch-toggle input.highlight:checked+label{background:#333;color:#fff}.switch-toggle input:checked+label{color:#fff;background:#999}.signature-pad{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;font-size:10px;width:100%;height:100%;max-width:700px;max-height:460px;border:1px solid #f0f0f0;background-color:#fff;padding:16px}.signature-pad--body{position:relative;-webkit-box-flex:1;-ms-flex:1;flex:1;border:1px solid #f6f6f6;min-height:100px}.signature-pad--body canvas{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:4px;box-shadow:0 0 5px rgba(0,0,0,.02) inset}.signature-pad--footer{color:#c3c3c3;text-align:center;font-size:1.2em}.signature-pad--actions{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin-top:8px}[data-grav-field=array] .form-row{display:flex;align-items:center;margin-bottom:.5rem}[data-grav-field=array] .form-row>input,[data-grav-field=array] .form-row>textarea{margin:0 .5rem;display:inline-block}.form-data.basic-captcha .form-input-wrapper{border:1px solid #ccc;border-radius:5px;display:flex;overflow:hidden}.form-data.basic-captcha .form-input-prepend{display:flex;color:#333;background-color:#ccc;flex-shrink:0}.form-data.basic-captcha .form-input-prepend img{margin:0}.form-data.basic-captcha .form-input-prepend button>svg{margin:0 8px;width:18px;height:18px}.form-data.basic-captcha input.form-input{border:0}/*# sourceMappingURL=form-styles.css.map */
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../scss/form-styles.scss"],"names":[],"mappings":"CAGA,uBACI,6BACA,kCACA,kBACA,cACA,cAGJ,aACI,cAGJ,gBACI,aACA,6BACA,WACA,UACA,gBACA,mCAGJ,eACI,SAKA,uBACI,aAGJ,wCACI,kBACA,kBACA,OACA,QACA,QACA,2BACA,SAGJ,0BACI,kBACA,gBACA,kBACA,qBACA,uBACA,WACA,cAEA,sCACI,aAEA,4CACI,UAGJ,oDACE,SAGF,iDACE,eACA,kBACA,QACA,UACA,oBACA,YACA,WACA,qBACA,uBACA,mBACA,WACA,iBACA,kBACA,eACA,WACA,uDACI,yBACA,qBAIN,wDACI,gBACA,WAGJ,gHAEI,kBACA,UAOhB,2BACE,gBACA,YACA,gBACA,SAME,qBACI,aACA,gBAEA,mBAEA,uBACI,OACA,8CACA,eACA,kBACA,aACA,aACA,mBACA,uBACA,6BACA,0BAEA,8BACI,sBACA,sCACA,cAEA,mCACI,MAtIA,KA2IZ,0BACI,qBACA,gBAKR,4BACI,0BAKA,uCACI,aACA,iBAEA,8CACI,cAOhB,YACI,qBAEA,kBACI,eACA,eACA,kBACA,mBACA,kBAGJ,yBACI,WACA,qBACA,WACA,YACA,OACA,aACA,kBACA,kBACA,kBAEA,yBAGJ,iCACI,aAEJ,sDACI,YACA,eACA,cACA,kBAGJ,6BACI,eAMJ,8CACI,iBACA,sBAEJ,yCACI,qBAKR,eACI,oBACA,gBACA,kBACA,iBACA,sBAEA,iCACI,kBACA,kBACA,aAGJ,qBACI,qBACA,eACA,eACA,SACA,mBACA,cACA,qCAGJ,6CACI,gBACA,WAGJ,mCACI,WACA,gBAOR,eACI,kBACA,oBACA,oBACA,aACA,4BACA,6BACA,0BACA,sBACA,eACA,WACA,YACA,gBACA,iBACA,yBACA,sBACA,aAGJ,qBACI,kBACA,mBACA,WACA,OACA,yBACA,iBAGJ,4BACI,kBACA,OACA,MACA,WACA,YACA,kBACA,yCAGJ,uBACI,cACA,kBACA,gBAGJ,wBACI,oBACA,oBACA,aACA,yBACA,sBACA,8BACA,eAGJ,kCACI,aACA,mBACA,oBAGJ,mFAGI,eACA,qBAIA,6CACI,sBACA,kBACA,aACA,gBAEJ,6CACI,aACA,WACA,sBACA,cACA,iDACI,SAEJ,wDACI,aACA,WACA,YAGR,0CACI","file":"form-styles.css"}
|
||||
-1
@@ -1 +0,0 @@
|
||||
.form-group.has-errors{margin:0 -5px;padding:0 5px;border:1px solid rgba(255,0,0,.2);border-radius:3px;background:rgba(255,0,0,.05)}.form-errors{color:#b52b27}.form-honeybear{position:absolute!important;visibility:hidden;overflow:hidden;clip:rect(1px,1px,1px,1px);width:1px;height:1px}.form-errors p{margin:0}.form-input-file input{display:none}.form-input-file .dz-default.dz-message{position:absolute;top:50%;right:0;left:0;margin:0;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center}.form-input-file.dropzone{position:relative;min-height:70px;margin-bottom:.85rem;padding:.5rem;color:#aaa;border:2px dashed #ccc;border-radius:3px}.form-input-file.dropzone .dz-preview{margin:.5rem}.form-input-file.dropzone .dz-preview:hover{z-index:2}.form-input-file.dropzone .dz-preview .dz-error-message{width:auto;min-width:140px}.form-input-file.dropzone .dz-preview .dz-image,.form-input-file.dropzone .dz-preview.dz-file-preview .dz-image{z-index:1;border-radius:3px}.form-tabs .tabs-nav{display:flex;margin-bottom:-1px;padding-top:1px}.form-tabs .tabs-nav a{display:flex;padding:10px;cursor:pointer;transition:color .5s ease,background .5s ease;text-align:center;border-bottom:1px solid #eee;border-radius:5px 5px 0 0;flex:1;align-items:center;justify-content:center}.form-tabs .tabs-nav a.active{margin:0 -1px;border:1px solid #eee;border-bottom:1px solid transparent}.form-tabs .tabs-nav a.active span{color:#000}.form-tabs .tabs-nav span{line-height:1.1;display:inline-block}.form-tabs.subtle .tabs-nav{margin-right:0!important}.form-tabs .tabs-content .tab__content{display:none;padding-top:2rem}.form-tabs .tabs-content .tab__content.active{display:block}.checkboxes{display:inline-block}.checkboxes label{position:relative;display:inline;margin-right:15px;padding:0 0 0 20px;cursor:pointer}.checkboxes label:before{position:absolute;left:0;display:inline-block;width:20px;height:20px;margin-top:0;margin-right:10px;content:'';border:1px solid #e6e6e6;border-radius:3px}.checkboxes input[type=checkbox]{display:none}.checkboxes input[type=checkbox]:checked+label:before{font-size:20px;line-height:1;content:'\2713';text-align:center}.checkboxes.toggleable label{margin-right:0}.form-field-toggleable .checkboxes.toggleable{margin-right:5px;vertical-align:middle}.form-field-toggleable .checkboxes+label{display:inline-block}.switch-toggle{line-height:35px;display:inline-flex;overflow:hidden;border:1px solid #eee;border-radius:3px}.switch-toggle input[type=radio]{position:absolute;display:none;visibility:hidden}.switch-toggle label{display:inline-block;margin:0;padding:0 15px;cursor:pointer;transition:background-color .5s ease;white-space:nowrap;color:inherit}.switch-toggle input.highlight:checked+label{color:#fff;background:#333}.switch-toggle input:checked+label{color:#fff;background:#999}.signature-pad{font-size:10px;position:relative;display:flex;flex-direction:column;width:100%;max-width:700px;height:100%;max-height:460px;padding:16px;border:1px solid #f0f0f0;background-color:#fff}.signature-pad--body{position:relative;min-height:100px;border:1px solid #f6f6f6;flex:1}.signature-pad--body canvas{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:4px;box-shadow:0 0 5px rgba(0,0,0,.02) inset}.signature-pad--footer{font-size:1.2em;text-align:center;color:#c3c3c3}.signature-pad--actions{display:flex;margin-top:8px;justify-content:space-between}[data-grav-field=array] .form-row{display:flex;margin-bottom:.5rem;align-items:center}[data-grav-field=array] .form-row>input,[data-grav-field=array] .form-row>textarea{display:inline-block;margin:0 .5rem}
|
||||
Vendored
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,29 +0,0 @@
|
||||
if (typeof Object.assign !== 'function') {
|
||||
// Must be writable: true, enumerable: false, configurable: true
|
||||
Object.defineProperty(Object, 'assign', {
|
||||
value: function assign(target, varArgs) { // .length of function is 2
|
||||
'use strict';
|
||||
if (target == null) { // TypeError if undefined or null
|
||||
throw new TypeError('Cannot convert undefined or null to object');
|
||||
}
|
||||
|
||||
var to = Object(target);
|
||||
|
||||
for (var index = 1; index < arguments.length; index++) {
|
||||
var nextSource = arguments[index];
|
||||
|
||||
if (nextSource != null) { // Skip over if undefined or null
|
||||
for (var nextKey in nextSource) {
|
||||
// Avoid bugs when hasOwnProperty is shadowed
|
||||
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
||||
to[nextKey] = nextSource[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return to;
|
||||
},
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
@@ -1,617 +0,0 @@
|
||||
/*!
|
||||
* Signature Pad v2.3.2
|
||||
* https://github.com/szimek/signature_pad
|
||||
*
|
||||
* Copyright 2017 Szymon Nowak
|
||||
* Released under the MIT license
|
||||
*
|
||||
* The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from:
|
||||
* http://corner.squareup.com/2012/07/smoother-signatures.html
|
||||
*
|
||||
* Implementation of interpolation using cubic Bézier curves is taken from:
|
||||
* http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript
|
||||
*
|
||||
* Algorithm for approximated length of a Bézier curve is taken from:
|
||||
* http://www.lemoda.net/maths/bezier-length/index.html
|
||||
*
|
||||
*/
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.SignaturePad = factory());
|
||||
}(this, (function () { 'use strict';
|
||||
|
||||
function Point(x, y, time) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.time = time || new Date().getTime();
|
||||
}
|
||||
|
||||
Point.prototype.velocityFrom = function (start) {
|
||||
return this.time !== start.time ? this.distanceTo(start) / (this.time - start.time) : 1;
|
||||
};
|
||||
|
||||
Point.prototype.distanceTo = function (start) {
|
||||
return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
|
||||
};
|
||||
|
||||
Point.prototype.equals = function (other) {
|
||||
return this.x === other.x && this.y === other.y && this.time === other.time;
|
||||
};
|
||||
|
||||
function Bezier(startPoint, control1, control2, endPoint) {
|
||||
this.startPoint = startPoint;
|
||||
this.control1 = control1;
|
||||
this.control2 = control2;
|
||||
this.endPoint = endPoint;
|
||||
}
|
||||
|
||||
// Returns approximated length.
|
||||
Bezier.prototype.length = function () {
|
||||
var steps = 10;
|
||||
var length = 0;
|
||||
var px = void 0;
|
||||
var py = void 0;
|
||||
|
||||
for (var i = 0; i <= steps; i += 1) {
|
||||
var t = i / steps;
|
||||
var cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
|
||||
var cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
|
||||
if (i > 0) {
|
||||
var xdiff = cx - px;
|
||||
var ydiff = cy - py;
|
||||
length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
|
||||
}
|
||||
px = cx;
|
||||
py = cy;
|
||||
}
|
||||
|
||||
return length;
|
||||
};
|
||||
|
||||
/* eslint-disable no-multi-spaces, space-in-parens */
|
||||
Bezier.prototype._point = function (t, start, c1, c2, end) {
|
||||
return start * (1.0 - t) * (1.0 - t) * (1.0 - t) + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t + 3.0 * c2 * (1.0 - t) * t * t + end * t * t * t;
|
||||
};
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
// http://stackoverflow.com/a/27078401/815507
|
||||
function throttle(func, wait, options) {
|
||||
var context, args, result;
|
||||
var timeout = null;
|
||||
var previous = 0;
|
||||
if (!options) options = {};
|
||||
var later = function later() {
|
||||
previous = options.leading === false ? 0 : Date.now();
|
||||
timeout = null;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
};
|
||||
return function () {
|
||||
var now = Date.now();
|
||||
if (!previous && options.leading === false) previous = now;
|
||||
var remaining = wait - (now - previous);
|
||||
context = this;
|
||||
args = arguments;
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
previous = now;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
} else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function SignaturePad(canvas, options) {
|
||||
var self = this;
|
||||
var opts = options || {};
|
||||
|
||||
this.velocityFilterWeight = opts.velocityFilterWeight || 0.7;
|
||||
this.minWidth = opts.minWidth || 0.5;
|
||||
this.maxWidth = opts.maxWidth || 2.5;
|
||||
this.throttle = 'throttle' in opts ? opts.throttle : 16; // in miliseconds
|
||||
this.minDistance = 'minDistance' in opts ? opts.minDistance : 5;
|
||||
|
||||
if (this.throttle) {
|
||||
this._strokeMoveUpdate = throttle(SignaturePad.prototype._strokeUpdate, this.throttle);
|
||||
} else {
|
||||
this._strokeMoveUpdate = SignaturePad.prototype._strokeUpdate;
|
||||
}
|
||||
|
||||
this.dotSize = opts.dotSize || function () {
|
||||
return (this.minWidth + this.maxWidth) / 2;
|
||||
};
|
||||
this.penColor = opts.penColor || 'black';
|
||||
this.backgroundColor = opts.backgroundColor || 'rgba(0,0,0,0)';
|
||||
this.onBegin = opts.onBegin;
|
||||
this.onEnd = opts.onEnd;
|
||||
|
||||
this._canvas = canvas;
|
||||
this._ctx = canvas.getContext('2d');
|
||||
this.clear();
|
||||
|
||||
// We need add these inline so they are available to unbind while still having
|
||||
// access to 'self' we could use _.bind but it's not worth adding a dependency.
|
||||
this._handleMouseDown = function (event) {
|
||||
if (event.which === 1) {
|
||||
self._mouseButtonDown = true;
|
||||
self._strokeBegin(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleMouseMove = function (event) {
|
||||
if (self._mouseButtonDown) {
|
||||
self._strokeMoveUpdate(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleMouseUp = function (event) {
|
||||
if (event.which === 1 && self._mouseButtonDown) {
|
||||
self._mouseButtonDown = false;
|
||||
self._strokeEnd(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleTouchStart = function (event) {
|
||||
// Prevent scrolling.
|
||||
event.preventDefault();
|
||||
|
||||
if (event.targetTouches.length === 1) {
|
||||
var touch = event.changedTouches[0];
|
||||
self._strokeBegin(touch);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleTouchMove = function (event) {
|
||||
// Prevent scrolling.
|
||||
event.preventDefault();
|
||||
|
||||
var touch = event.targetTouches[0];
|
||||
self._strokeMoveUpdate(touch);
|
||||
};
|
||||
|
||||
this._handleTouchEnd = function (event) {
|
||||
var wasCanvasTouched = event.target === self._canvas;
|
||||
if (wasCanvasTouched) {
|
||||
event.preventDefault();
|
||||
self._strokeEnd(event);
|
||||
}
|
||||
};
|
||||
|
||||
// Enable mouse and touch event handlers
|
||||
this.on();
|
||||
}
|
||||
|
||||
// Public methods
|
||||
SignaturePad.prototype.clear = function () {
|
||||
var ctx = this._ctx;
|
||||
var canvas = this._canvas;
|
||||
|
||||
ctx.fillStyle = this.backgroundColor;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
this._data = [];
|
||||
this._reset();
|
||||
this._isEmpty = true;
|
||||
};
|
||||
|
||||
SignaturePad.prototype.fromDataURL = function (dataUrl) {
|
||||
var _this = this;
|
||||
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
|
||||
var image = new Image();
|
||||
var ratio = options.ratio || window.devicePixelRatio || 1;
|
||||
var width = options.width || this._canvas.width / ratio;
|
||||
var height = options.height || this._canvas.height / ratio;
|
||||
|
||||
this._reset();
|
||||
image.src = dataUrl;
|
||||
image.onload = function () {
|
||||
_this._ctx.drawImage(image, 0, 0, width, height);
|
||||
};
|
||||
this._isEmpty = false;
|
||||
};
|
||||
|
||||
SignaturePad.prototype.toDataURL = function (type) {
|
||||
var _canvas;
|
||||
|
||||
switch (type) {
|
||||
case 'image/svg+xml':
|
||||
return this._toSVG();
|
||||
default:
|
||||
for (var _len = arguments.length, options = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
||||
options[_key - 1] = arguments[_key];
|
||||
}
|
||||
|
||||
return (_canvas = this._canvas).toDataURL.apply(_canvas, [type].concat(options));
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype.on = function () {
|
||||
this._handleMouseEvents();
|
||||
this._handleTouchEvents();
|
||||
};
|
||||
|
||||
SignaturePad.prototype.off = function () {
|
||||
// Pass touch events to canvas element on mobile IE11 and Edge.
|
||||
this._canvas.style.msTouchAction = 'auto';
|
||||
this._canvas.style.touchAction = 'auto';
|
||||
|
||||
this._canvas.removeEventListener('mousedown', this._handleMouseDown);
|
||||
this._canvas.removeEventListener('mousemove', this._handleMouseMove);
|
||||
document.removeEventListener('mouseup', this._handleMouseUp);
|
||||
|
||||
this._canvas.removeEventListener('touchstart', this._handleTouchStart);
|
||||
this._canvas.removeEventListener('touchmove', this._handleTouchMove);
|
||||
this._canvas.removeEventListener('touchend', this._handleTouchEnd);
|
||||
};
|
||||
|
||||
SignaturePad.prototype.isEmpty = function () {
|
||||
return this._isEmpty;
|
||||
};
|
||||
|
||||
// Private methods
|
||||
SignaturePad.prototype._strokeBegin = function (event) {
|
||||
this._data.push([]);
|
||||
this._reset();
|
||||
this._strokeUpdate(event);
|
||||
|
||||
if (typeof this.onBegin === 'function') {
|
||||
this.onBegin(event);
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeUpdate = function (event) {
|
||||
var x = event.clientX;
|
||||
var y = event.clientY;
|
||||
|
||||
var point = this._createPoint(x, y);
|
||||
var lastPointGroup = this._data[this._data.length - 1];
|
||||
var lastPoint = lastPointGroup && lastPointGroup[lastPointGroup.length - 1];
|
||||
var isLastPointTooClose = lastPoint && point.distanceTo(lastPoint) < this.minDistance;
|
||||
|
||||
// Skip this point if it's too close to the previous one
|
||||
if (!(lastPoint && isLastPointTooClose)) {
|
||||
var _addPoint = this._addPoint(point),
|
||||
curve = _addPoint.curve,
|
||||
widths = _addPoint.widths;
|
||||
|
||||
if (curve && widths) {
|
||||
this._drawCurve(curve, widths.start, widths.end);
|
||||
}
|
||||
|
||||
this._data[this._data.length - 1].push({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
time: point.time,
|
||||
color: this.penColor
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeEnd = function (event) {
|
||||
var canDrawCurve = this.points.length > 2;
|
||||
var point = this.points[0]; // Point instance
|
||||
|
||||
if (!canDrawCurve && point) {
|
||||
this._drawDot(point);
|
||||
}
|
||||
|
||||
if (point) {
|
||||
var lastPointGroup = this._data[this._data.length - 1];
|
||||
var lastPoint = lastPointGroup[lastPointGroup.length - 1]; // plain object
|
||||
|
||||
// When drawing a dot, there's only one point in a group, so without this check
|
||||
// such group would end up with exactly the same 2 points.
|
||||
if (!point.equals(lastPoint)) {
|
||||
lastPointGroup.push({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
time: point.time,
|
||||
color: this.penColor
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this.onEnd === 'function') {
|
||||
this.onEnd(event);
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._handleMouseEvents = function () {
|
||||
this._mouseButtonDown = false;
|
||||
|
||||
this._canvas.addEventListener('mousedown', this._handleMouseDown);
|
||||
this._canvas.addEventListener('mousemove', this._handleMouseMove);
|
||||
document.addEventListener('mouseup', this._handleMouseUp);
|
||||
};
|
||||
|
||||
SignaturePad.prototype._handleTouchEvents = function () {
|
||||
// Pass touch events to canvas element on mobile IE11 and Edge.
|
||||
this._canvas.style.msTouchAction = 'none';
|
||||
this._canvas.style.touchAction = 'none';
|
||||
|
||||
this._canvas.addEventListener('touchstart', this._handleTouchStart);
|
||||
this._canvas.addEventListener('touchmove', this._handleTouchMove);
|
||||
this._canvas.addEventListener('touchend', this._handleTouchEnd);
|
||||
};
|
||||
|
||||
SignaturePad.prototype._reset = function () {
|
||||
this.points = [];
|
||||
this._lastVelocity = 0;
|
||||
this._lastWidth = (this.minWidth + this.maxWidth) / 2;
|
||||
this._ctx.fillStyle = this.penColor;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._createPoint = function (x, y, time) {
|
||||
var rect = this._canvas.getBoundingClientRect();
|
||||
|
||||
return new Point(x - rect.left, y - rect.top, time || new Date().getTime());
|
||||
};
|
||||
|
||||
SignaturePad.prototype._addPoint = function (point) {
|
||||
var points = this.points;
|
||||
var tmp = void 0;
|
||||
|
||||
points.push(point);
|
||||
|
||||
if (points.length > 2) {
|
||||
// To reduce the initial lag make it work with 3 points
|
||||
// by copying the first point to the beginning.
|
||||
if (points.length === 3) points.unshift(points[0]);
|
||||
|
||||
tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]);
|
||||
var c2 = tmp.c2;
|
||||
tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]);
|
||||
var c3 = tmp.c1;
|
||||
var curve = new Bezier(points[1], c2, c3, points[2]);
|
||||
var widths = this._calculateCurveWidths(curve);
|
||||
|
||||
// Remove the first element from the list,
|
||||
// so that we always have no more than 4 points in points array.
|
||||
points.shift();
|
||||
|
||||
return { curve: curve, widths: widths };
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
SignaturePad.prototype._calculateCurveControlPoints = function (s1, s2, s3) {
|
||||
var dx1 = s1.x - s2.x;
|
||||
var dy1 = s1.y - s2.y;
|
||||
var dx2 = s2.x - s3.x;
|
||||
var dy2 = s2.y - s3.y;
|
||||
|
||||
var m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
|
||||
var m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
|
||||
|
||||
var l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
||||
var l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
||||
|
||||
var dxm = m1.x - m2.x;
|
||||
var dym = m1.y - m2.y;
|
||||
|
||||
var k = l2 / (l1 + l2);
|
||||
var cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
|
||||
|
||||
var tx = s2.x - cm.x;
|
||||
var ty = s2.y - cm.y;
|
||||
|
||||
return {
|
||||
c1: new Point(m1.x + tx, m1.y + ty),
|
||||
c2: new Point(m2.x + tx, m2.y + ty)
|
||||
};
|
||||
};
|
||||
|
||||
SignaturePad.prototype._calculateCurveWidths = function (curve) {
|
||||
var startPoint = curve.startPoint;
|
||||
var endPoint = curve.endPoint;
|
||||
var widths = { start: null, end: null };
|
||||
|
||||
var velocity = this.velocityFilterWeight * endPoint.velocityFrom(startPoint) + (1 - this.velocityFilterWeight) * this._lastVelocity;
|
||||
|
||||
var newWidth = this._strokeWidth(velocity);
|
||||
|
||||
widths.start = this._lastWidth;
|
||||
widths.end = newWidth;
|
||||
|
||||
this._lastVelocity = velocity;
|
||||
this._lastWidth = newWidth;
|
||||
|
||||
return widths;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeWidth = function (velocity) {
|
||||
return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
|
||||
};
|
||||
|
||||
SignaturePad.prototype._drawPoint = function (x, y, size) {
|
||||
var ctx = this._ctx;
|
||||
|
||||
ctx.moveTo(x, y);
|
||||
ctx.arc(x, y, size, 0, 2 * Math.PI, false);
|
||||
this._isEmpty = false;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._drawCurve = function (curve, startWidth, endWidth) {
|
||||
var ctx = this._ctx;
|
||||
var widthDelta = endWidth - startWidth;
|
||||
var drawSteps = Math.floor(curve.length());
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
for (var i = 0; i < drawSteps; i += 1) {
|
||||
// Calculate the Bezier (x, y) coordinate for this step.
|
||||
var t = i / drawSteps;
|
||||
var tt = t * t;
|
||||
var ttt = tt * t;
|
||||
var u = 1 - t;
|
||||
var uu = u * u;
|
||||
var uuu = uu * u;
|
||||
|
||||
var x = uuu * curve.startPoint.x;
|
||||
x += 3 * uu * t * curve.control1.x;
|
||||
x += 3 * u * tt * curve.control2.x;
|
||||
x += ttt * curve.endPoint.x;
|
||||
|
||||
var y = uuu * curve.startPoint.y;
|
||||
y += 3 * uu * t * curve.control1.y;
|
||||
y += 3 * u * tt * curve.control2.y;
|
||||
y += ttt * curve.endPoint.y;
|
||||
|
||||
var width = startWidth + ttt * widthDelta;
|
||||
this._drawPoint(x, y, width);
|
||||
}
|
||||
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
SignaturePad.prototype._drawDot = function (point) {
|
||||
var ctx = this._ctx;
|
||||
var width = typeof this.dotSize === 'function' ? this.dotSize() : this.dotSize;
|
||||
|
||||
ctx.beginPath();
|
||||
this._drawPoint(point.x, point.y, width);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
SignaturePad.prototype._fromData = function (pointGroups, drawCurve, drawDot) {
|
||||
for (var i = 0; i < pointGroups.length; i += 1) {
|
||||
var group = pointGroups[i];
|
||||
|
||||
if (group.length > 1) {
|
||||
for (var j = 0; j < group.length; j += 1) {
|
||||
var rawPoint = group[j];
|
||||
var point = new Point(rawPoint.x, rawPoint.y, rawPoint.time);
|
||||
var color = rawPoint.color;
|
||||
|
||||
if (j === 0) {
|
||||
// First point in a group. Nothing to draw yet.
|
||||
|
||||
// All points in the group have the same color, so it's enough to set
|
||||
// penColor just at the beginning.
|
||||
this.penColor = color;
|
||||
this._reset();
|
||||
|
||||
this._addPoint(point);
|
||||
} else if (j !== group.length - 1) {
|
||||
// Middle point in a group.
|
||||
var _addPoint2 = this._addPoint(point),
|
||||
curve = _addPoint2.curve,
|
||||
widths = _addPoint2.widths;
|
||||
|
||||
if (curve && widths) {
|
||||
drawCurve(curve, widths, color);
|
||||
}
|
||||
} else {
|
||||
// Last point in a group. Do nothing.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._reset();
|
||||
var _rawPoint = group[0];
|
||||
drawDot(_rawPoint);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._toSVG = function () {
|
||||
var _this2 = this;
|
||||
|
||||
var pointGroups = this._data;
|
||||
var canvas = this._canvas;
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
var minX = 0;
|
||||
var minY = 0;
|
||||
var maxX = canvas.width / ratio;
|
||||
var maxY = canvas.height / ratio;
|
||||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
|
||||
svg.setAttributeNS(null, 'width', canvas.width);
|
||||
svg.setAttributeNS(null, 'height', canvas.height);
|
||||
|
||||
this._fromData(pointGroups, function (curve, widths, color) {
|
||||
var path = document.createElement('path');
|
||||
|
||||
// Need to check curve for NaN values, these pop up when drawing
|
||||
// lines on the canvas that are not continuous. E.g. Sharp corners
|
||||
// or stopping mid-stroke and than continuing without lifting mouse.
|
||||
if (!isNaN(curve.control1.x) && !isNaN(curve.control1.y) && !isNaN(curve.control2.x) && !isNaN(curve.control2.y)) {
|
||||
var attr = 'M ' + curve.startPoint.x.toFixed(3) + ',' + curve.startPoint.y.toFixed(3) + ' ' + ('C ' + curve.control1.x.toFixed(3) + ',' + curve.control1.y.toFixed(3) + ' ') + (curve.control2.x.toFixed(3) + ',' + curve.control2.y.toFixed(3) + ' ') + (curve.endPoint.x.toFixed(3) + ',' + curve.endPoint.y.toFixed(3));
|
||||
|
||||
path.setAttribute('d', attr);
|
||||
path.setAttribute('stroke-width', (widths.end * 2.25).toFixed(3));
|
||||
path.setAttribute('stroke', color);
|
||||
path.setAttribute('fill', 'none');
|
||||
path.setAttribute('stroke-linecap', 'round');
|
||||
|
||||
svg.appendChild(path);
|
||||
}
|
||||
}, function (rawPoint) {
|
||||
var circle = document.createElement('circle');
|
||||
var dotSize = typeof _this2.dotSize === 'function' ? _this2.dotSize() : _this2.dotSize;
|
||||
circle.setAttribute('r', dotSize);
|
||||
circle.setAttribute('cx', rawPoint.x);
|
||||
circle.setAttribute('cy', rawPoint.y);
|
||||
circle.setAttribute('fill', rawPoint.color);
|
||||
|
||||
svg.appendChild(circle);
|
||||
});
|
||||
|
||||
var prefix = 'data:image/svg+xml;base64,';
|
||||
var header = '<svg' + ' xmlns="http://www.w3.org/2000/svg"' + ' xmlns:xlink="http://www.w3.org/1999/xlink"' + (' viewBox="' + minX + ' ' + minY + ' ' + maxX + ' ' + maxY + '"') + (' width="' + maxX + '"') + (' height="' + maxY + '"') + '>';
|
||||
var body = svg.innerHTML;
|
||||
|
||||
// IE hack for missing innerHTML property on SVGElement
|
||||
if (body === undefined) {
|
||||
var dummy = document.createElement('dummy');
|
||||
var nodes = svg.childNodes;
|
||||
dummy.innerHTML = '';
|
||||
|
||||
for (var i = 0; i < nodes.length; i += 1) {
|
||||
dummy.appendChild(nodes[i].cloneNode(true));
|
||||
}
|
||||
|
||||
body = dummy.innerHTML;
|
||||
}
|
||||
|
||||
var footer = '</svg>';
|
||||
var data = header + body + footer;
|
||||
|
||||
return prefix + btoa(data);
|
||||
};
|
||||
|
||||
SignaturePad.prototype.fromData = function (pointGroups) {
|
||||
var _this3 = this;
|
||||
|
||||
this.clear();
|
||||
|
||||
this._fromData(pointGroups, function (curve, widths) {
|
||||
return _this3._drawCurve(curve, widths.start, widths.end);
|
||||
}, function (rawPoint) {
|
||||
return _this3._drawDot(rawPoint);
|
||||
});
|
||||
|
||||
this._data = pointGroups;
|
||||
};
|
||||
|
||||
SignaturePad.prototype.toData = function () {
|
||||
return this._data;
|
||||
};
|
||||
|
||||
return SignaturePad;
|
||||
|
||||
})));
|
||||
@@ -1,461 +0,0 @@
|
||||
/**
|
||||
* Grav Form XHR Submitter
|
||||
*
|
||||
* A modular system for handling form submissions via XMLHttpRequest (AJAX).
|
||||
* Features include content replacement, captcha handling, and error management.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Main namespace
|
||||
window.GravFormXHR = {};
|
||||
|
||||
/**
|
||||
* Core Module - Contains configuration and utility functions
|
||||
*/
|
||||
const Core = {
|
||||
config: {
|
||||
debug: false,
|
||||
enableLoadingIndicator: false
|
||||
},
|
||||
|
||||
/**
|
||||
* Configure global settings
|
||||
* @param {Object} options - Configuration options
|
||||
*/
|
||||
configure: function(options) {
|
||||
Object.assign(this.config, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Logger utility
|
||||
* @param {string} message - Message to log
|
||||
* @param {string} level - Log level ('log', 'warn', 'error')
|
||||
*/
|
||||
log: function(message, level = 'log') {
|
||||
if (!this.config.debug) return;
|
||||
|
||||
const validLevels = ['log', 'warn', 'error'];
|
||||
const finalLevel = validLevels.includes(level) ? level : 'log';
|
||||
|
||||
console[finalLevel](`[GravFormXHR] ${message}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Display an error message within a target element
|
||||
* @param {HTMLElement} target - The element to display the error in
|
||||
* @param {string} message - The error message
|
||||
*/
|
||||
displayError: function(target, message) {
|
||||
const errorMsgContainer = target.querySelector('.form-messages') || target;
|
||||
const errorMsg = document.createElement('div');
|
||||
errorMsg.className = 'form-message error';
|
||||
errorMsg.textContent = message;
|
||||
errorMsgContainer.insertBefore(errorMsg, errorMsgContainer.firstChild);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DOM Module - Handles DOM manipulation and form tracking
|
||||
*/
|
||||
const DOM = {
|
||||
/**
|
||||
* Find a form wrapper by formId
|
||||
* @param {string} formId - ID of the form
|
||||
* @returns {HTMLElement|null} - The wrapper element or null
|
||||
*/
|
||||
getFormWrapper: function(formId) {
|
||||
const wrapperId = formId + '-wrapper';
|
||||
return document.getElementById(wrapperId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add or remove loading indicators
|
||||
* @param {HTMLElement} form - The form element
|
||||
* @param {HTMLElement} wrapper - The wrapper element
|
||||
* @param {boolean} isLoading - Whether to add or remove loading classes
|
||||
*/
|
||||
updateLoadingState: function(form, wrapper, isLoading) {
|
||||
if (!Core.config.enableLoadingIndicator) return;
|
||||
|
||||
if (isLoading) {
|
||||
wrapper.classList.add('loading');
|
||||
form.classList.add('submitting');
|
||||
} else {
|
||||
wrapper.classList.remove('loading');
|
||||
form.classList.remove('submitting');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update form content with server response
|
||||
* @param {string} responseText - Server response HTML
|
||||
* @param {string} wrapperId - ID of the wrapper to update
|
||||
* @param {string} formId - ID of the original form
|
||||
*/
|
||||
updateFormContent: function(responseText, wrapperId, formId) {
|
||||
const wrapperElement = document.getElementById(wrapperId);
|
||||
if (!wrapperElement) {
|
||||
console.error(`Cannot update content: Wrapper #${wrapperId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
Core.log(`Updating content for wrapper: ${wrapperId}`);
|
||||
|
||||
// Parse response
|
||||
const tempDiv = document.createElement('div');
|
||||
try {
|
||||
tempDiv.innerHTML = responseText;
|
||||
} catch (e) {
|
||||
console.error(`Error parsing response HTML for wrapper: ${wrapperId}`, e);
|
||||
Core.displayError(wrapperElement, 'An error occurred processing the server response.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._updateWrapperContent(tempDiv, wrapperElement, wrapperId, formId);
|
||||
this._reinitializeUpdatedForm(wrapperElement, formId);
|
||||
} catch (e) {
|
||||
console.error(`Error during content update for wrapper ${wrapperId}:`, e);
|
||||
Core.displayError(wrapperElement, 'An error occurred updating the form content.');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update wrapper content based on response parsing strategy
|
||||
* @private
|
||||
*/
|
||||
_updateWrapperContent: function(tempDiv, wrapperElement, wrapperId, formId) {
|
||||
// Strategy 1: Look for matching wrapper ID in response
|
||||
const newWrapperElement = tempDiv.querySelector('#' + wrapperId);
|
||||
|
||||
if (newWrapperElement) {
|
||||
wrapperElement.innerHTML = newWrapperElement.innerHTML;
|
||||
Core.log(`Update using newWrapperElement.innerHTML SUCCESSFUL for wrapper: ${wrapperId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Strategy 2: Look for matching form ID in response
|
||||
const hasMatchingForm = tempDiv.querySelector('#' + formId);
|
||||
|
||||
if (hasMatchingForm) {
|
||||
Core.log(`Wrapper element #${wrapperId} not found in XHR response, but found matching form. Using entire response.`);
|
||||
wrapperElement.innerHTML = tempDiv.innerHTML;
|
||||
return;
|
||||
}
|
||||
|
||||
// Strategy 3: Look for toast messages
|
||||
const hasToastMessages = tempDiv.querySelector('.toast');
|
||||
|
||||
if (hasToastMessages) {
|
||||
Core.log('Found toast messages in response. Updating wrapper with the response.');
|
||||
wrapperElement.innerHTML = tempDiv.innerHTML;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: Use entire response with warning
|
||||
Core.log('No matching content found in response. Response may not be valid for this wrapper.', 'warn');
|
||||
wrapperElement.innerHTML = tempDiv.innerHTML;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reinitialize updated form and its components
|
||||
* @private
|
||||
*/
|
||||
_reinitializeUpdatedForm: function(wrapperElement, formId) {
|
||||
const updatedForm = wrapperElement.querySelector('#' + formId);
|
||||
|
||||
if (updatedForm) {
|
||||
Core.log(`Re-running initialization for form ${formId} after update`);
|
||||
|
||||
// First reinitialize any captchas
|
||||
CaptchaManager.reinitializeAll(updatedForm);
|
||||
|
||||
// Trigger mutation._grav event for Dropzone and other field reinitializations
|
||||
setTimeout(() => {
|
||||
Core.log('Triggering mutation._grav event for field reinitialization');
|
||||
|
||||
// Trigger using jQuery if available (preferred method for compatibility)
|
||||
if (typeof jQuery !== 'undefined') {
|
||||
jQuery('body').trigger('mutation._grav', [wrapperElement]);
|
||||
} else {
|
||||
// Fallback: dispatch native custom event
|
||||
const event = new CustomEvent('mutation._grav', {
|
||||
detail: { target: wrapperElement },
|
||||
bubbles: true
|
||||
});
|
||||
document.body.dispatchEvent(event);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
// Then re-attach the XHR listener
|
||||
setTimeout(() => {
|
||||
FormHandler.setupListener(formId);
|
||||
}, 10);
|
||||
} else {
|
||||
// Check if this was a successful submission with just a message
|
||||
const hasSuccessMessage = wrapperElement.querySelector('.toast-success, .form-success');
|
||||
|
||||
if (hasSuccessMessage) {
|
||||
Core.log('No form found after update, but success message detected. This appears to be a successful submission.');
|
||||
} else {
|
||||
console.warn(`Could not find form #${formId} inside the updated wrapper after update. Cannot re-attach listener/initializers.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* XHR Module - Handles XMLHttpRequest operations
|
||||
*/
|
||||
const XHRManager = {
|
||||
/**
|
||||
* Send form data via XHR
|
||||
* @param {HTMLFormElement} form - The form to submit
|
||||
*/
|
||||
sendFormData: function(form) {
|
||||
const formId = form.id;
|
||||
const wrapperId = formId + '-wrapper';
|
||||
const wrapperElement = DOM.getFormWrapper(formId);
|
||||
|
||||
if (!wrapperElement) {
|
||||
console.error(`XHR submission: Target wrapper element #${wrapperId} not found on the page! Cannot proceed.`);
|
||||
form.innerHTML = '<p class="form-message error">Error: Form wrapper missing. Cannot update content.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
Core.log(`Initiating XHR submission for form: ${formId}, targeting wrapper: ${wrapperId}`);
|
||||
DOM.updateLoadingState(form, wrapperElement, true);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(form.getAttribute('method') || 'POST', form.getAttribute('action') || window.location.href);
|
||||
|
||||
// Set Headers
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||
xhr.setRequestHeader('X-Grav-Form-XHR', 'true');
|
||||
|
||||
// Success handler
|
||||
xhr.onload = () => {
|
||||
Core.log(`XHR request completed for form: ${formId}, Status: ${xhr.status}`);
|
||||
DOM.updateLoadingState(form, wrapperElement, false);
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
DOM.updateFormContent(xhr.responseText, wrapperId, formId);
|
||||
} else {
|
||||
Core.log(`Form submission failed for form: ${formId}, HTTP Status: ${xhr.status} ${xhr.statusText}`, 'error');
|
||||
Core.displayError(wrapperElement, `An error occurred during submission (Status: ${xhr.status}). Please check the form and try again.`);
|
||||
}
|
||||
};
|
||||
|
||||
// Network error handler
|
||||
xhr.onerror = () => {
|
||||
Core.log(`Form submission failed due to network error for form: ${formId}`, 'error');
|
||||
DOM.updateLoadingState(form, wrapperElement, false);
|
||||
Core.displayError(wrapperElement, 'A network error occurred. Please check your connection and try again.');
|
||||
};
|
||||
|
||||
// Prepare and send data
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const urlEncodedData = new URLSearchParams(formData).toString();
|
||||
Core.log(`Sending XHR request for form: ${formId} with custom header X-Grav-Form-XHR`);
|
||||
xhr.send(urlEncodedData);
|
||||
} catch (e) {
|
||||
Core.log(`Error preparing or sending XHR request for form: ${formId}: ${e.message}`, 'error');
|
||||
DOM.updateLoadingState(form, wrapperElement, false);
|
||||
Core.displayError(wrapperElement, 'An unexpected error occurred before sending the form.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CaptchaManager - Handles captcha registration and initialization
|
||||
*/
|
||||
const CaptchaManager = {
|
||||
providers: {},
|
||||
|
||||
/**
|
||||
* Register a captcha provider
|
||||
* @param {string} name - Provider name
|
||||
* @param {object} provider - Provider object with init and reset methods
|
||||
*/
|
||||
register: function(name, provider) {
|
||||
this.providers[name] = provider;
|
||||
Core.log(`Registered captcha provider: ${name}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a provider by name
|
||||
* @param {string} name - Provider name
|
||||
* @returns {object|null} Provider object or null if not found
|
||||
*/
|
||||
getProvider: function(name) {
|
||||
return this.providers[name] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all registered providers
|
||||
* @returns {object} Object containing all providers
|
||||
*/
|
||||
getProviders: function() {
|
||||
return this.providers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reinitialize all captchas in a form
|
||||
* @param {HTMLFormElement} form - Form element containing captchas
|
||||
*/
|
||||
reinitializeAll: function(form) {
|
||||
if (!form || !form.id) return;
|
||||
|
||||
const formId = form.id;
|
||||
const containers = form.querySelectorAll('[data-captcha-provider]');
|
||||
|
||||
containers.forEach(container => {
|
||||
const providerName = container.dataset.captchaProvider;
|
||||
Core.log(`Found captcha container for provider: ${providerName} in form: ${formId}`);
|
||||
|
||||
const provider = this.getProvider(providerName);
|
||||
if (provider && typeof provider.reset === 'function') {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
provider.reset(container, form);
|
||||
Core.log(`Successfully reset ${providerName} captcha in form: ${formId}`);
|
||||
} catch (e) {
|
||||
console.error(`Error resetting ${providerName} captcha:`, e);
|
||||
}
|
||||
}, 0);
|
||||
} else {
|
||||
console.warn(`Could not reset captcha provider "${providerName}" - provider not registered or missing reset method`);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* FormHandler - Handles form submission and event listeners
|
||||
*/
|
||||
const FormHandler = {
|
||||
/**
|
||||
* Submit a form via XHR
|
||||
* @param {HTMLFormElement} form - Form to submit
|
||||
*/
|
||||
submitForm: function(form) {
|
||||
if (!form || !form.id) {
|
||||
console.error('submitForm called with invalid form element or form missing ID.');
|
||||
return;
|
||||
}
|
||||
|
||||
XHRManager.sendFormData(form);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set up XHR submission listener for a form
|
||||
* @param {string} formId - ID of the form
|
||||
*/
|
||||
setupListener: function(formId) {
|
||||
setTimeout(() => {
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) {
|
||||
Core.log(`XHR Setup (delayed): Form with ID "${formId}" not found.`, 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove stale marker from previous runs
|
||||
delete form.dataset.directXhrListenerAttached;
|
||||
|
||||
// Check if any captcha provider is handling the submission
|
||||
const captchaContainer = form.querySelector('[data-captcha-provider][data-intercepts-submit="true"]');
|
||||
|
||||
if (!captchaContainer) {
|
||||
// No intercepting captcha found, attach direct listener
|
||||
this._attachDirectListener(form);
|
||||
} else {
|
||||
// Captcha will intercept, don't attach direct listener
|
||||
const providerName = captchaContainer.dataset.captchaProvider;
|
||||
Core.log(`XHR listener deferred: ${providerName} should intercept submit for form: ${formId}`);
|
||||
// Ensure no stale listener marker remains
|
||||
delete form.dataset.directXhrListenerAttached;
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach a direct submit event listener to a form
|
||||
* @private
|
||||
* @param {HTMLFormElement} form - Form element
|
||||
*/
|
||||
_attachDirectListener: function(form) {
|
||||
// Only proceed if XHR is enabled for this form
|
||||
if (form.dataset.xhrEnabled !== 'true') {
|
||||
Core.log(`XHR not enabled for form: ${form.id}. Skipping direct listener attachment.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we already attached a listener
|
||||
if (form.dataset.directXhrListenerAttached === 'true') {
|
||||
Core.log(`Direct XHR listener already attached for form: ${form.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const directXhrSubmitHandler = (event) => {
|
||||
Core.log(`Direct XHR submit handler triggered for form: ${form.id}`);
|
||||
event.preventDefault();
|
||||
FormHandler.submitForm(form);
|
||||
};
|
||||
|
||||
Core.log(`Attaching direct XHR listener for form: ${form.id}`);
|
||||
form.addEventListener('submit', directXhrSubmitHandler);
|
||||
form.dataset.directXhrListenerAttached = 'true';
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize basic built-in captcha handlers
|
||||
// Other providers will register themselves via separate handler JS files
|
||||
const initializeBasicCaptchaHandlers = function() {
|
||||
// Basic captcha handler (image refresh etc.)
|
||||
CaptchaManager.register('basic-captcha', {
|
||||
reset: function(container, form) {
|
||||
const formId = form.id;
|
||||
const captchaImg = container.querySelector('img');
|
||||
const captchaInput = container.querySelector('input[type="text"]');
|
||||
|
||||
if (captchaImg) {
|
||||
// Add a timestamp to force image reload
|
||||
const timestamp = new Date().getTime();
|
||||
const imgSrc = captchaImg.src.split('?')[0] + '?t=' + timestamp;
|
||||
captchaImg.src = imgSrc;
|
||||
|
||||
// Clear any existing input
|
||||
if (captchaInput) {
|
||||
captchaInput.value = '';
|
||||
}
|
||||
|
||||
Core.log(`Reset basic-captcha for form: ${formId}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Initialize basic captcha handlers
|
||||
initializeBasicCaptchaHandlers();
|
||||
|
||||
// --- Expose Public API ---
|
||||
|
||||
// Core configuration
|
||||
window.GravFormXHR.configure = Core.configure.bind(Core);
|
||||
|
||||
// Form submission
|
||||
window.GravFormXHR.submit = FormHandler.submitForm.bind(FormHandler);
|
||||
window.GravFormXHR.setupListener = FormHandler.setupListener.bind(FormHandler);
|
||||
|
||||
// Captcha management
|
||||
window.GravFormXHR.captcha = CaptchaManager;
|
||||
|
||||
// Legacy support
|
||||
window.GravFormXHRSubmitters = {submit: FormHandler.submitForm.bind(FormHandler)};
|
||||
window.attachFormSubmitListener = FormHandler.setupListener.bind(FormHandler);
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user