https://mercijeannot.com/wp-content/plugins/listeo-core/assets/js/submit-listing.steps.js?ver=1.9.40
1document.addEventListener("DOMContentLoaded", function () { 2 3 function showLoader() { 4 const loader = document.createElement("div"); 5 loader.className = "msf-loader-overlay"; 6 7 loader.innerHTML = ` 8 <svg class="msf-loader-spinner" viewBox="0 0 50 50"> 9 <circle cx="25" cy="25" r="20"></circle> 10 </svg> 11 `; 12 13 document.body.appendChild(loader); 14 } 15 16 17 18 const form = document.querySelector(".listing-manager-form"); 19 if (!form) return; 20 21 const allSections = Array.from(form.querySelectorAll(".add-listing-section")); 22 23const submitPageDiv = document.querySelector(".submit-page"); 24 25if (!submitPageDiv || !submitPageDiv.classList.contains("multi-step-form")) { 26 return; // or throw new Error('Required class not found'); 27} 28 29 30 const submitButton = form.querySelector('button[name="submit_listing"]'); 31 32 if (!submitButton || allSections.length <= 1) { 33 return; 34 } 35 36 // --- Start of Your Custom Configuration --- 37 let stepConfiguration = []; 38 39 try { 40 const rawStepsEncoded = document.getElementById( 41 "listeo_form_steps_json" 42 )?.value; 43 44 if (rawStepsEncoded) { 45 const textarea = document.createElement("textarea"); 46 textarea.innerHTML = rawStepsEncoded; 47 const rawSteps = textarea.value; 48 49 stepConfiguration = JSON.parse(rawSteps); 50 } 51 } catch (e) { 52 console.error("Failed to parse step configuration", e); 53 } 54 55 // if stepConfiguration is empty or not an array, disable multi-step 56 if (!Array.isArray(stepConfiguration) || stepConfiguration.length === 0) { 57 return; 58 } 59 60 allSections.forEach((section) => { 61 section.removeAttribute("style"); 62 }); 63 // const stepConfiguration = [ 64 // { 65 // title: "Listing Essentials", 66 // selectors: [".basic_info", ".location", ".gallery", ".details"], 67 // }, 68 // { 69 // title: "Offerings & Schedule", 70 // selectors: [ 71 // ".opening_hours", 72 // ".menu", 73 // ".booking", 74 // ".slots", 75 // ".basic_prices", 76 // ".availability_calendar", 77 // ], 78 // }, 79 // { 80 // title: "Final Details", 81 // selectors: [".faq", ".my_listings_section"], 82 // }, 83 // ]; 84 // --- End of Your Custom Configuration --- 85 86 let currentStep = 0; 87 let highestStepReached = 0; 88 const steps = []; 89 90 // Reworked Step Creation Logic 91 stepConfiguration.forEach((groupInfo) => { 92 const groupSections = []; 93 groupInfo.selectors.forEach((selector) => { 94 const foundSections = form.querySelectorAll(selector); 95 foundSections.forEach((section) => { 96 // Ensure we only add sections that actually exist in the form 97 if (allSections.includes(section)) { 98 groupSections.push(section); 99 } 100 }); 101 }); 102 103 // Also check for custom fields sections using both ID and class selectors 104 groupInfo.selectors.forEach((selector) => { 105 if (selector.startsWith('#custom-fields-') || selector === '.custom-term-features') { 106 // For ID selectors, look for specific custom section 107 if (selector.startsWith('#custom-fields-')) { 108 const customSection = document.querySelector(selector); 109 if (customSection && !groupSections.includes(customSection)) { 110 groupSections.push(customSection); 111 } 112 } 113 // For class selector, get all custom sections 114 else if (selector === '.custom-term-features') { 115 const customSections = document.querySelectorAll(selector); 116 customSections.forEach((customSection) => { 117 if (!groupSections.includes(customSection)) { 118 groupSections.push(customSection); 119 } 120 }); 121 } 122 } 123 }); 124 125 if (groupSections.length > 0) { 126 steps.push(groupSections); 127 } 128 }); 129 130 const progressContainer = document.createElement("div"); 131 progressContainer.className = "form-progress-container"; 132 const navContainer = document.createElement("div"); 133 navContainer.className = "form-navigation"; 134 const prevButton = document.createElement("button"); 135 prevButton.type = "button"; 136 prevButton.className = "btn-prev button"; 137 prevButton.textContent = listeo_core.prev; 138 const nextButton = document.createElement("button"); 139 nextButton.type = "button"; 140 nextButton.className = "btn-next button"; 141 nextButton.textContent = listeo_core.next; 142 form.insertBefore(progressContainer, form.firstChild); 143 const lastSection = allSections[allSections.length - 1]; 144 lastSection.parentNode.insertBefore(navContainer, lastSection.nextSibling); 145 navContainer.appendChild(prevButton); 146 navContainer.appendChild(nextButton); 147 navContainer.appendChild(submitButton); 148 if (submitButton.parentElement.nodeName === "P") { 149 submitButton.parentElement.style.display = "block"; 150 submitButton.parentElement.style.margin = "0"; 151 submitButton.parentElement.classList.add("form-navigation-submit"); 152 } 153 154 // Reworked Progress Bar Creation 155 steps.forEach((stepSections, index) => { 156 const stepElement = document.createElement("div"); 157 stepElement.className = "form-progress-step"; 158 const icon = document.createElement("div"); 159 icon.className = "progress-step-icon"; 160 const titleElement = document.createElement("div"); 161 titleElement.className = "progress-step-title"; 162 163 // Use the title from the new configuration 164 titleElement.textContent = 165 stepConfiguration[index].title || `Step ${index + 1}`; 166 167 stepElement.appendChild(icon); 168 stepElement.appendChild(titleElement); 169 progressContainer.appendChild(stepElement); 170 }); 171 const progressSteps = progressContainer.querySelectorAll( 172 ".form-progress-step" 173 ); 174 175 function smoothScrollToTop() { 176 window.scrollTo({ top: 0, behavior: "smooth" }); 177 } 178 179 function isFieldRequired(label) { 180 if (!label) return false;
181 const iTags = label.querySelectorAll("i"); 182 for (const iTag of iTags) { 183 if (iTag.textContent.trim() === "*") return true; 184 } 185 return false; 186 } 187 188 function validateStep(stepIndex) { 189 let isStepValid = true; 190 const sectionsToValidate = steps[stepIndex]; 191 for (const section of sectionsToValidate) { 192 const standardFields = section.querySelectorAll( 193 'input[required]:not([type="hidden"]), select[required], textarea[required]' 194 ); 195 for (const field of standardFields) { 196 if (!field.checkValidity()) { 197 isStepValid = false; 198 field.classList.add("msf-input-error"); 199 } else { 200 field.classList.remove("msf-input-error"); 201 } 202 } 203 const drilldownMenus = section.querySelectorAll("div.drilldown-menu"); 204 for (const menu of drilldownMenus) { 205 const name = menu.getAttribute("data-name"); 206 if (!name) continue; 207 const label = section.querySelector( 208 `label[for="${name}"], .label-${name}` 209 ); 210 if (!isFieldRequired(label)) continue; 211 const hasValue = 212 menu.querySelector("input.drilldown-generated") !== null; 213 const menuToggle = menu.querySelector(".menu-toggle"); 214 if (menuToggle) { 215 if (!hasValue) { 216 isStepValid = false; 217 menuToggle.classList.add("msf-input-error"); 218 } else { 219 menuToggle.classList.remove("msf-input-error"); 220 } 221 } 222 } 223 const galleryUploader = section.querySelector("#media-uploader"); 224 if ( 225 galleryUploader && 226 galleryUploader.getAttribute("data-required") === "required" 227 ) { 228 const hasImages = 229 galleryUploader.querySelector(".dz-image-preview") !== null; 230 const existingError = galleryUploader.nextElementSibling; 231 if (!hasImages) { 232 isStepValid = false; 233 if ( 234 !existingError || 235 !existingError.classList.contains("msf-gallery-error-message") 236 ) { 237 const errorDiv = document.querySelector( 238 ".msf-gallery-error-message" 239 ); 240 if (errorDiv) { 241 errorDiv.style.display = "block"; 242 } 243 } 244 } else { 245 if ( 246 existingError && 247 existingError.classList.contains("msf-gallery-error-message") 248 ) { 249 existingError.remove(); 250 } 251 } 252 } 253 if (typeof tinymce !== "undefined") { 254 const textareas = section.querySelectorAll("textarea"); 255 for (const textarea of textareas) { 256 const editorId = textarea.id; 257 if (!editorId) continue; 258 const editor = tinymce.get(editorId); 259 if (!editor) continue; 260 const label = section.querySelector(`label[for="${editorId}"]`); 261 if (!isFieldRequired(label)) continue; 262 const content = editor.getContent({ format: "text" }).trim(); 263 const editorContainer = editor.getContainer(); 264 if (content === "") { 265 isStepValid = false; 266 if (editorContainer) 267 editorContainer.classList.add("msf-input-error"); 268 } else { 269 if (editorContainer) 270 editorContainer.classList.remove("msf-input-error"); 271 } 272 } 273 } 274 const fileInputs = section.querySelectorAll('input[type="file"]'); 275 for (const fileInput of fileInputs) { 276 const inputId = fileInput.id; 277 if (!inputId) continue; 278 const label = section.querySelector(`label[for="${inputId}"]`); 279 if (!isFieldRequired(label)) continue; 280 const fieldContainer = fileInput.closest( 281 ".form-field-container-type-file" 282 ); 283 const uploadWrapper = fileInput.closest(".uploadButton"); 284 if (uploadWrapper && fieldContainer) { 285 const uploadButtonLabel = uploadWrapper.querySelector( 286 ".uploadButton-button" 287 ); 288 const hasExistingPreview = 289 fieldContainer.querySelector(".listeo-uploaded-file-preview") !== 290 null; 291 const hasNewFile = fileInput.files.length > 0; 292 const isFieldValid = hasNewFile || hasExistingPreview; 293 if (uploadButtonLabel) { 294 if (!isFieldValid) { 295 isStepValid = false;
296 uploadButtonLabel.classList.add("msf-input-error"); 297 } else { 298 uploadButtonLabel.classList.remove("msf-input-error"); 299 } 300 } 301 } 302 } 303 } 304 return isStepValid; 305 } 306 307 function updateStepStatus(stepIndex) { 308 const isStepValid = validateStep(stepIndex); 309 const stepElement = progressSteps[stepIndex]; 310 if (isStepValid) { 311 stepElement.classList.remove("step-warning"); 312 } else { 313 stepElement.classList.add("step-warning"); 314 } 315 } 316 317 function updateBookingDependencies() { 318 const bookingInput = form.querySelector( 319 '.booking input[name*="booking_status"], .booking select[name*="booking_status"]' 320 ); 321 if (!bookingInput) return; 322 323 let isEnabled = false; 324 325 if (bookingInput.type === "checkbox" || bookingInput.type === "radio") { 326 isEnabled = bookingInput.checked || bookingInput.value === "enabled"; 327 } else if (bookingInput.tagName === "SELECT") { 328 isEnabled = bookingInput.value === "enabled"; 329 } 330 331 const dependentSelectors = [ 332 ".basic_prices", 333 ".slots", 334 ".availability_calendar", 335 ]; 336 dependentSelectors.forEach((selector) => { 337 const section = form.querySelector(selector); 338 if (!section) return; 339 340 if (isEnabled) { 341 section.classList.add("manual-visible"); 342 section.classList.add("active"); 343 section.style.display = ""; 344 } else { 345 section.classList.remove("manual-visible"); 346 section.classList.remove("active"); 347 section.style.display = "none"; 348 } 349 }); 350 } 351 352 // Set up initial state and watch for changes 353 updateBookingDependencies(); 354 const bookingInput = form.querySelector( 355 '.booking input[name*="booking_status"]' 356 ); 357 if (bookingInput) { 358 bookingInput.addEventListener("change", updateBookingDependencies); 359 } 360 361 // Modify the updateFormUI function to handle all sections properly 362 function updateFormUI() { 363 if (typeof window.listeoCurrentStep !== "undefined") { 364 window.listeoCurrentStep = currentStep; 365 } 366 367 // Hide all regular sections first 368 allSections.forEach((section) => { 369 if (!section.classList.contains("manual-visible")) { 370 section.classList.remove("active"); 371 section.style.display = "none"; 372 } 373 }); 374 375 // Hide all custom fields sections 376 const customTermFeatures = document.querySelectorAll(".custom-term-features"); 377 customTermFeatures.forEach(function (element) { 378 if (!element.classList.contains("manual-visible")) { 379 element.classList.remove("active"); 380 element.style.display = "none"; 381 } 382 }); 383 384 // Clean up empty custom sections before showing the current step 385 const emptyCustomSections = document.querySelectorAll(".custom-term-features"); 386 emptyCustomSections.forEach(function (element) { 387 const content = element.querySelector('.custom-term-features-content'); 388 if (content && content.children.length === 0) { 389 console.log("Removing empty custom section from updateFormUI:", element.id); 390 element.remove(); 391 } 392 }); 393 394 if (steps[currentStep]) { 395 const activeSections = steps[currentStep]; 396 let needsResize = false; 397 398 const isBookingEnabled = form 399 .querySelector(".booking") 400 ?.classList.contains("switcher-on"); 401 402 // Show all sections that belong to the current step 403 activeSections.forEach((section) => { 404 // Check if the section still exists in the DOM (might have been removed as empty) 405 if (!document.contains(section)) { 406 return; 407 } 408 409 const sectionClass = [...section.classList].find((cls) => 410 [".basic_prices", ".slots", ".availability_calendar"].includes( 411 "." + cls 412 ) 413 ); 414 415 const isDependentSection = !!sectionClass; 416 417 if (isDependentSection && !isBookingEnabled) { 418 section.classList.remove("active"); 419 section.style.display = "none"; 420 return; 421 } 422 423 section.classList.add("active"); 424 section.style.display = ""; 425 426 if ( 427 section.querySelector("#submit_map") || 428 section.querySelector(".fullCalendar, .fc") 429 ) { 430 needsResize = true; 431 } 432 }); 433 434 if (needsResize) { 435 setTimeout(() => { 436 window.dispatchEvent(new Event("resize")); 437 }, 50); 438 } 439 } 440 441 // FIXED: Check custom sections based on current step configuration 442 const customSections = document.querySelectorAll(".custom-term-features"); 443 customSections.forEach(function (element) {
444 var sectionId = element.id; 445 var shouldBeVisible = false; 446 447 // Check if this section's selector is in current step configuration 448 if (stepConfiguration[currentStep] && stepConfiguration[currentStep].selectors) { 449 // Check for both ID selector and class selector 450 var idSelector = "#" + sectionId; 451 var classSelector = ".custom-term-features"; 452 453 shouldBeVisible = 454 stepConfiguration[currentStep].selectors.indexOf(idSelector) !== -1 || 455 stepConfiguration[currentStep].selectors.indexOf(classSelector) !== -1; 456 } 457 458 // Only show sections that have content 459 const content = element.querySelector('.custom-term-features-content'); 460 const hasContent = content && content.children.length > 0; 461 462 if (shouldBeVisible && hasContent) { 463 element.classList.add("active"); 464 element.style.display = ""; 465 console.log("Showing custom section:", sectionId, "in step:", currentStep); 466 } else { 467 console.log("Hiding custom section:", sectionId, "in step:", currentStep, "hasContent:", hasContent); 468 } 469 }); 470 471 progressSteps.forEach((step, index) => { 472 const icon = step.querySelector(".progress-step-icon"); 473 step.classList.remove("active", "completed"); 474 if ( 475 index <= highestStepReached && 476 !step.classList.contains("step-warning") 477 ) { 478 step.classList.add("completed"); 479 } 480 if (index === currentStep) { 481 step.classList.add("active"); 482 step.classList.remove("completed"); 483 } 484 if ( 485 step.classList.contains("completed") && 486 !step.classList.contains("active") 487 ) { 488 icon.textContent = ""; 489 } else { 490 icon.textContent = index + 1; 491 } 492 }); 493 prevButton.style.display = currentStep === 0 ? "none" : "inline-block"; 494 nextButton.style.display = 495 currentStep === steps.length - 1 ? "none" : "inline-block"; 496 submitButton.style.display = 497 currentStep === steps.length - 1 ? "inline-block" : "none"; 498} 499 500// Add this function after the step creation logic (around line 120) 501// Add a function to refresh steps when custom sections are added 502window.refreshStepsConfiguration = function() { 503 // Get the updated step configuration from the hidden input 504 const stepsInput = document.getElementById('listeo_form_steps_json'); 505 if (!stepsInput) return; 506 507 try { 508 const textarea = document.createElement("textarea"); 509 textarea.innerHTML = stepsInput.value; 510 const updatedStepConfiguration = JSON.parse(textarea.value); 511 512 // Rebuild the steps array 513 steps.length = 0; // Clear existing steps 514 515 updatedStepConfiguration.forEach((groupInfo) => { 516 const groupSections = []; 517 groupInfo.selectors.forEach((selector) => { 518 // Use querySelectorAll on the entire document for custom sections 519 const foundSections = document.querySelectorAll(selector); 520 foundSections.forEach((section) => { 521 // Check if it's in allSections or if it's a custom section 522 if (allSections.includes(section) || section.classList.contains('custom-term-features')) { 523 groupSections.push(section); 524 } 525 }); 526 }); 527 528 if (groupSections.length > 0) { 529 steps.push(groupSections); 530 } 531 }); 532 533 // Update the step configuration reference 534 stepConfiguration = updatedStepConfiguration; 535 536 console.log('Steps configuration refreshed:', steps); 537 538 // DON'T force update the UI here - let it happen naturally 539 // This was causing the section to be removed before content was added 540 // updateFormUI(); 541 542 } catch (e) { 543 console.error('Error refreshing step configuration:', e); 544 } 545}; 546 547// Also add debugging to the step navigation to see what's happening 548nextButton.addEventListener("click", () => { 549 updateStepStatus(currentStep); 550 if (currentStep < steps.length - 1) { 551 currentStep++; 552 highestStepReached = Math.max(highestStepReached, currentStep); 553 console.log("Moving to step:", currentStep, "with sections:", steps[currentStep]); 554 updateFormUI(); 555 smoothScrollToTop(); 556 } 557}); 558 559prevButton.addEventListener("click", () => { 560 updateStepStatus(currentStep); 561 if (currentStep > 0) { 562 currentStep--; 563 console.log("Moving to step:", currentStep, "with sections:", steps[currentStep]); 564 updateFormUI(); 565 smoothScrollToTop(); 566 } 567}); 568 569progressSteps.forEach((step, index) => { 570 step.addEventListener("click", () => { 571 if (index === currentStep) return; 572 if (index > currentStep) { 573 for (let i = currentStep; i < index; i++) { 574 updateStepStatus(i); 575 } 576 } else { 577 updateStepStatus(currentStep); 578 } 579 currentStep = index;
580 highestStepReached = Math.max(highestStepReached, currentStep); 581 console.log("Clicking to step:", currentStep, "with sections:", steps[currentStep]); 582 updateFormUI(); 583 smoothScrollToTop(); 584 }); 585}); 586 submitButton.addEventListener("click", (e) => { 587 const isPreview = 588 e.target.name === "submit_listing" || 589 e.target.value.toLowerCase().includes("preview") || 590 e.target.textContent.toLowerCase().includes("preview"); 591 592 // Always validate steps 593 for (let i = 0; i < steps.length; i++) { 594 updateStepStatus(i); 595 } 596 597 let firstInvalidStep = -1; 598 progressSteps.forEach((step, index) => { 599 if (step.classList.contains("step-warning") && firstInvalidStep === -1) { 600 firstInvalidStep = index; 601 } 602 }); 603 604 if (firstInvalidStep !== -1) { 605 e.preventDefault(); // prevent even preview from submitting 606 currentStep = firstInvalidStep; 607 highestStepReached = Math.max(highestStepReached, currentStep); 608 updateFormUI(); 609 smoothScrollToTop(); 610 611 const firstInvalidField = steps[firstInvalidStep][0].querySelector( 612 ".msf-input-error, .msf-gallery-error-message" 613 ); 614 if (firstInvalidField) firstInvalidField.focus({ preventScroll: true }); 615 // alert( 616 // "Please correct the errors on the highlighted step before continuing." 617 // ); 618 return; 619 } 620 621 // If valid 622 if (isPreview) { 623 showLoader(); 624 allSections.forEach((section) => { 625 section.classList.add("active", "msf-preview-hidden"); 626 }); 627 628 progressContainer.style.display = "none"; 629 navContainer.style.display = "none"; 630 631 console.log("Preview button clicked - showing all sections"); 632 return; 633 } 634 635 e.preventDefault(); 636 console.log("Form is valid. Submitting..."); 637 form.submit(); 638 }); 639 640 641 const previewButton = form.querySelector( 642 'button[name="preview_listing"], input[name="preview_listing"]' 643 ); 644 if (previewButton) { 645 previewButton.addEventListener("click", (e) => { 646 // Don't prevent default - let preview work normally 647 648 // Temporarily show all sections for preview 649 allSections.forEach((section) => { 650 section.classList.add("active"); 651 section.style.display = "block"; 652 }); 653 654 // Let the preview submission proceed without validation 655 console.log("Preview button clicked - showing all sections"); 656 }); 657 } 658 659 // Alternative: If preview button is identified differently, try this selector 660 const previewButtons = form.querySelectorAll( 661 'button[value*="preview"], input[value*="preview"], [name*="preview"]' 662 ); 663 previewButtons.forEach((button) => { 664 button.addEventListener("click", (e) => { 665 allSections.forEach((section) => { 666 section.classList.add("active"); 667 section.style.display = "block"; 668 }); 669 console.log("Preview button clicked - showing all sections"); 670 }); 671 }); 672 673 const urlParams = new URLSearchParams(window.location.search); 674 if (urlParams.get("action") === "edit") { 675 console.log("Edit mode detected. All steps will be marked as completed."); 676 highestStepReached = steps.length - 1; 677 } 678 updateBookingDependencies(); 679 updateFormUI(); 680});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.