1// Set datepicker default value. 2var datepicker_format = 'yy-mm-dd'; 3 4function mecEnhanceDatepickerAccessibility($inputs) 5{ 6 if(!$inputs || !$inputs.length) return; 7 8 $inputs.attr('aria-haspopup', 'dialog'); 9 $inputs.off('focus.mecDatepickerA11y').on('focus.mecDatepickerA11y', function() 10 { 11 window.setTimeout(function() 12 { 13 var $datepicker = jQuery('#ui-datepicker-div'); 14 if(!$datepicker.length) return; 15 16 $datepicker.attr({ 17 role: 'dialog', 18 'aria-live': 'polite', 19 'aria-label': (typeof mecdata !== 'undefined' && mecdata.a11y_calendar_dialog) ? mecdata.a11y_calendar_dialog : 'Calendar date picker' 20 }); 21 }, 0); 22 }); 23} 24 25jQuery(document).ready(function($) 26{ 27 // Image picker on terms menu 28 // Image picker on terms menu 29 // Several buttons share this handler with different data attributes, so the 30 // select callback must close over the CURRENT click's IDs â dispose the old 31 // frame and rebuild on each click (reusing one frame would keep stale IDs). 32 var mecMediaFrame; 33 $('.mec_upload_image_button').click(function(event) 34 { 35 event.preventDefault(); 36 37 if(wp.ajax.settings.url.indexOf('mec_fes=1') === -1) wp.ajax.settings.url = wp.ajax.settings.url + '?mec_fes=1'; 38 39 var post_id = $(this).data('post-id'); 40 if(post_id && post_id !== -1) wp.media.model.settings.post.id = post_id; 41 if(post_id === -1) wp.media.model.settings.post.id = null; 42 43 var preview_id = 'mec_thumbnail_img'; 44 var input_id = 'mec_thumbnail'; 45 46 if($(this).data('preview-id')) preview_id = $(this).data('preview-id'); 47 if($(this).data('input-id')) input_id = $(this).data('input-id'); 48 49 if(mecMediaFrame) mecMediaFrame.dispose(); 50 51 mecMediaFrame = wp.media(); 52 mecMediaFrame.on('select', function() 53 { 54 // Grab the selected attachment. 55 var attachment = mecMediaFrame.state().get('selection').first(); 56 57 $('#'+preview_id).html('<img src="'+attachment.attributes.url+'" alt="Featured image preview" />'); 58 $('#'+input_id).val(attachment.attributes.url); 59 60 // Toggle only this pair's remove button (several upload/remove pairs can coexist on one page) 61 $('.mec_remove_image_button').filter(function() 62 { 63 return (($(this).data('preview-id') || 'mec_thumbnail_img') === preview_id); 64 }).removeClass('mec-util-hidden'); 65 66 // Banner row: hide the "Choose image" button while an image is set 67 if(preview_id === 'mec_banner_thumbnail_img') mecSyncThumbnailButtons('banner'); 68 69 mecMediaFrame.close(); 70 }); 71 72 mecMediaFrame.open(); 73 }); 74 75 // Image remover on terms menu 76 $('.mec_remove_image_button').click(function(event) 77 { 78 event.preventDefault(); 79 80 var preview_id = 'mec_thumbnail_img'; 81 var input_id = 'mec_thumbnail'; 82 83 if($(this).data('preview-id')) preview_id = $(this).data('preview-id'); 84 if($(this).data('input-id')) input_id = $(this).data('input-id'); 85 86 $('#'+preview_id).html(''); 87 $('#'+input_id).val(''); 88 89 $('.mec_remove_image_button').filter(function() 90 { 91 return (($(this).data('preview-id') || 'mec_thumbnail_img') === preview_id); 92 }).addClass('mec-util-hidden'); 93 94 // Banner row: bring back the "Choose image" button 95 if(preview_id === 'mec_banner_thumbnail_img') mecSyncThumbnailButtons('banner'); 96 }); 97 98 // Keep thumbnail "Choose image" / "Remove image" buttons in sync with the current image state 99 function mecSyncThumbnailButtons(which) 100 { 101 var hasImage = $.trim($('#mec_' + which + '_thumbnail').val()) !== '' || $('#mec_' + which + '_thumbnail_img').children('img').length > 0; 102 // Dedicated remove button (.mec_{which}_remove_image_button) or the generic 103 // pair (.mec_remove_image_button[data-preview-id]) â the popup has neither. 104 var $remove = $('.mec_' + which + '_remove_image_button'); 105 if(!$remove.length) $remove = $('.mec_remove_image_button').filter(function() 106 { 107 return (($(this).data('preview-id') || 'mec_thumbnail_img') === 'mec_' + which + '_thumbnail_img'); 108 }); 109 110 $remove.toggleClass('mec-util-hidden', !hasImage); 111 $('#mec_' + which + '_thumbnail_button').toggleClass('mec-util-hidden', hasImage && $remove.length > 0); 112 } 113 114 // Image picker on add event menu for location 115 var mecLocationMediaFrame; 116 $('.mec_location_upload_image_button').click(function(event) 117 { 118 event.preventDefault(); 119 120 // Reuse the open frame across clicks (IDs are fixed for this pair) 121 if(mecLocationMediaFrame) 122 { 123 mecLocationMediaFrame.open(); 124 return; 125 } 126 127 mecLocationMediaFrame = wp.media(); 128 mecLocationMediaFrame.on('select', function() 129 { 130 // Grab the selected attachment. 131 var attachment = mecLocationMediaFrame.state().get('selection').first(); 132 133 $('#mec_location_thumbnail_img').html('<img src="'+attachment.attributes.url+'" />'); 134 $('#mec_location_thumbnail').val(attachment.attributes.url); 135 136 mecSyncThumbnailButtons('location'); 137 138 mecLocationMediaFrame.close(); 139 }); 140 141 mecLocationMediaFrame.open(); 142 });
143 144 // Image remover on add event menu for location 145 $('.mec_location_remove_image_button').click(function(event) 146 { 147 event.preventDefault(); 148 149 $('#mec_location_thumbnail_img').html(''); 150 $('#mec_location_thumbnail').val(''); 151 152 mecSyncThumbnailButtons('location'); 153 }); 154 155 // Image picker on add event menu for organizer 156 var mecOrganizerMediaFrame; 157 $('.mec_organizer_upload_image_button').click(function(event) 158 { 159 event.preventDefault(); 160 161 // Reuse the open frame across clicks (IDs are fixed for this pair) 162 if(mecOrganizerMediaFrame) 163 { 164 mecOrganizerMediaFrame.open(); 165 return; 166 } 167 168 mecOrganizerMediaFrame = wp.media(); 169 mecOrganizerMediaFrame.on('select', function() 170 { 171 // Grab the selected attachment. 172 var attachment = mecOrganizerMediaFrame.state().get('selection').first(); 173 174 $('#mec_organizer_thumbnail_img').html('<img src="'+attachment.attributes.url+'" />'); 175 $('#mec_organizer_thumbnail').val(attachment.attributes.url); 176 177 mecSyncThumbnailButtons('organizer'); 178 179 mecOrganizerMediaFrame.close(); 180 }); 181 182 mecOrganizerMediaFrame.open(); 183 }); 184 185 // Image remover on add event menu for organizer 186 $('.mec_organizer_remove_image_button').click(function(event) 187 { 188 event.preventDefault(); 189 190 $('#mec_organizer_thumbnail_img').html(''); 191 $('#mec_organizer_thumbnail').val(''); 192 193 mecSyncThumbnailButtons('organizer'); 194 }); 195 196 // Initial state sync (e.g. edit forms pre-populated by other scripts) 197 mecSyncThumbnailButtons('location'); 198 mecSyncThumbnailButtons('organizer'); 199 mecSyncThumbnailButtons('banner'); 200 201 // Image remover on frontend event submission menu 202 $('#mec_fes_remove_image_button').click(function(event) 203 { 204 event.preventDefault(); 205 206 $('#mec_fes_thumbnail_img').html(''); 207 $('#mec_fes_thumbnail').val(''); 208 $('#mec_featured_image_file').val(''); 209 210 $('#mec_fes_remove_image_button').addClass('mec-util-hidden'); 211 }); 212 213 // Location Image remover on frontend event submission menu 214 $('#mec_fes_location_remove_image_button').click(function(event) 215 { 216 event.preventDefault(); 217 218 $('#mec_fes_location_thumbnail_img').html(''); 219 $('#mec_fes_location_thumbnail').val(''); 220 $('#mec_fes_location_thumbnail_file').val(''); 221 222 $('#mec_fes_location_remove_image_button').addClass('mec-util-hidden'); 223 }); 224 225 // Organizer Image remover on frontend event submission menu 226 $('#mec_fes_organizer_remove_image_button').click(function(event) 227 { 228 event.preventDefault(); 229 230 $('#mec_fes_organizer_thumbnail_img').html(''); 231 $('#mec_fes_organizer_thumbnail').val(''); 232 $('#mec_fes_organizer_thumbnail_file').val(''); 233 234 $('#mec_fes_organizer_remove_image_button').addClass('mec-util-hidden'); 235 }); 236 237 // Sponsor Image remover on frontend event submission menu 238 $('#mec_fes_sponsor_remove_image_button').click(function(event) 239 { 240 event.preventDefault(); 241 242 $('#mec_fes_sponsor_thumbnail_img').html(''); 243 $('#mec_fes_sponsor_thumbnail').val(''); 244 $('#mec_fes_sponsor_thumbnail_file').val(''); 245 246 $('#mec_fes_sponsor_remove_image_button').addClass('mec-util-hidden'); 247 }); 248 249 // Speaker Image remover on frontend event submission menu 250 $('#mec_fes_speaker_remove_image_button').click(function(event) 251 { 252 event.preventDefault(); 253 254 $('#mec_fes_speaker_thumbnail_img').html(''); 255 $('#mec_fes_speaker_thumbnail').val(''); 256 $('#mec_fes_speaker_thumbnail_file').val(''); 257 258 $('#mec_fes_speaker_remove_image_button').addClass('mec-util-hidden'); 259 }); 260 261 var date_splite; 262 if(typeof mec_admin_localize !== 'undefined') 263 { 264 date_splite = mec_admin_localize.datepicker_format.split('&'); 265 if(date_splite[0] !== undefined && date_splite.length == 2) datepicker_format = date_splite[0]; 266 } 267 else if(typeof mecdata !== 'undefined') 268 { 269 date_splite = mecdata.datepicker_format.split( '&' ); 270 if(date_splite[0] !== undefined && date_splite.length == 2) datepicker_format = date_splite[0]; 271 } 272 273 $('[id^="mec-book-form-btn-step"]').on('click',function() 274 { 275 setTimeout(function() 276 { 277 if($.fn.datepicker) 278 { 279 if('date' !== $('.mec-date-picker').prop('type')) 280 { 281 $('.mec-date-picker').datepicker( 282 { 283 changeYear: true, 284 changeMonth: true, 285 dateFormat: datepicker_format, 286 gotoCurrent: true, 287 yearRange: 'c-3:c+5', 288 }); 289 } 290 291 } 292 }, 1000); 293 });
294 295 if($.fn.datepicker) 296 { 297 mecEnhanceDatepickerAccessibility($('.mec-date-picker, .mec_date_picker_dynamic_format, .mec_date_picker, .mec_date_picker_dynamic_format_start, .mec_date_picker_dynamic_format_end, #mec_start_date, #mec_end_date, #mec_date_repeat_end_at_date')); 298 299 $('.mec-date-picker').datepicker( 300 { 301 changeYear: true, 302 changeMonth: true, 303 dateFormat: datepicker_format, 304 gotoCurrent: true, 305 yearRange: 'c-3:c+5', 306 }); 307 308 $('#mec_start_date').datepicker( 309 { 310 changeYear: true, 311 changeMonth: true, 312 dateFormat: datepicker_format, 313 gotoCurrent: true, 314 yearRange: 'c-3:c+5', 315 onSelect: function(value, i) 316 { 317 const end = $("#mec_end_date"); 318 if(value !== i.lastVal) 319 { 320 end.datepicker("option", "minDate", value); 321 } 322 323 if(end.val() === '') 324 { 325 end.val(value); 326 } 327 328 // Datepicker doesn't natively fire `change` on the input, so 329 // trigger it to clear inline validation errors (audit issue #1). 330 $(this).trigger('change'); 331 } 332 }); 333 334 $('#mec_end_date').datepicker( 335 { 336 changeYear: true, 337 changeMonth: true, 338 dateFormat: datepicker_format, 339 gotoCurrent: true, 340 yearRange: 'c-3:c+5', 341 onSelect: function(value, i) 342 { 343 const start = $("#mec_start_date"); 344 if(value !== i.lastVal) 345 { 346 start.datepicker("option", "maxDate", value); 347 } 348 349 if(start.val() === '') 350 { 351 start.val(value); 352 } 353 354 // Datepicker doesn't natively fire `change` on the input, so 355 // trigger it to clear inline validation errors (audit issue #1). 356 $(this).trigger('change'); 357 } 358 }); 359 360 // ===== Combobox time control (audit R3) ===== 361 // A typeable field; typing filters a dropdown of every 5-minute slot. 362 // The hidden [hour][minutes][ampm] fields stay in sync (looked up LOCALLY 363 // inside the wrapper, so cloned rows â tickets â keep working). Save is 364 // unchanged. 365 366 function mecTimeLabel(h24, min, is12) 367 { 368 var pad = function(n){ return (n < 10 ? '0' : '') + n; }; 369 if(is12) 370 { 371 var h12 = h24 % 12; if(h12 === 0) h12 = 12; 372 return pad(h12) + ':' + pad(min) + ' ' + (h24 < 12 ? 'AM' : 'PM'); 373 } 374 return pad(h24) + ':' + pad(min); 375 } 376 377 // Write total-minutes-from-midnight into the hidden fields + sync the field. 378 // Used by the duration auto-advance (start/end event time, which have ids). 379 function mecWriteTime(prefix, totalMinutes) 380 { 381 totalMinutes = Math.max(0, Math.min(1439, totalMinutes)); 382 var hour24 = Math.floor(totalMinutes / 60); 383 var min = Math.floor((totalMinutes % 60) / 5) * 5; 384 var $h = $('#' + prefix + 'hour'); 385 if(!$h.length) return; 386 var $ampm = $('#' + prefix + 'ampm'); 387 if($ampm.length) 388 { 389 var h12 = hour24 % 12; if(h12 === 0) h12 = 12; 390 $h.val(h12); 391 $ampm.val(hour24 < 12 ? 'AM' : 'PM'); 392 } 393 else $h.val(hour24); 394 $('#' + prefix + 'minutes').val(min); 395 var $w = $('#' + prefix + 'time'); 396 if($w.length) $w.val(mecTimeLabel(hour24, min, $ampm.length > 0)); 397 } 398 399 function mecTimeInitOne($in) 400 { 401 // Use jQuery .data() (not a class) so that elements cloned via 402 // .html() + .append() (MEC's ticket-add flow) are re-initialized. 403 if(!$in.length || $in.data('mec-time-init')) return; 404 $in.data('mec-time-init', true); 405 406 var $wrap = $in.closest('.mec-time-combo-wrap'); 407 if(!$wrap.length) return; 408 var is12 = parseInt($in.data('format'), 10) !== 24; 409 var $dd = $('<div class="mec-time-dd"><ul></ul></div>').appendTo('body'); 410 var $list = $dd.find('ul'); 411 var $clear = $wrap.find('.mec-time-clear'); 412 var hi = -1; 413 414 // Hidden fields are siblings inside the wrapper (local lookup). 415 var $h = $wrap.find('.mec-time-hour'); 416 var $m = $wrap.find('.mec-time-minutes'); 417 var $ampm = $wrap.find('.mec-time-ampm'); 418 if(!$h.length) return; // not a combobox instance 419 420 var options = []; 421 for(var i = 0; i < 288; i++) 422 { 423 var total = i * 5; 424 options.push({total: total, label: mecTimeLabel(Math.floor(total / 60), total % 60, is12)}); 425 } 426 427 function toggleClear(){ $clear.toggle(!!$in.val()); } 428 429 function matches(term, label) 430 { 431 term = (term || '').toLowerCase().trim(); 432 if(!term) return true; 433 if(label.toLowerCase().indexOf(term) > -1) return true; 434 var td = term.replace(/\D/g, ''); 435 return td.length > 0 && label.replace(/\D/g, '').indexOf(td) > -1; 436 } 437 438 function render(term) 439 { 440 $list.empty(); 441 options.forEach(function(o){ if(matches(term, o.label)) $('<li/>').text(o.label).data('total', o.total).appendTo($list); }); 442 hi = $list.children().length ? 0 : -1; 443 highlight(); 444 } 445 446 function highlight() 447 { 448 $list.children().removeClass('mec-time-dd-active'); 449 if(hi >= 0) $list.children().eq(hi).addClass('mec-time-dd-active'); 450 } 451 452 function move(delta) 453 { 454 var n = $list.children().length; 455 if(!n) return; 456 hi = (hi + delta + n) % n; 457 highlight(); 458 var $el = $list.children().eq(hi); 459 if($el.length) $dd.scrollTop($el.position().top + $dd.scrollTop() - 60);
460 } 461 462 function open() 463 { 464 render(''); 465 var pos = $in.offset(); 466 $dd.css({ left: pos.left + 'px', top: (pos.top + $in.outerHeight() + 2) + 'px', width: $in.outerWidth() + 'px' }).show(); 467 } 468 function close(){ $dd.hide(); hi = -1; } 469 470 function writeLocal(total) 471 { 472 var hour24 = Math.floor(total / 60); 473 var min = Math.floor((total % 60) / 5) * 5; 474 if($ampm.length) 475 { 476 var h12 = hour24 % 12; if(h12 === 0) h12 = 12; 477 $h.val(h12); 478 $ampm.val(hour24 < 12 ? 'AM' : 'PM'); 479 } 480 else $h.val(hour24); 481 $m.val(min); 482 $in.val(mecTimeLabel(hour24, min, is12)); 483 } 484 485 function readLocalH24() 486 { 487 var h24 = parseInt($h.val(), 10); if(isNaN(h24)) h24 = 0; 488 if($ampm.length){ if($ampm.val() === 'PM' && h24 !== 12) h24 += 12; else if($ampm.val() === 'AM' && h24 === 12) h24 = 0; } 489 return h24; 490 } 491 492 function choose(total) 493 { 494 writeLocal(total); 495 $in.trigger('change'); // duration auto-advance (#8) 496 toggleClear(); 497 close(); 498 } 499 500 toggleClear(); 501 502 // "Clear" (x) button: empty the text for retyping (hidden value preserved). 503 $wrap.on('mousedown', '.mec-time-clear', function(e){ e.preventDefault(); $in.val(''); open(); $in.focus(); toggleClear(); }); 504 505 $in.on('focus', function(){ open(); }); 506 $in.on('click', function(){ if(!$dd.is(':visible')) open(); }); 507 $in.on('input', function(){ if(!$dd.is(':visible')) $dd.show(); render($in.val()); toggleClear(); }); 508 $in.on('keydown', function(e) 509 { 510 if(e.key === 'ArrowDown'){ e.preventDefault(); if(!$dd.is(':visible')) open(); else move(1); } 511 else if(e.key === 'ArrowUp'){ e.preventDefault(); move(-1); } 512 else if(e.key === 'Enter'){ if(hi >= 0){ e.preventDefault(); var t = $list.children().eq(hi).data('total'); if(t !== undefined) choose(t); } } 513 else if(e.key === 'Escape'){ close(); } 514 else if(e.key === 'Tab'){ close(); } 515 }); 516 $list.on('mousedown', 'li', function(e){ e.preventDefault(); choose($(this).data('total')); }); 517 $in.on('blur', function() 518 { 519 setTimeout(function() 520 { 521 close(); 522 var val = ($in.val() || '').trim(); 523 var found = null; 524 for(var k = 0; k < options.length; k++){ if(options[k].label.toLowerCase() === val.toLowerCase()){ found = options[k]; break; } } 525 if(found) writeLocal(found.total); 526 else 527 { 528 var h24 = readLocalH24(); 529 var mm = parseInt($m.val(), 10) || 0; 530 $in.val(mecTimeLabel(h24, mm, is12)); 531 } 532 toggleClear(); 533 }, 150); 534 }); 535 } 536 537 function mecInitTimeCombos(){ $('.mec-time-combo').each(function(){ mecTimeInitOne($(this)); }); } 538 539 mecInitTimeCombos(); 540 541 // Re-init comboboxes added dynamically (in_days rows, tickets, appointments, popup modal). 542 if(typeof MutationObserver !== 'undefined') 543 { 544 var mecTimeMO = new MutationObserver(function(mutations) 545 { 546 for(var i = 0; i < mutations.length; i++) 547 { 548 var added = mutations[i].addedNodes; 549 for(var j = 0; j < added.length; j++) 550 { 551 var n = added[j]; 552 if(n.nodeType === 1 && ((n.matches && (n.matches('.mec-time-combo') || n.matches('.mec-hourly-time'))) || (n.querySelector && (n.querySelector('.mec-time-combo') || n.querySelector('.mec-hourly-time'))))) 553 { 554 setTimeout(function(){ mecInitTimeCombos(); mecInitHourlyTimes(); }, 30); 555 return; 556 } 557 } 558 } 559 }); 560 mecTimeMO.observe(document.body, {childList: true, subtree: true}); 561 } 562 563 // Close dropdowns when clicking outside (dropdown is in <body>, not the wrap). 564 var $activeDDs = $(); // track dropdowns created by comboboxes 565 $(document).on('mousedown', function(e) 566 { 567 $('.mec-time-dd').each(function() 568 { 569 if(this !== e.target && !this.contains(e.target)) 570 { 571 var $w = $(this); 572 var $combo = $w.prevAll('.mec-time-combo-wrap').first().find('.mec-time-combo'); 573 // Simple check: if click is not in this dropdown and not in any combo wrap 574 var inCombo = false; 575 $('.mec-time-combo-wrap').each(function(){ if(this.contains(e.target)) inCombo = true; }); 576 $('.mec-hourly-time-wrap').each(function(){ if(this.contains(e.target)) inCombo = true; }); 577 if(!inCombo) $w.hide(); 578 } 579 }); 580 }); 581 582 // ===== Hourly schedule string time combobox (audit #5) ===== 583 // Same UX as the R3 combobox, but the value lives in a single text 584 // field (the hourly schedule stores "from"/"to" as raw strings echoed 585 // on the front-end), so we read/write the input directly â no hidden 586 // fields, no storage change. 587 588 function mecParseTimeString(str, is12) 589 { 590 str = (str || '').trim().toUpperCase(); 591 if(!str) return null; 592 var m = str.match(/^(\d{1,2})[:.\s](\d{1,2})\s*(AM|PM)?$/); 593 if(!m) return null; 594 var h = parseInt(m[1], 10); 595 var min = parseInt(m[2], 10); 596 if(isNaN(h) || isNaN(min) || h < 0 || h > 23 || min < 0 || min > 59) return null; 597 var ap = m[3]; 598 if(ap === 'PM' && h !== 12) h += 12; 599 else if(ap === 'AM' && h === 12) h = 0; 600 return h * 60 + min; 601 } 602 603 function mecHourlyTimeInitOne($in) 604 { 605 // Use jQuery .data() (not a class) so dynamically added rows are re-initialized. 606 if(!$in.length || $in.data('mec-time-init')) return; 607 $in.data('mec-time-init', true); 608 609 var $wrap = $in.closest('.mec-hourly-time-wrap'); 610 if(!$wrap.length) return; 611 var is12 = parseInt($in.data('format'), 10) !== 24; 612 var $dd = $('<div class="mec-time-dd"><ul></ul></div>').appendTo('body'); 613 var $list = $dd.find('ul'); 614 var $clear = $wrap.find('.mec-time-clear'); 615 var hi = -1; 616 617 var options = []; 618 for(var i = 0; i < 288; i++) 619 { 620 var total = i * 5; 621 options.push({total: total, label: mecTimeLabel(Math.floor(total / 60), total % 60, is12)}); 622 } 623 624 function toggleClear(){ $clear.toggle(!!$in.val()); } 625 626 function matches(term, label) 627 { 628 term = (term || '').toLowerCase().trim(); 629 if(!term) return true; 630 if(label.toLowerCase().indexOf(term) > -1) return true; 631 var td = term.replace(/\D/g, ''); 632 return td.length > 0 && label.replace(/\D/g, '').indexOf(td) > -1; 633 } 634 635 function highlight() 636 { 637 $list.children().removeClass('mec-time-dd-active'); 638 if(hi >= 0) $list.children().eq(hi).addClass('mec-time-dd-active'); 639 } 640 641 function render(term) 642 { 643 $list.empty(); 644 options.forEach(function(o){ if(matches(term, o.label)) $('<li/>').text(o.label).data('total', o.total).appendTo($list); }); 645 hi = $list.children().length ? 0 : -1; 646 highlight(); 647 } 648 649 function move(delta) 650 { 651 var n = $list.children().length; 652 if(!n) return; 653 hi = (hi + delta + n) % n; 654 highlight(); 655 var $el = $list.children().eq(hi); 656 if($el.length) $dd.scrollTop($el.position().top + $dd.scrollTop() - 60);
657 } 658 659 function open() 660 { 661 render(''); 662 // Highlight the current value if parseable. 663 var cur = mecParseTimeString($in.val(), is12); 664 if(cur !== null) 665 { 666 var snapped = Math.floor(cur / 5) * 5; 667 $list.children().each(function(){ if($(this).data('total') === snapped){ hi = $(this).index(); return false; } }); 668 highlight(); 669 } 670 var pos = $in.offset(); 671 $dd.css({ left: pos.left + 'px', top: (pos.top + $in.outerHeight() + 2) + 'px', width: $in.outerWidth() + 'px' }).show(); 672 } 673 function close(){ $dd.hide(); hi = -1; } 674 675 function choose(total) 676 { 677 var hour24 = Math.floor(total / 60); 678 var min = total % 60; 679 $in.val(mecTimeLabel(hour24, min, is12)).trigger('change'); 680 toggleClear(); 681 close(); 682 } 683 684 toggleClear(); 685 686 // "Clear" (x) button: empty the text for retyping. 687 $wrap.on('mousedown', '.mec-time-clear', function(e){ e.preventDefault(); $in.val(''); open(); $in.focus(); toggleClear(); }); 688 689 $in.on('focus', function(){ open(); }); 690 $in.on('click', function(){ if(!$dd.is(':visible')) open(); }); 691 $in.on('input', function(){ if(!$dd.is(':visible')) $dd.show(); render($in.val()); toggleClear(); }); 692 $in.on('keydown', function(e) 693 { 694 if(e.key === 'ArrowDown'){ e.preventDefault(); if(!$dd.is(':visible')) open(); else move(1); } 695 else if(e.key === 'ArrowUp'){ e.preventDefault(); move(-1); } 696 else if(e.key === 'Enter'){ if(hi >= 0){ e.preventDefault(); var t = $list.children().eq(hi).data('total'); if(t !== undefined) choose(t); } } 697 else if(e.key === 'Escape'){ close(); } 698 else if(e.key === 'Tab'){ close(); } 699 }); 700 $list.on('mousedown', 'li', function(e){ e.preventDefault(); choose($(this).data('total')); }); 701 $in.on('blur', function() 702 { 703 setTimeout(function() 704 { 705 close(); 706 var val = ($in.val() || '').trim(); 707 if(!val){ toggleClear(); return; } 708 // Snap typed text to the nearest 5-minute slot when parseable. 709 var total = mecParseTimeString(val, is12); 710 if(total !== null) 711 { 712 var snapped = Math.floor(total / 5) * 5; 713 $in.val(mecTimeLabel(Math.floor(snapped / 60), snapped % 60, is12)); 714 } 715 toggleClear(); 716 }, 150); 717 }); 718 } 719 720 function mecInitHourlyTimes(){ $('.mec-hourly-time').each(function(){ mecHourlyTimeInitOne($(this)); }); } 721 722 mecInitHourlyTimes(); 723 724 // ===== Admin event editor: required-field + End >= Start validation ===== 725 // Audit issue #1: no required-field markers/validation in the admin editor. 726 (function() 727 { 728 var $postForm = $('#post'); 729 var $startDate = $('#mec_start_date'); 730 var $endDate = $('#mec_end_date'); 731 // Only relevant on the admin event editor (#post form with MEC date fields). 732 if(!$postForm.length || !$startDate.length) return; 733 734 var mecVL = (typeof mec_admin_localize !== 'undefined') ? mec_admin_localize : {}; 735 var warningsAcknowledged = false; 736 737 // MEC reusable notice system â modern card with accent bar, type-aware, 738 // dismissible. Supports title, message/items, primary + dismiss buttons. 739 function mecNotice(opts) 740 { 741 var o = $.extend({ 742 type: 'info', // error | warning | info | success 743 title: '', 744 message: '', 745 items: [], 746 primaryText: '', 747 primaryAction: null,
748 dismissText: '', 749 container: '#poststuff', 750 id: 'mec-notice-' + Date.now() 751 }, opts); 752 753 $('.mec-notice-temp').remove(); 754 755 var body = ''; 756 if(o.title) body += '<div class="mec-notice-title">' + o.title + '</div>'; 757 if(o.items && o.items.length) body += '<ul class="mec-notice-list">' + o.items.map(function(i){ return '<li>' + i + '</li>'; }).join('') + '</ul>'; 758 else if(o.message) body += '<p class="mec-notice-text">' + o.message + '</p>'; 759 760 if(o.primaryText || o.dismissText) 761 { 762 body += '<div class="mec-notice-actions">'; 763 if(o.primaryText) body += '<button type="button" class="mec-notice-btn mec-notice-btn--primary mec-notice-primary">' + o.primaryText + '</button>'; 764 if(o.dismissText) body += '<button type="button" class="mec-notice-btn mec-notice-btn--ghost mec-notice-dismiss">' + o.dismissText + '</button>'; 765 body += '</div>'; 766 } 767 768 var $n = $('<div/>', { 769 id: o.id, 770 'class': 'mec-notice mec-notice--' + o.type + ' mec-notice-temp', 771 html: '<div class="mec-notice-accent"></div><div class="mec-notice-body">' + body + '</div><div class="mec-notice-close" aria-label="Close" role="button" tabindex="0">×</div>' 772 }); 773 774 $(o.container).prepend($n); 775 $('html, body').animate({scrollTop: $n.offset().top - 40}, 200); 776 777 $n.find('.mec-notice-dismiss, .mec-notice-close').on('click', function(){ $n.remove(); }); 778 if(o.primaryText) 779 { 780 $n.find('.mec-notice-primary').on('click', function() 781 { 782 if(typeof o.primaryAction === 'function') o.primaryAction(); 783 $n.remove(); 784 }); 785 } 786 return $n; 787 } 788 789 function mecVLTab(tabId) 790 { 791 $('#mec_metabox_details .mec-add-event-tabs-link[data-href="' + tabId + '"]').trigger('click'); 792 } 793 794 function mecVLTime24(prefix) 795 { 796 var $h = $('#' + prefix + 'hour'); 797 if(!$h.length) return {hour: 0, min: 0}; 798 var hour = parseInt($h.val(), 10) || 0; 799 var min = ($('#' + prefix + 'minutes').length) ? (parseInt($('#' + prefix + 'minutes').val(), 10) || 0) : 0; 800 var $ampm = $('#' + prefix + 'ampm'); 801 if($ampm.length) 802 { 803 if($ampm.val() === 'PM' && hour !== 12) hour += 12; 804 else if($ampm.val() === 'AM' && hour === 12) hour = 0; 805 } 806 return {hour: hour, min: min}; 807 } 808 809 // Write a 24h time (in total minutes since midnight) back into the 810 // hour/minutes/ampm selects, respecting the active time format. 811 function mecVLSetTime24(prefix, totalMinutes) 812 { 813 if(typeof mecWriteTime === 'function') mecWriteTime(prefix, totalMinutes); 814 } 815 816 function mecVLParseDate($field) 817 { 818 var raw = $.trim($field.val() || ''); 819 if(!raw) return null; 820 try 821 { 822 return $.datepicker.parseDate(datepicker_format, raw); 823 } 824 catch(e) 825 { 826 var fb = new Date(raw); 827 return isNaN(fb.getTime()) ? null : fb; 828 } 829 } 830 831 function mecVLShowError($field, message) 832 { 833 $field.addClass('mec-field-invalid').attr('aria-invalid', 'true'); 834 var $err = $field.next('.mec-field-error'); 835 if(!$err.length) 836 { 837 $err = $('<span class="mec-field-error" role="alert"></span>'); 838 $field.after($err); 839 } 840 $err.text(message); 841 } 842 843 function mecVLClearError($field) 844 { 845 $field.removeClass('mec-field-invalid').removeAttr('aria-invalid'); 846 $field.next('.mec-field-error').remove(); 847 } 848 849 // Clear the inline error as soon as the user edits the field. 850 $startDate.add($endDate).on('input change', function() { mecVLClearError($(this)); }); 851 852 // Smart end time: when the start time changes, shift the end time to 853 // preserve the event duration (Google Calendar behavior). Audit issue #8. 854 var $startWidget = $('#mec_start_time'); 855 if($startWidget.length) 856 { 857 // Combobox mode (R3): the visible field is a text input; capture on 858 // focus, shift on the 'change' the combobox dispatches on
858selection. 859 var startBefore = null; 860 $startWidget.on('focus', function() 861 { 862 if($('#mec_allday').is(':checked')) return; 863 var t = mecVLTime24('mec_start_'); 864 startBefore = t.hour * 60 + t.min; 865 }).on('change', function() 866 { 867 if(startBefore === null) return; 868 var t = mecVLTime24('mec_start_'); 869 var now = t.hour * 60 + t.min; 870 var delta = now - startBefore; 871 startBefore = now; 872 if(!delta) return; 873 var e = mecVLTime24('mec_end_'); 874 mecWriteTime('mec_end_', (e.hour * 60 + e.min) + delta); 875 }); 876 } 877 else 878 { 879 // Legacy 3-select mode: focus/change on the selects. 880 var $startSelects = $('#mec_start_hour, #mec_start_minutes, #mec_start_ampm'); 881 if($startSelects.length) 882 { 883 var legacyBefore = null; 884 $startSelects.on('focus', function() 885 { 886 if($('#mec_allday').is(':checked')) return; 887 var t = mecVLTime24('mec_start_'); 888 legacyBefore = t.hour * 60 + t.min; 889 }).on('change', function() 890 { 891 if($('#mec_allday').is(':checked')) { legacyBefore = null; return; } 892 if(legacyBefore === null) return; 893 var t = mecVLTime24('mec_start_'); 894 var now = t.hour * 60 + t.min; 895 var delta = now - legacyBefore; 896 legacyBefore = now; 897 if(delta === 0) return; 898 var e = mecVLTime24('mec_end_'); 899 mecVLSetTime24('mec_end_', (e.hour * 60 + e.min) + delta); 900 }); 901 } 902 } 903 904 $postForm.on('submit', function(e) 905 { 906 // Clear previous errors before re-validating. 907 mecVLClearError($startDate); 908 mecVLClearError($endDate); 909 910 // Publish-intent detection: enforce on every submit EXCEPT 911 // explicit draft saves and previews. This covers the publish 912 // button, Enter-key submits and any programmatic path â the 913 // old "publishClicked" flag missed keyboard submits. 914 var submitter = (e.originalEvent && e.originalEvent.submitter) ? $(e.originalEvent.submitter) : null; 915 if(submitter && submitter.attr('id') === 'save-post') return; 916 if($('#wp-preview').val() === 'dopreview') return; 917 918 // Undo WordPress core's URL rewrite: post.js appends 919 // "wp-post-new-reload=true" to the address bar on every publish 920 // click of an auto-draft, even when the submit ends up blocked. 921 // Strip it so the misleading state doesn't linger. 922 if(window.history && window.history.replaceState && window.location.href.indexOf('wp-post-new-reload') > -1) 923 { 924 window.history.replaceState(null, null, window.location.href.replace(/([?&])wp-post-new-reload=true&?/, '$1').replace(/[?&]$/, '')); 925 } 926 927 var startRaw = $.trim($startDate.val() || ''); 928 var endRaw = $.trim($endDate.val() || ''); 929 930 // 1. Start Date is required. 931 if(!startRaw) 932 { 933 e.preventDefault(); 934 mecVLTab('mec_meta_box_date_form'); 935 mecVLShowError($startDate, mecVL.start_date_required || 'Please enter a start date for the event.'); 936 $startDate.trigger('focus'); 937 mecNotice({ type:'error', title:'Start date required', message: mecVL.start_date_required || 'Please enter a start date for the event.', dismissText:'Got it' }); 938 return; 939 } 940 941 // 2. End must be on or after Start. 942 if(endRaw) 943 { 944 var sDate = mecVLParseDate($startDate); 945 var eDate = mecVLParseDate($endDate); 946 if(sDate && eDate) 947 { 948 var allday = $('#mec_allday').is(':checked'); 949 var sT = allday ? {hour: 0, min: 0} : mecVLTime24('mec_start_'); 950 var eT = allday ? {hour: 0, min: 0} : mecVLTime24('mec_end_'); 951 var start = new Date(sDate.getFullYear(), sDate.getMonth(), sDate.getDate(), sT.hour, sT.min, 0, 0); 952 var finish = new Date(eDate.getFullYear(), eDate.getMonth(), eDate.getDate(), eT.hour, eT.min, 0, 0); 953 954 if(finish.getTime() < start.getTime()) 955 { 956 e.preventDefault(); 957 mecVLTab('mec_meta_box_date_form'); 958 mecVLShowError($endDate, mecVL.end_after_start || 'The end date and time must be on or after the start date and time.'); 959 $endDate.trigger('focus'); 960 mecNotice({ type:'error', title:'End is before start', message: mecVL.end_after_start || 'The end date and time must be on or after the start date and time.', dismissText:'Got it' }); 961 return; 962 } 963 } 964 } 965 966 // ===== R5: Extended pre-publish checks ===== 967 var warnings = []; 968 var $bookingBox = $('#mec_metabox_booking'); 969 $('.mec-notice-temp').remove(); 970 971 // Only treat as "bookable" if there are real tickets (with names). 972 // A blank default ticket row is NOT a real ticket. 973 var $realTickets = $('#mec-tickets input[name*="[name]"]').filter(function(){ return $.trim($(this).val()) !== ''; }); 974 var hasTickets = $realTickets.length > 0;
975 976 // --- BLOCKS (prevent publish) --- 977 // B3: certain_weekdays + none checked 978 if($('#mec_repeat').is(':checked') && $('#mec_repeat_type').val() === 'certain_weekdays' && $('input[name="mec[date][repeat][certain_weekdays][]"]:checked').length === 0) 979 { 980 e.preventDefault(); mecVLTab('mec_meta_box_repeat_form'); 981 mecNotice({ type:'error', title:'No weekdays selected', message:'Repeating is set to "Certain Weekdays" but no weekdays are checked.', dismissText:'Got it' }); 982 return; 983 } 984 // B4: occurrences = 0 985 var $endRadio = $('#mec_repeat_end_type').length ? $('#mec_repeat_end_type') : $('input[name="mec[date][repeat][end]"]:checked'); 986 if($endRadio.val() === 'occurrences') 987 { 988 var occ = parseInt($('#mec_date_repeat_end_at_occurrences').val(), 10); 989 if(isNaN(occ) || occ <= 0) 990 { 991 e.preventDefault(); mecVLTab('mec_meta_box_repeat_form'); 992 mecNotice({ type:'error', title:'Invalid occurrence count', message:'The number of occurrences must be at least 1.', dismissText:'Got it' }); 993 return; 994 } 995 } 996 997 if(parseInt(mecVL.publish_warnings_status, 10) === 0) return; 998 999 // --- WARNINGS (allow publish with confirmation) --- 1000 // Booking-specific warnings only if there are real tickets 1001 if(hasTickets) 1002 { 1003 // W2: ticket with name but no price 1004 $realTickets.each(function(){ 1005 var $name = $(this); 1006 var $row = $name.closest('.mec-ticket-start-time, .wn-ticket-time').parent(); 1007 var $price = $row.find('input[name*="price"]'); 1008 if(!$price.length) $price = $name.closest('[class*="ticket"]').find('input[name*="price"]'); 1009 if($price.length && $.trim($price.val()) === '') warnings.push('A ticket has a name but no price set.'); 1010 }); 1011 // W3: cost empty on bookable event with tickets 1012 if($.trim($('#mec_cost').val()) === '' && !$('#mec_cost_is_free').is(':checked') && !$('#mec_cost_auto_calculate').is(':checked')) warnings.push('This event has tickets but no cost is displayed.'); 1013 } 1014 // W4: no featured image 1015 if(!$('#set-post-thumbnail img').length && !$('#postimagediv img').length) warnings.push('No featured image set.'); 1016 // W5: no content 1017 var c = (typeof tinymce !== 'undefined' && tinymce.get('content')) ? tinymce.get('content').getContent() : ($('#content').val() || ''); 1018 if($.trim(c.replace(/<[^>]*>/g, '')) === '') warnings.push('No event description/content.'); 1019 // W6: no location 1020 var lv = $('#mec_location_id').val(); if(!lv || lv === '0' || lv === '1') warnings.push('No location set.'); 1021 // W7: no organizer 1022 var ov = $('#mec_organizer_id').val(); if(!ov || ov === '0' || ov === '1') warnings.push('No organizer set.'); 1023 // W8: occurrences <= 1 1024 if($endRadio.val() === 'occurrences'){ var ow = parseInt($('#mec_date_repeat_end_at_occurrences').val(), 10); if(!isNaN(ow) && ow <= 1) warnings.push('Repeat ends after only ' + ow + ' occurrence(s).'); } 1025 // W10: zero duration 1026 if(!$('#mec_allday').is(':checked')){ var sT = mecVLTime24('mec_start_'), eT = mecVLTime24('mec_end_'); if(sT && eT && sT.hour === eT.hour && sT.min === eT.min) warnings.push('Start and end times are identical (zero duration).'); } 1027 1028 if(!warningsAcknowledged && warnings.length > 0) 1029 { 1030 e.preventDefault(); 1031 mecNotice({ 1032 type:'warning',
1033 title:'Publishing with warnings', 1034 items: warnings, 1035 primaryText:'Publish anyway', 1036 primaryAction: function(){ warningsAcknowledged = true; $('#publish').trigger('click'); }, 1037 dismissText:'Go back and fix' 1038 }); 1039 return; 1040 } 1041 }); 1042 })(); 1043 1044 // ===== Unsaved-changes protection for MEC meta boxes ===== 1045 // Audit issue #10. Warn before navigating away with unsaved MEC edits. 1046 (function() 1047 { 1048 var mecDirty = false; 1049 var $scope = $('#mec_metabox_details, #mec_metabox_booking, #mec_metabox_visibility, #mec_metabox_gallery'); 1050 1051 // Ignore spurious change/input events fired by 3rd-party init scripts 1052 // (nice-select, select2, color picker, etc.) before the user interacts. 1053 var mecUserInteracted = false; 1054 $('#post').one('mousedown keydown', function() { mecUserInteracted = true; }); 1055 1056 $scope.on('change input', 'input, select, textarea', function() { 1057 if(mecUserInteracted) mecDirty = true; 1058 }); 1059 1060 // Clear the dirty flag when the post actually submits. Must run AFTER 1061 // the validation handler so preventDefault (invalid form) keeps the flag. 1062 $('#post').on('submit', function(e) 1063 { 1064 if(!e.isDefaultPrevented()) mecDirty = false; 1065 }); 1066 1067 $(window).on('beforeunload', function(e) 1068 { 1069 if(mecDirty) 1070 { 1071 e.preventDefault(); 1072 e.returnValue = ''; 1073 return ''; 1074 } 1075 }); 1076 })(); 1077 1078 $('#mec_date_repeat_end_at_date').datepicker( 1079 { 1080 changeYear: true, 1081 changeMonth: true, 1082 dateFormat: datepicker_format, 1083 gotoCurrent: true, 1084 yearRange: 'c-3:c+5', 1085 }); 1086 1087 $('.mec_date_picker_dynamic_format').datepicker( 1088 { 1089 changeYear: true, 1090 changeMonth: true, 1091 dateFormat: datepicker_format, 1092 gotoCurrent: true, 1093 yearRange: 'c-3:c+5', 1094 }); 1095 1096 // End Repeat: conditional fields driven by the type <select> (was radios). 1097 // Date field shows only for "date", occurrences field only for "occurrences". 1098 $('#mec_repeat_end_type').on('change', function() 1099 { 1100 var v = $(this).val(); 1101 $('.mec-end-repeat-date-wrap').toggleClass('mec-is-hidden', v !== 'date'); 1102 $('.mec-end-repeat-occurrences-wrap').toggleClass('mec-is-hidden', v !== 'occurrences'); 1103 }); 1104 1105 $('.mec_date_picker').datepicker( 1106 { 1107 changeYear: true, 1108 changeMonth: true, 1109 dateFormat: 'yy-mm-dd', 1110 gotoCurrent: true, 1111 yearRange: 'c-3:c+5', 1112 }); 1113 1114 $('.mec_date_picker_dynamic_format_start').datepicker( 1115 { 1116 changeYear: true, 1117 changeMonth: true, 1118 dateFormat: datepicker_format, 1119 gotoCurrent: true, 1120 yearRange: 'c-1:c+5', 1121 onSelect: function(date) 1122 { 1123 var selectedDate; 1124 1125 try 1126 { 1127 selectedDate = $.datepicker.parseDate(datepicker_format, date); 1128 } 1129 catch(e) 1130 { 1131 selectedDate = new Date(date); 1132 } 1133 1134 if(!(selectedDate instanceof Date) || isNaN(selectedDate.getTime())) return; 1135 1136 var endDate = new Date(selectedDate.getTime()); 1137 1138 var $end_picker = $(this).next(); 1139 $end_picker.datepicker("option", "minDate", endDate); 1140 $end_picker.datepicker("option", "maxDate", '+5y'); 1141 1142 $(this).trigger('change'); 1143 } 1144 }); 1145 1146 $('.mec_date_picker_dynamic_format_end').datepicker( 1147 { 1148 changeYear: true, 1149 changeMonth: true, 1150 dateFormat: datepicker_format, 1151 gotoCurrent: true, 1152 yearRange: 'c-1:c+5', 1153 }); 1154
1155 trigger_period_picker(); 1156 } 1157 1158 // Initialize WP Color Picker 1159 if($.fn.wpColorPicker) jQuery('.mec-color-picker').wpColorPicker(); 1160 1161 $('#mec_location_id').on('change', function() 1162 { 1163 mec_location_toggle(); 1164 1165 // Mirror of the Other Organizers behavior: "Other Locations" is only 1166 // available once a real main location is selected (not "Hide location" = 1). 1167 var mec_location_val = parseInt($(this).val()); 1168 if(mec_location_val != 1) $('#mec-additional-location-wrap').show(); 1169 else $('#mec-additional-location-wrap').hide(); 1170 }); 1171 1172 $('#mec_organizer_id').on('change', function() 1173 { 1174 mec_organizer_toggle(); 1175 var mec_organizer_val = parseInt($(this).val()); 1176 1177 // Direct ID lookup â the wrap is a sibling of the select's row, so 1178 // the old ".parent().parent().find(...)" traversal never reached it. 1179 if(mec_organizer_val != 1) $('#mec-additional-organizer-wrap').show(); 1180 else $('#mec-additional-organizer-wrap').hide(); 1181 }); 1182 1183 mec_location_toggle(); 1184 mec_organizer_toggle(); 1185 1186 $('#mec_repeat').on('change', function() 1187 { 1188 mec_repeat_toggle(); 1189 }); 1190 1191 mec_repeat_toggle(); 1192 1193 $('#mec_repeat_type').on('change', function() 1194 { 1195 mec_repeat_type_toggle(); 1196 }); 1197 1198 mec_repeat_type_toggle(); 1199 1200 // "This event is free" toggle: sets the cost field to 0 (which the frontend 1201 // renders as "Free") and locks it, preserving the previous value to restore. 1202 // Audit issue #15. 1203 (function() 1204 { 1205 var $toggle = $('#mec_cost_is_free'); 1206 var $cost = $('#mec_cost'); 1207 if(!$toggle.length || !$cost.length) return; 1208 1209 function applyFree() 1210 { 1211 if($toggle.is(':checked')) 1212 { 1213 $cost.data('prev-cost', $cost.val()); 1214 $cost.val('0').prop('readonly', true).addClass('mec-cost-locked'); 1215 } 1216 else 1217 { 1218 $cost.prop('readonly', false).removeClass('mec-cost-locked'); 1219 var prev = $cost.data('prev-cost'); 1220 if(typeof prev !== 'undefined') $cost.val(prev); 1221 } 1222 } 1223 1224 $toggle.on('change', applyFree); 1225 applyFree(); 1226 })(); 1227 1228 // ===== Accessibility: ARIA tabs + keyboard nav + focusable tooltips ===== 1229 // Audit issue #3. Enhances the existing tab markup in place (no PHP changes). 1230 function mecEnhanceTabs(cfg) 1231 { 1232 var $ctx = $(cfg.context); 1233 if(!$ctx.length) return; 1234 var $tablist = $ctx.find(cfg.tablist); 1235 var $tabs = $ctx.find(cfg.tab); 1236 var $panels = $ctx.find(cfg.panel); 1237 if(!$tabs.length) return; 1238 1239 $tablist.attr('role', 'tablist'); 1240 1241 $tabs.each(function() { 1242 var $t = $(this); 1243 var panelId = $t.attr('data-href'); 1244 var tabId = $t.attr('id') || (panelId + '-tab'); 1245 var active = $t.hasClass('mec-tab-active'); 1246 $t.attr({ 1247 'id': tabId, 1248 'role': 'tab', 1249 'aria-controls': panelId, 1250 'aria-selected': active ? 'true' : 'false', 1251 'tabindex': active ? '0' : '-1' 1252 }); 1253 }); 1254 1255 $panels.each(function() { 1256 var $p = $(this); 1257 $p.attr({ 1258 'role': 'tabpanel', 1259 'aria-labelledby': $p.attr('id') + '-tab', 1260 'aria-hidden': $p.hasClass('mec-tab-active') ? 'false' : 'true' 1261 }); 1262 }); 1263 1264 function syncTabs() 1265 { 1266 $tabs.each(function() { 1267 var $t = $(this); 1268 var active = $t.hasClass('mec-tab-active'); 1269 $t.attr('aria-selected', active ? 'true' : 'false'); 1270 $t.attr('tabindex', active ? '0' : '-1'); 1271 }); 1272 $panels.each(function() { 1273 $(this).attr('aria-hidden', $(this).hasClass('mec-tab-active') ? 'false' : 'true'); 1274 }); 1275 } 1276 1277 // Keep ARIA state in sync after the existing inline click handlers switch tabs. 1278 $tablist.on('click', cfg.tab, function() { syncTabs(); }); 1279 1280 // Keyboard navigation (automatic activation): arrows + Home/End. 1281 $tablist.on('keydown', cfg.tab, function(e) { 1282 var idx = $tabs.index(this); 1283 var $target = null; 1284 switch(e.key) { 1285 case 'ArrowRight': case 'ArrowDown': $target = $tabs.eq(idx + 1); break; 1286 case 'ArrowLeft': case 'ArrowUp': $target = $tabs.eq(idx - 1); break; 1287 case 'Home': $target = $tabs.first(); break;
1288 case 'End': $target = $tabs.last(); break; 1289 case 'Enter': case ' ': 1290 e.preventDefault(); 1291 $(this).trigger('click'); 1292 this.focus(); 1293 return; 1294 default: return; 1295 } 1296 if($target && $target.length) { 1297 e.preventDefault(); 1298 $target.trigger('click'); // activate (existing handler runs, then syncTabs) 1299 $target[0].focus(); 1300 } 1301 }); 1302 } 1303 1304 mecEnhanceTabs({ 1305 context: '#mec_metabox_details', 1306 tablist: '.mec-add-event-tabs-left', 1307 tab: '.mec-add-event-tabs-link', 1308 panel: '.mec-event-tab-content' 1309 }); 1310 mecEnhanceTabs({ 1311 context: '#mec_metabox_booking', 1312 tablist: '.mec-add-booking-tabs-left', 1313 tab: '.mec-add-booking-tabs-link', 1314 panel: '.mec-booking-tab-content' 1315 }); 1316 1317 // Make hover-only help tooltips keyboard accessible (audit issue #3). 1318 $('.mec-tooltip').each(function(idx) { 1319 var $tip = $(this); 1320 var $box = $tip.find('.box').first(); 1321 var $icon = $tip.find('i').first(); 1322 if(!$box.length || !$icon.length) return; 1323 var boxId = $box.attr('id') || ('mec-tip-' + idx); 1324 $box.attr('id', boxId); 1325 $icon.attr({ 1326 'tabindex': '0', 1327 'role': 'button', 1328 'aria-describedby': boxId 1329 }); 1330 if(!$icon.attr('aria-label') && !$icon.attr('title')) { 1331 var title = $tip.find('.title').first().text(); 1332 $icon.attr('aria-label', title || 'More information'); 1333 } 1334 }); 1335 1336 // ===== Recurrence summary + next-5 preview ===== 1337 // Audit issue #6. Live, human-readable rule summary + next occurrences, 1338 // computed from the event start date (not "today"). 1339 (function() 1340 { 1341 var $box = $('#mec-repeat-preview'); 1342 var $body = $box.find('.mec-repeat-preview-body'); 1343 var $form = $('#mec_meta_box_repeat_form'); 1344 if(!$box.length || !$body.length || !$form.length) return; 1345 1346 var loc = (typeof mec_admin_localize !== 'undefined') ? mec_admin_localize : ((typeof mecdata !== 'undefined') ? mecdata : {}); 1347 var units = loc.interval_units || {}; 1348 var PREVIEW_MAX = 5; 1349 1350 function parseDate($field) 1351 { 1352 var raw = $.trim($field.val() || ''); 1353 if(!raw) return null; 1354 try { return $.datepicker.parseDate(datepicker_format, raw); } 1355 catch(e) { var d = new Date(raw); return isNaN(d.getTime()) ? null : d; } 1356 } 1357 1358 function fmt(date) 1359 { 1360 try { return $.datepicker.formatDate(datepicker_format, date); } 1361 catch(e) { return date.toDateString(); } 1362 } 1363 1364 function addDays(d, n){ var x = new Date(d.getTime()); x.setDate(x.getDate() + n); return x; } 1365 function addMonths(d, n){ var x = new Date(d.getTime()); x.setMonth(x.getMonth() + n); return x; } 1366 function addYears(d, n){ var x = new Date(d.getTime()); x.setFullYear(x.getFullYear() + n); return x; } 1367 1368 // MEC weekday number (1=Mon..7=Sun) -> JS getDay() (0=Sun..6=Sat) 1369 function mecDayToJs(n){ n = parseInt(n, 10); return n === 7 ? 0 : n; } 1370 1371 function readEnd() 1372 { 1373 var end = ($('#mec_repeat_end_type').length ? $('#mec_repeat_end_type').val() : $('input[name="mec[date][repeat][end]"]:checked').val()); 1374 var endDate = null, maxOcc = null; 1375 if(end === 'date') endDate = parseDate($('#mec_date_repeat_end_at_date')); 1376 else if(end === 'occurrences') 1377 { 1378 var n = parseInt($('#mec_date_repeat_end_at_occurrences').val(), 10); 1379 if(n > 0) maxOcc = n; 1380 } 1381 return {type: end || 'never', endDate: endDate, maxOcc: maxOcc}; 1382 } 1383 1384 function generate(type, interval, start, weekdays, end) 1385 { 1386 var dates = []; 1387 var guard = 0; 1388 // Generate one extra beyond the display cap so the UI can detect 1389 // truncation and show a "+N more" chip. Only PREVIEW_MAX are shown. 1390 var max = end.maxOcc ? Math.min(end.maxOcc, PREVIEW_MAX + 1) : (PREVIEW_MAX + 1); 1391 var endDateMs = end.endDate ? end.endDate.getTime() : null; 1392 1393 function pushIfValid(d) 1394 { 1395 if(endDateMs !== null && d.getTime() > endDateMs) return false; 1396 dates.push(d); 1397 return dates.length < max; 1398 } 1399 1400 if(type === 'daily' || type === 'weekly') 1401 { 1402 var stepDays = (type === 'weekly' ? Math.max(1, interval) * 7 : Math.max(1, interval)); 1403 var d = new Date(start.getTime()); 1404 while(dates.length < max && guard++ < 10000){ if(!pushIfValid(d)) break; d = addDays(d, stepDays); } 1405 } 1406 else if(type === 'monthly') 1407 { 1408 var n = Math.max(1, interval); 1409 var d = new Date(start.getTime()); 1410 while(dates.length < max && guard++ < 10000){ if(!pushIfValid(d)) break; d = addMonths(d, n); } 1411 } 1412 else if(type === 'yearly') 1413 { 1414 var n = Math.max(1, interval); 1415 var d = new Date(start.getTime()); 1416 while(dates.length < max && guard++ < 10000){ if(!pushIfValid(d)) break; d = addYears(d, n); } 1417 } 1418 else if(type === 'weekday' || type === 'weekend' || type === 'certain_weekdays') 1419 { 1420 var target; 1421 if(type === 'weekday') target = [1, 2, 3, 4, 5]; 1422 else if(type === 'weekend') target = [0, 6]; 1423 else target = weekdays.map(mecDayToJs); 1424 var d = new Date(start.getTime()); 1425 while(dates.length < max && guard++ < 40000) 1426 { 1427 if(target.indexOf(d.getDay()) !== -1){ if(!pushIfValid(d)) break; } 1428 if(endDateMs !== null && d.getTime() > endDateMs) break; 1429 d = addDays(d, 1); 1430 } 1431 } 1432 return dates; 1433 } 1434 1435 function buildSummary(type, interval, start, weekdays, end) 1436 { 1437 var s = ''; 1438 var u = {daily: units.daily || 'days', weekly: units.weekly || 'weeks', monthly: units.monthly || 'months', yearly: units.yearly || 'years'}; 1439 if(type === 'daily' || type === 'weekly' || type === 'monthly' || type === 'yearly') 1440 { 1441 var n = Math.max(1, interval); 1442 var unit = u[type]; 1443 s = (n === 1) ? ('Every ' + unit.replace(/s$/, '')) : ('Every ' + n + ' ' + unit); 1444 } 1445 else if(type === 'weekday') s = 'Every weekday (Mon-Fri)'; 1446 else if(type === 'weekend') s = 'Every weekend (Sat-Sun)'; 1447 else if(type === 'certain_weekdays') s = weekdays.length ? ('Weekly on ' + weekdays.length + ' day(s)') : 'No weekdays selected'; 1448 else if(type === 'custom_days') s = 'On the custom dates listed above'; 1449 else if(type === 'advanced') s = 'Advanced repeating pattern'; 1450 else s = type; 1451 1452 if(start) s += ', starting ' + fmt(start); 1453 if(end.type === 'date' && end.endDate) s += ', until ' + fmt(end.endDate); 1454 else if(end.type === 'occurrences' && end.maxOcc) s += ', for ' + end.maxOcc + ' occurrence' + (end.maxOcc === 1 ? '' : 's'); 1455 else s += ', no end'; 1456 return s; 1457 } 1458
1459 function update() 1460 { 1461 if(!$('#mec_repeat').is(':checked')) 1462 { 1463 $box.hide(); 1464 return; 1465 } 1466 $box.show(); 1467 1468 var type = $('#mec_repeat_type').val(); 1469 var interval = parseInt($('#mec_repeat_interval').val(), 10); 1470 if(isNaN(interval) || interval < 1) interval = 1; 1471 var start = parseDate($('#mec_start_date')); 1472 var weekdays = []; 1473 $('input[name="mec[date][repeat][certain_weekdays][]"]:checked').each(function(){ weekdays.push($(this).val()); }); 1474 var end = readEnd(); 1475 1476 var html = '<p class="mec-repeat-preview-summary">' + buildSummary(type, interval, start, weekdays, end) + '</p>'; 1477 1478 var previewable = ['daily', 'weekly', 'monthly', 'yearly', 'weekday', 'weekend', 'certain_weekdays']; 1479 if(start && previewable.indexOf(type) !== -1) 1480 { 1481 if(type === 'certain_weekdays' && !weekdays.length) 1482 { 1483 html += '<p class="mec-repeat-preview-empty">' + (loc.repeat_preview_no_weekdays || 'Select weekdays to preview dates.') + '</p>'; 1484 } 1485 else 1486 { 1487 var dates = generate(type, interval, start, weekdays, end); 1488 var total = ''; 1489 if(end.type === 'occurrences' && end.maxOcc) total = end.maxOcc; 1490 else if(end.type === 'date' && dates.length < PREVIEW_MAX) total = dates.length; 1491 1492 if(dates.length) 1493 { 1494 var shown = Math.min(dates.length, PREVIEW_MAX); 1495 var items = dates.slice(0, PREVIEW_MAX).map(function(d){ return '<li>' + fmt(d) + '</li>'; }).join(''); 1496 // Truncation indicator: when more occurrences exist than 1497 // shown, append a muted chip so the user understands the 5 1498 // dates are samples (resolves "5 shown vs. N total" confusion). 1499 if(dates.length > PREVIEW_MAX) 1500 { 1501 var moreText = 'â¦'; 1502 if(total !== '' && parseInt(total, 10) > shown) 1503 { 1504 moreText = (loc.repeat_preview_more || '+ %d more').replace('%d', parseInt(total, 10) - shown); 1505 } 1506 items += '<li class="mec-repeat-preview-more">' + moreText + '</li>'; 1507 } 1508 html += '<div class="mec-repeat-preview-dates"><span class="mec-repeat-preview-label">' + (loc.repeat_preview_next || 'Next occurrences (from start date)') + ':</span><ul>' + items + '</ul></div>'; 1509 } 1510 if(total !== '') html += '<span class="mec-repeat-preview-count">' + (loc.repeat_preview_total || 'This event will occur %d times in total.').replace('%d', total) + '</span>'; 1511 } 1512 } 1513 else if(!start) 1514 { 1515 html += '<p class="mec-repeat-preview-empty">' + (loc.repeat_preview_need_start || 'Enter a start date to preview dates.') + '</p>'; 1516 } 1517 1518 $body.html(html); 1519 } 1520 1521 $form.on('change input', 'input, select', update); 1522 $('#mec_start_date').on('change input', update); 1523 update(); 1524 })(); 1525 1526 // ===== Currency options live preview (audit #11) ===== 1527 // Mirrors main::render_price() so the sample matches the real front-end. 1528 (function() 1529 { 1530 var $preview = $('#mec_currency_preview'); 1531 if(!$preview.length) return; 1532 1533 var sample = parseFloat($preview.data('sample')); 1534 if(isNaN(sample)) sample = 1234.5; 1535 var defaultSign = $preview.data('default-sign') || '$'; 1536 1537 // Equivalent of PHP number_format(amount, decs, decChar, thouChar). 1538 function format(amount, decs, decChar, thouChar) 1539 { 1540 decs = Math.max(0, parseInt(decs, 10) || 0); 1541 var s = amount.toFixed(decs); 1542 var parts = s.split('.'); 1543 parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thouChar); 1544 return parts.length > 1 ? parts[0] + decChar + parts[1] : parts[0]; 1545 } 1546 1547 function val($el, fallback) 1548 { 1549 var v = $el.val(); 1550 return (v === null || v === '') ? fallback : v; 1551 } 1552
1553 function update() 1554 { 1555 var sign = $.trim($('#mec_currency_currency_symptom').val()) || defaultSign; 1556 var pos = val($('#mec_currency_currency_sign'), 'before'); 1557 var thou = val($('#mec_currency_thousand_separator'), ','); 1558 var decChar = val($('#mec_currency_decimal_separator'), '.'); 1559 // "No decimal" checkbox is checked => decimal_separator_status = 0 => no decimals. 1560 var noDec = $('#mec_currency_decimal_separator_status').is(':checked'); 1561 var decs = noDec ? 0 : Math.max(0, parseInt($('#mec_currency_decimals').val(), 10) || 0); 1562 1563 var num = format(sample, decs, (noDec ? '' : decChar), thou); 1564 var result; 1565 if(pos === 'after') result = num + sign; 1566 else if(pos === 'after_space') result = num + ' ' + sign; 1567 else if(pos === 'before_space') result = sign + ' ' + num; 1568 else result = sign + num; 1569 1570 $preview.text(result); 1571 } 1572 1573 $('#mec_currency_currency_symptom, #mec_currency_currency_sign, #mec_currency_thousand_separator, #mec_currency_decimal_separator, #mec_currency_decimals, #mec_currency_decimal_separator_status').on('input change', update); 1574 update(); 1575 })(); 1576 1577 // ===== Location duplicate-name guard ===== 1578 // Audit issue #14. Warn when a newly typed location name matches an existing one. 1579 (function() 1580 { 1581 var $name = $('#mec_location_name'); 1582 var $select = $('#mec_location_id'); 1583 if(!$name.length || !$select.length) return; 1584 1585 var loc = (typeof mec_admin_localize !== 'undefined') ? mec_admin_localize : ((typeof mecdata !== 'undefined') ? mecdata : {}); 1586 1587 // Build {lowercaseName: termId} from existing location options (exclude the special 0 / 1). 1588 var existing = {}; 1589 $select.find('option').each(function() 1590 { 1591 var v = $(this).attr('value'); 1592 if(v === '0' || v === '1') return; 1593 existing[$.trim($(this).text()).toLowerCase()] = v; 1594 }); 1595 1596 var $warn = null; 1597 function clearWarn(){ if($warn){ $warn.remove(); $warn = null; } } 1598 1599 function showWarn(existingId) 1600 { 1601 $warn = $('<div class="mec-location-duplicate-warn" role="alert"></div>'); 1602 $('<span class="mec-location-duplicate-text"></span>') 1603 .text(loc.location_duplicate_warn || 'A location with this name already exists.') 1604 .appendTo($warn); 1605 var $btn = $('<button type="button" class="button mec-location-use-existing"></button>') 1606 .text(loc.location_use_existing || 'Use existing'); 1607 $btn.on('click', function() 1608 { 1609 // Switch the select to the existing location; mec_location_toggle() hides the new form. 1610 $select.val(existingId).trigger('change'); 1611 clearWarn(); 1612 }); 1613 $warn.append(' ').append($btn); 1614 $name.closest('.mec-form-row').after($warn); 1615 } 1616 1617 $name.on('input change', function() 1618 { 1619 clearWarn(); 1620 var val = $.trim($name.val() || '').toLowerCase(); 1621 if(!val) return; 1622 if(existing.hasOwnProperty(val)) showWarn(existing[val]); 1623 }); 1624 })(); 1625 1626 $('#mec_bookings_limit_unlimited').on('change', function() 1627 { 1628 mec_bookings_unlimited_toggle(); 1629 }); 1630 1631 $('#mec_add_in_days').on('click', function() 1632 { 1633 var allday = $(this).data('allday'); 1634 1635 var start = $('#mec_exceptions_in_days_start_date').val(); 1636 if(start === '') return false; 1637 1638 var end = $('#mec_exceptions_in_days_end_date').val(); 1639 if(end === '') return false; 1640 1641 var start_hour = $('#mec_exceptions_in_days_start_hour').val(); 1642 if(start_hour.length === 1) start_hour = '0'+start_hour; 1643 1644 var start_minutes = $('#mec_exceptions_in_days_start_minutes').val(); 1645 if(start_minutes.length === 1) start_minutes = '0'+start_minutes; 1646 1647 var start_ampm = $('#mec_exceptions_in_days_start_ampm').val(); 1648 if(typeof start_ampm === 'undefined') start_ampm = ''; 1649 1650 var end_hour = $('#mec_exceptions_in_days_end_hour').val(); 1651 if(end_hour.length === 1) end_hour = '0'+end_hour; 1652 1653 var end_minutes = $('#mec_exceptions_in_days_end_minutes').val(); 1654 if(end_minutes.length === 1) end_minutes = '0'+end_minutes; 1655 1656 var end_ampm = $('#mec_exceptions_in_days_end_ampm').val(); 1657 if(typeof end_ampm === 'undefined') end_ampm = ''; 1658 1659 var value = start + ':' + end + ':' + start_hour + '-' + start_minutes + '-' + start_ampm + ':' + end_hour + '-' + end_minutes + '-' + end_ampm; 1660 var label = start + ' <span class="mec-time-picker-label '+(allday ? 'mec-util-hidden' : '')+'">' + start_hour + ':' + start_minutes + ' ' + start_ampm + '</span> - ' + end + ' <span class="mec-time-picker-label '+(allday ? 'mec-util-hidden' : '')+'">' + end_hour + ':' + end_minutes + ' ' + end_ampm + '</span>'; 1661 1662 // Don't add exactly same occurrences 1663 if($('#mec_in_days input[value="'+value+'"]').length > 0) return false; 1664 1665 var $key = $('#mec_new_in_days_key'); 1666 1667 var key = $key.val(); 1668 var html = $('#mec_new_in_days_raw').html().replace(/:i:/g, key).replace(/:val:
1668/g, value).replace(/:label:/g, label); 1669 1670 $('#mec_in_days').append(html); 1671 $key.val(parseInt(key)+1); 1672 }); 1673 1674 $('#mec_edit_in_days').on('click', function() 1675 { 1676 // Form 1677 const $form = $('#mec-in-days-form'); 1678 1679 const modify_id = $form.data('modify'); 1680 const $row = $('#mec_in_days_row'+modify_id); 1681 1682 var allday = $(this).data('allday'); 1683 1684 var start = $('#mec_exceptions_in_days_start_date').val(); 1685 if(start === '') return false; 1686 1687 var end = $('#mec_exceptions_in_days_end_date').val(); 1688 if(end === '') return false; 1689 1690 var start_hour = $('#mec_exceptions_in_days_start_hour').val(); 1691 if(start_hour.length === 1) start_hour = '0'+start_hour; 1692 1693 var start_minutes = $('#mec_exceptions_in_days_start_minutes').val(); 1694 if(start_minutes.length === 1) start_minutes = '0'+start_minutes; 1695 1696 var start_ampm = $('#mec_exceptions_in_days_start_ampm').val(); 1697 if(typeof start_ampm === 'undefined') start_ampm = ''; 1698 1699 var end_hour = $('#mec_exceptions_in_days_end_hour').val(); 1700 if(end_hour.length === 1) end_hour = '0'+end_hour; 1701 1702 var end_minutes = $('#mec_exceptions_in_days_end_minutes').val(); 1703 if(end_minutes.length === 1) end_minutes = '0'+end_minutes; 1704 1705 var end_ampm = $('#mec_exceptions_in_days_end_ampm').val(); 1706 if(typeof end_ampm === 'undefined') end_ampm = ''; 1707 1708 var value = start + ':' + end + ':' + start_hour + '-' + start_minutes + '-' + start_ampm + ':' + end_hour + '-' + end_minutes + '-' + end_ampm; 1709 var label = start + ' <span class="mec-time-picker-label '+(allday ? 'mec-util-hidden' : '')+'">' + start_hour + ':' + start_minutes + ' ' + start_ampm + '</span> - ' + end + ' <span class="mec-time-picker-label '+(allday ? 'mec-util-hidden' : '')+'">' + end_hour + ':' + end_minutes + ' ' + end_ampm + '</span>'; 1710 1711 $row.find($('input[type=hidden]')).val(value); 1712 $row.find($('.mec-in-days-day')).html(label); 1713 1714 // Reset Dates 1715 $form.parent().find($('input[type=text]')).val(''); 1716 1717 // Modification Mode 1718 $form.removeClass('mec-in-days-edit-mode').addClass('mec-in-days-add-mode').removeData('modify'); 1719 }); 1720 1721 $('#mec_cancel_in_days').on('click', function() 1722 { 1723 // Form 1724 let $form = $('#mec-in-days-form'); 1725 1726 // Reset Dates 1727 $form.parent().find($('input[type=text]')).val(''); 1728 1729 // Modification Mode 1730 $form.removeClass('mec-in-days-edit-mode').addClass('mec-in-days-add-mode').removeData('modify'); 1731 }); 1732 1733 // Keyboard support for Custom Days card action buttons (audit #6). 1734 $('#mec_in_days').on('keydown', '[role="button"]', function(e) 1735 { 1736 if(e.key === 'Enter' || e.key === ' '){ e.preventDefault(); this.click(); } 1737 }); 1738 1739 $('#mec_add_not_in_days').on('click', function() 1740 { 1741 let date = $('#mec_exceptions_not_in_days_date').val(); 1742 if(date === '') return false; 1743 1744 let d = date.replaceAll('-', ''); 1745 d = d.replaceAll('/', ''); 1746 d = d.replaceAll('.', ''); 1747 1748 1749 let $wrapper = $('#mec_not_in_days'); 1750 let $key = $('#mec_new_not_in_days_key'); 1751 1752 let c = 'mec-date-'+d; 1753 if($wrapper.find($('.'+c)).length) return; 1754 1755 let key = $key.val(); 1756 let html = $('#mec_new_not_in_days_raw').html().replace(/:i:/g, key).replace(/:d:/g, d).replace(/:val:/g, date); 1757 1758 $wrapper.append(html); 1759 $key.val(parseInt(key)+1); 1760 }); 1761 1762 $('#mec_add_ticket_button').on('click', function() 1763 { 1764 let $key = $('#mec_new_ticket_key'); 1765 let key = $key.val(); 1766 let html = $('#mec_new_ticket_raw').html().replace(/:i:/g, key); 1767 1768 $('#mec_tickets').append(html); 1769 $key.val(parseInt(key)+1); 1770 1771 mec_init_sortable_sections(); 1772 1773 $('.mec_add_price_date_button').off('click').on('click', function() 1774 { 1775 mec_handle_add_price_date_button(this); 1776 }); 1777 1778 $.each($(".mec-select2"), function(i,v) 1779 { 1780 if($(v).attr('name').search(":i:") > 0) 1781 { 1782 return; 1783 } 1784 1785 if(typeof $(v).data('select2-id') == 'undefined') 1786 { 1787 $(v).select2(); 1788 } 1789 }); 1790
1791 trigger_period_picker(); 1792 }); 1793 1794 $('.mec_add_price_date_button').off('click').on('click', function() 1795 { 1796 mec_handle_add_price_date_button(this); 1797 }); 1798 1799 mec_hourly_schedule_add_day_listener(); 1800 mec_init_sortable_sections(); 1801 1802 $('#mec_add_fee_button').on('click', function() 1803 { 1804 var key = $('#mec_new_fee_key').val(); 1805 var html = $('#mec_new_fee_raw').html().replace(/:i:/g, key); 1806 1807 $('#mec_fees_list').append(html); 1808 $('#mec_new_fee_key').val(parseInt(key)+1); 1809 1810 mec_init_sortable_sections(); 1811 }); 1812 1813 $('#mec_add_ticket_variation_button').on('click', function() 1814 { 1815 var key = $('#mec_new_ticket_variation_key').val(); 1816 var html = $('#mec_new_ticket_variation_raw').html().replace(/:i:/g, key); 1817 1818 $('#mec_ticket_variations_list').append(html); 1819 $('#mec_new_ticket_variation_key').val(parseInt(key)+1); 1820 1821 mec_init_sortable_sections(); 1822 }); 1823 1824 $('.mec-form-row.mec-available-color-row span').on('click', function() 1825 { 1826 $('.mec-form-row.mec-available-color-row span').removeClass('color-selected'); 1827 $(this).addClass('color-selected'); 1828 }); 1829 1830 $('#mec_reg_form_field_types button').on('click', function() 1831 { 1832 var type = $(this).data('type'); 1833 1834 if (type == 'mec_email') { 1835 if ($('#mec_reg_form_fields').find('input[value="mec_email"][type="hidden"]').length) { 1836 return false; 1837 } 1838 } 1839 1840 if (type == 'name') { 1841 if ($('#mec_reg_form_fields').find('input[value="name"][type="hidden"]').length) { 1842 return false; 1843 } 1844 } 1845 1846 var key = $('#mec_new_reg_field_key').val(); 1847 var html = $('#mec_reg_field_'+type).html().replace(/:i:/g, key); 1848 1849 $('#mec_reg_form_fields').append(html); 1850 $('#mec_new_reg_field_key').val(parseInt(key)+1); 1851 1852 // Set onclick listener for add option fields 1853 mec_reg_fields_option_listeners(); 1854 mec_refresh_booking_condition_editors('reg'); 1855 }); 1856 1857 // Set onclick listener for add option fields 1858 mec_reg_fields_option_listeners(); 1859 mec_refresh_booking_condition_editors('reg'); 1860 1861 // Advanced Repeating 1862 $('#mec-advanced-wraper ul > ul > li').click(function() 1863 { 1864 if($(this).attr('class') == '') $(this).attr('class', 'mec-active'); 1865 else $(this).attr('class', ''); 1866 1867 $('#mec_date_repeat_advanced').val($('#mec-advanced-wraper div:first-child > ul').find('.mec-active').find('span').text().slice(0, -1)); 1868 }); 1869 1870 $('#mec_event_form_field_types button').on('click', function() 1871 { 1872 var type = $(this).data('type'); 1873 1874 var key = $('#mec_new_event_field_key').val(); 1875 var html = $('#mec_event_field_'+type).html().replace(/:i:/g, key); 1876 1877 $('#mec_event_form_fields').append(html); 1878 $('#mec_new_event_field_key').val(parseInt(key)+1); 1879 1880 // Set onclick listener for add option fields 1881 mec_event_fields_option_listeners(); 1882 }); 1883 1884 // Set onclick listener for add option fields 1885 mec_event_fields_option_listeners(); 1886 1887 $('#mec_bfixed_form_field_types button').on('click', function() 1888 { 1889 var type = $(this).data('type'); 1890 1891 var key = $('#mec_new_bfixed_field_key').val(); 1892 var html = $('#mec_bfixed_field_'+type).html().replace(/:i:/g, key); 1893 1894 $('#mec_bfixed_form_fields').append(html); 1895 $('#mec_new_bfixed_field_key').val(parseInt(key)+1); 1896 1897 // Set onclick listener for add option fields 1898 mec_bfixed_fields_option_listeners(); 1899 mec_refresh_booking_condition_editors('bfixed'); 1900 }); 1901 1902 // Set onclick listener for add option fields 1903 mec_bfixed_fields_option_listeners(); 1904 mec_refresh_booking_condition_editors('bfixed'); 1905 1906 jQuery(document).on('change', '.mec-booking-condition-enabled, .mec-booking-condition-source, .mec-booking-condition-option', function() 1907 { 1908 const $trigger = jQuery(this); 1909 const $conditionBox = jQuery(this).closest('.mec-booking-condition-box'); 1910 const prefix = String($conditionBox.data('conditionPrefix') || ''); 1911 1912 if($trigger.hasClass('mec-booking-condition-source')) 1913 { 1914 $conditionBox.data('currentSourceFieldId', String($trigger.val() || '')); 1915 $conditionBox.data('currentOptionKey', ''); 1916 $conditionBox.find('.mec-booking-condition-option').val(''); 1917 } 1918 else if($trigger.hasClass('mec-booking-condition-option')) 1919 { 1920 $conditionBox.data('currentOptionKey', String($trigger.val() || '')); 1921 } 1922 1923 mec_refresh_booking_condition_editor($conditionBox); 1924 if(prefix) mec_refresh_booking_condition_editors(prefix); 1925 });
1926 1927 jQuery(document).on('input change', '#mec_reg_form_fields input[name*="[label]"], #mec_bfixed_form_fields input[name*="[label]"]', function() 1928 { 1929 const $field = jQuery(this).closest('li[id^="mec_reg_fields_"], li[id^="mec_bfixed_fields_"]'); 1930 if($field.attr('id') && $field.attr('id').indexOf('mec_reg_fields_') === 0) mec_refresh_booking_condition_editors('reg'); 1931 if($field.attr('id') && $field.attr('id').indexOf('mec_bfixed_fields_') === 0) mec_refresh_booking_condition_editors('bfixed'); 1932 }); 1933 1934 // Additional Organizers 1935 mec_additional_organizers_listeners(); 1936 1937 // Additional Locations 1938 mec_additional_locations_listeners(); 1939 1940 // Show / Hide Password 1941 $('.mec-show-hide-password').on('click', function() 1942 { 1943 var $input = $(this).siblings("input"); 1944 var current = $input.attr('type'); 1945 1946 if(current === 'password') $input.attr('type', 'text'); 1947 else $input.attr('type', 'password'); 1948 }); 1949 1950 // FAQ 1951 $('#mec_add_faq_button').on('click', function() 1952 { 1953 const $key = $('#mec_new_faq_key'); 1954 const key = $key.val(); 1955 const html = $('#mec_new_faq_raw').html().replace(/:i:/g, key); 1956 1957 $('#mec_faq_list').append(html); 1958 $key.val(parseInt(key)+1); 1959 1960 mec_init_sortable_sections(); 1961 }); 1962 1963 // Appointments 1964 // Move the selector to the metabox header 1965 var $header = $('#mec_metabox_details .hndle'); 1966 // If .hndle is not found, try .postbox-header (newer WP versions) 1967 if ($header.length === 0) { 1968 $header = $('#mec_metabox_details .postbox-header'); 1969 } 1970 $('.mec-event-appointment-type-wrap').appendTo($header).removeClass('mec-util-hidden').on('click', function(e) { 1971 e.stopPropagation(); 1972 }); 1973 1974 $('#mec_entity_type_select').on('change', function() 1975 { 1976 const entity_type = $(this).val(); 1977 const $fes_form = $('#mec_fes_form'); 1978 const $metabox_details = $('#mec_metabox_details'); 1979 const $appointment_form_wrapper = $('.mec-appointment-form-wrap'); 1980 1981 if (entity_type === 'appointment') 1982 { 1983 $fes_form.removeClass('mec-entity-type-event').addClass('mec-entity-type-appointment'); 1984 $metabox_details.removeClass('mec-entity-type-event').addClass('mec-entity-type-appointment'); 1985 1986 $appointment_form_wrapper.removeClass('mec-util-hidden'); 1987 } 1988 else 1989 { 1990 $fes_form.removeClass('mec-entity-type-appointment').addClass('mec-entity-type-event'); 1991 $metabox_details.removeClass('mec-entity-type-appointment').addClass('mec-entity-type-event'); 1992 1993 $appointment_form_wrapper.addClass('mec-util-hidden'); 1994 } 1995 }); 1996 1997 $('#mec_entity_type_select').trigger('change'); 1998 1999 const $repeatType = $('#mec_appointments_availability_repeat_type'); 2000 if($.fn.datepicker) 2001 { 2002 $('#mec_appointments_start_date').datepicker({ 2003 changeYear: true, 2004 changeMonth: true, 2005 dateFormat: datepicker_format, 2006 gotoCurrent: true, 2007 yearRange: 'c-3:c+5', 2008 }); 2009 } 2010 function mec_toggle_repeat_type() 2011 { 2012 const val = $repeatType.val(); 2013 if (val === 'no_repeat') 2014 { 2015 $('.lsd-apt-days-wrapper').addClass('mec-util-hidden'); 2016 $('.lsd-apt-adjusted-title').addClass('mec-util-hidden'); 2017 $('.lsd-apt-start-date-wrapper').addClass('mec-util-hidden'); 2018 } 2019 else 2020 { 2021 $('.lsd-apt-days-wrapper').removeClass('mec-util-hidden'); 2022 $('.lsd-apt-adjusted-title').removeClass('mec-util-hidden'); 2023 $('.lsd-apt-start-date-wrapper').removeClass('mec-util-hidden'); 2024 } 2025 } 2026 $repeatType.on('change', mec_toggle_repeat_type); 2027 mec_toggle_repeat_type(); 2028 2029 $(document).on('click', '.lsd-apt-day-icon-remove', function() 2030 { 2031 const $button = $(this); 2032 const $timeslot = $button.parent(); 2033 const $timeslots = $button.parents('.lsd-apt-day-timeslots'); 2034 2035 $timeslot.remove(); 2036 if ($timeslots.find('.lsd-apt-day-timeslot-wrapper').length === 0) 2037 { 2038 $timeslots.find('.lsd-apt-day-timeslots-unavailable').removeClass('mec-util-hidden'); 2039 } 2040 });
2041 2042 $(document).on('click', '.lsd-apt-day-icon-plus', function() 2043 { 2044 const $button = $(this); 2045 let key = $button.data('key'); 2046 2047 const $day = $button.parents('.lsd-apt-day-wrapper'); 2048 const day = $day.data('key'); 2049 2050 const $timeslots = $day.find('.lsd-apt-day-timeslots-wrapper'); 2051 const $template = $('#lsd-apt-day-templates-'+day+'-timeslot'); 2052 2053 key = key + 1; 2054 const html = $template.html().replace(/:t:/g, key); 2055 $timeslots.append(html); 2056 2057 $button.data('key', key); 2058 $day.find('.lsd-apt-day-timeslots-unavailable').addClass('mec-util-hidden'); 2059 }); 2060 2061 $(document).on('click', '.lsd-apt-day-icon-copy', function () 2062 { 2063 const $button = $(this); 2064 const $sourceDay = $button.closest('.lsd-apt-day-wrapper'); 2065 const sourceDayKey = $sourceDay.data('key'); 2066 2067 const $weekWrapper = $sourceDay.parent(); 2068 const $sourceTimeslots = $sourceDay.find('.lsd-apt-day-timeslots-wrapper .lsd-apt-day-timeslot-wrapper'); 2069 2070 $weekWrapper.find('.lsd-apt-day-wrapper').each(function () 2071 { 2072 const $targetDay = $(this); 2073 2074 const targetDayKey = $targetDay.data('key'); 2075 if (targetDayKey === sourceDayKey) return; 2076 2077 const isActive = $targetDay.find('.lsd-apt-day-timeslots-wrapper .lsd-apt-day-timeslot-wrapper').length > 0; 2078 if (!isActive) return; 2079 2080 const $targetWrapper = $targetDay.find('.lsd-apt-day-timeslots-wrapper'); 2081 $targetWrapper.empty(); 2082 2083 $sourceTimeslots.each(function () 2084 { 2085 const $sourceSlot = $(this); 2086 const $clone = $sourceSlot.clone(true); 2087 2088 $sourceSlot.find('select').each(function (i) { 2089 const value = $(this).val(); 2090 $clone.find('select').eq(i).val(value); 2091 }); 2092 2093 $sourceSlot.find('input').each(function (i) { 2094 const value = $(this).val(); 2095 $clone.find('input').eq(i).val(value); 2096 }); 2097 2098 $clone.find('[name], [id], [for]').each(function () 2099 { 2100 $.each(this.attributes, function () { 2101 if (this.name === 'name' || this.name === 'id' || this.name === 'for') { 2102 this.value = this.value 2103 .replace(`[availability][${sourceDayKey}]`, `[availability][${targetDayKey}]`) 2104 .replace(`availability_${sourceDayKey}_`, `availability_${targetDayKey}_`); 2105 } 2106 }); 2107 }); 2108 2109 $targetWrapper.append($clone); 2110 }); 2111 }); 2112 }); 2113 2114 $(document).on('click', '.lsd-apt-adjusted-day-add', function() 2115 { 2116 const $wrapper = $('.lsd-apt-adjusted-days-wrapper'); 2117 let key = $wrapper.data('key') || 0; 2118 2119 key = key + 1; 2120 let html = $('#lsd-apt-adjusted-template-day').html(); 2121 html = html.replace(/:i:/g, key); 2122 2123 $wrapper.append(html); 2124 $wrapper.data('key', key); 2125 2126 if($.fn.datepicker) 2127 { 2128 $wrapper.find('.mec-apt-date-picker').last().datepicker({ 2129 changeYear: true, 2130 changeMonth: true, 2131 dateFormat: datepicker_format, 2132 gotoCurrent: true, 2133 yearRange: 'c-3:c+5', 2134 }); 2135 } 2136 }); 2137 2138 $(document).on('click', '.lsd-apt-adjusted-day-remove', function() 2139 { 2140 $(this).closest('.lsd-apt-day-wrapper').remove(); 2141 }); 2142 2143 $(document).on('click', '.lsd-apt-adj-day-icon-plus', function() 2144 { 2145 const $button = $(this); 2146 let key = $button.data('key'); 2147 const $day = $button.closest('.lsd-apt-day-wrapper'); 2148 const day = $day.data('day'); 2149 const $timeslots = $day.find('.lsd-apt-day-timeslots-wrapper'); 2150 2151 key = key + 1; 2152 let html = $('#lsd-apt-adjusted-template-timeslot').html(); 2153 2154 html = html.replace(/:i:/g, day).replace(/:t:/g, key); 2155 $timeslots.append(html); 2156 2157 $button.data('key', key); 2158 $day.find('.lsd-apt-day-timeslots-unavailable').addClass('mec-util-hidden'); 2159 });
2160}); 2161 2162function trigger_period_picker() 2163{ 2164 jQuery('.mec-date-picker-start').datepicker( 2165 { 2166 changeYear: true, 2167 changeMonth: true, 2168 dateFormat: 'yy-mm-dd', 2169 gotoCurrent: true, 2170 yearRange: 'c-1:c+5', 2171 onSelect: function(date) 2172 { 2173 const selectedDate = new Date(date); 2174 const endDate = new Date(selectedDate.getTime()); 2175 2176 const $end_picker = jQuery(this).next(); 2177 $end_picker.datepicker("option", "minDate", endDate); 2178 $end_picker.datepicker("option", "maxDate", '+5y'); 2179 jQuery(this).trigger('change'); 2180 } 2181 }); 2182 2183 jQuery('.mec-date-picker-end').datepicker( 2184 { 2185 changeYear: true, 2186 changeMonth: true, 2187 dateFormat: 'yy-mm-dd', 2188 gotoCurrent: true, 2189 yearRange: 'c-1:c+5', 2190 }); 2191} 2192 2193function mec_location_toggle() 2194{ 2195 if(jQuery('#mec_location_id').val() != '0') jQuery('#mec_location_new_container').hide(); 2196 else jQuery('#mec_location_new_container').show(); 2197} 2198 2199function mec_organizer_toggle() 2200{ 2201 if(jQuery('#mec_organizer_id').val() != '0') jQuery('#mec_organizer_new_container').hide(); 2202 else jQuery('#mec_organizer_new_container').show(); 2203} 2204 2205function mec_repeat_toggle() 2206{ 2207 if(jQuery('#mec_repeat').is(':checked')) jQuery('.mec-form-repeating-event-row').show(); 2208 else jQuery('.mec-form-repeating-event-row').hide(); 2209} 2210 2211// Update the "Repeat Interval" unit suffix (days/weeks/months/years) based on 2212// the selected repeat type. Audit issue #17. 2213function mec_update_interval_unit() 2214{ 2215 var $unit = jQuery('#mec_repeat_interval').siblings('.mec-interval-unit'); 2216 if(!$unit.length) return; 2217 var loc = (typeof mec_admin_localize !== 'undefined') ? mec_admin_localize : ((typeof mecdata !== 'undefined') ? mecdata : {}); 2218 var units = loc.interval_units || {}; 2219 var type = jQuery('#mec_repeat_type').val(); 2220 $unit.text(units[type] || ''); 2221} 2222 2223function mec_repeat_type_toggle() 2224{ 2225 var repeat_type = jQuery('#mec_repeat_type').val(); 2226 2227 if(repeat_type == 'certain_weekdays') 2228 { 2229 jQuery('#mec_repeat_interval_container').hide(); 2230 jQuery('#mec_repeat_certain_weekdays_container').show(); 2231 jQuery('#mec_exceptions_in_days_container').hide(); 2232 jQuery('#mec_end_wrapper').show(); 2233 jQuery('#mec-advanced-wraper').hide(); 2234 } 2235 else if(repeat_type == 'custom_days') 2236 { 2237 jQuery('#mec_repeat_interval_container').hide(); 2238 jQuery('#mec_repeat_certain_weekdays_container').hide(); 2239 jQuery('#mec_exceptions_in_days_container').show(); 2240 jQuery('#mec_end_wrapper').hide(); 2241 jQuery('#mec-advanced-wraper').hide(); 2242 } 2243 else if(repeat_type == 'advanced') 2244 { 2245 jQuery('#mec_repeat_interval_container').hide(); 2246 jQuery('#mec_repeat_certain_weekdays_container').hide(); 2247 jQuery('#mec_exceptions_in_days_container').hide(); 2248 jQuery('#mec_end_wrapper').show(); 2249 jQuery('#mec-advanced-wraper').show(); 2250 } 2251 else if(repeat_type != 'daily' && repeat_type != 'weekly' && repeat_type != 'monthly') 2252 { 2253 jQuery('#mec_repeat_interval_container').hide(); 2254 jQuery('#mec_repeat_certain_weekdays_container').hide(); 2255 jQuery('#mec_exceptions_in_days_container').hide(); 2256 jQuery('#mec_end_wrapper').show(); 2257 jQuery('#mec-advanced-wraper').hide(); 2258 } 2259 else 2260 { 2261 jQuery('#mec_repeat_interval_container').show(); 2262 jQuery('#mec_repeat_certain_weekdays_container').hide(); 2263 jQuery('#mec_exceptions_in_days_container').hide(); 2264 jQuery('#mec_end_wrapper').show(); 2265 jQuery('#mec-advanced-wraper').hide(); 2266 } 2267 2268 mec_update_interval_unit(); 2269} 2270 2271function mec_in_days_remove(i) 2272{ 2273 var $row = jQuery('#mec_in_days_row'+i); 2274 // Fade-out animation before removal (audit #6). 2275 if($row.length) 2276 { 2277 $row.addClass('mec-in-days-removing'); 2278 setTimeout(function(){ jQuery('#mec_in_days_row'+i).remove(); }, 180); 2279 } 2280} 2281 2282function mec_in_days_edit(i) 2283{ 2284 // Date 2285 let $row = jQuery('#mec_in_days_row'+i); 2286 let value = $row.find(jQuery('input[type=hidden]')).val(); 2287 2288 const values = value.split(':'); 2289 const start_times = values[2].split('-') 2290 const end_times = values[3].split('-') 2291 2292 // Form 2293 let $form = jQuery('#mec-in-days-form'); 2294 2295 // Set Dates 2296 jQuery('#mec_exceptions_in_days_start_date').val(values[0]); 2297 jQuery('#mec_exceptions_in_days_end_date').val(values[1]); 2298 2299 // Set Times 2300 jQuery('#mec_exceptions_in_days_start_hour').val(parseInt(start_times[0])); 2301 jQuery('#mec_exceptions_in_days_start_minutes').val(parseInt(start_times[1])); 2302 jQuery('#mec_exceptions_in_days_start_ampm').val(start_times[2]); 2303 2304 jQuery('#mec_exceptions_in_days_end_hour').val(parseInt(end_times[0])); 2305 jQuery('#mec_exceptions_in_days_end_minutes').val(parseInt(end_times[1])); 2306 jQuery('#mec_exceptions_in_days_end_ampm').val(end_times[2]); 2307 2308 // Modification Mode 2309 $form.removeClass('mec-in-days-add-mode').addClass('mec-in-days-edit-mode').data('modify', i); 2310} 2311 2312function mec_not_in_days_remove(i) 2313{ 2314 jQuery('#mec_not_in_days_row'+i).remove(); 2315} 2316
2317function mec_bookings_unlimited_toggle() 2318{ 2319 jQuery('#mec_bookings_limit').toggleClass('mec-util-hidden'); 2320} 2321 2322function mec_hourly_schedule_add_day_listener() 2323{ 2324 jQuery('.mec-add-hourly-schedule-day-button').each(function() 2325 { 2326 jQuery(this).off('click').on('click', function() 2327 { 2328 var k = jQuery(this).data('key'); 2329 var raw = jQuery(this).data('raw'); 2330 var append = jQuery(this).data('append'); 2331 2332 var key = jQuery(k).val(); 2333 var html = jQuery(raw).html().replace(/:d:/g, key).replace(/:dd:/g, parseInt(key)+1); 2334 2335 jQuery(append).append(html); 2336 jQuery(k).val(parseInt(key)+1); 2337 2338 mec_hourly_schedule_listeners(); 2339 }); 2340 2341 mec_hourly_schedule_listeners(); 2342 }); 2343} 2344 2345function mec_bookings_after_occurrence_cancel_listener() 2346{ 2347 jQuery('.mec-occurrences-bookings-after-occurrences-cancel').off('change').on('change', function() 2348 { 2349 const $dropdown = jQuery(this); 2350 const value = $dropdown.val(); 2351 const $moveWrapper = $dropdown.next(); 2352 2353 if(value === 'move' || value === 'move_notify') 2354 { 2355 $moveWrapper.removeClass('w-hidden'); 2356 } 2357 else 2358 { 2359 $moveWrapper.addClass('w-hidden'); 2360 } 2361 }); 2362} 2363 2364function mec_hourly_schedule_listeners() 2365{ 2366 jQuery('.mec-add-hourly-schedule-button').off('click').on('click', function() 2367 { 2368 var prefix = jQuery(this).data('prefix'); 2369 var day = jQuery(this).data('day'); 2370 var $key = jQuery('#'+prefix+'mec_new_hourly_schedule_key'+day); 2371 2372 var key = $key.val(); 2373 var html = jQuery('#'+prefix+'mec_new_hourly_schedule_raw'+day).html().replace(/:i:/g, key).replace(/:d:/g, day).replace(); 2374 var g_field_id = prefix+"-hourly_schedules-"+day+"-schedules-:i:-description"; 2375 var field_id = prefix+"-hourly_schedules-"+day+"-schedules-"+key+"-description"; 2376 html = html.replace(g_field_id,field_id); 2377 html = html.replace(':k:',key); 2378 jQuery('#'+prefix+'mec_hourly_schedules'+day).append(html); 2379 $key.val(parseInt(key)+1); 2380 2381 wp.editor.initialize("mec"+field_id,{ 2382 tinymce: { 2383 wpautop: true, 2384 plugins : 'charmap colorpicker compat3x directionality fullscreen hr image lists media paste tabfocus textcolor wordpress wpautoresize wpdialogs wpeditimage wpemoji wpgallery wplink wptextpattern wpview', 2385 toolbar1: 'bold italic underline strikethrough | bullist numlist | blockquote hr wp_more | alignleft aligncenter alignright | link unlink | fullscreen | wp_adv', 2386 toolbar2: 'formatselect alignjustify forecolor | pastetext removeformat charmap | outdent indent | undo redo | wp_help' 2387 }, 2388 quicktags: true, 2389 mediaButtons: false, 2390 }); 2391 }); 2392 2393 jQuery(".mec-hourly-schedule-schedules").sortable({ 2394 handle: '.mec_field_sort' 2395 }); 2396} 2397 2398function mec_hourly_schedule_remove(day, i, prefix) 2399{ 2400 jQuery("#"+prefix+"mec_hourly_schedule_row"+day+'_'+i).remove(); 2401} 2402 2403function mec_hourly_schedule_day_remove(day, prefix) 2404{ 2405 jQuery("#"+prefix+"mec_meta_box_hourly_schedule_day_"+day).remove(); 2406} 2407 2408function mec_ticket_remove(i) 2409{ 2410 jQuery("#mec_ticket_row"+i).remove(); 2411} 2412 2413function mec_set_event_color(color) 2414{ 2415 try 2416 { 2417 jQuery("#mec_event_color").wpColorPicker('color', '#'+color); 2418 } 2419 catch(e) 2420 { 2421 jQuery("#mec_event_color").val(color); 2422 } 2423} 2424 2425function mec_remove_fee(key) 2426{ 2427 jQuery("#mec_fee_row"+key).remove(); 2428} 2429 2430function mec_remove_ticket_variation(key, id_prefix) 2431{ 2432 jQuery("#mec_"+id_prefix+"_row"+key).remove(); 2433} 2434 2435function add_variation_per_ticket(ticket_id) 2436{ 2437 var $input = jQuery('#mec_new_variation_per_ticket_key'); 2438 2439 var key = $input.val(); 2440 var html = jQuery('#mec_new_variation_per_ticket_raw'+ticket_id).html().replace(/:v:/g, key); 2441 2442 jQuery('#mec_ticket_variations_list'+ticket_id).append(html); 2443 $input.val(parseInt(key)+1); 2444 2445 mec_init_sortable_sections(); 2446} 2447 2448function mec_init_sortable_instance(selector, options) 2449{ 2450 jQuery(selector).each(function() 2451 { 2452 var $element = jQuery(this); 2453 2454 if(!$element.length) return; 2455 2456 if(typeof $element.data('ui-sortable') === 'undefined') 2457 { 2458 $element.sortable(options); 2459 } 2460 else 2461 { 2462 $element.sortable('refresh'); 2463 } 2464 });
2465} 2466 2467function mec_init_sortable_sections() 2468{ 2469 if(typeof jQuery.fn.sortable === 'undefined') return; 2470 2471 mec_init_sortable_instance('#mec_tickets', { handle: '.mec_field_sort', items: '> .mec_ticket_row' }); 2472 mec_init_sortable_instance('#mec_fees_list', { handle: '.mec_field_sort', items: '> .mec-box' }); 2473 mec_init_sortable_instance('[id^="mec_ticket_variations_list"]', { handle: '.mec_field_sort', items: '> .mec_ticket_variation_row' }); 2474 mec_init_sortable_instance('#mec_faq_list', { handle: '.mec_field_sort', items: '> .mec_faq_row' }); 2475} 2476 2477function mec_get_booking_condition_form_fields(prefix) 2478{ 2479 return jQuery('#mec_' + prefix + '_form_fields > li'); 2480} 2481 2482function mec_get_booking_condition_field_id($field, prefix) 2483{ 2484 return String(($field.attr('id') || '').replace('mec_' + prefix + '_fields_', '')); 2485} 2486 2487function mec_get_booking_condition_field_type($field, prefix) 2488{ 2489 const fieldId = mec_get_booking_condition_field_id($field, prefix); 2490 return String($field.find('input[name="mec[' + prefix + '_fields][' + fieldId + '][type]"]').val() || ''); 2491} 2492 2493function mec_get_booking_condition_field_label($field, prefix) 2494{ 2495 const fieldId = mec_get_booking_condition_field_id($field, prefix); 2496 const $labelInput = $field.find('input[name="mec[' + prefix + '_fields][' + fieldId + '][label]"]').first(); 2497 const fallback = jQuery.trim($field.find('.mec_' + prefix + '_field_type').first().text() || ''); 2498 2499 return jQuery.trim(($labelInput.val() || fallback || '')); 2500} 2501 2502function mec_get_booking_condition_field_option_list($field, prefix) 2503{ 2504 const fieldId = mec_get_booking_condition_field_id($field, prefix); 2505 const options = []; 2506 2507 $field.find('input[name^="mec[' + prefix + '_fields][' + fieldId + '][options]"][name$="[label]"]').each(function() 2508 { 2509 const name = jQuery(this).attr('name') || ''; 2510 const match = name.match(/\[options\]\[(.*?)\]\[label\]$/); 2511 if(!match) return; 2512 2513 options.push({ 2514 key: String(match[1]), 2515 label: jQuery.trim(jQuery(this).val() || '') 2516 }); 2517 }); 2518 2519 return options; 2520} 2521 2522function mec_booking_condition_field_has_duplicate_options($field, prefix) 2523{ 2524 const seen = {}; 2525 const options = mec_get_booking_condition_field_option_list($field, prefix); 2526 2527 for(let i = 0; i < options.length; i++) 2528 { 2529 const label = options[i].label; 2530 if(!label) continue; 2531 if(seen[label]) return true; 2532 2533 seen[label] = true; 2534 } 2535 2536 return false; 2537} 2538 2539function mec_refresh_booking_condition_editor($conditionBox) 2540{ 2541 if(!$conditionBox || !$conditionBox.length) return; 2542 2543 const prefix = String($conditionBox.data('conditionPrefix') || ''); 2544 const currentFieldId = String($conditionBox.data('conditionFieldId') || ''); 2545 const $enabled = $conditionBox.find('.mec-booking-condition-enabled'); 2546 const $controls = $conditionBox.find('.mec-booking-condition-controls'); 2547 const $source = $conditionBox.find('.mec-booking-condition-source'); 2548 const $option = $conditionBox.find('.mec-booking-condition-option'); 2549 const $matchType = $conditionBox.find('.mec-booking-condition-match-type'); 2550 const $message = $conditionBox.find('.mec-booking-condition-message'); 2551 const enabled = $enabled.is(':checked'); 2552 const strings = (typeof mec_admin_localize !== 'undefined' && mec_admin_localize.booking_conditions) ? mec_admin_localize.booking_conditions : {}; 2553 2554 $controls.toggleClass('mec-util-hidden', !enabled); 2555 if(!enabled) 2556 { 2557 $message.text(''); 2558 return; 2559 } 2560 2561 const currentSourceId = String($source.val() || $conditionBox.data('currentSourceFieldId') || ''); 2562 const currentOptionKey = String($option.val() || $conditionBox.data('currentOptionKey') || ''); 2563 const sourceFields = []; 2564 const sourceMap = {}; 2565 2566 mec_get_booking_condition_form_fields(prefix).each(function() 2567 { 2568 const $field = jQuery(this); 2569 const fieldId = mec_get_booking_condition_field_id($field, prefix); 2570 const fieldType = mec_get_booking_condition_field_type($field, prefix); 2571 2572 if(!fieldId) return; 2573 if(fieldId === currentFieldId) return false; 2574 if(jQuery.inArray(fieldType, ['checkbox', 'radio', 'select', 'agreement']) === -1) return; 2575 if(jQuery.inArray(fieldType, ['checkbox', 'radio', 'select']) !== -1 && mec_booking_condition_field_has_duplicate_options($field, prefix)) return; 2576 2577 const sourceField = { 2578 id: fieldId, 2579 type: fieldType, 2580 label: mec_get_booking_condition_field_label($field, prefix) 2581 }; 2582 2583 sourceFields.push(sourceField); 2584 sourceMap[fieldId] = sourceField; 2585 });
2586 2587 $source.empty().append('<option value="">' + (strings.select_field || 'Select a field') + '</option>'); 2588 jQuery.each(sourceFields, function(index, sourceField) 2589 { 2590 $source.append('<option value="' + sourceField.id + '">' + sourceField.label + '</option>'); 2591 }); 2592 2593 if(currentSourceId && sourceMap[currentSourceId]) $source.val(currentSourceId); 2594 2595 const selectedSourceId = String($source.val() || ''); 2596 const selectedSource = sourceMap[selectedSourceId] || null; 2597 2598 $option.empty(); 2599 2600 if(!selectedSource) 2601 { 2602 $matchType.val(''); 2603 $option.prop('disabled', true).append('<option value="">' + (strings.select_option || 'Select an option') + '</option>'); 2604 $message.text(sourceFields.length ? (strings.select_controller || 'Choose a controller field.') : (strings.no_sources || 'No eligible controller fields are available.')); 2605 $conditionBox.data('currentSourceFieldId', ''); 2606 $conditionBox.data('currentOptionKey', ''); 2607 return; 2608 } 2609 2610 if(selectedSource.type === 'agreement') 2611 { 2612 $matchType.val('checked'); 2613 $option.prop('disabled', true).append('<option value="">' + (strings.checked_state || 'Checked') + '</option>'); 2614 $message.text((strings.checked_message || 'This field will show when "%s" is checked.').replace('%s', selectedSource.label)); 2615 $conditionBox.data('currentSourceFieldId', selectedSourceId); 2616 $conditionBox.data('currentOptionKey', ''); 2617 return; 2618 } 2619 2620 const $sourceField = jQuery('#mec_' + prefix + '_fields_' + selectedSource.id); 2621 const options = mec_get_booking_condition_field_option_list($sourceField, prefix); 2622 const matchType = selectedSource.type === 'checkbox' ? 'contains_option' : 'equals_option'; 2623 2624 $matchType.val(matchType); 2625 $option.prop('disabled', false).append('<option value="">' + (strings.select_option || 'Select an option') + '</option>'); 2626 2627 jQuery.each(options, function(index, option) 2628 { 2629 $option.append('<option value="' + option.key + '">' + option.label + '</option>'); 2630 }); 2631 2632 if(currentOptionKey) $option.val(currentOptionKey); 2633 2634 if(!$option.val()) 2635 { 2636 $message.text((strings.select_value || 'Choose the value that should reveal this field.').replace('%s', selectedSource.label)); 2637 } 2638 else 2639 { 2640 $message.text(''); 2641 } 2642 2643 $conditionBox.data('currentSourceFieldId', selectedSourceId); 2644 $conditionBox.data('currentOptionKey', String($option.val() || '')); 2645} 2646 2647function mec_refresh_booking_condition_editors(prefix) 2648{ 2649 jQuery('.mec-booking-condition-box[data-condition-prefix="' + prefix + '"]').each(function() 2650 { 2651 mec_refresh_booking_condition_editor(jQuery(this)); 2652 }); 2653} 2654 2655function mec_reg_fields_option_listeners() 2656{ 2657 jQuery('button.mec-reg-field-add-option').off('click').on('click', function() 2658 { 2659 var field_id = jQuery(this).data('field-id'); 2660 var key = jQuery('#mec_new_reg_field_option_key_'+field_id).val(); 2661 var html = jQuery('#mec_reg_field_option').html().replace(/:i:/g, key).replace(/:fi:/g, field_id); 2662 2663 jQuery('#mec_reg_fields_'+field_id+'_options_container').append(html); 2664 jQuery('#mec_new_reg_field_option_key_'+field_id).val(parseInt(key)+1); 2665 mec_refresh_booking_condition_editors('reg'); 2666 }); 2667 2668 if(typeof jQuery.fn.sortable !== 'undefined') 2669 { 2670 jQuery("#mec_reg_form_fields").sortable( 2671 { 2672 handle: '.mec_reg_field_sort', 2673 stop: function() 2674 { 2675 mec_refresh_booking_condition_editors('reg'); 2676 } 2677 }); 2678 2679 jQuery(".mec_reg_fields_options_container").sortable( 2680 { 2681 handle: '.mec_reg_field_option_sort' 2682 }); 2683 2684 jQuery(".mec-hourly-schedule-days").sortable( 2685 { 2686 handle: 'h4' 2687 }); 2688 2689 jQuery(".mec-hourly-schedule-schedules").sortable({ 2690 handle: '.mec_field_sort' 2691 }); 2692 } 2693} 2694 2695function mec_reg_fields_option_remove(field_key, key) 2696{ 2697 jQuery("#mec_reg_fields_option_"+field_key+"_"+key).remove(); 2698 mec_refresh_booking_condition_editors('reg'); 2699} 2700 2701function mec_reg_fields_remove(key) 2702{ 2703 jQuery("#mec_reg_fields_"+key).remove(); 2704 mec_refresh_booking_condition_editors('reg'); 2705} 2706 2707function mec_handle_add_price_date_button(e) 2708{ 2709 var key = jQuery(e).data('key'); 2710 var p = jQuery('#mec_new_ticket_price_key_'+key).val(); 2711 var html = jQuery('#mec_new_ticket_price_raw_'+key).html().replace(/:i:/g, key).replace(/:j:/g, p); 2712 2713 jQuery('#mec-ticket-price-dates-'+key).append(html); 2714 jQuery('#mec_new_ticket_price_key_'+key).val(parseInt(p)+1); 2715 jQuery('#mec-ticket-price-dates-'+key+' .new_added').datepicker( 2716 { 2717 changeYear: true, 2718 changeMonth: true, 2719 dateFormat: datepicker_format, 2720 gotoCurrent: true, 2721 yearRange: 'c-3:c+5', 2722 });
2723} 2724 2725function mec_ticket_price_remove(ticket_key, price_key) 2726{ 2727 jQuery("#mec_ticket_price_raw_"+ticket_key+"_"+price_key).remove(); 2728} 2729 2730function mec_event_fields_option_listeners() 2731{ 2732 jQuery('button.mec-event-field-add-option').off('click').on('click', function() 2733 { 2734 var field_id = jQuery(this).data('field-id'); 2735 var key = jQuery('#mec_new_event_field_option_key_'+field_id).val(); 2736 var html = jQuery('#mec_event_field_option').html().replace(/:i:/g, key).replace(/:fi:/g, field_id); 2737 2738 jQuery('#mec_event_fields_'+field_id+'_options_container').append(html); 2739 jQuery('#mec_new_event_field_option_key_'+field_id).val(parseInt(key)+1); 2740 }); 2741 2742 if(typeof jQuery.fn.sortable !== 'undefined') 2743 { 2744 jQuery("#mec_event_form_fields").sortable( 2745 { 2746 handle: '.mec_event_field_sort' 2747 }); 2748 2749 jQuery(".mec_event_fields_options_container").sortable( 2750 { 2751 handle: '.mec_event_field_option_sort' 2752 }); 2753 } 2754} 2755 2756function mec_event_fields_option_remove(field_key, key) 2757{ 2758 jQuery("#mec_event_fields_option_"+field_key+"_"+key).remove(); 2759} 2760 2761function mec_event_fields_remove(key) 2762{ 2763 jQuery("#mec_event_fields_"+key).remove(); 2764} 2765 2766function mec_bfixed_fields_option_listeners() 2767{ 2768 jQuery('button.mec-bfixed-field-add-option').off('click').on('click', function() 2769 { 2770 var field_id = jQuery(this).data('field-id'); 2771 var key = jQuery('#mec_new_bfixed_field_option_key_'+field_id).val(); 2772 var html = jQuery('#mec_bfixed_field_option').html().replace(/:i:/g, key).replace(/:fi:/g, field_id); 2773 2774 jQuery('#mec_bfixed_fields_'+field_id+'_options_container').append(html); 2775 jQuery('#mec_new_bfixed_field_option_key_'+field_id).val(parseInt(key)+1); 2776 mec_refresh_booking_condition_editors('bfixed'); 2777 }); 2778 2779 if(typeof jQuery.fn.sortable !== 'undefined') 2780 { 2781 jQuery("#mec_bfixed_form_fields").sortable( 2782 { 2783 handle: '.mec_bfixed_field_sort', 2784 stop: function() 2785 { 2786 mec_refresh_booking_condition_editors('bfixed'); 2787 } 2788 }); 2789 2790 jQuery(".mec_bfixed_fields_options_container").sortable( 2791 { 2792 handle: '.mec_bfixed_field_option_sort' 2793 }); 2794 } 2795} 2796 2797function mec_bfixed_fields_option_remove(field_key, key) 2798{ 2799 jQuery("#mec_bfixed_fields_option_"+field_key+"_"+key).remove(); 2800 mec_refresh_booking_condition_editors('bfixed'); 2801} 2802 2803function mec_bfixed_fields_remove(key) 2804{ 2805 jQuery("#mec_bfixed_fields_"+key).remove(); 2806 mec_refresh_booking_condition_editors('bfixed'); 2807} 2808 2809function mec_additional_organizers_listeners() 2810{ 2811 jQuery('#mec_additional_organizers_add').off('click').on('click', function() 2812 { 2813 var value = jQuery('.mec-additional-organizers select').val(); 2814 if (!value) return; 2815 2816 // Each organizer can be added only once. 2817 if (jQuery('.mec-additional-organizers-list input[name="mec[additional_organizer_ids][]"][value="' + value + '"]').length) return; 2818 2819 var text = jQuery('.mec-additional-organizers select option:selected').text(); 2820 2821 var sortLabel = jQuery(this).data('sort-label'); 2822 var removeLabel = jQuery(this).data('remove-label'); 2823 2824 jQuery('.mec-additional-organizers-list').append('<li><span class="mec-additional-organizer-sort">'+sortLabel+'</span> <span onclick="mec_additional_organizers_remove(this);" class="mec-additional-organizer-remove">'+removeLabel+'</span><input type="hidden" name="mec[additional_organizer_ids][]" value="'+value+'"><span class="mec_orgz_item_name">'+text+'</span></li>'); 2825 2826 mec_additional_organizers_listeners(); 2827 }); 2828 2829 if(typeof jQuery.fn.sortable !== 'undefined') 2830 { 2831 jQuery(".mec-additional-organizers-list").sortable( 2832 { 2833 handle: '.mec-additional-organizer-sort' 2834 }); 2835 } 2836} 2837 2838function mec_additional_organizers_remove(element) 2839{ 2840 jQuery(element).parent().remove(); 2841} 2842 2843function mec_additional_locations_listeners() 2844{ 2845 jQuery('#mec_additional_locations_add').off('click').on('click', function() 2846 { 2847 var value = jQuery('.mec-additional-locations select').val(); 2848 if (!value) return; 2849 2850 // Each location can be added only once. 2851 if (jQuery('.mec-additional-locations-list input[name="mec[additional_location_ids][]"][value="' + value + '"]').length) return; 2852 2853 var text = jQuery('.mec-additional-locations select option:selected').text(); 2854 2855 var sortLabel = jQuery(this).data('sort-label'); 2856 var removeLabel = jQuery(this).data('remove-label'); 2857 2858 jQuery('.mec-additional-locations-list').append('<li><span class="mec-additional-location-sort">'+sortLabel+'</span> <span onclick="mec_additional_locations_remove(this);" class="mec-additional-location-remove">'+removeLabel+'</span><input type="hidden" name="mec[additional_location_ids][]" value="'+value+'"><span class="mec_loc_item_name">'+text+'</span></li>'); 2859 2860 mec_additional_locations_listeners(); 2861 });
2862 2863 if(typeof jQuery.fn.sortable !== 'undefined') 2864 { 2865 jQuery(".mec-additional-locations-list").sortable( 2866 { 2867 handle: '.mec-additional-location-sort' 2868 }); 2869 } 2870} 2871 2872function mec_additional_locations_remove(element) 2873{ 2874 jQuery(element).parent().remove(); 2875} 2876 2877function mec_faq_remove(key) 2878{ 2879 jQuery("#mec_faq_row"+key).remove(); 2880} 2881 2882jQuery(document).on('focus', '.mec_date_picker', function () 2883{ 2884 if (!jQuery(this).hasClass('hasDatepicker')) 2885 { 2886 jQuery(this).datepicker({ 2887 changeYear: true, 2888 changeMonth: true, 2889 dateFormat: 'yy-mm-dd', 2890 gotoCurrent: true, 2891 yearRange: 'c-3:c+5', 2892 }); 2893 } 2894}); 2895 2896// Ticket availability start/end: live-init + open for rows that missed (or 2897// were added after) trigger_period_picker(). Two quirks to handle: 2898// 1) jQuery UI only opens a picker from its own focus handler â one bound 2899// BEFORE the current focus event â so we must explicitly show() here. 2900// 2) The hidden "#mec_new_ticket_raw" template gets its pickers initialized 2901// on page load; that serializes a stale "hasDatepicker" class into rows 2902// cloned from it, and jQuery UI refuses to attach to such elements 2903// (_connectDatepicker bails on the class). So test for the actual 2904// instance and strip the stale class before initializing. 2905jQuery(document).on('focus', '.mec-date-picker-start, .mec-date-picker-end', function () 2906{ 2907 var $input = jQuery(this); 2908 if(!jQuery.fn.datepicker) return; 2909 2910 if(!$input.data('datepicker')) 2911 { 2912 $input.removeClass('hasDatepicker'); 2913 2914 var isStart = $input.hasClass('mec-date-picker-start'); 2915 $input.datepicker({ 2916 changeYear: true, 2917 changeMonth: true, 2918 dateFormat: 'yy-mm-dd', 2919 gotoCurrent: true, 2920 yearRange: 'c-1:c+5', 2921 onSelect: isStart ? function(date) 2922 { 2923 var endDate = new Date(date); 2924 var $end_picker = $input.next(); 2925 $end_picker.datepicker("option", "minDate", endDate); 2926 $end_picker.datepicker("option", "maxDate", '+5y'); 2927 $input.trigger('change'); 2928 } : null, 2929 }); 2930 2931 // Initialize the sibling end picker eagerly so the start picker's 2932 // onSelect targets a live instance even before end is ever focused. 2933 if(isStart) 2934 { 2935 var $end = $input.next('.mec-date-picker-end'); 2936 if($end.length && !$end.data('datepicker')) 2937 { 2938 $end.removeClass('hasDatepicker'); 2939 $end.datepicker({ 2940 changeYear: true, 2941 changeMonth: true, 2942 dateFormat: 'yy-mm-dd', 2943 gotoCurrent: true, 2944 yearRange: 'c-1:c+5', 2945 }); 2946 } 2947 } 2948 } 2949 2950 $input.datepicker('show'); 2951}); 2952 2953// Appointment date pickers (.mec-apt-date-picker): only the "Add a date" 2954// button initializes them, so server-rendered inputs (start date, existing 2955// adjusted days) never get a picker â and day-copy clones carry a stale 2956// "hasDatepicker" class without an instance. Live-init + open on focus, 2957// same pattern as the availability handlers above. 2958jQuery(document).on('focus', '.mec-apt-date-picker', function () 2959{ 2960 var $input = jQuery(this); 2961 if(!jQuery.fn.datepicker) return; 2962 2963 if(!$input.data('datepicker')) 2964 { 2965 $input.removeClass('hasDatepicker'); 2966 $input.datepicker({ 2967 changeYear: true, 2968 changeMonth: true, 2969 dateFormat: datepicker_format, 2970 gotoCurrent: true, 2971 yearRange: 'c-3:c+5', 2972 }); 2973 } 2974 2975 $input.datepicker('show'); 2976}); 2977 2978// ===== Basic / Advanced toggle (mec-basvanced-toggle) ===== 2979// Delegated so it works wherever events.js loads: the admin editor AND the 2980// FES front-end form (backend.js, which used to bind this directly, is not 2981// loaded on the front-end). Replaces the old direct binding in backend.js. 2982jQuery(document).on('click', '.mec-basvanced-toggle .mec-backend-tab-item', function (e) 2983{ 2984 e.preventDefault(); 2985 2986 var $item = jQuery(this); 2987 if($item.hasClass('mec-b-active-tab')) return; 2988 2989 var $toggle = $item.closest('.mec-basvanced-toggle'); 2990 var wrapper = $toggle.data('for'); 2991 var method = $toggle.data('method'); 2992 2993 $item.parent().find('.mec-b-active-tab').removeClass('mec-b-active-tab'); 2994 $item.addClass('mec-b-active-tab'); 2995 2996 var $wrapper = jQuery(wrapper); 2997 if(method === 'addition') $wrapper.find('.mec-basvanced-advanced').toggleClass('w-hidden'); 2998 else 2999 { 3000 $wrapper.find('.mec-basvanced-basic').toggleClass('w-hidden'); 3001 $wrapper.find('.mec-basvanced-advanced').toggleClass('w-hidden'); 3002 } 3003}); 3004 3005// ===== Publish-validation fallback (crash-isolated) ===== 3006// The main validation module lives INSIDE jQuery(document).ready and shares
3007// that callback with a dozen initializers â a runtime error anywhere above 3008// it would silently prevent its submit handler from ever being bound (no 3009// error surfaces at click time; the form simply submits). This top-level, 3010// document-delegated copy guarantees the critical guard â "cannot publish 3011// without a start date" + the wp-post-new-reload URL cleanup â survives any 3012// such failure. It is bound on document, so it runs AFTER the main 3013// form-level handler; if that one already handled the submit it stays 3014// silent (e.isDefaultPrevented()). 3015jQuery(document).on('submit', '#post', function (e) 3016{ 3017 if(e.isDefaultPrevented()) return; // the main validation already handled it 3018 3019 // Draft saves and previews are exempt from publish validation. 3020 var submitter = (e.originalEvent && e.originalEvent.submitter) ? jQuery(e.originalEvent.submitter) : null; 3021 if(submitter && submitter.attr('id') === 'save-post') return; 3022 if(jQuery('#wp-preview').val() === 'dopreview') return; 3023 3024 var $startDate = jQuery('#mec_start_date'); 3025 if(!$startDate.length) return; 3026 3027 // Undo WP core's address-bar rewrite: wp-admin/js/post.js appends 3028 // "wp-post-new-reload=true" on publish-click of an auto-draft even when 3029 // the submit ends up blocked, leaving a misleading URL behind. 3030 if(window.history && window.history.replaceState && window.location.href.indexOf('wp-post-new-reload') > -1) 3031 { 3032 window.history.replaceState(null, null, window.location.href.replace(/([?&])wp-post-new-reload=true&?/, '$1').replace(/[?&]$/, '')); 3033 } 3034 3035 if(jQuery.trim($startDate.val() || '') === '') 3036 { 3037 e.preventDefault(); 3038 3039 // Jump to the Date & Time tab so the field is visible. 3040 jQuery('#mec_metabox_details .mec-add-event-tabs-link[data-href="mec_meta_box_date_form"]').trigger('click'); 3041 3042 // Inline error on the field. 3043 $startDate.addClass('mec-field-invalid').attr('aria-invalid', 'true'); 3044 var $err = $startDate.next('.mec-field-error'); 3045 if(!$err.length) $err = jQuery('<span class="mec-field-error" role="alert"></span>').insertAfter($startDate); 3046 var msg = (typeof mec_admin_localize !== 'undefined' && mec_admin_localize.start_date_required) ? mec_admin_localize.start_date_required : 'Please enter a start date for the event.'; 3047 $err.text(msg); 3048 3049 // Notice card â self-contained so it renders even if the main module died. 3050 jQuery('.mec-notice-temp').remove(); 3051 var $n = jQuery('<div/>', { 3052 'class': 'mec-notice mec-notice--error mec-notice-temp', 3053 html: '<div class="mec-notice-accent"></div><div class="mec-notice-body"><div class="mec-notice-title">Start date required</div><p class="mec-notice-text">' + msg + '</p><div class="mec-notice-actions"><button type="button" class="mec-notice-btn mec-notice-btn--ghost mec-notice-dismiss">Got it</button></div></div><div class="mec-notice-close" aria-label="Close" role="button" tabindex="0">×</div>' 3054 }); 3055 jQuery('#poststuff').prepend($n); 3056 jQuery('html, body').animate({scrollTop: $n.offset().top - 40}, 200); 3057 $n.find('.mec-notice-dismiss, .mec-notice-close').on('click', function(){ $n.remove(); }); 3058 3059 $startDate.trigger('focus'); 3060 } 3061}); 3062 3063// ===== Native-required feedback (e.g. required Event Data fields) ===== 3064// When a field carries the HTML5 "required" attribute (custom Event Data 3065// fields can), the browser blocks submission BEFORE the submit event fires: 3066// none of the submit handlers above run, so no MEC notice appears and WP 3067// core's publish-click URL rewrite ("wp-post-new-reload=true") lingers in 3068// the address bar. The only signal is the non-bubbling "invalid" event â 3069// captured here on the document so the user still gets MEC-style feedback. 3070document.addEventListener('invalid', function (ev) 3071{ 3072 var field = ev.target; 3073 if(!field || !field.form || field.form.id !== 'post') return; 3074 3075 // Undo WP core's address-bar rewrite (same cleanup as the fallback). 3076 if(window.history && window.history.replaceState && window.location.href.indexOf('wp-post-new-reload') > -1) 3077 { 3078 window.history.replaceState(null, null, window.location.href.replace(/([?&])wp-post-new-reload=true&?/, '$1').replace(/[?&]$/, '')); 3079 } 3080 3081 // Friendly field name: its label, aria-label, placeholder or title. 3082 var name = ''; 3083 if(field.id) 3084 { 3085 var $l = jQuery('label[for="' + field.id + '"]').first(); 3086 if($l.length) name = jQuery.trim($l.text()); 3087 } 3088 if(!name) name = field.getAttribute('aria-label') || field.placeholder || field.title || ''; 3089 3090 jQuery('.mec-notice-temp').remove(); 3091 var msg = name 3092 ? '"' + jQuery('<div/>').text(name).html() + '" ' + (field.validationMessage || 'is required.') 3093 : (field.validationMessage || 'Please fill in the required fields.'); 3094 3095 var $n = jQuery('<div/>', { 3096 'class': 'mec-notice mec-notice--error mec-notice-temp', 3097 html: '<div class="mec-notice-accent"></div><div class="mec-notice-body"><div class="mec-notice-title">Required field missing</div><p class="mec-notice-text">' + msg + '</p><div class="mec-notice-actions"><button type="button" class="mec-notice-btn mec-notice-btn--ghost mec-notice-dismiss">Got it</button></div></div><div class="mec-notice-close" aria-label="Close" role="button" tabindex="0">×</div>' 3098 });
3099 jQuery('#poststuff').prepend($n); 3100 jQuery('html, body').animate({scrollTop: $n.offset().top - 40}, 200); 3101 $n.find('.mec-notice-dismiss, .mec-notice-close').on('click', function(){ $n.remove(); }); 3102}, true);
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.