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 // --- WARNINGS (allow publish with confirmation) --- 998 // Booking-specific warnings only if there are real tickets 999 if(hasTickets) 1000 { 1001 // W2: ticket with name but no price 1002 $realTickets.each(function(){ 1003 var $name = $(this); 1004 var $row = $name.closest('.mec-ticket-start-time, .wn-ticket-time').parent(); 1005 var $price = $row.find('input[name*="price"]'); 1006 if(!$price.length) $price = $name.closest('[class*="ticket"]').find('input[name*="price"]'); 1007 if($price.length && $.trim($price.val()) === '') warnings.push('A ticket has a name but no price set.'); 1008 }); 1009 // W3: cost empty on bookable event with tickets 1010 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.'); 1011 } 1012 // W4: no featured image 1013 if(!$('#set-post-thumbnail img').length && !$('#postimagediv img').length) warnings.push('No featured image set.'); 1014 // W5: no content 1015 var c = (typeof tinymce !== 'undefined' && tinymce.get('content')) ? tinymce.get('content').getContent() : ($('#content').val() || ''); 1016 if($.trim(c.replace(/<[^>]*>/g, '')) === '') warnings.push('No event description/content.'); 1017 // W6: no location 1018 var lv = $('#mec_location_id').val(); if(!lv || lv === '0' || lv === '1') warnings.push('No location set.'); 1019 // W7: no organizer 1020 var ov = $('#mec_organizer_id').val(); if(!ov || ov === '0' || ov === '1') warnings.push('No organizer set.'); 1021 // W8: occurrences <= 1 1022 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).'); } 1023 // W10: zero duration 1024 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).'); } 1025 1026 if(!warningsAcknowledged && warnings.length > 0) 1027 { 1028 e.preventDefault(); 1029 mecNotice({ 1030 type:'warning', 1031 title:'Publishing with warnings', 1032 items: warnings, 1033 primaryText:'Publish anyway', 1034 primaryAction: function(){ warningsAcknowledged = true; $('#publish').trigger('click'); },
1035 dismissText:'Go back and fix' 1036 }); 1037 return; 1038 } 1039 }); 1040 })(); 1041 1042 // ===== Unsaved-changes protection for MEC meta boxes ===== 1043 // Audit issue #10. Warn before navigating away with unsaved MEC edits. 1044 (function() 1045 { 1046 var mecDirty = false; 1047 var $scope = $('#mec_metabox_details, #mec_metabox_booking, #mec_metabox_visibility, #mec_metabox_gallery'); 1048 1049 // Ignore spurious change/input events fired by 3rd-party init scripts 1050 // (nice-select, select2, color picker, etc.) before the user interacts. 1051 var mecUserInteracted = false; 1052 $('#post').one('mousedown keydown', function() { mecUserInteracted = true; }); 1053 1054 $scope.on('change input', 'input, select, textarea', function() { 1055 if(mecUserInteracted) mecDirty = true; 1056 }); 1057 1058 // Clear the dirty flag when the post actually submits. Must run AFTER 1059 // the validation handler so preventDefault (invalid form) keeps the flag. 1060 $('#post').on('submit', function(e) 1061 { 1062 if(!e.isDefaultPrevented()) mecDirty = false; 1063 }); 1064 1065 $(window).on('beforeunload', function(e) 1066 { 1067 if(mecDirty) 1068 { 1069 e.preventDefault(); 1070 e.returnValue = ''; 1071 return ''; 1072 } 1073 }); 1074 })(); 1075 1076 $('#mec_date_repeat_end_at_date').datepicker( 1077 { 1078 changeYear: true, 1079 changeMonth: true, 1080 dateFormat: datepicker_format, 1081 gotoCurrent: true, 1082 yearRange: 'c-3:c+5', 1083 }); 1084 1085 $('.mec_date_picker_dynamic_format').datepicker( 1086 { 1087 changeYear: true, 1088 changeMonth: true, 1089 dateFormat: datepicker_format, 1090 gotoCurrent: true, 1091 yearRange: 'c-3:c+5', 1092 }); 1093 1094 // End Repeat: conditional fields driven by the type <select> (was radios). 1095 // Date field shows only for "date", occurrences field only for "occurrences". 1096 $('#mec_repeat_end_type').on('change', function() 1097 { 1098 var v = $(this).val(); 1099 $('.mec-end-repeat-date-wrap').toggleClass('mec-is-hidden', v !== 'date'); 1100 $('.mec-end-repeat-occurrences-wrap').toggleClass('mec-is-hidden', v !== 'occurrences'); 1101 }); 1102 1103 $('.mec_date_picker').datepicker( 1104 { 1105 changeYear: true, 1106 changeMonth: true, 1107 dateFormat: 'yy-mm-dd', 1108 gotoCurrent: true, 1109 yearRange: 'c-3:c+5', 1110 }); 1111 1112 $('.mec_date_picker_dynamic_format_start').datepicker( 1113 { 1114 changeYear: true, 1115 changeMonth: true, 1116 dateFormat: datepicker_format, 1117 gotoCurrent: true, 1118 yearRange: 'c-1:c+5', 1119 onSelect: function(date) 1120 { 1121 var selectedDate; 1122 1123 try 1124 { 1125 selectedDate = $.datepicker.parseDate(datepicker_format, date); 1126 } 1127 catch(e) 1128 { 1129 selectedDate = new Date(date); 1130 } 1131 1132 if(!(selectedDate instanceof Date) || isNaN(selectedDate.getTime())) return; 1133 1134 var endDate = new Date(selectedDate.getTime()); 1135 1136 var $end_picker = $(this).next(); 1137 $end_picker.datepicker("option", "minDate", endDate); 1138 $end_picker.datepicker("option", "maxDate", '+5y'); 1139 1140 $(this).trigger('change'); 1141 } 1142 }); 1143 1144 $('.mec_date_picker_dynamic_format_end').datepicker( 1145 { 1146 changeYear: true, 1147 changeMonth: true, 1148 dateFormat: datepicker_format, 1149 gotoCurrent: true, 1150 yearRange: 'c-1:c+5', 1151 }); 1152
1153 trigger_period_picker(); 1154 } 1155 1156 // Initialize WP Color Picker 1157 if($.fn.wpColorPicker) jQuery('.mec-color-picker').wpColorPicker(); 1158 1159 $('#mec_location_id').on('change', function() 1160 { 1161 mec_location_toggle(); 1162 1163 // Mirror of the Other Organizers behavior: "Other Locations" is only 1164 // available once a real main location is selected (not "Hide location" = 1). 1165 var mec_location_val = parseInt($(this).val()); 1166 if(mec_location_val != 1) $('#mec-additional-location-wrap').show(); 1167 else $('#mec-additional-location-wrap').hide(); 1168 }); 1169 1170 $('#mec_organizer_id').on('change', function() 1171 { 1172 mec_organizer_toggle(); 1173 var mec_organizer_val = parseInt($(this).val()); 1174 1175 // Direct ID lookup â the wrap is a sibling of the select's row, so 1176 // the old ".parent().parent().find(...)" traversal never reached it. 1177 if(mec_organizer_val != 1) $('#mec-additional-organizer-wrap').show(); 1178 else $('#mec-additional-organizer-wrap').hide(); 1179 }); 1180 1181 mec_location_toggle(); 1182 mec_organizer_toggle(); 1183 1184 $('#mec_repeat').on('change', function() 1185 { 1186 mec_repeat_toggle(); 1187 }); 1188 1189 mec_repeat_toggle(); 1190 1191 $('#mec_repeat_type').on('change', function() 1192 { 1193 mec_repeat_type_toggle(); 1194 }); 1195 1196 mec_repeat_type_toggle(); 1197 1198 // "This event is free" toggle: sets the cost field to 0 (which the frontend 1199 // renders as "Free") and locks it, preserving the previous value to restore. 1200 // Audit issue #15. 1201 (function() 1202 { 1203 var $toggle = $('#mec_cost_is_free'); 1204 var $cost = $('#mec_cost'); 1205 if(!$toggle.length || !$cost.length) return; 1206 1207 function applyFree() 1208 { 1209 if($toggle.is(':checked')) 1210 { 1211 $cost.data('prev-cost', $cost.val()); 1212 $cost.val('0').prop('readonly', true).addClass('mec-cost-locked'); 1213 } 1214 else 1215 { 1216 $cost.prop('readonly', false).removeClass('mec-cost-locked'); 1217 var prev = $cost.data('prev-cost'); 1218 if(typeof prev !== 'undefined') $cost.val(prev); 1219 } 1220 } 1221 1222 $toggle.on('change', applyFree); 1223 applyFree(); 1224 })(); 1225 1226 // ===== Accessibility: ARIA tabs + keyboard nav + focusable tooltips ===== 1227 // Audit issue #3. Enhances the existing tab markup in place (no PHP changes). 1228 function mecEnhanceTabs(cfg) 1229 { 1230 var $ctx = $(cfg.context); 1231 if(!$ctx.length) return; 1232 var $tablist = $ctx.find(cfg.tablist); 1233 var $tabs = $ctx.find(cfg.tab); 1234 var $panels = $ctx.find(cfg.panel); 1235 if(!$tabs.length) return; 1236 1237 $tablist.attr('role', 'tablist'); 1238 1239 $tabs.each(function() { 1240 var $t = $(this); 1241 var panelId = $t.attr('data-href'); 1242 var tabId = $t.attr('id') || (panelId + '-tab'); 1243 var active = $t.hasClass('mec-tab-active'); 1244 $t.attr({ 1245 'id': tabId, 1246 'role': 'tab', 1247 'aria-controls': panelId, 1248 'aria-selected': active ? 'true' : 'false', 1249 'tabindex': active ? '0' : '-1' 1250 }); 1251 }); 1252 1253 $panels.each(function() { 1254 var $p = $(this); 1255 $p.attr({ 1256 'role': 'tabpanel', 1257 'aria-labelledby': $p.attr('id') + '-tab', 1258 'aria-hidden': $p.hasClass('mec-tab-active') ? 'false' : 'true' 1259 }); 1260 }); 1261 1262 function syncTabs() 1263 { 1264 $tabs.each(function() { 1265 var $t = $(this); 1266 var active = $t.hasClass('mec-tab-active'); 1267 $t.attr('aria-selected', active ? 'true' : 'false'); 1268 $t.attr('tabindex', active ? '0' : '-1'); 1269 }); 1270 $panels.each(function() { 1271 $(this).attr('aria-hidden', $(this).hasClass('mec-tab-active') ? 'false' : 'true'); 1272 }); 1273 } 1274 1275 // Keep ARIA state in sync after the existing inline click handlers switch tabs. 1276 $tablist.on('click', cfg.tab, function() { syncTabs(); }); 1277 1278 // Keyboard navigation (automatic activation): arrows + Home/End. 1279 $tablist.on('keydown', cfg.tab, function(e) { 1280 var idx = $tabs.index(this); 1281 var $target = null; 1282 switch(e.key) { 1283 case 'ArrowRight': case 'ArrowDown': $target = $tabs.eq(idx + 1); break; 1284 case 'ArrowLeft': case 'ArrowUp': $target = $tabs.eq(idx - 1); break; 1285 case 'Home': $target = $tabs.first(); break;
1286 case 'End': $target = $tabs.last(); break; 1287 case 'Enter': case ' ': 1288 e.preventDefault(); 1289 $(this).trigger('click'); 1290 this.focus(); 1291 return; 1292 default: return; 1293 } 1294 if($target && $target.length) { 1295 e.preventDefault(); 1296 $target.trigger('click'); // activate (existing handler runs, then syncTabs) 1297 $target[0].focus(); 1298 } 1299 }); 1300 } 1301 1302 mecEnhanceTabs({ 1303 context: '#mec_metabox_details', 1304 tablist: '.mec-add-event-tabs-left', 1305 tab: '.mec-add-event-tabs-link', 1306 panel: '.mec-event-tab-content' 1307 }); 1308 mecEnhanceTabs({ 1309 context: '#mec_metabox_booking', 1310 tablist: '.mec-add-booking-tabs-left', 1311 tab: '.mec-add-booking-tabs-link', 1312 panel: '.mec-booking-tab-content' 1313 }); 1314 1315 // Make hover-only help tooltips keyboard accessible (audit issue #3). 1316 $('.mec-tooltip').each(function(idx) { 1317 var $tip = $(this); 1318 var $box = $tip.find('.box').first(); 1319 var $icon = $tip.find('i').first(); 1320 if(!$box.length || !$icon.length) return; 1321 var boxId = $box.attr('id') || ('mec-tip-' + idx); 1322 $box.attr('id', boxId); 1323 $icon.attr({ 1324 'tabindex': '0', 1325 'role': 'button', 1326 'aria-describedby': boxId 1327 }); 1328 if(!$icon.attr('aria-label') && !$icon.attr('title')) { 1329 var title = $tip.find('.title').first().text(); 1330 $icon.attr('aria-label', title || 'More information'); 1331 } 1332 }); 1333 1334 // ===== Recurrence summary + next-5 preview ===== 1335 // Audit issue #6. Live, human-readable rule summary + next occurrences, 1336 // computed from the event start date (not "today"). 1337 (function() 1338 { 1339 var $box = $('#mec-repeat-preview'); 1340 var $body = $box.find('.mec-repeat-preview-body'); 1341 var $form = $('#mec_meta_box_repeat_form'); 1342 if(!$box.length || !$body.length || !$form.length) return; 1343 1344 var loc = (typeof mec_admin_localize !== 'undefined') ? mec_admin_localize : ((typeof mecdata !== 'undefined') ? mecdata : {}); 1345 var units = loc.interval_units || {}; 1346 var PREVIEW_MAX = 5; 1347 1348 function parseDate($field) 1349 { 1350 var raw = $.trim($field.val() || ''); 1351 if(!raw) return null; 1352 try { return $.datepicker.parseDate(datepicker_format, raw); } 1353 catch(e) { var d = new Date(raw); return isNaN(d.getTime()) ? null : d; } 1354 } 1355 1356 function fmt(date) 1357 { 1358 try { return $.datepicker.formatDate(datepicker_format, date); } 1359 catch(e) { return date.toDateString(); } 1360 } 1361 1362 function addDays(d, n){ var x = new Date(d.getTime()); x.setDate(x.getDate() + n); return x; } 1363 function addMonths(d, n){ var x = new Date(d.getTime()); x.setMonth(x.getMonth() + n); return x; } 1364 function addYears(d, n){ var x = new Date(d.getTime()); x.setFullYear(x.getFullYear() + n); return x; } 1365 1366 // MEC weekday number (1=Mon..7=Sun) -> JS getDay() (0=Sun..6=Sat) 1367 function mecDayToJs(n){ n = parseInt(n, 10); return n === 7 ? 0 : n; } 1368 1369 function readEnd() 1370 { 1371 var end = ($('#mec_repeat_end_type').length ? $('#mec_repeat_end_type').val() : $('input[name="mec[date][repeat][end]"]:checked').val()); 1372 var endDate = null, maxOcc = null; 1373 if(end === 'date') endDate = parseDate($('#mec_date_repeat_end_at_date')); 1374 else if(end === 'occurrences') 1375 { 1376 var n = parseInt($('#mec_date_repeat_end_at_occurrences').val(), 10); 1377 if(n > 0) maxOcc = n; 1378 } 1379 return {type: end || 'never', endDate: endDate, maxOcc: maxOcc}; 1380 } 1381 1382 function generate(type, interval, start, weekdays, end) 1383 { 1384 var dates = []; 1385 var guard = 0; 1386 // Generate one extra beyond the display cap so the UI can detect 1387 // truncation and show a "+N more" chip. Only PREVIEW_MAX are shown. 1388 var max = end.maxOcc ? Math.min(end.maxOcc, PREVIEW_MAX + 1) : (PREVIEW_MAX + 1); 1389 var endDateMs = end.endDate ? end.endDate.getTime() : null; 1390 1391 function pushIfValid(d) 1392 { 1393 if(endDateMs !== null && d.getTime() > endDateMs) return false; 1394 dates.push(d); 1395 return dates.length < max; 1396 } 1397 1398 if(type === 'daily' || type === 'weekly') 1399 { 1400 var stepDays = (type === 'weekly' ? Math.max(1, interval) * 7 : Math.max(1, interval)); 1401 var d = new Date(start.getTime()); 1402 while(dates.length < max && guard++ < 10000){ if(!pushIfValid(d)) break; d = addDays(d, stepDays); } 1403 } 1404 else if(type === 'monthly') 1405 { 1406 var n = Math.max(1, interval); 1407 var d = new Date(start.getTime()); 1408 while(dates.length < max && guard++ < 10000){ if(!pushIfValid(d)) break; d = addMonths(d, n); } 1409 } 1410 else if(type === 'yearly') 1411 { 1412 var n = Math.max(1, interval); 1413 var d = new Date(start.getTime()); 1414 while(dates.length < max && guard++ < 10000){ if(!pushIfValid(d)) break; d = addYears(d, n); } 1415 } 1416 else if(type === 'weekday' || type === 'weekend' || type === 'certain_weekdays') 1417 { 1418 var target; 1419 if(type === 'weekday') target = [1, 2, 3, 4, 5]; 1420 else if(type === 'weekend') target = [0, 6]; 1421 else target = weekdays.map(mecDayToJs); 1422 var d = new Date(start.getTime()); 1423 while(dates.length < max && guard++ < 40000) 1424 { 1425 if(target.indexOf(d.getDay()) !== -1){ if(!pushIfValid(d)) break; } 1426 if(endDateMs !== null && d.getTime() > endDateMs) break; 1427 d = addDays(d, 1); 1428 } 1429 } 1430 return dates; 1431 } 1432 1433 function buildSummary(type, interval, start, weekdays, end) 1434 { 1435 var s = ''; 1436 var u = {daily: units.daily || 'days', weekly: units.weekly || 'weeks', monthly: units.monthly || 'months', yearly: units.yearly || 'years'}; 1437 if(type === 'daily' || type === 'weekly' || type === 'monthly' || type === 'yearly') 1438 { 1439 var n = Math.max(1, interval); 1440 var unit = u[type]; 1441 s = (n === 1) ? ('Every ' + unit.replace(/s$/, '')) : ('Every ' + n + ' ' + unit); 1442 } 1443 else if(type === 'weekday') s = 'Every weekday (Mon-Fri)'; 1444 else if(type === 'weekend') s = 'Every weekend (Sat-Sun)'; 1445 else if(type === 'certain_weekdays') s = weekdays.length ? ('Weekly on ' + weekdays.length + ' day(s)') : 'No weekdays selected'; 1446 else if(type === 'custom_days') s = 'On the custom dates listed above'; 1447 else if(type === 'advanced') s = 'Advanced repeating pattern'; 1448 else s = type; 1449 1450 if(start) s += ', starting ' + fmt(start); 1451 if(end.type === 'date' && end.endDate) s += ', until ' + fmt(end.endDate); 1452 else if(end.type === 'occurrences' && end.maxOcc) s += ', for ' + end.maxOcc + ' occurrence' + (end.maxOcc === 1 ? '' : 's'); 1453 else s += ', no end'; 1454 return s; 1455 } 1456
1457 function update() 1458 { 1459 if(!$('#mec_repeat').is(':checked')) 1460 { 1461 $box.hide(); 1462 return; 1463 } 1464 $box.show(); 1465 1466 var type = $('#mec_repeat_type').val(); 1467 var interval = parseInt($('#mec_repeat_interval').val(), 10); 1468 if(isNaN(interval) || interval < 1) interval = 1; 1469 var start = parseDate($('#mec_start_date')); 1470 var weekdays = []; 1471 $('input[name="mec[date][repeat][certain_weekdays][]"]:checked').each(function(){ weekdays.push($(this).val()); }); 1472 var end = readEnd(); 1473 1474 var html = '<p class="mec-repeat-preview-summary">' + buildSummary(type, interval, start, weekdays, end) + '</p>'; 1475 1476 var previewable = ['daily', 'weekly', 'monthly', 'yearly', 'weekday', 'weekend', 'certain_weekdays']; 1477 if(start && previewable.indexOf(type) !== -1) 1478 { 1479 if(type === 'certain_weekdays' && !weekdays.length) 1480 { 1481 html += '<p class="mec-repeat-preview-empty">' + (loc.repeat_preview_no_weekdays || 'Select weekdays to preview dates.') + '</p>'; 1482 } 1483 else 1484 { 1485 var dates = generate(type, interval, start, weekdays, end); 1486 var total = ''; 1487 if(end.type === 'occurrences' && end.maxOcc) total = end.maxOcc; 1488 else if(end.type === 'date' && dates.length < PREVIEW_MAX) total = dates.length; 1489 1490 if(dates.length) 1491 { 1492 var shown = Math.min(dates.length, PREVIEW_MAX); 1493 var items = dates.slice(0, PREVIEW_MAX).map(function(d){ return '<li>' + fmt(d) + '</li>'; }).join(''); 1494 // Truncation indicator: when more occurrences exist than 1495 // shown, append a muted chip so the user understands the 5 1496 // dates are samples (resolves "5 shown vs. N total" confusion). 1497 if(dates.length > PREVIEW_MAX) 1498 { 1499 var moreText = 'â¦'; 1500 if(total !== '' && parseInt(total, 10) > shown) 1501 { 1502 moreText = (loc.repeat_preview_more || '+ %d more').replace('%d', parseInt(total, 10) - shown); 1503 } 1504 items += '<li class="mec-repeat-preview-more">' + moreText + '</li>'; 1505 } 1506 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>'; 1507 } 1508 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>'; 1509 } 1510 } 1511 else if(!start) 1512 { 1513 html += '<p class="mec-repeat-preview-empty">' + (loc.repeat_preview_need_start || 'Enter a start date to preview dates.') + '</p>'; 1514 } 1515 1516 $body.html(html); 1517 } 1518 1519 $form.on('change input', 'input, select', update); 1520 $('#mec_start_date').on('change input', update); 1521 update(); 1522 })(); 1523 1524 // ===== Currency options live preview (audit #11) ===== 1525 // Mirrors main::render_price() so the sample matches the real front-end. 1526 (function() 1527 { 1528 var $preview = $('#mec_currency_preview'); 1529 if(!$preview.length) return; 1530 1531 var sample = parseFloat($preview.data('sample')); 1532 if(isNaN(sample)) sample = 1234.5; 1533 var defaultSign = $preview.data('default-sign') || '$'; 1534 1535 // Equivalent of PHP number_format(amount, decs, decChar, thouChar). 1536 function format(amount, decs, decChar, thouChar) 1537 { 1538 decs = Math.max(0, parseInt(decs, 10) || 0); 1539 var s = amount.toFixed(decs); 1540 var parts = s.split('.'); 1541 parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thouChar); 1542 return parts.length > 1 ? parts[0] + decChar + parts[1] : parts[0]; 1543 } 1544 1545 function val($el, fallback) 1546 { 1547 var v = $el.val(); 1548 return (v === null || v === '') ? fallback : v; 1549 } 1550
1551 function update() 1552 { 1553 var sign = $.trim($('#mec_currency_currency_symptom').val()) || defaultSign; 1554 var pos = val($('#mec_currency_currency_sign'), 'before'); 1555 var thou = val($('#mec_currency_thousand_separator'), ','); 1556 var decChar = val($('#mec_currency_decimal_separator'), '.'); 1557 // "No decimal" checkbox is checked => decimal_separator_status = 0 => no decimals. 1558 var noDec = $('#mec_currency_decimal_separator_status').is(':checked'); 1559 var decs = noDec ? 0 : Math.max(0, parseInt($('#mec_currency_decimals').val(), 10) || 0); 1560 1561 var num = format(sample, decs, (noDec ? '' : decChar), thou); 1562 var result; 1563 if(pos === 'after') result = num + sign; 1564 else if(pos === 'after_space') result = num + ' ' + sign; 1565 else if(pos === 'before_space') result = sign + ' ' + num; 1566 else result = sign + num; 1567 1568 $preview.text(result); 1569 } 1570 1571 $('#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); 1572 update(); 1573 })(); 1574 1575 // ===== Location duplicate-name guard ===== 1576 // Audit issue #14. Warn when a newly typed location name matches an existing one. 1577 (function() 1578 { 1579 var $name = $('#mec_location_name'); 1580 var $select = $('#mec_location_id'); 1581 if(!$name.length || !$select.length) return; 1582 1583 var loc = (typeof mec_admin_localize !== 'undefined') ? mec_admin_localize : ((typeof mecdata !== 'undefined') ? mecdata : {}); 1584 1585 // Build {lowercaseName: termId} from existing location options (exclude the special 0 / 1). 1586 var existing = {}; 1587 $select.find('option').each(function() 1588 { 1589 var v = $(this).attr('value'); 1590 if(v === '0' || v === '1') return; 1591 existing[$.trim($(this).text()).toLowerCase()] = v; 1592 }); 1593 1594 var $warn = null; 1595 function clearWarn(){ if($warn){ $warn.remove(); $warn = null; } } 1596 1597 function showWarn(existingId) 1598 { 1599 $warn = $('<div class="mec-location-duplicate-warn" role="alert"></div>'); 1600 $('<span class="mec-location-duplicate-text"></span>') 1601 .text(loc.location_duplicate_warn || 'A location with this name already exists.') 1602 .appendTo($warn); 1603 var $btn = $('<button type="button" class="button mec-location-use-existing"></button>') 1604 .text(loc.location_use_existing || 'Use existing'); 1605 $btn.on('click', function() 1606 { 1607 // Switch the select to the existing location; mec_location_toggle() hides the new form. 1608 $select.val(existingId).trigger('change'); 1609 clearWarn(); 1610 }); 1611 $warn.append(' ').append($btn); 1612 $name.closest('.mec-form-row').after($warn); 1613 } 1614 1615 $name.on('input change', function() 1616 { 1617 clearWarn(); 1618 var val = $.trim($name.val() || '').toLowerCase(); 1619 if(!val) return; 1620 if(existing.hasOwnProperty(val)) showWarn(existing[val]); 1621 }); 1622 })(); 1623 1624 $('#mec_bookings_limit_unlimited').on('change', function() 1625 { 1626 mec_bookings_unlimited_toggle(); 1627 }); 1628 1629 $('#mec_add_in_days').on('click', function() 1630 { 1631 var allday = $(this).data('allday'); 1632 1633 var start = $('#mec_exceptions_in_days_start_date').val(); 1634 if(start === '') return false; 1635 1636 var end = $('#mec_exceptions_in_days_end_date').val(); 1637 if(end === '') return false; 1638 1639 var start_hour = $('#mec_exceptions_in_days_start_hour').val(); 1640 if(start_hour.length === 1) start_hour = '0'+start_hour; 1641 1642 var start_minutes = $('#mec_exceptions_in_days_start_minutes').val(); 1643 if(start_minutes.length === 1) start_minutes = '0'+start_minutes; 1644 1645 var start_ampm = $('#mec_exceptions_in_days_start_ampm').val(); 1646 if(typeof start_ampm === 'undefined') start_ampm = ''; 1647 1648 var end_hour = $('#mec_exceptions_in_days_end_hour').val(); 1649 if(end_hour.length === 1) end_hour = '0'+end_hour; 1650 1651 var end_minutes = $('#mec_exceptions_in_days_end_minutes').val(); 1652 if(end_minutes.length === 1) end_minutes = '0'+end_minutes; 1653 1654 var end_ampm = $('#mec_exceptions_in_days_end_ampm').val(); 1655 if(typeof end_ampm === 'undefined') end_ampm = ''; 1656 1657 var value = start + ':' + end + ':' + start_hour + '-' + start_minutes + '-' + start_ampm + ':' + end_hour + '-' + end_minutes + '-' + end_ampm; 1658 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>'; 1659 1660 // Don't add exactly same occurrences 1661 if($('#mec_in_days input[value="'+value+'"]').length > 0) return false; 1662 1663 var $key = $('#mec_new_in_days_key'); 1664 1665 var key = $key.val(); 1666 var html = $('#mec_new_in_days_raw').html().replace(/:i:/g, key).replace(/:val:
1666/g, value).replace(/:label:/g, label); 1667 1668 $('#mec_in_days').append(html); 1669 $key.val(parseInt(key)+1); 1670 }); 1671 1672 $('#mec_edit_in_days').on('click', function() 1673 { 1674 // Form 1675 const $form = $('#mec-in-days-form'); 1676 1677 const modify_id = $form.data('modify'); 1678 const $row = $('#mec_in_days_row'+modify_id); 1679 1680 var allday = $(this).data('allday'); 1681 1682 var start = $('#mec_exceptions_in_days_start_date').val(); 1683 if(start === '') return false; 1684 1685 var end = $('#mec_exceptions_in_days_end_date').val(); 1686 if(end === '') return false; 1687 1688 var start_hour = $('#mec_exceptions_in_days_start_hour').val(); 1689 if(start_hour.length === 1) start_hour = '0'+start_hour; 1690 1691 var start_minutes = $('#mec_exceptions_in_days_start_minutes').val(); 1692 if(start_minutes.length === 1) start_minutes = '0'+start_minutes; 1693 1694 var start_ampm = $('#mec_exceptions_in_days_start_ampm').val(); 1695 if(typeof start_ampm === 'undefined') start_ampm = ''; 1696 1697 var end_hour = $('#mec_exceptions_in_days_end_hour').val(); 1698 if(end_hour.length === 1) end_hour = '0'+end_hour; 1699 1700 var end_minutes = $('#mec_exceptions_in_days_end_minutes').val(); 1701 if(end_minutes.length === 1) end_minutes = '0'+end_minutes; 1702 1703 var end_ampm = $('#mec_exceptions_in_days_end_ampm').val(); 1704 if(typeof end_ampm === 'undefined') end_ampm = ''; 1705 1706 var value = start + ':' + end + ':' + start_hour + '-' + start_minutes + '-' + start_ampm + ':' + end_hour + '-' + end_minutes + '-' + end_ampm; 1707 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>'; 1708 1709 $row.find($('input[type=hidden]')).val(value); 1710 $row.find($('.mec-in-days-day')).html(label); 1711 1712 // Reset Dates 1713 $form.parent().find($('input[type=text]')).val(''); 1714 1715 // Modification Mode 1716 $form.removeClass('mec-in-days-edit-mode').addClass('mec-in-days-add-mode').removeData('modify'); 1717 }); 1718 1719 $('#mec_cancel_in_days').on('click', function() 1720 { 1721 // Form 1722 let $form = $('#mec-in-days-form'); 1723 1724 // Reset Dates 1725 $form.parent().find($('input[type=text]')).val(''); 1726 1727 // Modification Mode 1728 $form.removeClass('mec-in-days-edit-mode').addClass('mec-in-days-add-mode').removeData('modify'); 1729 }); 1730 1731 // Keyboard support for Custom Days card action buttons (audit #6). 1732 $('#mec_in_days').on('keydown', '[role="button"]', function(e) 1733 { 1734 if(e.key === 'Enter' || e.key === ' '){ e.preventDefault(); this.click(); } 1735 }); 1736 1737 $('#mec_add_not_in_days').on('click', function() 1738 { 1739 let date = $('#mec_exceptions_not_in_days_date').val(); 1740 if(date === '') return false; 1741 1742 let d = date.replaceAll('-', ''); 1743 d = d.replaceAll('/', ''); 1744 d = d.replaceAll('.', ''); 1745 1746 1747 let $wrapper = $('#mec_not_in_days'); 1748 let $key = $('#mec_new_not_in_days_key'); 1749 1750 let c = 'mec-date-'+d; 1751 if($wrapper.find($('.'+c)).length) return; 1752 1753 let key = $key.val(); 1754 let html = $('#mec_new_not_in_days_raw').html().replace(/:i:/g, key).replace(/:d:/g, d).replace(/:val:/g, date); 1755 1756 $wrapper.append(html); 1757 $key.val(parseInt(key)+1); 1758 }); 1759 1760 $('#mec_add_ticket_button').on('click', function() 1761 { 1762 let $key = $('#mec_new_ticket_key'); 1763 let key = $key.val(); 1764 let html = $('#mec_new_ticket_raw').html().replace(/:i:/g, key); 1765 1766 $('#mec_tickets').append(html); 1767 $key.val(parseInt(key)+1); 1768 1769 mec_init_sortable_sections(); 1770 1771 $('.mec_add_price_date_button').off('click').on('click', function() 1772 { 1773 mec_handle_add_price_date_button(this); 1774 }); 1775 1776 $.each($(".mec-select2"), function(i,v) 1777 { 1778 if($(v).attr('name').search(":i:") > 0) 1779 { 1780 return; 1781 } 1782 1783 if(typeof $(v).data('select2-id') == 'undefined') 1784 { 1785 $(v).select2(); 1786 } 1787 }); 1788
1789 trigger_period_picker(); 1790 }); 1791 1792 $('.mec_add_price_date_button').off('click').on('click', function() 1793 { 1794 mec_handle_add_price_date_button(this); 1795 }); 1796 1797 mec_hourly_schedule_add_day_listener(); 1798 mec_init_sortable_sections(); 1799 1800 $('#mec_add_fee_button').on('click', function() 1801 { 1802 var key = $('#mec_new_fee_key').val(); 1803 var html = $('#mec_new_fee_raw').html().replace(/:i:/g, key); 1804 1805 $('#mec_fees_list').append(html); 1806 $('#mec_new_fee_key').val(parseInt(key)+1); 1807 1808 mec_init_sortable_sections(); 1809 }); 1810 1811 $('#mec_add_ticket_variation_button').on('click', function() 1812 { 1813 var key = $('#mec_new_ticket_variation_key').val(); 1814 var html = $('#mec_new_ticket_variation_raw').html().replace(/:i:/g, key); 1815 1816 $('#mec_ticket_variations_list').append(html); 1817 $('#mec_new_ticket_variation_key').val(parseInt(key)+1); 1818 1819 mec_init_sortable_sections(); 1820 }); 1821 1822 $('.mec-form-row.mec-available-color-row span').on('click', function() 1823 { 1824 $('.mec-form-row.mec-available-color-row span').removeClass('color-selected'); 1825 $(this).addClass('color-selected'); 1826 }); 1827 1828 $('#mec_reg_form_field_types button').on('click', function() 1829 { 1830 var type = $(this).data('type'); 1831 1832 if (type == 'mec_email') { 1833 if ($('#mec_reg_form_fields').find('input[value="mec_email"][type="hidden"]').length) { 1834 return false; 1835 } 1836 } 1837 1838 if (type == 'name') { 1839 if ($('#mec_reg_form_fields').find('input[value="name"][type="hidden"]').length) { 1840 return false; 1841 } 1842 } 1843 1844 var key = $('#mec_new_reg_field_key').val(); 1845 var html = $('#mec_reg_field_'+type).html().replace(/:i:/g, key); 1846 1847 $('#mec_reg_form_fields').append(html); 1848 $('#mec_new_reg_field_key').val(parseInt(key)+1); 1849 1850 // Set onclick listener for add option fields 1851 mec_reg_fields_option_listeners(); 1852 mec_refresh_booking_condition_editors('reg'); 1853 }); 1854 1855 // Set onclick listener for add option fields 1856 mec_reg_fields_option_listeners(); 1857 mec_refresh_booking_condition_editors('reg'); 1858 1859 // Advanced Repeating 1860 $('#mec-advanced-wraper ul > ul > li').click(function() 1861 { 1862 if($(this).attr('class') == '') $(this).attr('class', 'mec-active'); 1863 else $(this).attr('class', ''); 1864 1865 $('#mec_date_repeat_advanced').val($('#mec-advanced-wraper div:first-child > ul').find('.mec-active').find('span').text().slice(0, -1)); 1866 }); 1867 1868 $('#mec_event_form_field_types button').on('click', function() 1869 { 1870 var type = $(this).data('type'); 1871 1872 var key = $('#mec_new_event_field_key').val(); 1873 var html = $('#mec_event_field_'+type).html().replace(/:i:/g, key); 1874 1875 $('#mec_event_form_fields').append(html); 1876 $('#mec_new_event_field_key').val(parseInt(key)+1); 1877 1878 // Set onclick listener for add option fields 1879 mec_event_fields_option_listeners(); 1880 }); 1881 1882 // Set onclick listener for add option fields 1883 mec_event_fields_option_listeners(); 1884 1885 $('#mec_bfixed_form_field_types button').on('click', function() 1886 { 1887 var type = $(this).data('type'); 1888 1889 var key = $('#mec_new_bfixed_field_key').val(); 1890 var html = $('#mec_bfixed_field_'+type).html().replace(/:i:/g, key); 1891 1892 $('#mec_bfixed_form_fields').append(html); 1893 $('#mec_new_bfixed_field_key').val(parseInt(key)+1); 1894 1895 // Set onclick listener for add option fields 1896 mec_bfixed_fields_option_listeners(); 1897 mec_refresh_booking_condition_editors('bfixed'); 1898 }); 1899 1900 // Set onclick listener for add option fields 1901 mec_bfixed_fields_option_listeners(); 1902 mec_refresh_booking_condition_editors('bfixed'); 1903 1904 jQuery(document).on('change', '.mec-booking-condition-enabled, .mec-booking-condition-source, .mec-booking-condition-option', function() 1905 { 1906 const $trigger = jQuery(this); 1907 const $conditionBox = jQuery(this).closest('.mec-booking-condition-box'); 1908 const prefix = String($conditionBox.data('conditionPrefix') || ''); 1909 1910 if($trigger.hasClass('mec-booking-condition-source')) 1911 { 1912 $conditionBox.data('currentSourceFieldId', String($trigger.val() || '')); 1913 $conditionBox.data('currentOptionKey', ''); 1914 $conditionBox.find('.mec-booking-condition-option').val(''); 1915 } 1916 else if($trigger.hasClass('mec-booking-condition-option')) 1917 { 1918 $conditionBox.data('currentOptionKey', String($trigger.val() || '')); 1919 } 1920 1921 mec_refresh_booking_condition_editor($conditionBox); 1922 if(prefix) mec_refresh_booking_condition_editors(prefix); 1923 });
1924 1925 jQuery(document).on('input change', '#mec_reg_form_fields input[name*="[label]"], #mec_bfixed_form_fields input[name*="[label]"]', function() 1926 { 1927 const $field = jQuery(this).closest('li[id^="mec_reg_fields_"], li[id^="mec_bfixed_fields_"]'); 1928 if($field.attr('id') && $field.attr('id').indexOf('mec_reg_fields_') === 0) mec_refresh_booking_condition_editors('reg'); 1929 if($field.attr('id') && $field.attr('id').indexOf('mec_bfixed_fields_') === 0) mec_refresh_booking_condition_editors('bfixed'); 1930 }); 1931 1932 // Additional Organizers 1933 mec_additional_organizers_listeners(); 1934 1935 // Additional Locations 1936 mec_additional_locations_listeners(); 1937 1938 // Show / Hide Password 1939 $('.mec-show-hide-password').on('click', function() 1940 { 1941 var $input = $(this).siblings("input"); 1942 var current = $input.attr('type'); 1943 1944 if(current === 'password') $input.attr('type', 'text'); 1945 else $input.attr('type', 'password'); 1946 }); 1947 1948 // FAQ 1949 $('#mec_add_faq_button').on('click', function() 1950 { 1951 const $key = $('#mec_new_faq_key'); 1952 const key = $key.val(); 1953 const html = $('#mec_new_faq_raw').html().replace(/:i:/g, key); 1954 1955 $('#mec_faq_list').append(html); 1956 $key.val(parseInt(key)+1); 1957 1958 mec_init_sortable_sections(); 1959 }); 1960 1961 // Appointments 1962 // Move the selector to the metabox header 1963 var $header = $('#mec_metabox_details .hndle'); 1964 // If .hndle is not found, try .postbox-header (newer WP versions) 1965 if ($header.length === 0) { 1966 $header = $('#mec_metabox_details .postbox-header'); 1967 } 1968 $('.mec-event-appointment-type-wrap').appendTo($header).removeClass('mec-util-hidden').on('click', function(e) { 1969 e.stopPropagation(); 1970 }); 1971 1972 $('#mec_entity_type_select').on('change', function() 1973 { 1974 const entity_type = $(this).val(); 1975 const $fes_form = $('#mec_fes_form'); 1976 const $metabox_details = $('#mec_metabox_details'); 1977 const $appointment_form_wrapper = $('.mec-appointment-form-wrap'); 1978 1979 if (entity_type === 'appointment') 1980 { 1981 $fes_form.removeClass('mec-entity-type-event').addClass('mec-entity-type-appointment'); 1982 $metabox_details.removeClass('mec-entity-type-event').addClass('mec-entity-type-appointment'); 1983 1984 $appointment_form_wrapper.removeClass('mec-util-hidden'); 1985 } 1986 else 1987 { 1988 $fes_form.removeClass('mec-entity-type-appointment').addClass('mec-entity-type-event'); 1989 $metabox_details.removeClass('mec-entity-type-appointment').addClass('mec-entity-type-event'); 1990 1991 $appointment_form_wrapper.addClass('mec-util-hidden'); 1992 } 1993 }); 1994 1995 $('#mec_entity_type_select').trigger('change'); 1996 1997 const $repeatType = $('#mec_appointments_availability_repeat_type'); 1998 if($.fn.datepicker) 1999 { 2000 $('#mec_appointments_start_date').datepicker({ 2001 changeYear: true, 2002 changeMonth: true, 2003 dateFormat: datepicker_format, 2004 gotoCurrent: true, 2005 yearRange: 'c-3:c+5', 2006 }); 2007 } 2008 function mec_toggle_repeat_type() 2009 { 2010 const val = $repeatType.val(); 2011 if (val === 'no_repeat') 2012 { 2013 $('.lsd-apt-days-wrapper').addClass('mec-util-hidden'); 2014 $('.lsd-apt-adjusted-title').addClass('mec-util-hidden'); 2015 $('.lsd-apt-start-date-wrapper').addClass('mec-util-hidden'); 2016 } 2017 else 2018 { 2019 $('.lsd-apt-days-wrapper').removeClass('mec-util-hidden'); 2020 $('.lsd-apt-adjusted-title').removeClass('mec-util-hidden'); 2021 $('.lsd-apt-start-date-wrapper').removeClass('mec-util-hidden'); 2022 } 2023 } 2024 $repeatType.on('change', mec_toggle_repeat_type); 2025 mec_toggle_repeat_type(); 2026 2027 $(document).on('click', '.lsd-apt-day-icon-remove', function() 2028 { 2029 const $button = $(this); 2030 const $timeslot = $button.parent(); 2031 const $timeslots = $button.parents('.lsd-apt-day-timeslots'); 2032 2033 $timeslot.remove(); 2034 if ($timeslots.find('.lsd-apt-day-timeslot-wrapper').length === 0) 2035 { 2036 $timeslots.find('.lsd-apt-day-timeslots-unavailable').removeClass('mec-util-hidden'); 2037 } 2038 });
2039 2040 $(document).on('click', '.lsd-apt-day-icon-plus', function() 2041 { 2042 const $button = $(this); 2043 let key = $button.data('key'); 2044 2045 const $day = $button.parents('.lsd-apt-day-wrapper'); 2046 const day = $day.data('key'); 2047 2048 const $timeslots = $day.find('.lsd-apt-day-timeslots-wrapper'); 2049 const $template = $('#lsd-apt-day-templates-'+day+'-timeslot'); 2050 2051 key = key + 1; 2052 const html = $template.html().replace(/:t:/g, key); 2053 $timeslots.append(html); 2054 2055 $button.data('key', key); 2056 $day.find('.lsd-apt-day-timeslots-unavailable').addClass('mec-util-hidden'); 2057 }); 2058 2059 $(document).on('click', '.lsd-apt-day-icon-copy', function () 2060 { 2061 const $button = $(this); 2062 const $sourceDay = $button.closest('.lsd-apt-day-wrapper'); 2063 const sourceDayKey = $sourceDay.data('key'); 2064 2065 const $weekWrapper = $sourceDay.parent(); 2066 const $sourceTimeslots = $sourceDay.find('.lsd-apt-day-timeslots-wrapper .lsd-apt-day-timeslot-wrapper'); 2067 2068 $weekWrapper.find('.lsd-apt-day-wrapper').each(function () 2069 { 2070 const $targetDay = $(this); 2071 2072 const targetDayKey = $targetDay.data('key'); 2073 if (targetDayKey === sourceDayKey) return; 2074 2075 const isActive = $targetDay.find('.lsd-apt-day-timeslots-wrapper .lsd-apt-day-timeslot-wrapper').length > 0; 2076 if (!isActive) return; 2077 2078 const $targetWrapper = $targetDay.find('.lsd-apt-day-timeslots-wrapper'); 2079 $targetWrapper.empty(); 2080 2081 $sourceTimeslots.each(function () 2082 { 2083 const $sourceSlot = $(this); 2084 const $clone = $sourceSlot.clone(true); 2085 2086 $sourceSlot.find('select').each(function (i) { 2087 const value = $(this).val(); 2088 $clone.find('select').eq(i).val(value); 2089 }); 2090 2091 $sourceSlot.find('input').each(function (i) { 2092 const value = $(this).val(); 2093 $clone.find('input').eq(i).val(value); 2094 }); 2095 2096 $clone.find('[name], [id], [for]').each(function () 2097 { 2098 $.each(this.attributes, function () { 2099 if (this.name === 'name' || this.name === 'id' || this.name === 'for') { 2100 this.value = this.value 2101 .replace(`[availability][${sourceDayKey}]`, `[availability][${targetDayKey}]`) 2102 .replace(`availability_${sourceDayKey}_`, `availability_${targetDayKey}_`); 2103 } 2104 }); 2105 }); 2106 2107 $targetWrapper.append($clone); 2108 }); 2109 }); 2110 }); 2111 2112 $(document).on('click', '.lsd-apt-adjusted-day-add', function() 2113 { 2114 const $wrapper = $('.lsd-apt-adjusted-days-wrapper'); 2115 let key = $wrapper.data('key') || 0; 2116 2117 key = key + 1; 2118 let html = $('#lsd-apt-adjusted-template-day').html(); 2119 html = html.replace(/:i:/g, key); 2120 2121 $wrapper.append(html); 2122 $wrapper.data('key', key); 2123 2124 if($.fn.datepicker) 2125 { 2126 $wrapper.find('.mec-apt-date-picker').last().datepicker({ 2127 changeYear: true, 2128 changeMonth: true, 2129 dateFormat: datepicker_format, 2130 gotoCurrent: true, 2131 yearRange: 'c-3:c+5', 2132 }); 2133 } 2134 }); 2135 2136 $(document).on('click', '.lsd-apt-adjusted-day-remove', function() 2137 { 2138 $(this).closest('.lsd-apt-day-wrapper').remove(); 2139 }); 2140 2141 $(document).on('click', '.lsd-apt-adj-day-icon-plus', function() 2142 { 2143 const $button = $(this); 2144 let key = $button.data('key'); 2145 const $day = $button.closest('.lsd-apt-day-wrapper'); 2146 const day = $day.data('day'); 2147 const $timeslots = $day.find('.lsd-apt-day-timeslots-wrapper'); 2148 2149 key = key + 1; 2150 let html = $('#lsd-apt-adjusted-template-timeslot').html(); 2151 2152 html = html.replace(/:i:/g, day).replace(/:t:/g, key); 2153 $timeslots.append(html); 2154 2155 $button.data('key', key); 2156 $day.find('.lsd-apt-day-timeslots-unavailable').addClass('mec-util-hidden'); 2157 });
2158}); 2159 2160function trigger_period_picker() 2161{ 2162 jQuery('.mec-date-picker-start').datepicker( 2163 { 2164 changeYear: true, 2165 changeMonth: true, 2166 dateFormat: 'yy-mm-dd', 2167 gotoCurrent: true, 2168 yearRange: 'c-1:c+5', 2169 onSelect: function(date) 2170 { 2171 const selectedDate = new Date(date); 2172 const endDate = new Date(selectedDate.getTime()); 2173 2174 const $end_picker = jQuery(this).next(); 2175 $end_picker.datepicker("option", "minDate", endDate); 2176 $end_picker.datepicker("option", "maxDate", '+5y'); 2177 jQuery(this).trigger('change'); 2178 } 2179 }); 2180 2181 jQuery('.mec-date-picker-end').datepicker( 2182 { 2183 changeYear: true, 2184 changeMonth: true, 2185 dateFormat: 'yy-mm-dd', 2186 gotoCurrent: true, 2187 yearRange: 'c-1:c+5', 2188 }); 2189} 2190 2191function mec_location_toggle() 2192{ 2193 if(jQuery('#mec_location_id').val() != '0') jQuery('#mec_location_new_container').hide(); 2194 else jQuery('#mec_location_new_container').show(); 2195} 2196 2197function mec_organizer_toggle() 2198{ 2199 if(jQuery('#mec_organizer_id').val() != '0') jQuery('#mec_organizer_new_container').hide(); 2200 else jQuery('#mec_organizer_new_container').show(); 2201} 2202 2203function mec_repeat_toggle() 2204{ 2205 if(jQuery('#mec_repeat').is(':checked')) jQuery('.mec-form-repeating-event-row').show(); 2206 else jQuery('.mec-form-repeating-event-row').hide(); 2207} 2208 2209// Update the "Repeat Interval" unit suffix (days/weeks/months/years) based on 2210// the selected repeat type. Audit issue #17. 2211function mec_update_interval_unit() 2212{ 2213 var $unit = jQuery('#mec_repeat_interval').siblings('.mec-interval-unit'); 2214 if(!$unit.length) return; 2215 var loc = (typeof mec_admin_localize !== 'undefined') ? mec_admin_localize : ((typeof mecdata !== 'undefined') ? mecdata : {}); 2216 var units = loc.interval_units || {}; 2217 var type = jQuery('#mec_repeat_type').val(); 2218 $unit.text(units[type] || ''); 2219} 2220 2221function mec_repeat_type_toggle() 2222{ 2223 var repeat_type = jQuery('#mec_repeat_type').val(); 2224 2225 if(repeat_type == 'certain_weekdays') 2226 { 2227 jQuery('#mec_repeat_interval_container').hide(); 2228 jQuery('#mec_repeat_certain_weekdays_container').show(); 2229 jQuery('#mec_exceptions_in_days_container').hide(); 2230 jQuery('#mec_end_wrapper').show(); 2231 jQuery('#mec-advanced-wraper').hide(); 2232 } 2233 else if(repeat_type == 'custom_days') 2234 { 2235 jQuery('#mec_repeat_interval_container').hide(); 2236 jQuery('#mec_repeat_certain_weekdays_container').hide(); 2237 jQuery('#mec_exceptions_in_days_container').show(); 2238 jQuery('#mec_end_wrapper').hide(); 2239 jQuery('#mec-advanced-wraper').hide(); 2240 } 2241 else if(repeat_type == 'advanced') 2242 { 2243 jQuery('#mec_repeat_interval_container').hide(); 2244 jQuery('#mec_repeat_certain_weekdays_container').hide(); 2245 jQuery('#mec_exceptions_in_days_container').hide(); 2246 jQuery('#mec_end_wrapper').show(); 2247 jQuery('#mec-advanced-wraper').show(); 2248 } 2249 else if(repeat_type != 'daily' && repeat_type != 'weekly' && repeat_type != 'monthly') 2250 { 2251 jQuery('#mec_repeat_interval_container').hide(); 2252 jQuery('#mec_repeat_certain_weekdays_container').hide(); 2253 jQuery('#mec_exceptions_in_days_container').hide(); 2254 jQuery('#mec_end_wrapper').show(); 2255 jQuery('#mec-advanced-wraper').hide(); 2256 } 2257 else 2258 { 2259 jQuery('#mec_repeat_interval_container').show(); 2260 jQuery('#mec_repeat_certain_weekdays_container').hide(); 2261 jQuery('#mec_exceptions_in_days_container').hide(); 2262 jQuery('#mec_end_wrapper').show(); 2263 jQuery('#mec-advanced-wraper').hide(); 2264 } 2265 2266 mec_update_interval_unit(); 2267} 2268 2269function mec_in_days_remove(i) 2270{ 2271 var $row = jQuery('#mec_in_days_row'+i); 2272 // Fade-out animation before removal (audit #6). 2273 if($row.length) 2274 { 2275 $row.addClass('mec-in-days-removing'); 2276 setTimeout(function(){ jQuery('#mec_in_days_row'+i).remove(); }, 180); 2277 } 2278} 2279 2280function mec_in_days_edit(i) 2281{ 2282 // Date 2283 let $row = jQuery('#mec_in_days_row'+i); 2284 let value = $row.find(jQuery('input[type=hidden]')).val(); 2285 2286 const values = value.split(':'); 2287 const start_times = values[2].split('-') 2288 const end_times = values[3].split('-') 2289 2290 // Form 2291 let $form = jQuery('#mec-in-days-form'); 2292 2293 // Set Dates 2294 jQuery('#mec_exceptions_in_days_start_date').val(values[0]); 2295 jQuery('#mec_exceptions_in_days_end_date').val(values[1]); 2296 2297 // Set Times 2298 jQuery('#mec_exceptions_in_days_start_hour').val(parseInt(start_times[0])); 2299 jQuery('#mec_exceptions_in_days_start_minutes').val(parseInt(start_times[1])); 2300 jQuery('#mec_exceptions_in_days_start_ampm').val(start_times[2]); 2301 2302 jQuery('#mec_exceptions_in_days_end_hour').val(parseInt(end_times[0])); 2303 jQuery('#mec_exceptions_in_days_end_minutes').val(parseInt(end_times[1])); 2304 jQuery('#mec_exceptions_in_days_end_ampm').val(end_times[2]); 2305 2306 // Modification Mode 2307 $form.removeClass('mec-in-days-add-mode').addClass('mec-in-days-edit-mode').data('modify', i); 2308} 2309 2310function mec_not_in_days_remove(i) 2311{ 2312 jQuery('#mec_not_in_days_row'+i).remove(); 2313} 2314
2315function mec_bookings_unlimited_toggle() 2316{ 2317 jQuery('#mec_bookings_limit').toggleClass('mec-util-hidden'); 2318} 2319 2320function mec_hourly_schedule_add_day_listener() 2321{ 2322 jQuery('.mec-add-hourly-schedule-day-button').each(function() 2323 { 2324 jQuery(this).off('click').on('click', function() 2325 { 2326 var k = jQuery(this).data('key'); 2327 var raw = jQuery(this).data('raw'); 2328 var append = jQuery(this).data('append'); 2329 2330 var key = jQuery(k).val(); 2331 var html = jQuery(raw).html().replace(/:d:/g, key).replace(/:dd:/g, parseInt(key)+1); 2332 2333 jQuery(append).append(html); 2334 jQuery(k).val(parseInt(key)+1); 2335 2336 mec_hourly_schedule_listeners(); 2337 }); 2338 2339 mec_hourly_schedule_listeners(); 2340 }); 2341} 2342 2343function mec_bookings_after_occurrence_cancel_listener() 2344{ 2345 jQuery('.mec-occurrences-bookings-after-occurrences-cancel').off('change').on('change', function() 2346 { 2347 const $dropdown = jQuery(this); 2348 const value = $dropdown.val(); 2349 const $moveWrapper = $dropdown.next(); 2350 2351 if(value === 'move' || value === 'move_notify') 2352 { 2353 $moveWrapper.removeClass('w-hidden'); 2354 } 2355 else 2356 { 2357 $moveWrapper.addClass('w-hidden'); 2358 } 2359 }); 2360} 2361 2362function mec_hourly_schedule_listeners() 2363{ 2364 jQuery('.mec-add-hourly-schedule-button').off('click').on('click', function() 2365 { 2366 var prefix = jQuery(this).data('prefix'); 2367 var day = jQuery(this).data('day'); 2368 var $key = jQuery('#'+prefix+'mec_new_hourly_schedule_key'+day); 2369 2370 var key = $key.val(); 2371 var html = jQuery('#'+prefix+'mec_new_hourly_schedule_raw'+day).html().replace(/:i:/g, key).replace(/:d:/g, day).replace(); 2372 var g_field_id = prefix+"-hourly_schedules-"+day+"-schedules-:i:-description"; 2373 var field_id = prefix+"-hourly_schedules-"+day+"-schedules-"+key+"-description"; 2374 html = html.replace(g_field_id,field_id); 2375 html = html.replace(':k:',key); 2376 jQuery('#'+prefix+'mec_hourly_schedules'+day).append(html); 2377 $key.val(parseInt(key)+1); 2378 2379 wp.editor.initialize("mec"+field_id,{ 2380 tinymce: { 2381 wpautop: true, 2382 plugins : 'charmap colorpicker compat3x directionality fullscreen hr image lists media paste tabfocus textcolor wordpress wpautoresize wpdialogs wpeditimage wpemoji wpgallery wplink wptextpattern wpview', 2383 toolbar1: 'bold italic underline strikethrough | bullist numlist | blockquote hr wp_more | alignleft aligncenter alignright | link unlink | fullscreen | wp_adv', 2384 toolbar2: 'formatselect alignjustify forecolor | pastetext removeformat charmap | outdent indent | undo redo | wp_help' 2385 }, 2386 quicktags: true, 2387 mediaButtons: false, 2388 }); 2389 }); 2390 2391 jQuery(".mec-hourly-schedule-schedules").sortable({ 2392 handle: '.mec_field_sort' 2393 }); 2394} 2395 2396function mec_hourly_schedule_remove(day, i, prefix) 2397{ 2398 jQuery("#"+prefix+"mec_hourly_schedule_row"+day+'_'+i).remove(); 2399} 2400 2401function mec_hourly_schedule_day_remove(day, prefix) 2402{ 2403 jQuery("#"+prefix+"mec_meta_box_hourly_schedule_day_"+day).remove(); 2404} 2405 2406function mec_ticket_remove(i) 2407{ 2408 jQuery("#mec_ticket_row"+i).remove(); 2409} 2410 2411function mec_set_event_color(color) 2412{ 2413 try 2414 { 2415 jQuery("#mec_event_color").wpColorPicker('color', '#'+color); 2416 } 2417 catch(e) 2418 { 2419 jQuery("#mec_event_color").val(color); 2420 } 2421} 2422 2423function mec_remove_fee(key) 2424{ 2425 jQuery("#mec_fee_row"+key).remove(); 2426} 2427 2428function mec_remove_ticket_variation(key, id_prefix) 2429{ 2430 jQuery("#mec_"+id_prefix+"_row"+key).remove(); 2431} 2432 2433function add_variation_per_ticket(ticket_id) 2434{ 2435 var $input = jQuery('#mec_new_variation_per_ticket_key'); 2436 2437 var key = $input.val(); 2438 var html = jQuery('#mec_new_variation_per_ticket_raw'+ticket_id).html().replace(/:v:/g, key); 2439 2440 jQuery('#mec_ticket_variations_list'+ticket_id).append(html); 2441 $input.val(parseInt(key)+1); 2442 2443 mec_init_sortable_sections(); 2444} 2445 2446function mec_init_sortable_instance(selector, options) 2447{ 2448 jQuery(selector).each(function() 2449 { 2450 var $element = jQuery(this); 2451 2452 if(!$element.length) return; 2453 2454 if(typeof $element.data('ui-sortable') === 'undefined') 2455 { 2456 $element.sortable(options); 2457 } 2458 else 2459 { 2460 $element.sortable('refresh'); 2461 } 2462 });
2463} 2464 2465function mec_init_sortable_sections() 2466{ 2467 if(typeof jQuery.fn.sortable === 'undefined') return; 2468 2469 mec_init_sortable_instance('#mec_tickets', { handle: '.mec_field_sort', items: '> .mec_ticket_row' }); 2470 mec_init_sortable_instance('#mec_fees_list', { handle: '.mec_field_sort', items: '> .mec-box' }); 2471 mec_init_sortable_instance('[id^="mec_ticket_variations_list"]', { handle: '.mec_field_sort', items: '> .mec_ticket_variation_row' }); 2472 mec_init_sortable_instance('#mec_faq_list', { handle: '.mec_field_sort', items: '> .mec_faq_row' }); 2473} 2474 2475function mec_get_booking_condition_form_fields(prefix) 2476{ 2477 return jQuery('#mec_' + prefix + '_form_fields > li'); 2478} 2479 2480function mec_get_booking_condition_field_id($field, prefix) 2481{ 2482 return String(($field.attr('id') || '').replace('mec_' + prefix + '_fields_', '')); 2483} 2484 2485function mec_get_booking_condition_field_type($field, prefix) 2486{ 2487 const fieldId = mec_get_booking_condition_field_id($field, prefix); 2488 return String($field.find('input[name="mec[' + prefix + '_fields][' + fieldId + '][type]"]').val() || ''); 2489} 2490 2491function mec_get_booking_condition_field_label($field, prefix) 2492{ 2493 const fieldId = mec_get_booking_condition_field_id($field, prefix); 2494 const $labelInput = $field.find('input[name="mec[' + prefix + '_fields][' + fieldId + '][label]"]').first(); 2495 const fallback = jQuery.trim($field.find('.mec_' + prefix + '_field_type').first().text() || ''); 2496 2497 return jQuery.trim(($labelInput.val() || fallback || '')); 2498} 2499 2500function mec_get_booking_condition_field_option_list($field, prefix) 2501{ 2502 const fieldId = mec_get_booking_condition_field_id($field, prefix); 2503 const options = []; 2504 2505 $field.find('input[name^="mec[' + prefix + '_fields][' + fieldId + '][options]"][name$="[label]"]').each(function() 2506 { 2507 const name = jQuery(this).attr('name') || ''; 2508 const match = name.match(/\[options\]\[(.*?)\]\[label\]$/); 2509 if(!match) return; 2510 2511 options.push({ 2512 key: String(match[1]), 2513 label: jQuery.trim(jQuery(this).val() || '') 2514 }); 2515 }); 2516 2517 return options; 2518} 2519 2520function mec_booking_condition_field_has_duplicate_options($field, prefix) 2521{ 2522 const seen = {}; 2523 const options = mec_get_booking_condition_field_option_list($field, prefix); 2524 2525 for(let i = 0; i < options.length; i++) 2526 { 2527 const label = options[i].label; 2528 if(!label) continue; 2529 if(seen[label]) return true; 2530 2531 seen[label] = true; 2532 } 2533 2534 return false; 2535} 2536 2537function mec_refresh_booking_condition_editor($conditionBox) 2538{ 2539 if(!$conditionBox || !$conditionBox.length) return; 2540 2541 const prefix = String($conditionBox.data('conditionPrefix') || ''); 2542 const currentFieldId = String($conditionBox.data('conditionFieldId') || ''); 2543 const $enabled = $conditionBox.find('.mec-booking-condition-enabled'); 2544 const $controls = $conditionBox.find('.mec-booking-condition-controls'); 2545 const $source = $conditionBox.find('.mec-booking-condition-source'); 2546 const $option = $conditionBox.find('.mec-booking-condition-option'); 2547 const $matchType = $conditionBox.find('.mec-booking-condition-match-type'); 2548 const $message = $conditionBox.find('.mec-booking-condition-message'); 2549 const enabled = $enabled.is(':checked'); 2550 const strings = (typeof mec_admin_localize !== 'undefined' && mec_admin_localize.booking_conditions) ? mec_admin_localize.booking_conditions : {}; 2551 2552 $controls.toggleClass('mec-util-hidden', !enabled); 2553 if(!enabled) 2554 { 2555 $message.text(''); 2556 return; 2557 } 2558 2559 const currentSourceId = String($source.val() || $conditionBox.data('currentSourceFieldId') || ''); 2560 const currentOptionKey = String($option.val() || $conditionBox.data('currentOptionKey') || ''); 2561 const sourceFields = []; 2562 const sourceMap = {}; 2563 2564 mec_get_booking_condition_form_fields(prefix).each(function() 2565 { 2566 const $field = jQuery(this); 2567 const fieldId = mec_get_booking_condition_field_id($field, prefix); 2568 const fieldType = mec_get_booking_condition_field_type($field, prefix); 2569 2570 if(!fieldId) return; 2571 if(fieldId === currentFieldId) return false; 2572 if(jQuery.inArray(fieldType, ['checkbox', 'radio', 'select', 'agreement']) === -1) return; 2573 if(jQuery.inArray(fieldType, ['checkbox', 'radio', 'select']) !== -1 && mec_booking_condition_field_has_duplicate_options($field, prefix)) return; 2574 2575 const sourceField = { 2576 id: fieldId, 2577 type: fieldType, 2578 label: mec_get_booking_condition_field_label($field, prefix) 2579 }; 2580 2581 sourceFields.push(sourceField); 2582 sourceMap[fieldId] = sourceField; 2583 });
2584 2585 $source.empty().append('<option value="">' + (strings.select_field || 'Select a field') + '</option>'); 2586 jQuery.each(sourceFields, function(index, sourceField) 2587 { 2588 $source.append('<option value="' + sourceField.id + '">' + sourceField.label + '</option>'); 2589 }); 2590 2591 if(currentSourceId && sourceMap[currentSourceId]) $source.val(currentSourceId); 2592 2593 const selectedSourceId = String($source.val() || ''); 2594 const selectedSource = sourceMap[selectedSourceId] || null; 2595 2596 $option.empty(); 2597 2598 if(!selectedSource) 2599 { 2600 $matchType.val(''); 2601 $option.prop('disabled', true).append('<option value="">' + (strings.select_option || 'Select an option') + '</option>'); 2602 $message.text(sourceFields.length ? (strings.select_controller || 'Choose a controller field.') : (strings.no_sources || 'No eligible controller fields are available.')); 2603 $conditionBox.data('currentSourceFieldId', ''); 2604 $conditionBox.data('currentOptionKey', ''); 2605 return; 2606 } 2607 2608 if(selectedSource.type === 'agreement') 2609 { 2610 $matchType.val('checked'); 2611 $option.prop('disabled', true).append('<option value="">' + (strings.checked_state || 'Checked') + '</option>'); 2612 $message.text((strings.checked_message || 'This field will show when "%s" is checked.').replace('%s', selectedSource.label)); 2613 $conditionBox.data('currentSourceFieldId', selectedSourceId); 2614 $conditionBox.data('currentOptionKey', ''); 2615 return; 2616 } 2617 2618 const $sourceField = jQuery('#mec_' + prefix + '_fields_' + selectedSource.id); 2619 const options = mec_get_booking_condition_field_option_list($sourceField, prefix); 2620 const matchType = selectedSource.type === 'checkbox' ? 'contains_option' : 'equals_option'; 2621 2622 $matchType.val(matchType); 2623 $option.prop('disabled', false).append('<option value="">' + (strings.select_option || 'Select an option') + '</option>'); 2624 2625 jQuery.each(options, function(index, option) 2626 { 2627 $option.append('<option value="' + option.key + '">' + option.label + '</option>'); 2628 }); 2629 2630 if(currentOptionKey) $option.val(currentOptionKey); 2631 2632 if(!$option.val()) 2633 { 2634 $message.text((strings.select_value || 'Choose the value that should reveal this field.').replace('%s', selectedSource.label)); 2635 } 2636 else 2637 { 2638 $message.text(''); 2639 } 2640 2641 $conditionBox.data('currentSourceFieldId', selectedSourceId); 2642 $conditionBox.data('currentOptionKey', String($option.val() || '')); 2643} 2644 2645function mec_refresh_booking_condition_editors(prefix) 2646{ 2647 jQuery('.mec-booking-condition-box[data-condition-prefix="' + prefix + '"]').each(function() 2648 { 2649 mec_refresh_booking_condition_editor(jQuery(this)); 2650 }); 2651} 2652 2653function mec_reg_fields_option_listeners() 2654{ 2655 jQuery('button.mec-reg-field-add-option').off('click').on('click', function() 2656 { 2657 var field_id = jQuery(this).data('field-id'); 2658 var key = jQuery('#mec_new_reg_field_option_key_'+field_id).val(); 2659 var html = jQuery('#mec_reg_field_option').html().replace(/:i:/g, key).replace(/:fi:/g, field_id); 2660 2661 jQuery('#mec_reg_fields_'+field_id+'_options_container').append(html); 2662 jQuery('#mec_new_reg_field_option_key_'+field_id).val(parseInt(key)+1); 2663 mec_refresh_booking_condition_editors('reg'); 2664 }); 2665 2666 if(typeof jQuery.fn.sortable !== 'undefined') 2667 { 2668 jQuery("#mec_reg_form_fields").sortable( 2669 { 2670 handle: '.mec_reg_field_sort', 2671 stop: function() 2672 { 2673 mec_refresh_booking_condition_editors('reg'); 2674 } 2675 }); 2676 2677 jQuery(".mec_reg_fields_options_container").sortable( 2678 { 2679 handle: '.mec_reg_field_option_sort' 2680 }); 2681 2682 jQuery(".mec-hourly-schedule-days").sortable( 2683 { 2684 handle: 'h4' 2685 }); 2686 2687 jQuery(".mec-hourly-schedule-schedules").sortable({ 2688 handle: '.mec_field_sort' 2689 }); 2690 } 2691} 2692 2693function mec_reg_fields_option_remove(field_key, key) 2694{ 2695 jQuery("#mec_reg_fields_option_"+field_key+"_"+key).remove(); 2696 mec_refresh_booking_condition_editors('reg'); 2697} 2698 2699function mec_reg_fields_remove(key) 2700{ 2701 jQuery("#mec_reg_fields_"+key).remove(); 2702 mec_refresh_booking_condition_editors('reg'); 2703} 2704 2705function mec_handle_add_price_date_button(e) 2706{ 2707 var key = jQuery(e).data('key'); 2708 var p = jQuery('#mec_new_ticket_price_key_'+key).val(); 2709 var html = jQuery('#mec_new_ticket_price_raw_'+key).html().replace(/:i:/g, key).replace(/:j:/g, p); 2710 2711 jQuery('#mec-ticket-price-dates-'+key).append(html); 2712 jQuery('#mec_new_ticket_price_key_'+key).val(parseInt(p)+1); 2713 jQuery('#mec-ticket-price-dates-'+key+' .new_added').datepicker( 2714 { 2715 changeYear: true, 2716 changeMonth: true, 2717 dateFormat: datepicker_format, 2718 gotoCurrent: true, 2719 yearRange: 'c-3:c+5', 2720 });
2721} 2722 2723function mec_ticket_price_remove(ticket_key, price_key) 2724{ 2725 jQuery("#mec_ticket_price_raw_"+ticket_key+"_"+price_key).remove(); 2726} 2727 2728function mec_event_fields_option_listeners() 2729{ 2730 jQuery('button.mec-event-field-add-option').off('click').on('click', function() 2731 { 2732 var field_id = jQuery(this).data('field-id'); 2733 var key = jQuery('#mec_new_event_field_option_key_'+field_id).val(); 2734 var html = jQuery('#mec_event_field_option').html().replace(/:i:/g, key).replace(/:fi:/g, field_id); 2735 2736 jQuery('#mec_event_fields_'+field_id+'_options_container').append(html); 2737 jQuery('#mec_new_event_field_option_key_'+field_id).val(parseInt(key)+1); 2738 }); 2739 2740 if(typeof jQuery.fn.sortable !== 'undefined') 2741 { 2742 jQuery("#mec_event_form_fields").sortable( 2743 { 2744 handle: '.mec_event_field_sort' 2745 }); 2746 2747 jQuery(".mec_event_fields_options_container").sortable( 2748 { 2749 handle: '.mec_event_field_option_sort' 2750 }); 2751 } 2752} 2753 2754function mec_event_fields_option_remove(field_key, key) 2755{ 2756 jQuery("#mec_event_fields_option_"+field_key+"_"+key).remove(); 2757} 2758 2759function mec_event_fields_remove(key) 2760{ 2761 jQuery("#mec_event_fields_"+key).remove(); 2762} 2763 2764function mec_bfixed_fields_option_listeners() 2765{ 2766 jQuery('button.mec-bfixed-field-add-option').off('click').on('click', function() 2767 { 2768 var field_id = jQuery(this).data('field-id'); 2769 var key = jQuery('#mec_new_bfixed_field_option_key_'+field_id).val(); 2770 var html = jQuery('#mec_bfixed_field_option').html().replace(/:i:/g, key).replace(/:fi:/g, field_id); 2771 2772 jQuery('#mec_bfixed_fields_'+field_id+'_options_container').append(html); 2773 jQuery('#mec_new_bfixed_field_option_key_'+field_id).val(parseInt(key)+1); 2774 mec_refresh_booking_condition_editors('bfixed'); 2775 }); 2776 2777 if(typeof jQuery.fn.sortable !== 'undefined') 2778 { 2779 jQuery("#mec_bfixed_form_fields").sortable( 2780 { 2781 handle: '.mec_bfixed_field_sort', 2782 stop: function() 2783 { 2784 mec_refresh_booking_condition_editors('bfixed'); 2785 } 2786 }); 2787 2788 jQuery(".mec_bfixed_fields_options_container").sortable( 2789 { 2790 handle: '.mec_bfixed_field_option_sort' 2791 }); 2792 } 2793} 2794 2795function mec_bfixed_fields_option_remove(field_key, key) 2796{ 2797 jQuery("#mec_bfixed_fields_option_"+field_key+"_"+key).remove(); 2798 mec_refresh_booking_condition_editors('bfixed'); 2799} 2800 2801function mec_bfixed_fields_remove(key) 2802{ 2803 jQuery("#mec_bfixed_fields_"+key).remove(); 2804 mec_refresh_booking_condition_editors('bfixed'); 2805} 2806 2807function mec_additional_organizers_listeners() 2808{ 2809 jQuery('#mec_additional_organizers_add').off('click').on('click', function() 2810 { 2811 var value = jQuery('.mec-additional-organizers select').val(); 2812 if (!value) return; 2813 2814 // Each organizer can be added only once. 2815 if (jQuery('.mec-additional-organizers-list input[name="mec[additional_organizer_ids][]"][value="' + value + '"]').length) return; 2816 2817 var text = jQuery('.mec-additional-organizers select option:selected').text(); 2818 2819 var sortLabel = jQuery(this).data('sort-label'); 2820 var removeLabel = jQuery(this).data('remove-label'); 2821 2822 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>'); 2823 2824 mec_additional_organizers_listeners(); 2825 }); 2826 2827 if(typeof jQuery.fn.sortable !== 'undefined') 2828 { 2829 jQuery(".mec-additional-organizers-list").sortable( 2830 { 2831 handle: '.mec-additional-organizer-sort' 2832 }); 2833 } 2834} 2835 2836function mec_additional_organizers_remove(element) 2837{ 2838 jQuery(element).parent().remove(); 2839} 2840 2841function mec_additional_locations_listeners() 2842{ 2843 jQuery('#mec_additional_locations_add').off('click').on('click', function() 2844 { 2845 var value = jQuery('.mec-additional-locations select').val(); 2846 if (!value) return; 2847 2848 // Each location can be added only once. 2849 if (jQuery('.mec-additional-locations-list input[name="mec[additional_location_ids][]"][value="' + value + '"]').length) return; 2850 2851 var text = jQuery('.mec-additional-locations select option:selected').text(); 2852 2853 var sortLabel = jQuery(this).data('sort-label'); 2854 var removeLabel = jQuery(this).data('remove-label'); 2855 2856 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>'); 2857 2858 mec_additional_locations_listeners(); 2859 });
2860 2861 if(typeof jQuery.fn.sortable !== 'undefined') 2862 { 2863 jQuery(".mec-additional-locations-list").sortable( 2864 { 2865 handle: '.mec-additional-location-sort' 2866 }); 2867 } 2868} 2869 2870function mec_additional_locations_remove(element) 2871{ 2872 jQuery(element).parent().remove(); 2873} 2874 2875function mec_faq_remove(key) 2876{ 2877 jQuery("#mec_faq_row"+key).remove(); 2878} 2879 2880jQuery(document).on('focus', '.mec_date_picker', function () 2881{ 2882 if (!jQuery(this).hasClass('hasDatepicker')) 2883 { 2884 jQuery(this).datepicker({ 2885 changeYear: true, 2886 changeMonth: true, 2887 dateFormat: 'yy-mm-dd', 2888 gotoCurrent: true, 2889 yearRange: 'c-3:c+5', 2890 }); 2891 } 2892}); 2893 2894// Ticket availability start/end: live-init + open for rows that missed (or 2895// were added after) trigger_period_picker(). Two quirks to handle: 2896// 1) jQuery UI only opens a picker from its own focus handler â one bound 2897// BEFORE the current focus event â so we must explicitly show() here. 2898// 2) The hidden "#mec_new_ticket_raw" template gets its pickers initialized 2899// on page load; that serializes a stale "hasDatepicker" class into rows 2900// cloned from it, and jQuery UI refuses to attach to such elements 2901// (_connectDatepicker bails on the class). So test for the actual 2902// instance and strip the stale class before initializing. 2903jQuery(document).on('focus', '.mec-date-picker-start, .mec-date-picker-end', function () 2904{ 2905 var $input = jQuery(this); 2906 if(!jQuery.fn.datepicker) return; 2907 2908 if(!$input.data('datepicker')) 2909 { 2910 $input.removeClass('hasDatepicker'); 2911 2912 var isStart = $input.hasClass('mec-date-picker-start'); 2913 $input.datepicker({ 2914 changeYear: true, 2915 changeMonth: true, 2916 dateFormat: 'yy-mm-dd', 2917 gotoCurrent: true, 2918 yearRange: 'c-1:c+5', 2919 onSelect: isStart ? function(date) 2920 { 2921 var endDate = new Date(date); 2922 var $end_picker = $input.next(); 2923 $end_picker.datepicker("option", "minDate", endDate); 2924 $end_picker.datepicker("option", "maxDate", '+5y'); 2925 $input.trigger('change'); 2926 } : null, 2927 }); 2928 2929 // Initialize the sibling end picker eagerly so the start picker's 2930 // onSelect targets a live instance even before end is ever focused. 2931 if(isStart) 2932 { 2933 var $end = $input.next('.mec-date-picker-end'); 2934 if($end.length && !$end.data('datepicker')) 2935 { 2936 $end.removeClass('hasDatepicker'); 2937 $end.datepicker({ 2938 changeYear: true, 2939 changeMonth: true, 2940 dateFormat: 'yy-mm-dd', 2941 gotoCurrent: true, 2942 yearRange: 'c-1:c+5', 2943 }); 2944 } 2945 } 2946 } 2947 2948 $input.datepicker('show'); 2949}); 2950 2951// Appointment date pickers (.mec-apt-date-picker): only the "Add a date" 2952// button initializes them, so server-rendered inputs (start date, existing 2953// adjusted days) never get a picker â and day-copy clones carry a stale 2954// "hasDatepicker" class without an instance. Live-init + open on focus, 2955// same pattern as the availability handlers above. 2956jQuery(document).on('focus', '.mec-apt-date-picker', function () 2957{ 2958 var $input = jQuery(this); 2959 if(!jQuery.fn.datepicker) return; 2960 2961 if(!$input.data('datepicker')) 2962 { 2963 $input.removeClass('hasDatepicker'); 2964 $input.datepicker({ 2965 changeYear: true, 2966 changeMonth: true, 2967 dateFormat: datepicker_format, 2968 gotoCurrent: true, 2969 yearRange: 'c-3:c+5', 2970 }); 2971 } 2972 2973 $input.datepicker('show'); 2974}); 2975 2976// ===== Basic / Advanced toggle (mec-basvanced-toggle) ===== 2977// Delegated so it works wherever events.js loads: the admin editor AND the 2978// FES front-end form (backend.js, which used to bind this directly, is not 2979// loaded on the front-end). Replaces the old direct binding in backend.js. 2980jQuery(document).on('click', '.mec-basvanced-toggle .mec-backend-tab-item', function (e) 2981{ 2982 e.preventDefault(); 2983 2984 var $item = jQuery(this); 2985 if($item.hasClass('mec-b-active-tab')) return; 2986 2987 var $toggle = $item.closest('.mec-basvanced-toggle'); 2988 var wrapper = $toggle.data('for'); 2989 var method = $toggle.data('method'); 2990 2991 $item.parent().find('.mec-b-active-tab').removeClass('mec-b-active-tab'); 2992 $item.addClass('mec-b-active-tab'); 2993 2994 var $wrapper = jQuery(wrapper); 2995 if(method === 'addition') $wrapper.find('.mec-basvanced-advanced').toggleClass('w-hidden'); 2996 else 2997 { 2998 $wrapper.find('.mec-basvanced-basic').toggleClass('w-hidden'); 2999 $wrapper.find('.mec-basvanced-advanced').toggleClass('w-hidden'); 3000 } 3001}); 3002 3003// ===== Publish-validation fallback (crash-isolated) ===== 3004// The main validation module lives INSIDE jQuery(document).ready and shares
3005// that callback with a dozen initializers â a runtime error anywhere above 3006// it would silently prevent its submit handler from ever being bound (no 3007// error surfaces at click time; the form simply submits). This top-level, 3008// document-delegated copy guarantees the critical guard â "cannot publish 3009// without a start date" + the wp-post-new-reload URL cleanup â survives any 3010// such failure. It is bound on document, so it runs AFTER the main 3011// form-level handler; if that one already handled the submit it stays 3012// silent (e.isDefaultPrevented()). 3013jQuery(document).on('submit', '#post', function (e) 3014{ 3015 if(e.isDefaultPrevented()) return; // the main validation already handled it 3016 3017 // Draft saves and previews are exempt from publish validation. 3018 var submitter = (e.originalEvent && e.originalEvent.submitter) ? jQuery(e.originalEvent.submitter) : null; 3019 if(submitter && submitter.attr('id') === 'save-post') return; 3020 if(jQuery('#wp-preview').val() === 'dopreview') return; 3021 3022 var $startDate = jQuery('#mec_start_date'); 3023 if(!$startDate.length) return; 3024 3025 // Undo WP core's address-bar rewrite: wp-admin/js/post.js appends 3026 // "wp-post-new-reload=true" on publish-click of an auto-draft even when 3027 // the submit ends up blocked, leaving a misleading URL behind. 3028 if(window.history && window.history.replaceState && window.location.href.indexOf('wp-post-new-reload') > -1) 3029 { 3030 window.history.replaceState(null, null, window.location.href.replace(/([?&])wp-post-new-reload=true&?/, '$1').replace(/[?&]$/, '')); 3031 } 3032 3033 if(jQuery.trim($startDate.val() || '') === '') 3034 { 3035 e.preventDefault(); 3036 3037 // Jump to the Date & Time tab so the field is visible. 3038 jQuery('#mec_metabox_details .mec-add-event-tabs-link[data-href="mec_meta_box_date_form"]').trigger('click'); 3039 3040 // Inline error on the field. 3041 $startDate.addClass('mec-field-invalid').attr('aria-invalid', 'true'); 3042 var $err = $startDate.next('.mec-field-error'); 3043 if(!$err.length) $err = jQuery('<span class="mec-field-error" role="alert"></span>').insertAfter($startDate); 3044 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.'; 3045 $err.text(msg); 3046 3047 // Notice card â self-contained so it renders even if the main module died. 3048 jQuery('.mec-notice-temp').remove(); 3049 var $n = jQuery('<div/>', { 3050 'class': 'mec-notice mec-notice--error mec-notice-temp', 3051 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>' 3052 }); 3053 jQuery('#poststuff').prepend($n); 3054 jQuery('html, body').animate({scrollTop: $n.offset().top - 40}, 200); 3055 $n.find('.mec-notice-dismiss, .mec-notice-close').on('click', function(){ $n.remove(); }); 3056 3057 $startDate.trigger('focus'); 3058 } 3059}); 3060 3061// ===== Native-required feedback (e.g. required Event Data fields) ===== 3062// When a field carries the HTML5 "required" attribute (custom Event Data 3063// fields can), the browser blocks submission BEFORE the submit event fires: 3064// none of the submit handlers above run, so no MEC notice appears and WP 3065// core's publish-click URL rewrite ("wp-post-new-reload=true") lingers in 3066// the address bar. The only signal is the non-bubbling "invalid" event â 3067// captured here on the document so the user still gets MEC-style feedback. 3068document.addEventListener('invalid', function (ev) 3069{ 3070 var field = ev.target; 3071 if(!field || !field.form || field.form.id !== 'post') return; 3072 3073 // Undo WP core's address-bar rewrite (same cleanup as the fallback). 3074 if(window.history && window.history.replaceState && window.location.href.indexOf('wp-post-new-reload') > -1) 3075 { 3076 window.history.replaceState(null, null, window.location.href.replace(/([?&])wp-post-new-reload=true&?/, '$1').replace(/[?&]$/, '')); 3077 } 3078 3079 // Friendly field name: its label, aria-label, placeholder or title. 3080 var name = ''; 3081 if(field.id) 3082 { 3083 var $l = jQuery('label[for="' + field.id + '"]').first(); 3084 if($l.length) name = jQuery.trim($l.text()); 3085 } 3086 if(!name) name = field.getAttribute('aria-label') || field.placeholder || field.title || ''; 3087 3088 jQuery('.mec-notice-temp').remove(); 3089 var msg = name 3090 ? '"' + jQuery('<div/>').text(name).html() + '" ' + (field.validationMessage || 'is required.') 3091 : (field.validationMessage || 'Please fill in the required fields.'); 3092 3093 var $n = jQuery('<div/>', { 3094 'class': 'mec-notice mec-notice--error mec-notice-temp', 3095 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>' 3096 });
3097 jQuery('#poststuff').prepend($n); 3098 jQuery('html, body').animate({scrollTop: $n.offset().top - 40}, 200); 3099 $n.find('.mec-notice-dismiss, .mec-notice-close').on('click', function(){ $n.remove(); }); 3100}, 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.