1 2class Divhunt 3{ 4 config = {}; 5 hold = {}; 6 data = { 7 debounce: {}, 8 triggers: {}, 9 counter: {}, 10 increaser: {}, 11 taken: {}, 12 config: {}, 13 focus: { 14 window: true 15 }, 16 keys: { 17 focus: {code: 0, time: 0, password: null, callbacks: {}}, 18 alt: {code: 18, time: 0, password: null, callbacks: {}}, 19 ctrl: {code: 17, time: 0, password: null, callbacks: {}}, 20 enter: {code: 13, time: 0, password: null, callbacks: {}}, 21 meta: {code: 91, time: 0, password: null, callbacks: {}}, 22 shift: {code: 16, time: 0, password: null, callbacks: {}}, 23 space: {code: 32, time: 0, password: null, callbacks: {}}, 24 backspace: {code: 8, time: 0, password: null, callbacks: {}}, 25 escape: {code: 27, time: 0, password: null, callbacks: {}}, 26 up: {code: 38, time: 0, password: null, callbacks: {}}, 27 down: {code: 40, time: 0, password: null, callbacks: {}}, 28 dot: {code: 190, time: 0, password: null, callbacks: {}}, 29 a: {code: 65, time: 0, password: null, callbacks: {}}, 30 p: {code: 80, time: 0, password: null, callbacks: {}}, 31 w: {code: 87, time: 0, password: null, callbacks: {}}, 32 q: {code: 81, time: 0, password: null, callbacks: {}}, 33 e: {code: 69, time: 0, password: null, callbacks: {}}, 34 d: {code: 68, time: 0, password: null, callbacks: {}}, 35 c: {code: 67, time: 0, password: null, callbacks: {}}, 36 r: {code: 82, time: 0, password: null, callbacks: {}}, 37 s: {code: 83, time: 0, password: null, callbacks: {}}, 38 v: {code: 86, time: 0, password: null, callbacks: {}}, 39 f: {code: 70, time: 0, password: null, callbacks: {}}, 40 z: {code: 90, time: 0, password: null, callbacks: {}}, 41 y: {code: 89, time: 0, password: null, callbacks: {}}, 42 43 mouseLeft: {code: null, time: 0, password: null, callbacks: {}}, 44 mouseMiddle: {code: null, time: 0, password: null, callbacks: {}}, 45 } 46 }; 47 48 constructor() 49 { 50 /* Mouse */ 51 window.mouse = { 52 left: false, 53 middle: false, 54 right: false, 55 x: false, 56 y: false, 57 skip: false 58 }; 59 60 /* Position */ 61 window.x = false; 62 window.y = false; 63 64 /* Config */ 65 let config = JSON.parse(localStorage.getItem('dh_config')); 66 67 if(config && typeof config === 'object') 68 { 69 this.data.config = config; 70 } 71 } 72} 73 74const dh = new Divhunt(); 75const $dh = {};$(document).on('click', '[dh-clipboard]', function() 76{ 77 let text = $(this).attr('dh-clipboard'); 78 79 if(!text) 80 { 81 return; 82 } 83 84 navigator.clipboard.writeText(text).then(() => 85 { 86 popup.notification('Copied to clipboard.', true); 87 }); 88});$(document).on('blur', 'input', function(event) 89{ 90 setTimeout(() => 91 { 92 if( 93 !$('input:focus').length && 94 !$('textarea:focus').length && 95 !$('[contenteditable]:focus').length 96 ) 97 { 98 dh.hold.stop('focus'); 99 } 100 }, 1); 101}); 102 103$(document).on('blur', 'textarea', function(event) 104{ 105 setTimeout(() => 106 { 107 if( 108 !$('input:focus').length && 109 !$('textarea:focus').length && 110 !$('[contenteditable]:focus').length 111 ) 112 { 113 dh.hold.stop('focus'); 114 } 115 }, 1); 116}); 117 118$(document).on('blur', '[contenteditable]', function(event) 119{ 120 setTimeout(() => 121 { 122 if( 123 !$('input:focus').length && 124 !$('textarea:focus').length && 125 !$('[contenteditable]:focus').length 126 ) 127 { 128 dh.hold.stop('focus'); 129 } 130 }, 1); 131}); 132 133$(document).on('focus', 'input', function(event) 134{ 135 dh.hold.start('focus'); 136}); 137 138$(document).on('focus', 'textarea', function(event) 139{ 140 dh.hold.start('focus'); 141}); 142 143$(document).on('focus', '[contenteditable]', function(event) 144{ 145 dh.hold.start('focus'); 146});window.addEventListener('popstate', function(event) 147{ 148 window.location.reload(); 149});$(document).on('keydown', function(event) 150{ 151 $.each(dh.data.keys, function(name, key) 152 { 153 if(key.interval) { return true; } 154 if(key.code === event.keyCode) 155 { 156 dh.hold.start(name); 157 } 158 }); 159}); 160 161$(document).on('keyup', function(event) 162{ 163 $.each(dh.data.keys, function(name, key) 164 { 165 if(key.code === event.keyCode || (event.keyCode === 91 && name !== 'focus')) 166 { 167 dh.hold.stop(name); 168 } 169 }); 170});$(document).mousemove(function(event) 171{ 172 window.x = event.clientX; 173 window.y = event.clientY; 174 175 if(window.mouse.skip) 176 { 177 window.mouse.skip = false;
178 } 179 else 180 { 181 window.mouse.skip = true; 182 } 183}); 184 185$(document).mouseup(function(event) 186{ 187 if(event.which === 1) 188 { 189 dh.hold.stop('mouseLeft'); 190 } 191 else if(event.which === 2) 192 { 193 dh.hold.stop('mouseMiddle'); 194 } 195}); 196 197$(document).mousedown(function(event) 198{ 199 window.mouse.x = window.x; 200 window.mouse.y = window.y; 201 202 if(event.which === 1) 203 { 204 dh.hold.start('mouseLeft'); 205 } 206 else if(event.which === 2) 207 { 208 dh.hold.start('mouseMiddle'); 209 } 210});$(document).on('input', '[dh-search]', function() 211{ 212 let value = $(this).val().toLowerCase(); 213 let id = $(this).attr('dh-search'); 214 let finds = []; 215 216 $.each($(`[search-id="${id}"]`), function() 217 { 218 let name = $(this).attr('search-value').toString().toLowerCase(); 219 220 if(!name.includes(value)) 221 { 222 $(this).hide(); 223 } 224 else 225 { 226 finds.push(this); 227 } 228 }); 229 230 finds.forEach((find) => 231 { 232 $(find).parents('[search-id]').show(); 233 $(find).find('[search-id]').show(); 234 $(find).show(); 235 }); 236});$(window).on('blur', function() 237{ 238 /* Keys */ 239 $.each(dh.data.keys, function(name, hold) 240 { 241 if(!hold.password) 242 { 243 hold.time = 0; 244 clearInterval(hold.interval); 245 hold.interval = null; 246 } 247 }); 248 249 /* Mouse */ 250 window.mouse.left = false; 251 window.mouse.middle = false; 252 window.mouse.right = false; 253 window.mouse.rightskip = false; 254 255 /* Position */ 256 window.x = false; 257 window.y = false; 258});$(window).focus(function() 259{ 260 /* Keys */ 261 $.each(dh.data.keys, function(name, hold) 262 { 263 if(!hold.password) 264 { 265 hold.time = 0; 266 clearInterval(hold.interval); 267 hold.interval = null; 268 } 269 }); 270 271 /* Mouse */ 272 window.mouse.left = false; 273 window.mouse.middle = false; 274 window.mouse.right = false; 275 window.mouse.rightskip = false; 276 277 /* Position */ 278 window.x = false; 279 window.y = false; 280});dh.attributes = function(attributes, sanitize = true) 281{ 282 let html = ''; 283 let pattern = /[^a-z0-9-_]/g; 284 285 $.each(attributes, function(key, value) 286 { 287 key = key.toLowerCase().replace(pattern, ''); 288 289 html += `${key}="${value !== null ? (sanitize ? dh.sanitize(value) : value) : ''}" `; 290 }); 291 292 return html.trim(); 293};dh.command = function(string) 294{ 295 let command = {name: null, options: {}}; 296 297 /* Build */ 298 command.build = () => 299 { 300 let build = command.name + ' '; 301 302 if(!command.name) 303 { 304 return null; 305 } 306 307 $.each(command.options, (key, values) => 308 { 309 values.forEach((value) => 310 { 311 build += `--${key} ${value}`; 312 }); 313 }); 314 315 return build; 316 }; 317 318 /* To String */ 319 command.toString = () => 320 { 321 let build = command.build(); 322 323 return build ? build : ''; 324 }; 325 326 if(typeof string !== 'string') 327 { 328 return command; 329 } 330 331 string = string.split('--'); 332 333 if(!(0 in string)) 334 { 335 return command; 336 } 337 338 command.name = string[0].trim(); 339 string.shift(); 340 341 string.forEach(data => 342 { 343 data = data.split(' '); 344 345 if(!data[0]) 346 { 347 return true; 348 } 349 350 let key = data[0].trim(); 351 352 data.shift(); 353 354 let value = data.join(' ').trim(); 355 356 if(!command.options[key]) 357 { 358 command.options[key] = []; 359 } 360 361 command.options[key].push(value); 362 }); 363 364 return command; 365};dh.config.get = function(key, value = null) 366{ 367 if(key in dh.data.config) 368 { 369 return dh.data.config[key]; 370 } 371 372 return value; 373};dh.config.set = function(key, value, save = false) 374{ 375 if(value === null) 376 { 377 delete dh.data.config[key]; 378 } 379 else 380 { 381 dh.data.config[key] = value; 382 } 383 384 if(save) 385 { 386 localStorage.setItem('dh_config', JSON.stringify(dh.data.config)); 387 } 388 389 return value; 390};dh.count = function(countable, count = 0) 391{ 392 if(typeof countable !== 'object') 393 { 394 return 0; 395 } 396 397 if($.isArray(countable)) 398 { 399 countable.forEach(function() 400 { 401 count++; 402 }); 403 404 return count; 405 } 406 407 $.each(countable, function() 408 { 409 count++; 410 }); 411 412 return count; 413}; 414dh.counter = function(key, value = null) 415{ 416 if(value === null) 417 { 418 return delete dh.data.counter[key]; 419 } 420 421 if(!(key in dh.data.counter)) 422 { 423 dh.data.counter[key] = 1; 424 } 425 426 if(dh.data.counter[key] >= value) 427 { 428 dh.data.counter[key] = 1; 429 return true; 430 } 431 432 dh.data.counter[key]++; 433 return false;
434}; 435dh.date = function(value, format = 'D M, Y') 436{ 437 if(typeof value !== 'string' || typeof format !== 'string') 438 { 439 return value; 440 } 441 442 let date = new Date(value); 443 444 let placeholders = { 445 Y: date.getFullYear(), 446 m: String(date.getMonth() + 1).padStart(2, '0'), 447 D: String(date.getDate()).padStart(2, '0'), 448 H: String(date.getHours()).padStart(2, '0'), 449 i: String(date.getMinutes()).padStart(2, '0'), 450 s: String(date.getSeconds()).padStart(2, '0'), 451 f: String(date.getMilliseconds()).padStart(3, '0'), 452 M: date.toLocaleString('default', { month: 'long' }) 453 }; 454 455 return format.replace(/Y|m|D|H|i|s|f|M/g, match => placeholders[match]); 456}; 457dh.debounce = function(callback, timeout = 100, key = 'main') 458{ 459 if(!(key in dh.data.debounce)) 460 { 461 dh.data.debounce[key] = null; 462 } 463 464 clearTimeout(dh.data.debounce[key]); 465 dh.data.debounce[key] = setTimeout(() => { callback(); }, timeout); 466};dh.ellipsisMiddle = function(text, maxLength = 30, endChars = 10) 467{ 468 if(typeof text !== 'string') 469 { 470 return text; 471 } 472 473 if(text.length <= maxLength) 474 { 475 return text; 476 } 477 478 let startLength = maxLength - endChars - 3; 479 480 if(startLength < 1) 481 { 482 startLength = 1; 483 } 484 485 let start = text.substring(0, startLength); 486 let end = text.substring(text.length - endChars); 487 488 return start + '...' + end; 489}; 490dh.extractor = function([beg, end]) 491{ 492 const matcher = new RegExp(`${beg}(.*?)${end}`,'gm'); 493 const normalise = (str) => str.slice(beg.length,end.length*-1); 494 495 return function(str) 496 { 497 str = str + ''; 498 499 let match = str.match(matcher); 500 501 if(match !== null) 502 { 503 return match.map(normalise); 504 } 505 506 return false; 507 } 508};dh.focus = function(target, color = 'var(--dh-bg-3)') 509{ 510 target.css('transition', 'background-color 250ms ease-out').css('background', color); 511 512 setTimeout(() => 513 { 514 target.css('background', ''); 515 }, 250); 516};dh.getPoint = function(x, y) 517{ 518 let event = document.elementFromPoint(x, y); 519 520 if($(event).length) 521 { 522 return event; 523 } 524}; 525dh.guid = function() 526{ 527 let d = new Date().getTime(); 528 let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0; 529 530 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) 531 { 532 let r = Math.random() * 16; 533 534 if(d > 0) 535 { 536 r = (d + r)%16 | 0; 537 d = Math.floor(d/16); 538 } 539 else 540 { 541 r = (d2 + r)%16 | 0; 542 d2 = Math.floor(d2/16); 543 } 544 return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 545 }); 546};dh.hold.callback = function(name, id, callback = null) 547{ 548 let hold = dh.data.keys[name]; 549 550 if(callback === null) 551 { 552 return delete hold.callbacks[id]; 553 } 554 555 hold.callbacks[id] = callback; 556};dh.hold.start = function(name, password = null) 557{ 558 let hold = dh.data.keys[name]; 559 if(hold.interval) { return false; } 560 561 hold.time = 1; 562 hold.password = password; 563 hold.interval = setInterval(() => 564 { 565 hold.time += 25; 566 }, 25); 567 568 return true; 569};dh.hold.stop = function(name, password = null) 570{ 571 let hold = dh.data.keys[name]; 572 if(!hold.interval) { return true; } 573 574 if(hold.password !== password) 575 { 576 console.log('Incorrect hold password'); 577 return false 578 } 579 580 clearInterval(hold.interval); 581 setTimeout(function() 582 { 583 hold.time = 0; 584 hold.interval = null; 585 hold.password = null; 586 }, 25); 587 588 return true; 589};dh.icon = function(icon, size = 18, group = 'all', attributes = {}) 590{ 591 size = size === null ? '' : `size${size}x${size}`; 592 593 if('class' in attributes) 594 { 595 attributes['class'] = attributes['class'] + ` dh-svg-sprite-icon ${size}`; 596 } 597 else 598 { 599 attributes['class'] = `dh-svg-sprite-icon ${size}`; 600 } 601 602 return ` 603 <svg ${dh.attributes(attributes)}> 604 <use xlink:href="/assets/icons/${group}.svg?v=54654354#${dh.sanitize(icon)}"></use> 605 </svg> 606 `; 607};dh.inView = function(element, offsetY = 0, offsetX = 0) 608{ 609 let rect = element[0].getBoundingClientRect(); 610 let windowHeight = window.innerHeight || document.documentElement.clientHeight; 611 let windowWidth = window.innerWidth || document.documentElement.clientWidth; 612 613 return ( 614 rect.bottom - offsetY >= 0 && 615 rect.top + offsetY <= windowHeight && 616 rect.right - offsetX >= 0 && 617 rect.left + offsetX <= windowWidth 618 ); 619 };dh.increaser = function(key, max) 620{ 621 if(!(key in dh.data.increaser)) 622 { 623 dh.data.increaser[key] = 0; 624 } 625 626 if(dh.data.increaser[key] >= max) 627 { 628 dh.data.increaser[key] = 0; 629 } 630 631 dh.data.increaser[key]++; 632 return dh.data.increaser[key]; 633}; 634/* 635 * Detects if key is currently pressed 636 * Other keys can be pessed at same time 637 */ 638dh.key = function(name, duration = null) 639{ 640 if(!(name in this.data.keys)) 641 { 642 return null; 643 } 644 645 if(duration === null) 646 { 647 return !!this.data.keys[name].interval; 648 } 649 650 return this.data.keys[name].time >= duration; 651}; 652 653/* 654 * Detects if multiple keys are pressed if name time 655 * Strict: All keys must be pressed 656 * !Strict: At least 1 key must be pressed 657 * Other keys can be pessed at same time 658 */ 659dh.keys = function(keys, strict = false) 660{ 661 let pressed = strict ? true : false;
662 663 keys.forEach((name) => 664 { 665 if(dh.key(name)) 666 { 667 if(!strict) { pressed = true; } 668 } 669 else 670 { 671 if(strict) { pressed = false; } 672 } 673 }); 674 675 return pressed; 676}; 677 678/* 679 * Detects if no keys are pressed at the time 680 */ 681dh.keysClear = function() 682{ 683 let pressed = false; 684 685 $.each(this.data.keys, (name, key) => 686 { 687 if(key.interval) 688 { 689 pressed = true; 690 return false; 691 } 692 }); 693 694 return !pressed; 695};/* 696 * Detects if key is pressed 697 * No other keys are pessed at same time 698 */ 699dh.keyOnly = function(name) 700{ 701 if(!(name in this.data.keys)) 702 { 703 return false; 704 } 705 706 let pressed = false; 707 708 $.each(this.data.keys, function(id, hold) 709 { 710 if(id === name) 711 { 712 if(hold.interval) 713 { 714 pressed = true; 715 } 716 717 return true; 718 } 719 720 if(hold.interval) 721 { 722 pressed = false; return false; 723 } 724 }); 725 726 return pressed; 727}; 728 729/* 730 * Detects if keys are pressed 731 * Strict: All keys must be pressed 732 * !Strict: At least 1 key must be pressed 733 * No other keys are pessed at same time 734 */ 735dh.keysOnly = function(keys, strict = false) 736{ 737 let pressed = strict ? true : false; 738 739 keys.forEach((name) => 740 { 741 if(dh.key(name)) 742 { 743 if(!strict) { pressed = true; } 744 } 745 else 746 { 747 if(strict) { pressed = false; } 748 } 749 }); 750 751 $.each(dh.data.keys, (name, hold) => 752 { 753 if(!keys.includes(name) && hold.interval) 754 { 755 pressed = false; 756 return false; 757 } 758 }); 759 760 return pressed; 761};dh.loader = function(id, count) 762{ 763 let i = 1; 764 let html = ''; 765 766 while(i <= count) 767 { 768 html+= `<div></div>`; 769 i++; 770 } 771 772 return `<div class="dh-loader dh-loader-${id}">${html}</div>`; 773};dh.matrix = function(target, key = null, default_value = false) 774{ 775 let matrix = target.css('transform'); 776 777 if(matrix === 'none') 778 { 779 return default_value; 780 } 781 782 if(key === null) 783 { 784 return matrix; 785 } 786 787 matrix = matrix.match(/-?[\d\.]+/g); 788 789 if(key === 'x') 790 { 791 return parseInt(matrix[matrix.length - 2]); 792 } 793 794 if(key === 'y') 795 { 796 return parseInt(matrix[matrix.length - 1]); 797 } 798 799 if(key === 'scale') 800 { 801 return parseInt(matrix[0]); 802 } 803 804 if(key === 'rotate') 805 { 806 return parseInt(matrix[1]); 807 } 808}; 809dh.max = function(key = null, array, max = 0) 810{ 811 if($.isArray(array)) 812 { 813 array.forEach(function(value) 814 { 815 if(key === null) 816 { 817 if(max < parseInt(value)) 818 { 819 max = parseInt(value); 820 } 821 } 822 else 823 { 824 if(max < parseInt(value[key])) 825 { 826 max = parseInt(value[key]); 827 } 828 } 829 }); 830 } 831 else if(typeof array == 'object') 832 { 833 $.each(array, function(index, value) 834 { 835 if(key === null) 836 { 837 if(max < parseInt(index)) 838 { 839 max = parseInt(index); 840 } 841 } 842 else 843 { 844 if(max < parseInt(value[key])) 845 { 846 max = parseInt(value[key]); 847 } 848 } 849 }); 850 } 851 852 return parseInt(max); 853}; 854dh.min = function(key = null, array, min = 1000000000000000) 855{ 856 if($.isArray(array)) 857 { 858 array.forEach(function(value) 859 { 860 if(key === null) 861 { 862 if(min > value) 863 { 864 min = value; 865 } 866 } 867 else 868 { 869 if(min > value[key]) 870 { 871 min = value[key]; 872 } 873 } 874 }); 875 } 876 else if(typeof array == 'object') 877 { 878 $.each(array, function(index, value) 879 { 880 if(key === null) 881 { 882 if(min > index) 883 { 884 min = index; 885 } 886 } 887 else 888 { 889 if(min > value[key]) 890 { 891 min = value[key]; 892 } 893 } 894 }); 895 } 896 897 if(min === 1000000000000000) 898 { 899 return 0; 900 } 901 902 return parseInt(min); 903}; 904dh.monaco = function(id) 905{ 906 let find = null; 907 908 if(typeof monaco === 'undefined') 909 { 910 return null; 911 } 912 913 let editors = monaco.editor.getEditors(); 914 915 $.each(editors, function(index, editor) 916 { 917 if($(editor.getContainerDomNode()).attr('id') === id) 918 { 919 find = editors[index]; 920 } 921 }); 922 923 return find; 924};dh.offset = function(target, percent = 10, horizontal = false, calculate = null) 925{ 926 let offset = {}; 927 928 offset.insert = 'inside'; 929 930 offset.width = target.outerWidth(); 931 offset.height = target.outerHeight(); 932 933 if(typeof calculate === 'function') 934 { 935 calculate(offset); 936 } 937 938 offset.offset = target.offset(); 939 940 offset.left = window.x - offset.offset.left; 941 offset.top = window.y - offset.offset.top; 942 943 offset.percent = {}; 944 offset.percent.left = (offset.left / offset.width * 100); 945 offset.percent.top = (offset.top / offset.height * 100); 946 offset.percent.right = (offset.width - offset.left) / offset.width * 100; 947 offset.percent.bottom = (offset.height - offset.top) / offset.height * 100; 948 949 if(horizontal) 950 { 951 if(offset.percent.left <= percent) 952 { 953 offset.insert = 'before'; 954 } 955 956 if(offset.percent.right <= percent) 957 { 958 offset.insert = 'after'; 959 } 960 } 961 962 if(offset.percent.top <= percent) 963 { 964 offset.insert = 'before'; 965 } 966 967 if(offset.percent.bottom <= percent) 968 { 969 offset.insert = 'after'; 970 } 971 972 return offset; 973};dh.position = function(target) 974{ 975 let position = {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0}; 976 977 position.width = $(target).outerWidth(); 978 position.height = $(target).outerHeight(); 979 980 position.top = $(target).offset().top; 981 position.left = $(target).offset().left; 982 983 position.right = position.width + position.left; 984 position.bottom = position.height + position.top; 985 986 return position; 987}; 988dh.query = function(url, callback = null) 989{ 990 if(typeof url !== 'string') 991 { 992 return {}; 993 } 994 995 let query = {}; 996
997 new URLSearchParams(url.split('?')[1]).forEach(function(value, key) 998 { 999 let data = {key, value: decodeURIComponent(value)}; 1000 1001 if(callback) 1002 { 1003 callback(data); 1004 } 1005 1006 query[data.key] = data.value; 1007 }); 1008 1009 return query; 1010};dh.route = function(route, data = null) 1011{ 1012 route = route.substring(0, 70); 1013 route = route.replace(/\s+/g, '-'); 1014 route = route.toLowerCase().split('/'); 1015 route = route.filter(function(value) 1016 { 1017 return /^[a-z0-9-_:\s]+$/i.test(value); 1018 }); 1019 1020 if(data && typeof data === 'object') 1021 { 1022 route.forEach((value, index) => 1023 { 1024 if(value.startsWith(':')) 1025 { 1026 let key = value.slice(1); 1027 1028 if(typeof data[key] === 'string' || typeof data[key] === 'number') 1029 { 1030 route[index] = data[key]; 1031 } 1032 } 1033 }); 1034 } 1035 1036 return '/' + route.join('/'); 1037};dh.sanitize = function(string) 1038{ 1039 if(typeof string !== 'string') 1040 { 1041 return string; 1042 } 1043 1044 var map = {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}; 1045 1046 return string.replace(/[&<>"']/g, function(c) 1047 { 1048 return map[c]; 1049 }); 1050};dh.slug = function(string) 1051{ 1052 if(typeof string !== 'string') 1053 { 1054 return null; 1055 } 1056 1057 string = string.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); 1058 1059 let slug = string.toLowerCase().trim(); 1060 1061 slug = slug.replace(/[^a-z0-9]+/g, '-'); 1062 slug = slug.replace(/-+/g, '-'); 1063 slug = slug.trim('-'); 1064 1065 return slug; 1066}; 1067dh.sort = function(key, array, type = 'ASC') 1068{ 1069 if($.isArray(array)) 1070 { 1071 if(type === 'ASC') 1072 { 1073 return array.sort(function(first, second) 1074 { 1075 return first[key] - second[key]; 1076 }); 1077 } 1078 1079 return array.sort(function(first, second) 1080 { 1081 return second[key] - first[key]; 1082 }); 1083 } 1084 else if(typeof array == 'object') 1085 { 1086 if(type === 'ASC') 1087 { 1088 return Object.keys(array).sort(function(first, second) 1089 { 1090 return array[first][key] - array[second][key]; 1091 }); 1092 } 1093 1094 return Object.keys(array).sort(function(first, second) 1095 { 1096 return array[second][key] - array[first][key]; 1097 }); 1098 } 1099}; 1100dh.taken = function(key, check = null) 1101{ 1102 if(!(key in dh.data.taken)) 1103 { 1104 dh.data.taken[key] = false; 1105 } 1106 1107 if(check === null) 1108 { 1109 return dh.data.taken[key]; 1110 } 1111 1112 return dh.data.taken[key] = check; 1113}; 1114dh.throttle = function(callback, delay) 1115{ 1116 let lastCall = 0; 1117 1118 return function (...args) 1119 { 1120 const now = new Date().getTime(); 1121 1122 if(now - lastCall >= delay) 1123 { 1124 lastCall = now; 1125 callback(...args); 1126 } 1127 }; 1128};dh.trigger = function(name, info = '', ...params) 1129{ 1130 if(dh.config.get('debug')) 1131 { 1132 console.log('%cTrigger: ' + '%c' + name + ' %c['+info+']', 'color: #438ce6', '', 'color: #cebc18'); 1133 } 1134 1135 $(document).trigger(name, [...params]); 1136};dh.updater = function(id, value, type = 'text') 1137{ 1138 if(!value) 1139 { 1140 return; 1141 } 1142 1143 let target = $(`[dh-updater="${id}"]`); 1144 1145 target.animate({opacity: 0}, 100); 1146 1147 setTimeout(() => 1148 { 1149 if(type === 'text') 1150 { 1151 target.text(value); 1152 } 1153 else if(type === 'html') 1154 { 1155 target.html(html); 1156 } 1157 }, 100); 1158 1159 setTimeout(() => 1160 { 1161 target.animate({opacity: 1}, 100); 1162 }, 200); 1163};dh.uuid = function(name) 1164{ 1165 let hash = 0; 1166 1167 for (let i = 0; i < name.length; i++) 1168 { 1169 hash = (hash << 5) - hash + name.charCodeAt(i); 1170 hash |= 0; 1171 } 1172 1173 return Math.abs(hash).toString(16); 1174}; 1175const Addons = new class 1176{ 1177 data = {}; 1178 1179 /* 1180 * * * * * * * 1181 * * * * * * * 1182 * * * * * * * 1183 * * * * * * * 1184 * * * * * * * 1185 * * * * * * * 1186 * 1187 * Addon GET Functionality 1188 * 1189 * * * * * * * 1190 * * * * * * * 1191 * * * * * * * 1192 * * * * * * * 1193 * * * * * * * 1194 * * * * * * * 1195 */ 1196 1197 get() 1198 { 1199 return this.data; 1200 }; 1201 1202 getOne(name) 1203 { 1204 return this.data[name]; 1205 } 1206 1207 /* 1208 * * * * * * * 1209 * * * * * * * 1210 * * * * * * * 1211 * * * * * * * 1212 * * * * * * * 1213 * * * * * * * 1214 *
1215 * Addon THEN Functionality 1216 * 1217 * * * * * * * 1218 * * * * * * * 1219 * * * * * * * 1220 * * * * * * * 1221 * * * * * * * 1222 * * * * * * * 1223 */ 1224 1225 then(name, callback) 1226 { 1227 if(!(name in this.data)) 1228 { 1229 return; 1230 } 1231 1232 callback(this.data[name]); 1233 }; 1234 1235 /* 1236 * * * * * * * 1237 * * * * * * * 1238 * * * * * * * 1239 * * * * * * * 1240 * * * * * * * 1241 * * * * * * * 1242 * 1243 * Addon CREATE Functionality 1244 * 1245 * * * * * * * 1246 * * * * * * * 1247 * * * * * * * 1248 * * * * * * * 1249 * * * * * * * 1250 * * * * * * * 1251 */ 1252 1253 create(name, callback = null) 1254 { 1255 if(!(name in this.data)) 1256 { 1257 this.data[name] = new Addon({ 1258 name, 1259 route: null, 1260 items: { 1261 callbacks: {add: [], set: [], remove: []}, 1262 keys: [], 1263 fields: {}, 1264 data: {}, 1265 id: 1 1266 }, 1267 pages: {}, 1268 functions: {}, 1269 actions: {progress: false, pool: []}, 1270 options: {temp: {}, save: dh.config.get(name, {})}, 1271 ready: false, 1272 reload: true, 1273 global: null 1274 }); 1275 } 1276 1277 if(callback) 1278 { 1279 callback(this.data[name]); 1280 } 1281 1282 return this.data[name]; 1283 }; 1284};class Addon 1285{ 1286 data; 1287 1288 constructor(data) 1289 { 1290 this.data = data; 1291 } 1292 1293 /* 1294 * * * * * * * 1295 * * * * * * * 1296 * * * * * * * 1297 * * * * * * * 1298 * * * * * * * 1299 * * * * * * * 1300 * 1301 * Addon GET Functionality 1302 * 1303 * * * * * * * 1304 * * * * * * * 1305 * * * * * * * 1306 * * * * * * * 1307 * * * * * * * 1308 * * * * * * * 1309 */ 1310 1311 GetName() 1312 { 1313 return this.data.name; 1314 } 1315 1316 GetReload() 1317 { 1318 return this.data.reload; 1319 } 1320 1321 GetRoute() 1322 { 1323 return this.data.route; 1324 } 1325 1326 GetItems() 1327 { 1328 return this.data.items; 1329 } 1330 1331 GetFunctions() 1332 { 1333 return this.data.functions; 1334 } 1335 1336 GetOptions() 1337 { 1338 return this.data.options; 1339 } 1340 1341 GetPages() 1342 { 1343 return this.data.pages; 1344 } 1345 1346 GetReady() 1347 { 1348 return this.data.ready; 1349 } 1350 1351 GetCreated() 1352 { 1353 return this.data.created; 1354 } 1355 1356 GetTemp(key, _default = null) 1357 { 1358 return this.OptionGet('temp', key, _default); 1359 } 1360 1361 GetSave(key, _default = null) 1362 { 1363 return this.OptionGet('save', key, _default); 1364 } 1365 1366 /* 1367 * * * * * * * 1368 * * * * * * * 1369 * * * * * * * 1370 * * * * * * * 1371 * * * * * * * 1372 * * * * * * * 1373 * 1374 * Addon SET Functionality 1375 * 1376 * * * * * * * 1377 * * * * * * * 1378 * * * * * * * 1379 * * * * * * * 1380 * * * * * * * 1381 * * * * * * * 1382 */ 1383 1384 SetReload(reload) 1385 { 1386 this.data.reload = reload; 1387 1388 return this; 1389 }; 1390 1391 SetRoute(route) 1392 { 1393 this.data.route = route; 1394 1395 return this; 1396 }; 1397 1398 SetTemp(key, value = null) 1399 { 1400 return this.OptionSet('temp', key, value); 1401 }; 1402 1403 SetSave(key, value = null) 1404 { 1405 return this.OptionSet('save', key, value); 1406 }; 1407 1408 /* 1409 * * * * * * * 1410 * * * * * * * 1411 * * * * * * * 1412 * * * * * * * 1413 * * * * * * * 1414 * * * * * * * 1415 * 1416 * Addon FIELDS Functionality 1417 * 1418 * * * * * * * 1419 * * * * * * * 1420 * * * * * * * 1421 * * * * * * * 1422 * * * * * * * 1423 * * * * * * * 1424 */ 1425 1426 FieldAdd(name, get = null, set = null) 1427 { 1428 if(typeof get !== 'function') { get = null; } 1429 if(typeof set !== 'function') { set = null; } 1430 1431 this.data.items.fields[name] = {get, set}; 1432 } 1433 1434 /* 1435 * * * * * * * 1436 * * * * * * * 1437 * * * * * * * 1438 * * * * * * * 1439 * * * * * * * 1440 * * * * * * * 1441 * 1442 * Addon ITEMS Functionality 1443 * 1444 * * * * * * * 1445 * * * * * * * 1446 * * * * * * * 1447 * * * * * * * 1448 * * * * * * * 1449 * * * * * * * 1450 */ 1451 1452 ItemsKeys(keys) 1453 { 1454 this.data.items.keys = keys; 1455 } 1456 1457 ItemsGet() 1458 { 1459 return this.data.items.data; 1460 } 1461 1462 ItemsRaw() 1463 { 1464 let items = {}; 1465 1466 $.each(this.data.items.data, function(id, item) 1467 { 1468 items[id] = item.data; 1469 }); 1470 1471 return items; 1472 } 1473 1474 ItemGet(item, then = null) 1475 { 1476 let find = null; 1477 1478 if(item && typeof item === 'object') 1479 { 1480 find = this.ItemAdd(item, false); 1481 } 1482 else 1483 { 1484 if(item in this.data.items.data) 1485 { 1486 find = this.data.items.data[item]; 1487 } 1488 } 1489 1490 return then ? (find ? then(find) : null) : find; 1491 } 1492 1493 ItemsTarget(field = null) 1494 { 1495 if(field !== null) 1496 { 1497 return $(`items[field="${field}"][for="${this.GetName()}"]`); 1498 } 1499 1500 return $(`items[for="${this.GetName()}"]`); 1501 } 1502 1503 ItemsClear(hard = false) 1504 { 1505 if(!hard) 1506 { 1507 return this.data.items.data = {}; 1508 } 1509 1510 $.each(this.data.items.data, function(id, item) 1511 { 1512 item.Remove(); 1513 }); 1514 } 1515 1516 ItemsAdd(...items) 1517 { 1518 let added = []; 1519
1520 items.forEach((item) => 1521 { 1522 added.push(this.ItemAdd(item, 'multiple')); 1523 }); 1524 1525 return added; 1526 } 1527 1528 ItemsArrayAdd(items) 1529 { 1530 let added = []; 1531 1532 items.forEach((item) => 1533 { 1534 added.push(this.ItemAdd(item, 'multiple')); 1535 }); 1536 1537 return added; 1538 } 1539 1540 ItemAdd(object, type = 'single') 1541 { 1542 /* Object */ 1543 if(typeof object !== 'object') { return; } 1544 1545 /* Binds */ 1546 if(Array.isArray(object)) 1547 { 1548 object = object.reduce((acc, value, index) => 1549 { 1550 acc[this.data.items.keys[index]] = value; 1551 return acc; 1552 }, {}); 1553 } 1554 1555 /* Id */ 1556 if(!('id' in object)) 1557 { 1558 object.id = this.data.items.id; 1559 } 1560 1561 let item; 1562 1563 /* If Exist */ 1564 if(object.id in this.data.items.data) 1565 { 1566 item = this.data.items.data[object.id]; 1567 1568 /* Modify */ 1569 $.each(object, function(key, value) 1570 { 1571 if(key in item.data) 1572 { 1573 item.data[key] = value; 1574 } 1575 }); 1576 } 1577 else 1578 { 1579 /* Increment */ 1580 this.data.items.id++; 1581 1582 /* Validate */ 1583 item = new AddonItem(this, this.ItemValidate(object)); 1584 } 1585 1586 /* Set */ 1587 this.data.items.data[item.data.id] = item; 1588 1589 /* Callbacks */ 1590 if(type) 1591 { 1592 this.data.items.callbacks.add.forEach((callback) => 1593 { 1594 callback(this, item, type); 1595 }); 1596 } 1597 1598 return item; 1599 } 1600 1601 ItemValidate(item) 1602 { 1603 /* Remove Non-Existing Keys */ 1604 $.each(item, (key) => 1605 { 1606 if(!(key in this.data.items.fields) && !['id', 'history'].includes(key)) 1607 { 1608 delete item[key]; 1609 } 1610 }); 1611 1612 /* Add Existing Non-Provided Keys */ 1613 $.each(this.data.items.fields, (name, field) => 1614 { 1615 if(!(name in item)) 1616 { 1617 item[name] = null; 1618 } 1619 }); 1620 1621 /* Process SET Callback */ 1622 $.each(this.data.items.fields, (name, field) => 1623 { 1624 if(field.set) 1625 { 1626 item[name] = field.set(item[name], item); 1627 } 1628 }); 1629 1630 return item; 1631 } 1632 1633 ItemCatch(type, callback) 1634 { 1635 if(typeof callback !== 'function') { return null; } 1636 1637 if(type in this.data.items.callbacks) 1638 { 1639 this.data.items.callbacks[type].push(callback); 1640 } 1641 } 1642 1643 OnItemAdd(callback) 1644 { 1645 return this.ItemCatch('add', callback); 1646 } 1647 1648 OnItemSet(callback) 1649 { 1650 return this.ItemCatch('set', callback); 1651 } 1652 1653 OnItemRemove(callback) 1654 { 1655 return this.ItemCatch('remove', callback); 1656 } 1657 1658 ItemNewId() 1659 { 1660 let seconds = Math.floor(Date.now() / 1000); 1661 let miliseconds = parseInt(((Date.now() % 1000) / 1000).toFixed(3).replace('.', '')); 1662 1663 return parseInt(seconds + miliseconds.toString().padStart(3, '0') + dh.increaser(this.GetName(), 999).toString().padStart(3, '0')); 1664 } 1665 1666 /* 1667 * * * * * * * 1668 * * * * * * * 1669 * * * * * * * 1670 * * * * * * * 1671 * * * * * * * 1672 * * * * * * * 1673 * 1674 * Addon Pages Functionality 1675 * 1676 * * * * * * * 1677 * * * * * * * 1678 * * * * * * * 1679 * * * * * * * 1680 * * * * * * * 1681 * * * * * * * 1682 */ 1683 1684 PagesGet() 1685 { 1686 return this.data.pages; 1687 } 1688 1689 PageGet(slug) 1690 { 1691 return this.data.pages[slug]; 1692 } 1693 1694 PageAdd(slug, route, callback) 1695 { 1696 this.data.pages[slug] = new AddonPage(this, {slug, route, description: '', default: true}); 1697 1698 this.FunctionCreate('page.' + slug, callback); 1699 } 1700 1701 OnPage(slug, before = null, after = null) 1702 { 1703 this.FunctionCatch('page.' + slug, before, after); 1704 } 1705 1706 Route(slug, vars = {}) 1707 { 1708 let page = this.PageGet(slug); 1709 1710 if(!page) 1711 { 1712 return this.GetRoute(); 1713 } 1714 1715 return page.GetRoute(true, vars); 1716 } 1717 1718 /* 1719 * * * * * * * 1720 * * * * * * * 1721 * * * * * * * 1722 * * * * * * * 1723 * * * * * * * 1724 * * * * * * * 1725 * 1726 * Addon FUNCTIONS Functionality 1727 * 1728 * * * * * * * 1729 * * * * * * * 1730 * * * * * * * 1731 * * * * * * * 1732 * * * * * * * 1733 * * * * * * * 1734 */ 1735 1736 FunctionCreate(name, callback) 1737 { 1738 if(typeof callback !== 'function') { return; } 1739 1740 if(!(name in this.data.functions)) 1741 { 1742 return this.data.functions[name] = {callback, before: [], after: []}; 1743 } 1744 1745 this.data.functions[name].callback = callback; 1746 }; 1747 1748 FunctionGet(name) 1749 { 1750 return this.data.functions[name]; 1751 }; 1752 1753 FunctionRun(name, ...data) 1754 { 1755 /* Check if !Exist */ 1756 if(!(name in this.data.functions)) 1757 { 1758 if(name[0] === '@') { return ''; } 1759 return console.error(`Function "${name}" from "${this.GetName()}" does not exists.`); 1760 } 1761 1762 /* Catch Before */ 1763 if(this.data.functions[name].trigger) 1764 { 1765 $.each(this.data.functions[name].before, (index, callbackBefore) => 1766 { 1767 try 1768 { 1769 callbackBefore(this, ...data); 1770 } 1771 catch(error) 1772 { 1773 1774 } 1775 }); 1776 } 1777 1778 /* Callback */ 1779 let callback = this.data.functions[name].callback; 1780 let response = null; 1781 1782 if(typeof callback !== 'function') 1783 { 1784 if(name[0] !== '@') 1785 { 1786 console.warn(`Function "${name}" callback from "${this.GetName()}" does not exists.`); 1787 } 1788 } 1789 else 1790 { 1791 /* Clone */ 1792 let newCallback = callback.bind({}); 1793 1794 try 1795 { 1796 /* Response */ 1797 response = newCallback(this, ...data); 1798 1799 /* Object */ 1800 if(response && typeof response === 'object' && ('init' in response)) 1801 { 1802 response = response.init(); 1803 } 1804 } 1805 catch(error) 1806 { 1807 1808 } 1809 } 1810 1811 /* Log */ 1812 if(this.data.functions[name].trigger && window.location.href.includes('logging')) 1813 { 1814 console.log('%c'+this.GetName()+'%c %c['+name+']', 'color: #438ce6', '', 'color: #cebc18'); 1815 } 1816 1817 /* Catch After */ 1818 if(this.data.functions[name].trigger) 1819 { 1820 $.each(this.data.functions[name].after, (index, callbackAfter) => 1821 { 1822 try 1823 { 1824 callbackAfter(this, response, ...data); 1825 } 1826 catch(error) 1827 { 1828 1829 } 1830 }); 1831 } 1832 1833 return response; 1834 }; 1835 1836 FunctionCatch(name, before = null, after = false) 1837 { 1838 /* Check if !Exist */ 1839 if(!(name in this.data.functions)) 1840 { 1841 this.data.functions[name] = {callback: false, trigger: true, before: [], after: []}; 1842 } 1843 else 1844 { 1845 this.data.functions[name].trigger = true; 1846 } 1847 1848 /* Before */ 1849 if(typeof before === 'function') 1850 { 1851 this.data.functions[name].before.push(before); 1852 } 1853 1854 /* After */ 1855 if(typeof after === 'function') 1856 { 1857 this.data.functions[name].after.push(after); 1858 } 1859 }; 1860 1861 Fn(name, ...data) 1862 { 1863 return this.FunctionRun(name, ...data); 1864 }; 1865 1866 /* 1867 * * * * * * * 1868 * * * * * * * 1869 * * * * * * * 1870 * * * * * * * 1871 * * * * * * * 1872 * * * * * * * 1873 * 1874 * Addon OPTIONS Functionality 1875 * 1876 * * * * * * * 1877 * * * * * * * 1878 * * * * * * * 1879 * * * * * * * 1880 * * * * * * * 1881 * * * * * * * 1882 */ 1883 1884 OptionGet(type = 'save', key, _default = null) 1885 { 1886 /* Options */ 1887 let options = this.data.options[type === 'temp' ? 'temp' : 'save']; 1888 1889 /* Return Default */ 1890 if(!(key in options)) { return _default; } 1891 1892 /* Value */ 1893 let value = options[key]; 1894 1895 /* Default */ 1896 if(_default !== null && typeof value !== typeof _default) 1897 { 1898 return _default; 1899 } 1900 1901 /* Value */ 1902 return value; 1903 }; 1904 1905 OptionSet(type = 'save', key, value = null) 1906 { 1907 /* Options */ 1908 let options = this.data.options[type === 'temp' ? 'temp' : 'save']; 1909 1910 /* Set */ 1911 if(value === null) 1912 { 1913 delete options[key]; 1914 } 1915 else 1916 { 1917 options[key] = value; 1918 } 1919 1920 /* Save */ 1921 if(type === 'save') 1922 { 1923 dh.config.set(this.GetName(), options, true); 1924 } 1925 }; 1926 1927 /* 1928 * * * * * * * 1929 * * * * * * * 1930 * * * * * * * 1931 * * * * * * * 1932 * * * * * * * 1933 * * * * * * * 1934 * 1935 * Addon RENDER Functionality 1936 * 1937 * * * * * * * 1938 * * * * * * * 1939 * * * * * * * 1940 * * * * * * * 1941 * * * * * * * 1942 * * * * * * * 1943 */ 1944 1945 Render(name, data = {}, attributes = {}, wrap = true, replace = false, target = null, item = null) 1946 { 1947 return this.FunctionRun('render.' + name, data, attributes, wrap, replace, target, item); 1948 }; 1949 1950 RenderCatch(name, before = null, after = null) 1951 { 1952 return this.FunctionCatch('render.' + name, before, after); 1953 }; 1954 1955 RenderCreate(name, callback) 1956 { 1957 return this.FunctionCreate('render.' + name, function(addon, _data = {}, _attributes = {}, _wrap = true, _replace = false, _target, _item) 1958 { 1959 /* Create Data Function */ 1960 let data = function(key = null, _default = null, sanitize = false) 1961 { 1962 /* Return all */ 1963 if(key === null) { return _data; } 1964 1965 /* Identifier */ 1966 if(key === 'identifier') { return dh.increaser('callbacks', 10000); } 1967 1968 /* Default */ 1969 if(!(key in _data)) { return sanitize ? dh.sanitize(_default) : _default; } 1970 1971 /* Default */ 1972 if(_default !== null && typeof _data[key] !== typeof _default) 1973 { 1974 return sanitize ? dh.sanitize(_default) : _default; 1975 } 1976 1977 return sanitize ? dh.sanitize(_data[key]) : _data[key]; 1978 }; 1979 1980 /* For */ 1981 _attributes['for'] = addon.GetName(); if(name !== 'main') { _attributes['for'] += '.' + name; } 1982 1983 /* Target */ 1984 if(_target === null) { _target = `block[for="${_attributes['for']}"]`; } 1985 1986 /* Clone */ 1987 let newCallback = callback.bind({}); 1988 1989 /* Html */ 1990 let html = _item ? newCallback(addon, _item, data, _attributes) : newCallback(addon, data, _attributes); 1991 1992 /* !Wrap */ 1993 if(!_wrap) 1994 { 1995 if(_replace) 1996 { 1997 $(_target).html(html); 1998 } 1999 2000 return html; 2001 } 2002 2003 /* Class */ 2004 _attributes['class'] = ('class' in _attributes) ? (_attributes['class'] + ' b' + dh.uuid(addon.GetName() + name)) : ('b' + dh.uuid(addon.GetName() + name)); 2005 2006 html = `<block ${dh.attributes(_attributes)}>${html}</block>`; 2007 2008 if(_replace) 2009 { 2010 $(_target).replaceWith(html); 2011 } 2012 2013 return html; 2014 }); 2015 }; 2016 2017 RenderTarget(name = 'main') 2018 { 2019 let id = this.GetName(); 2020 2021 if(name !== 'main') 2022 { 2023 id += '.' + name; 2024 } 2025 2026 return $(`block[for="${id}"]`); 2027 }; 2028 2029 /* 2030 * * * * * * * 2031 * * * * * * * 2032 * * * * * * * 2033 * * * * * * * 2034 * * * * * * * 2035 * * * * * * * 2036 * 2037 * Addon FETCH Functionality 2038 * 2039 * * * * * * * 2040 * * * * * * * 2041 * * * * * * * 2042 * * * * * * * 2043 * * * * * * * 2044 * * * * * * * 2045 */ 2046 2047 Fetch(name, data, callback, then = null, fail = null) 2048 { 2049 /* Share */
2050 name = $dh.share() ? 'share.' + name : name; 2051 2052 /* Unique ID */ 2053 let id = Math.random().toString(36).slice(2); 2054 2055 /* Execute */ 2056 let fetch = mdAjax.Fn('get'); 2057 2058 $.each(data, function(key, value) 2059 { 2060 fetch.get(key, value); 2061 }); 2062 2063 fetch.url('/api/' + this.GetName() + '/' + name); 2064 fetch.run(null, (response) => 2065 { 2066 let html = callback ? callback(response.out) : null; 2067 let target = $('#dh-addon-fetch-' + id); 2068 2069 if(!html || !target.length) 2070 { 2071 if(then) { then(response.out); } return; 2072 } 2073 2074 target.html(html); 2075 2076 mdAnimations.Fn('run', 'fadeIn', target, 200, 0, () => 2077 { 2078 target.find('>*').first().unwrap(); 2079 2080 if(then) { then(response.out); } 2081 }); 2082 }, 2083 (response) => 2084 { 2085 let target = $('#dh-addon-fetch-' + id); 2086 2087 if(target.length) 2088 { 2089 target.replaceWith('Failed to fetch. ' + response.message); 2090 } 2091 2092 if(fail) { fail(null); } 2093 }); 2094 2095 return '<div id="dh-addon-fetch-'+id+'" class="dh-addon-fetch"></div>'; 2096 } 2097 2098 FetchMulti(names, data, callback, then = null, fail = null) 2099 { 2100 /* Unique ID */ 2101 let id = Math.random().toString(36).slice(2); 2102 2103 let executed = 0; 2104 let response = []; 2105 let success = true; 2106 2107 names.forEach((name, index) => 2108 { 2109 this.Fetch(name, name in data ? data[name] : {}, null, function(data) 2110 { 2111 executed++; 2112 response[index] = data; 2113 2114 if(executed === names.length) 2115 { 2116 if(!success) 2117 { 2118 if(fail) { fail(null); } return; 2119 } 2120 2121 let html = callback ? callback(...response) : null; 2122 let target = $('#dh-addon-fetch-' + id); 2123 2124 if(!html || !target.length) 2125 { 2126 if(then) { then(...response); } return; 2127 } 2128 2129 target.html(html); 2130 2131 mdAnimations.Fn('run', 'fadeIn', target, 200, 0, () => 2132 { 2133 target.find('>*').first().unwrap(); 2134 2135 if(then) { then(...response); } 2136 }); 2137 } 2138 }, 2139 function() 2140 { 2141 executed++; 2142 success = false; 2143 }); 2144 }); 2145 2146 return '<div id="dh-addon-fetch-'+id+'" class="dh-addon-fetch"></div>'; 2147 } 2148 2149 /* 2150 * * * * * * * 2151 * * * * * * * 2152 * * * * * * * 2153 * * * * * * * 2154 * * * * * * * 2155 * * * * * * * 2156 * 2157 * Addon REQUEST Functionality 2158 * 2159 * * * * * * * 2160 * * * * * * * 2161 * * * * * * * 2162 * * * * * * * 2163 * * * * * * * 2164 * * * * * * * 2165 */ 2166 2167 Request(name, value) 2168 { 2169 let request = {value}; 2170 2171 this.FunctionCreate('request.' + name, function(addon, requestValue) 2172 { 2173 return requestValue; 2174 }); 2175 2176 let data = this.FunctionRun('request.' + name, request); 2177 2178 if(typeof data !== 'object') 2179 { 2180 return value; 2181 } 2182 2183 if(!('value' in data)) 2184 { 2185 return value; 2186 } 2187 2188 if(typeof data.value !== typeof value) 2189 { 2190 return value; 2191 } 2192 2193 return data.value; 2194 }; 2195 2196 OnRequest(name, callback) 2197 { 2198 return this.FunctionCatch('request.' + name, null, callback); 2199 }; 2200 2201 /* 2202 * * * * * * * 2203 * * * * * * * 2204 * * * * * * * 2205 * * * * * * * 2206 * * * * * * * 2207 * * * * * * * 2208 * 2209 * Addon READY Functionality 2210 * 2211 * * * * * * * 2212 * * * * * * * 2213 * * * * * * * 2214 * * * * * * * 2215 * * * * * * * 2216 * * * * * * * 2217 */ 2218 2219 Ready(...data) 2220 { 2221 if(!this.GetReady()) 2222 { 2223 this.data.ready = true; 2224 this.FunctionCreate('ready', function() {}); 2225 this.FunctionRun('ready', ...data); 2226 } 2227 2228 return this; 2229 }; 2230 2231 OnReady(before = null, after = null) 2232 { 2233 if(!this.GetReady()) 2234 { 2235 this.FunctionCatch('ready', before, after); 2236 } 2237 else 2238 { 2239 if(before) { before(this, null); } 2240 if(after) { after(this, null); } 2241 } 2242 }; 2243 2244 /* 2245 * * * * * * * 2246 * * * * * * * 2247 * * * * * * * 2248 * * * * * * * 2249 * * * * * * * 2250 * * * * * * * 2251 * 2252 * Addon OTHER Functionality 2253 * 2254 * * * * * * * 2255 * * * * * * * 2256 * * * * * * * 2257 * * * * * * * 2258 * * * * * * * 2259 * * * * * * * 2260 */ 2261 2262 Reset(...types) 2263 {
2264 types.forEach((type) => 2265 { 2266 if(type === 'ready') 2267 { 2268 this.data.ready = false; 2269 } 2270 }); 2271 }; 2272};class AddonItem 2273{ 2274 data; 2275 addon; 2276 2277 constructor(addon, data) 2278 { 2279 this.addon = addon; 2280 this.data = data; 2281 } 2282 2283 /* 2284 * * * * * * * 2285 * * * * * * * 2286 * * * * * * * 2287 * * * * * * * 2288 * * * * * * * 2289 * * * * * * * 2290 * 2291 * Item GET Functionality 2292 * 2293 * * * * * * * 2294 * * * * * * * 2295 * * * * * * * 2296 * * * * * * * 2297 * * * * * * * 2298 * * * * * * * 2299 */ 2300 2301 Get(key, sanitize = false) 2302 { 2303 /* Check for Field */ 2304 if(!(key in this.addon.data.items.fields) && key !== 'id') 2305 { 2306 return null; 2307 } 2308 2309 let value = null; 2310 2311 /* Check for Field Callback */ 2312 if(key !== 'id' && this.addon.data.items.fields[key].get) 2313 { 2314 value = this.addon.data.items.fields[key].get(this.data[key], this); 2315 } 2316 else 2317 { 2318 value = this.data[key]; 2319 } 2320 2321 if(typeof value === 'string' && sanitize) 2322 { 2323 value = dh.sanitize(value); 2324 } 2325 2326 return value; 2327 } 2328 2329 /* 2330 * * * * * * * 2331 * * * * * * * 2332 * * * * * * * 2333 * * * * * * * 2334 * * * * * * * 2335 * * * * * * * 2336 * 2337 * Item SET Functionality 2338 * 2339 * * * * * * * 2340 * * * * * * * 2341 * * * * * * * 2342 * * * * * * * 2343 * * * * * * * 2344 * * * * * * * 2345 */ 2346 2347 Set(key, value, callback = true) 2348 { 2349 /* Check for Field */ 2350 if(!(key in this.addon.data.items.fields)) 2351 { 2352 return null; 2353 } 2354 2355 let oldValue = this.data[key]; 2356 2357 /* Check for Field Callback */ 2358 if(key !== 'id' && this.addon.data.items.fields[key].set) 2359 { 2360 this.data[key] = this.addon.data.items.fields[key].set(value, this.data, this); 2361 } 2362 else 2363 { 2364 this.data[key] = value; 2365 } 2366 2367 /* Callbacks */ 2368 if(callback) 2369 { 2370 this.addon.data.items.callbacks.set.forEach((callback) => 2371 { 2372 callback(this.addon, key, value, this, oldValue); 2373 }); 2374 } 2375 }; 2376 2377 /* 2378 * * * * * * * 2379 * * * * * * * 2380 * * * * * * * 2381 * * * * * * * 2382 * * * * * * * 2383 * * * * * * * 2384 * 2385 * Item TARGET Functionality 2386 * 2387 * * * * * * * 2388 * * * * * * * 2389 * * * * * * * 2390 * * * * * * * 2391 * * * * * * * 2392 * * * * * * * 2393 */ 2394 2395 TargetItem(field = null) 2396 { 2397 if(field !== null) 2398 { 2399 return $(`item[field="${field}"][for="${this.addon.GetName()}"][data-id="${this.Get('id', false)}"]`); 2400 } 2401 2402 return $(`item[for="${this.addon.GetName()}"][data-id="${this.Get('id', false)}"]`); 2403 } 2404 2405 TargetItems(field = null) 2406 { 2407 if(field !== null) 2408 { 2409 return $(`items[field="${field}"][for="${this.addon.GetName()}"]`); 2410 } 2411 2412 return $(`items[for="${this.addon.GetName()}"]`); 2413 } 2414 2415 TargetValid(target) 2416 { 2417 let valid = true; 2418 let item = this; 2419 2420 $.each(target.attributes, function() 2421 { 2422 if(!valid) { return true; } 2423 if(!this.name.startsWith('data-')) { return true; } 2424 if(!(this.name.slice(5) in item.data)) { return true; } 2425 2426 let value = item.Get(this.name.slice(5)); 2427 2428 if(value === null) 2429 { 2430 valid = false; 2431 } 2432 else 2433 { 2434 if(value.toString() !== this.value) 2435 { 2436 valid = false; 2437 } 2438 } 2439 }); 2440 2441 return valid; 2442 } 2443 2444 /* 2445 * * * * * * * 2446 * * * * * * * 2447 * * * * * * * 2448 * * * * * * * 2449 * * * * * * * 2450 * * * * * * * 2451 * 2452 * Item CLONE Functionality 2453 * 2454 * * * * * * * 2455 * * * * * * * 2456 * * * * * * * 2457 * * * * * * * 2458 * * * * * * * 2459 * * * * * * * 2460 */ 2461 2462 Clone(data) 2463 { 2464 $.each(this.data, function(key, value) 2465 { 2466 if(!(key in data)) 2467 { 2468 data[key] = value; 2469 } 2470 }); 2471 2472 return this.addon.ItemAdd($.extend({}, data)); 2473 } 2474 2475 /* 2476 * * * * * * * 2477 * * * * * * * 2478 * * * * * * * 2479 * * * * * * * 2480 * * * * * * * 2481 * * * * * * * 2482 * 2483 * Addon FIELDS Functionality 2484 * 2485 * * * * * * * 2486 * * * * * * * 2487 * * * * * * * 2488 * * * * * * * 2489 * * * * * * * 2490 * * * * * * * 2491 */ 2492 2493 FieldAdd(key, item) 2494 { 2495 /* Item */ 2496 if(typeof item !== 'object') { return null; } 2497 2498 /* Item */ 2499 if(!('Get' in item)) { return null; } 2500 2501 /* Check for Field */ 2502 if(!(key in this.addon.data.items.fields)) { return null; } 2503 2504 /* Is Object */ 2505 if(!this.data[key] || typeof this.data[key] !== 'object') { this.data[key] = {}; } 2506 2507 /* Add */ 2508 this.data[key][item.Get('id')] = item; 2509 }; 2510 2511 FieldRemove(key, item) 2512 { 2513 /* Item */ 2514 if(typeof item !== 'object') { return null; } 2515 2516 /* Item */ 2517 if(!('Get' in item)) { return null; } 2518 2519 /* Check for Field */ 2520 if(!(key in this.addon.data.items.fields)) { return null; } 2521 2522 /* Is Object */ 2523 if(!this.data[key] || typeof this.data[key] !== 'object') { this.data[key] = {}; } 2524 2525 /* Remove */ 2526 delete this.data[key][item.Get('id')]; 2527 }; 2528 2529 /* 2530 * * * * * * * 2531 * * * * * * * 2532 * * * * * * * 2533 * * * * * * * 2534 * * * * * * * 2535 * * * * * * * 2536 * 2537 * Addon RENDER Functionality 2538 * 2539 * * * * * * * 2540 * * * * * * * 2541 * * * * * * * 2542 * * * * * * * 2543 * * * * * * * 2544 * * * * * * * 2545 */ 2546 2547 Render(name, data = {}, attributes = {}, wrap = true) 2548 { 2549 /* Html */
2550 let html = this.addon.Render('item.' + name, data, attributes, false, false, null, this); 2551 2552 /* For */ 2553 attributes['for'] = this.addon.GetName(); 2554 2555 /* Data Id */ 2556 attributes['data-id'] = this.Get('id'); 2557 2558 /* Field */ 2559 attributes['field'] = name; 2560 2561 /* UUID */ 2562 attributes['class'] = 'class' in attributes ? (attributes['class'] + (' i' + dh.uuid(this.addon.GetName() + name))) : ('i' + dh.uuid(this.addon.GetName() + name)) 2563 2564 /* !Wrap */ 2565 if(!wrap) 2566 { 2567 return html; 2568 } 2569 2570 return`<item ${dh.attributes(attributes)}>${html}</item>`; 2571 }; 2572 2573 /* 2574 * * * * * * * 2575 * * * * * * * 2576 * * * * * * * 2577 * * * * * * * 2578 * * * * * * * 2579 * * * * * * * 2580 * 2581 * Addon PUSH Functionality 2582 * 2583 * * * * * * * 2584 * * * * * * * 2585 * * * * * * * 2586 * * * * * * * 2587 * * * * * * * 2588 * * * * * * * 2589 */ 2590 2591 Push(name, data = {}, attributes = {}, wrap = true, target = null) 2592 { 2593 /* Target */ 2594 if(target === null) { target = this.TargetItems(name); } else { target = $(target); } 2595 2596 /* Push */ 2597 $.each(target, (index, _target) => 2598 { 2599 if(this.TargetValid(_target)) 2600 { 2601 let html = this.Render(name, data, attributes, wrap); 2602 2603 if($(_target).attr('type') === 'prepend') 2604 { 2605 $(_target).prepend(html) 2606 } 2607 else if($(_target).attr('type') === 'before') 2608 { 2609 $(_target).before(html) 2610 } 2611 else if($(_target).attr('type') === 'after') 2612 { 2613 $(_target).after(html) 2614 } 2615 else 2616 { 2617 $(_target).append(html) 2618 } 2619 2620 /* Animation */ 2621 if('animation' in attributes) 2622 { 2623 let target = this.TargetItem(name); 2624 2625 target.css('opacity', 0); 2626 mdAnimations.Fn('run', 'fadeIn', target); 2627 } 2628 } 2629 }); 2630 }; 2631 2632 /* 2633 * * * * * * * 2634 * * * * * * * 2635 * * * * * * * 2636 * * * * * * * 2637 * * * * * * * 2638 * * * * * * * 2639 * 2640 * Addon REPLACE Functionality 2641 * 2642 * * * * * * * 2643 * * * * * * * 2644 * * * * * * * 2645 * * * * * * * 2646 * * * * * * * 2647 * * * * * * * 2648 */ 2649 2650 Replace(name, data = {}, attributes = {}, wrap = true, target = null) 2651 { 2652 let html = this.Render(name, data, attributes, wrap); 2653 2654 /* Data Id */ 2655 attributes['data-id'] = this.Get('id'); 2656 2657 /* For */ 2658 attributes['field'] = name; 2659 2660 /* Target */ 2661 if(target === null) { target = this.TargetItem(name); } else { target = $(target); } 2662 2663 /* !Wrap */ 2664 if(!wrap) 2665 { 2666 $(target).html(html); 2667 } 2668 else 2669 { 2670 $(target).replaceWith(html); 2671 } 2672 }; 2673 2674 /* 2675 * * * * * * * 2676 * * * * * * * 2677 * * * * * * * 2678 * * * * * * * 2679 * * * * * * * 2680 * * * * * * * 2681 * 2682 * Addon REMOVE Functionality 2683 * 2684 * * * * * * * 2685 * * * * * * * 2686 * * * * * * * 2687 * * * * * * * 2688 * * * * * * * 2689 * * * * * * * 2690 */ 2691 2692 Remove(attributes = {}) 2693 { 2694 /* Callbacks */ 2695 this.addon.data.items.callbacks.remove.forEach((callback) => 2696 { 2697 callback(this.addon, this); 2698 }); 2699 2700 this.UnPush(null, attributes); 2701 delete this.addon.data.items.data[this.Get('id', false)]; 2702 }; 2703 2704 /* 2705 * * * * * * * 2706 * * * * * * * 2707 * * * * * * * 2708 * * * * * * * 2709 * * * * * * * 2710 * * * * * * * 2711 * 2712 * Addon UN PUSH Functionality 2713 * 2714 * * * * * * * 2715 * * * * * * * 2716 * * * * * * * 2717 * * * * * * * 2718 * * * * * * * 2719 * * * * * * * 2720 */ 2721 2722 UnPush(field = null, attributes = {}) 2723 { 2724 let target = this.TargetItem(field); 2725 2726 /* Animation */ 2727 if('animation' in attributes) 2728 { 2729 mdAnimations.Fn('run', 'fadeOut', target, 200, 0, function () 2730 { 2731 target.remove(); 2732 }); 2733 } 2734 else 2735 { 2736 target.remove(); 2737 } 2738 }; 2739 2740 /* 2741 * * * * * * * 2742 * * * * * * * 2743 * * * * * * * 2744 * * * * * * * 2745 * * * * * * * 2746 * * * * * * * 2747 * 2748 * Addon FUNCTIONS Functionality 2749 * 2750 * * * * * * * 2751 * * * * * * * 2752 * * * * * * * 2753 * * * * * * * 2754 * * * * * * * 2755 * * * * * * * 2756 */ 2757 2758 Fn(name, ...data) 2759 {
2760 return this.addon.FunctionRun('item.' + name, this, ...data); 2761 }; 2762};const common = Addons.create('common').SetRoute('');const elements = Addons.create('elements').SetRoute('');const mdFunctions = Addons.create('md.functions').SetRoute('');const mdActions = Addons.create('md.actions').SetRoute('');const mdEvents = Addons.create('md.events').SetRoute('');const mdOperators = Addons.create('md.operators').SetRoute('');const cms = Addons.create('cms').SetRoute('');const html = Addons.create('html').SetRoute('');const mdBugs = Addons.create('md.bugs').SetRoute('');const mdAnimations = Addons.create('md.animations').SetRoute('');const mdVars = Addons.create('md.vars').SetRoute('');const mdLibraries = Addons.create('md.libraries').SetRoute('');const mdSources = Addons.create('md.sources').SetRoute('');const mdBlocks = Addons.create('md.blocks').SetRoute('');const mdAjax = Addons.create('md.ajax').SetRoute('');const mdModal = Addons.create('md.modal').SetRoute('');const mdWorkflows = Addons.create('md.workflows').SetRoute('');const ai_dashboard = Addons.create('ai_dashboard').SetRoute('');const search = Addons.create('search').SetRoute('');const plugins = Addons.create('plugins').SetRoute('');const account = Addons.create('account').SetRoute('');const project = Addons.create('project').SetRoute('');const sv_prerender = Addons.create('sv_prerender').SetRoute('');const sv_storage = Addons.create('sv_storage').SetRoute('');const sv_members = Addons.create('sv_members').SetRoute('');const sv_dashboard = Addons.create('sv_dashboard').SetRoute('');const sv_billing = Addons.create('sv_billing').SetRoute('');const admin = Addons.create('admin').SetRoute('/admin');const css = Addons.create('builder.css').SetRoute('');const blocks = Addons.create('builder.blocks').SetRoute('');const predefines = Addons.create('builder.predefines').SetRoute('');const outline = Addons.create('builder.outline').SetRoute('');const edit = Addons.create('builder.edit').SetRoute('');const preview = Addons.create('builder.preview').SetRoute('');const navbar = Addons.create('builder.navbar').SetRoute('');const area = Addons.create('builder.area').SetRoute('');const tabs = Addons.create('builder.tabs').SetRoute('');const designer = Addons.create('builder.designer').SetRoute('');const integrations = Addons.create('builder.integrations').SetRoute('');const content = Addons.create('builder.content').SetRoute('');const settings = content;const advanced = Addons.create('builder.advanced').SetRoute('');const interactions = Addons.create('builder.interactions').SetRoute('');const transform = Addons.create('builder.transform').SetRoute('');const gsapcode = Addons.create('builder.gsapcode').SetRoute('');const ai_new = Addons.create('builder.ai_new').SetRoute('');const responsive = Addons.create('builder.responsive').SetRoute('');const files = Addons.create('builder.files').SetRoute('');const colors = Addons.create('builder.colors').SetRoute('');const classes = Addons.create('builder.classes').SetRoute('');const components = Addons.create('builder.components').SetRoute('');const pages = Addons.create('builder.pages').SetRoute('');const activity = Addons.create('builder.activity').SetRoute('');const explorer = Addons.create('builder.explorer').SetRoute('');const htmltojson = Addons.create('builder.htmltojson').SetRoute('');const quickActions = Addons.create('builder.quickActions').SetRoute('');const preferences = Addons.create('builder.preferences').SetRoute('');const shortcuts = Addons.create('builder.shortcuts').SetRoute('');const seomanager = Addons.create('builder.seomanager').SetRoute('');const builder = Addons.create('builder').SetRoute('/');const collections = Addons.create('collections').SetRoute('/collections');const creator = Addons.create('creator').SetRoute('/creator');const storage = Addons.create('storage').SetRoute('/storage');const backups = Addons.create('admin.backups').SetRoute('');const webhooks = Addons.create('admin.webhooks').SetRoute('');const fonts = Addons.create('admin.fonts').SetRoute('');const seo = Addons.create('admin.seo').SetRoute('');const forms_webhook = Addons.create('admin.forms_webhook').SetRoute('');const memberships_webhook = Addons.create('admin.memberships_webhook').SetRoute('');const rss = Addons.create('admin.rss').SetRoute('');const figma = Addons.create('admin.figma').SetRoute(''); 2763common.FunctionCreate('confirm', (addon, title, description, settings, c
2763allback = null) => 2764{ 2765 let modal = { 2766 id: 'dh-common-confirm', 2767 overlay: {opacity: 0.5, closeable: false}, 2768 align: {x: 'center', y: 'center'}, 2769 element: $('#dh-modal'), 2770 html: () => 2771 { 2772 return ` 2773 <main style="background: unset; height: unset; min-height: unset; padding: unset; margin: unset"> 2774 ${common.Render('confirm', {title, description, settings})} 2775 </main> 2776 `; 2777 } 2778 }; 2779 2780 if(callback) 2781 { 2782 callback(modal); 2783 } 2784 2785 mdModal.ItemAdd(modal); 2786});common.RenderCreate('confirm', (addon, data) => 2787{ 2788 // let title = data('title', 'Prompt'); 2789 // let prompt = {}; 2790 // let fields = ''; 2791 2792 // $.each(keys, function(key, setting) 2793 // { 2794 // prompt[key] = 'value' in setting ? setting.value : null; 2795 2796 // fields += html.Render('setting', { 2797 // setting, 2798 // value: 'value' in setting ? setting.value : null, 2799 // onChange: (value) => 2800 // { 2801 // prompt[key] = value; 2802 // } 2803 // }); 2804 // }); 2805 2806 return ` 2807 <div class="title"> 2808 <h3>${data('title', 'Confirm')}</h4> 2809 <p>${data('description', '')}</p> 2810 </div> 2811 2812 <div class="fields"> 2813 ${common.Render('form', { 2814 settings: data('settings'), 2815 onChange: (values) => 2816 { 2817 console.log(values); 2818 } 2819 })} 2820 </div> 2821 2822 <div class="actions"> 2823 2824 </div> 2825 `; 2826});common.FunctionCreate('render', (addon) => 2827{ 2828 return 'ds'; 2829});common.RenderCreate('form', (addon, data) => 2830{ 2831 let id = dh.increaser('common.form'); 2832 2833 let settings = data('settings', {}); 2834 let values = data('values', {}); 2835 2836 this.init = () => 2837 { 2838 setTimeout(() => 2839 { 2840 let form = $('form[id="common-form-'+id+'"]'); 2841 2842 form.on('submit', function(event) 2843 { 2844 event.preventDefault(); 2845 }); 2846 2847 this.start(form); 2848 }); 2849 2850 return ` 2851 <form id="common-form-${id}"></form> 2852 `; 2853 }; 2854 2855 this.start = (target) => 2856 { 2857 let content = ''; 2858 2859 $.each(settings, (key, setting) => 2860 { 2861 if(!setting || typeof setting !== 'object') 2862 { 2863 return true; 2864 } 2865 2866 /* Type */ 2867 setting.type = 'type' in setting && typeof setting.type === 'string' ? setting.type.toLowerCase() : 'input'; 2868 2869 /* Wrapper */ 2870 let wrapper = this.wrapper(target, setting.type); 2871 2872 /* Label */ 2873 this.label(wrapper, key, setting); 2874 2875 /* Render */ 2876 common.Render('form.' + setting.type, { 2877 key, 2878 setting, 2879 target: wrapper, 2880 onChange: (value) => 2881 { 2882 values[key] = value === '' ? null : value; 2883 2884 console.log(value); 2885 2886 if(data('onChange')) 2887 { 2888 data('onChange')(values); 2889 } 2890 } 2891 }); 2892 }); 2893 2894 return content; 2895 }; 2896 2897 this.wrapper = (target, type) => 2898 { 2899 let block = ''; 2900 2901 switch(type) 2902 { 2903 case 'input': 2904 block = 'b93951b5'; 2905 break; 2906 case 'textarea': 2907 block = 'b42db4519'; 2908 break; 2909 case 'button': 2910 block = 'b297e5f2f'; 2911 break; 2912 } 2913 2914 let wrapper = $(`<div class="wrapper ${block}" for="${type}"></div>`); 2915 2916 target.append(wrapper); 2917 2918 return wrapper; 2919 }; 2920 2921 this.label = (wrapper, key, setting) => 2922 { 2923 let label = { 2924 title: typeof setting.label === 'string' ? setting.label : key, 2925 description: null 2926 }; 2927 2928 if(setting.label === null) 2929 { 2930 return; 2931 } 2932 2933 if(!('label' in setting) || typeof setting.label === 'string') 2934 { 2935 return wrapper.append(`<label><p>${label.title}</p></label>`); 2936 } 2937 2938 if(!setting.label || typeof setting.label !== 'object') 2939 { 2940 return; 2941 } 2942 2943 if(setting.label.position === 'left') 2944 { 2945 wrapper.addClass('label-left'); 2946 } 2947 2948 label.title = typeof setting.label.title === 'string' ? setting.label.title : null; 2949 label.description = typeof setting.label.description === 'string' ? setting.label.description : null; 2950 2951 return wrapper.append(` 2952 <label> 2953 ${label.title ? `<h3>${label.title}</h3>` : ''} 2954 ${label.description ? `<p>${label.description}</p>` : ''} 2955 </label> 2956 `); 2957 }; 2958 2959 return this.init(); 2960});common.RenderCreate('form.button', (addon, data) => 2961{ 2962 let id = dh.increaser('common.form.button'); 2963 2964 let setting = data('setting', {}); 2965 2966 let attributes = {};
2967 let callbacks = []; 2968 2969 this.init = () => 2970 { 2971 if(!data('target')) 2972 { 2973 setTimeout(() => 2974 { 2975 this.create($('div[id="common-form-button-'+id+'"]'), true); 2976 }); 2977 2978 return ` 2979 <div id="common-form-button-${id}"></div> 2980 `; 2981 } 2982 2983 this.create(data('target')) 2984 }; 2985 2986 this.create = (target, replace = false) => 2987 { 2988 let element = $(` 2989 <div> 2990 <button ${dh.attributes(attributes)}>${this.value()}</button> 2991 </div> 2992 `); 2993 2994 replace ? target.replaceWith(element) : target.append(element); 2995 2996 this.prefix(element); 2997 this.suffix(element); 2998 2999 if(data('onChange') || data('onClick')) 3000 { 3001 callbacks.push((button) => 3002 { 3003 if(data('onChange')) { data('onChange')(null, button); } 3004 if(data('onClick')) { data('onClick')(null, button); } 3005 }); 3006 } 3007 3008 this.onClick(element); 3009 }; 3010 3011 this.value = () => 3012 { 3013 let value = data('value'); 3014 3015 if(value && (typeof value === 'string' || typeof value === 'number')) 3016 { 3017 return dh.sanitize(value); 3018 } 3019 3020 return ''; 3021 }; 3022 3023 this.prefix = (element) => 3024 { 3025 if(!('prefix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 3026 { 3027 return; 3028 } 3029 3030 element.find('button').prepend(`<span class="prefix">${setting.prefix}</span>`); 3031 }; 3032 3033 this.suffix = (element) => 3034 { 3035 if(!('suffix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 3036 { 3037 return; 3038 } 3039 3040 element.find('button').append(`<span class="suffix">${setting.suffix}</span>`); 3041 }; 3042 3043 this.onClick = (element) => 3044 { 3045 if(!callbacks.length) 3046 { 3047 return; 3048 } 3049 3050 element.find('button').on('click', function() 3051 { 3052 callbacks.forEach((callback) => 3053 { 3054 callback($(this)); 3055 }); 3056 }); 3057 }; 3058 3059 return this.init(); 3060});common.RenderCreate('form.input', (addon, data) => 3061{ 3062 let id = dh.increaser('common.form.input'); 3063 3064 let setting = data('setting', {}); 3065 3066 let attributes = {}; 3067 let callbacks = { 3068 input: [], 3069 change: [] 3070 }; 3071 3072 this.init = () => 3073 { 3074 if(!data('target')) 3075 { 3076 setTimeout(() => 3077 { 3078 this.create($('div[id="common-form-input-'+id+'"]'), true); 3079 }); 3080 3081 return ` 3082 <div id="common-form-input-${id}"></div> 3083 `; 3084 } 3085 3086 this.create(data('target')) 3087 }; 3088 3089 this.create = (target, replace = false) => 3090 { 3091 this.placeholder(); 3092 this.value(); 3093 3094 let element = $(` 3095 <div> 3096 <input ${dh.attributes(attributes)}> 3097 </div> 3098 `); 3099 3100 replace ? target.replaceWith(element) : target.append(element); 3101 3102 this.prefix(element); 3103 this.suffix(element); 3104 this.characters(element); 3105 this.regex(element); 3106 3107 if(data('onInput')) 3108 { 3109 callbacks.input.push((input) => 3110 { 3111 data('onInput')($(input).val(), input); 3112 }); 3113 } 3114 3115 if(data('onChange')) 3116 { 3117 callbacks.change.push((input) => 3118 { 3119 data('onChange')($(input).val(), input); 3120 }); 3121 } 3122 3123 this.onInput(element); 3124 this.onChange(element); 3125 }; 3126 3127 this.value = () => 3128 { 3129 let value = data('value'); 3130 3131 if(value && (typeof value === 'string' || typeof value === 'number')) 3132 { 3133 attributes.value = value; 3134 } 3135 }; 3136 3137 this.placeholder = () => 3138 { 3139 if(!('placeholder'in setting) || typeof setting.placeholder !== 'string') 3140 { 3141 return; 3142 } 3143 3144 attributes.placeholder = setting.placeholder; 3145 }; 3146 3147 this.prefix = (element) => 3148 { 3149 if(!('prefix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 3150 { 3151 return; 3152 } 3153
3154 element.prepend(`<span class="prefix">${setting.prefix}</span>`); 3155 element.find('input').css('padding-left', element.find('.prefix').outerWidth() + 10); 3156 }; 3157 3158 this.suffix = (element) => 3159 { 3160 if(!('suffix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 3161 { 3162 return; 3163 } 3164 3165 element.append(`<span class="suffix">${setting.suffix}</span>`); 3166 element.find('input').css('padding-right', element.find('.suffix').outerWidth() + 10); 3167 }; 3168 3169 this.characters = (element) => 3170 { 3171 if(!('characters' in setting) || typeof setting.characters !== 'string') 3172 { 3173 return; 3174 } 3175 3176 let characters = setting.characters.split('/'); 3177 3178 if(characters.length !== 2) 3179 { 3180 return; 3181 } 3182 3183 if(typeof parseInt(characters[0]) !== 'number' || typeof parseInt(characters[1]) !== 'number') 3184 { 3185 return; 3186 } 3187 3188 element.append(` 3189 <div class="characters"> 3190 ${element.find('input').val().length} / ${characters[1]} 3191 </div> 3192 `); 3193 3194 callbacks.input.push((input) => 3195 { 3196 let length = input.val().length; 3197 3198 if(length > characters[1]) 3199 { 3200 input.parent().find('.characters').addClass('error'); 3201 } 3202 else if(length < characters[0]) 3203 { 3204 input.parent().find('.characters').addClass('error'); 3205 } 3206 else 3207 { 3208 input.parent().find('.characters').removeClass('error'); 3209 } 3210 3211 input.parent().find('.characters').html(` 3212 ${length} / ${characters[1]} 3213 `); 3214 }); 3215 }; 3216 3217 this.regex = (element) => 3218 { 3219 if(!('regex' in setting) && typeof setting.regex !== 'string') 3220 { 3221 return; 3222 } 3223 3224 let regex = new RegExp('^' + setting.regex + '$'); 3225 3226 callbacks.input.push((input) => 3227 { 3228 let value = input.val(); 3229 3230 if(value && !regex.test(value)) 3231 { 3232 input.parent().addClass('error'); 3233 } 3234 else 3235 { 3236 input.parent().removeClass('error'); 3237 } 3238 }); 3239 }; 3240 3241 this.onInput = (element) => 3242 { 3243 if(!callbacks.input.length) 3244 { 3245 return; 3246 } 3247 3248 element.find('input').on('input', function() 3249 { 3250 callbacks.input.forEach((callback) => 3251 { 3252 callback($(this)); 3253 }); 3254 }); 3255 }; 3256 3257 this.onChange = (element) => 3258 { 3259 if(!callbacks.change.length) 3260 { 3261 return; 3262 } 3263 3264 element.find('input').on('change', function() 3265 { 3266 callbacks.change.forEach((callback) => 3267 { 3268 callback($(this)); 3269 }); 3270 }); 3271 }; 3272 3273 return this.init(); 3274});common.RenderCreate('form.repeater', (addon, data) => 3275{ 3276 let id = dh.increaser('common.form.repeater'); 3277 3278 let setting = data('setting', {}); 3279 3280 console.log(setting); 3281 3282 let attributes = {}; 3283 let callbacks = { 3284 input: [], 3285 change: [] 3286 }; 3287 3288 this.init = () => 3289 { 3290 if(!data('target')) 3291 { 3292 setTimeout(() => 3293 { 3294 this.create($('div[id="common-form-repeater-'+id+'"]'), true); 3295 }); 3296 3297 return ` 3298 <div id="common-form-repeater-${id}"></div> 3299 `; 3300 } 3301 3302 this.create(data('target')) 3303 }; 3304 3305 this.create = (target, replace = false) => 3306 { 3307 // this.placeholder(); 3308 // this.value(); 3309 3310 let element = $(` 3311 <div> 3312 <div class="list"></div> 3313 </div> 3314 `); 3315 3316 replace ? target.replaceWith(element) : target.append(element); 3317 3318 this.add(element); 3319 // this.suffix(element); 3320 // this.characters(element); 3321 // this.regex(element); 3322 3323 // if(data('onInput')) 3324 // { 3325 // callbacks.input.push((input) => 3326 // { 3327 // data('onInput')($(input).val(), input); 3328 // }); 3329 // } 3330 3331 // if(data('onChange')) 3332 // { 3333 // callbacks.change.push((input) => 3334 // { 3335 // data('onChange')($(input).val(), input); 3336 // }); 3337 // } 3338 3339 // this.onInput(element); 3340 // this.onChange(element); 3341 }; 3342 3343 this.value = () => 3344 { 3345 let value = data('value'); 3346 3347 if(value && (typeof value === 'string' || typeof value === 'number')) 3348 { 3349 attributes.value = value; 3350 } 3351 }; 3352 3353 this.add = (element) => 3354 { 3355 element.append(common.Render('form.button', { 3356 onClick: () => { 3357 console.log('dsa'); 3358 }, 3359 value: 'Add' 3360 })); 3361 }; 3362 3363 this.prefix = (element) => 3364 { 3365 if(!('prefix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 3366 { 3367 return; 3368 } 3369
3370 element.prepend(`<span class="prefix">${setting.prefix}</span>`); 3371 element.find('input').css('padding-left', element.find('.prefix').outerWidth() + 10); 3372 }; 3373 3374 this.suffix = (element) => 3375 { 3376 if(!('suffix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 3377 { 3378 return; 3379 } 3380 3381 element.append(`<span class="suffix">${setting.suffix}</span>`); 3382 element.find('input').css('padding-right', element.find('.suffix').outerWidth() + 10); 3383 }; 3384 3385 this.characters = (element) => 3386 { 3387 if(!('characters' in setting) || typeof setting.characters !== 'string') 3388 { 3389 return; 3390 } 3391 3392 let characters = setting.characters.split('/'); 3393 3394 if(characters.length !== 2) 3395 { 3396 return; 3397 } 3398 3399 if(typeof parseInt(characters[0]) !== 'number' || typeof parseInt(characters[1]) !== 'number') 3400 { 3401 return; 3402 } 3403 3404 element.append(` 3405 <div class="characters"> 3406 ${element.find('input').val().length} / ${characters[1]} 3407 </div> 3408 `); 3409 3410 callbacks.input.push((input) => 3411 { 3412 let length = input.val().length; 3413 3414 if(length > characters[1]) 3415 { 3416 input.parent().find('.characters').addClass('error'); 3417 } 3418 else if(length < characters[0]) 3419 { 3420 input.parent().find('.characters').addClass('error'); 3421 } 3422 else 3423 { 3424 input.parent().find('.characters').removeClass('error'); 3425 } 3426 3427 input.parent().find('.characters').html(` 3428 ${length} / ${characters[1]} 3429 `); 3430 }); 3431 }; 3432 3433 this.regex = (element) => 3434 { 3435 if(!('regex' in setting) && typeof setting.regex !== 'string') 3436 { 3437 return; 3438 } 3439 3440 let regex = new RegExp('^' + setting.regex + '$'); 3441 3442 callbacks.input.push((input) => 3443 { 3444 let value = input.val(); 3445 3446 if(value && !regex.test(value)) 3447 { 3448 input.parent().addClass('error'); 3449 } 3450 else 3451 { 3452 input.parent().removeClass('error'); 3453 } 3454 }); 3455 }; 3456 3457 this.onInput = (element) => 3458 { 3459 if(!callbacks.input.length) 3460 { 3461 return; 3462 } 3463 3464 element.find('input').on('input', function() 3465 { 3466 callbacks.input.forEach((callback) => 3467 { 3468 callback($(this)); 3469 }); 3470 }); 3471 }; 3472 3473 this.onChange = (element) => 3474 { 3475 if(!callbacks.change.length) 3476 { 3477 return; 3478 } 3479 3480 element.find('input').on('change', function() 3481 { 3482 callbacks.change.forEach((callback) => 3483 { 3484 callback($(this)); 3485 }); 3486 }); 3487 }; 3488 3489 return this.init(); 3490});common.RenderCreate('form.textarea', (addon, data) => 3491{ 3492 let id = dh.increaser('common.form.textarea'); 3493 3494 let setting = data('setting', {}); 3495 3496 let attributes = {}; 3497 let callbacks = { 3498 input: [], 3499 change: [] 3500 }; 3501 3502 this.init = () => 3503 { 3504 if(!data('target')) 3505 { 3506 setTimeout(() => 3507 { 3508 this.create($('div[id="common-form-textarea-'+id+'"]'), true); 3509 }); 3510 3511 return ` 3512 <div id="common-form-textarea-${id}"></div> 3513 `; 3514 } 3515 3516 this.create(data('target')) 3517 }; 3518 3519 this.create = (target, replace = false) => 3520 { 3521 this.placeholder(); 3522 this.value(); 3523 3524 let element = $(` 3525 <div> 3526 <textarea common-scrollbar ${dh.attributes(attributes)}>${this.value()}</textarea> 3527 </div> 3528 `); 3529 3530 replace ? target.replaceWith(element) : target.append(element); 3531 3532 this.prefix(element); 3533 this.suffix(element); 3534 this.characters(element); 3535 this.regex(element); 3536 3537 if(data('onInput')) 3538 { 3539 callbacks.input.push((textarea) => 3540 { 3541 data('onInput')($(textarea).val(), textarea); 3542 }); 3543 } 3544 3545 if(data('onChange')) 3546 { 3547 callbacks.change.push((textarea) => 3548 { 3549 data('onChange')($(textarea).val(), textarea); 3550 }); 3551 } 3552 3553 this.onInput(element); 3554 this.onChange(element); 3555 }; 3556 3557 this.value = () => 3558 { 3559 let value = data('value'); 3560 3561 if(value && (typeof value === 'string' || typeof value === 'number')) 3562 { 3563 return dh.sanitize(value); 3564 } 3565 3566 return ''; 3567 }; 3568 3569 this.placeholder = () => 3570 { 3571 if(!('placeholder'in setting) || typeof setting.placeholder !== 'string') 3572 { 3573 return; 3574 } 3575 3576 attributes.placeholder = setting.placeholder; 3577 }; 3578 3579 this.prefix = (element) => 3580 { 3581 if(!('prefix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 3582 { 3583 return; 3584 } 3585
3586 element.prepend(`<span class="prefix">${setting.prefix}</span>`); 3587 element.find('textarea').css('padding-left', element.find('.prefix').outerWidth() + 10); 3588 }; 3589 3590 this.suffix = (element) => 3591 { 3592 if(!('suffix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 3593 { 3594 return; 3595 } 3596 3597 element.append(`<span class="suffix">${setting.suffix}</span>`); 3598 element.find('textarea').css('padding-right', element.find('.suffix').outerWidth() + 10); 3599 }; 3600 3601 this.characters = (element) => 3602 { 3603 if(!('characters' in setting) || typeof setting.characters !== 'string') 3604 { 3605 return; 3606 } 3607 3608 let characters = setting.characters.split('/'); 3609 3610 if(characters.length !== 2) 3611 { 3612 return; 3613 } 3614 3615 if(typeof parseInt(characters[0]) !== 'number' || typeof parseInt(characters[1]) !== 'number') 3616 { 3617 return; 3618 } 3619 3620 element.append(` 3621 <div class="characters"> 3622 ${element.find('textarea').val().length} / ${characters[1]} 3623 </div> 3624 `); 3625 3626 callbacks.input.push((textarea) => 3627 { 3628 let length = textarea.val().length; 3629 3630 if(length > characters[1]) 3631 { 3632 textarea.parent().find('.characters').addClass('error'); 3633 } 3634 else if(length < characters[0]) 3635 { 3636 textarea.parent().find('.characters').addClass('error'); 3637 } 3638 else 3639 { 3640 textarea.parent().find('.characters').removeClass('error'); 3641 } 3642 3643 textarea.parent().find('.characters').html(` 3644 ${length} / ${characters[1]} 3645 `); 3646 }); 3647 }; 3648 3649 this.regex = (element) => 3650 { 3651 if(!('regex' in setting) && typeof setting.regex !== 'string') 3652 { 3653 return; 3654 } 3655 3656 let regex = new RegExp('^' + setting.regex + '$'); 3657 3658 callbacks.input.push((textarea) => 3659 { 3660 let value = textarea.val(); 3661 3662 if(value && !regex.test(value)) 3663 { 3664 textarea.parent().addClass('error'); 3665 } 3666 else 3667 { 3668 textarea.parent().removeClass('error'); 3669 } 3670 }); 3671 }; 3672 3673 this.onInput = (element) => 3674 { 3675 if(!callbacks.input.length) 3676 { 3677 return; 3678 } 3679 3680 element.find('textarea').on('input', function() 3681 { 3682 callbacks.input.forEach((callback) => 3683 { 3684 callback($(this)); 3685 }); 3686 }); 3687 }; 3688 3689 this.onChange = (element) => 3690 { 3691 if(!callbacks.change.length) 3692 { 3693 return; 3694 } 3695 3696 element.find('textarea').on('change', function() 3697 { 3698 callbacks.change.forEach((callback) => 3699 { 3700 callback($(this)); 3701 }); 3702 }); 3703 }; 3704 3705 return this.init(); 3706});common.FunctionCreate('notification', (addon, status, title, message, callback = null) => 3707{ 3708 let id = dh.increaser('common.notification'); 3709 let modal = { 3710 id: 'dh-common-notification', 3711 align: {x: 'right-in', y: 'top'}, 3712 element: $('#dh-modal'), 3713 html: () => 3714 { 3715 return ` 3716 <main class="common-notification-${id}"> 3717 ${common.Render('notification', {status, title, message})} 3718 </main> 3719 `; 3720 }, 3721 timer: 5000 3722 }; 3723 3724 let timer = 0; 3725 let interval = setInterval(() => 3726 { 3727 timer++; 3728 3729 if(timer > 5) 3730 { 3731 clearInterval(interval); 3732 return false; 3733 } 3734 3735 $(`.common-notification-${id}`).find('.timer').text(`${timer} sec ago`); 3736 }, 1000); 3737 3738 if(callback) 3739 { 3740 callback(modal); 3741 } 3742 3743 mdModal.ItemAdd(modal); 3744});common.RenderCreate('notification', (addon, data, attributes) => 3745{ 3746 let status = data('status', 'success'); 3747 let title = data('title', 'Success'); 3748 let message = data('message', 'Action finished successfully.'); 3749 3750 let icon = 'https://global.divhunt.com/0ae574520494fd60d40ed81ae983ae1a_1494.svg'; 3751 3752 if(status === 'warning') 3753 { 3754 icon = 'https://global.divhunt.com/4feebff1a607359ba0f82bf1bd04c472_1802.svg'; 3755 } 3756 else if(status === 'fail') 3757 { 3758 icon = 'https://global.divhunt.com/ee85136c328a151286131945fbb2781e_1723.svg' 3759 } 3760 3761 attributes['modal-close'] = true; 3762 3763 return ` 3764 <div> 3765 <img src="${icon}"> 3766 3767 <div> 3768 <h4>${title}</h4> 3769 <p>${message}</p> 3770 <div class="timer">0 sec ago</div> 3771 </div> 3772 3773 </div> 3774 3775 <img class="close" src="https://global.divhunt.com/c041b7d45b9ed8ae22d4d77cb39da60c_47
37755.svg" modal-close> 3776 `; 3777});$(document).on('change', '[common-change]', function(event) 3778{ 3779 if(!$(this).attr('common-change')) 3780 { 3781 return; 3782 } 3783 3784 let value = $(this).attr('common-change').replace('%{value}%', 3785 $(this).attr('type') === 'checkbox' ? $(this).is(':checked') : $(this).val() 3786 ); 3787 3788 let command = dh.command(value); 3789 3790 let fn = (command) => 3791 { 3792 /* Modal */ 3793 if(command.name === 'modal') 3794 { 3795 common.Fn('modal', chcommandange.options.component ? command.options.component[0] : 'null', command.options); 3796 } 3797 3798 /* Action */ 3799 else if(command.name === 'action') 3800 { 3801 let fields = {}; 3802 fields[$(this).attr('name')] = $(this).attr('type') === 'checkbox' ? $(this).is(':checked') : $(this).val(); 3803 3804 common.Fn('action', command.options, fields); 3805 } 3806 3807 /* Function */ 3808 else if(command.name === 'function') 3809 { 3810 let fields = {}; 3811 fields[$(this).attr('name')] = $(this).attr('type') === 'checkbox' ? $(this).is(':checked') : $(this).val(); 3812 3813 common.Fn('function', command.options.name ? command.options.name[0] : 'null', command.options); 3814 } 3815 }; 3816 3817 /* Confirm */ 3818 if(command.options.confirm) 3819 { 3820 common.Fn('confirm', 'Confirm', command.options.confirm[0], () => 3821 { 3822 fn(command); 3823 }); 3824 } 3825 3826 /* Non Confirm */ 3827 else 3828 { 3829 fn(command); 3830 } 3831});$(document).on('click', '[common-click]', function(event) 3832{ 3833 if(!$(this).attr('common-click')) 3834 { 3835 return; 3836 } 3837 3838 let command = dh.command($(this).attr('common-click')); 3839 3840 let fn = (command) => 3841 { 3842 /* Modal */ 3843 if(command.name === 'modal') 3844 { 3845 common.Fn('modal', command.options.component ? command.options.component[0] : 'null', command.options); 3846 } 3847 3848 /* Action */ 3849 else if(command.name === 'action') 3850 { 3851 $(this).css('opacity', 0.3); 3852 $(this).css('pointer-events', 'none'); 3853 3854 common.Fn('action', command.options, {}, () => 3855 { 3856 $(this).css('opacity', ''); 3857 $(this).css('pointer-events', '') 3858 }, () => 3859 { 3860 $(this).css('opacity', ''); 3861 $(this).css('pointer-events', '') 3862 }); 3863 } 3864 3865 /* Function */ 3866 else if(command.name === 'function') 3867 { 3868 common.Fn('function', command.options.name ? command.options.name[0] : 'null', command.options); 3869 } 3870 }; 3871 3872 /* Confirm */ 3873 if(command.options.confirm) 3874 { 3875 common.Fn('confirm', 'Confirm', command.options.confirm[0], () => 3876 { 3877 fn(command); 3878 }); 3879 } 3880 3881 /* Non Confirm */ 3882 else 3883 { 3884 fn(command); 3885 } 3886});$(document).on('mouseenter', '[common-hover]', function(event) 3887{ 3888 if(!$(this).attr('common-hover')) 3889 { 3890 return; 3891 } 3892 3893 let command = dh.command($(this).attr('common-click')); 3894 3895 let fn = (command) => 3896 { 3897 /* Modal */ 3898 if(command.name === 'modal') 3899 { 3900 common.Fn('modal', command.options.component ? command.options.component[0] : 'null', command.options); 3901 } 3902 3903 /* Action */ 3904 else if(command.name === 'action') 3905 { 3906 common.Fn('action', command.options); 3907 } 3908 3909 /* Function */ 3910 else if(command.name === 'function') 3911 { 3912 common.Fn('function', command.options.name ? command.options.name[0] : 'null', command.options); 3913 } 3914 }; 3915 3916 /* Confirm */ 3917 if(command.options.confirm) 3918 { 3919 common.Fn('confirm', 'Confirm', command.options.confirm[0], () => 3920 { 3921 fn(command); 3922 }); 3923 } 3924 3925 /* Non Confirm */ 3926 else 3927 { 3928 fn(command); 3929 } 3930}); 3931 3932$(document).on('mouseleave', '[common-hover]', function(event) 3933{ 3934 if(!$(this).attr('common-hover')) 3935 { 3936 return; 3937 } 3938 3939 let command = dh.command($(this).attr('common-click')); 3940 3941 let fn = (command) => 3942 { 3943 /* Modal */ 3944 if(command.name === 'modal') 3945 { 3946 common.Fn('modal', command.options.component ? command.options.component[0] : 'null', command.options); 3947 } 3948 3949 /* Action */ 3950 else if(command.name === 'action') 3951 { 3952 common.Fn('action', command.options); 3953 } 3954 3955 /* Function */ 3956 else if(command.name === 'function') 3957 { 3958 common.Fn('function', command.options.name ? command.options.name[0] : 'null', command.options); 3959 } 3960 }; 3961 3962 /* Confirm */ 3963 if(command.options.confirm) 3964 { 3965 common.Fn('confirm', 'Confirm', command.options.confirm[0], () => 3966 { 3967 fn(command); 3968 }); 3969 } 3970 3971 /* Non Confirm */ 3972 else 3973 { 3974 fn(command); 3975 } 3976});$(document).on('input', '[common-input]', function(event) 3977{ 3978 if(!$(this).attr('common-input')) 3979 { 3980 return; 3981 } 3982 3983 let value = $(this).attr('common-input').replace('%{value}%', $(this).val());
3984 let command = dh.command(value); 3985 3986 let fn = (command) => 3987 { 3988 /* Modal */ 3989 if(command.name === 'modal') 3990 { 3991 common.Fn('modal', command.options.component ? command.options.component[0] : 'null', command.options); 3992 } 3993 3994 /* Action */ 3995 else if(command.name === 'action') 3996 { 3997 let fields = {}; 3998 fields[$(this).attr('name')] = $(this).val() 3999 4000 common.Fn('action', command.options, fields); 4001 } 4002 4003 /* Function */ 4004 else if(command.name === 'function') 4005 { 4006 let fields = {}; 4007 fields[$(this).attr('name')] = $(this).val() 4008 4009 common.Fn('function', command.options.name ? command.options.name[0] : 'null', command.options); 4010 } 4011 }; 4012 4013 dh.debounce(() => 4014 { 4015 /* Confirm */ 4016 if(command.options.confirm) 4017 { 4018 common.Fn('confirm', 'Confirm', command.options.confirm[0], () => 4019 { 4020 fn(command); 4021 }); 4022 } 4023 4024 /* Non Confirm */ 4025 else 4026 { 4027 fn(command); 4028 } 4029 }, command.options.debounce ? command.options.debounce[0] : 2000); 4030});$(document).on('mouseenter', '[common-reorder]', function() 4031{ 4032 if(!$(this).attr('common-reorder')) 4033 { 4034 return; 4035 } 4036 4037 if($(this).attr('reorder') === 'initiated') 4038 { 4039 return; 4040 } 4041 4042 let reorder = dh.command($(this).attr('common-reorder')); 4043 4044 /* Initiate */ 4045 common.Fn('reorder', $(this), reorder.options, () => 4046 { 4047 /* Confirm */ 4048 if(reorder.options.confirm) 4049 { 4050 common.Fn('confirm', 'Confirm', reorder.options.confirm[0], () => 4051 { 4052 common.Fn('action', reorder.options); 4053 }); 4054 } 4055 4056 /* Non Confirm */ 4057 else 4058 { 4059 common.Fn('action', reorder.options); 4060 } 4061 4062 return true; 4063 }); 4064});$(document).on('submit', '[common-submit]', function(event) 4065{ 4066 if(!$(this).attr('common-submit')) 4067 { 4068 return; 4069 } 4070 4071 event.preventDefault(); 4072 4073 let value = $(this).attr('common-submit'); 4074 let fields = {}; 4075 4076 $.each($(this).serializeArray(), (index, field) => 4077 { 4078 fields[field.name] = field.value; 4079 value = value.replace('%{'+field.name+'}%', field.value); 4080 }); 4081 4082 let command = dh.command(value); 4083 4084 let fn = (command) => 4085 { 4086 dh.debounce(() => 4087 { 4088 /* Errors */ 4089 let error = false; 4090 4091 $.each(fields, (key, value) => 4092 { 4093 let target = $(this).find(`[name="${key}"]`); 4094 4095 if(target.attr('dh-error')) 4096 { 4097 error = true; 4098 return common.Fn('notification', 'warning', 'Error', 'Please fix error on field "' + key + '". ' + target.attr('dh-error')); 4099 } 4100 4101 if(target.attr('dh-required') === 'true' && !target.val()) 4102 { 4103 target.attr('dh-error', 'Field is required.'); 4104 4105 error = true; 4106 return common.Fn('notification', 'warning', 'Error', 'Please provide value for required field "' + key + '".'); 4107 } 4108 }); 4109 4110 if(error) 4111 { 4112 return; 4113 } 4114 4115 $(this).find('button').css('opacity', 0.3); 4116 $(this).find('button').css('pointer-events', 'none'); 4117 4118 /* Modal */ 4119 if(command.name === 'modal') 4120 { 4121 common.Fn('modal', command.options.component ? command.options.component[0] : 'null', command.options, () => 4122 { 4123 $(this).find('button').css('opacity', ''); 4124 $(this).find('button').css('pointer-events', '') 4125 }); 4126 } 4127 4128 /* Action */ 4129 else if(command.name === 'action') 4130 { 4131 common.Fn('action', command.options, fields, (response) => 4132 { 4133 $(this).find('button').css('opacity', ''); 4134 $(this).find('button').css('pointer-events', '') 4135 }, 4136 (response) => 4137 { 4138 $(this).find('button').css('opacity', ''); 4139 $(this).find('button').css('pointer-events', '') 4140 }); 4141 } 4142 4143 /* Function */ 4144 else if(command.name === 'function') 4145 { 4146 common.Fn('function', command.options.name ? command.options.name[0] : 'null', command.options); 4147 } 4148 }, 200); 4149 }; 4150 4151 /* Confirm */ 4152 if(command.options.confirm) 4153 { 4154 common.Fn('confirm', 'Confirm', command.options.confirm[0], () => 4155 { 4156 fn(command); 4157 }); 4158 } 4159 4160 /* Non Confirm */ 4161 else 4162 { 4163 fn(command); 4164 } 4165});$(document).on('input', '[common-regex]', function(event) 4166{ 4167 if(!$(this).attr('common-regex')) 4168 { 4169 return; 4170 } 4171 4172 let regex = dh.command($(this).attr('common-regex')); 4173 let message = regex.options.message ? regex.options.message[0] : 'Invalid value provided.'; 4174 4175 try 4176 { 4177 if((!$(this).val() && $(this).attr('dh-required') !== 'true') || new RegExp(regex.name).test($(this).val())) 4178 { 4179 $(this).removeAttr('dh-error'); 4180 } 4181 else 4182 { 4183 dh.debounce(() => 4184 { 4185 common.Fn('notification', 'fail', 'Error', message); 4186 }, 500); 4187 4188 $(this).attr('dh-error', message); 4189 } 4190 } 4191 catch(error) 4192 { 4193 dh.debounce(() => 4194 { 4195 common.Fn('notification', 'fail', 'Error', message); 4196 }, 500); 4197 4198 $(this).attr('dh-error', message); 4199 } 4200});$(document).on('mouseenter', '[common-tooltip]', function(event) 4201{ 4202 if(!$(this).attr('common-tooltip')) 4203 { 4204 return; 4205 } 4206
4207 let command = dh.command($(this).attr('common-tooltip')); 4208 let id = dh.increaser('common-tooltip'); 4209 4210 $(this).attr('common-tooltip-modal', id); 4211 4212 let align = {x: 'center', y: 'bottom-out'}; 4213 let offset = {x: 0, y: 10}; 4214 let overlay = {opacity: command.options.opacity ? command.options.opacity[0] : 0, clickable: true}; 4215 4216 switch(command.name) 4217 { 4218 case 'center': 4219 align = {x: 'center', y: 'center'}; 4220 offset = {x: 0, y: 0}; 4221 break; 4222 case 'right': 4223 align = {x: 'right-out', y: 'center'}; 4224 offset = {x: 10, y: 0}; 4225 break; 4226 case 'left': 4227 align = {x: 'left-out', y: 'center'}; 4228 offset = {x: -10, y: 0}; 4229 break; 4230 case 'top': 4231 align = {x: 'center', y: 'top-out'}; 4232 offset = {x: 0, y: -10}; 4233 break; 4234 } 4235 4236 mdModal.ItemAdd({ 4237 id: 'common-tooltip-' + id, 4238 html: function() 4239 { 4240 return ` 4241 <main class="common-tooltip${command.options.component ? ' clean' : ''}">${command.options.component ? $dh.Component(command.options.component[0]) : command.options.text ? command.options.text[0] : 'Missing text.'}</main> 4242 `; 4243 }, 4244 align, 4245 offset, 4246 overlay, 4247 corners: true, 4248 element: $(this) 4249 }); 4250}); 4251 4252$(document).on('mouseleave', '[common-tooltip]', function(event) 4253{ 4254 if(!$(this).attr('common-tooltip')) 4255 { 4256 return; 4257 } 4258 4259 let id = $(this).attr('common-tooltip-modal'); 4260 4261 mdModal.ItemGet('common-tooltip-' + id, (item) => 4262 { 4263 item.Remove(); 4264 }); 4265});common.FunctionCreate('action', (addon, options, fields = {}, then = null, fail = null) => 4266{ 4267 $.each(options, (key, value) => 4268 { 4269 if(!key.startsWith('field-')) 4270 { 4271 return true; 4272 } 4273 4274 fields[key.replace(/^field-/, '')] = value[0]; 4275 delete options[key]; 4276 }); 4277 4278 let settings = { 4279 endpoint: options.endpoint ? options.endpoint[0] : '/api/null/null', 4280 type: options.type ? options.type[0] : 'POST', 4281 onSuccess: options.onSuccess ? options.onSuccess[0] : null, 4282 onFail: options.onFail ? options.onFail[0] : null, 4283 fields: fields 4284 }; 4285 4286 common.Fn('curl', { 4287 type: settings.type, 4288 endpoint: settings.endpoint, 4289 fields: settings.fields 4290 }) 4291 .then((response) => 4292 { 4293 if(settings.onSuccess) 4294 { 4295 $dh.Fn(settings.onSuccess); 4296 } 4297 4298 if(then) 4299 { 4300 if(!then(response)) 4301 { 4302 common.Fn('options', options, $.extend({ 4303 message: response.message, 4304 }, options.key && options.key[0] in response.out ? response.out[options.key[0]] : {})); 4305 } 4306 } 4307 else 4308 { 4309 common.Fn('options', options, $.extend({ 4310 message: response.message, 4311 }, options.key && options.key[0] in response.out ? response.out[options.key[0]] : {})); 4312 } 4313 }) 4314 .catch((response) => 4315 { 4316 if(settings.onFail) 4317 { 4318 $dh.Fn(settings.onFail); 4319 } 4320 4321 if(fail) 4322 { 4323 fail(response); 4324 } 4325 }); 4326});common.FunctionCreate('confirm', (addon, title, description, onConfirm = null) => 4327{ 4328 common.Fn('modal', null, { 4329 title: ['Confirm'], 4330 closeable: ['false'], 4331 padding: [10], 4332 html: [` 4333 <p style="max-width: 420px">${description}</p> 4334 4335 <div class="common-confirm"> 4336 <button modal-close class="dh-btn-secondary">Close</button> 4337 <button modal-close class="dh-btn-primary">Confirm</button> 4338 </div> 4339 `] 4340 }, () => 4341 { 4342 $('.common-confirm').find('.dh-btn-primary').on('click', function() 4343 { 4344 if(onConfirm) 4345 { 4346 onConfirm($(this)); 4347 } 4348 }); 4349 }); 4350});common.FunctionCreate('curl', (addon, data = {}) => 4351{ 4352 let {type, endpoint, fields, headers} = data; 4353 4354 if(!headers || typeof headers !== 'object') 4355 { 4356 headers = { 4357 'Content-Type': 'application/json', 4358 }; 4359 } 4360 4361 return new Promise((resolve, reject) => 4362 { 4363 fetch(endpoint, { 4364 method: type, 4365 headers, 4366 body: JSON.stringify(fields), 4367 }) 4368 .then(response => response.json()) 4369 .then(response => 4370 { 4371 if(response.success) 4372 { 4373 resolve(response); 4374 } 4375 else 4376 { 4377 common.Fn('notification', 'fail', 'Error', response.message); 4378 reject(response); 4379 } 4380 }) 4381 .catch((response) => 4382 { 4383 common.Fn('notification', 'fail', 'Error', response.message); 4384 reject(response); 4385 }); 4386 }); 4387});common.FunctionCreate('function', (addon, name, options = {}, fields = {}) => 4388{ 4389 $.each(options, (key, value) => 4390 { 4391 if(!key.startsWith('field-')) 4392 { 4393 return true; 4394 } 4395 4396 fields[key.replace(/^field-/, '')] = value[0]; 4397 delete options[key]; 4398 }); 4399 4400 let response = $dh.Fn(name, fields); 4401 4402 if(!response || typeof response !== 'string') 4403 { 4404 return; 4405 } 4406
4407 let command = dh.command(response); 4408 4409 let fn = (command) => 4410 { 4411 /* Modal */ 4412 if(command.name === 'modal') 4413 { 4414 common.Fn('modal', command.options.component ? command.options.component[0] : 'null', command.options); 4415 } 4416 4417 /* Action */ 4418 else if(command.name === 'action') 4419 { 4420 common.Fn('action', command.options); 4421 } 4422 4423 /* Function */ 4424 else if(command.name === 'function') 4425 { 4426 common.Fn('function', command.options.name ? command.options.name[0] : 'null', command.options); 4427 } 4428 }; 4429 4430 /* Confirm */ 4431 if(command.options.confirm) 4432 { 4433 common.Fn('confirm', 'Confirm', command.options.confirm[0], () => 4434 { 4435 fn(command); 4436 }); 4437 } 4438 4439 /* Non Confirm */ 4440 else 4441 { 4442 fn(command); 4443 } 4444});common.FunctionCreate('modal', (addon, component, options, onOpen = null, onClose = null) => 4445{ 4446 let fields = {}; 4447 4448 $.each(options, (key, value) => 4449 { 4450 if(!key.startsWith('field-')) 4451 { 4452 return true; 4453 } 4454 4455 fields[key.replace(/^field-/, '')] = value[0]; 4456 delete options[key]; 4457 }); 4458 4459 let settings = { 4460 html: options.html ? options.html[0] : $dh.Component(component, fields), 4461 title: options.title ? options.title[0] : 'Modal', 4462 opacity: options.opacity ? options.opacity[0] : '0.4', 4463 padding: options.padding ? options.padding[0] : '0', 4464 closeable: options.closeable ? options.closeable[0] === 'true' : true, 4465 clickeable: options.clickeable ? options.clickeable[0] === 'true' : false, 4466 position: options.position ? options.position[0] : 'middle', 4467 onOpen: options.onOpen ? options.onOpen[0] : null, 4468 onClose: options.onClose ? options.onClose[0] : null, 4469 align: { 4470 x: 'center', 4471 y: 'center' 4472 } 4473 }; 4474 4475 if(settings.position === 'sidebar') 4476 { 4477 settings.align.x = 'right-in'; 4478 settings.align.y = 'bottom-in'; 4479 } 4480 else if(settings.position === 'terminal') 4481 { 4482 settings.align.x = 'center'; 4483 settings.align.y = 'bottom-in'; 4484 } 4485 4486 mdModal.ItemAdd({ 4487 html: function() 4488 { 4489 return ` 4490 <main class="common-modal ${settings.position}"> 4491 <div class="common-modal-top"> 4492 ${settings.title} 4493 <img modal-close src="https://global.divhunt.com/8a5bd21cedb8e3cf4d79c9253c90f2c2_503.svg"> 4494 </div> 4495 4496 <div class="common-modal-bottom" common-scrollbar style="padding: ${settings.padding}px">${settings.html}</div> 4497 </main> 4498 `; 4499 }, 4500 align: { 4501 x: settings.align.x, 4502 y: settings.align.y 4503 }, 4504 overlay: {opacity: settings.opacity, closeable: settings.closeable, clickeable: settings.clickeable}, 4505 onOpen: () => 4506 { 4507 if(settings.onOpen) 4508 { 4509 $dh.Fn(settings.onOpen); 4510 } 4511 4512 if(onOpen) 4513 { 4514 if(!onOpen()) 4515 { 4516 common.Fn('options', options); 4517 } 4518 } 4519 else 4520 { 4521 common.Fn('options', options); 4522 } 4523 }, 4524 onClose: () => 4525 { 4526 if(settings.onClose) 4527 { 4528 $dh.Fn(settings.onClose); 4529 } 4530 4531 if(onClose) 4532 { 4533 onClose(); 4534 } 4535 } 4536 }); 4537});common.FunctionCreate('options', (addon, options, data = {}) => 4538{ 4539 this.init = () => 4540 { 4541 this.reload(); 4542 this.notification(); 4543 this.page(); 4544 this.refresh(); 4545 this.remove(); 4546 this.modalClose(); 4547 }; 4548 4549 this.reload = () => 4550 { 4551 if(!options.reload) 4552 { 4553 return; 4554 } 4555 4556 $dh.Reload(); 4557 }; 4558 4559 this.notification = () => 4560 { 4561 if(!options.notification) 4562 { 4563 return; 4564 } 4565 4566 common.Fn('notification', 'success', 'Success', options.notification && options.notification[0] ? options.notification[0] : data.message ? data.message : 'Missing notification description.'); 4567 }; 4568 4569 this.page = () => 4570 { 4571 if(!options.page) 4572 { 4573 return; 4574 } 4575 4576 let vars = {}; 4577 let query = {}; 4578 4579 if(options.var) 4580 { 4581 options.var.forEach((name) => 4582 { 4583 name = name.split('|'); 4584 4585 if(name.length === 1) 4586 { 4587 vars[name[0]] = data[name]; 4588 } 4589 else 4590 { 4591 vars[name[0]] = name[1]; 4592 } 4593 }); 4594 } 4595 4596 if(options.query) 4597 {
4598 options.query.forEach((name) => 4599 { 4600 name = name.split('|'); 4601 4602 if(name.length === 1) 4603 { 4604 query[name[0]] = data[name]; 4605 } 4606 else 4607 { 4608 query[name[0]] = name[1]; 4609 } 4610 }); 4611 } 4612 4613 $dh.Page(options.page[0], vars, query); 4614 }; 4615 4616 this.refresh = () => 4617 { 4618 console.log(data); 4619 console.log(options); 4620 4621 if(!options.refresh) 4622 { 4623 return; 4624 } 4625 4626 let target = $('#' + options.refresh[0]); 4627 4628 if(target) 4629 { 4630 tag = components.tags.ItemGet(target.attr('class').split(' ')[0].replace('t', '')); 4631 4632 if(tag) 4633 { 4634 target.replaceWith(tag.Fn('render.html', {symbols: []})); 4635 } 4636 } 4637 }; 4638 4639 this.remove = () => 4640 { 4641 if(!options.remove) 4642 { 4643 return; 4644 } 4645 4646 let target = $('#' + options.remove); 4647 4648 if(target) 4649 { 4650 mdAnimations.Fn('run', 'fadeOut', target, 200, 0, function() 4651 { 4652 target.remove(); 4653 }); 4654 } 4655 }; 4656 4657 this.modalClose = () => 4658 { 4659 if(!options['modal-close']) 4660 { 4661 return; 4662 } 4663 4664 let i = options['modal-close'][0] ? options['modal-close'][0] : 1; 4665 4666 Object.values(mdModal.ItemsGet()).reverse().forEach((item) => 4667 { 4668 if(i >= 1) 4669 { 4670 item.Remove(); 4671 i--; 4672 } 4673 }); 4674 4675 }; 4676 4677 return this.init(); 4678});common.FunctionCreate('reorder', (addon, target, options, onEnd = null) => 4679{ 4680 let settings = { 4681 handle: options.handle ? options.handle[0] : 'common-reorder', 4682 onEnd: options.onEnd ? options.onEnd[0] : null, 4683 }; 4684 4685 mdLibraries.Fn('load', ['sortablejs'], () => 4686 { 4687 $.each(target, function() 4688 { 4689 if(target.attr('reorder') === 'initiated') 4690 { 4691 return true; 4692 } 4693 4694 target.attr('reorder', 'initiated'); 4695 4696 new Sortable($(this)[0], 4697 { 4698 animation: 150, 4699 ghostClass: '', 4700 handle: '.' + settings.handle, 4701 onEnd: (event) => 4702 { 4703 if(settings.onEnd) 4704 { 4705 $dh.Fn(settings.onEnd); 4706 } 4707 4708 if(onEnd) 4709 { 4710 if(!onEnd()) 4711 { 4712 common.Fn('options', options); 4713 } 4714 } 4715 else 4716 { 4717 common.Fn('options', options); 4718 } 4719 }, 4720 }); 4721 }); 4722 }); 4723}); 4724elements.SetReload(false); 4725 4726elements.FieldAdd('name'); 4727elements.FieldAdd('icon'); 4728elements.FieldAdd('description'); 4729elements.FieldAdd('group'); 4730elements.FieldAdd('config', (value) => 4731{ 4732 if(typeof value === 'function') 4733 { 4734 value = value(); 4735 } 4736 4737 return value && typeof value === 'object' ? value : {}; 4738}); 4739 4740elements.FieldAdd('html', (value) => 4741{ 4742 if(typeof value !== 'function') 4743 { 4744 return () => 4745 { 4746 return '<p>Please provide callback function for HTML field.</p>' 4747 } 4748 } 4749 4750 return value; 4751}); 4752 4753elements.Ready();$(document).on('change', 'input[dh-input]', function(event) 4754{ 4755 let callbacks = elements.Fn('callbacks.get', 'input', 'change', $(this).attr('dh-input')); 4756 4757 callbacks.forEach((callback) => 4758 { 4759 callback({ 4760 value: $(this).val() + ($(this).attr('unit') ? $(this).attr('unit') : ''), 4761 target: $(this), 4762 event 4763 }); 4764 }); 4765}); 4766 4767$(document).on('change', 'textarea[dh-elements-textarea]', function(event) 4768{ 4769 let callbacks = elements.Fn('callbacks.get', 'textarea', 'change', $(this).attr('dh-elements-textarea')); 4770 4771 callbacks.forEach((callback) => 4772 { 4773 callback({ 4774 value: $(this).val(), 4775 target: $(this), 4776 event 4777 }); 4778 }); 4779});/* Button */ 4780 4781$(document).on('click', 'button[dh-button]', function(event) 4782{
4783 let callbacks = elements.Fn('callbacks.get', 'button', 'click', $(this).attr('dh-button')); 4784 4785 callbacks.forEach((callback) => 4786 { 4787 callback({ 4788 target: $(this), 4789 event 4790 }); 4791 }); 4792}); 4793 4794/* Input */ 4795 4796$(document).on('click', 'input[dh-input]', function(event) 4797{ 4798 let callbacks = elements.Fn('callbacks.get', 'input', 'click', $(this).attr('dh-input')); 4799 4800 callbacks.forEach((callback) => 4801 { 4802 callback({ 4803 target: $(this), 4804 event 4805 }); 4806 }); 4807}); 4808 4809/* Textarea */ 4810 4811$(document).on('click', 'textarea[dh-elements-textarea]', function(event) 4812{ 4813 let callbacks = elements.Fn('callbacks.get', 'textarea', 'click', $(this).attr('dh-elements-textarea')); 4814 4815 callbacks.forEach((callback) => 4816 { 4817 callback({ 4818 target: $(this), 4819 event 4820 }); 4821 }); 4822}); 4823 4824 4825/* List */ 4826 4827$(document).on('click', '[dh-list]', function(event) 4828{ 4829 let callbacks = elements.Fn('callbacks.get', 'list', 'click', $(this).attr('dh-list')); 4830 4831 callbacks.forEach((callback) => 4832 { 4833 callback({ 4834 target: $(this), 4835 event 4836 }); 4837 }); 4838}); 4839 4840/* Repeater */ 4841 4842$(document).on('click', '[dh-repeater] .row.item', function(event) 4843{ 4844 let callbacks = elements.Fn('callbacks.get', 'repeater', 'click', $(this).closest('[dh-repeater]').attr('dh-repeater')); 4845 4846 callbacks.forEach((callback) => 4847 { 4848 callback({ 4849 target: $(this), 4850 event 4851 }); 4852 }); 4853}); 4854 4855/* Holder */ 4856 4857$(document).on('click', '[dh-holder]', function(event) 4858{ 4859 if($(event.target).hasClass('.holder-prevent') || $(event.target).closest('.holder-prevent').length) 4860 { 4861 return; 4862 } 4863 4864 let callbacks = elements.Fn('callbacks.get', 'holder', 'click', $(this).attr('dh-holder')); 4865 4866 callbacks.forEach((callback) => 4867 { 4868 callback({ 4869 target: $(this), 4870 event 4871 }); 4872 }); 4873}); 4874 4875/* Dropdown */ 4876 4877$(document).on('click', '[dh-dropdown]', function(event) 4878{ 4879 let callbacks = elements.Fn('callbacks.get', 'dropdown', 'click', $(this).attr('dh-dropdown')); 4880 4881 callbacks.forEach((callback) => 4882 { 4883 callback({ 4884 target: $(this), 4885 event 4886 }); 4887 }); 4888});$(document).on('input', 'input[dh-input]', function(event) 4889{ 4890 let callbacks = elements.Fn('callbacks.get', 'input', 'input', $(this).attr('dh-input')); 4891 4892 callbacks.forEach((callback) => 4893 { 4894 callback({ 4895 value: $(this).val() + ($(this).attr('unit') ? $(this).attr('unit') : ''), 4896 target: $(this), 4897 event 4898 }); 4899 }); 4900}); 4901 4902$(document).on('input', 'textarea[dh-elements-textarea]', function(event) 4903{ 4904 let callbacks = elements.Fn('callbacks.get', 'textarea', 'input', $(this).attr('dh-elements-textarea')); 4905 4906 callbacks.forEach((callback) => 4907 { 4908 callback({ 4909 value: $(this).val(), 4910 target: $(this), 4911 event 4912 }); 4913 }); 4914});elements.FunctionCreate('callbacks.add', function(addon, type, event, id, callback) 4915{ 4916 if(typeof callback !== 'function') 4917 { 4918 return; 4919 } 4920 4921 let callbacks = elements.GetTemp('callbacks', {}); 4922 let identifier = type + '-' + event + '-' + id; 4923 4924 if(!(identifier in callbacks)) 4925 { 4926 callbacks[identifier] = []; 4927 } 4928 4929 callbacks[identifier].push(callback); 4930 4931 elements.SetTemp('callbacks', callbacks); 4932}); 4933 4934elements.FunctionCreate('callbacks.get', function(addon, type, event, id) 4935{ 4936 let callbacks = elements.GetTemp('callbacks', {}); 4937 let identifier = type + '-' + event + '-' + id; 4938 4939 if(!(identifier in callbacks)) 4940 { 4941 return [] 4942 } 4943 4944 return callbacks[identifier]; 4945});elements.FunctionCreate('list', function(addon) 4946{ 4947 let list = []; 4948 4949 $.each(addon.ItemsGet(), (index, item) => 4950 { 4951 list.push({ 4952 title: item.Get('name'), 4953 description: item.Get('description'), 4954 group: item.Get('group'), 4955 value: item.Get('id') 4956 }); 4957 }); 4958 4959 return list; 4960}); 4961 4962elements.FunctionCreate('modal.builder', function(addon, values = [], callback = null) 4963{ 4964 let settings = { 4965 html: () => 4966 { 4967 return ` 4968 <main class="dh-elements-modal ${settings.position}"> 4969 <div class="top"> 4970 ${settings.title} 4971 <img modal-close src="https://global.divhunt.com/8a5bd21cedb8e3cf4d79c9253c90f2c2_503.svg"> 4972 </div> 4973 4974 <div class="bottom dh-elements-scrollbar" dh-element-scrollbar style="padding: ${settings.padding}px"> 4975 ${elements.Render('builder', { 4976 values, 4977 onSave: settings.onSave 4978 })} 4979 </div> 4980 </main> 4981 `; 4982 }, 4983 title: 'Builder', 4984 opacity: 0.4, 4985 closeable: false, 4986 clickeable: false, 4987 position: 'middle', 4988 corners: false, 4989 padding: 20, 4990 onSave: null, 4991 onOpen: null, 4992 onClose: null, 4993 element: null, 4994 align: { 4995 x: 'center', 4996 y: 'center' 4997 }, 4998 offset: { 4999 x: 0, 5000 y: 0 5001 } 5002 }; 5003 5004 if(settings.position === 'sidebar') 5005 { 5006 settings.align.x = 'right-in'; 5007 settings.align.y = 'bottom-in'; 5008 } 5009 else if(settings.position === 'terminal') 5010 { 5011 settings.align.x = 'center'; 5012 settings.align.y = 'bottom-in'; 5013 } 5014 5015 if(callback) 5016 { 5017 callback({values, settings}); 5018 } 5019 5020 return mdModal.ItemAdd({ 5021 html: settings.html, 5022 align: { 5023 x: settings.align.x, 5024 y: settings.align.y 5025 }, 5026 offset: { 5027 x: settings.offset.x, 5028 y: settings.offset.y 5029 }, 5030 corners: settings.corners, 5031 overlay: {opacity: settings.opacity, closeable: settings.closeable, clickeable: settings.clickeable}, 5032 element: settings.element, 5033 onOpen: (modal) => 5034 { 5035 if(settings.onOpen) 5036 { 5037 settings.onOpen({config, values, settings, modal}); 5038 } 5039 }, 5040 onClose: (modal) => 5041 { 5042 if(settings.onClose) 5043 { 5044 settings.onClose({config, values, settings, modal}); 5045 } 5046 } 5047 }); 5048});elements.FunctionCreate('modal.form', function(addon, config = {}, values = {}, callback = null) 5049{ 5050 let id = dh.increaser('elements.modal.form'); 5051 5052 let settings = { 5053 html: () => 5054 { 5055 return ` 5056 <main id="dh-elements-modal-${id}" class="dh-elements-modal ${settings.position}"> 5057 <div class="top"> 5058 ${settings.title} 5059 <img modal-close src="https://global.divhunt.com/8a5bd21cedb8e3cf4d79c9253c90f2c2_503.svg"> 5060 </div> 5061 5062 <div class="bottom dh-elements-scrollbar" dh-element-scrollbar style="padding: ${settings.padding}px"> 5063 ${elements.Render('form', { 5064 config, 5065 values, 5066 onChange: (...event) => 5067 { 5068 if(settings.onChange) 5069 { 5070 settings.onChange(...event); 5071 } 5072 5073 /* Modal Position */ 5074 mdModal.ItemGet($('#dh-elements-modal-' + id).parent().parent().attr('data-id'), (modal) => 5075 { 5076 setTimeout(() => 5077 { 5078 modal.Fn('position'); 5079 }); 5080 }); 5081 }, 5082 onSave: settings.onSave 5083 })} 5084 </div> 5085 </main> 5086 `; 5087 }, 5088 title: 'Form', 5089 opacity: 0.4, 5090 closeable: false, 5091 clickeable: false, 5092 position: 'middle', 5093 corners: false, 5094 padding: 20, 5095 onChange: null, 5096 onSave: null, 5097 onOpen: null, 5098 onClose: null, 5099 element: null, 5100 align: { 5101 x: 'center', 5102 y: 'center' 5103 }, 5104 offset: { 5105 x: 0, 5106 y: 0 5107 } 5108 }; 5109 5110 if(settings.position === 'sidebar') 5111 { 5112 settings.align.x = 'right-in'; 5113 settings.align.y = 'bottom-in'; 5114 } 5115 else if(settings.position === 'terminal') 5116 { 5117 settings.align.x = 'center'; 5118 settings.align.y = 'bottom-in'; 5119 } 5120 5121 if(callback) 5122 { 5123 callback({config, values, settings}); 5124 } 5125 5126 return mdModal.ItemAdd({ 5127 html: settings.html, 5128 align: { 5129 x: settings.align.x, 5130 y: settings.align.y 5131 }, 5132 offset: { 5133 x: settings.offset.x, 5134 y: settings.offset.y 5135 }, 5136 corners: settings.corners, 5137 overlay: {opacity: settings.opacity, closeable: settings.closeable, clickeable: settings.clickeable}, 5138 element: settings.element, 5139 onOpen: (modal) => 5140 { 5141 if(settings.onOpen) 5142 { 5143 settings.onOpen({config, values, settings, modal}); 5144 } 5145 }, 5146 onClose: (modal) => 5147 { 5148 if(settings.onClose) 5149 { 5150 settings.onClose({config, values, settings, modal}); 5151 } 5152 } 5153 }); 5154});elements.FunctionCreate('modal.list', function(addon, values, target, value = null, multiple = false, onSelect = null, callback = null) 5155{ 5156 let settings = { 5157 html: () => 5158 { 5159 return ` 5160 <main class="dh-elements-modal mini"> 5161 <div class="top${settings.title ? '' : ' hidden'}"> 5162 ${settings.title} 5163 <img modal-close src="https://global.divhunt.com/8a5bd21cedb8e3cf4d79c9253c90f2c2_503.svg"> 5164 </div> 5165 5166 <div class="bottom dh-elements-scrollbar" style="padding: ${settings.padding}">${elements.Render('list', {values, value, onSelect, multiple}, {class: settings.style})}</div> 5167 </main> 5168 `; 5169 }, 5170 title: 'Select', 5171 opacity: 0.2, 5172 closeable: true, 5173 clickeable: false, 5174 corners: true, 5175 padding: '10px', 5176 onOpen: null, 5177 onClose: null, 5178 element: target, 5179 style: '', 5180 offset: { 5181 x: 0, 5182 y: 5 5183 }, 5184 align: { 5185 x: 'left-in', 5186 y: 'bottom-out' 5187 } 5188 }; 5189 5190 if(callback) 5191 { 5192 callback(settings); 5193 } 5194 5195 mdModal.ItemAdd({ 5196 id: 'dh-elements-list', 5197 html: settings.html, 5198 align: { 5199 x: settings.align.x, 5200 y: settings.align.y 5201 }, 5202 corners: settings.corners, 5203 overlay: {opacity: settings.opacity, closeable: settings.closeable, clickeable: settings.clickeable}, 5204 element: settings.element, 5205 offset: settings.offset, 5206 width: 'width' in settings ? settings.width : target.outerWidth(), 5207 onOpen: () => 5208 { 5209 if(settings.onOpen) 5210 { 5211 settings.onOpen(); 5212 } 5213 }, 5214 onClose: () => 5215 { 5216 if(settings.onClose) 5217 { 5218 settings.onClose(); 5219 } 5220 } 5221 }); 5222});
5222elements.FunctionCreate('sort', (addon, callback = null, onEnd = null) => 5223{ 5224 let identifier = 'dh-sort-' + dh.increaser('elements.sort'); 5225 5226 let settings = { 5227 animation: 150, 5228 handle: '.dh-sort-handle', 5229 ghostClass: '', 5230 onEnd, 5231 }; 5232 5233 if(callback) 5234 { 5235 callback(settings); 5236 } 5237 5238 setTimeout(() => 5239 { 5240 mdLibraries.Fn('load', ['sortablejs'], () => 5241 { 5242 let target = $('.' + identifier); 5243 5244 $.each(target, function() 5245 { 5246 if(target.attr('dh-sort') === 'initiated') 5247 { 5248 return true; 5249 } 5250 5251 target.attr('dh-sort', 'initiated'); 5252 5253 new Sortable($(this)[0], 5254 { 5255 animation: settings.animation, 5256 ghostClass: settings.ghostClass, 5257 handle: settings.handle, 5258 onEnd: settings.onEnd 5259 }); 5260 }); 5261 }); 5262 }); 5263 5264 return identifier; 5265});elements.FunctionCreate('timeline.move', function(addon, onEnd = null, unit) 5266{ 5267 this.position = 0; 5268 5269 this.item; 5270 this.unit = 'second'; 5271 5272 this.init = () => 5273 { 5274 mdLibraries.Fn('load', ['interact'], () => 5275 { 5276 interact('.b1adf1430 .timeline-item').draggable({ 5277 listeners: { 5278 start: event => this.start(event), 5279 move: event => this.move(event), 5280 end: event => this.end(event) 5281 } 5282 }); 5283 }); 5284 }; 5285 5286 this.start = (event) => 5287 { 5288 this.item = $(event.target); 5289 this.containerWidth = this.item.closest('.timeline-line').width(); 5290 this.width = parseInt(this.item.width()); 5291 if (this.item.closest(".timeline-line").hasClass("no-duration")) { 5292 this.width = 0; 5293 } 5294 5295 this.unit = $(event.target).closest(".timeline-area").attr("unit"); 5296 5297 const initialLeftPx = parseFloat(this.item.css('left')) || 0; 5298 this.position = (initialLeftPx / this.containerWidth) * 100; 5299 5300 this.item.find('.delay').show(); 5301 }; 5302 5303 5304 this.move = (event) => 5305 { 5306 let position = Math.max(0, this.position + (event.dx / this.containerWidth) * 100); 5307 5308 5309 if ((this.width / this.containerWidth) * 100 + position > 100) { 5310 position = 100 - (this.width / this.containerWidth) * 100; 5311 } 5312 5313 5314 5315 this.item.css({ left: position + '%' }); 5316 5317 5318 if ( this.unit === "second") { 5319 this.item.find('.delay span').text("Delay:" + (Math.round(position) * 100) + 'ms'); 5320 } else { 5321 this.item.find('.delay span').text("Start at " + Math.round(position) + '%'); 5322 } 5323 5324 this.position = position; 5325 }; 5326 5327 this.end = (event) => 5328 { 5329 this.delay = (Math.round(this.position) * 100); 5330 this.item.find('.duration').hide(); 5331 this.item.find('.delay').hide(); 5332 if(onEnd) 5333 { 5334 onEnd({ 5335 index: this.item.attr('data-index'), 5336 target: this.item, 5337 delay: this.delay, 5338 position: this.position 5339 }); 5340 } 5341 }; 5342 5343 return this; 5344}); 5345elements.FunctionCreate('timeline.resize', function(addon, onEnd = null, unit) 5346{ 5347 this.position = 0; 5348 this.width = 0; 5349 this.item; 5350 this.side = 'right'; 5351 this.unit = 'second'; 5352 5353 this.init = () => 5354 { 5355 mdLibraries.Fn('load', ['interact'], () => 5356 { 5357 interact('.b1adf1430 .timeline-item .resize').resizable({ 5358 listeners: { 5359 start: event => this.start(event), 5360 move: event => this.move(event), 5361 end: event => this.end(event) 5362 } 5363 }); 5364 }); 5365 }; 5366 5367 this.start = (event) => 5368 { 5369 this.unit = $(event.target).closest(".timeline-area").attr("unit"); 5370 this.item = $(event.target).closest('.timeline-item'); 5371 this.containerWidth = this.item.closest('.timeline-line').width(); 5372 5373 5374 const initialLeftPx = parseFloat(this.item.css('left')) || 0; 5375 const initialWidthPx = parseFloat(this.item.width()); 5376 this.position = (initialLeftPx / this.containerWidth) * 100; 5377 this.width = (initialWidthPx / this.containerWidth) * 100; 5378 5379 if ($(event.target).hasClass('left')) { 5380 this.side = 'left'; 5381 this.item.find('.delay').show(); 5382 } else { 5383 this.side = 'right'; 5384 } 5385 this.item.find('.duration').show(); 5386 }; 5387 5388 this.move = (event) => { 5389 5390 const dxPercent = (event.dx / this.containerWidth) * 100; 5391 5392 if (this.side === 'left') { 5393 this.position = Math.max(0, this.position + dxPercent); 5394 this.width = Math.max(0, this.width - dxPercent); 5395 5396 if (this.position + this.width > 100) { 5397 this.width = 100 - this.position; 5398 } 5399 5400 this.item.css({ 5401 left: `${this.position}%`, 5402 width: `${this.width}%` 5403 }); 5404
5405 const delay = this.position ; 5406 5407 if (this.unit === "second") { 5408 this.item.find('.delay span').text("Delay: " + Math.round(delay) * 100 + 'ms'); 5409 } else { 5410 this.item.find('.delay span').text("Start at " + Math.round(this.position ) + '%'); 5411 } 5412 5413 } else { 5414 this.width = Math.max(0, this.width + dxPercent); 5415 5416 if (this.width + this.position > 100) { 5417 this.width = 100 - this.position; 5418 } 5419 5420 this.item.css('width', `${this.width}%`); 5421 } 5422 5423 const duration = this.width; 5424 5425 if (this.unit === "second") { 5426 this.item.find('.duration span').text(`Duration: ${Math.round(duration)*100}ms`); 5427 } else { 5428 this.item.find('.duration span').text(`End at ${Math.round((this.width + this.position)) }%`); 5429 } 5430 }; 5431 5432 5433 this.end = (event) => { 5434 5435 this.item.find('.duration').hide(); 5436 this.item.find('.delay').hide(); 5437 if(onEnd) 5438 { 5439 onEnd({ 5440 index: this.item.attr('data-index'), 5441 target: this.item, 5442 duration: (Math.round(this.width) * 100), 5443 width: this.width, 5444 delay: (Math.round(this.position) * 100) 5445 }); 5446 } 5447 }; 5448 5449 return this; 5450}); 5451elements.ItemAdd({ 5452 id: 'button', 5453 name: 'Button', 5454 description: 'Button.', 5455 config: () => 5456 { 5457 return {}; 5458 }, 5459 html: (data) => 5460 { 5461 return elements.Render('button', { 5462 label: data.config.title, 5463 icon: data.config.icon, 5464 onClick: (event) => 5465 { 5466 data.onChange(null, event); 5467 5468 if(data.config.onClick) 5469 { 5470 data.config.onClick(event); 5471 } 5472 } 5473 }, {class: data.config.style ? data.config.style : 'brand'}); 5474 } 5475});elements.ItemAdd({ 5476 id: 'date', 5477 name: 'Date', 5478 description: 'Date picker.', 5479 html: (data) => 5480 { 5481 return elements.Render('date', { 5482 value: data.value, 5483 min: data.config.min, 5484 max: data.config.max, 5485 variable: data.config.variable, 5486 attributes: data.config.attributes, 5487 placeholder: data.config.placeholder, 5488 onClick: data.config.onClick, 5489 onChange: (event) => 5490 { 5491 data.onChange(event.value, event); 5492 5493 if(data.config.onChange) 5494 { 5495 data.config.onChange(event); 5496 } 5497 } 5498 }); 5499 } 5500});elements.ItemAdd({ 5501 id: 'dropdown', 5502 name: 'Dropdown', 5503 description: 'Chose one or multiple options from a dropdown list.', 5504 config: (data) => 5505 { 5506 5507 }, 5508 html: (data) => 5509 { 5510 return elements.Render('dropdown', { 5511 value: data.value, 5512 values: data.config.values, 5513 style: data.config.style, 5514 multiple: data.config.multiple, 5515 onClick: data.config.onClick, 5516 onSelect: (event) => 5517 { 5518 if(data.config.onSelect) 5519 { 5520 data.config.onSelect(event); 5521 } 5522 } 5523 }); 5524 } 5525});elements.ItemAdd({ 5526 id: 'dynamic-text', 5527 name: 'Dynamic Text', 5528 description: 'Input/Textarea with dynamic variable chips.', 5529 5530 config: () => { 5531 return { 5532 type: { 5533 width: 6, 5534 order: 10, 5535 type: 'SELECT', 5536 label: 'Type', 5537 value: 'input', 5538 values: [ 5539 {title: 'Input', value: 'input'}, 5540 {title: 'Textarea', value: 'textarea'} 5541 ] 5542 }, 5543 placeholder: { 5544 width: 6, 5545 order: 11, 5546 type: 'INPUT', 5547 label: 'Placeholder', 5548 placeholder: 'Placeholder' 5549 } 5550 }; 5551 }, 5552 5553 html: (data) => { 5554 return elements.Render('dynamic-text', { 5555 value: data.value, 5556 type: data.config.type || 'input', 5557 placeholder: data.config.placeholder, 5558 identifier: dh.unique(), 5559 onChange: (value) => { 5560 data.onChange(value); 5561 } 5562 }); 5563 } 5564});elements.ItemAdd({ 5565 id: 'heading', 5566 name: 'Heading', 5567 description: 'Title and description separator.', 5568 config: () => 5569 { 5570 return { 5571 title: { 5572 order: 10, 5573 type: 'INPUT', 5574 label: 'Title', 5575 placeholder: 'Title', 5576 value: 'Title' 5577 }, 5578 description: { 5579 order: 20, 5580 type: 'TEXTAREA', 5581 label: 'Description', 5582 placeholder: 'Description', 5583 } 5584 }; 5585 }, 5586 html: (data) => 5587 { 5588 return elements.Render('heading', 5589 { 5590 title: data.config.title, 5591 description: data.config.description, 5592 }); 5593 } 5594});elements.ItemAdd({ 5595 id: 'html', 5596 name: 'HTML', 5597 description: 'Button.', 5598 config: () => 5599 { 5600 return {}; 5601 }, 5602 html: (data) => 5603 { 5604 if(typeof data.value === 'function') 5605 { 5606 return data.value(); 5607 } 5608 5609 return data.value; 5610 } 5611});elements.ItemAdd({ 5612 id: 'input', 5613 name: 'Input', 5614 description: 'Short text.', 5615 config: () => 5616 { 5617 return { 5618 value: { 5619 width: 6, 5620 order: 10, 5621 type: 'INPUT', 5622 label: 'Value', 5623 placeholder: 'Default Value', 5624 }, 5625 placeholder: { 5626 width: 6, 5627 order: 11, 5628 type: 'INPUT', 5629 label: 'Placeholder', 5630 placeholder: 'Placeholder', 5631 }, 5632 attributes_repeater: { 5633 type: 'REPEATER', 5634 order: 20, 5635 label: 'Attributes', 5636 style: '', 5637 config: { 5638 key: { 5639 type: 'INPUT', 5640 width: 6, 5641 placeholder: 'Key' 5642 }, 5643 value: { 5644 type: 'INPUT', 5645 width: 6, 5646 placeholder: 'Value' 5647 } 5648 }, 5649 whenChanged: (data) => 5650 { 5651 let attributes = {}; 5652 5653 data.values.attributes_repeater.forEach((attribute) => 5654 { 5655 if(attribute.key && attribute.value) 5656 { 5657 attributes[attribute.key] = attribute.value; 5658 } 5659 }); 5660 5661 data.values.attributes = attributes; 5662 } 5663 }, 5664 attributes: { 5665 value: {} 5666 } 5667 }; 5668 }, 5669 html: (data) => 5670 { 5671 return elements.Render('input', { 5672 value: data.value, 5673 prefix: data.config.prefix, 5674 suffix: data.config.suffix, 5675 variable: data.config.variable, 5676 units: data.config.units, 5677 placeholder: data.config.placeholder, 5678 attributes: data.config.attributes, 5679 onClick: data.config.onClick, 5680 onInput: data.config.onInput, 5681 onChange: (event) => 5682 { 5683 data.onChange(event.value, event); 5684 5685 if(data.config.onChange) 5686 { 5687 data.config.onChange(event); 5688 } 5689 } 5690 }); 5691 } 5692});elements.ItemAdd({ 5693 id: 'line', 5694 name: 'Line', 5695 description: 'Line separator.', 5696 html: (data) => 5697 { 5698 return '<hr>'; 5699 } 5700});elements.ItemAdd({ 5701 id: 'repeater', 5702 name: 'Repeater', 5703 description: 'Configure repeater and start adding multiple items.', 5704 html: (data) => 5705 {
5706 return elements.Render('repeater.' + (data.config.view ? data.config.view : 'list'), { 5707 config: data.config.config, 5708 unit: data.config.unit, 5709 caption: data.config.caption, 5710 values: Array.isArray(data.value) ? data.value.filter(Boolean) : [], 5711 onAdd: data.config.onAdd, 5712 onEdit: data.config.onEdit, 5713 onRemove: data.config.onRemove, 5714 onModal: data.config.onModal, 5715 onSave: data.config.onSave, 5716 onMove: data.config.onMove, 5717 onResize: data.config.onResize, 5718 onClick: data.config.onClick, 5719 onDisplay: data.config.onDisplay, 5720 onSort: data.config.onSort, 5721 onChange: (event) => 5722 { 5723 data.onChange(event.values, event); 5724 5725 if(data.config.onChange) 5726 { 5727 data.config.onChange(event); 5728 } 5729 } 5730 }, {class: 'style' in data.config ? data.config.style : 'labeled'}); 5731 } 5732});elements.ItemAdd({ 5733 id: 'select', 5734 name: 'Select', 5735 description: 'Chose one or multiple options from a list.', 5736 config: (data) => 5737 { 5738 return { 5739 value: { 5740 width: 4, 5741 order: 10, 5742 type: 'SELECT', 5743 label: 'Value', 5744 placeholder: 'Default Value', 5745 values: [], 5746 configure: (data) => 5747 { 5748 data.config.value.values = Array.isArray(data.values.values) ? data.values.values : []; 5749 } 5750 }, 5751 placeholder: { 5752 width: 4, 5753 order: 11, 5754 type: 'INPUT', 5755 label: 'Placeholder', 5756 placeholder: 'Placeholder', 5757 }, 5758 multiple: { 5759 width: 4, 5760 order: 12, 5761 label: 'Multiple', 5762 type: 'SELECT', 5763 placeholder: 'Multiple', 5764 values: [ 5765 {'title': 'Yes', value: 'yes'}, 5766 {'title': 'No', value: 'no'}, 5767 ], 5768 configure: (data) => 5769 { 5770 data.values.multiple = data.values.multiple === 'yes'; 5771 }, 5772 whenChanged: (event) => 5773 { 5774 event.values.multiple = event.value === 'yes'; 5775 } 5776 }, 5777 values: { 5778 label: 'Options', 5779 description: 'Add as many options as you need.', 5780 order: 13, 5781 type: 'REPEATER', 5782 value: [], 5783 config: { 5784 title: { 5785 type: 'INPUT', 5786 width: 6, 5787 value: 'Option', 5788 label: 'Title', 5789 }, 5790 value: { 5791 type: 'INPUT', 5792 width: 6, 5793 value: 'option', 5794 label: 'Value' 5795 } 5796 }, 5797 whenChanged: (event) => 5798 { 5799 event.reload(); 5800 } 5801 }, 5802 5803 } 5804 }, 5805 html: (data) => 5806 { 5807 return elements.Render('select', { 5808 value: data.value, 5809 values: data.config.values, 5810 placeholder: data.config.placeholder, 5811 attributes: data.config.attributes, 5812 multiple: data.config.multiple, 5813 onClick: data.config.onClick, 5814 onInput: data.config.onInput, 5815 onChange: data.config.onChange, 5816 onSelect: (event) => 5817 { 5818 let values = []; 5819 5820 event.items.forEach((item) => 5821 { 5822 values.push(item.value); 5823 }); 5824 5825 data.onChange(values.join(','), event); 5826 5827 if(data.config.onSelect) 5828 { 5829 data.config.onSelect(event); 5830 } 5831 } 5832 }); 5833 } 5834});elements.ItemAdd({ 5835 id: 'textarea', 5836 name: 'Textarea', 5837 description: 'Long text.', 5838 config: () => 5839 { 5840 return { 5841 value: { 5842 width: 6, 5843 order: 10, 5844 type: 'INPUT', 5845 label: 'Value', 5846 placeholder: 'Default Value', 5847 }, 5848 placeholder: { 5849 width: 6, 5850 order: 11, 5851 type: 'INPUT', 5852 label: 'Placeholder', 5853 placeholder: 'Placeholder', 5854 }, 5855 attributes_repeater: { 5856 type: 'REPEATER', 5857 order: 20, 5858 label: 'Attributes', 5859 style: '', 5860 config: { 5861 key: { 5862 type: 'INPUT', 5863 width: 6, 5864 placeholder: 'Key' 5865 }, 5866 value: { 5867 type: 'INPUT', 5868 width: 6, 5869 placeholder: 'Value' 5870 } 5871 }, 5872 whenChanged: (data) => 5873 { 5874 let attributes = {}; 5875 5876 data.values.attributes_repeater.forEach((attribute) => 5877 { 5878 if(attribute.key && attribute.value) 5879 { 5880 attributes[attribute.key] = attribute.value; 5881 } 5882 }); 5883 5884 data.values.attributes = attributes; 5885 } 5886 }, 5887 attributes: { 5888 value: {} 5889 } 5890 }; 5891 }, 5892 html: (data) => 5893 { 5894 return elements.Render('textarea', { 5895 value: data.value, 5896 placeholder: data.config.placeholder, 5897 attributes: data.config.attributes, 5898 onClick: data.config.onClick, 5899 onInput: data.config.onInput, 5900 onChange: (event) => 5901 { 5902 data.onChange(event.value, event); 5903 5904 if(data.config.onChange) 5905 { 5906 data.config.onChange(event); 5907 } 5908 } 5909 }); 5910 } 5911});elements.RenderCreate('builder', function(addon, data, attributes) 5912{ 5913 let values = data('values', []); 5914 let onSave = data('onSave'); 5915 5916 this.init = () => 5917 { 5918 return elements.Render('form', { 5919 config: data('type', 'form') === 'form' ? this.form() : this.repeater(), 5920 values, 5921 onSave 5922 }) 5923 }; 5924 5925 this.form = () => 5926 { 5927 return { 5928 fields: { 5929 label: 'Fields', 5930 type: 'REPEATER', 5931 config: { 5932 name: { 5933 width: 4, 5934 type: 'INPUT', 5935 label: 'Name', 5936 placeholder: 'Name', 5937 description: 'Field name.', 5938 }, 5939 width: { 5940 width: 2, 5941 type: 'SELECT', 5942 label: 'Width', 5943 placeholder: 'Select Width', 5944 value: 12, 5945 description: 'Field width.', 5946 values: [ 5947 {title: '25%', value: 3}, 5948 {title: '50%', value: 6}, 5949 {title: '75%', value: 9}, 5950 {title: '100%', value: 12}, 5951 ] 5952 }, 5953 type: { 5954 width: 6, 5955 type: 'SELECT', 5956 label: 'Type', 5957 placeholder: 'Select Type', 5958 description: 'Select field type.', 5959 values: () => { 5960 return elements.Fn('types'); 5961 }, 5962 whenChanged: (event) => 5963 { 5964 $.each(event.config, (index, value) => 5965 { 5966 if(value.custom) 5967 { 5968 delete event.config[index]; 5969 } 5970 }); 5971 5972 switch(event.select.item.value) 5973 { 5974 case 'INPUT': 5975 this.configInput(event.config); 5976 break; 5977 case 'SELECT': 5978 this.configSelect(event.config); 5979 break; 5980 } 5981 5982 event.reload(); 5983 } 5984 } 5985 } 5986 } 5987 }; 5988 }; 5989 5990 this.repeater = () => 5991 { 5992 return {}; 5993 }; 5994 5995 this.configInput = (config) => 5996 { 5997 config.value = { 5998 width: 6, 5999 type: 'INPUT', 6000 label: 'Value', 6001 placeholder: 'Default Value', 6002 description: 'Default value unless overriden.', 6003 custom: true, 6004 }; 6005 6006 config.placeholder = { 6007 width: 6, 6008 type: 'INPUT', 6009 label: 'Placeholder', 6010 placeholder: 'Placeholder', 6011 description: 'Placeholder value.', 6012 custom: true, 6013 }; 6014 }; 6015 6016 this.configSelect = (config) => 6017 { 6018 config.value = { 6019 width: 6, 6020 type: 'SELECT', 6021 label: 'Value', 6022 values: [], 6023 placeholder: 'Default Value', 6024 description: 'Default value unless overriden.', 6025 custom: true, 6026 }; 6027 6028 config.placeholder = { 6029 width: 6, 6030 type: 'INPUT', 6031 label: 'Placeholder', 6032 placeholder: 'Placeholder', 6033 description: 'Placeholder value.', 6034 custom: true, 6035 }; 6036 6037 config.values = { 6038 type: 'REPEATER', 6039 label: 'Options', 6040 description: 'Separate up to 50 options each with new line.', 6041 custom: true, 6042 config: { 6043 title: { 6044 width: 6, 6045 label: 'Title', 6046 placeholder: 'Title', 6047 type: 'INPUT', 6048 }, 6049 value: { 6050 width: 6, 6051 label: 'Value', 6052 placeholder: 'Value', 6053 type: 'INPUT', 6054 } 6055 }, 6056 onAdd: (event) => 6057 { 6058 let index = dh.increaser('elements.builder.option'); 6059 event.item.title = 'Option ' + index; 6060 event.item.value = 'option-' + index; 6061 }, 6062 whenChanged: (event) => 6063 { 6064 config.value.values = event.repeater.values; 6065 6066 setTimeout(() => 6067 { 6068 event.reload(); 6069 }); 6070 } 6071 }; 6072 }; 6073 6074 return this.init(); 6075});elements.RenderCreate('form', function(addon, data, attributes) 6076{ 6077 let id = dh.increaser('elements.form'); 6078 6079 attributes['dh-elements-form'] = id; 6080
6081 let config = data('config', {}); 6082 let values = data('values', {}); 6083 6084 let onSave = data('onSave'); 6085 let onChange = data('onChange'); 6086 let onClose = data('onClose'); 6087 6088 let changes = {}; 6089 let fields = {}; 6090 6091 this.init = () => 6092 { 6093 let form = ''; 6094 let groups = this.groups(); 6095 6096 this.configure(); 6097 6098 $.each(config, (key, setting) => 6099 { 6100 if(setting.value) 6101 { 6102 setting.value = JSON.parse(JSON.stringify(setting.value)); 6103 } 6104 6105 values[key] = values[key] ? values[key] : 'value' in setting ? setting.value : null; 6106 }); 6107 6108 $.each(config, (key, setting) => 6109 { 6110 if(!('type' in setting)) 6111 { 6112 return true; 6113 } 6114 6115 let type = elements.ItemGet(setting.type.toLowerCase()); 6116 let value = values[key]; 6117 6118 if(type) 6119 { 6120 let html = type.Get('html')({ 6121 key, 6122 values, 6123 value, 6124 config: setting, 6125 onChange: (changedValue, data = {}) => 6126 { 6127 changes[key] = { 6128 from: value, 6129 to: changedValue 6130 }; 6131 6132 value = values[key] = changedValue; 6133 6134 let callback = {config, values, value: values[key], key, changes, element: data, reload: this.reload()}; 6135 6136 if(onChange) 6137 { 6138 onChange(callback); 6139 } 6140 6141 if(setting.whenChanged) 6142 { 6143 setting.whenChanged(callback); 6144 } 6145 6146 if('configure' in setting) 6147 { 6148 this.reload()(); 6149 } 6150 } 6151 }); 6152 6153 if(data('separated')) 6154 { 6155 fields[key] = ` 6156 <div element-form="${id}" element-field="${key}" class="${setting.hidden ? 'dh-element-hidden' : ''}"> 6157 ${html} 6158 </div> 6159 `; 6160 } 6161 else 6162 { 6163 form += ` 6164 <div style="order: ${'order' in setting ? setting.order : 1}" element-form="${id}" element-field="${key}" class="${setting.hidden ? 'dh-element-hidden ' : ''}row dh-elements-grid s${'width' in setting ? setting.width : 12}"> 6165 ${'label' in setting ? ` 6166 <div class="row ${setting['labelInline'] ? 's3' : 's12'}"> 6167 <p>${setting.label ? setting.label : ''}${setting.description ? `<span>${setting.description}</span>` : ''}</p> 6168 </div> 6169 ` : ''} 6170 6171 <div class="row ${setting['labelInline'] ? 's9' : 's12'}"> 6172 ${html} 6173 </div> 6174 </div> 6175 `; 6176 } 6177 } 6178 else 6179 { 6180 if(data('separated')) 6181 { 6182 fields[key] = `<p>Type "${setting.type}" does not exist.</p>`; 6183 } 6184 else 6185 { 6186 form += ` 6187 <div style="order: ${'order' in setting ? setting.order : 1}" class="row"> 6188 <p>Type "${setting.type}" does not exist.</p> 6189 </div> 6190 `; 6191 } 6192 } 6193 }); 6194 6195 if(data('separated')) 6196 { 6197 return {id, fields, config, values, reload: this.reload(), trigger: this.trigger()}; 6198 } 6199 6200 if(data('object')) 6201 { 6202 return {id, html: ` 6203 <div dh-elements-form="${id}"> 6204 <div class="dh-elements-grid">${form}</div>${this.actions()} 6205 </div> 6206 `, config, values, reload: this.reload(), trigger: this.trigger()}; 6207 } 6208 6209 return `<div class="dh-elements-grid">${form}</div>${this.actions()}`; 6210 }; 6211 6212 this.groups = () => 6213 { 6214 let groups = []; 6215 6216 $.each(config, (key, value) => 6217 { 6218 let group = 'group' in value ? value.group : 'General'; 6219 6220 if(!groups.includes(group)) 6221 { 6222 groups.push(group); 6223 } 6224 }); 6225 6226 return groups; 6227 }; 6228 6229 this.actions = () => 6230 { 6231 let actions = []; 6232 6233 if(onClose) 6234 { 6235 actions.push(elements.Render('button', { 6236 label: 'Close', 6237 attributes: { 6238 'modal-close': true 6239 }, 6240 onClick: (event) => 6241 { 6242 onClose({values, changes, button: event}); 6243 } 6244 }, {class: 'color-2'})); 6245 } 6246 6247 if(onSave) 6248 { 6249 actions.push(elements.Render('button', { 6250 label: 'Save', 6251 attributes: { 6252 'modal-close': true 6253 }, 6254 onClick: (event) => 6255 { 6256 onSave({values, changes, button: event}); 6257 } 6258 })); 6259 } 6260 6261 return actions.length ? ` 6262 <div class="actions"> 6263 ${actions.join('')} 6264 </div> 6265 ` : ''; 6266 }; 6267 6268 this.reload = () => 6269 { 6270 return () => 6271 { 6272 let form = this.init(); 6273 6274 if(data('separated')) 6275 { 6276 $.each(form.fields, (key, html) => 6277 { 6278 $(`[element-form="${id}"][element-field="${key}"]`).replaceWith(html); 6279 }); 6280 } 6281 else if(data('object')) 6282 { 6283 $(`[dh-elements-form="${id}"]`).html(form.html); 6284 } 6285 else 6286 { 6287 $(`[dh-elements-form="${id}"]`).html(form); 6288 } 6289 }; 6290 }; 6291 6292 this.trigger = () => 6293 { 6294 return (type) => 6295 { 6296 if(type === 'change') 6297 { 6298 if(onChange) 6299 { 6300 onChange({config, values, changes, reload: this.reload()}); 6301 } 6302 } 6303 }; 6304 }; 6305 6306 this.configure = (configure = null, remove = true) => 6307 { 6308 let fields = {}; 6309 6310 if(remove) 6311 { 6312 $.each(config, (key, setting) => 6313 { 6314 if(setting['configured']) 6315 { 6316 delete config[key]; 6317 } 6318 }); 6319 } 6320 6321 if(configure === null) 6322 { 6323 $.each(config, (key, value) => 6324 { 6325 if(typeof value.configure === 'function') 6326 { 6327 this.configure(value.configure, false); 6328 } 6329 }); 6330 6331 return; 6332 } 6333 6334 configure = configure({config, values}); 6335 6336 if(configure) 6337 { 6338 $.each(configure, (key, value) => 6339 { 6340 value.configured = true; 6341 fields[key] = value; 6342 }); 6343 6344 config = $.extend(config, fields); 6345 6346 $.each(configure, (key, value) => 6347 { 6348 if(typeof value.configure === 'function') 6349 { 6350 this.configure(value.configure, false); 6351 } 6352 }); 6353 6354 } 6355 }; 6356 6357 return this.init(); 6358});elements.RenderCreate('button', function(addon, data, renderAttributes) 6359{ 6360 let id = dh.increaser('elements.button'); 6361 6362 elements.Fn('callbacks.add', 'button', 'click', id, data('onClick')); 6363 6364 let attributes = data('attributes', {}); 6365 attributes['dh-button'] = id; 6366 6367 if(data('icon')) 6368 { 6369 renderAttributes['icon'] = 'true'; 6370 } 6371 6372 this.init = () => 6373 { 6374 return ` 6375 <button ${dh.attributes(attributes)}>${this.content()}</button> 6376 `; 6377 }; 6378 6379 this.content = () => 6380 { 6381 if(data('label')) 6382 { 6383 return data('label', ''); 6384 } 6385 6386 if(data('icon')) 6387 { 6388 return `<img src="${data('icon')}">`; 6389 } 6390 6391 return 'Button'; 6392 } 6393 6394 this.prefix = (element) => 6395 { 6396 if(!('prefix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 6397 { 6398 return; 6399 } 6400 6401 element.find('button').prepend(`<span class="prefix">${setting.prefix}</span>`); 6402 }; 6403 6404 this.suffix = (element) => 6405 { 6406 if(!('suffix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 6407 { 6408 return; 6409 } 6410 6411 element.find('button').append(`<span class="suffix">${setting.suffix}</span>`); 6412 }; 6413 6414 return this.init(); 6415});elements.RenderCreate('date', function(addon, data, renderAttributes) 6416{ 6417 let id = dh.increaser('elements.date'); 6418 let attributes = data('attributes', {}); 6419 let onChange = data('onChange'); 6420 6421 let value = (typeof data('value') === 'string' || typeof data('value') === 'number') ? data('value') : ''; 6422 6423 console.log(value); 6424 6425 attributes['dh-date'] = id; 6426 attributes['value'] = value; 6427 6428 elements.Fn('callbacks.add', 'date', 'click', id, data('onClick')); 6429 6430 this.init = () => 6431 { 6432 this.placeholder(); 6433 6434 setTimeout(() => 6435 { 6436 mdLibraries.Fn('load', ['air-datepicker'], () => 6437 { 6438 this.initiate(); 6439 }); 6440 }); 6441 6442 return ` 6443 <input ${dh.attributes(attributes)}> 6444 `; 6445 }; 6446 6447 this.placeholder = () => 6448 { 6449 let placeholder = data('placeholder'); 6450 6451 if(placeholder && (typeof placeholder === 'string' || typeof placeholder === 'number')) 6452 { 6453 attributes.placeholder = placeholder; 6454 } 6455 }; 6456 6457 6458 this.initiate = () => 6459 { 6460 let target = $('input[dh-date="'+id+'"]'); 6461 6462 let picker = new AirDatepicker(target[0], { 6463 minDate: data('min'), 6464 maxDate: data('max'), 6465 selectedDates: [new Date(value)], 6466 dateFormat: 'yyyy-MM-dd', 6467 locale: { 6468 days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 6469 daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], 6470 daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], 6471 months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
6471November", "December"], 6472 monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], today: "Today", 6473 clear: "Clear", 6474 firstDay: 0 6475 }, 6476 onSelect: (event) => 6477 { 6478 let date = new Date(event.date); 6479 6480 let year = date.getFullYear(); 6481 let month = String(date.getMonth() + 1).padStart(2, '0'); 6482 let day = String(date.getDate()).padStart(2, '0'); 6483 6484 onChange({ 6485 event, 6486 target, 6487 date, 6488 value: (year + '-' + month + '-' + day) 6489 }); 6490 6491 target.val((year + '-' + month + '-' + day)); 6492 picker.hide(); 6493 } 6494 }); 6495 }; 6496 6497 return this.init(); 6498});elements.RenderCreate('dropdown', function(addon, data, attributes) 6499{ 6500 let id = dh.increaser('elements.dropdown'); 6501 6502 elements.Fn('callbacks.add', 'dropdown', 'click', id, data('onClick')); 6503 elements.Fn('callbacks.add', 'dropdown', 'select', id, data('onSelect')); 6504 6505 let onSelect = data('onSelect'); 6506 6507 attributes['dh-dropdown'] = id; 6508 6509 this.init = () => 6510 { 6511 this.list(); 6512 6513 return data('value', ''); 6514 }; 6515 6516 this.list = () => 6517 { 6518 elements.Fn('callbacks.add', 'dropdown', 'click', id, (event) => 6519 { 6520 let values = data('values'); 6521 6522 if(typeof values === 'function') 6523 { 6524 values = values(); 6525 } 6526 6527 if(values instanceof Promise) 6528 { 6529 values.then((values) => 6530 { 6531 elements.Fn('modal.list', values, event.target, null, data('multiple', false), this.listCallback(event), (settings) => 6532 { 6533 settings.width = 200; 6534 settings.style = data('style', ''); 6535 settings.title = ''; 6536 }); 6537 }); 6538 } 6539 else 6540 { 6541 elements.Fn('modal.list', values, event.target, null, data('multiple', false), this.listCallback(event), (settings) => 6542 { 6543 settings.width = 200; 6544 settings.style = data('style', ''); 6545 settings.title = ''; 6546 }); 6547 } 6548 }); 6549 }; 6550 6551 this.listCallback = (selectEvent) => 6552 { 6553 return (listEvent) => 6554 { 6555 if(onSelect) 6556 { 6557 onSelect({ 6558 items: listEvent.items, 6559 events: { 6560 select: selectEvent, 6561 list: listEvent 6562 }, 6563 multiple: data('multiple', false) 6564 }); 6565 } 6566 6567 let values = []; 6568 6569 listEvent.items.forEach((item) => 6570 { 6571 values.push(item.value); 6572 }); 6573 6574 if(data('multiple', false)) 6575 { 6576 selectEvent.target.parent().find('> .value').text(values.length === 1 ? '1 Item Selected' : !values.length ? '' : (values.length + ' Items Selected')); 6577 } 6578 else 6579 { 6580 selectEvent.target.parent().find('> .value').text(listEvent.items.length ? listEvent.items[0].title : ''); 6581 } 6582 6583 selectEvent.target.val(values.join(',')).trigger('change'); 6584 }; 6585 }; 6586 6587 return this.init(); 6588});elements.RenderCreate('heading', function(addon, data) 6589{ 6590 return ` 6591 <h2>${data('title', 'Title', true)}</h2> 6592 <p>${data('description', 'Description', true)}</p> 6593 `; 6594});elements.RenderCreate('holder', function(addon, data, attributes) 6595{ 6596 let id = dh.increaser('elements.holder'); 6597 6598 attributes['dh-holder'] = id; 6599 6600 elements.Fn('callbacks.add', 'holder', 'click', id, data('onClick')); 6601 elements.Fn('callbacks.add', 'holder', 'dblclick', id, data('onDblclick')); 6602 6603 return data('html', data('value', '')); 6604});elements.RenderCreate('input', function(addon, data) 6605{ 6606 let id = dh.increaser('elements.input'); 6607 6608 elements.Fn('callbacks.add', 'input', 'click', id, data('onClick')); 6609 elements.Fn('callbacks.add', 'input', 'input', id, data('onInput')); 6610 elements.Fn('callbacks.add', 'input', 'change', id, data('onChange')); 6611 6612 let attributes = data('attributes', {}); 6613 let value = (typeof data('value') === 'string' || typeof data('value') === 'number') ? data('value') : ''; 6614 6615 attributes['dh-input'] = id; 6616 attributes['value'] = value; 6617 6618 this.init = () => 6619 { 6620 this.placeholder(); 6621 this.units(); 6622 6623 return ` 6624 ${this.prefix()} 6625 <input ${dh.attributes(attributes)}> 6626 ${this.suffix()} 6627 ${this.characters()} 6628 ${this.variable()} 6629 `; 6630 }; 6631 6632 this.placeholder = () => 6633 { 6634 let placeholder = data('placeholder'); 6635 6636 if(placeholder && (typeof placeholder === 'string' || typeof placeholder === 'number')) 6637 { 6638 attributes.placeholder = placeholder; 6639 } 6640 }; 6641 6642 this.prefix = () => 6643 { 6644 if(!data('prefix')) 6645 { 6646 return ''; 6647 } 6648 6649 return ` 6650 <div class="prefix"> 6651 ${data('prefix')} 6652 </div> 6653 `; 6654 }; 6655 6656 this.suffix = () => 6657 { 6658 if(!data('suffix')) 6659 { 6660 return ''; 6661 } 6662 6663 return ` 6664 <div class="suffix"> 6665 ${data('suffix')} 6666 </div> 6667 `; 6668 }; 6669 6670 this.characters = () => 6671 { 6672 let characters = data('characters', '').split('/'); 6673 6674 if(characters.length !== 2) 6675 { 6676 return ''; 6677 } 6678 6679 if(typeof parseInt(characters[0]) !== 'number' || typeof parseInt(characters[1]) !== 'number') 6680 { 6681 return ''; 6682 } 6683 6684 elements.Fn('callbacks.add', 'input', 'input', id, (value, target) => 6685 { 6686 let length = value.length; 6687 6688 if(length > characters[1]) 6689 { 6690 target.parent().find('.characters').addClass('error'); 6691 } 6692 else if(length < characters[0]) 6693 { 6694 target.parent().find('.characters').addClass('error'); 6695 } 6696 else 6697 { 6698 target.parent().find('.characters').removeClass('error'); 6699 } 6700 6701 target.parent().find('.characters').html(` 6702 ${length} / ${characters[1]} 6703 `); 6704 }); 6705 6706 return ` 6707 <div class="characters"> 6708 ${'value' in attributes ? attributes.value.length : 0} / ${characters[1]} 6709 </div> 6710 `; 6711 }; 6712 6713 this.regex = () => 6714 { 6715 if(!('regex' in setting) && typeof setting.regex !== 'string') 6716 { 6717 return; 6718 } 6719 6720 let regex = new RegExp('^' + setting.regex + '$'); 6721 6722 callbacks.input.push((input) => 6723 { 6724 let value = input.val(); 6725 6726 if(value && !regex.test(value)) 6727 { 6728 input.parent().addClass('error'); 6729 } 6730 else 6731 { 6732 input.parent().removeClass('error'); 6733 } 6734 }); 6735 }; 6736 6737 this.units = () => 6738 { 6739 let units = data('units', []); 6740 6741 if(!units.length) 6742 { 6743 return; 6744 } 6745 6746 let unit = units[0]; 6747 let found = false;
6748 6749 units.forEach(function(item) 6750 { 6751 if((value + '').endsWith(item)) 6752 { 6753 value = value.slice(0, -(item.length)); 6754 unit = item; 6755 found = true; 6756 return true; 6757 } 6758 }); 6759 6760 if(!found && units.includes('-')) 6761 { 6762 unit = '-'; 6763 } 6764 6765 if(/^(-?\d+(\.\d*)?)?$/.test(value)) 6766 { 6767 attributes['value'] = value; 6768 } 6769 else 6770 { 6771 unit = ''; 6772 } 6773 6774 attributes['unit'] = unit; 6775 6776 data(null).suffix = ` 6777 <span style="font-size: 10px; font-weight: bold; padding: 5px; color: var(--dh-text-1)">${unit}</span> 6778 `; 6779 6780 elements.Fn('callbacks.add', 'input', 'input', id, (event) => 6781 { 6782 let value = (event.target.val() + '').toLowerCase(); 6783 6784 if(!/^(-?\d+([./*]\d*)?)?$/.test(value) && units.length) 6785 { 6786 let string = value.replace(/[0-9.*/]/g, '').replace(/^-/, ''); 6787 6788 if(units.includes(string)) 6789 { 6790 event.target.val(value.slice(0, -(string.length))).focus(); 6791 event.target.attr('unit', string); 6792 event.target.next().find('span').text(string); 6793 6794 if(unit !== string) 6795 { 6796 unit = string; 6797 event.target.trigger('change'); 6798 } 6799 } 6800 else 6801 { 6802 event.target.attr('unit', ''); 6803 event.target.next().find('span').text('-'); 6804 } 6805 } 6806 }); 6807 }; 6808 6809 this.variable = () => 6810 { 6811 let variable = data('variable'); 6812 6813 if(typeof variable !== 'function') 6814 { 6815 return ''; 6816 } 6817 6818 if(value[0] === '$' && !value.startsWith('${')) 6819 { 6820 if('style' in attributes) 6821 { 6822 attributes['style'] += 'display: none;'; 6823 } 6824 else 6825 { 6826 attributes['style'] = 'display: none;'; 6827 } 6828 } 6829 6830 return ` 6831 <div class="variable"> 6832 ${elements.Render('button', { 6833 icon: 'https://global.divhunt.com/cf6b5c06ca5f1bee0784c4b67010ddd6_1125.svg', 6834 onClick: (event) => 6835 { 6836 mdVars.Fn('builder', variable()); 6837 } 6838 })} 6839 </div> 6840 `; 6841 6842 return mdVars.Render('button', 6843 { 6844 data: variable, 6845 value, 6846 onChange: (target, value, preview) => 6847 { 6848 if(value && value[0] === '$' && !value.startsWith('${')) 6849 { 6850 target.hide(); 6851 6852 target.parent().find('.b345c8eeb').addClass('on'); 6853 target.parent().find('.b345c8eeb .value').text(preview); 6854 } 6855 else 6856 { 6857 target.show(); 6858 6859 target.parent().find('.b345c8eeb').removeClass('on'); 6860 target.parent().find('.b345c8eeb .value').text(''); 6861 } 6862 } 6863 }); 6864 }; 6865 6866 return this.init(); 6867});elements.RenderCreate('list', function(addon, data, attributes) 6868{ 6869 let id = dh.increaser('elements.list'); 6870 6871 let value = data('value', '').split(',').filter(Boolean); 6872 let values = []; 6873 let onSelect = data('onSelect'); 6874 let selected = []; 6875 6876 this.init = () => 6877 { 6878 this.click(); 6879 this.multiple(); 6880 6881 let items = data('values'); 6882 6883 if(typeof items === 'function') 6884 { 6885 items = items(); 6886 } 6887 6888 if(items instanceof Promise) 6889 { 6890 items.then((items) => 6891 { 6892 values = items; 6893 6894 $(`.dh-list-${id}`).replaceWith(this.render(items)) 6895 }); 6896 6897 return `<div class="dh-list-${id}"></div>`; 6898 } 6899 else 6900 { 6901 values = items; 6902 6903 return this.render(items); 6904 } 6905 }; 6906 6907 this.render = (values) => 6908 { 6909 let groups = this.groups(values); 6910 let content = ''; 6911 6912 $.each(groups, (name, items) => 6913 { 6914 content += ` 6915 <div class="group${name ? '' : ' no-name'}"> 6916 <div class="name">${name}</div> 6917 <div class="items"> 6918 `; 6919
6920 items.forEach((item) => 6921 { 6922 if(item.parent || item.parent === 0) 6923 { 6924 return true; 6925 } 6926 6927 content += this.item(item, items); 6928 }); 6929 6930 content += '</div></div>'; 6931 }); 6932 6933 return content + this.actions(values); 6934 }; 6935 6936 this.groups = (values) => 6937 { 6938 let groups = {}; 6939 6940 values.forEach((item, index) => 6941 { 6942 if(!('group' in item) || !item.group) 6943 { 6944 item.group = ''; 6945 } 6946 6947 if(!(item.group in groups)) 6948 { 6949 groups[item.group] = []; 6950 } 6951 6952 if(!('index' in item)) 6953 { 6954 item.index = index; 6955 } 6956 6957 groups[item.group].push(item); 6958 }); 6959 6960 return groups; 6961 }; 6962 6963 this.click = () => 6964 { 6965 elements.Fn('callbacks.add', 'list', 'click', id, (event) => 6966 { 6967 if(event.target.parent().hasClass('parent')) 6968 { 6969 event.target.parent().toggleClass('opened'); 6970 } 6971 6972 let item = values[event.target.attr('data-item')]; 6973 6974 if(onSelect && !data('multiple', false)) 6975 { 6976 onSelect({ 6977 items: [item], 6978 element: event, 6979 parent: event.target.parent().hasClass('parent'), 6980 multiple: data('multiple', false) 6981 }); 6982 } 6983 }); 6984 }; 6985 6986 this.multiple = () => 6987 { 6988 if(!data('multiple', false)) 6989 { 6990 return; 6991 } 6992 6993 elements.Fn('callbacks.add', 'list', 'click', id, (event) => 6994 { 6995 if(event.target.parent().hasClass('parent')) 6996 { 6997 return; 6998 } 6999 7000 event.target.toggleClass('selected'); 7001 7002 let item = parseInt(event.target.attr('data-item')); 7003 7004 if(selected.includes(item)) 7005 { 7006 selected.forEach((value, index) => 7007 { 7008 if(item === value) 7009 { 7010 delete selected[index]; 7011 } 7012 }) 7013 7014 selected = selected.filter(Boolean); 7015 } 7016 else 7017 { 7018 selected.push(item); 7019 } 7020 7021 event.target.closest('.b2113e1eb').find('.actions > p').text(selected.length !== 1 ? (selected.length + ' Items Selected') : '1 Item Selected'); 7022 }); 7023 }; 7024 7025 this.item = (item, items, child = false) => 7026 { 7027 let classes = []; 7028 let children = this.children(item.index, items); 7029 let attributes = 'attributes' in item ? item.attributes : {}; 7030 7031 attributes['dh-list'] = id; 7032 7033 if(data('attributes')) 7034 { 7035 data('attributes')(item, attributes); 7036 } 7037 7038 attributes['data-item'] = item.index; 7039 7040 if(!data('multiple', false)) 7041 { 7042 attributes['modal-close'] = 'true'; 7043 } 7044 7045 if(child) 7046 { 7047 classes.push('child'); 7048 } 7049 7050 if(children) 7051 { 7052 classes.push('parent'); 7053 } 7054 7055 if('hidden' in item && item.hidden) 7056 { 7057 classes.push('hidden'); 7058 } 7059 7060 value.forEach((val) => 7061 { 7062 if(val == item.value) 7063 { 7064 selected.push(item.index); 7065 7066 if(!('class' in attributes)) 7067 { 7068 attributes['class'] = ''; 7069 } 7070 7071 attributes['class'] += ' selected'; 7072 } 7073 }) 7074 7075 return ` 7076 <div${classes.length ? ` class="${classes.join(' ')} opened"` : ''}> 7077 <span ${dh.attributes(attributes)}>${children ? '<img hidden class="caret modal-close-prevent" src="https://global.divhunt.com/15975a368079082940619f4cfe5937d1_238.svg">' : ''}${dh.sanitize(item.title)}${('description' in item && item.description) ? `<p>${dh.sanitize(item.description)}</p>` : ''}</span> 7078 ${children ? children : ''} 7079 </div> 7080 `; 7081 }; 7082 7083 this.children = (parent, items) => 7084 { 7085 let children = ''; 7086 7087 $.each(items, (index, item) => 7088 { 7089 if(item.parent === parent) 7090 { 7091 children += this.item(item, items, true); 7092 } 7093 }); 7094 7095 if(children) 7096 { 7097 return `<div class="children">${children}</div>`; 7098 } 7099 7100 return null; 7101 }; 7102 7103 this.actions = (values) => 7104 { 7105 return ` 7106 <div class="actions"> 7107 <p>${selected.length} ${selected.length === 1 ? 'Item' : 'Items'} Selected</p> 7108 <div> 7109 ${elements.Render('button', { 7110 label: 'Clear', 7111 onClick: (event) => 7112 { 7113 onSelect({ 7114 items: [], 7115 element: event, 7116 parent: false, 7117 mutliple: data('multiple', false) 7118 }); 7119 } 7120 }, {'modal-close': true, 'class': 'color-3'})} 7121 7122 ${data('multiple', false) ? elements.Render('button', { 7123 label: 'Save', 7124 onClick: (event) => 7125 { 7126 let items = []; 7127 7128 selected.forEach((index) => 7129 { 7130 items.push(values[index]); 7131 }); 7132 7133 onSelect({ 7134 items, 7135 element: event, 7136 parent: false, 7137 mutliple: data('multiple', false) 7138 }); 7139 } 7140 }, {'modal-close': true}) : ''} 7141 </div> 7142 </div> 7143 `; 7144 }; 7145 7146 return this.init(); 7147});elements.RenderCreate('repeater.list', function(addon, data, attributes) 7148{ 7149 let id = dh.increaser('elements.repeater'); 7150 7151 attributes['dh-repeater'] = id; 7152
7153 let config = data('config', {}); 7154 let values = data('values', []); 7155 7156 let onChange = data('onChange'); 7157 let onAdd = data('onAdd'); 7158 let onRemove = data('onRemove'); 7159 let onEdit = data('onEdit'); 7160 let onSave = data('onSave'); 7161 let onModal = data('onModal'); 7162 let onSort = data('onSort'); 7163 let onDisplay = data('onDisplay'); 7164 let onClick = data('onClick'); 7165 7166 this.init = () => 7167 { 7168 let html = ''; 7169 7170 html += this.items(); 7171 html += this.actions(); 7172 7173 return html; 7174 }; 7175 7176 this.items = () => 7177 { 7178 let html = ''; 7179 7180 values.forEach((value, index) => 7181 { 7182 let actions = []; 7183 7184 if(onEdit) 7185 { 7186 actions.push(elements.Render('button', { 7187 icon: 'https://global.divhunt.com/975d316db2ab3af0800f508f59feb304_496.svg', 7188 attributes: { 7189 class: 'action-edit', 7190 }, 7191 onClick: (event) => 7192 { 7193 setTimeout(() => 7194 { 7195 this.actionEdit(index, config, event.target); 7196 }); 7197 } 7198 })); 7199 } 7200 7201 if(onRemove) 7202 { 7203 actions.push(elements.Render('button', { 7204 icon: 'https://global.divhunt.com/27fd4b231c400730bb7380b9d6d68923_371.svg', 7205 attributes: { 7206 class: 'action-remove' 7207 }, 7208 onClick: (event) => 7209 { 7210 this.actionRemove(index); 7211 } 7212 })); 7213 } 7214 7215 html += elements.Render('holder', { 7216 html: ` 7217 <div class="holder-prevent dh-sort-handle reorder" style="${onSort ? '' : 'display: none'}"> 7218 <img src="https://global.divhunt.com/24519ef31071c7dd6e3be117d14e021b_391.svg" loading="lazy" alt="" class="t734"> 7219 </div> 7220 <div class="values" style="${onSort ? '' : 'padding-left: 10px'}"> 7221 ${this.itemValues(value, index)} 7222 </div> 7223 <div class="holder-prevent actions">${actions.join('')}</div> 7224 `, 7225 onClick: (event) => 7226 { 7227 if(onClick) 7228 { 7229 onClick({item: value, index, values, element: event, edit: () => 7230 { 7231 this.actionEdit(index, config, event.target); 7232 }}); 7233 } 7234 } 7235 }, {class: 'row item', 'data-index': index}) 7236 }); 7237 7238 return `<div class="dh-elements-grid items ${this.sort()}">${html}</div>`; 7239 }; 7240 7241 this.itemValues = (values, index) => 7242 { 7243 if(onDisplay) 7244 { 7245 return onDisplay({values, config, index}); 7246 } 7247 7248 let content = []; 7249 7250 $.each(config, (key, setting) => 7251 { 7252 if(['heading', 'line'].includes(setting.type.toLowerCase())) 7253 { 7254 return true; 7255 } 7256 7257 if('preview' in setting && !setting.preview) 7258 { 7259 return true; 7260 } 7261 7262 let value = values[key] ? values[key] : 'value' in setting ? setting.value : null; 7263 let label = 'label' in setting ? setting.label : key; 7264 7265 if(setting.type.toLowerCase() === 'select' && setting.multiple) 7266 { 7267 value = (value + '').split(',').filter(Boolean); 7268 content.push(`<span><b>${label}</b>${'label' in setting ? setting.label : key} [${value.length}]</span>`); 7269 } 7270 else if(Array.isArray(value)) 7271 { 7272 value = Array.isArray(value) ? value : []; 7273 content.push(`<span><b>${label}</b>${'label' in setting ? setting.label : key} [${value.length}]</span>`); 7274 } 7275 else if(value && typeof value === 'object') 7276 { 7277 value = Object.keys(value); 7278 content.push(`<span><b>${label}</b>
7278${'label' in setting ? setting.label : key} [${value.length}]</span>`); 7279 } 7280 else 7281 { 7282 value = value ? value : '['+key+']'; 7283 content.push(`<span><b>${label}</b>${value}</span>`); 7284 } 7285 }); 7286 7287 return content.join(''); 7288 }; 7289 7290 this.actions = () => 7291 { 7292 let actions = []; 7293 7294 if(onAdd) 7295 { 7296 actions.push( 7297 elements.Render('button', { 7298 label: 'Add', 7299 attributes: { 7300 class: 'action-add' 7301 }, 7302 onClick: (event) => 7303 { 7304 event.event.preventDefault(); 7305 this.actionAdd(); 7306 } 7307 }, {class: 'color-3'}) 7308 ); 7309 } 7310 7311 if(onSave) 7312 { 7313 actions.push( 7314 elements.Render('button', { 7315 label: 'Save', 7316 onClick: (event) => 7317 { 7318 /* Save Event */ 7319 if(onSave) { onSave({values, element: event, reload: this.reload()}); } 7320 } 7321 }, {class: 'green', 'modal-close': true}) 7322 ); 7323 } 7324 7325 return '<div class="actions">' + actions.join('') + '</div>'; 7326 }; 7327 7328 this.reload = () => 7329 { 7330 return () => 7331 { 7332 $(`[dh-repeater="${id}"]`).find('> .items').replaceWith(this.items()); 7333 }; 7334 }; 7335 7336 this.actionEdit = (index, config, target = null) => 7337 { 7338 let item = values[index]; 7339 let configClone = $.extend({}, config); 7340 7341 setTimeout(() => 7342 { 7343 elements.Fn('modal.form', configClone, item, (callback) => 7344 { 7345 callback.settings.element = target ? target : null; 7346 callback.settings.align = {x: target ? 'right-in' : 'center', y: target ? 'top-out' : 'center'}; 7347 callback.settings.corners = true; 7348 callback.settings.onOpen = (openEvent) => 7349 { 7350 if(onModal) 7351 { 7352 onModal({item, index, values, settings: openEvent.settings, modal: openEvent.modal}); 7353 } 7354 }; 7355 callback.settings.onSave = (event) => 7356 { 7357 values[index] = event.values; 7358 7359 this.reload()(); 7360 7361 /* Change Event */ 7362 if(onChange) 7363 { 7364 onChange({values: values.filter(Boolean), config, reload: this.reload()}); 7365 } 7366 7367 /* Edit Event */ 7368 if(onEdit) 7369 { 7370 onEdit({item, config, index, values, reload: this.reload()}); 7371 } 7372 } 7373 }); 7374 }); 7375 }; 7376 7377 this.actionRemove = (index) => 7378 { 7379 let item = values[index]; delete values[index]; 7380 7381 values = values.filter(Boolean); 7382 7383 this.reload()(); 7384 7385 if(onChange) 7386 { 7387 onChange({values: values.filter(Boolean), config, reload: this.reload()}); 7388 } 7389 7390 if(onRemove) 7391 { 7392 onRemove({item, index, values, reload: this.reload()}); 7393 } 7394 }; 7395 7396 this.actionAdd = () => 7397 { 7398 let item = {}; 7399 7400 $.each(config, (key, setting) => 7401 { 7402 item[key] = 'value' in setting ? setting.value : null; 7403 }); 7404 7405 values.push(item); 7406 7407 /* Change Event */ 7408 if(onChange) 7409 { 7410 onChange({values: values.filter(Boolean), config, reload: this.reload()}); 7411 } 7412 7413 /* Add Event */ 7414 if(onAdd) 7415 { 7416 onAdd({item, index: values.length - 1, values, reload: this.reload(), edit: () => 7417 { 7418 this.actionEdit(values.length - 1, config); 7419 }}); 7420 } 7421 7422 this.reload()(); 7423 }; 7424 7425 this.sort = () => 7426 { 7427 return elements.Fn('sort', null, (event) => 7428 { 7429 let target = $(event.target); 7430 7431 let newValues = []; 7432 7433 $.each(target.find('> block'), function(index) 7434 { 7435 let order = $(this).attr('data-index'); 7436 7437 $(this).attr('data-index', index); 7438 7439 newValues[index] = values[order]; 7440 }); 7441 7442 values = []; 7443
7444 newValues.forEach((value) => 7445 { 7446 values.push(value); 7447 }); 7448 7449 /* Change Event */ 7450 if(onChange) 7451 { 7452 onChange({values, config, reload: this.reload()}); 7453 } 7454 7455 /* Sort Event */ 7456 if(onSort) 7457 { 7458 onSort({values, config, element: event, reload: this.reload()}); 7459 } 7460 }); 7461 }; 7462 7463 return this.init(); 7464}); 7465 7466elements.RenderCreate('repeater.timeline', function(addon, data, attributes) 7467{ 7468 let id = data('identifier', dh.increaser('elements.repeater')); 7469 7470 attributes['dh-repeater'] = id; 7471 7472 let config = data('config', {}); 7473 let values = data('values', []); 7474 7475 let unit = data('unit', 'second'); 7476 let caption = data('caption', 'actions'); 7477 7478 let onChange = data('onChange'); 7479 let onAdd = data('onAdd'); 7480 let onRemove = data('onRemove'); 7481 let onEdit = data('onEdit'); 7482 let onModal = data('onModal'); 7483 let onMove = data('onMove'); 7484 let onResize = data('onResize'); 7485 let onDisplay = data('onDisplay'); 7486 let onSort = data('onSort'); 7487 7488 this.init = () => 7489 { 7490 let html = ` 7491 <div class="timeline-area" unit="${unit === 'second' ? 'second' : 'percent'}"> 7492 <div class="top"> 7493 <div class="second"> <span> ${unit === 'second' ? '0s' : '0%'} </span> <div class="frames"></div> </div> 7494 <div class="second"> <span> ${unit === 'second' ? '1s' : '10%'} </span> <div class="frames"></div> </div> 7495 <div class="second"> <span> ${unit === 'second' ? '2s' : '20%'} </span> <div class="frames"></div> </div> 7496 <div class="second"> <span> ${unit === 'second' ? '3s' : '30%'} </span> <div class="frames"></div> </div> 7497 <div class="second"> <span> ${unit === 'second' ? '4s' : '40%'} </span> <div class="frames"></div> </div> 7498 <div class="second"> <span> ${unit === 'second' ? '5s' : '50%'} </span> <div class="frames"></div> </div> 7499 <div class="second"> <span> ${unit === 'second' ? '6s' : '60%'} </span> <div class="frames"></div> </div> 7500 <div class="second"> <span> ${unit === 'second' ? '7s' : '70%'} </span> <div class="frames"></div> </div> 7501 <div class="second"> <span> ${unit === 'second' ? '8s' : '80%'} </span> <div class="frames"></div> </div> 7502 <div class="second"> <span> ${unit === 'second' ? '9s' : '90%'} </span> <div class="frames"></div> </div> 7503 <div class="second last"> <span> ${unit === 'second' ? '10s' : '100%'} </span> </div> 7504 </div> 7505 `; 7506 7507 html += this.rows(); 7508 html += this.actions(); 7509 7510 html += `</div>`; 7511 7512 this.move(); 7513 this.resize(); 7514 7515 return html; 7516 }; 7517 7518 this.move = () => 7519 { 7520 elements.Fn('timeline.move', (event) => 7521 { 7522 values[event.index].delay = event.delay; 7523 7524 /* Change Event */ 7525 if(onChange) 7526 { 7527 onChange({values: values.filter(Boolean), config, reload: this.reload()}); 7528 } 7529 7530 /* Move Event */ 7531 if(onMove) 7532 { 7533 onMove({item: values[event.index], index: event.index, values, config, element: event, reload: this.reload()}); 7534 } 7535 }); 7536 }; 7537 7538 this.resize = () => 7539 { 7540 elements.Fn('timeline.resize', (event) => 7541 { 7542 values[event.index].delay = event.delay; 7543 values[event.index].duration = event.duration; 7544 7545 /* Change Event */ 7546 if(onChange) 7547 { 7548 onChange({values: values.filter(Boolean), config, reload: this.reload()}); 7549 } 7550 7551 /* Resize Event */ 7552 if(onResize) 7553 { 7554 onResize({item: values[event.index], index: event.index, values, config, element: event, reload: this.reload()}); 7555 } 7556 }); 7557 }; 7558 7559 this.rows = () => 7560 { 7561 let html = ''; 7562
7563 values.forEach((value, index) => 7564 { 7565 if(value === null) 7566 { 7567 delete values[index]; return true; 7568 } 7569 7570 let delay = 'delay' in value ? parseInt(value.delay) : 0; 7571 let duration = 'duration' in value ? parseInt(value.duration) : 1000; 7572 7573 let resizers = ` 7574 <div class="resize"> <line></line> <line></line> </div> 7575 <div class="resize left"><line></line><line></line></div> 7576 `; 7577 let duration_class = ''; 7578 7579 if(!('duration' in value)) 7580 { 7581 resizers = ''; 7582 duration_class = ' no-duration' 7583 } 7584 7585 html += ` 7586 <div class="timeline-row" data-timeline="${id}" data-index="${index}"> 7587 <div class="timeline-item-left"> 7588 <div class="holder-prevent dh-sort-handle reorder"> 7589 <img src="https://global.divhunt.com/24519ef31071c7dd6e3be117d14e021b_391.svg" loading="lazy" alt="" class="t734"> 7590 </div> 7591 7592 ${elements.Render('holder', { 7593 value: onDisplay ? onDisplay({value, config}) : ` 7594 <div> 7595 <img src="https://global.divhunt.com/a889b0be003ffd070faeb5176f593156_267.svg"> 7596 <span> Action name </span> 7597 </div> 7598 `, 7599 onClick: (event) => 7600 { 7601 this.actionEdit(index, config, event.target); 7602 } 7603 })} 7604 7605 <div class="actions"> 7606 ${elements.Render('dropdown', { 7607 style: 'minimal', 7608 value: `<button> <img src="https://global.divhunt.com/5e7b5f5c24ed54bed73de9e091a66d02_382.svg"> </button>`, 7609 values: () => 7610 { 7611 return [ 7612 {title: 'Edit', value: 'edit'}, 7613 {title: 'Remove', value: 'remove'}, 7614 ]; 7615 }, 7616 onSelect: (event) => 7617 { 7618 if(event.items.length && event.items[0].value === 'edit') 7619 { 7620 this.actionEdit(index, config, event.events.select.target); 7621 } 7622 7623 if(event.items.length && event.items[0].value === 'remove') 7624 { 7625 this.actionRemove(index); 7626 } 7627 } 7628 })} 7629 </div> 7630 </div> 7631 7632 <div class="timeline-line-wrapper"> 7633 <div class="timeline-line${duration_class}"> 7634 <div class="timeline-item" data-index="${index}" style="width: ${duration / 100}%; left: ${delay ? (delay / 100) : 0}%"> 7635 ${resizers} 7636 <div class="delay"> <span> 0ms </span> </div> 7637 <div class="duration"> <span> 100ms </span> </div> 7638 </div> 7639 </div> 7640 </div> 7641 </div> 7642 `; 7643 }); 7644 7645 return ` 7646 <div class="timeline-rows dh-elements-scrollbar ${this.sort()}"> 7647 ${html} 7648 7649 <div class="actions-empty"> 7650 <p> Timeline has currently no ${caption.toLowerCase()}. </p> 7651 ${elements.Render('button', { 7652 label: `Create`, 7653 attributes: { 7654 class: 'action-add' 7655 }, 7656 onClick: (event) => 7657 { 7658 event.event.preventDefault(); 7659 this.actionAdd(); 7660 } 7661 }, {}) 7662 } 7663 </div> 7664 </div> 7665 `; 7666 }; 7667 7668 this.actions = () => 7669 { 7670 let actions = []; 7671 7672 if(onAdd) 7673 { 7674 actions.push( 7675 elements.Render('button', { 7676 icon: 'https://global.divhunt.com/931f5317ab81023f64a81a7740b48564_475.svg', 7677 attributes: { 7678 class: 'action-add' 7679 }, 7680 onClick: (event) => 7681 { 7682 event.event.preventDefault(); 7683 this.actionAdd(); 7684 } 7685 }, {class: 'color-2'}) 7686 ); 7687 } 7688 7689 return ` 7690 <div class="actions"> 7691 <div class="left"> <span> ${caption}
7691 </span> </div> 7692 <div class="right"> ${actions.join('')} </div> 7693 </div> 7694 `; 7695 }; 7696 7697 this.reload = () => 7698 { 7699 return () => 7700 { 7701 $(`[dh-repeater="${id}"]`).find('.timeline-rows').replaceWith(this.rows()); 7702 }; 7703 }; 7704 7705 this.actionEdit = (index, config, target = null) => 7706 { 7707 let item = values[index]; 7708 let configClone = $.extend({}, config); 7709 7710 setTimeout(() => 7711 { 7712 elements.Fn('modal.form', configClone, item, (callback) => 7713 { 7714 callback.settings.element = target ? target : null; 7715 callback.settings.align = {x: target ? 'right-in' : 'center', y: target ? 'top-out' : 'center'}; 7716 callback.settings.corners = true; 7717 callback.settings.onOpen = (openEvent) => 7718 { 7719 if(onModal) 7720 { 7721 onModal({item, index, values, settings: openEvent.settings, modal: openEvent.modal}); 7722 } 7723 }; 7724 callback.settings.onSave = (event) => 7725 { 7726 values[index] = event.values; 7727 7728 this.reload()(); 7729 7730 /* Change Event */ 7731 if(onChange) 7732 { 7733 onChange({values: values.filter(Boolean), config, reload: this.reload()}); 7734 } 7735 7736 /* Edit Event */ 7737 if(onEdit) 7738 { 7739 onEdit({item, config, index, values, reload: this.reload()}); 7740 } 7741 } 7742 }); 7743 }); 7744 }; 7745 7746 this.actionRemove = (index) => 7747 { 7748 let item = values[index]; delete values[index]; 7749 7750 values = values.filter(Boolean); 7751 7752 this.reload()(); 7753 7754 if(onChange) 7755 { 7756 onChange({values: values.filter(Boolean), config, reload: this.reload()}); 7757 } 7758 7759 if(onRemove) 7760 { 7761 onRemove({item, index, values, reload: this.reload()}); 7762 } 7763 }; 7764 7765 this.actionAdd = () => 7766 { 7767 let item = {}; 7768 7769 $.each(config, (key, setting) => 7770 { 7771 item[key] = 'value' in setting ? setting.value : null; 7772 }); 7773 7774 values.push(item); 7775 7776 /* Change Event */ 7777 if(onChange) 7778 { 7779 onChange({values: values.filter(Boolean), config, reload: this.reload()}); 7780 } 7781 7782 /* Add Event */ 7783 if(onAdd) 7784 { 7785 onAdd({item, index: values.length - 1, values, reload: this.reload(), edit: () => 7786 { 7787 this.actionEdit(values.length - 1, config); 7788 }}); 7789 } 7790 7791 this.reload()(); 7792 }; 7793 7794 this.sort = () => 7795 { 7796 return elements.Fn('sort', null, (event) => 7797 { 7798 let target = $(event.target); 7799 let newValues = []; 7800 7801 $.each(target.find('> .timeline-row'), function(index) 7802 { 7803 newValues[index] = values[$(this).attr('data-index')]; 7804 }); 7805 7806 values = []; 7807 7808 newValues.forEach((value) => 7809 { 7810 values.push(value); 7811 }); 7812 7813 target.replaceWith(this.rows()); 7814 7815 /* Change Event */ 7816 if(onChange) 7817 { 7818 onChange({values, config, reload: this.reload()}); 7819 } 7820 7821 /* Sort Event */ 7822 if(onSort) 7823 { 7824 onSort({values, config, element: event, reload: this.reload()}); 7825 } 7826 }); 7827 } 7828 7829 return this.init(); 7830}); 7831 7832 7833elements.RenderCreate('select', function(addon, data) 7834{ 7835 let id = dh.increaser('elements.input'); 7836 7837 elements.Fn('callbacks.add', 'input', 'click', id, data('onClick')); 7838 elements.Fn('callbacks.add', 'input', 'input', id, data('onInput')); 7839 elements.Fn('callbacks.add', 'input', 'change', id, data('onChange')); 7840 elements.Fn('callbacks.add', 'input', 'select', id, data('onSelect')); 7841 7842 let attributes = data('attributes', {}); 7843 let onSelect = data('onSelect'); 7844 7845 attributes['dh-input'] = id; 7846 attributes['dh-select'] = true; 7847 7848 this.init = () => 7849 { 7850 this.list(); 7851 this.placeholder(); 7852 this.value(); 7853 this.valueOverlay(); 7854 7855 return ` 7856 <input ${dh.attributes(attributes)}> 7857 <img class="caret" src="https://global.divhunt.com/15975a368079082940619f4cfe5937d1_238.svg"> 7858 <div class="value"></div> 7859 `; 7860 }; 7861 7862 this.list = () => 7863 { 7864 elements.Fn('callbacks.add', 'input', 'click', id, (event) => 7865 { 7866 event.target.val(''); 7867 event.target.parent().find('> .value').css('opacity', 0); 7868 7869 let values = data('values'); 7870 7871 if(typeof values === 'function') 7872 { 7873 values = values(); 7874 } 7875 7876 if(values instanceof Promise) 7877 { 7878 values.then((values) => 7879 { 7880 elements.Fn('modal.list', values, event.target, event.target.val(), data('multiple', false), this.listCallback(event), (settings) => 7881 { 7882 settings.onClose = () => 7883 { 7884 event.target.parent().find('> .value').css('opacity', ''); 7885 } 7886 }); 7887 }); 7888 } 7889 else 7890 { 7891 elements.Fn('modal.list', values, event.target, event.target.val(), data('multiple', false), this.listCallback(event), (settings) => 7892 { 7893 settings.onClose = () => 7894 { 7895 event.target.parent().find('> .value').css('opacity', ''); 7896 } 7897 }); 7898 } 7899 }); 7900 7901 elements.Fn('callbacks.add', 'input', 'input', id, (event) => 7902 { 7903 dh.debounce(() => 7904 { 7905 let values = data('values'); 7906 7907 if(typeof values === 'function') 7908 { 7909 values = values(); 7910 } 7911 7912 if(values instanceof Promise) 7913 { 7914 values.then((values) => 7915 { 7916 elements.Fn('modal.list', this.search(values, event.value), event.target, event.value, data('multiple', false), this.listCallback(event), (settings) => 7917 { 7918 settings.onClose = () => 7919 { 7920 event.target.parent().find('> .value').css('opacity', ''); 7921 } 7922 }); 7923 }); 7924 } 7925 else 7926 { 7927 7928 elements.Fn('modal.list', this.search(values, event.value), event.target, event.value, data('mutliple', false), this.listCallback(event), (settings) => 7929 { 7930 settings.onClose = () => 7931 { 7932 event.target.parent().find('> .value').css('opacity', ''); 7933 } 7934 }); 7935 } 7936 }, 100); 7937 }); 7938 }; 7939 7940 this.search = (values, search) => 7941 { 7942 if(!search) 7943 { 7944 return values; 7945 } 7946
7947 values.forEach((value, index) => 7948 { 7949 let group = 'group' in value ? (value.group + '').toLowerCase() : ''; 7950 let title = 'title' in value ? (value.title + '').toLowerCase() : ''; 7951 let description = 'description' in value ? (value.description + '').toLowerCase() : ''; 7952 7953 if(!group.includes(search) && !title.includes(search) && !description.includes(search)) 7954 { 7955 values[index].hidden = true; 7956 } 7957 else 7958 { 7959 values[index].hidden = false; 7960 } 7961 }); 7962 7963 return values; 7964 }; 7965 7966 this.listCallback = (selectEvent) => 7967 { 7968 return (listEvent) => 7969 { 7970 if(onSelect) 7971 { 7972 onSelect({ 7973 items: listEvent.items, 7974 events: { 7975 select: selectEvent, 7976 list: listEvent 7977 }, 7978 multiple: data('multiple', false) 7979 }); 7980 } 7981 7982 let values = []; 7983 7984 listEvent.items.forEach((item) => 7985 { 7986 values.push(item.value); 7987 }); 7988 7989 if(data('multiple', false)) 7990 { 7991 selectEvent.target.parent().find('> .value').text(values.length === 1 ? '1 Item Selected' : !values.length ? '' : (values.length + ' Items Selected')); 7992 } 7993 else 7994 { 7995 selectEvent.target.parent().find('> .value').text(listEvent.items.length ? listEvent.items[0].title : ''); 7996 } 7997 7998 selectEvent.target.parent().find('> .value').css('opacity', ''); 7999 selectEvent.target.val(values.join(',')).trigger('change'); 8000 }; 8001 }; 8002 8003 this.value = () => 8004 { 8005 let value = data('value'); 8006 8007 if(value && (typeof value === 'string' || typeof value === 'number')) 8008 { 8009 attributes.value = value; 8010 } 8011 }; 8012 8013 this.valueOverlay = () => 8014 { 8015 setTimeout(() => 8016 { 8017 let value = ((data('value') ? data('value') : '') + '').split(',').filter(Boolean); 8018 let values = data('values'); 8019 8020 if(data('multiple', false)) 8021 { 8022 if(value.length > 0) 8023 { 8024 $(`[dh-input="${id}"]`).parent().find('> .value').text(value.length === 1 ? '1 Item Selected' : (value.length + ' Items Selected')); 8025 } 8026 8027 return; 8028 } 8029 8030 if(typeof values === 'function') 8031 { 8032 values = values(); 8033 } 8034 8035 let check = (values) => 8036 { 8037 let find = null; 8038 8039 values.forEach((item) => 8040 { 8041 if(value.includes(item.value + '')) 8042 { 8043 find = item; 8044 } 8045 }); 8046 8047 $(`[dh-input="${id}"]`).parent().find('> .value').text(find ? find.title : ''); 8048 }; 8049 8050 if(values instanceof Promise) 8051 { 8052 $(`[dh-input="${id}"]`).parent().find('> .value').text('Searching Item...'); 8053 8054 values.then((values) => 8055 { 8056 check(values); 8057 }); 8058 } 8059 else 8060 { 8061 check(values); 8062 } 8063 }); 8064 }; 8065 8066 this.placeholder = () => 8067 { 8068 let placeholder = data('placeholder'); 8069 8070 if(placeholder && (typeof placeholder === 'string' || typeof placeholder === 'number')) 8071 { 8072 attributes.placeholder = placeholder; 8073 } 8074 }; 8075 8076 return this.init(); 8077});elements.RenderCreate('textarea', function(addon, data) 8078{ 8079 let id = dh.increaser('elements.textarea'); 8080 8081 elements.Fn('callbacks.add', 'textarea', 'click', id, data('onClick')); 8082 elements.Fn('callbacks.add', 'textarea', 'input', id, data('onInput')); 8083 elements.Fn('callbacks.add', 'textarea', 'change', id, data('onChange')); 8084 8085 let attributes = data('attributes', {}); 8086 8087 attributes['dh-elements-textarea'] = id;
8088 attributes['class'] = 'class' in attributes ? (attributes['class'] += ' dh-elements-scrollbar') : 'dh-elements-scrollbar'; 8089 8090 this.init = () => 8091 { 8092 this.placeholder(); 8093 8094 return ` 8095 <textarea ${dh.attributes(attributes)}>${this.value()}</textarea> 8096 ${this.characters()} 8097 `; 8098 }; 8099 8100 this.value = () => 8101 { 8102 let value = data('value'); 8103 8104 if(value && (typeof value === 'string' || typeof value === 'number')) 8105 { 8106 return value; 8107 } 8108 8109 return ''; 8110 }; 8111 8112 this.placeholder = () => 8113 { 8114 let placeholder = data('placeholder'); 8115 8116 if(placeholder && (typeof placeholder === 'string' || typeof placeholder === 'number')) 8117 { 8118 attributes.placeholder = placeholder; 8119 } 8120 }; 8121 8122 this.prefix = (element) => 8123 { 8124 if(!('prefix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 8125 { 8126 return; 8127 } 8128 8129 element.prepend(`<span class="prefix">${setting.prefix}</span>`); 8130 element.find('input').css('padding-left', element.find('.prefix').outerWidth() + 10); 8131 }; 8132 8133 this.suffix = (element) => 8134 { 8135 if(!('suffix' in setting) || (typeof setting.prefix !== 'string' && typeof setting.prefix !== 'number')) 8136 { 8137 return; 8138 } 8139 8140 element.append(`<span class="suffix">${setting.suffix}</span>`); 8141 element.find('input').css('padding-right', element.find('.suffix').outerWidth() + 10); 8142 }; 8143 8144 this.characters = (element) => 8145 { 8146 let characters = data('characters', '').split('/'); 8147 8148 if(characters.length !== 2) 8149 { 8150 return ''; 8151 } 8152 8153 if(typeof parseInt(characters[0]) !== 'number' || typeof parseInt(characters[1]) !== 'number') 8154 { 8155 return ''; 8156 } 8157 8158 elements.Fn('callbacks.add', 'input', 'input', id, (value, target) => 8159 { 8160 let length = value.length; 8161 8162 if(length > characters[1]) 8163 { 8164 target.parent().find('.characters').addClass('error'); 8165 } 8166 else if(length < characters[0]) 8167 { 8168 target.parent().find('.characters').addClass('error'); 8169 } 8170 else 8171 { 8172 target.parent().find('.characters').removeClass('error'); 8173 } 8174 8175 target.parent().find('.characters').html(` 8176 ${length} / ${characters[1]} 8177 `); 8178 }); 8179 8180 return ` 8181 <div class="characters"> 8182 ${'value' in attributes ? attributes.value.length : 0} / ${characters[1]} 8183 </div> 8184 `; 8185 }; 8186 8187 this.regex = (element) => 8188 { 8189 if(!('regex' in setting) && typeof setting.regex !== 'string') 8190 { 8191 return; 8192 } 8193 8194 let regex = new RegExp('^' + setting.regex + '$'); 8195 8196 callbacks.input.push((input) => 8197 { 8198 let value = input.val(); 8199 8200 if(value && !regex.test(value)) 8201 { 8202 input.parent().addClass('error'); 8203 } 8204 else 8205 { 8206 input.parent().removeClass('error'); 8207 } 8208 }); 8209 }; 8210 8211 return this.init(); 8212}); 8213mdFunctions.SetReload(false); 8214 8215mdFunctions.FieldAdd('function'); 8216mdFunctions.FieldAdd('parameters'); 8217mdFunctions.FieldAdd('description'); 8218mdFunctions.FieldAdd('group'); 8219 8220mdFunctions.Ready(); 8221 8222function fnc(id, data, ...rest) 8223{ 8224 let fnc = mdFunctions.ItemGet(id); 8225 8226 if(fnc) 8227 { 8228 return fnc.Get('function')(data, ...rest); 8229 } 8230 8231 return null; 8232}mdFunctions.ItemsAdd({ 8233 id: 'reverse', 8234 parameters: { 8235 8236 }, 8237 description: 'Reverse array values.', 8238 group: 'array', 8239 function: function(data, value) 8240 { 8241 if(Array.isArray(value)) 8242 { 8243 return value.slice().reverse(); 8244 } 8245 8246 return value; 8247 } 8248}, { 8249 id: 'pop', 8250 parameters: { 8251 8252 }, 8253 description: 'Remove last value from array.', 8254 group: 'array', 8255 function: function(data, value) 8256 { 8257 if(Array.isArray(value)) 8258 { 8259 value.pop(); 8260 } 8261 8262 return value; 8263 } 8264}, { 8265 id: 'join', 8266 parameters: { 8267 separator: {type: 'INPUT', 'value': ''}, 8268 }, 8269 description: 'Join array values into string.', 8270 group: 'array', 8271 function: function(data, value, separator) 8272 { 8273 if(Array.isArray(value)) 8274 { 8275 return value.join(separator); 8276 } 8277 8278 return value; 8279 } 8280}, { 8281 id: 'index', 8282 parameters: { 8283 position: {type: 'INPUT', 'value': '0'}, 8284 }, 8285 description: 'Get specific array index position.', 8286 group: 'array', 8287 function: function(data, value, position) 8288 { 8289 if(Array.isArray(value)) 8290 { 8291 return position in value ? value[position] : null; 8292 } 8293 8294 return value; 8295 } 8296});mdFunctions.ItemsAdd({ 8297 id: 'length', 8298 parameters: {}, 8299 description: 'Count array or string length.', 8300 group: ['Object', 'String'], 8301 function: function(data, value) 8302 { 8303 if(Array.isArray(value)) 8304 { 8305 return value.length; 8306 } 8307 8308 if(typeof value === 'string') 8309 { 8310 return value.length; 8311 } 8312 8313 return 0; 8314 } 8315});mdFunctions.ItemsAdd({ 8316 id: 'get', 8317 parameters: { 8318 key: {type: 'INPUT', 'value': 'key'}, 8319 default: {type: 'INPUT', 'value': ''}, 8320 }, 8321 description: 'Get object key.', 8322 group: 'Object', 8323 function: function(data, value, key, _default = null) 8324 { 8325 if(!value || typeof value !== 'object') 8326 { 8327 return _default; 8328 } 8329 8330 if(!(key in value) || value[key] === null) 8331 { 8332 return _default; 8333 } 8334 8335 return value[key]; 8336 } 8337}, { 8338 id: 'encode', 8339 parameters: {}, 8340 description: 'Converts object to string.', 8341 group: 'Object', 8342 function: function(data, value, index) 8343 { 8344 if(!value || typeof value !== 'object') 8345 { 8346 return null; 8347 } 8348 8349 return JSON.stringify(value); 8350 } 8351});mdFunctions.ItemsAdd({ 8352 id: 'charAt', 8353 parameters: { 8354 index: {type: 'INPUT', 'value': 0}, 8355 }, 8356 description: 'Returns character at specific position.', 8357 group: 'String', 8358 function: function(data, value, index) 8359 { 8360 if(typeof value !== 'string') 8361 { 8362 return null; 8363 } 8364 8365 return value.charAt(index); 8366 } 8367}, { 8368 id: 'toLowerCase', 8369 parameters: { 8370 8371 }, 8372 description: 'Converts string to lower case.', 8373 group: 'String', 8374 function: function(data, value) 8375 { 8376 if(typeof value !== 'string') 8377 { 8378 return value; 8379 } 8380 8381 return value.toLowerCase(); 8382 } 8383}, { 8384 id: 'split', 8385 parameters: { 8386 separator: {type: 'INPUT', 'value': ''}, 8387 }, 8388 description: 'Split string with separator.', 8389 group: 'String', 8390 function: function(data, value, separator) 8391 { 8392 if(typeof value !== 'string') 8393 { 8394 return value; 8395 } 8396 8397 return value.split(separator); 8398 } 8399}, { 8400 id: 'replace', 8401 parameters: { 8402 pattern: {type: 'INPUT', 'value': ''}, 8403 keyword: {type: 'INPUT', 'value': ''}, 8404 }, 8405 description: 'Replace pattern with keyword.', 8406 group: 'String', 8407 function: function(data, value, pattern, keyword) 8408 { 8409 if(typeof value !== 'string') 8410 { 8411 return value; 8412 } 8413 8414 return value.replaceAll(pattern, keyword); 8415 } 8416}, { 8417 id: 'date', 8418 parameters: { 8419 FormData: {type: 'INPUT', 'value': ''}, 8420 }, 8421 description: 'Convert timestamp into date format.', 8422 group: 'String', 8423 function: function(data, value, format = 'D M, Y') 8424 { 8425 return dh.date(value, format); 8426 } 8427}, { 8428 id: 'slug', 8429 parameters: {}, 8430 description: 'Convert string such as title or name into slug.', 8431 group: 'String', 8432 function: function(data, value) 8433 { 8434 return dh.slug(value); 8435 } 8436}, { 8437 id: 'markdown', 8438 parameters: {}, 8439 description: 'Convert markdown language to HTML.', 8440 group: 'String', 8441 function: function(data, value) 8442 { 8443 if(typeof value !== 'string') 8444 { 8445 return value; 8446 } 8447 8448 return new Promise((resolve) => 8449 { 8450 mdLibraries.Fn('load', ['markdown'], () => 8451 { 8452 resolve(new markdownit().render(value)); 8453 }); 8454 }); 8455 } 8456}); 8457mdActions.SetReload(false); 8458 8459mdActions.FieldAdd('name'); 8460mdActions.FieldAdd('description'); 8461mdActions.FieldAdd('group'); 8462mdActions.FieldAdd('code'); 8463mdActions.FieldAdd('variable'); 8464mdActions.FieldAdd('duration'); 8465mdActions.FieldAdd('initial'); 8466mdActions.FieldAdd('target'); 8467mdActions.FieldAdd('manual'); 8468mdActions.FieldAdd('icon'); 8469mdActions.FieldAdd('play'); 8470mdActions.FieldAdd('loop'); 8471 8472mdActions.FieldAdd('config', (value) => 8473{ 8474 if(typeof value === 'function') 8475 { 8476 value = value(); 8477 } 8478 8479 return value && typeof value === 'object' ? value : {}; 8480}); 8481 8482mdActions.Ready();mdActions.OnReady(function(addon) 8483{ 8484 mdActions.ItemAdd({ 8485 id: 'animate', 8486 name: 'Animate', 8487 icon: 'https://global.divhunt.com/b0b6cc9986ff1d3b0f9152931778d597_668.svg', 8488 description: 'Animate CSS property based on scroll percent using native JavaScript', 8489 initial: true, 8490 duration: true, 8491 manual: true, 8492 target: true, 8493 play: true, 8494 loop: true, 8495 config: () => ({ 8496 properties: { 8497 type: 'REPEATER', 8498 order: 20, 8499 label: 'CSS Properties', 8500 onAdd: event => event.edit(), 8501 onEdit: () => {}, 8502 onRemove: () => {}, 8503 config: { 8504 property: { 8505 label: 'CSS Property', 8506 width: 4, 8507 type: 'SELECT', 8508 value: 'opacity', 8509 values: () => 8510 { 8511 return [ 8512 // Opacity 8513 {title: 'Opacity', value: 'opacity', group: 'Opacity'}, 8514 8515 // Transformations (merged under transform) 8516 {title: 'X Position', value: 'translateX', group: 'Transformations'}, 8517 {title: 'Y Position', value: 'translateY', group: 'Transformations'}, 8518 {title: 'Scale', value: 'scale', group: 'Transformations'}, 8519 {title: 'Rotation', value: 'rotate', group: 'Transformations'}, 8520 {title: 'Rotation X', value: 'rotateX', group: 'Transformations'}, 8521 {title: 'Rotation Y', value: 'rotateY', group: 'Transformations'}, 8522 {title: 'Skew X', value: 'skewX', group: 'Transformations'}, 8523 {title: 'Skew Y', value: 'skewY', group: 'Transformations'}, 8524 8525 // Sizing 8526 {title: 'Width', value: 'width', group: 'Sizing'}, 8527 {title: 'Height', value: 'height', group: 'Sizing'}, 8528 8529 // Typography 8530 {title: 'Font size', value: 'fontSize', group: 'Typography'}, 8531 {title: 'Font weight', value: 'fontWeight', group: 'Typography'}, 8532 {title: 'Line height', value: 'lineHeight', group: 'Typography'}, 8533 {title: 'Letter spacing', value: 'letterSpacing', group: 'Typography'}, 8534 8535 // Filters 8536 {title: 'Blur', value: 'blur', group: 'Filters'}, 8537 {title: 'Brightness', value: 'brightness', group: 'Filters'}, 8538 {title: 'Contrast', value: 'contrast', group: 'Filters'}, 8539 {title: 'Grayscale', value: 'grayscale', group: 'Filters'}, 8540 8541 // Colors
8542 {title: 'Background color', value: 'backgroundColor', group: 'Colors'}, 8543 {title: 'Color', value: 'color', group: 'Colors'}, 8544 {title: 'Border color', value: 'borderColor', group: 'Colors'}, 8545 8546 // Position 8547 {title: 'Top', value: 'top', group: 'Position'}, 8548 {title: 'Left', value: 'left', group: 'Position'}, 8549 {title: 'Right', value: 'right', group: 'Position'}, 8550 {title: 'Bottom', value: 'bottom', group: 'Position'}, 8551 8552 // Margins 8553 {title: 'Margin top', value: 'marginTop', group: 'Margin'}, 8554 {title: 'Margin right', value: 'marginRight', group: 'Margin'}, 8555 {title: 'Margin bottom', value: 'marginBottom', group: 'Margin'}, 8556 {title: 'Margin left', value: 'marginLeft', group: 'Margin'}, 8557 8558 // Padding 8559 {title: 'Padding top', value: 'paddingTop', group: 'Padding'}, 8560 {title: 'Padding right', value: 'paddingRight', group: 'Margin'}, 8561 {title: 'Padding bottom', value: 'paddingBottom', group: 'Margin'}, 8562 {title: 'Padding left', value: 'paddingLeft', group: 'Margin'}, 8563 ]; 8564 }, 8565 configure: (data) => 8566 { 8567 if(['top', 'left', 'right', 'bottom', 'translateX', 'translateY'].includes(data.values.property)) 8568 { 8569 data.config.from.units = ['px', 'em', 'rem', '%', 'vh', 'vw']; 8570 data.config.to.units = ['px', 'em', 'rem', '%', 'vh', 'vw']; 8571 } 8572 else if(['width', 'height'].includes(data.values.property)) 8573 { 8574 data.config.from.units = ['px', 'em', 'rem', '%', 'vh', 'vw', 'dvh']; 8575 data.config.to.units = ['px', 'em', 'rem', '%', 'vh', 'vw', 'dvh']; 8576 } 8577 else if(['fontSize', 'lineHeight', 'letterSpacing'].includes(data.values.property)) 8578 { 8579 data.config.from.units = ['px', 'em', 'rem']; 8580 data.config.to.units = ['px', 'em', 'rem']; 8581 } 8582 else if(['rotate', 'rotateX', 'rotateY', 'skewX', 'skewY'].includes(data.values.property)) 8583 { 8584 data.config.from.units = ['deg', 'rad']; 8585 data.config.to.units = ['deg', 'rad']; 8586 } 8587 else if(['margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left'].includes(data.values.property)) 8588 { 8589 data.config.from.units = ['px', 'em', 'rem', '%']; 8590 data.config.to.units = ['px', 'em', 'rem', '%']; 8591 } 8592 else if(['brightness', 'contrast', 'grayscale', 'opacity'].includes(data.values.property)) 8593 { 8594 data.config.from.units = ['%']; 8595 data.config.to.units = ['%']; 8596 } 8597 else if(data.values.property === 'blur') 8598 { 8599 data.config.from.units = ['px']; 8600 data.config.to.units = ['px']; 8601 } 8602 else 8603 { 8604 delete data.config.from.units; 8605 delete data.config.to.units; 8606 } 8607 }, 8608 whenChanged: (event) => 8609 { 8610 event.reload(); 8611 } 8612 }, 8613 from: {label: 'From Value', width: 4, type: 'INPUT'}, 8614 to: {label: 'To Value', width: 4, type: 'INPUT' }, 8615 } 8616 }, 8617 easing: { 8618 label: 'Easing', 8619 width: 4, 8620 type: 'SELECT', 8621 value: 'linear', 8622 values: () => 8623 { 8624 return [ 8625 {title: 'Linear', value: 'linear', group: 'Basic Easing', description: 'Constant speed from start to end.'}, 8626 {title: 'Ease', value: 'ease', group: 'Basic Easing', description: 'Starts slow, speeds up in the middle, and slows down at the end.'}, 8627 {title: 'Ease In', value: 'ease-in', group: 'Basic Easing', description: 'Starts slow and accelerates towards the end.'}, 8628 {title: 'Ease Out', value: 'ease-out', group: 'Basic Easing', description: 'Starts fast and decelerates towards the end.'}, 8629 {title: 'Ease In-Out', value: 'ease-in-out', group: 'Basic Easing', description: 'Starts slow, speeds up in the middle, and slows down at the end.'}, 8630 8631 {title: 'Custom Bezier', value: 'cubic-bezier', group: 'Cubic Bezier', description: 'Custom easing curve defined by control points.'}, 8632 {title: 'Ease (Bezier)', value: 'cubic-bezier(0.25, 0.1, 0.25, 1)', group: 'Cubic Bezier', description: 'Default cubic bezier for ease.'}, 8633 {title: 'Ease In (Bezier)', value: 'cubic-bezier(0.42, 0, 1, 1)', group: 'Cubic Bezier', description: 'Cubic bezier easing for ease-in.'}, 8634 {title: 'Ease Out (Bezier)', value: 'cubic-bezier(0, 0, 0.58, 1)', group: 'Cubic Bezier', description: 'Cubic bezier easing for ease-out.'},
8635 {title: 'Ease In-Out (Bezier)', value: 'cubic-bezier(0.42, 0, 0.58, 1)', group: 'Cubic Bezier', description: 'Cubic bezier easing for ease-in-out.'}, 8636 8637 {title: 'Step Start', value: 'step-start', group: 'Steps', description: 'Jumps instantly to the end state at the start.'}, 8638 {title: 'Step End', value: 'step-end', group: 'Steps', description: 'Holds the start state until the end, then jumps to the final state.'}, 8639 {title: 'Steps (5)', value: 'steps(5, start)', group: 'Steps', description: 'Divides the animation into 5 equal steps starting immediately.'}, 8640 ]; 8641 } 8642 } 8643 }), 8644 code: function(data, resolve, reject, variable, identifier) 8645 { 8646 this.delay = parseInt(data.values.delay ? data.values.delay : 0); 8647 this.duration = parseInt(data.values.duration ? data.values.duration : 100); 8648 this.target = data.target, 8649 this.easing = data.values.easing ? data.values.easing : 'linear', 8650 this.percentFrom = null; 8651 this.percentTo = null; 8652 8653 this.init = () => 8654 { 8655 if(!this.target) 8656 { 8657 return reject('No target provided.'); 8658 } 8659 8660 if(['dh.scroll', 'dh.scrolling'].includes(data.event.name)) 8661 { 8662 this.percentFrom = this.delay / 100; 8663 this.percentTo = this.percentFrom + (this.duration / 100); 8664 } 8665 8666 data.action.initial ? this.initialState() : this.animate(); 8667 }; 8668 8669 this.initialState = () => 8670 { 8671 let [fromKeyframe] = this.fromTo(); 8672 8673 this.target[0].animate([fromKeyframe], { duration: 0, fill: 'forwards' }); 8674 }; 8675 8676 this.play = () => 8677 { 8678 let playing = mdActions.GetTemp('playing.' + identifier, false); 8679 let scrollPercent = Math.min(Math.max(data.event.data.recalculate()?.percent.top, 0), 100); 8680 8681 if(scrollPercent >= this.percentFrom && scrollPercent <= this.percentTo) 8682 { 8683 let [fromKeyframe, toKeyframe] = this.fromTo(); 8684 let animation = mdActions.GetTemp('animate.' + identifier); 8685 8686 if(!animation) { 8687 8688 animation = $(this.target)[0].animate([fromKeyframe, toKeyframe], { 8689 duration: 1000, 8690 fill: 'forwards' 8691 }); 8692 8693 animation.pause(); 8694 mdActions.SetTemp('animate.' + identifier, animation); 8695 } 8696 8697 animation.currentTime = (scrollPercent - this.percentFrom) / (this.percentTo - this.percentFrom) * 1000; 8698 } 8699 8700 if(playing) 8701 { 8702 requestAnimationFrame(this.play); 8703 } 8704 }; 8705 8706 this.animate = () => 8707 { 8708 let playing = mdActions.GetTemp('playing.' + identifier, false); 8709 8710 8711 if(['dh.scroll', 'dh.scrolling'].includes(data.event.name)) 8712 { 8713 if(!playing) 8714 { 8715 mdActions.SetTemp('playing.' + identifier, true); 8716 requestAnimationFrame(this.play); 8717 } 8718 8719 dh.debounce(() => 8720 { 8721 mdActions.SetTemp('playing.' + identifier, false); 8722 }, 250, 'playing.' + identifier); 8723 } 8724 else 8725 { 8726 8727 let [fromKeyframe, toKeyframe] = this.fromTo(); 8728 clearTimeout(mdActions.GetTemp('timeouts.' + identifier)); 8729 8730 let timeout = setTimeout(() => 8731 { 8732 animation = $(this.target)[0].animate([fromKeyframe, toKeyframe], { 8733 duration: this.duration, 8734 fill: 'forwards', 8735 easing: this.easing 8736 }); 8737 8738 clearTimeout(mdActions.GetTemp('timeouts.duration.' + identifier)); 8739 8740 let timeoutDuration = setTimeout(() => 8741 { 8742 resolve(); 8743 }, this.duration); 8744 8745 mdActions.SetTemp('timeouts.duration.' + identifier, timeoutDuration); 8746 8747 }, this.delay); 8748 8749 mdActions.SetTemp('timeouts.' + identifier, timeout); 8750 } 8751 }; 8752 8753 this.fromTo = () => 8754 { 8755 const fromKeyframe = {}; 8756 const toKeyframe = {}; 8757 const transformFrom = []; 8758 const transformTo = []; 8759 const filterFrom = []; 8760 const filterTo = []; 8761
8762 data.values.properties.forEach(value => 8763 { 8764 if(value.property && value.from && value.to) 8765 { 8766 if (['translateX', 'translateY', 'scale', 'rotate', 'skewX', 'skewY'].includes(value.property)) 8767 { 8768 transformFrom.push(`${value.property}(${value.from})`); 8769 transformTo.push(`${value.property}(${value.to})`); 8770 } 8771 8772 else if (['blur', 'brightness', 'contrast', 'grayscale'].includes(value.property)) 8773 { 8774 filterFrom.push(`${value.property}(${value.from})`); 8775 filterTo.push(`${value.property}(${value.to})`); 8776 } 8777 else 8778 { 8779 fromKeyframe[value.property] = value.from; 8780 toKeyframe[value.property] = value.to; 8781 } 8782 } 8783 }); 8784 8785 if(transformFrom.length) 8786 { 8787 fromKeyframe.transform = transformFrom.join(' '); 8788 toKeyframe.transform = transformTo.join(' '); 8789 } 8790 8791 if(filterFrom.length) 8792 { 8793 fromKeyframe.filter = filterFrom.join(' '); 8794 toKeyframe.filter = filterTo.join(' '); 8795 } 8796 8797 return [fromKeyframe, toKeyframe]; 8798 }; 8799 8800 this.init(); 8801 } 8802 }); 8803});mdActions.OnReady(() => 8804{ 8805 return; 8806 8807 mdActions.ItemAdd({ 8808 id: 'attribute', 8809 target:true, 8810 icon: 'https://global.divhunt.com/f04bf9b3ceb8a570a31d8a0d52bf4986_262.svg', 8811 name: 'Attribute', 8812 description: 'Add, remove or edit attributes', 8813 config: () => 8814 { 8815 return { 8816 type: { 8817 label: 'Type', 8818 value: 'add', 8819 width: 4, 8820 type: 'SELECT', 8821 values: () => 8822 { 8823 return [ 8824 {title: 'Add', value: 'add'}, 8825 {title: 'Remove', value: 'remove'}, 8826 {title: 'Edit', value: 'edit'} 8827 ]; 8828 }, 8829 configure: (data) => 8830 { 8831 if(data.values.type === 'remove') 8832 { 8833 data.config.type.width = 6; 8834 data.config.name.width = 6; 8835 data.config.value.hidden = true; 8836 } 8837 else 8838 { 8839 data.config.type.width = 4; 8840 data.config.name.width = 4; 8841 data.config.value.hidden = false; 8842 } 8843 } 8844 }, 8845 name: { 8846 label: 'Name', 8847 width: 4, 8848 type: 'INPUT', 8849 placeholder: 'Name' 8850 }, 8851 value: { 8852 label: 'Value', 8853 width: 4, 8854 type: 'INPUT', 8855 placeholder: 'Value' 8856 } 8857 }; 8858 }, 8859 code: function(data, resolve, reject) 8860 { 8861 8862 if(['add', 'edit'].includes(data.values.type)) 8863 { 8864 data.target.attr(data.values.name, data.values.value); 8865 } 8866 else 8867 { 8868 data.target.removeAttr(data.values.name); 8869 } 8870 8871 resolve(); 8872 } 8873 }); 8874}); 8875mdActions.OnReady(() => 8876{ 8877 mdActions.ItemAdd({ 8878 id: 'class', 8879 target: true, 8880 play: true, 8881 name: 'Class', 8882 icon: 'https://global.divhunt.com/13c1a27fc562bc29e119efe8f85a4eb7_542.svg', 8883 description: 'Add, remove or toggle class.', 8884 config: () => 8885 { 8886 return { 8887 type: { 8888 label: 'Type', 8889 width: 6, 8890 value: 'add', 8891 type: 'SELECT', 8892 values: () => 8893 { 8894 return [ 8895 {title: 'Add', value: 'add'}, 8896 {title: 'Remove', value: 'remove'}, 8897 {title: 'Toggle', value: 'toggle'} 8898 ] 8899 }, 8900 }, 8901 value: { 8902 width: 6, 8903 label: 'Class', 8904 type: 'INPUT' 8905 } 8906 }; 8907 },
8908 code: (data, resolve, reject) => 8909 { 8910 switch(data.values.type) 8911 { 8912 case 'add': 8913 data.target.addClass(data.values.value); 8914 break; 8915 case 'remove': 8916 data.target.removeClass(data.values.value); 8917 break; 8918 case 'toggle': 8919 data.target.toggleClass(data.values.value); 8920 break; 8921 } 8922 8923 resolve(); 8924 } 8925 }); 8926}); 8927mdActions.OnReady(() => 8928{ 8929 return; 8930 8931 mdActions.ItemAdd({ 8932 id: 'copy-to-clipboard', 8933 name: 'Copy to Clipboard', 8934 icon: 'https://global.divhunt.com/204fc7e65ee7ddf24810f04ebe45b3aa_1163.svg', 8935 description: 'Copy specified text to the clipboard', 8936 target:true, 8937 config: () => 8938 { 8939 return { 8940 type: { 8941 label: 'Type', 8942 value: 'static', 8943 width: 6, 8944 type: 'SELECT', 8945 values: () => 8946 { 8947 return [ 8948 {title: 'Static', value: 'static'}, 8949 {title: 'Dynamic', value: 'dynamic'}, 8950 ]; 8951 }, 8952 configure: (data) => 8953 { 8954 if (data.values.type === 'dynamic') { 8955 data.config.target.hidden = false; 8956 data.config.contentSource.hidden = false; 8957 data.config.attributeName.hidden = data.values.contentSource !== 'attribute'; 8958 data.config.text.hidden = true; 8959 } else { 8960 data.config.target.hidden = true; 8961 data.config.contentSource.hidden = true; 8962 data.config.attributeName.hidden = true; 8963 data.config.text.hidden = false; 8964 } 8965 } 8966 }, 8967 text: { 8968 label: 'Text to Copy', 8969 width: 6, 8970 type: 'INPUT', 8971 placeholder: 'Enter text to copy...', 8972 }, 8973 8974 contentSource: { 8975 label: 'Content Source', 8976 value: 'text', 8977 width: 6, 8978 type: 'SELECT', 8979 values: () => 8980 { 8981 return [ 8982 {title: 'Text Content', value: 'text'}, 8983 {title: 'Attribute', value: 'attribute'}, 8984 ]; 8985 }, 8986 configure: (data) => 8987 { 8988 if (data.values.contentSource === 'attribute') { 8989 data.config.attributeName.hidden = false; 8990 } else { 8991 data.config.attributeName.hidden = true; 8992 } 8993 } 8994 }, 8995 attributeName: { 8996 label: 'Attribute Name', 8997 width: 6, 8998 type: 'INPUT', 8999 placeholder: 'Enter attribute name...', 9000 }, 9001 }; 9002 }, 9003 code: (data, resolve, reject) => 9004 { 9005 9006 this.init = () => { 9007 if (data.values.type === 'static') { 9008 this.static(); 9009 } else if (data.values.type === 'dynamic') { 9010 this.dynamic(); 9011 } 9012 }; 9013 9014 this.static = () => { 9015 if (!data.values.text) { 9016 console.log("No text provided. Interaction: Copy to Clipboard"); 9017 reject("No text provided"); 9018 return; 9019 } 9020 this.copy(data.values.text); 9021 }; 9022 9023 this.dynamic = () => { 9024 9025 9026 let textToCopy = ''; 9027 if (data.values.contentSource === 'text') { 9028 textToCopy = data.target.text(); 9029 } else if (data.values.contentSource === 'attribute') { 9030 textToCopy = data.target.attr(data.values.attributeName); 9031 if (!textToCopy) { 9032 console.log("Attribute not found or empty. Interaction: Copy to Clipboard"); 9033 reject("Attribute not found or empty"); 9034 return; 9035 } 9036 } 9037 this.copy(textToCopy); 9038 }; 9039 9040 this.copy = (textToCopy) => { 9041 try { 9042 navigator.clipboard.writeText(textToCopy).then(() => { 9043 console.log("Text copied to clipboard: ", textToCopy); 9044 resolve(); 9045 }).catch((error) => { 9046 console.error("Error copying text to clipboard: ", error); 9047 reject("Error copying text to clipboard"); 9048 }); 9049 } catch (error) { 9050 console.error("Clipboard API not supported: ", error); 9051 reject("Clipboard API not supported"); 9052 } 9053 }; 9054 9055 this.init(); 9056 } 9057 }); 9058});mdActions.OnReady(() => 9059{ 9060 return; 9061 9062 mdActions.ItemAdd({ 9063 id: 'cookie', 9064 name: 'Cookie', 9065 icon: 'https://global.divhunt.com/ffe79026e89e9368c91ba6f3c6d7c427_1736.svg', 9066 description: 'Add, remove or edit cookie', 9067 config: () => 9068 { 9069 return { 9070 type: { 9071 label: 'Type', 9072 value: 'add', 9073 width: 4, 9074 type: 'SELECT', 9075 values: () => 9076 { 9077 return [ 9078 {title: 'Add', value: 'add'}, 9079 {title: 'Remove', value: 'remove'}, 9080 {title: 'Edit', value: 'edit'}, 9081 {title: 'Get', value: 'get'}, 9082 ]; 9083 }, 9084 configure: (data) => 9085 { 9086 if(data.values.type === 'remove' || data.values.type === 'get') 9087 { 9088 data.config.type.width = 6; 9089 data.config.name.width = 6; 9090 data.config.value.hidden = true; 9091 } 9092 else 9093 { 9094 data.config.type.width = 4; 9095 data.config.name.width = 4; 9096 data.config.value.hidden = false;
9097 } 9098 } 9099 }, 9100 name: { 9101 label: 'Name', 9102 width: 4, 9103 type: 'INPUT', 9104 placeholder: 'Name' 9105 }, 9106 value: { 9107 label: 'Value', 9108 width: 4, 9109 type: 'INPUT', 9110 placeholder: 'Value' 9111 } 9112 }; 9113 }, 9114 code: function(data, resolve, reject) 9115 { 9116 9117 if(['add', 'edit'].includes(data.values.type)) { 9118 interactions.SetSave(data.values.name, data.values.value); 9119 } else if (data.values.type === "remove") { 9120 interactions.SetSave(data.values.name, null); 9121 } else { 9122 return interactions.GetSave(data.values.name); 9123 } 9124 9125 resolve(); 9126 } 9127 }); 9128}); 9129mdActions.OnReady(() => 9130{ 9131 return; 9132 9133 mdActions.ItemAdd({ 9134 id: 'download', 9135 name: 'Download File', 9136 description: 'Download a specified file', 9137 icon: 'https://global.divhunt.com/abe7812594d34ee28d079c21542c3012_552.svg', 9138 config: () => 9139 { 9140 return { 9141 file: { 9142 label: 'File', 9143 width: 12, 9144 type: 'FILE', 9145 }, 9146 }; 9147 }, 9148 code: (data, resolve, reject) => 9149 { 9150 if (!data.values.file) { 9151 console.log("No file specified. Interaction: Download File"); 9152 reject("No file specified"); 9153 return; 9154 } 9155 9156 try { 9157 const link = document.createElement('a'); 9158 link.href = URL.createObjectURL(data.values.file); 9159 link.download = data.values.file.name; 9160 document.body.appendChild(link); 9161 link.click(); 9162 document.body.removeChild(link); 9163 URL.revokeObjectURL(link.href); 9164 resolve(); 9165 } catch (error) { 9166 console.error("Error during file download: ", error); 9167 reject("Error during file download"); 9168 } 9169 } 9170 }); 9171}); 9172 mdActions.OnReady(() => 9173{ 9174 mdActions.ItemAdd({ 9175 id: 'html', 9176 name: 'HTML', 9177 target: true, 9178 icon: 'https://global.divhunt.com/09b5b89fd2dfb45654291a6fd47661c9_540.svg', 9179 description: 'Change HTML of an element', 9180 config: () => 9181 { 9182 return { 9183 method: { 9184 type: 'SELECT', 9185 label: 'Method', 9186 value: 'replace', 9187 values: () => 9188 { 9189 return [ 9190 {title: 'Replace', value: 'replace', description: 'Replace the current content with new content.'}, 9191 {title: 'Append', value: 'append', description: 'Add new content to the end of the existing content.'}, 9192 {title: 'Prepend', value: 'prepend', description: 'Add new content to the beginning of the existing content.'}, 9193 {title: 'Insert Before', value: 'insertBefore', description: 'Insert new content immediately before the specified element.'}, 9194 {title: 'Insert After', value: 'insertAfter', description: 'Insert new content immediately after the specified element.'}, 9195 {title: 'Remove', value: 'remove', description: 'Delete the specified element or content.'}, 9196 {title: 'Clear', value: 'clear', description: 'Remove all content from the target element.'}, 9197 ]; 9198 } 9199 }, 9200 value: { 9201 label: 'HTML', 9202 type: 'TEXTAREA' 9203 } 9204 }; 9205 }, 9206 code: (data, resolve, reject, variable) => 9207 { 9208 try 9209 { 9210 let $target = $(data.target); 9211 9212 switch(data.values.method) 9213 { 9214 case 'replace': 9215 $target.html(data.values.value); 9216 break; 9217 9218 case 'append': 9219 $target.append(data.values.value); 9220 break; 9221 9222 case 'prepend': 9223 $target.prepend(data.values.value); 9224 break; 9225 9226 case 'insertBefore': 9227 $target.before(data.values.value); 9228 break; 9229 9230 case 'insertAfter': 9231 $target.after(data.values.value); 9232 break; 9233 9234 case 'remove': 9235 $target.remove(); 9236 break; 9237 9238 case 'clear': 9239 $target.empty(); 9240 break; 9241 9242 default: 9243 throw new Error('Invalid method provided.'); 9244 } 9245 9246 resolve(); 9247 } 9248 catch (error) 9249 { 9250 reject(error.message); 9251 } 9252 } 9253 }); 9254}); 9255mdActions.OnReady(function(addon) 9256{ 9257 mdActions.ItemAdd({ 9258 id: 'http-request', 9259 name: 'HTTP Request', 9260 icon: 'https://global.divhunt.com/icons/http-request.svg',
9261 description: 'Send an HTTP request with customizable method, headers, parameters, and body using jQuery', 9262 variable: true, 9263 config: () => ({ 9264 method: { 9265 label: 'HTTP Method', 9266 type: 'SELECT', 9267 value: 'GET', 9268 values: [ 9269 { title: 'GET', value: 'GET' }, 9270 { title: 'POST', value: 'POST' }, 9271 { title: 'PUT', value: 'PUT' }, 9272 { title: 'DELETE', value: 'DELETE' } 9273 ], 9274 whenChanged: (event) => 9275 { 9276 if(['POST', 'PUT'].includes(event.values.method)) 9277 { 9278 event.config.body.hidden = false; 9279 } 9280 else 9281 { 9282 event.config.body.hidden = true; 9283 } 9284 9285 event.reload(); 9286 }, 9287 width: 4 9288 }, 9289 url: { 9290 label: 'URL', 9291 type: 'INPUT', 9292 placeholder: 'Enter endpoint URL', 9293 width: 8 9294 }, 9295 parameters: { 9296 label: 'Parameters', 9297 type: 'REPEATER', 9298 order: 20, 9299 style: 'list', 9300 onAdd: event => event.edit(), 9301 onEdit: () => {}, 9302 onSort: () => {}, 9303 onRemove: () => {}, 9304 config: { 9305 key: { 9306 label: 'Key', 9307 type: 'INPUT', 9308 placeholder: 'Key', 9309 width: 6 9310 }, 9311 value: { 9312 label: 'Value', 9313 type: 'INPUT', 9314 placeholder: 'Value', 9315 width: 6 9316 } 9317 } 9318 }, 9319 headers: { 9320 label: 'Headers', 9321 type: 'REPEATER', 9322 order: 10, 9323 style: 'list', 9324 onAdd: event => event.edit(), 9325 onEdit: () => {}, 9326 onSort: () => {}, 9327 onRemove: () => {}, 9328 config: { 9329 key: { 9330 label: 'Key', 9331 type: 'INPUT', 9332 placeholder: 'Key', 9333 width: 6 9334 }, 9335 value: { 9336 label: 'Value', 9337 type: 'INPUT', 9338 placeholder: 'Value', 9339 width: 6 9340 } 9341 } 9342 }, 9343 body: { 9344 label: 'Body', 9345 type: 'REPEATER', 9346 order: 30, 9347 style: 'list', 9348 hidden: true, 9349 onAdd: event => event.edit(), 9350 onEdit: () => {}, 9351 onSort: () => {}, 9352 onRemove: () => {}, 9353 configure: (data) => 9354 { 9355 if(['POST', 'PUT'].includes(data.values.method)) 9356 { 9357 data.config.body.hidden = false; 9358 } 9359 else 9360 { 9361 data.config.body.hidden = true; 9362 } 9363 }, 9364 config: { 9365 key: { 9366 label: 'Key', 9367 type: 'INPUT', 9368 placeholder: 'Key', 9369 width: 6 9370 }, 9371 value: { 9372 label: 'Value', 9373 type: 'INPUT', 9374 placeholder: 'Value', 9375 width: 6 9376 } 9377 } 9378 } 9379 }), 9380 code: function(data, resolve, reject) 9381 { 9382 let method = data.values.method || 'GET'; 9383 let url = data.values.url; 9384 9385 if(!url) 9386 { 9387 return reject('No URL provided.'); 9388 } 9389 9390 let headers = {}; 9391 let parameters = {}; 9392 let body = {}; 9393 let query = ''; 9394 9395 if(Array.isArray(data.values.headers)) 9396 {
9397 data.values.headers.forEach(header => 9398 { 9399 headers[header.key] = header.value; 9400 }); 9401 } 9402 9403 if(Array.isArray(data.values.parameters)) 9404 { 9405 data.values.parameters.forEach(parameter => 9406 { 9407 parameters[parameter.key] = parameter.value; 9408 }); 9409 } 9410 9411 if(Array.isArray(data.values.body)) 9412 { 9413 data.values.body.forEach(b => 9414 { 9415 body[b.key] = b.value; 9416 }); 9417 } 9418 9419 if(Object.keys(parameters).length) 9420 { 9421 query = '?' + $.param(parameters); 9422 } 9423 9424 $.ajax({ 9425 url: url + query, 9426 method: method, 9427 headers: headers, 9428 data: method === 'GET' || method === 'DELETE' ? null : JSON.stringify(body), 9429 contentType: 'application/json', 9430 success: function(response) 9431 { 9432 if(resolve && typeof response === 'object') 9433 { 9434 resolve(response); 9435 } 9436 else 9437 { 9438 reject('Expect JSON, got ' + (typeof response)); 9439 } 9440 }, 9441 error: function(xhr) 9442 { 9443 reject(xhr.responseText || 'Error occurred while sending the request.'); 9444 } 9445 }); 9446 } 9447 }); 9448}); 9449mdActions.OnReady(() => 9450{ 9451 mdActions.ItemAdd({ 9452 id: 'javascript', 9453 name: 'Custom JavaScript', 9454 icon: 'https://global.divhunt.com/e8d6591823662bdd75a6cb58bb3048d5_2496.svg', 9455 description: 'Execute custom JavaScript code', 9456 config: () => 9457 { 9458 return { 9459 code: { 9460 label: 'JavaScript Code', 9461 type: 'TEXTAREA', 9462 placeholder: 'Type your JavaScript code here.', 9463 } 9464 }; 9465 }, 9466 code: (data, resolve, reject) => 9467 { 9468 try 9469 { 9470 new Function('data', data.values.code)(data); 9471 resolve(); 9472 } 9473 catch(error) 9474 { 9475 console.log(error); 9476 reject(error); 9477 } 9478 } 9479 }); 9480}); 9481mdActions.OnReady(function() 9482{ 9483 return; 9484 9485 let timeouts = {}; 9486 9487 mdActions.ItemAdd({ 9488 id: 'language', 9489 name: 'Language', 9490 icon: 'language', 9491 description: 'Switch website language', 9492 icon: 'https://global.divhunt.com/97a136e2513978a6c2c6565d09766cbf_827.svg', 9493 config: () => ({ 9494 9495 language: { 9496 label: 'Language', 9497 width: 12, 9498 type: 'SELECT', 9499 values: () => 9500 { 9501 9502 const formattedLanguages = Object.values($dh.var('language').data).map(language => { 9503 return { 9504 title: language.name, 9505 value: language.slug 9506 }; 9507 }); 9508 9509 return formattedLanguages; 9510 }, 9511 configure: (data) => 9512 { 9513 9514 } 9515 }, 9516 9517 }), 9518 code: function(data, resolve, reject, identifier) 9519 { 9520 9521 let language = $dh.language(); 9522 let name = data.values.language; 9523 let uri = window.location.pathname.split('/').filter(Boolean); 9524 9525 if(language.active !== language.default) 9526 { 9527 uri.shift(); 9528 } 9529 9530 if(name !== language.default) 9531 { 9532 uri.unshift(name.toLowerCase()); 9533 } 9534 9535 window.location.href = '/' + uri.join('/'); 9536 9537 } 9538 }); 9539}); 9540mdActions.OnReady(() => 9541{ 9542 return; 9543 9544 mdActions.ItemAdd({ 9545 id: 'notification', 9546 name: 'Notification', 9547 icon: 'https://global.divhunt.com/0a06de10aa32e9602505d11c0a99e7ff_1490.svg', 9548 description: 'Show notification', 9549 config: { 9550 type: { 9551 label: "Type", 9552 width: 6, 9553 value: 'success', 9554 type: "SELECT", 9555 values: ()=> { 9556 return [ 9557 {title: "Success", value: "success"}, 9558 {title: "Error", value: "fail"}, 9559 {title: "Warning", value: "warning"} 9560 ] 9561 }, 9562 }, 9563 title: { 9564 label: "Title", 9565 width: 6, 9566 type: "INPUT", 9567 }, 9568 message: { 9569 label: "Message", 9570 width: 6, 9571 type: "INPUT", 9572 }, 9573 },
9574 code: (data, resolve, reject) => { 9575 common.Fn('notification', data.values.type, data.values.title, data.values.message); 9576 resolve(); 9577 } 9578 }); 9579}); 9580mdActions.OnReady(() => 9581{ 9582 mdActions.ItemAdd({ 9583 id: 'redirect', 9584 name: 'Redirect', 9585 description: 'Redirect user to different URL', 9586 icon: 'https://global.divhunt.com/8f97aedccd4df1568acbd646adc652a4_535.svg', 9587 config: { 9588 url: { 9589 label: "URL", 9590 width: 6, 9591 placeholder: 'https://', 9592 type: "INPUT", 9593 }, 9594 target: { 9595 label: 'Target', 9596 width: 6, 9597 value: '_self', 9598 type: 'SELECT', 9599 values: () => 9600 { 9601 return [ 9602 {title: 'Self', value: '_self', description: 'Opens the URL in the same browsing context as the current one.'}, 9603 {title: 'Blank', value: '_blank', description: 'Opens the URL in a new browsing context (typically a new tab or window, depending on.'}, 9604 ]; 9605 } 9606 }, 9607 relationships: { 9608 label: 'Relationships', 9609 width: 12, 9610 type: 'REPEATER', 9611 style: 'list', 9612 onAdd: () => {}, 9613 onEdit: () => {}, 9614 onRemove: () => {}, 9615 config: { 9616 name: { 9617 type: 'SELECT', 9618 value: 'noopener', 9619 width: 6, 9620 values: () => 9621 { 9622 return [ 9623 { title: 'Noopener', value: 'noopener', description: 'Prevents the new window from accessing the originating window via window.opener.' }, 9624 { title: 'Noreferrer', value: 'noreferrer', description: 'Prevents the Referer header from being sent and disables window.opener access.' }, 9625 { title: 'Width', value: 'width', description: 'Sets the width of the new window in pixels.' }, 9626 { title: 'Height', value: 'height', description: 'Sets the height of the new window in pixels.' }, 9627 { title: 'Left', value: 'left', description: 'Sets the position of the new window from the left edge of the screen in pixels.' }, 9628 { title: 'Top', value: 'top', description: 'Sets the position of the new window from the top edge of the screen in pixels.' }, 9629 { title: 'Resizable', value: 'resizable', description: 'Allows or disallows the user to resize the new window (yes or no).' }, 9630 { title: 'Scrollbars', value: 'scrollbars', description: 'Determines if the new window should have scrollbars (yes or no).' }, 9631 { title: 'Menubar', value: 'menubar', description: 'Determines if the new window should display a menu bar (yes or no).' }, 9632 { title: 'Toolbar', value: 'toolbar', description: 'Determines if the new window should display a toolbar (yes or no).' }, 9633 { title: 'Location', value: 'location', description: 'Determines if the new window should display the address bar (yes or no).' }, 9634 { title: 'Status', value: 'status', description: 'Determines if the new window should display the status bar (yes or no).' }, 9635 { title: 'Fullscreen', value: 'fullscreen', description: 'Opens the new window in fullscreen mode (yes or no).' }, 9636 { title: 'Popup', value: 'popup', description: 'Indicates that the opened window is a popup (custom non-standard feature).' } 9637 ]; 9638 }, 9639 whenChanged: (event) => 9640 { 9641 delete event.values.value; 9642 }, 9643 configure: (data) => 9644 { 9645 if(['width', 'height', 'left', 'top'].includes(data.values.name)) 9646 { 9647 return { 9648 value: { 9649 type: 'INPUT', 9650 units: ['px'], 9651 value: '100px', 9652 width: 6 9653 } 9654 }; 9655 } 9656 else if(['resizable', 'scrollbars', 'menubar', 'toolbar', 'location', 'fullscreen', 'popup'].includes(data.values.name)) 9657 { 9658 return { 9659 value: { 9660 type: 'SELECT', 9661 value: 'yes', 9662 values: () => 9663 { 9664 return [ 9665 {title: 'Yes', value: 'yes'}, 9666 {title: 'No', value: 'no'} 9667 ]; 9668 }, 9669 width: 6 9670 } 9671 }; 9672 } 9673 else 9674 { 9675 return {}; 9676 } 9677 } 9678 } 9679 } 9680 } 9681 },
9682 code: (data, resolve, reject) => 9683 { 9684 let relationships = []; 9685 9686 if(Array.isArray(data.values.relationships)) 9687 { 9688 data.values.relationships.forEach((relationship) => 9689 { 9690 if(['width', 'height', 'left', 'top'].includes(relationship.name)) 9691 { 9692 relationships.push(relationship.name + '=' + (relationship.value + '').replace(/\D/g, '')); 9693 } 9694 else if(['resizable', 'scrollbars', 'menubar', 'toolbar', 'location', 'fullscreen', 'popup'].includes(relationship.name)) 9695 { 9696 relationships.push(relationship.name + '=' + (relationship.value === 'yes' ? 'yes' : 'no')); 9697 } 9698 else 9699 { 9700 relationships.push(relationship.name); 9701 } 9702 }); 9703 } 9704 9705 window.open(data.values.url, data.values.target, relationships.join(',')); 9706 resolve(); 9707 } 9708 }); 9709}); 9710mdActions.OnReady(() => 9711{ 9712 mdActions.ItemAdd({ 9713 id: 'scroll', 9714 name: 'Scroll', 9715 target: true, 9716 duration: true, 9717 icon: 'https://global.divhunt.com/2fa3d7e52a4cfee8b5f406a7ca48c58e_812.svg', 9718 description: 'Scroll to specific element', 9719 config: { 9720 offset: { 9721 label: 'Offset', 9722 width: 6, 9723 value: '0px', 9724 units: ['px'], 9725 type: 'INPUT', 9726 } 9727 }, 9728 code: (data, resolve, reject, variable, identifier) => { 9729 9730 $('html, body').animate({ 9731 scrollTop: data.target.offset().top + (data.values.offset + '').replace(/\D/g, '') 9732 }, data.values.duration); 9733 9734 dh.debounce(() => 9735 { 9736 resolve(); 9737 }, data.values.duration, identifier) 9738 } 9739 }); 9740}); 9741mdActions.OnReady(() => 9742{ 9743 mdActions.ItemAdd({ 9744 id: 'text', 9745 name: 'Text', 9746 target: true, 9747 play: true, 9748 icon: 'https://global.divhunt.com/09b5b89fd2dfb45654291a6fd47661c9_540.svg', 9749 description: 'Change text of a element', 9750 config: () => 9751 { 9752 return { 9753 value: { 9754 label: 'Text', 9755 type: 'TEXTAREA' 9756 } 9757 }; 9758 }, 9759 code: (data, resolve, reject, variable) => 9760 { 9761 if(data.event.name === 'play') 9762 { 9763 data.target.find('> text').text(variable(data.values.value)); 9764 } 9765 else 9766 { 9767 data.target.text(variable(data.values.value)); 9768 } 9769 9770 resolve(); 9771 } 9772 }); 9773}); 9774mdActions.OnReady(() => 9775{ 9776 mdActions.ItemAdd({ 9777 id: 'visibility', 9778 name: 'Visibility', 9779 icon: 'https://global.divhunt.com/c0176d474226e531eb0ad02491351853_672.svg', 9780 target: true, 9781 play: true, 9782 duration: true, 9783 description: 'Hide, show or toggle visibility.', 9784 config: { 9785 type: { 9786 label: 'Type', 9787 width: 6, 9788 value: 'hide', 9789 type: 'SELECT', 9790 values: ()=> 9791 { 9792 return [ 9793 {title: 'Hide', value: 'hide'}, 9794 {title: 'Show', value: 'show'}, 9795 {title: 'Toggle', value: 'toggle'} 9796 ]; 9797 }, 9798 }, 9799 animation: { 9800 label: 'Animation', 9801 width: 6, 9802 value: 'fade', 9803 type: 'SELECT', 9804 values: ()=> { 9805 return [ 9806 {title: 'None', value: 'none'}, 9807 {title: 'Fade', value: 'fade'}, 9808 {title: 'Slide', value: 'slide'} 9809 ] 9810 }, 9811 } 9812 }, 9813 code: (data, resolve, reject) => 9814 { 9815 switch (data.values.type) 9816 { 9817 case 'hide': 9818 switch (data.values.animation) { 9819 case 'fade': 9820 data.target.fadeOut(data.values.duration, () => 9821 { 9822 resolve(); 9823 }); 9824 break; 9825 case 'slide': 9826 data.target.slideUp(data.values.duration, () => 9827 { 9828 resolve(); 9829 }); 9830 break; 9831 default: 9832 data.target.hide(data.values.duration, () => 9833 { 9834 resolve(); 9835 }); 9836 break; 9837 } 9838 break; 9839 case 'show': 9840 switch (data.values.animation) 9841 { 9842 case 'fade': 9843 data.target.fadeIn(data.values.duration, () => 9844 { 9845 resolve(); 9846 }); 9847 break; 9848 case 'slide': 9849 data.target.slideDown(data.values.duration, () => 9850 { 9851 resolve(); 9852 }); 9853 break; 9854 default: 9855 data.target.show(data.values.duration, () => 9856 { 9857 resolve(); 9858 }); 9859 break; 9860 } 9861 break; 9862 case 'toggle': 9863 switch (data.values.animation) { 9864 case 'fade': 9865 data.target.fadeToggle(data.values.duration, () => 9866 { 9867 resolve(); 9868 }); 9869 break; 9870 case 'slide': 9871 data.target.slideToggle(data.values.duration, () => 9872 { 9873 resolve(); 9874 }); 9875 break; 9876 default: 9877 data.target.toggle(data.values.duration, () => 9878 { 9879 resolve(); 9880 }); 9881 break; 9882 } 9883 break; 9884 } 9885 9886 } 9887 }); 9888}); 9889 9890mdEvents.SetReload(false); 9891mdEvents.FieldAdd('name'); 9892mdEvents.FieldAdd('group'); 9893mdEvents.FieldAdd('description'); 9894mdEvents.FieldAdd('callbacks'); 9895mdEvents.FieldAdd('debug'); 9896mdEvents.FieldAdd('once'); 9897 9898mdEvents.FieldAdd('settings', null, (value) => 9899{ 9900 return value ? value : {}; 9901}); 9902 9903mdEvents.FieldAdd('config', (value) => 9904{ 9905 if(typeof value === 'function') 9906 { 9907 value = value(); 9908 } 9909 9910 return value && typeof value === 'object' ? value : {}; 9911}); 9912 9913mdEvents.FieldAdd('role', null, (value) => 9914{ 9915 return Array.isArray(value) ? value : []; 9916});mdEvents.FunctionCreate('callbacks', function(addon, id) 9917{ 9918 let event = addon.ItemGet(id); 9919 9920 if(!event) 9921 { 9922 return []; 9923 } 9924
9925 let callbacks = event.Get('callbacks'); 9926 9927 return Array.isArray(callbacks) ? callbacks : []; 9928});mdEvents.FunctionCreate('catch', function(addon, id, callback, condition = null, once = false, values = {}) 9929{ 9930 let event = addon.ItemGet(id); 9931 9932 if(!event) 9933 { 9934 event = addon.ItemAdd({id}); 9935 } 9936 9937 return event.Fn('catch', callback, condition, once, values); 9938});mdEvents.FunctionCreate('clear', function(addon, id) 9939{ 9940 let event = addon.ItemGet(id); 9941 9942 if(!event) 9943 { 9944 return []; 9945 } 9946 9947 event.Set('callbacks', null); 9948});mdEvents.FunctionCreate('debug', function(addon, item, data = {}) 9949{ 9950 let query = $dh.query('dh-events'); 9951 9952 if(query !== 'debug') 9953 { 9954 return; 9955 } 9956 9957 let callback = item.Get('debug'); 9958 9959 if(!callback) 9960 { 9961 return; 9962 } 9963 9964 let debug = $('#dh-events-debug'); 9965 9966 if(!debug.length) 9967 { 9968 $('body').append(`<div id="dh-events-debug"></div>`); 9969 debug = $('#dh-events-debug'); 9970 } 9971 9972 let items = callback(item, data); 9973 9974 debug.find('> .dh-event-' + item.Get('id').replaceAll('.', '-')).remove(); 9975 debug.append(`<div class="dh-event-${item.Get('id').replaceAll('.', '-')}"><b>${item.Get('name')}</b>${items.map(item => `<li>${item}</li>`).join('')}</div>`); 9976 9977 dh.debounce(() => 9978 { 9979 debug.html(''); 9980 }, 5000, 'events.debug'); 9981});mdEvents.FunctionCreate('list', function(addon, role = 'website') 9982{ 9983 let list = []; 9984 9985 $.each(addon.ItemsGet(), (id, item) => 9986 { 9987 if((item.Get('role').includes(role) || role === '*') && item.Get('name') && item.Get('description')) 9988 { 9989 list.push({ 9990 group: item.Get('group'), 9991 title: item.Get('name'), 9992 description: item.Get('description'), 9993 value: id 9994 }); 9995 } 9996 }); 9997 9998 return list.reverse(); 9999});mdEvents.FunctionCreate('run', function(addon, id, data = {}, intercept = null) 10000{ 10001 let event = addon.ItemGet(id); 10002 10003 if(!event) 10004 { 10005 return; 10006 } 10007 10008 event.Fn('run', data, intercept); 10009});mdEvents.FunctionCreate('item.catch', function(addon, item, callback, condition = null, once = false, values = {}) 10010{ 10011 if(callback && typeof callback !== 'function') 10012 { 10013 return; 10014 } 10015 10016 if(condition && typeof condition !== 'function') 10017 { 10018 return; 10019 } 10020 10021 let callbacks = item.Get('callbacks'); 10022 10023 if(!Array.isArray(callbacks)) 10024 { 10025 callbacks = []; 10026 } 10027 10028 let index = callbacks.length; 10029 let object = {callback, condition, once: !!once, values, remove: () => 10030 { 10031 delete callbacks[index]; 10032 item.Set('callbacks', callbacks); 10033 }}; 10034 10035 callbacks.push(object); 10036 item.Set('callbacks', callbacks); 10037 10038 return object; 10039});mdEvents.FunctionCreate('item.run', function(addon, item, object = {}, intercept = null) 10040{ 10041 let query = $dh.query('dh-events'); 10042 10043 if(query === 'off') 10044 { 10045 return; 10046 } 10047 10048 this.init = () => 10049 { 10050 let callbacks = item.Get('callbacks'); 10051 10052 if(!Array.isArray(callbacks)) 10053 { 10054 return; 10055 } 10056 10057 callbacks.forEach((callback, index) => 10058 { 10059 if(!callback) 10060 { 10061 return true; 10062 } 10063 10064 let run = (data) => 10065 { 10066 if((callback.condition && !callback.condition(data))) 10067 { 10068 return; 10069 } 10070 10071 let respone = callback.callback(data, item); 10072 addon.Fn('debug', item, data); 10073 10074 return respone; 10075 }; 10076 10077 let data = object; 10078 let interceptData = {item, data, callbacks, callback, index, prevent: false, run}; 10079 10080 if(intercept) 10081 { 10082 intercept(interceptData); 10083 10084 if(interceptData.prevent) 10085 { 10086 return true; 10087 } 10088 } 10089 10090 run(interceptData.data); 10091 10092 if(callback.once) 10093 { 10094 callback.remove(); 10095 } 10096 }); 10097 }; 10098 10099 return this.init(); 10100}); 10101mdOperators.SetReload(false); 10102 10103mdOperators.FieldAdd('compare'); 10104mdOperators.FieldAdd('backend'); 10105mdOperators.FieldAdd('function');mdOperators.FunctionCreate('process', function(addon, items, data = {}) 10106{ 10107 let valid = true; 10108
10109 items.forEach(function(item) 10110 { 10111 let operator = mdOperators.ItemGet(item.operator); 10112 10113 if(!operator) 10114 { 10115 return true; 10116 } 10117 10118 if(!operator.Get('function')(data, item.value, item.compare)) 10119 { 10120 valid = false; 10121 return false; 10122 } 10123 }); 10124 10125 return valid; 10126});mdOperators.OnItemAdd(function(addon, item) 10127{ 10128 mdFunctions.OnReady(() => 10129 { 10130 if(!item.Get('compare')) 10131 { 10132 mdFunctions.ItemsAdd({ 10133 id: item.Get('id').replace(' ', ''), 10134 parameters: { 10135 content: {type: 'INPUT', 'value': ''}, 10136 }, 10137 description: 'Operator '+item.Get('id')+'.', 10138 group: 'operators', 10139 function: function(data, value, content) 10140 { 10141 if(item.Get('function')(data, value)) 10142 { 10143 return content; 10144 } 10145 10146 return null; 10147 } 10148 }); 10149 } 10150 else 10151 { 10152 mdFunctions.ItemsAdd({ 10153 id: item.Get('id').replace(' ', ''), 10154 parameters: { 10155 compare: {type: 'INPUT', 'value': ''}, 10156 content: {type: 'INPUT', 'value': ''}, 10157 }, 10158 description: 'Operator '+item.Get('id')+'.', 10159 group: 'operators', 10160 function: function(data, value, compare) 10161 { 10162 if(item.Get('function')(data, value, compare)) 10163 { 10164 return content; 10165 } 10166 10167 return null; 10168 } 10169 }); 10170 } 10171 }); 10172});mdOperators.ItemAdd({ 10173 id: 'Equals', 10174 compare: true, 10175 backend: true, 10176 function: function(data, value, compare) 10177 { 10178 return mdVars.Fn('process', value, data) == mdVars.Fn('process', compare, data); 10179 } 10180}); 10181 10182mdOperators.ItemAdd({ 10183 id: 'Not Equals', 10184 compare: true, 10185 backend: true, 10186 function: function(data, value, compare) 10187 { 10188 return mdVars.Fn('process', value, data) != mdVars.Fn('process', compare, data); 10189 } 10190}); 10191 10192mdOperators.ItemAdd({ 10193 id: 'Identical', 10194 compare: true, 10195 backend: true, 10196 function: function(data, value, compare) 10197 { 10198 return mdVars.Fn('process', value, data) === mdVars.Fn('process', compare, data); 10199 } 10200}); 10201 10202mdOperators.ItemAdd({ 10203 id: 'Not Identical', 10204 compare: true, 10205 backend: true, 10206 function: function(data, value, compare) 10207 { 10208 return mdVars.Fn('process', value, data) !== mdVars.Fn('process', compare, data); 10209 } 10210}); 10211 10212mdOperators.ItemAdd({ 10213 id: 'Empty', 10214 compare: false, 10215 backend: true, 10216 function: function(data, value) 10217 { 10218 value = mdVars.Fn('process', value, data); 10219 10220 if(value === null || value === undefined) 10221 { 10222 return true; 10223 } 10224 10225 if(Array.isArray(value) || typeof value === 'string') 10226 { 10227 return value.length === 0; 10228 } 10229 10230 if(typeof value === 'object') 10231 { 10232 return Object.keys(value).length === 0; 10233 } 10234 10235 return !!!value; 10236 } 10237}); 10238 10239mdOperators.ItemAdd({ 10240 id: 'Not Empty', 10241 compare: false, 10242 backend: true, 10243 function: function(data, value) 10244 { 10245 value = mdVars.Fn('process', value, data); 10246 10247 if(value === null || value === undefined) 10248 { 10249 return false; 10250 } 10251 10252 if(Array.isArray(value) || typeof value === 'string') 10253 { 10254 return value.length !== 0; 10255 } 10256 10257 if(typeof value === 'object') 10258 { 10259 return Object.keys(value).length !== 0; 10260 } 10261 10262 return !!value; 10263 } 10264}); 10265 10266mdOperators.ItemAdd({ 10267 id: 'Null', 10268 compare: false, 10269 backend: true, 10270 function: function(data, value) 10271 { 10272 return mdVars.Fn('process', value, data) === null; 10273 } 10274}); 10275 10276mdOperators.ItemAdd({ 10277 id: 'Not Null', 10278 compare: false, 10279 backend: true, 10280 function: function(data, value) 10281 { 10282 return mdVars.Fn('process', value, data) !== null; 10283 } 10284}); 10285 10286mdOperators.ItemAdd({ 10287 id: 'Truthy', 10288 compare: false, 10289 backend: true, 10290 function: function(data, value) 10291 { 10292 return mdVars.Fn('process', value, data); 10293 } 10294}); 10295 10296mdOperators.ItemAdd({ 10297 id: 'Falsy', 10298 compare: false, 10299 backend: true, 10300 function: function(data, value) 10301 { 10302 return !mdVars.Fn('process', value, data); 10303 } 10304}); 10305 10306mdOperators.ItemAdd({ 10307 id: 'True', 10308 compare: false, 10309 backend: true, 10310 function: function(data, value) 10311 { 10312 return mdVars.Fn('process', value, data) === true; 10313 } 10314}); 10315 10316mdOperators.ItemAdd({ 10317 id: 'Not True', 10318 compare: false, 10319 backend: true, 10320 function: function(data, value) 10321 { 10322 return mdVars.Fn('process', value, data) !== true; 10323 } 10324}); 10325 10326mdOperators.ItemAdd({ 10327 id: 'False', 10328 compare: false, 10329 backend: true, 10330 function: function(data, value) 10331 { 10332 return mdVars.Fn('process', value, data) === false; 10333 } 10334}); 10335 10336mdOperators.ItemAdd({ 10337 id: 'Not False', 10338 compare: false, 10339 backend: true, 10340 function: function(data, value) 10341 { 10342 return mdVars.Fn('process', value, data) !== false; 10343 } 10344}); 10345 10346mdOperators.ItemAdd({ 10347 id: 'Higher Than', 10348 compare: true, 10349 backend: true, 10350 function: function(data, value, compare) 10351 { 10352 return mdVars.Fn('process', value, data) > mdVars.Fn('process', compare, data); 10353 } 10354}); 10355 10356mdOperators.ItemAdd({ 10357 id: 'Higher Equal Than', 10358 compare: true, 10359 backend: true, 10360 function: function(data, value, compare) 10361 { 10362 return mdVars.Fn('process', value, data) >= mdVars.Fn('process', compare, data); 10363 } 10364}); 10365 10366mdOperators.ItemAdd({ 10367 id: 'Less Than', 10368 compare: true, 10369 backend: true, 10370 function: function(data, value, compare) 10371 { 10372 return mdVars.Fn('process', value, data) < mdVars.Fn('process', compare, data); 10373 } 10374}); 10375 10376mdOperators.ItemAdd({ 10377 id: 'Less Equal Than', 10378 compare: true, 10379 backend: true, 10380 function: function(data, value, compare) 10381 { 10382 return mdVars.Fn('process', value, data) <= mdVars.Fn('process', compare, data); 10383 } 10384}); 10385 10386mdOperators.ItemAdd({ 10387 id: 'Contains', 10388 compare: true, 10389 backend: true, 10390 function: function(data, value, compare) 10391 { 10392 value = mdVars.Fn('process', value, data); 10393 compare = mdVars.Fn('process', compare, data); 10394 10395 if(value === null || value === undefined) return false;
10396 10397 value = String(value); 10398 compare = String(compare); 10399 10400 return value.includes(compare); 10401 } 10402}); 10403 10404mdOperators.ItemAdd({ 10405 id: 'Contains Any', 10406 compare: true, 10407 backend: true, 10408 function: function(data, value, compare) 10409 { 10410 value = mdVars.Fn('process', value, data); 10411 compare = mdVars.Fn('process', compare, data); 10412 10413 if(value === null || value === undefined) return false; 10414 10415 value = String(value); 10416 let items = String(compare).split(','); 10417 10418 for(let i = 0; i < items.length; i++) 10419 { 10420 if(items[i].trim() && value.includes(items[i].trim())) 10421 { 10422 return true; 10423 } 10424 } 10425 10426 return false; 10427 } 10428}); 10429 10430mdOperators.ItemAdd({ 10431 id: 'Not Contains', 10432 compare: true, 10433 backend: true, 10434 function: function(data, value, compare) 10435 { 10436 value = mdVars.Fn('process', value, data); 10437 compare = mdVars.Fn('process', compare, data); 10438 10439 if(value === null || value === undefined) return true; 10440 10441 value = String(value); 10442 compare = String(compare); 10443 10444 return !value.includes(compare); 10445 } 10446}); 10447 10448mdOperators.ItemAdd({ 10449 id: 'Not Contains Any', 10450 compare: true, 10451 backend: true, 10452 function: function(data, value, compare) 10453 { 10454 value = mdVars.Fn('process', value, data); 10455 compare = mdVars.Fn('process', compare, data); 10456 10457 if(value === null || value === undefined) return true; 10458 10459 value = String(value); 10460 let items = String(compare).split(','); 10461 10462 for(let i = 0; i < items.length; i++) 10463 { 10464 if(items[i].trim() && value.includes(items[i].trim())) 10465 { 10466 return false; 10467 } 10468 } 10469 10470 return true; 10471 } 10472}); 10473mdAnimations.FieldAdd('name'); 10474mdAnimations.FieldAdd('type'); 10475mdAnimations.FieldAdd('animate'); 10476 10477mdAnimations.FieldAdd('reverse'); 10478mdAnimations.FieldAdd('Reverse', (value, item) => 10479{ 10480 return mdAnimations.ItemGet(item.Get('reverse')); 10481}); 10482 10483mdAnimations.SetReload(false); 10484mdAnimations.Ready();mdAnimations.FunctionCreate('list', function (addon, type = null) 10485{ 10486 let list = []; 10487 10488 $.each(addon.ItemsGet(), (id, item) => 10489 { 10490 if(type === null || type === item.Get('type')) 10491 { 10492 list.push({title: item.Get('name'), value: item.Get('id')}); 10493 } 10494 }); 10495 10496 return list; 10497});mdAnimations.FunctionCreate('run', function (addon, name, targets, duration = 100, delay = 0, then = null) 10498{ 10499 let animation = mdAnimations.ItemGet(name); 10500 10501 if(!animation) 10502 { 10503 return mdBugs.ItemAdd({message: `Animation with name ${name} doesn't exist.`}); 10504 } 10505 10506 animation.Fn('run', targets, duration, delay, then); 10507});mdAnimations.FunctionCreate('item.run', function (addon, animation, targets, duration = 100, delay = 0, then = null) 10508{ 10509 if(!targets.length) 10510 { 10511 return mdBugs.ItemAdd({message: `Invalid target provided for animation ${animation.Get('id')}.`}); 10512 } 10513 10514 let start = delay; 10515 10516 $.each(targets, function() 10517 { 10518 if(delay < 1) 10519 { 10520 animation.Get('animate')(animation, this, duration,then); 10521 } 10522 else 10523 { 10524 setTimeout(() => 10525 { 10526 animation.Get('animate')(animation, this, duration, then); 10527 }, start); 10528 } 10529 10530 start += delay; 10531 }); 10532});mdAnimations.OnReady(function() 10533{ 10534 /* Fade */ 10535 mdAnimations.ItemsAdd({ 10536 id: 'fadeIn', 10537 name: 'Fade In', 10538 type: 'in', 10539 reverse: 'fadeOut', 10540 animate: function(item, target, duration, then) 10541 { 10542 target.animate([{opacity: '0'}, {opacity: '1'}], {duration, fill: 'forwards'}).onfinish = () => 10543 { 10544 if(then) { then(item); } 10545 } 10546 } 10547 }, { 10548 id: 'fadeOut', 10549 name: 'Fade Out', 10550 type: 'out', 10551 reverse: 'fadeIn', 10552 animate: function(item, target, duration, then) 10553 { 10554 target.animate([{opacity: '1'}, {opacity: '0'}], {duration, fill: 'forwards'}).onfinish = () => 10555 { 10556 if(then) { then(item); } 10557 } 10558 } 10559 }); 10560 10561 /* Fade Top */ 10562 mdAnimations.ItemsAdd({ 10563 id: 'fadeInTop', 10564 name: 'Fade In Top', 10565 type: 'in', 10566 reverse: 'fadeOutTop', 10567 animate: function(item, target, duration, then) 10568 { 10569 target.animate([{opacity: '0', transform: 'translateY(-50px)'}, {opacity: '1', transform: 'translateY(0)'}], {duration, fill: 'forwards'}).onfinish = () => 10570 { 10571 if(then) { then(item); } 10572 } 10573 } 10574 }, {
10575 id: 'fadeOutTop', 10576 name: 'Fade Out Top', 10577 type: 'out', 10578 reverse: 'fadeInTop', 10579 animate: function(item, target, duration, then) 10580 { 10581 target.animate([{opacity: '1', transform:'translateX(0)'}, {opacity: '0', transform: 'translateY(-50px)'}], {duration, fill: 'forwards'}).onfinish = () => 10582 { 10583 if(then) { then(item); } 10584 } 10585 } 10586 }); 10587 10588 /* Fade Down */ 10589 mdAnimations.ItemsAdd({ 10590 id: 'fadeInDown', 10591 name: 'Fade In Down', 10592 type: 'in', 10593 reverse: 'fadeOutDown', 10594 animate: function(item, target, duration, then) 10595 { 10596 target.animate([{opacity: '0', transform: 'translateY(50px)'}, {opacity: '1', transform: 'translateY(0)'}], {duration, fill: 'forwards'}).onfinish = () => 10597 { 10598 if(then) { then(item); } 10599 } 10600 } 10601 }, { 10602 id: 'fadeOutDown', 10603 name: 'Fade Out Down', 10604 type: 'out', 10605 reverse: 'fadeInDown', 10606 animate: function(item, target, duration, then) 10607 { 10608 target.animate([{opacity: '1', transform:'translateX(0)'}, {opacity: '0', transform: 'translateY(50px)'}], {duration, fill: 'forwards'}).onfinish = () => 10609 { 10610 if(then) { then(item); } 10611 } 10612 } 10613 }); 10614 10615 /* Fade Left */ 10616 mdAnimations.ItemsAdd({ 10617 id: 'fadeInLeft', 10618 name: 'Fade In Left', 10619 type: 'in', 10620 reverse: 'fadeOutLeft', 10621 animate: function(item, target, duration, then) 10622 { 10623 target.animate([{opacity: '0', transform:'translateX(-50px)'}, {opacity: '1', transform:'translateX(0)'}], {duration, fill: 'forwards'}).onfinish = () => 10624 { 10625 if(then) { then(item); } 10626 } 10627 } 10628 }, { 10629 id: 'fadeOutLeft', 10630 name: 'Fade Out Left', 10631 type: 'out', 10632 reverse: 'fadeInLeft', 10633 animate: function(item, target, duration, then) 10634 { 10635 target.animate([{opacity: '1', transform:'translateX(0)'}, {opacity: '0', transform:'translateX(-50px)'}], {duration, fill: 'forwards'}).onfinish = () => 10636 { 10637 if(then) { then(item); } 10638 } 10639 } 10640 }); 10641 10642 /* Fade Right */ 10643 mdAnimations.ItemsAdd({ 10644 id: 'fadeInRight', 10645 name: 'Fade In Right', 10646 type: 'in', 10647 reverse: 'fadeOutRight', 10648 animate: function(item, target, duration, then) 10649 { 10650 target.animate([{opacity: '0', transform:'translateX(50px)'}, {opacity: '1', transform:'translateX(0)'}], {duration, fill: 'forwards'}).onfinish = () => 10651 { 10652 if(then) { then(item); } 10653 } 10654 } 10655 }, { 10656 id: 'fadeOutRight', 10657 name: 'Fade Out Right', 10658 type: 'out', 10659 reverse: 'fadeInRight', 10660 animate: function(item, target, duration, then) 10661 { 10662 target.animate([{opacity: '1', transform:'translateX(0)'}, {opacity: '0', transform:'translateX(50px)'}], {duration, fill: 'forwards'}).onfinish = () => 10663 { 10664 if(then) { then(item); } 10665 } 10666 } 10667 }); 10668}); 10669mdVars.SetReload(false); 10670 10671mdVars.FieldAdd('options'); 10672mdVars.FieldAdd('description'); 10673mdVars.FieldAdd('value'); 10674mdVars.FieldAdd('type'); 10675mdVars.FieldAdd('group'); 10676 10677$dh.client = function() 10678{ 10679 return mdVars.ItemGet('client')?.Get('value'); 10680}; 10681 10682$dh.site = function() 10683{ 10684 return mdVars.ItemGet('site')?.Get('value'); 10685}; 10686 10687$dh.domain = function() 10688{ 10689 return mdVars.ItemGet('domain')?.Get('value'); 10690}; 10691 10692$dh.language = function() 10693{ 10694 return mdVars.ItemGet('language')?.Get('value'); 10695}; 10696 10697$dh.page = function() 10698{ 10699 return mdVars.ItemGet('page')?.Get('value'); 10700}; 10701 10702$dh.application = function() 10703{ 10704 return mdVars.ItemGet('application')?.Get('value'); 10705}; 10706 10707$dh.route = function(key = null) 10708{ 10709 let route = mdVars.ItemGet('route')?.Get('value'); 10710 10711 if(key) 10712 { 10713 return route?.[key]; 10714 } 10715 10716 return route; 10717}; 10718 10719$dh.query = function(key = null) 10720{ 10721 let query = mdVars.ItemGet('query')?.Get('value'); 10722 10723 if(key) 10724 { 10725 return query?.[key]; 10726 } 10727 10728 return query; 10729}; 10730 10731$dh.global = function() 10732{ 10733 return mdVars.ItemGet('global')?.Get('value'); 10734}; 10735 10736$dh.request = function() 10737{ 10738 return mdVars.ItemGet('request')?.Get('value'); 10739}; 10740 10741$dh.share = function() 10742{ 10743 return mdVars.ItemGet('share')?.Get('value'); 10744}; 10745 10746$dh.applications = function() 10747{ 10748 return mdVars.ItemGet('applications')?.Get('value'); 10749}; 10750 10751$dh.extensions = function() 10752{ 10753 return mdVars.ItemGet('extensions')?.Get('value'); 10754}; 10755 10756$dh.plugins = function() 10757{ 10758 return mdVars.ItemGet('plugins')?.Get('value'); 10759}; 10760 10761$dh.prerender = function() 10762{ 10763 return mdVars.ItemGet('prerender')?.Get('value'); 10764}; 10765 10766$dh.var = function(variable, process = false, data = {}) 10767{ 10768 if(process) 10769 { 10770 return mdVars.Fn('process', variable); 10771 } 10772 10773 return mdVars.Fn('build', variable).value; 10774}; 10775 10776$dh.vars = function() 10777{ 10778 return mdVars.ItemsRaw(); 10779}; 10780 10781function variable(variable) 10782{ 10783 return mdVars.Fn('build', variable).value; 10784}mdVars.FunctionCreate('build', function(mdVars, string, data = {}) 10785{ 10786 if(!data || typeof data !== 'object') 10787 { 10788 data = {}; 10789 } 10790 10791 this.init = () => 10792 { 10793 let build = this.build(string); 10794 10795 build.value = this.value(build.name, data); 10796 build.functions.forEach((fn, index) => 10797 { 10798 if(build['name'] === '#' && index === 0) 10799 { 10800 build['value'] = fnc(fn['name'], data, null, ...fn['parameters']); 10801 } 10802 else 10803 { 10804 if(build['value'] instanceof Promise) 10805 { 10806 build['value'] = this.promise(build['value'], build.functions, index); 10807 return false;
10808 } 10809 10810 build['value'] = fnc(fn['name'], data, build['value'], ...fn['parameters']); 10811 } 10812 }); 10813 10814 return build; 10815 }; 10816 10817 this.promise = (promise, functions, i) => 10818 { 10819 return new Promise((resolve) => 10820 { 10821 promise.then((response) => 10822 { 10823 functions.forEach((fn, index) => 10824 { 10825 if(index < i) 10826 { 10827 return true; 10828 } 10829 10830 if(response instanceof Promise) 10831 { 10832 resolve(this.promise(response, functions, index)); 10833 return false; 10834 } 10835 10836 resolve(fnc(fn['name'], data, response, ...fn['parameters'])); 10837 }); 10838 }); 10839 }); 10840 }; 10841 10842 this.value = (name, data) => 10843 { 10844 if(name in data) 10845 { 10846 return data[name]; 10847 } 10848 10849 let variable = mdVars.ItemGet(name); 10850 10851 if(variable) 10852 { 10853 return variable.Get('value'); 10854 } 10855 10856 return null; 10857 }; 10858 10859 this.build = (string) => 10860 { 10861 let match = {data: string, name: null, value: null, functions: []}; 10862 10863 if(typeof string !== 'string') 10864 { 10865 return match; 10866 } 10867 10868 match.data = match.data.split('->') 10869 10870 if(0 in match.data) 10871 { 10872 match.name = match.data[0]; 10873 match.data.shift(); 10874 } 10875 10876 match.data.forEach((fn) => 10877 { 10878 let data = {name: '', start: false, param: null, parameters: []}; 10879 10880 fn.split('').forEach((char, index) => 10881 { 10882 if(char !== '(' && !data.start) 10883 { 10884 data.name += char; 10885 } 10886 10887 if(char === '(' && !data.start) 10888 { 10889 data.start = true; 10890 } 10891 10892 if(!data.start) 10893 { 10894 return true; 10895 } 10896 10897 if(char === "'" && data.param === null) 10898 { 10899 data.param = data.parameters.length; 10900 data.parameters[data.param] = ''; 10901 } 10902 else if(char === '/' && data.param !== null && fn[index + 1] === "'") 10903 { 10904 return true; 10905 } 10906 else if(char === "'" && data.param !== null && fn[index - 1] === '/') 10907 { 10908 data.parameters[data.param] += char; 10909 } 10910 else if(char === "'" && data.param !== null) 10911 { 10912 data.param = null; 10913 } 10914 else if(data.param !== null) 10915 { 10916 data.parameters[data.param] += char; 10917 } 10918 }); 10919 10920 match.functions.push(data); 10921 }); 10922 10923 return match; 10924 }; 10925 10926 return this; 10927});mdVars.FunctionCreate('merge', function(addon, config = {}, data = {}, variables = {}) 10928{ 10929 if(!config || typeof config !== 'object') 10930 { 10931 config = {}; 10932 } 10933 10934 if(!data || typeof data !== 'object') 10935 { 10936 data = {}; 10937 } 10938 10939 $.each(config, (key, item) => 10940 { 10941 if(!(key in data)) 10942 { 10943 data[key] = item.value ? item.value : null; 10944 } 10945 }); 10946 10947 $.each(data, (key, value) => 10948 { 10949 if(!(key in config)) 10950 { 10951 delete data[key]; 10952 } 10953 else 10954 { 10955 data[key] = mdVars.Fn('process', data[key], variables); 10956 } 10957 }); 10958 10959 /* Render Components */ 10960 $.each(data, (key, value) => 10961 { 10962 if(config[key].type === 'COMPONENT') 10963 { 10964 let component = components.ItemGet(value?.id); 10965 10966 if(!component) 10967 { 10968 data[key] = 'Component doesn\'t exist.'; 10969 return true; 10970 } 10971 10972 // if(variables.symbols.includes(component.Get('id'))) 10973 // { 10974 // data[key] = 'Component can\'t be rendered.'; 10975 // return true; 10976 // } 10977 // else 10978 // { 10979 // variables.symbols.push(component.Get('id')) 10980 // } 10981 10982 let merge = mdVars.Fn('merge', component.Get('variables'), value?.data, variables); 10983 10984 variables.widget = 'widget' in variables ? $.extend(variables.widget, merge) : merge; 10985 10986 data[key] = component.Fn('render.html', variables, null, false); 10987 } 10988 10989 else if(config[key].type === 'BLOCKS') 10990 { 10991 value.forEach((object) => 10992 { 10993 $.each(object, (objectKey, objectValue) => 10994 { 10995 object[objectKey] = mdVars.Fn('process', objectValue, variables); 10996 }); 10997 }); 10998 } 10999 }); 11000 11001 return data; 11002});mdVars.FunctionCreate('process', function(mdVars, string, data = {}) 11003{ 11004 if(typeof string !== 'string') 11005 { 11006 return string; 11007 } 11008 11009 if(string.startsWith('\\$')) 11010 { 11011 return mdVars.Fn('var', string.substring(1), data); 11012 } 11013 else if(string.startsWith('\\return ')) 11014 { 11015 return mdVars.Fn('var', string.substring(1), data); 11016 } 11017 else if(string.startsWith('${')) 11018 { 11019 return mdVars.Fn('var', string, data); 11020 } 11021 else if(string.startsWith('$')) 11022 { 11023 return mdVars.Fn('build', string.substring(1), data).value; 11024 } 11025 else if(string.startsWith('return ')) 11026 { 11027 return mdVars.Fn('return', string.substring(6), data); 11028 } 11029 11030 return mdVars.Fn('var', string, data); 11031});mdVars.FunctionCreate('return', function(mdVars, string, data) 11032{ 11033 let matches = []; 11034 let values = {}; 11035 11036 if(typeof string !== 'string') { return string; } 11037 if(!data || typeof data !== 'object') { data = {}; } 11038 11039 matches = string.match(/\$\{[^}]*}/g); 11040 11041 if(matches) 11042 {
11043 matches.forEach((match, index) => 11044 { 11045 let value = match.substring(2, match.length - 1); 11046 let build = mdVars.Fn('build', value, data); 11047 11048 data['val-' + index] = build.value; 11049 11050 string = string.replace(match, `data['val-${index}']`); 11051 }); 11052 } 11053 11054 try 11055 { 11056 let fn; 11057 11058 if(string.includes('return')) 11059 { 11060 fn = new Function('data', string); 11061 11062 } 11063 else 11064 { 11065 fn = new Function('data', 'return (' + string + ')'); 11066 } 11067 11068 return fn(data); 11069 } 11070 catch(error) 11071 { 11072 return error; 11073 } 11074});mdVars.FunctionCreate('var', function(mdVars, string, data = {}) 11075{ 11076 let matches = []; 11077 11078 if(typeof string !== 'string') { return string; } 11079 if(!data || typeof data !== 'object') { data = {}; } 11080 11081 matches = string.match(/\$\{[^}]*}/g); 11082 11083 if(!matches) 11084 { 11085 return string; 11086 } 11087 11088 matches.forEach((match) => 11089 { 11090 let value = match.substring(2, match.length - 1); 11091 let build = mdVars.Fn('build', value, data); 11092 11093 string = string.replace(match, build.value); 11094 }); 11095 11096 return string; 11097}); 11098mdLibraries.SetReload(false); 11099mdLibraries.FieldAdd('loaded'); 11100mdLibraries.FieldAdd('loading'); 11101mdLibraries.FieldAdd('callbacks', function(value) 11102{ 11103 if(Array.isArray(value)) 11104 { 11105 return value; 11106 } 11107 11108 return []; 11109}); 11110 11111mdLibraries.FieldAdd('js', function(value) 11112{ 11113 return Array.isArray(value) ? value : []; 11114}); 11115 11116mdLibraries.FieldAdd('css', function(value) 11117{ 11118 return Array.isArray(value) ? value : []; 11119});/* Visitor: load Lucide only when icons are present on the page */ 11120mdEvents.Fn('catch', 'dh.pages.ready', function() 11121{ 11122 if(document.querySelector('[data-lucide]')) 11123 { 11124 mdLibraries.Fn('load', ['lucide'], function() 11125 { 11126 lucide.createIcons(); 11127 }); 11128 } 11129}); 11130 11131mdEvents.Fn('catch', 'dh.pages.load', function() 11132{ 11133 if(document.querySelector('[data-lucide]')) 11134 { 11135 mdLibraries.Fn('load', ['lucide'], function() 11136 { 11137 lucide.createIcons(); 11138 }); 11139 } 11140}); 11141mdLibraries.FunctionCreate('editorjs.html', function(tags, blocks) 11142{ 11143 this.html = ''; 11144 11145 this.init = () => 11146 { 11147 try 11148 { 11149 blocks.forEach(block => 11150 { 11151 if(block.type in this) 11152 { 11153 this[block.type](block); 11154 } 11155 else 11156 { 11157 this.html += '<p>Missing block: ' + block.type + '</p>'; 11158 } 11159 }); 11160 11161 return this.html; 11162 } 11163 catch(error) 11164 { 11165 return 'Invalid Rich-Text data. ' + error; 11166 } 11167 }; 11168 11169 /* Header */ 11170 this.header = (block, css = []) => 11171 { 11172 if('tunes' in block && 'alignTune' in block.tunes && block.tunes.alignTune['alignment']) 11173 { 11174 css.push('text-align: ' + block.tunes.alignTune['alignment']); 11175 } 11176 11177 this.html += `<h${block.data.level} ${css.length ? `style="${css.join(';')}"` : ''}>${block.data.text}</h${block.data.level}>`; 11178 }; 11179 11180 /* Paragraph */ 11181 this.paragraph = (block, css = [], classes = []) => 11182 { 11183 if('tunes' in block) 11184 { 11185 if('alignTune' in block.tunes && block.tunes.alignTune['alignment']) 11186 { 11187 if(block.tunes.alignTune['alignment'] !== 'left') 11188 { 11189 css.push('text-align: ' + block.tunes.alignTune['alignment'] ); 11190 } 11191 } 11192 11193 if('alertVariant' in block.tunes && block.tunes.alertVariant) 11194 { 11195 classes.push('dh-rich-text-' + block.tunes.alertVariant); 11196 } 11197 } 11198 11199 this.html += `<p ${classes.length ? `class="${classes.join(' ')}"` : ''} ${css.length ? `style="${css.join(';')}"` : ''}>${block.data.text}</p>`; 11200 }; 11201 11202 /* Quote */ 11203 this.quote = (block) => 11204 { 11205 let caption = ''; 11206 11207 if('caption' in block.data && block.data.caption) 11208 { 11209 caption = `<cite>${block.data.caption}</cite>`; 11210 } 11211 11212 this.html += `<blockquote>${block.data.text}${caption}</blockquote>`; 11213 }; 11214 11215 /* Raw */ 11216 this.raw = (block) => 11217 { 11218 this.html += block.data.html.replace(/<script\b[^<]*>([\s\S]*?)<\/script>/gi, function(match, p1) 11219 { 11220 return `<script>${p1}</script>`; 11221 }); 11222 }; 11223 11224 /* Delimiter */ 11225 this.delimiter = (block) => 11226 { 11227 this.html += '<hr />'; 11228 }; 11229 11230 /* Embed */ 11231 this.embed = (block) => 11232 { 11233 switch (block.data.service) 11234 { 11235 case 'youtube': 11236 case 'vimeo': 11237 this.html += ` 11238 <div class="dh-rich-text-embed"> 11239 <iframe src="${block.data.embed}" frameborder="0" allowfullscreen></iframe> 11240 </div> 11241 `; 11242 break; 11243 } 11244 }; 11245 11246 /* Table */ 11247 this.table = (block) => 11248 { 11249 let table = '<div class="dh-rich-text-table"><table>'; 11250 11251 block.data.content.forEach((row) => 11252 { 11253 table += '<tr>'; 11254 11255 row.forEach((cell) => 11256 { 11257 table += `<td>${cell}</td>`;
11258 }); 11259 11260 table += '</tr>'; 11261 }); 11262 11263 this.html += table + '</table></div>'; 11264 }; 11265 11266 this.image = (block, figure = [], image = []) => 11267 { 11268 figure.push('dh-rich-text-image'); 11269 11270 if(block.data.withBackground) 11271 { 11272 figure.push('background'); 11273 } 11274 11275 if(block.data.withBorder) 11276 { 11277 figure.push('border'); 11278 } 11279 11280 if(block.data.stretched) 11281 { 11282 image.push('stretched'); 11283 } 11284 11285 if(block.data.withBackground) 11286 { 11287 image.push('strebackgroundtched'); 11288 } 11289 11290 let src = `src="${block.data.file.url}"`; 11291 let alt = block.data.caption ? `alt="${block.data.caption}"` : ''; 11292 let caption = block.data.caption ? `<figcaption>${block.data.caption}</figcaption>` : ''; 11293 11294 this.html += ` 11295 <figure ${figure.length ? `class="${figure.join(' ')}"` : ''}> 11296 <img ${image.length ? `class="${image.join(' ')}"` : ''} ${src} ${alt}> 11297 ${caption} 11298 </figure> 11299 `; 11300 }; 11301 11302 11303 this.list = (block) => 11304 { 11305 let type = block.data.style === 'ordered' ? 'ol' : 'ul'; 11306 11307 this.html += `<${type}>${this.listRender(block.data.items, type)}</${type}>`; 11308 }; 11309 11310 this.listRender = (items, type) => 11311 { 11312 let list = ''; 11313 11314 items.forEach((item) => 11315 { 11316 list += `<li>${item.content}`; 11317 11318 if('items' in item && item.items.length) 11319 { 11320 list += `<${type}>${this.listRender(item.items, type)}</${type}>`; 11321 } 11322 11323 list += '</li>'; 11324 }); 11325 11326 return list; 11327 }; 11328 11329 this.code = (block) => 11330 { 11331 this.html += `<pre><code>${block.data.code}</code></pre>`; 11332 } 11333 11334 return this; 11335});mdLibraries.FunctionCreate('load', function(addon, libraries, then) 11336{ 11337 let items = []; 11338 let loaded = 0; 11339 11340 libraries.forEach(function(library) 11341 { 11342 library = mdLibraries.ItemGet(library); 11343 11344 if(!library || library.Get('loaded')) 11345 { 11346 return true; 11347 } 11348 11349 items.push(library); 11350 }); 11351 11352 if(!items.length) 11353 { 11354 return then ? then() : null; 11355 } 11356 11357 items.forEach(function(library) 11358 { 11359 if(library.Get('loading')) 11360 { 11361 library.Fn('callback', then); 11362 return true; 11363 } 11364 11365 library.Fn('load', function() 11366 { 11367 loaded++; 11368 11369 if(loaded === items.length) 11370 { 11371 if(then) { then(); } 11372 } 11373 }); 11374 }); 11375});mdLibraries.FunctionCreate('item.callback', function(addon, item, callback) 11376{ 11377 let callbacks = item.Get('callbacks'); 11378 11379 callbacks.push(callback); 11380 11381 item.Set('callbacks', callbacks); 11382});mdLibraries.FunctionCreate('item.load', function(addon, item, then) 11383{ 11384 let requests = []; 11385 let loaded = 0; 11386 11387 item.Get('css').forEach(function(link) 11388 { 11389 requests.push({type: 'css', link}); 11390 }); 11391 11392 item.Get('js').forEach(function(link) 11393 { 11394 requests.push({type: 'js', link}); 11395 }); 11396 11397 item.Set('loading', true); 11398 11399 if(!requests.length) 11400 { 11401 item.Set('loaded', true); 11402 item.Set('loading', false); 11403 item.Set('callbacks', []); 11404 11405 if(then) { then(); } 11406 11407 item.Get('callbacks').forEach(function(callback) 11408 { 11409 callback(); 11410 }); 11411 11412 return; 11413 } 11414 11415 requests.forEach(function(request) 11416 { 11417 let tag; 11418 11419 if(request.type === 'js') 11420 { 11421 tag = document.createElement('script'); 11422 tag.src = request.link; 11423 tag.type = 'text/javascript'; 11424 } 11425 else 11426 { 11427 tag = document.createElement('link'); 11428 tag.rel = 'stylesheet'; 11429 tag.type = 'text/css'; 11430 tag.href = request.link; 11431 } 11432 11433 tag.onload = function() 11434 { 11435 loaded++; 11436 11437 if(loaded === requests.length) 11438 { 11439 item.Set('loaded', true); 11440 item.Set('loading', false); 11441 11442 if(then) { then(); } 11443 11444 item.Get('callbacks').forEach(function(callback) 11445 { 11446 callback(); 11447 }); 11448 } 11449 }; 11450 11451 document.head.appendChild(tag); 11452 }); 11453});mdLibraries.ItemAdd({ 11454 id: 'markdown', 11455 js: ['https://global.divhunt.com/assets/library/Markdown.js'], 11456}); 11457 11458mdLibraries.ItemAdd({ 11459 id: 'pickr', 11460 js: ['https://global.divhunt.com/assets/library/Pickr.js'], 11461 css: ['https://global.divhunt.com/assets/library/Pickr.css'] 11462}); 11463 11464mdLibraries.ItemAdd({ 11465 id: 'coloris', 11466 js: ['https://global.divhunt.com/assets/library/Coloris.js'], 11467 css: ['https://global.divhunt.com/assets/library/Coloris.css'] 11468}); 11469 11470mdLibraries.ItemAdd({ 11471 id: 'alwan', 11472 js: ['https://global.divhunt.com/assets/library/AlwanColorPicker.js'], 11473 css: ['https://global.divhunt.com/assets/library/AlwanColorPicker.css'] 11474}); 11475 11476mdLibraries.ItemAdd({ 11477 id: 'gsap', 11478 js: ['https://global.divhunt.com/assets/library/Gsap26.js'], 11479}); 11480 11481mdLibraries.ItemAdd({ 11482 id: 'gsap-devtools', 11483 js: ['https://global.divhunt.com/assets/library/GsapDevTools26.js'], 11484}); 11485 11486mdLibraries.ItemAdd({ 11487 id: 'monaco', 11488 js: ['https://global.divhunt.com/assets/library/vs/loader.js'], 11489}); 11490 11491mdLibraries.ItemAdd({ 11492 id: 'trumbowyg', 11493 js: ['https://global.divhunt.com/assets/library/Trumbowyg.js'], 11494 css: ['https://global.divhunt.com/assets/library/Trumbowyg.css'] 11495}); 11496 11497mdLibraries.ItemAdd({ 11498 id: 'interact', 11499 js: ['https://global.divhunt.com/assets/library/Interact.js'], 11500}); 11501 11502mdLibraries.ItemAdd({ 11503 id: 'chartjs', 11504 js: ['https://global.divhunt.com/assets/library/Chart.js'], 11505}); 11506 11507mdLibraries.ItemAdd({ 11508 id: 'sortablejs', 11509 js: ['https://global.divhunt.com/assets/library/Sortable.js'], 11510}); 11511 11512mdLibraries.ItemAdd({ 11513 id: 'flatpickr', 11514 js: ['https://global.divhunt.com/assets/library/Flatpickr.js'], 11515 css: ['https://global.divhunt.com/assets/library/Flatpickr.css'], 11516}); 11517 11518mdLibraries.ItemAdd({ 11519 id: 'jszip', 11520 js: ['https://global.divhunt.com/assets/library/JSZip.js'], 11521}); 11522 11523mdLibraries.ItemAdd({ 11524 id: 'filesaver', 11525 js: ['https://global.divhunt.com/assets/library/FileSaver.js'], 11526}); 11527 11528mdLibraries.ItemAdd({ 11529 id: 'papaparse', 11530 js: ['https://global.divhunt.com/assets/library/PapaParse.js'], 11531}); 11532 11533mdLibraries.ItemAdd({ 11534 id: 'editorjs', 11535 js: [ 11536 'https://global.divhunt.com/assets/library/Editor.js', 11537 'https://global.divhunt.com/assets/library/Editor.check-list.js', 11538 'https://global.divhunt.com/assets/library/Editor.code.js', 11539 'https://global.divhunt.com/assets/library/Editor.embed.js', 11540 'https://global.divhunt.com/assets/library/Editor.paragraph.js', 11541 'https://global.divhunt.com/assets/library/Editor.header.js', 11542 'https://global.divhunt.com/assets/library/Editor.quote.js', 11543 'https://global.divhunt.com/assets/library/Editor.image.js', 11544 'https://global.divhunt.com/assets/library/Editor.link.js', 11545 'https://global.divhunt.com/assets/library/Editor.nested-list.js', 11546 'https://global.divhunt.com/assets/library/Editor-underline.js', 11547 'https://global.divhunt.com/assets/library/Editor.raw.js', 11548 'https://global.divhunt.com/assets/library/Editor.table.js', 11549 'https://global.divhunt.com/assets/library/Editor.delimeter.js', 11550 'https://global.divhunt.com/assets/library/Editor.hyperlink.js', 11551 'https://global.divhunt.com/assets/library/Editor.align.js', 11552 'https://global.divhunt.com/assets/library/Editor.alert.js', 11553 'https://global.divhunt.com/assets/library/Editor.styles.js', 11554 'https://global.divhunt.com/assets/library/Editor.code.umd.js' 11555 ] 11556}); 11557 11558mdLibraries.ItemAdd({ 11559 id: 'air-datepicker', 11560 js: ['https://global.divhunt.com/assets/library/AirDatepicker.js'], 11561 css: ['https://global.divhunt.com/assets/library/AirDatepicker.css'], 11562}); 11563 11564mdLibraries.ItemAdd({ 11565 id: 'lucide', 11566 js: ['https://unpkg.com/lucide@latest/dist/umd/lucide.min.js'], 11567}); 11568mdSources.SetReload(false); 11569 11570mdSources.FieldAdd('title'); 11571mdSources.FieldAdd('folder'); 11572mdSources.FieldAdd('description'); 11573 11574mdSources.FieldAdd('properties', null, (value) => 11575{ 11576 return Object.keys(value).length ? value : null; 11577}); 11578 11579mdSources.FieldAdd('sort', null, (value) => 11580{ 11581 return Object.keys(value).length ? value : null; 11582}); 11583 11584mdSources.FieldAdd('fields', null, (value) => 11585{ 11586 return Object.keys(value).length ? value : null; 11587}); 11588 11589mdSources.FieldAdd('operators', null, (value) => 11590{ 11591 return Object.keys(value).length ? value : null; 11592}); 11593 11594$dh.sources = function() 11595{ 11596 return mdSources.ItemsRaw(); 11597}; 11598 11599$dh.source = function(id, properties = {}, filters = [], sort = '', data = {}, callback = null) 11600{ 11601 let source = mdSources.ItemGet(id); 11602 11603 if(!source) 11604 { 11605 return callback ? callback([]) : []; 11606 } 11607 11608 mdSources.Fn('batch.queue', source, properties, filters, sort, data, callback); 11609};mdEvents.Fn('catch', 'dh.pages.load', (data) => { 11610 11611 console.log("LOAD PAGE", data); 11612 mdSources.Fn('dynamicFilters.setup'); 11613 mdSources.Fn('pagination.setup'); 11614 mdSources.Fn('multiSelectDropdown.setup'); 11615 11616}); 11617 11618/* Collects source runs fired during the same render pass into a single 11619 request. Identical queries share one result, and a failing batch falls 11620 back to the original per-source endpoint so nothing renders empty. */ 11621 11622mdSources.FunctionCreate('batch.queue', function(addon, item, properties = {}, filters = [], sort = '', data = {}, callback = null) 11623{ 11624 properties = JSON.parse(JSON.stringify(properties)); 11625 filters = JSON.parse(JSON.stringify(filters)); 11626 sort = mdVars.Fn('process', sort, data); 11627 11628 $.each(properties, function(key, value) 11629 { 11630 properties[key] = mdVars.Fn('process', value, data); 11631 }); 11632 11633 $.each(filters, function(key, field) 11634 { 11635 filters[key].value = mdVars.Fn('process', field.value, data); 11636 }); 11637 11638 let request = {id: item.Get('id'), properties, filters, sort}; 11639 let key = JSON.stringify(request); 11640 11641 let queue = addon.GetTemp('batch.queue', []); 11642 11643 /* Same query already queued in this window - ride along on its result 11644 instead of asking the server twice. */ 11645 let existing = null; 11646 11647 $.each(queue, function(index, entry) 11648 { 11649 if(entry.key === key) 11650 { 11651 existing = entry; 11652 return false;
11653 } 11654 }); 11655 11656 if(existing) 11657 { 11658 existing.callbacks.push(callback); 11659 return; 11660 } 11661 11662 queue.push({key, request, callbacks: [callback]}); 11663 addon.SetTemp('batch.queue', queue); 11664 11665 if(addon.GetTemp('batch.timer', null)) 11666 { 11667 return; 11668 } 11669 11670 addon.SetTemp('batch.timer', setTimeout(function() 11671 { 11672 addon.Fn('batch.flush'); 11673 }, 0)); 11674}); 11675 11676mdSources.FunctionCreate('batch.flush', function(addon) 11677{ 11678 let queue = addon.GetTemp('batch.queue', []); 11679 11680 addon.SetTemp('batch.queue', []); 11681 addon.SetTemp('batch.timer', null); 11682 11683 if(!queue.length) 11684 { 11685 return; 11686 } 11687 11688 /* The query string is the limit here, not the server - stay well under 11689 the usual 8KB header ceiling by splitting into chunks. */ 11690 let size = 10; 11691 11692 for(let i = 0; i < queue.length; i += size) 11693 { 11694 addon.Fn('batch.send', queue.slice(i, i + size)); 11695 } 11696}); 11697 11698mdSources.FunctionCreate('batch.send', function(addon, entries) 11699{ 11700 let object = JSON.stringify(entries.map((entry) => entry.request)); 11701 11702 let resolve = (entry, value) => 11703 { 11704 $.each(entry.callbacks, function(index, callback) 11705 { 11706 callback ? callback(value) : null; 11707 }); 11708 }; 11709 11710 /* Any failure of the batch itself must not lose the data - replay each 11711 entry through the single-source endpoint that is known to work. */ 11712 let fallback = () => 11713 { 11714 $.each(entries, function(index, entry) 11715 { 11716 let item = mdSources.ItemGet(entry.request.id); 11717 11718 if(!item) 11719 { 11720 return resolve(entry, null); 11721 } 11722 11723 item.Fn('run', entry.request.properties, entry.request.filters, entry.request.sort, {}, function(json) 11724 { 11725 resolve(entry, json); 11726 }); 11727 }); 11728 }; 11729 11730 mdAjax.Fn('get').get({data: encodeURIComponent(object)}, null).url('/api/md.sources/run.batch').run(null, function(response) 11731 { 11732 let results = response && response.out ? response.out.data : null; 11733 11734 if(!results || results.length !== entries.length) 11735 { 11736 return fallback(); 11737 } 11738 11739 $.each(entries, function(index, entry) 11740 { 11741 let result = results[index]; 11742 11743 resolve(entry, result && result.success ? result.data : null); 11744 }); 11745 }, 11746 function() 11747 { 11748 fallback(); 11749 }); 11750}); 11751mdSources.FunctionCreate('dynamicFilters.setup', function(addon) 11752{ 11753 let debounceTimers = {}; 11754 let processedSources = new Set(); 11755 let boundSources = new Set(); 11756 let sourceContainers = new Map(); 11757 let sourceElementInfo = new Map(); 11758 11759 // Inject CSS for dynamic filter states 11760 if(!document.getElementById('dh-dynamic-filters-styles')) 11761 { 11762 $('head').append('<style id="dh-dynamic-filters-styles">' + 11763 '.dh-source-loading { opacity: 0.5; pointer-events: none; transition: opacity 0.2s; }' + 11764 '.dh-source-no-results { padding: 20px; text-align: center; color: #888; font-size: 14px; }' + 11765 '</style>'); 11766 } 11767 11768 // Main function to scan page for elements with dynamic filters 11769 this.scanForDynamicFilters = () => 11770 { 11771 processedSources.clear(); 11772 11773 // Look for elements with filter config reference 11774 let foundElements = $('[dh-source][dh-filter-config]'); 11775 11776 foundElements.each((index, element) => 11777 { 11778 let sourceId = $(element).attr('dh-source'); 11779 let configId = $(element).attr('dh-filter-config'); 11780 11781 // Remember the parent container and element info for this source 11782 if(!sourceContainers.has(sourceId)) 11783 { 11784 let $container = $(element).parent(); 11785 sourceContainers.set(sourceId, $container); 11786 11787 // Store element info 11788 let isComponent = $(element).is('[component]'); 11789 let elementId = null; 11790 11791 if(isComponent) 11792 { 11793 elementId = $(element).attr('component'); 11794 } 11795 else 11796 { 11797 elementId = $(element).attr('class').split(' ')[0].replace('t', ''); 11798 } 11799 11800 sourceElementInfo.set(sourceId, { 11801 isComponent: isComponent, 11802 elementId: elementId 11803 }); 11804 } 11805
11806 if(processedSources.has(sourceId)) { 11807 return; 11808 } 11809 processedSources.add(sourceId); 11810 11811 // Only bind if we haven't already bound this source 11812 if(boundSources.has(sourceId)) { 11813 return; 11814 } 11815 11816 // Get config from temp storage 11817 let filterConfig = mdSources.GetTemp(configId); 11818 11819 if(filterConfig && filterConfig.dynamicFilters && filterConfig.dynamicFilters.length) 11820 { 11821 let source = { 11822 id: sourceId, 11823 dynamicFilters: filterConfig.dynamicFilters, 11824 dynamicFiltersMode: filterConfig.dynamicFiltersMode || 'change', 11825 dynamicFiltersTriggerButton: filterConfig.dynamicFiltersTriggerButton 11826 }; 11827 11828 // Get full source configuration 11829 if($(element).is('[component]')) 11830 { 11831 let componentId = $(element).attr('component'); 11832 let component = components.ItemGet(componentId); 11833 if(component && component.Get('source')) 11834 { 11835 source = {...component.Get('source'), ...filterConfig}; 11836 } 11837 } 11838 else 11839 { 11840 let tagId = $(element).attr('class').split(' ')[0].replace('t', ''); 11841 let tag = components.tags.ItemGet(tagId); 11842 if(tag && tag.Get('source')) 11843 { 11844 source = {...tag.Get('source'), ...filterConfig}; 11845 } 11846 } 11847 11848 this.bindSourceFilters(sourceId, source); 11849 boundSources.add(sourceId); 11850 11851 // Apply URL filters and trigger initial load only if URL has filters 11852 setTimeout(() => { 11853 let hasUrlFilters = this.applyURLFilters(source); 11854 if(hasUrlFilters) 11855 { 11856 this.updateAllElementsWithSource(sourceId, source); 11857 } 11858 }, 120); 11859 } 11860 }); 11861 }; 11862 11863 this.smartFieldMatch = (source) => 11864 { 11865 let item = mdSources.ItemGet(source.id); 11866 if(!item || !item.Get('fields')) return; 11867 11868 let params = new URLSearchParams(window.location.search); 11869 let hasUrlFilters = false; 11870 11871 params.forEach((value, key) => { 11872 if(key.startsWith('filter_')) { 11873 hasUrlFilters = true; 11874 } 11875 }); 11876 11877 if(!hasUrlFilters) return; 11878 11879 // Update filter field IDs based on field names 11880 source.dynamicFilters.forEach(filter => { 11881 let currentFieldName = item.Get('fields')[filter.field]?.name; 11882 11883 // If field ID doesn't match name in URL, try to find correct field 11884 $.each(item.Get('fields'), (id, field) => { 11885 params.forEach((value, key) => { 11886 if(key === `filter_${field.name}` && field.name !== currentFieldName) { 11887 // Update filter to use correct field ID 11888 filter.field = id; 11889 } 11890 }); 11891 }); 11892 }); 11893 }; 11894 11895 // Bind filters per source 11896 this.bindSourceFilters = (sourceId, source) => 11897 { 11898 // Get the global trigger mode (default to 'change' if not set) 11899 let globalMode = source.dynamicFiltersMode || 'change'; 11900 11901 this.smartFieldMatch(source); 11902 11903 if(globalMode === 'button') 11904 { 11905 // Button click mode - bind to the trigger button 11906 let triggerButton = $(source.dynamicFiltersTriggerButton); 11907 11908 if(!triggerButton.length) 11909 { 11910 console.warn('Dynamic filter trigger button not found:', source.dynamicFiltersTriggerButton); 11911 return; 11912 } 11913 11914 // Remove old listener and add new one 11915 triggerButton.off('click.dynamicFilter-' + sourceId); 11916 triggerButton.on('click.dynamicFilter-' + sourceId, (e) => 11917 { 11918 e.preventDefault(); 11919 this.updateAllElementsWithSource(sourceId, source); 11920 }); 11921 } 11922 else 11923 { 11924 // Change mode - bind to each filter target
11925 source.dynamicFilters.forEach((filter, index) => 11926 { 11927 let targetElement = $(filter.target); 11928 11929 if(!targetElement.length) 11930 { 11931 return; 11932 } 11933 11934 let eventType = this.getEventType(globalMode); 11935 let debounceKey = sourceId + '-' + index; 11936 11937 // Remove old listeners and add new ones 11938 targetElement.off(eventType + '.dynamicFilter-' + sourceId); 11939 targetElement.on(eventType + '.dynamicFilter-' + sourceId, () => 11940 { 11941 let currentValue = this.getElementValue(targetElement); 11942 11943 // For now, update immediately (no debounce) 11944 this.updateAllElementsWithSource(sourceId, source); 11945 }); 11946 }); 11947 } 11948 11949 // Bind clear/reset buttons (elements with class .dh-source-clear-filters) 11950 let clearButtons = $('.dh-source-clear-filters'); 11951 clearButtons.off('click.clearFilter-' + sourceId); 11952 clearButtons.on('click.clearFilter-' + sourceId, (e) => 11953 { 11954 e.preventDefault(); 11955 this.clearAllFilters(source); 11956 this.updateAllElementsWithSource(sourceId, source); 11957 }); 11958 }; 11959 11960 this.clearAllFilters = (source) => 11961 { 11962 source.dynamicFilters.forEach((filter) => 11963 { 11964 let targetElement = $(filter.target); 11965 if(!targetElement.length) return; 11966 11967 switch(filter.fieldType) 11968 { 11969 case 'toggle': 11970 targetElement.prop('checked', false); 11971 break; 11972 case 'checkbox_group': 11973 let name = targetElement.attr('name'); 11974 if(name) 11975 { 11976 $(`input[type="checkbox"][name="${name}"]`).prop('checked', false); 11977 } 11978 else 11979 { 11980 targetElement.prop('checked', false); 11981 } 11982 break; 11983 case 'radio': 11984 let radioName = targetElement.attr('name'); 11985 if(radioName) 11986 { 11987 $(`input[type="radio"][name="${radioName}"]`).prop('checked', false); 11988 } 11989 break; 11990 case 'multi_select': 11991 targetElement.val([]).trigger('change.select2'); 11992 break; 11993 case 'select': 11994 targetElement.val('').trigger('change.select2'); 11995 break; 11996 default: 11997 targetElement.val(''); 11998 } 11999 }); 12000 12001 // Clear URL parameters 12002 let params = new URLSearchParams(window.location.search); 12003 source.dynamicFilters.forEach((filter) => 12004 { 12005 let targetElement = $(filter.target); 12006 if(targetElement.length && targetElement.attr('name')) 12007 { 12008 params.delete(targetElement.attr('name')); 12009 } 12010 }); 12011 params.delete('page'); 12012 12013 let newURL = window.location.pathname; 12014 if(params.toString()) 12015 { 12016 newURL += '?' + params.toString(); 12017 } 12018 history.pushState(null, '', newURL); 12019 12020 // Clear cache 12021 if(window.mdSourcesCache && window.mdSourcesCache[source.id]) 12022 { 12023 delete window.mdSourcesCache[source.id]; 12024 } 12025 12026 // Reset last filters string 12027 this.lastFiltersString = ''; 12028 }; 12029 12030 this.updateAllElementsWithSource = (sourceId, source) => 12031 { 12032 // Check if we have cached data and only page changed 12033 if(window.mdSourcesCache && window.mdSourcesCache[sourceId]) { 12034 let cache = window.mdSourcesCache[sourceId]; 12035 12036 // Collect current filters to compare with cache 12037 let dynamicFilters = this.collectDynamicFilters(source); 12038 let allFilters = (source.filters || []).concat(dynamicFilters); 12039 let currentFiltersString = JSON.stringify(allFilters); 12040 let currentSort = source.sort || ''; 12041 let currentSearch = this.pendingSearch || ''; 12042 12043 // If filters, sort, and search haven't changed, use cached data 12044 if(cache.filters === currentFiltersString && cache.sort === currentSort && cache.search === currentSearch) { 12045 console.log('Using cached data - no API call needed'); 12046 12047 // Get current page from URL 12048 let params = new URLSearchParams(window.location.search); 12049 let currentPage = parseInt(params.get('page')) || 1; 12050 12051 // Get pagination config 12052 let paginationConfig = mdSources.GetTemp('pagination.' + sourceId) || 12053 mdSources.GetTemp('filters.' + sourceId)?.pagination; 12054 12055 if(paginationConfig && paginationConfig.enabled) { 12056 let perPage = cache.perPage; 12057 let startIndex = (currentPage - 1) * perPage; 12058 let endIndex = startIndex + perPage; 12059 let pageData = cache.allData.slice(startIndex, endIndex); 12060 12061 // Update pagination controls 12062 if(paginationConfig.target) { 12063 mdSources.Fn('pagination.renderControls', sourceId, paginationConfig, { 12064 currentPage: currentPage, 12065 totalPages: cache.totalPages, 12066 totalItems: cache.totalCount, 12067 perPage: perPage 12068 }); 12069 } 12070 12071 // Get container and element info 12072 let $container = sourceContainers.get(sourceId); 12073 let elementInfo = sourceElementInfo.get(sourceId); 12074 12075 // Render the paginated data 12076 this.renderFilteredData($container, sourceId, elementInfo, pageData, source.key); 12077 12078 return;
12078 // EXIT EARLY - no API call needed 12079 } else { 12080 // No pagination, use all cached data 12081 let $container = sourceContainers.get(sourceId); 12082 let elementInfo = sourceElementInfo.get(sourceId); 12083 this.renderFilteredData($container, sourceId, elementInfo, cache.allData, source.key); 12084 return; 12085 } 12086 } 12087 } 12088 12089 // Continue with existing logic if cache miss or filters changed... 12090 let dynamicFilters = this.collectDynamicFilters(source); 12091 12092 this.updateURL(dynamicFilters, source); 12093 12094 let allFilters = (source.filters || []).concat(dynamicFilters); 12095 12096 let sourceItem = addon.ItemGet(source.id); 12097 12098 if(!sourceItem) 12099 { 12100 return; 12101 } 12102 12103 // Get the remembered parent container and element info 12104 let $container = sourceContainers.get(sourceId); 12105 let elementInfo = sourceElementInfo.get(sourceId); 12106 12107 if(!$container || !$container.length) 12108 { 12109 return; 12110 } 12111 12112 if(!elementInfo) 12113 { 12114 return; 12115 } 12116 12117 // Check for pagination config 12118 let paginationConfig = null; 12119 let paginationConfigId = 'pagination.' + sourceId; 12120 paginationConfig = mdSources.GetTemp(paginationConfigId); 12121 12122 // Fallback to filters config for backward compatibility 12123 if(!paginationConfig) 12124 { 12125 let filterConfig = mdSources.GetTemp('filters.' + sourceId); 12126 if(filterConfig && filterConfig.pagination) 12127 { 12128 paginationConfig = filterConfig.pagination; 12129 } 12130 } 12131 12132 // Get source if not provided 12133 if(!source) 12134 { 12135 // Reconstruct source from element 12136 if(elementInfo.isComponent) 12137 { 12138 let component = components.ItemGet(elementInfo.elementId); 12139 if(component && component.Get('source')) 12140 { 12141 source = component.Get('source'); 12142 } 12143 } 12144 else 12145 { 12146 let tag = components.tags.ItemGet(elementInfo.elementId); 12147 if(tag && tag.Get('source')) 12148 { 12149 source = tag.Get('source'); 12150 } 12151 } 12152 12153 // Apply configs 12154 if(source) 12155 { 12156 let filterConfig = mdSources.GetTemp('filters.' + source.id); 12157 if(filterConfig) 12158 { 12159 source.dynamicFilters = filterConfig.dynamicFilters || []; 12160 source.dynamicFiltersMode = filterConfig.dynamicFiltersMode || 'change'; 12161 source.pagination = filterConfig.pagination || paginationConfig; 12162 } 12163 } 12164 } 12165 12166 if(!source) return; 12167 12168 // Make ONE API call to get ALL filtered data (no pagination) 12169 let apiProperties = {...(source.properties || {})}; 12170 12171 // Remove pagination from API call - we'll handle it client-side 12172 delete apiProperties.page; 12173 delete apiProperties.limit; 12174 12175 // Set a reasonable limit for fetching all data, or use original limit as fallback 12176 apiProperties.limit = 1000; 12177 12178 // Apply global search if present 12179 if(this.pendingSearch) 12180 { 12181 apiProperties.search = this.pendingSearch; 12182 } 12183 12184 // Show loading state 12185 $container.addClass('dh-source-loading'); 12186 12187 sourceItem.Fn('run', apiProperties, allFilters, source.sort || '', {}, (allData) => 12188 { 12189 // Now we have ALL the filtered data 12190 let totalCount = Array.isArray(allData) ? allData.length : 0; 12191 12192 if(paginationConfig && paginationConfig.enabled) 12193 { 12194 // Get current page from URL or default to 1 12195 let params = new URLSearchParams(window.location.search); 12196 let currentPage = parseInt(params.get('page')) || 1; 12197 let perPage = paginationConfig.perPage; 12198 let totalPages = Math.ceil(totalCount / perPage); 12199 12200 // Client-side pagination - slice the data 12201 let startIndex = (currentPage - 1) * perPage; 12202 let endIndex = startIndex + perPage; 12203 let pageData = allData.slice(startIndex, endIndex); 12204 12205 // Render pagination controls 12206 if(paginationConfig.target) 12207 { 12208 mdSources.Fn('pagination.renderControls', sourceId, paginationConfig, { 12209 currentPage: currentPage, 12210 totalPages: totalPages, 12211 totalItems: totalCount, 12212 perPage: perPage 12213 }); 12214 } 12215 12216 // Render the paginated data 12217 this.renderFilteredData($container, sourceId, elementInfo, pageData, source.key); 12218 12219 // Cache the full data for this source (for future page changes) 12220 if(!window.mdSourcesCache) window.mdSourcesCache = {};
12221 window.mdSourcesCache[sourceId] = { 12222 allData: allData, 12223 totalCount: totalCount, 12224 totalPages: totalPages, 12225 perPage: perPage, 12226 filters: JSON.stringify(allFilters), 12227 sort: source.sort || '', 12228 search: this.pendingSearch || '' 12229 }; 12230 } 12231 else 12232 { 12233 // No pagination, show all data 12234 this.renderFilteredData($container, sourceId, elementInfo, allData, source.key); 12235 } 12236 12237 // Remove loading state 12238 $container.removeClass('dh-source-loading'); 12239 }); 12240 }; 12241 12242 12243 12244 12245 this.renderFilteredData = ($container, sourceId, elementInfo, data, sourceKey) => 12246 { 12247 // Remove ALL existing elements with this source from the remembered container 12248 let $existingElements = $container.find(`[dh-source="${sourceId}"]`); 12249 $existingElements.remove(); 12250 12251 // Resolve the key for template variable name (defaults to 'value') 12252 let key = sourceKey || 'value'; 12253 12254 // Generate NEW elements based on the filtered API data 12255 if(data && data.length > 0) 12256 { 12257 if(elementInfo.isComponent) 12258 { 12259 let component = components.ItemGet(elementInfo.elementId); 12260 if(component) 12261 { 12262 // Set the filtered data 12263 component.data.temp = component.data.temp || {}; 12264 component.data.temp.source = data; 12265 12266 // Generate new HTML with filtered data 12267 let newHtml = component.Fn('render.html', {symbols: []}, null, false); 12268 12269 $container.append(newHtml); 12270 } 12271 } 12272 else 12273 { 12274 let tag = components.tags.ItemGet(elementInfo.elementId); 12275 if(tag) 12276 { 12277 // Clear caches 12278 if(tag.data.temp && tag.data.temp.source) 12279 { 12280 delete tag.data.temp.source; 12281 } 12282 12283 let cache = components.GetTemp('cache.source', {}); 12284 if(cache[tag.Get('id')]) 12285 { 12286 delete cache[tag.Get('id')]; 12287 components.SetTemp('cache.source', cache); 12288 } 12289 12290 // Set the filtered data 12291 tag.data.temp = tag.data.temp || {}; 12292 tag.data.temp.source = data; 12293 tag.data.temp['source.temp'] = true; 12294 12295 // Generate new HTML for each filtered item 12296 let allNewHtml = ''; 12297 data.forEach((item, index) => { 12298 // Create data context using the source key 12299 let itemData = { 12300 symbols: [], 12301 [key]: item, 12302 [key + '_index']: index 12303 }; 12304 12305 // Generate HTML for this single item 12306 let singleHtml = tag.Fn('render.single', itemData, index + 1); 12307 allNewHtml += singleHtml; 12308 }); 12309 12310 $container.append(allNewHtml); 12311 } 12312 } 12313 } 12314 else 12315 { 12316 $container.append(`<div class="dh-source-no-results" dh-source="${sourceId}">No results found</div>`); 12317 } 12318 12319 // Run transforms 12320 transform.Fn('run', {mode: 'website'}); 12321 }; 12322 12323 // Convert filter mode to event type 12324 this.getEventType = (mode) => 12325 { 12326 let eventType; 12327 switch(mode) 12328 { 12329 case 'instant': eventType = 'input'; break; 12330 case 'change': eventType = 'change'; break; 12331 case 'form': eventType = 'submit'; break; 12332 default: eventType = 'change'; break; 12333 } 12334 return eventType; 12335 }; 12336 12337 // Debounce function 12338 this.debounceUpdate = (key, callback, delay) => 12339 { 12340 clearTimeout(debounceTimers[key]); 12341 debounceTimers[key] = setTimeout(callback, delay); 12342 }; 12343 12344 // Collect current filter values 12345 this.collectDynamicFilters = (source) => 12346 { 12347 let dynamicFilters = []; 12348 this.pendingSearch = null; 12349 12350 // Store current filters to compare 12351 let previousFiltersString = this.lastFiltersString || ''; 12352
12353 source.dynamicFilters.forEach((filter, index) => 12354 { 12355 let targetElement = $(filter.target); 12356 12357 if(!targetElement.length) { 12358 return; 12359 } 12360 12361 // Global search - store separately, not as a filter 12362 if(filter.fieldType === 'global_search') 12363 { 12364 let searchVal = targetElement.val(); 12365 if(searchVal) 12366 { 12367 let searchFieldsList = filter.searchFields; 12368 if(typeof searchFieldsList === 'string') searchFieldsList = searchFieldsList.split(',').filter(Boolean); 12369 12370 if(searchFieldsList && searchFieldsList.length) 12371 { 12372 // Field-specific search: "field1,field2:value" 12373 this.pendingSearch = searchFieldsList.join(',') + ':' + searchVal; 12374 } 12375 else 12376 { 12377 // Generic search across all fields 12378 this.pendingSearch = searchVal; 12379 } 12380 } 12381 return; 12382 } 12383 12384 // Handle based on field type 12385 switch(filter.fieldType) 12386 { 12387 case 'toggle': 12388 let isChecked = targetElement.is(':checked'); 12389 12390 if(isChecked && filter.checkedBehavior === 'has_value') 12391 { 12392 dynamicFilters.push({ 12393 field: filter.field, 12394 operator: 'NotEmpty', 12395 value: '' 12396 }); 12397 } 12398 else if(isChecked && filter.checkedBehavior === 'no_value') 12399 { 12400 dynamicFilters.push({ 12401 field: filter.field, 12402 operator: 'Empty', 12403 value: '' 12404 }); 12405 } 12406 else if(!isChecked && filter.uncheckedBehavior === 'opposite') 12407 { 12408 let operator = filter.checkedBehavior === 'has_value' ? 'Empty' : 'NotEmpty'; 12409 dynamicFilters.push({ 12410 field: filter.field, 12411 operator: operator, 12412 value: '' 12413 }); 12414 } 12415 break; 12416 12417 case 'checkbox_group': 12418 let name = targetElement.attr('name'); 12419 12420 let checkedValues = []; 12421 12422 // If target element has a name, find all checkboxes with that name 12423 if(name) { 12424 $(`input[type="checkbox"][name="${name}"]:checked`).each(function() { 12425 checkedValues.push($(this).val()); 12426 }); 12427 } 12428 // If no name, check if the target itself is checked (single checkbox treated as group) 12429 else if(targetElement.is('input[type="checkbox"]')) { 12430 if(targetElement.is(':checked')) { 12431 checkedValues.push(targetElement.val()); 12432 } 12433 } 12434 12435 if(checkedValues.length > 0) 12436 { 12437 let operator = filter.checkboxGroupBehavior === 'match_all' ? 'ContainsAll' : 'In'; 12438 dynamicFilters.push({ 12439 field: filter.field, 12440 operator: operator, 12441 value: checkedValues 12442 }); 12443 } 12444 else if(filter.checkboxGroupEmpty === 'show_none') 12445 { 12446 dynamicFilters.push({ 12447 field: filter.field, 12448 operator: 'Equals', 12449 value: 'IMPOSSIBLE_VALUE_THAT_NEVER_MATCHES' 12450 }); 12451 } 12452 break; 12453 12454 case 'multi_select': 12455 let multiValue = targetElement.val(); 12456 12457 if(Array.isArray(multiValue) && multiValue.length > 0) 12458 { 12459 dynamicFilters.push({ 12460 field: filter.field, 12461 operator: filter.operator || 'In', 12462 value: multiValue 12463 }); 12464 } 12465 break; 12466 12467 case 'radio': 12468 let radioName = targetElement.attr('name'); 12469 let checkedRadio = $(`input[type="radio"][name="${radioName}"]:checked`); 12470 let radioValue = checkedRadio.val() || ''; 12471 12472 // Skip if radio value is empty, "on", or matches any ignore pattern 12473 if(!radioValue || radioValue === '' || radioValue === 'on' || radioValue === 'any' || radioValue === 'all' || radioValue === '*') 12474 { 12475 // Handle radioEmpty behavior 12476 if(filter.radioEmpty === 'show_none') 12477 { 12478 dynamicFilters.push({ 12479 field: filter.field, 12480 operator: 'Equals', 12481 value: 'IMPOSSIBLE_VALUE_THAT_NEVER_MATCHES' 12482 }); 12483 } 12484 break; 12485 } 12486 12487 let operator = filter.radioBehavior === 'exact' ? 'Equals' : 'Includes'; 12488 12489 dynamicFilters.push({ 12490 field: filter.field, 12491 operator: operator, 12492 value: radioValue 12493 }); 12494 break; 12495 12496 case 'number': 12497 let numberValue = targetElement.val(); 12498 12499 if(numberValue || filter.operator === 'Empty' || filter.operator === 'NotEmpty') 12500 { 12501 // Convert to number if value exists 12502 let processedValue = ''; 12503 if(numberValue) 12504 { 12505 processedValue = parseFloat(numberValue); 12506 // Skip if not a valid number 12507 if(isNaN(processedValue)) 12508 { 12509 break; 12510 } 12511 } 12512 12513 dynamicFilters.push({ 12514 field: filter.field, 12515 operator: filter.operator, 12516 value: processedValue 12517 }); 12518 } 12519 break; 12520 12521 case 'select': 12522 let selectValue = targetElement.val(); 12523 12524 // Skip if select value is empty, or matches any ignore pattern 12525 if(!selectValue || selectValue === '' || selectValue === 'any' || selectValue === 'all' || selectValue === '*') 12526 { 12527 break; 12528 } 12529 12530 if(selectValue || filter.operator === 'Empty' || filter.operator === 'NotEmpty') 12531 { 12532 dynamicFilters.push({ 12533 field: filter.field, 12534 operator: filter.operator, 12535 value: selectValue || '' 12536 }); 12537 } 12538 break; 12539 12540 case 'text': 12541 default: 12542 let value = targetElement.val(); 12543 12544 if(value || filter.operator === 'Empty' || filter.operator === 'NotEmpty') 12545 { 12546 dynamicFilters.push({ 12547 field: filter.field, 12548 operator: filter.operator, 12549 value: value || '' 12550 }); 12551 } 12552 break; 12553 } 12554 }); 12555 12556 // Check if filters have changed 12557 let currentFiltersString = JSON.stringify(dynamicFilters); 12558 let filtersChanged = currentFiltersString !== previousFiltersString; 12559 12560 if(filtersChanged && previousFiltersString !== '') 12561 { 12562 console.log('Filters changed, resetting to page 1'); 12563 12564 // Update URL to remove page parameter 12565 let params = new URLSearchParams(window.location.search); 12566 params.delete('page'); 12567 12568 // Update URL without triggering popstate 12569 let newURL = window.location.pathname; 12570 if(params.toString()) 12571 { 12572 newURL += '?' + params.toString(); 12573 } 12574 12575 history.replaceState(null, '', newURL); 12576 } 12577 12578 // Store current filters for next comparison 12579 this.lastFiltersString = currentFiltersString; 12580 12581 return dynamicFilters; 12582 }; 12583 // Get value from form elements 12584 this.getElementValue = (element) => 12585 { 12586 let $element = $(element); 12587 let elementType = this.getElementType($element); 12588 12589 let value; 12590 12591 switch(elementType) 12592 { 12593 case 'checkbox-group': 12594 let name = $element.attr('name'); 12595 value = []; 12596 $(`input[type="checkbox"][name="${name}"]:checked`).each(function() { 12597 value.push($(this).val()); 12598 }); 12599 break; 12600 12601 case 'single-checkbox': 12602 value = $element.is(':checked'); 12603 break; 12604 12605 default: 12606 value = $element.val() || ''; 12607 } 12608 12609 return { 12610 value: value, 12611 elementType: elementType 12612 }; 12613 }; 12614 12615 // Add this new helper function 12616 this.getElementType = ($element) => 12617 { 12618 let tagName = $element.prop('tagName').toLowerCase(); 12619 12620 if(tagName === 'input') 12621 { 12622 let type = $element.attr('type'); 12623 if(type === 'checkbox') 12624 { 12625 // Check if it's part of a group 12626 let name = $element.attr('name'); 12627 if(name && $(`input[type="checkbox"][name="${name}"]`).length > 1) 12628 { 12629 return 'checkbox-group'; 12630 } 12631 return 'single-checkbox'; 12632 } 12633 else if(type === 'radio') 12634 { 12635 return 'radio-group'; 12636 } 12637 } 12638 else if(tagName === 'select' && $element.prop('multiple')) 12639 { 12640 return 'multi-select'; 12641 } 12642 12643 return 'default'; 12644 }; 12645 12646 // Update URL parameters 12647 this.updateURL = (filters, source) => 12648 { 12649 let params = new URLSearchParams(window.location.search); 12650 12651 // Clear all parameters that match our filter inputs
12652 source.dynamicFilters.forEach(dynFilter => { 12653 let targetElement = $(dynFilter.target); 12654 if(targetElement.length && targetElement.attr('name')) { 12655 params.delete(targetElement.attr('name')); 12656 } 12657 }); 12658 12659 // Add current values 12660 source.dynamicFilters.forEach(dynFilter => { 12661 let targetElement = $(dynFilter.target); 12662 if(!targetElement.length) return; 12663 12664 let inputName = targetElement.attr('name'); 12665 if(!inputName) return; // Skip if no name attribute 12666 12667 // Get value based on field type 12668 if(dynFilter.fieldType === 'toggle') { 12669 let isChecked = targetElement.is(':checked'); 12670 if(isChecked || dynFilter.uncheckedBehavior === 'opposite') { 12671 params.set(inputName, isChecked ? 'checked' : ''); 12672 } 12673 } 12674 else if(dynFilter.fieldType === 'checkbox_group') { 12675 let checkedValues = []; 12676 $(`input[type="checkbox"][name="${inputName}"]:checked`).each(function() { 12677 checkedValues.push($(this).val()); 12678 }); 12679 if(checkedValues.length > 0) { 12680 params.set(inputName, checkedValues.join(',')); 12681 } 12682 } 12683 else if(dynFilter.fieldType === 'number') { 12684 let value = targetElement.val(); 12685 if(value) { 12686 // Ensure it's a valid number before adding to URL 12687 let numValue = parseFloat(value); 12688 if(!isNaN(numValue)) { 12689 params.set(inputName, numValue); 12690 } 12691 } 12692 } 12693 else { 12694 let value = targetElement.val(); 12695 // For select, skip ignored values 12696 if(dynFilter.fieldType === 'select' && (value === 'any' || value === 'all' || value === '*')) { 12697 return; 12698 } 12699 if(value) { 12700 params.set(inputName, value); 12701 } 12702 } 12703 }); 12704 12705 let newURL = window.location.pathname; 12706 if(params.toString()) { 12707 newURL += '?' + params.toString(); 12708 } 12709 12710 history.pushState(null, '', newURL); 12711 }; 12712 12713 // Handle browser back/forward 12714 this.setupURLSync = () => 12715 { 12716 window.addEventListener('popstate', () => 12717 { 12718 this.scanForDynamicFilters(); 12719 }); 12720 }; 12721 12722 // Apply filter values from URL using field names 12723 this.applyURLFilters = (source) => 12724 { 12725 let params = new URLSearchParams(window.location.search); 12726 let hasAppliedFilters = false; 12727 12728 // First try to apply filters based on config 12729 source.dynamicFilters.forEach(dynFilter => { 12730 let targetElement = $(dynFilter.target); 12731 if(!targetElement.length) return; 12732 12733 let inputName = targetElement.attr('name'); 12734 if(!inputName) return; 12735 12736 let value = params.get(inputName); 12737 if(value === null) return; // Important: check for null, not !value 12738 12739 hasAppliedFilters = true; 12740 12741 // Apply value based on field type 12742 switch(dynFilter.fieldType) { 12743 case 'toggle': 12744 targetElement.prop('checked', value === 'checked' || value === 'true' || value === '1'); 12745 break; 12746 12747 case 'checkbox_group': 12748 let values = value.split(','); 12749 // Find all checkboxes with this name, not just by the target class 12750 $(`input[type="checkbox"][name="${inputName}"]`).each(function() { 12751 $(this).prop('checked', values.includes($(this).val())); 12752 }); 12753 break; 12754 12755 case 'radio': 12756 $(`input[type="radio"][name="${inputName}"][value="${value}"]`).prop('checked', true); 12757 break; 12758 12759 case 'number': 12760 targetElement.val(value); 12761 break; 12762 12763 case 'select': 12764 case 'multi_select': 12765 if(dynFilter.fieldType === 'multi_select') { 12766 targetElement.val(value.split(',')); 12767 } else { 12768 targetElement.val(value); 12769 } 12770 // Trigger change event so select2 or other plugins update 12771 targetElement.trigger('change.select2'); 12772 break; 12773 12774 default: 12775 targetElement.val(value); 12776 } 12777 }); 12778 12779 // Also check for any URL params that might not be in our
12779config 12780 params.forEach((value, key) => { 12781 // Skip if already processed 12782 if(source.dynamicFilters.some(f => { 12783 let el = $(f.target); 12784 return el.length && el.attr('name') === key; 12785 })) { 12786 return; 12787 } 12788 12789 // Try to apply to any matching input 12790 let element = $(`[name="${key}"]`); 12791 if(element.length) { 12792 hasAppliedFilters = true; 12793 element.val(value).trigger('change.select2'); 12794 } 12795 }); 12796 12797 return hasAppliedFilters; 12798 }; 12799 12800 // Initialize 12801 this.scanForDynamicFilters(); 12802 this.setupURLSync(); 12803});mdSources.FunctionCreate('multiSelectDropdown.setup', function(addon) 12804{ 12805 // Inject CSS once 12806 if(!document.getElementById('dh-multiselect-dropdown-css')) 12807 { 12808 $('head').append( 12809 '<style id="dh-multiselect-dropdown-css">' + 12810 '[dh-dropdown] { position: relative; }' + 12811 '[dh-dropdown-trigger] { cursor: pointer; }' + 12812 '[dh-dropdown-content] { display: none; position: absolute; z-index: 100; left: 0; right: 0; max-height: 200px; overflow-y: auto; }' + 12813 '[dh-dropdown].dh-dropdown-open [dh-dropdown-content] { display: block; }' + 12814 '</style>' 12815 ); 12816 } 12817 12818 // Toggle dropdown on trigger click 12819 $(document).off('click.dh-dropdown-trigger').on('click.dh-dropdown-trigger', '[dh-dropdown-trigger]', function(e) 12820 { 12821 e.stopPropagation(); 12822 let $dropdown = $(this).closest('[dh-dropdown]'); 12823 let wasOpen = $dropdown.hasClass('dh-dropdown-open'); 12824 12825 // Close all others 12826 $('[dh-dropdown]').removeClass('dh-dropdown-open'); 12827 12828 if(!wasOpen) 12829 { 12830 $dropdown.addClass('dh-dropdown-open'); 12831 } 12832 }); 12833 12834 // Close on outside click 12835 $(document).off('click.dh-dropdown-outside').on('click.dh-dropdown-outside', function(e) 12836 { 12837 if(!$(e.target).closest('[dh-dropdown]').length) 12838 { 12839 $('[dh-dropdown]').removeClass('dh-dropdown-open'); 12840 } 12841 }); 12842 12843 // Update trigger text for a dropdown 12844 function updateTriggerText($dropdown) 12845 { 12846 let $trigger = $dropdown.find('[dh-dropdown-trigger] span').first(); 12847 let $checkboxes = $dropdown.find('[dh-dropdown-content] input[type="checkbox"]'); 12848 let checked = $checkboxes.filter(':checked'); 12849 12850 if(!$trigger.length) return; 12851 12852 if(!$trigger.attr('data-original-text')) 12853 { 12854 $trigger.attr('data-original-text', $trigger.text()); 12855 } 12856 12857 if(checked.length === 0) 12858 { 12859 $trigger.text($trigger.attr('data-original-text')); 12860 } 12861 else 12862 { 12863 $trigger.text(checked.length + ' selected'); 12864 } 12865 } 12866 12867 // Update trigger text when checkboxes change 12868 $(document).off('change.dh-dropdown-checkbox').on('change.dh-dropdown-checkbox', '[dh-dropdown-content] input[type="checkbox"]', function() 12869 { 12870 updateTriggerText($(this).closest('[dh-dropdown]')); 12871 }); 12872 12873 // Update trigger text on init for pre-populated checkboxes (from URL) 12874 setTimeout(() => 12875 { 12876 $('[dh-dropdown]').each(function() 12877 { 12878 updateTriggerText($(this)); 12879 }); 12880 }, 200); 12881}); 12882mdSources.FunctionCreate('pagination.bindEvents', function(addon, targetSelector, sourceId) 12883{ 12884 $(targetSelector).off('click.pagination').on('click.pagination', 'button[data-page]', function(e) 12885 { 12886 e.preventDefault(); 12887 12888 let page = parseInt($(this).attr('data-page')); 12889 if(isNaN(page)) return; 12890 12891 // Update URL 12892 let params = new URLSearchParams(window.location.search); 12893 if(page === 1) 12894 { 12895 params.delete('page'); 12896 } 12897 else 12898 { 12899 params.set('page', page); 12900 } 12901 12902 let newURL = window.location.pathname; 12903 if(params.toString()) 12904 { 12905 newURL += '?' + params.toString(); 12906 } 12907 12908 history.pushState(null, '', newURL); 12909 12910 // Trigger the same scan that happens on page load 12911 mdSources.Fn('dynamicFilters.setup'); 12912 }); 12913});mdSources.FunctionCreate('pagination.generateNumbers', function(addon, current, total, maxButtons, sourceId) 12914{ 12915 let html = ''; 12916 let startPage = 1; 12917 let endPage = total; 12918 12919 if(total > maxButtons) 12920 { 12921 let halfButtons = Math.floor(maxButtons / 2); 12922 12923 if(current <= halfButtons) 12924 { 12925 endPage = maxButtons; 12926 } 12927 else if(current >= total - halfButtons) 12928 { 12929 startPage = total - maxButtons + 1; 12930 } 12931 else 12932 { 12933 startPage = current - halfButtons; 12934 endPage = current + halfButtons; 12935 } 12936 } 12937 12938 // First page + ellipsis 12939 if(startPage > 1) 12940 { 12941 html += `<button class="dh-pagination-number" data-page="1" data-source="${sourceId}">1</button>`; 12942 if(startPage > 2) 12943 { 12944 html += `<span class="dh-pagination-ellipsis">...</span>`;
12945 } 12946 } 12947 12948 // Page numbers 12949 for(let i = startPage; i <= endPage; i++) 12950 { 12951 html += `<button class="dh-pagination-number ${i === current ? 'active' : ''}" data-page="${i}" data-source="${sourceId}">${i}</button>`; 12952 } 12953 12954 // Ellipsis + last page 12955 if(endPage < total) 12956 { 12957 if(endPage < total - 1) 12958 { 12959 html += `<span class="dh-pagination-ellipsis">...</span>`; 12960 } 12961 html += `<button class="dh-pagination-number" data-page="${total}" data-source="${sourceId}">${total}</button>`; 12962 } 12963 12964 return html; 12965});// Update pagination.js 12966mdSources.FunctionCreate('pagination.setup', function(addon) 12967{ 12968 let paginationStates = new Map(); 12969 let processedSources = new Set(); 12970 let sourceContainers = new Map(); 12971 let sourceElementInfo = new Map(); 12972 12973 // Main scan function - similar to dynamic filters 12974 this.scanForPagination = () => 12975 { 12976 processedSources.clear(); 12977 12978 // Look for elements with pagination config 12979 let foundElements = $('[dh-source][dh-pagination-config]'); 12980 12981 foundElements.each((index, element) => 12982 { 12983 let sourceId = $(element).attr('dh-source'); 12984 let paginationConfigId = $(element).attr('dh-pagination-config'); 12985 12986 if(processedSources.has(sourceId)) 12987 { 12988 return; 12989 } 12990 processedSources.add(sourceId); 12991 12992 // Get pagination config from temp storage 12993 let paginationConfig = mdSources.GetTemp(paginationConfigId); 12994 12995 if(!paginationConfig || !paginationConfig.enabled) 12996 { 12997 return; 12998 } 12999 13000 // Store container and element info 13001 if(!sourceContainers.has(sourceId)) 13002 { 13003 let $container = $(element).parent(); 13004 sourceContainers.set(sourceId, $container); 13005 13006 let isComponent = $(element).is('[component]'); 13007 let elementId = null; 13008 13009 if(isComponent) 13010 { 13011 elementId = $(element).attr('component'); 13012 } 13013 else 13014 { 13015 elementId = $(element).attr('class').split(' ')[0].replace('t', ''); 13016 } 13017 13018 sourceElementInfo.set(sourceId, { 13019 isComponent: isComponent, 13020 elementId: elementId, 13021 element: element, 13022 paginationConfig: paginationConfig 13023 }); 13024 } 13025 13026 // Initialize pagination state 13027 if(!paginationStates.has(sourceId)) 13028 { 13029 paginationStates.set(sourceId, { 13030 currentPage: 1, 13031 totalPages: 1, 13032 totalItems: 0, 13033 perPage: paginationConfig.perPage || 10 13034 }); 13035 } 13036 13037 // Check URL for page parameter 13038 let params = new URLSearchParams(window.location.search); 13039 let urlPage = parseInt(params.get('page')) || 1; 13040 13041 let state = paginationStates.get(sourceId); 13042 state.currentPage = urlPage; 13043 13044 // Initialize this source with pagination 13045 this.initializeSource(sourceId, paginationConfig); 13046 }); 13047 }; 13048 13049 // Initialize a source with pagination 13050 this.initializeSource = (sourceId, paginationConfig) => 13051 { 13052 let elementInfo = sourceElementInfo.get(sourceId); 13053 if(!elementInfo) return; 13054 13055 // Check if this source has dynamic filters 13056 let $element = $(elementInfo.element); 13057 let hasFilterConfig = $element.attr('dh-filter-config'); 13058 13059 if(hasFilterConfig) { 13060 // Dynamic filters will handle everything, just return 13061 return; 13062 } 13063 13064 // No dynamic filters, pagination needs to handle its own API calls 13065 let source = { 13066 id: sourceId, 13067 pagination: paginationConfig 13068 }; 13069 13070 // Get full source configuration 13071 if(elementInfo.isComponent) { 13072 let component = components.ItemGet(elementInfo.elementId); 13073 if(component && component.Get('source')) { 13074 source = {...component.Get('source'), pagination: paginationConfig}; 13075 } 13076 } else { 13077 let tag = components.tags.ItemGet(elementInfo.elementId); 13078 if(tag && tag.Get('source')) { 13079 source = {...tag.Get('source'), pagination: paginationConfig}; 13080 } 13081 } 13082 13083 // Initial load with pagination 13084 this.updateSourceWithPagination(sourceId, source); 13085 }; 13086 13087 // Update source data with pagination 13088 this.updateSourceWithPagination = (sourceId, source, page = null) => 13089 { 13090 let sourceItem = addon.ItemGet(source.id); 13091 if(!sourceItem) return; 13092 13093 let state = paginationStates.get(sourceId); 13094 if(!state) return; 13095 13096 // Use provided page or current state 13097 if(page !== null) { 13098 state.currentPage = page; 13099 } 13100 13101 let $container = sourceContainers.get(sourceId); 13102 let elementInfo = sourceElementInfo.get(sourceId); 13103 13104 if(!$container || !elementInfo) return; 13105 13106 // Check if this source has dynamic filters 13107 let $element = $(elementInfo.element); 13108 let hasFilterConfig = $element.attr('dh-filter-config'); 13109 13110 if(hasFilterConfig) { 13111 // Let dynamic filters handle everything 13112 return; 13113 } 13114 13115 // Standalone pagination - use client-side caching 13116 let cacheKey = sourceId + '-standalone'; 13117 if(!window.mdSourcesCache) window.mdSourcesCache = {}; 13118 13119 if(window.mdSourcesCache[cacheKey]) { 13120 // Use cached data 13121 let cache = window.mdSourcesCache[cacheKey]; 13122 let startIndex = (state.currentPage - 1) * state.perPage; 13123 let endIndex = startIndex + state.perPage; 13124 let pageData = cache.allData.slice(startIndex, endIndex); 13125 13126 state.totalItems = cache.totalCount; 13127 state.totalPages = cache.totalPages; 13128 13129 // Render pagination controls 13130 this.renderPagination(sourceId, source.pagination, state); 13131 13132 // Render the paginated data 13133 this.renderStandalonePaginationData($container, sourceId, elementInfo, pageData); 13134 } else { 13135 // First time - fetch all data and cache it 13136 let allProperties = {...(source.properties || {})}; 13137 delete allProperties.page; 13138 delete allProperties.limit; 13139 allProperties.limit = 1000; 13140 13141 sourceItem.Fn('run', allProperties, source.filters || [], source.sort || '', {}, (allData) => { 13142 let totalCount = Array.isArray(allData) ? allData.length : 0; 13143 state.totalItems = totalCount; 13144 state.totalPages = Math.ceil(totalCount / state.perPage); 13145 13146 // Cache the data 13147 window.mdSourcesCache[cacheKey] = { 13148 allData: allData, 13149 totalCount: totalCount, 13150 totalPages: state.totalPages 13151 }; 13152 13153 // Get current page data 13154 let startIndex = (state.currentPage - 1) * state.perPage; 13155 let endIndex = startIndex + state.perPage; 13156 let pageData = allData.slice(startIndex, endIndex); 13157 13158 // Render pagination controls 13159 this.renderPagination(sourceId, source.pagination, state); 13160 13161 // Render the paginated data 13162 this.renderStandalonePaginationData($container, sourceId, elementInfo, pageData); 13163 }); 13164 } 13165 }; 13166 13167 13168 13169 // Render the data (similar to dynamic filters) 13170 this.renderPageData = ($container, sourceId, elementInfo, data) => 13171 { 13172 // Remove existing elements 13173 let $existingElements = $container.find(`[dh-source="${sourceId}"]`); 13174 $existingElements.remove(); 13175 13176 // Generate new elements 13177 if(data && data.length > 0) 13178 { 13179 if(elementInfo.isComponent) 13180 { 13181 let component = components.ItemGet(elementInfo.elementId); 13182 if(component) 13183 { 13184 component.data.temp = component.data.temp || {}; 13185 component.data.temp.source = data; 13186 13187 let newHtml = component.Fn('render.html', {symbols: []}
13187, null, false); 13188 $container.append(newHtml); 13189 } 13190 } 13191 else 13192 { 13193 let tag = components.tags.ItemGet(elementInfo.elementId); 13194 if(tag) 13195 { 13196 // Clear caches 13197 if(tag.data.temp && tag.data.temp.source) 13198 { 13199 delete tag.data.temp.source; 13200 } 13201 13202 let cache = components.GetTemp('cache.source', {}); 13203 if(cache[tag.Get('id')]) 13204 { 13205 delete cache[tag.Get('id')]; 13206 components.SetTemp('cache.source', cache); 13207 } 13208 13209 tag.data.temp = tag.data.temp || {}; 13210 tag.data.temp.source = data; 13211 tag.data.temp['source.temp'] = true; 13212 13213 let allNewHtml = ''; 13214 data.forEach((item, index) => { 13215 let itemData = { 13216 symbols: [], 13217 value: item, 13218 value_index: index 13219 }; 13220 13221 let singleHtml = tag.Fn('render.single', itemData, index + 1); 13222 allNewHtml += singleHtml; 13223 }); 13224 13225 $container.append(allNewHtml); 13226 } 13227 } 13228 } 13229 13230 transform.Fn('run', {mode: 'website'}); 13231 }; 13232 13233 13234 this.renderStandalonePaginationData = ($container, sourceId, elementInfo, data) => 13235 { 13236 // Remove existing elements with this source 13237 let $existingElements = $container.find(`[dh-source="${sourceId}"]`); 13238 $existingElements.remove(); 13239 13240 // Generate new elements based on the paginated data 13241 if(data && data.length > 0) 13242 { 13243 if(elementInfo.isComponent) 13244 { 13245 let component = components.ItemGet(elementInfo.elementId); 13246 if(component) 13247 { 13248 // Set the paginated data 13249 component.data.temp = component.data.temp || {}; 13250 component.data.temp.source = data; 13251 13252 // Generate new HTML with paginated data 13253 let newHtml = component.Fn('render.html', {symbols: []}, null, false); 13254 13255 $container.append(newHtml); 13256 } 13257 } 13258 else 13259 { 13260 let tag = components.tags.ItemGet(elementInfo.elementId); 13261 if(tag) 13262 { 13263 // Clear caches 13264 if(tag.data.temp && tag.data.temp.source) 13265 { 13266 delete tag.data.temp.source; 13267 } 13268 13269 let cache = components.GetTemp('cache.source', {}); 13270 if(cache[tag.Get('id')]) 13271 { 13272 delete cache[tag.Get('id')]; 13273 components.SetTemp('cache.source', cache); 13274 } 13275 13276 // Set the paginated data 13277 tag.data.temp = tag.data.temp || {}; 13278 tag.data.temp.source = data; 13279 tag.data.temp['source.temp'] = true; 13280 13281 // Generate new HTML for each paginated item 13282 let src = tag.Get('source') || {}; 13283 let sourceKey = (src.key && src.key.length) ? src.key : 'value'; 13284 13285 let allNewHtml = ''; 13286 data.forEach((item, index) => { 13287 // Create data context for this item using source key 13288 let itemData = { 13289 symbols: [], 13290 repeat: true 13291 }; 13292 itemData[sourceKey] = item; 13293 itemData[sourceKey + '_index'] = index; 13294 13295 // Generate HTML for this single item 13296 let singleHtml = tag.Fn('render.single', itemData, index + 1);
13297 allNewHtml += singleHtml; 13298 }); 13299 13300 $container.append(allNewHtml); 13301 } 13302 } 13303 } 13304 13305 // Run transforms to initialize any new elements 13306 transform.Fn('run', {mode: 'website'}); 13307 }; 13308 13309 13310 // All the render methods remain the same... 13311 this.renderPagination = (sourceId, config, state) => 13312 { 13313 let targetSelector = config.target; 13314 if(!targetSelector) return; 13315 13316 let $target = $(targetSelector); 13317 if(!$target.length) return; 13318 13319 let html = '<div class="dh-pagination">'; 13320 13321 // Show info if enabled 13322 if(config.showInfo) 13323 { 13324 let start = ((state.currentPage - 1) * state.perPage) + 1; 13325 let end = Math.min(state.currentPage * state.perPage, state.totalItems); 13326 13327 html += `<div class="dh-pagination-info">Showing ${start}-${end} of ${state.totalItems}</div>`; 13328 } 13329 13330 html += '<div class="dh-pagination-controls">'; 13331 13332 // Previous button 13333 if(config.style === 'simple' || config.style === 'both') 13334 { 13335 html += `<button class="dh-pagination-prev" ${state.currentPage === 1 ? 'disabled' : ''} data-page="${state.currentPage - 1}"> 13336 ${config.prevText} 13337 </button>`; 13338 } 13339 13340 // Number buttons 13341 if(config.style === 'numbers' || config.style === 'both') 13342 { 13343 html += this.generateNumberButtons(state.currentPage, state.totalPages, config.maxNumbers || 5); 13344 } 13345 13346 // Next button 13347 if(config.style === 'simple' || config.style === 'both') 13348 { 13349 html += `<button class="dh-pagination-next" ${state.currentPage === state.totalPages ? 'disabled' : ''} data-page="${state.currentPage + 1}"> 13350 ${config.nextText} 13351 </button>`; 13352 } 13353 13354 html += '</div></div>'; 13355 13356 $target.html(html); 13357 13358 // Bind click events 13359 this.bindPaginationEvents(sourceId, targetSelector); 13360 }; 13361 13362 this.generateNumberButtons = (current, total, maxButtons) => 13363 { 13364 let html = ''; 13365 let startPage = 1; 13366 let endPage = total; 13367 13368 if(total > maxButtons) 13369 { 13370 let halfButtons = Math.floor(maxButtons / 2); 13371 13372 if(current <= halfButtons) 13373 { 13374 endPage = maxButtons; 13375 } 13376 else if(current >= total - halfButtons) 13377 { 13378 startPage = total - maxButtons + 1; 13379 } 13380 else 13381 { 13382 startPage = current - halfButtons; 13383 endPage = current + halfButtons; 13384 } 13385 } 13386 13387 // First page + ellipsis 13388 if(startPage > 1) 13389 { 13390 html += `<button class="dh-pagination-number" data-page="1">1</button>`; 13391 if(startPage > 2) 13392 { 13393 html += `<span class="dh-pagination-ellipsis">...</span>`; 13394 } 13395 } 13396 13397 // Page numbers 13398 for(let i = startPage; i <= endPage; i++) 13399 { 13400 html += `<button class="dh-pagination-number ${i === current ? 'active' : ''}" data-page="${i}">${i}</button>`; 13401 } 13402 13403 // Ellipsis + last page 13404 if(endPage < total) 13405 { 13406 if(endPage < total - 1) 13407 { 13408 html += `<span class="dh-pagination-ellipsis">...</span>`; 13409 } 13410 html += `<button class="dh-pagination-number" data-page="${total}">${total}</button>`; 13411 } 13412 13413 return html; 13414 }; 13415 13416 this.bindPaginationEvents = (sourceId, targetSelector) => 13417 { 13418 let self = this; 13419 13420 $(targetSelector).off('click.pagination').on('click.pagination', 'button[data-page]', function(e) 13421 { 13422 e.preventDefault(); 13423 13424 let page = parseInt($(this).attr('data-page')); 13425 if(isNaN(page)) return; 13426 13427 // Update URL 13428 let params = new URLSearchParams(window.location.search); 13429 if(page === 1) 13430 { 13431 params.delete('page'); 13432 } 13433 else 13434 { 13435 params.set('page', page); 13436 } 13437 13438 let newURL = window.location.pathname; 13439 if(params.toString()) 13440 { 13441 newURL += '?' + params.toString(); 13442 } 13443 13444 history.pushState(null, '', newURL); 13445 13446 // Get source info 13447 let elementInfo = sourceElementInfo.get(sourceId); 13448 if(!elementInfo) return; 13449 13450 let source = { 13451 id: sourceId 13452 }; 13453 13454 // Get full source configuration 13455 if(elementInfo.isComponent) 13456 { 13457 let component = components.ItemGet(elementInfo.elementId); 13458 if(component && component.Get('source')) 13459 { 13460 source = component.Get('source'); 13461 } 13462 } 13463 else 13464 { 13465 let tag = components.tags.ItemGet(elementInfo.elementId); 13466 if(tag && tag.Get('source')) 13467 { 13468 source = tag.Get('source'); 13469 } 13470 } 13471 13472 // Add pagination config 13473 let config = mdSources.GetTemp('filters.' + sourceId); 13474 if(config && config.pagination) 13475 { 13476 source.pagination = config.pagination; 13477 } 13478 13479 // Update with new page - use saved 'self' reference 13480 self.updateSourceWithPagination(sourceId, source, page); 13481 }); 13482 }; 13483 13484 // Public methods for dynamic filters integration 13485 this.getCurrentPage = (sourceId) => 13486 { 13487 let state = paginationStates.get(sourceId); 13488 return state ? state.currentPage : 1; 13489 }; 13490 13491 this.reset = (sourceId) => 13492 { 13493 let state = paginationStates.get(sourceId); 13494 if(state) 13495 { 13496 state.currentPage = 1; 13497 } 13498 }; 13499 13500 this.updatePagination = (sourceId, paginationData) => 13501 { 13502 // First check dedicated pagination config 13503 let paginationConfig = null; 13504 let paginationConfigId = 'pagination.' + sourceId; 13505 paginationConfig = mdSources.GetTemp(paginationConfigId); 13506 13507 // Fallback to filters config for backward compatibility 13508 if(!paginationConfig) 13509 { 13510 let filterConfig = mdSources.GetTemp('filters.' + sourceId); 13511 if(filterConfig && filterConfig.pagination) 13512 { 13513 paginationConfig = filterConfig.pagination; 13514 } 13515 } 13516 13517 if(!paginationConfig || !paginationConfig.enabled) 13518 { 13519 return; 13520 } 13521 13522 // Update state 13523 let state = paginationStates.get(sourceId) || { 13524 currentPage: 1, 13525 totalPages: 1, 13526 totalItems: 0, 13527 perPage: paginationConfig.perPage 13528 }; 13529 13530 state.totalItems = paginationData.total || 0; 13531 state.totalPages = Math.ceil(state.totalItems / state.perPage); 13532 state.currentPage = paginationData.currentPage || 1; 13533 13534 paginationStates.set(sourceId, state); 13535 13536 // Render pagination 13537 this.renderPagination(sourceId, paginationConfig, state); 13538 }; 13539 13540 // Handle browser back/forward 13541 this.setupURLSync = () => 13542 { 13543 window.addEventListener('popstate', () => 13544 { 13545 this.scanForPagination(); 13546 }); 13547 }; 13548 13549 // Initialize 13550 this.scanForPagination(); 13551 this.setupURLSync(); 13552});mdSources.FunctionCreate('pagination.renderControls', function(addon, sourceId, config, state) 13553{ 13554 let targetSelector = config.target; 13555 if(!targetSelector) return; 13556 13557 let $target = $(targetSelector); 13558 if(!$target.length) return; 13559 13560 let html = '<div class="dh-pagination">'; 13561 13562 // Show info if enabled 13563 if(config.showInfo) 13564 { 13565 let start = ((state.currentPage - 1) * config.perPage) + 1; 13566 let end = Math.min(state.currentPage * config.perPage, state.totalItems); 13567 13568 html += `<div class="dh-pagination-info">Showing ${start}-${end} of ${state.totalItems}</div>`; 13569 } 13570 13571 html += '<div class="dh-pagination-controls">'; 13572 13573 // Previous button 13574 if(config.style === 'simple' || config.style === 'both') 13575 { 13576 html += `<button class="dh-pagination-prev" ${state.currentPage === 1 ? 'disabled' : ''} data-page="${state.currentPage - 1}" data-source="${sourceId}"> 13577 ${config.prevText} 13578 </button>`; 13579 } 13580 13581 // Number buttons 13582 if(config.style === 'numbers' || config.style === 'both') 13583 { 13584 html += mdSources.Fn('pagination.generateNumbers', state.currentPage, state.totalPages, config.maxNumbers || 5, sourceId); 13585 } 13586 13587 // Next button 13588 if(config.style === 'simple' || config.style === 'both') 13589 { 13590 html += `<button class="dh-pagination-next" ${state.currentPage === state.totalPages ? 'disabled' : ''} data-page="${state.currentPage + 1}" data-source="${sourceId}"> 13591 ${config.nextText} 13592 </button>`; 13593 } 13594 13595 html += '</div></div>'; 13596 13597 $target.html(html); 13598 13599 // Bind click events 13600 mdSources.Fn('pagination.bindEvents', targetSelector, sourceId); 13601});mdSources.FunctionCreate('item.run', function(addon, item, properties = {}, filters = [], sort = '', data = {}, callback = null) 13602{ 13603 properties = JSON.parse(JSON.stringify(properties)); 13604 filters = JSON.parse(JSON.stringify(filters)); 13605 sort = mdVars.Fn('process', sort, data); 13606 13607 $.each(properties, function(key, value) 13608 { 13609 properties[key] = mdVars.Fn('process', value, data); 13610 }); 13611 13612 $.each(filters, function(key, field) 13613 { 13614 filters[key].value = mdVars.Fn('process', field.value, data); 13615 }); 13616 13617 let object = JSON.stringify({id: item.Get('id'), properties, filters, sort}); 13618 13619 mdAjax.Fn('get').get({data: encodeURIComponent(object)}, null).url('/api/md.sources/run').run(null, function(response) 13620 { 13621 callback ? callback(response.out.data) : null; 13622 }, 13623 function() 13624 { 13625 callback ? callback(null) : null; 13626 }); 13627}); 13628mdAjax.FunctionCreate('delete', function(addon) 13629{
13630 return addon.Fn('utils.builder', 'DELETE'); 13631});mdAjax.FunctionCreate('get', function(addon) 13632{ 13633 return addon.Fn('utils.builder', 'GET'); 13634});mdAjax.FunctionCreate('post', function(addon) 13635{ 13636 return addon.Fn('utils.builder', 'POST'); 13637});mdAjax.FunctionCreate('put', function(addon) 13638{ 13639 return addon.Fn('utils.builder', 'PUT'); 13640});mdAjax.FunctionCreate('utils.builder', function(addon, type = 'GET') 13641{ 13642 this.data = {type, url: '', get: {}, post: {}, form: false, headers: {}}; 13643 13644 this.init = () => 13645 { 13646 return this; 13647 }; 13648 13649 this.url = (url) => 13650 { 13651 this.data.url = url + ''; 13652 13653 return this; 13654 }; 13655 13656 this.header = (key, value) => 13657 { 13658 this.data.headers[key] = value; 13659 13660 return this; 13661 }; 13662 13663 this.get = (get, value = null) => 13664 { 13665 if(typeof get !== 'object' || value !== null) 13666 { 13667 this.data.get[get] = value; 13668 } 13669 else 13670 { 13671 $.each(get, (key, value) => 13672 { 13673 this.data.get[key] = value; 13674 }); 13675 } 13676 13677 return this; 13678 }; 13679 13680 this.post = (post, value = null) => 13681 { 13682 if(typeof post !== 'object' || value !== null) 13683 { 13684 this.data.post[post] = value; 13685 } 13686 else 13687 { 13688 $.each(post, (key, value) => 13689 { 13690 this.data.post[key] = value; 13691 }); 13692 } 13693 13694 return this; 13695 }; 13696 13697 this.form = (form) => 13698 { 13699 this.data.form = form; 13700 13701 return this; 13702 } 13703 13704 this.run = (callback = null, then = null, fail = null) => 13705 { 13706 return addon.Fn('utils.execute', this.data).run(callback, then, fail); 13707 }; 13708 13709 return this; 13710});mdAjax.FunctionCreate('utils.execute', function(addon, data) 13711{ 13712 this.response = null; 13713 13714 this.init = () => 13715 { 13716 this.history(); 13717 return this; 13718 }; 13719 13720 this.history = () => 13721 { 13722 if(data.url.includes('://')) { return; } 13723 13724 if(Object.keys(data.get).length) 13725 { 13726 /* history.pushState({}, null, data.url); */ 13727 } 13728 }; 13729 13730 this.run = (callback = null, then = null, fail = null) => 13731 { 13732 let done = function(response, headers, xhr) 13733 { 13734 if(callback) 13735 { 13736 callback(response, headers, xhr); 13737 } 13738 13739 if(then && (response && response.success)) 13740 { 13741 then(response, headers, xhr); 13742 } 13743 13744 if(fail && (!response || !response.success)) 13745 { 13746 fail(response, headers, xhr); 13747 } 13748 }; 13749 13750 if(!data.form) 13751 { 13752 return fetch(addon.Fn('utils.url', data.url, data.get), { 13753 method: data.type, 13754 body: (data.type !== 'GET' ? JSON.stringify(data.post) : null), 13755 }) 13756 .then(response => response.json()) 13757 .then((response) => 13758 { 13759 done(response); 13760 }) 13761 .catch(error => 13762 { 13763 console.log(error); 13764 done({success: false, message: `Fatal error. Please check console for more information and reach out to support.`}); 13765 }); 13766 } 13767 else 13768 { 13769 return fetch(addon.Fn('utils.url', data.url, data.get), { 13770 method: 'POST', 13771 body: data.form, 13772 }) 13773 .then(response => response.json()) 13774 .then((response) => 13775 { 13776 done(response); 13777 }) 13778 .catch(error => 13779 { 13780 console.log(error); 13781 done({success: false, message: `Fatal error. Please check console for more information and reach out to support.`}); 13782 }); 13783 } 13784 }; 13785 13786 return this; 13787});mdAjax.FunctionCreate('utils.url', function(addon, url, get) 13788{ 13789 url = url.split('?'); 13790 13791 let route = {}; 13792 13793 if(url.length > 1) 13794 { 13795 route = JSON.parse('{"' + decodeURI(url[1]).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}') 13796 } 13797 13798 for(let key in get) 13799 { 13800 route[key] = get[key]; 13801 } 13802 13803 url = url[0]; 13804 13805 if(Object.keys(route).length) 13806 { 13807 url = (url + '?' + $.param(route)); 13808 } 13809 13810 if(url[0] === '/') 13811 {
13812 let language = $dh.language(); 13813 13814 if(language.default !== language.active) 13815 { 13816 return '/' + language.active.toLowerCase() + url; 13817 } 13818 } 13819 13820 return url; 13821}); 13822mdModal.FieldAdd('html'); 13823mdModal.FieldAdd('onOpen'); 13824mdModal.FieldAdd('onClose'); 13825mdModal.FieldAdd('clean'); 13826mdModal.FieldAdd('overlay'); 13827mdModal.FieldAdd('timer'); 13828mdModal.FieldAdd('closeable'); 13829mdModal.FieldAdd('corners'); 13830mdModal.FieldAdd('width'); 13831mdModal.FieldAdd('height'); 13832 13833mdModal.FieldAdd('time', null, function() 13834{ 13835 return Math.floor(Date.now() / 1000); 13836}); 13837 13838mdModal.FieldAdd('align', function(value) 13839{ 13840 if(!value || typeof value !== 'object') 13841 { 13842 return {x: 'center', y: 'center'}; 13843 } 13844 13845 return value; 13846}); 13847 13848mdModal.FieldAdd('offset', function(value) 13849{ 13850 if(!value || typeof value !== 'object') 13851 { 13852 return {x: 0, y: 0}; 13853 } 13854 13855 return value; 13856}); 13857 13858mdModal.FieldAdd('element', function(value) 13859{ 13860 if(!value) 13861 { 13862 return $('#dh-modal'); 13863 } 13864 13865 return value; 13866});$(document).on('click', '[modal-close]', function() 13867{ 13868 let id = $(this).closest('item[for="md.modal"]').attr('data-id'); 13869 let modal = mdModal.ItemGet(id); 13870 13871 if(modal) 13872 { 13873 modal.Remove({animation: 'fadeIn'}); 13874 } 13875}); 13876 13877$(document).on('change', '[modal-close-change]', function() 13878{ 13879 let id = $(this).closest('item[for="md.modal"]').attr('data-id'); 13880 let modal = mdModal.ItemGet(id); 13881 13882 if(modal) 13883 { 13884 modal.Remove({animation: 'fadeIn'}); 13885 } 13886}); 13887 13888$(document).on('mousedown', function(event) 13889{ 13890 if(event.which !== 1) 13891 { 13892 return; 13893 } 13894 13895 let active = $(event.target).closest('item[for="md.modal"]').attr('data-id'); 13896 let modals = []; 13897 let closed = false; 13898 13899 $.each($('items[for="md.modal"]').children(), function() 13900 { 13901 modals.push($(this).attr('data-id')); 13902 }); 13903 13904 modals.reverse().forEach(function(id) 13905 { 13906 if(active === id) 13907 { 13908 return true; 13909 } 13910 13911 let modal = mdModal.ItemGet(id); 13912 13913 if(!modal) 13914 { 13915 return true; 13916 } 13917 13918 // if(Math.floor(Date.now() / 1000) - modal.Get('time') < 1) 13919 // { 13920 // return; 13921 // } 13922 13923 if(modal.Get('closeable') === 'soft' && !active && !closed) 13924 { 13925 modal.Remove({animation: 'fadeOut'}); 13926 closed = true; 13927 13928 return true; 13929 } 13930 13931 if(modal.Get('closeable') === 'hard') 13932 { 13933 modal.Remove({animation: 'fadeOut'}); 13934 } 13935 }); 13936});$(document).on('page/ready', function(event, addon) 13937{ 13938 $('items[for="md.modal"]').html(''); 13939}); 13940 13941$(document).on('page/load', function() 13942{ 13943 $('.dh-scrollbar').on('scroll', function() 13944 { 13945 $.each(mdModal.ItemsGet(), function(index, item) 13946 { 13947 item.Fn('position'); 13948 }); 13949 }); 13950});$(window).resize(function() 13951{ 13952 $.each(mdModal.ItemsGet(), function(index, item) 13953 { 13954 item.Fn('position'); 13955 }); 13956});$(window).on('scroll', function() 13957{ 13958 $.each(mdModal.ItemsGet(), function(index, item) 13959 { 13960 item.Fn('position'); 13961 }); 13962});mdModal.FunctionCreate('template.middle', function(addon, content, title = null, width = 480, callback = null) 13963{ 13964 let config = { 13965 id: 'template.middle', 13966 overlay: {opacity: 0.5, closeable: true}, 13967 actions: '', 13968 onClose: null 13969 }; 13970 13971 let actions = { 13972 top: '', 13973 close: dh.icon('close-x', 16, 'all', { 13974 'modal-close': true, 13975 'style': 'cursor: pointer' 13976 }) 13977 }; 13978 13979 if(callback) 13980 { 13981 callback(config, actions); 13982 } 13983 13984 config.html = () => 13985 { 13986 if(title) 13987 { 13988 return ` 13989 <div class="dh-scrollbar" style="border: 1px solid var(--dh-border-1); padding: 15px; max-height: 90vh; min-width: ${width}px; background: var(--dh-bg-1); border-radius: 8px;"> 13990 <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;"> 13991 <h4>${title}</h4> 13992 13993 <div class="dh-flex gap10" style="align-items: center"> 13994 ${actions.top} 13995 ${actions.close} 13996 </div> 13997 </div> 13998 13999 <div>${content}</div> 14000 </div> 14001 `; 14002 } 14003 14004 return content; 14005 } 14006 14007 mdModal.ItemAdd(config); 14008});mdModal.FunctionCreate('template.sidebar', function(addon, content, position = 'right', width = '50vw', overlay
14008= true, callback = null) 14009{ 14010 let config = { 14011 id: 'template.sidebar' + (overlay ? '.overlay' : ''), 14012 width: '100vw', 14013 height: '100vh', 14014 html: () => 14015 { 14016 if(overlay) 14017 { 14018 return ` 14019 <div style="background: rgb(0 0 0 / 50%); width: 100%; height: 100%; display: flex; justify-content: ${position === 'right' ? 'end' : 'start'}"> 14020 <div style="width: ${width};">${content}</div> 14021 </div> 14022 `; 14023 } 14024 14025 return ` 14026 <div style="pointer-events: none; width: 100%; height: 100%; display: flex; justify-content: ${position === 'right' ? 'end' : 'start'}"> 14027 <div style="pointer-events: auto; width: ${width};">${content}</div> 14028 </div> 14029 `; 14030 } 14031 }; 14032 14033 if(callback) 14034 { 14035 callback(config); 14036 } 14037 14038 mdModal.ItemAdd(config) 14039});mdModal.OnItemAdd(function(addon, item) 14040{ 14041 14042 if(item.TargetItem('html').length) 14043 { 14044 item.Replace('html'); 14045 } 14046 else 14047 { 14048 item.Push('html', {animation: 'fadeIn'}); 14049 } 14050 14051 item.Fn('position'); 14052 14053 if(item.Get('timer')) 14054 { 14055 dh.debounce(function() 14056 { 14057 item.Remove({animation: 'fadeIn'}); 14058 }, item.Get('timer'), 'mdModal'); 14059 } 14060 14061 let onOpen = item.Get('onOpen'); 14062 14063 if(typeof onOpen === 'function') 14064 { 14065 onOpen(item); 14066 } 14067});mdModal.OnItemRemove(function(addon, item) 14068{ 14069 let onClose = item.Get('onClose'); 14070 14071 if(typeof onClose === 'function') 14072 { 14073 onClose(item); 14074 } 14075});mdModal.FunctionCreate('item.position', function(addon, item) 14076{ 14077 let element = item.Get('element'); 14078 14079 if(!element.is(':visible')) 14080 { 14081 return item.Remove({animation: 'fadeOut'}); 14082 } 14083 14084 let target = item.TargetItem('html').find('>.html'); 14085 let align = item.Get('align'); 14086 let offset = item.Get('offset'); 14087 14088 /* Reset Width / Height */ 14089 target.css('width', ''); 14090 target.css('height', ''); 14091 14092 /* Width * Height */ 14093 let width = target.innerWidth(); 14094 let height = target.innerHeight(); 14095 14096 /* Width Inherit */ 14097 if(item.Get('width') === 'inherit') 14098 { 14099 width = element.innerWidth(); 14100 } 14101 else if(item.Get('width')) 14102 { 14103 width = item.Get('width'); 14104 } 14105 14106 /* Height Inherit */ 14107 if(item.Get('height') === 'inherit') 14108 { 14109 height = element.innerHeight(); 14110 } 14111 else if(item.Get('height')) 14112 { 14113 height = item.Get('height'); 14114 } 14115 14116 /* Position */ 14117 let x,y = 0; 14118 14119 /* Position */ 14120 let position = { 14121 x: element.offset().left + element.innerWidth() / 2, 14122 y: element.offset().top - $(window).scrollTop() + element.innerHeight() / 2 14123 }; 14124 14125 if(align.x === 'center') { x = position.x - width / 2 + offset.x } 14126 if(align.x === 'center-left') { x = position.x - width + offset.x } 14127 if(align.x === 'center-right') { x = position.x + offset.x } 14128 14129 if(align.x === 'left-in') { x = position.x - element.innerWidth() / 2 + offset.x; } 14130 if(align.x === 'left-out') { x = position.x - element.innerWidth() / 2 - width + offset.x; } 14131 14132 if(align.x === 'right-in') { x = position.x + element.innerWidth() / 2 - width + offset.x; } 14133 if(align.x === 'right-out') { x = position.x + element.innerWidth() / 2 + offset.x; } 14134 14135 if(align.y === 'center') { y = position.y - height / 2 + offset.y } 14136 if(align.y === 'center-top') { y = position.y - height + offset.y } 14137 if(align.y === 'center-bottom') { y = position.y + offset.y } 14138 14139 if(align.y === 'top-in') { y = position.y - element.innerHeight() / 2 + offset.y; } 14140 if(align.y === 'top-out') { y = position.y - element.innerHeight() / 2 - height + offset.y; } 14141 14142 if(align.y === 'bottom-in') { y = position.y + element.innerHeight() / 2 - height + offset.y; } 14143 if(align.y === 'bottom-out') { y = position.y + element.innerHeight() / 2 + offset.y; } 14144 14145 if(item.Get('corners')) 14146 { 14147 if(x + width > window.innerWidth) { x = window.innerWidth - width - 10; } 14148 if(y + height > window.innerHeight) { y = window.innerHeight - height - 10; } 14149 14150 if(x < 0) { x = 10; } 14151 if(y < 0) { y = 10; } 14152 } 14153 14154 target.css('transform', `translate(${parseInt(x)}px, ${parseInt(y)}px)`); 14155 target.css('width', width); 14156 target.css('height', height); 14157});mdModal.RenderCreate('item.content', function(addon, item, data) 14158{ 14159 let width = item.Get('width', true); 14160 let height = item.Get('height', true); 14161 let element = item.Get('element'); 14162 14163 /* Width Inherit */
14164 if(item.Get('width') === 'inherit') 14165 { 14166 width = element.innerWidth(); 14167 } 14168 14169 /* Height Inherit */ 14170 if(item.Get('height') === 'inherit') 14171 { 14172 height = element.innerHeight(); 14173 } 14174 14175 return ` 14176 <div class="html${item.Get('clean') ? '' : ' dh-default'}" style="width: ${width}; height: ${height}">${item.Get('html')(item)}</div> 14177 `; 14178});mdModal.RenderCreate('item.html', function(addon, item, data, attributes) 14179{ 14180 return ` 14181 ${item.Render('overlay', {}, {}, false)} 14182 ${item.Render('content', {}, {}, false)} 14183 `; 14184});mdModal.RenderCreate('item.overlay', function(addon, item, data) 14185{ 14186 let overlay = item.Get('overlay'); 14187 14188 if(!overlay || typeof overlay !== 'object') 14189 { 14190 return ''; 14191 } 14192 14193 let opacity = 'opacity' in overlay ? overlay.opacity : 0.2; 14194 14195 return ` 14196 <div class="overlay"${overlay.closeable ? ' modal-close ' : ''}style="opacity: ${dh.sanitize(opacity)}; ${overlay.clickable ? 'pointer-events: none' : ''}"></div> 14197 `; 14198}); 14199/* Plugin Class */ 14200account.FunctionCreate('login.form', (addon, email, password) => 14201{ 14202 return new Promise((resolve, reject) => 14203 { 14204 fetch('/api/account/login', { 14205 method: 'POST', 14206 headers: { 14207 'Accept': 'application/json', 14208 'Content-Type': 'application/json' 14209 }, 14210 body: JSON.stringify({email, password}) 14211 }) 14212 .then(response => response.json()) 14213 .then((response) => 14214 { 14215 if(response.success) 14216 { 14217 resolve(response); 14218 } 14219 else 14220 { 14221 reject(response); 14222 } 14223 }); 14224 }); 14225});account.FunctionCreate('login.token', (addon, token) => 14226{ 14227 return new Promise((resolve, reject) => 14228 { 14229 fetch('/api/account/login', { 14230 method: 'POST', 14231 headers: { 14232 'Accept': 'application/json', 14233 'Content-Type': 'application/json' 14234 }, 14235 body: JSON.stringify({token}) 14236 }) 14237 .then(response => response.json()) 14238 .then((response) => 14239 { 14240 if(response.success) 14241 { 14242 resolve(response); 14243 } 14244 else 14245 { 14246 reject(response); 14247 } 14248 }); 14249 }); 14250});account.FunctionCreate('logout', () => 14251{ 14252 return new Promise((resolve, reject) => 14253 { 14254 fetch('/api/account/logout', { 14255 method: 'POST', 14256 headers: { 14257 'Accept': 'application/json', 14258 'Content-Type': 'application/json' 14259 } 14260 }) 14261 .then(response => response.json()) 14262 .then((response) => 14263 { 14264 if(response.success) 14265 { 14266 resolve(response); 14267 } 14268 else 14269 { 14270 reject(response); 14271 } 14272 }); 14273 }); 14274});account.FunctionCreate('register', (addon, name, email, password, acknowledge = false) => 14275{ 14276 return new Promise((resolve, reject) => 14277 { 14278 fetch('/api/account/register', { 14279 method: 'POST', 14280 headers: { 14281 'Accept': 'application/json', 14282 'Content-Type': 'application/json' 14283 }, 14284 body: JSON.stringify({name, email, password, acknowledge}) 14285 }) 14286 .then(response => response.json()) 14287 .then((response) => 14288 { 14289 if(response.success) 14290 { 14291 resolve(response); 14292 } 14293 else 14294 { 14295 reject(response); 14296 } 14297 }); 14298 }); 14299});$(document).on('submit', 'form[dh-login]', function(event) 14300{ 14301 event.preventDefault(); 14302 14303 $(this).removeClass('dh-success'); 14304 $(this).removeClass('dh-fail'); 14305 14306 $(this).find('.dh-form-executed').addClass('dh-form-fail'); 14307 $(this).find('.dh-form-response').text(''); 14308 $(this).find('[name]').removeClass('dh-error'); 14309 14310 let email = $(this).find('input[name="dh-email"]').val(); 14311 let password = $(this).find('input[name="dh-password"]').val(); 14312 let redirect = $(this).find('input[name="dh-redirect"]').val(); 14313 14314 let fail = (message, errors = []) => 14315 { 14316 $(this).addClass('dh-fail'); 14317
14318 errors.forEach((error) => 14319 { 14320 $(this).find(`[name="${error}"]`).addClass('dh-error'); 14321 }); 14322 14323 if($(this).find('.dh-form-fail').length) 14324 { 14325 $(this).find('.dh-form-fail').removeClass('dh-form-fail').addClass('dh-form-executed'); 14326 } 14327 else if($(this).find('.dh-form-response').length) 14328 { 14329 $(this).find('.dh-form-response').text(message); 14330 } 14331 else 14332 { 14333 $(this).prepend(`<p class="dh-form-response">${message}</p>`); 14334 } 14335 }; 14336 14337 let success = (message) => 14338 { 14339 if(redirect) 14340 { 14341 return window.location.href = redirect; 14342 } 14343 14344 $(this).addClass('dh-success'); 14345 14346 if($(this).find('.dh-form-success').length) 14347 { 14348 let html = $(this).find('.dh-form-success')[0].outerHTML; 14349 14350 $(this).html(html); 14351 $(this).find('.dh-form-success').removeClass('dh-form-success'); 14352 } 14353 else if($(this).find('.dh-form-response').length) 14354 { 14355 $(this).find('.dh-form-response').text(message); 14356 } 14357 else 14358 { 14359 $(this).prepend(`<p class="dh-form-response">${message}</p>`); 14360 } 14361 }; 14362 14363 if(!email) 14364 { 14365 return fail('Please provide email address.', ['dh-email']); 14366 } 14367 14368 if(!password) 14369 { 14370 return fail('Please provide password.', ['dh-password']); 14371 } 14372 14373 account.Fn('login.form', email, password).then((response) => 14374 { 14375 let options = {email, password, redirect, message: response.message, prevent: false}; 14376 14377 mdEvents.Fn('run', 'dh.login.success', options); 14378 14379 if(!options.prevent) 14380 { 14381 success(response.message); 14382 } 14383 }) 14384 .catch((response) => 14385 { 14386 let options = {email, password, redirect, message: response.message, prevent: false}; 14387 14388 mdEvents.Fn('run', 'dh.login.fail', options); 14389 14390 if(!options.prevent) 14391 { 14392 fail(response.message, ['dh-email', 'dh-password']); 14393 } 14394 }); 14395});$(document).on('submit', 'form[dh-logout]', function(event) 14396{ 14397 let redirect = $(this).find('input[name="dh-redirect"]').val(); 14398 14399 account.Fn('logout').then((response) => 14400 { 14401 let options = {message: response.message, prevent: false}; 14402 14403 mdEvents.Fn('run', 'dh.logout.success', options); 14404 14405 if(!options.prevent) 14406 { 14407 if(redirect) 14408 { 14409 return window.location.href = redirect; 14410 } 14411 } 14412 }) 14413 .catch((response) => 14414 { 14415 let options = {message: response.message, prevent: false}; 14416 14417 mdEvents.Fn('run', 'dh.logout.fail', options); 14418 }); 14419});$(document).on('submit', 'form[dh-register]', function(event) 14420{ 14421 event.preventDefault(); 14422 14423 $(this).removeClass('dh-success'); 14424 $(this).removeClass('dh-fail'); 14425 14426 $(this).find('.dh-form-executed').addClass('dh-form-fail'); 14427 $(this).find('.dh-form-response').text(''); 14428 $(this).find('[name]').removeClass('dh-error'); 14429 14430 let name = $(this).find('input[name="dh-name"]').val(); 14431 let email = $(this).find('input[name="dh-email"]').val(); 14432 let password = $(this).find('input[name="dh-password"]').val(); 14433 let passwordConfirm = $(this).find('input[name="dh-password-confirm"]').val(); 14434 let redirect = $(this).find('input[name="dh-redirect"]').val(); 14435 14436 let acknowledge = $(this).find('input[name="dh-acknowledge"]'); 14437 14438 let fail = (message, errors = []) => 14439 { 14440 $(this).addClass('dh-fail'); 14441 14442 errors.forEach((error) => 14443 { 14444 $(this).find(`[name="${error}"]`).addClass('dh-error'); 14445 }); 14446 14447 if($(this).find('.dh-form-fail').length) 14448 { 14449 $(this).find('.dh-form-fail').removeClass('dh-form-fail').addClass('dh-form-executed'); 14450 } 14451 else if($(this).find('.dh-form-response').length) 14452 { 14453 $(this).find('.dh-form-response').text(message); 14454 } 14455 else 14456 { 14457 $(this).prepend(`<p class="dh-form-response">${message}</p>`); 14458 } 14459 }; 14460 14461 let success = (message) => 14462 { 14463 if(redirect) 14464 { 14465 return window.location.href = redirect; 14466 } 14467 14468 $(this).addClass('dh-success'); 14469 14470 if($(this).find('.dh-form-success').length) 14471 { 14472 let html = $(this).find('.dh-form-success')[0].outerHTML; 14473 14474 $(this).html(html); 14475 $(this).find('.dh-form-success').removeClass('dh-form-success'); 14476 } 14477 else if($(this).find('.dh-form-response').length) 14478 { 14479 $(this).find('.dh-form-response').text(message); 14480 } 14481 else 14482 { 14483 $(this).prepend(`<p class="dh-form-response">${message}</p>`); 14484 } 14485 }; 14486 14487 if(!name) 14488 { 14489 return fail('Please provide name.', ['dh-name']); 14490 } 14491 14492 if(!email) 14493 { 14494 return fail('Please provide email address.', ['dh-email']); 14495 } 14496 14497 if(!password || !passwordConfirm) 14498 { 14499 return fail('Please provide password.', ['dh-password', 'dh-password-confirm']); 14500 } 14501 14502 if(password !== passwordConfirm) 14503 { 14504 return fail('Password did not match.', ['dh-password', 'dh-password-confirm']); 14505 } 14506
14507 if(acknowledge.length && !acknowledge.is(':checked')) 14508 { 14509 return fail(acknowledge.attr('dh-message')); 14510 } 14511 14512 account.Fn('register', name, email, password, (acknowledge.length ? true : false)).then((response) => 14513 { 14514 let options = {acknowledge: acknowledge.length ? true : false, name, email, password, redirect, message: response.message, prevent: false}; 14515 14516 mdEvents.Fn('run', 'dh.register.success', options); 14517 14518 if(!options.prevent) 14519 { 14520 success(response.message); 14521 } 14522 }) 14523 .catch((response) => 14524 { 14525 let options = {acknowledge: acknowledge.length ? true : false, name, email, password, redirect, message: response.message, prevent: false}; 14526 14527 mdEvents.Fn('run', 'dh.register.fail', options); 14528 14529 if(!options.prevent) 14530 { 14531 fail(response.message, ['dh-name', 'dh-email', 'dh-password', 'dh-password-confirm']); 14532 } 14533 }); 14534}); 14535$(document).on('submit', '#sv_billing_balance', function(event) 14536{ 14537 event.preventDefault(); 14538 14539 let amount = $(this).find('input').val(); 14540 14541 let data = { 14542 amount: amount * 100, 14543 description: 'Wallet Top-up' 14544 }; 14545 14546 sv_billing.Fn('balance.add', data).then((response) => 14547 { 14548 $(this).parent().find(".in-progress").show(); 14549 $(this).parent().find(">p").hide(); 14550 $(this).parent().find(">h2").hide(); 14551 $(this).hide(); 14552 14553 window.open(response.recharge.url, '_BLANK'); 14554 14555 response.payment.then((paid) => 14556 { 14557 if(paid) 14558 { 14559 $(this).parent().find(".in-progress").hide(); 14560 $(this).parent().find(".success").show(); 14561 } 14562 else 14563 { 14564 $(this).parent().find(".in-progress").hide(); 14565 $(this).parent().find(".failed").show(); 14566 } 14567 }) 14568 .catch((message) => 14569 { 14570 alert(message); 14571 }); 14572 }) 14573 .catch((message) => 14574 { 14575 alert(message); 14576 }); 14577}); 14578$(document).on('click', '[billing-balance-modal]', function(event) 14579{ 14580 let amount = $(this).attr('data-amount'); 14581 14582 if(!amount) 14583 { 14584 amount = 5; 14585 } 14586 14587 sv_billing.Fn('balance.modal', { 14588 amount 14589 }); 14590});sv_billing.FunctionCreate('balance.add', function(addon, data = {}) 14591{ 14592 this.tries = 0; 14593 14594 this.init = () => 14595 { 14596 return new Promise((resolve, reject) => 14597 { 14598 if(!this.data(data)) 14599 { 14600 return reject('Invalid data sent.'); 14601 } 14602 14603 this.recharge((recharge) => 14604 { 14605 let payment = new Promise((resolve, reject) => 14606 { 14607 this.payment(recharge, resolve, reject); 14608 }); 14609 14610 resolve({recharge, payment}); 14611 }, 14612 (message) => 14613 { 14614 reject(message); 14615 }); 14616 }); 14617 }; 14618 14619 this.recharge = (then, fail) => 14620 { 14621 sv_billing.ActionSend('recharge', {type: 'POST', amount: data.amount, description: data.description}, (response) => 14622 { 14623 then(response.out.recharge); 14624 }, 14625 (response) => 14626 { 14627 fail(response.message); 14628 }); 14629 }; 14630 14631 this.data = () => 14632 { 14633 if(!('amount' in data) || typeof data.amount !== 'number') 14634 { 14635 return false; 14636 } 14637 14638 if(!('description' in data) || typeof data.description !== 'string') 14639 { 14640 return false; 14641 } 14642 14643 return true; 14644 }; 14645 14646 this.payment = (recharge, resolve, reject) => 14647 { 14648 if(this.tries >= 120) 14649 { 14650 return resolve(false); 14651 } 14652 14653 this.tries++; 14654 14655 setTimeout(() => 14656 { 14657 sv_billing.Fetch('recharge', {id: recharge.id}, (response) => 14658 { 14659 if(response.recharge.paid) 14660 { 14661 resolve(true); 14662 } 14663 else 14664 { 14665 this.payment(recharge, resolve, reject); 14666 } 14667 }, null, () => 14668 { 14669 reject('Failed to retreive recharge entity.'); 14670 }); 14671 }, 1000); 14672 }; 14673 14674 return this.init(); 14675});sv_billing.FunctionCreate('balance.check', function(addon, price = 0) 14676{ 14677 return new Promise((resolve, reject) => 14678 { 14679 sv_billing.Fn('balance.get').then((balance) => 14680 { 14681 if(balance >= price) 14682 { 14683 resolve({has: true, missing: 0}); 14684 } 14685 else 14686 { 14687 resolve({has: false, missing: (price - balance)}); 14688 } 14689 }) 14690 .catch((message) => 14691 { 14692 reject({has: false, missing: 0, message}); 14693 }); 14694 }); 14695});sv_billing.FunctionCreate('balance.get', function(addon) 14696{ 14697 return new Promise((resolve, reject) => 14698 { 14699 sv_billing.Fetch('balance', {}, (response) => 14700 { 14701 resolve(response.balance); 14702 }, 14703 (response) => 14704 { 14705 reject(response.message) 14706 }); 14707 }); 14708});sv_billing.FunctionCreate('balance.modal', function(addon, data = {}) 14709{ 14710 mdModal.ItemAdd({ 14711 id: 'sv_billing_balance_modal', 14712 overlay: {opacity: 0.7}, 14713 html: () => 14714 {
14715 return sv_billing.Render('balance.modal', data); 14716 } 14717 }); 14718});sv_billing.RenderCreate('balance.modal', (addon, data) => 14719{ 14720 let amount = data('amount', 5); 14721 14722 return ` 14723 14724 <div modal-close> ${dh.icon('close-x', 16, 'all')} </div> 14725 14726 <h2>Deposit funds</h2> 14727 <p>You can deposit funds into your Divhunt wallet, and use the wallet to purchase anything on Divhunt, such as subscriptions, templates, and plugins. The minimum balance to deposit is $5.</p> 14728 14729 <form id="sv_billing_balance"> 14730 <span> Deposit amount: </span> 14731 <div> 14732 <input placeholder="5" value="${amount}"> 14733 <span> $ </span> 14734 </div> 14735 <button type="submit">Deposit</button> 14736 </form> 14737 14738 14739 <div class="payment-status in-progress"> 14740 ${dh.icon('ph-spinner', 40, 'all')} 14741 <h3> Waiting for transaction </h3> 14742 <p> Your payment is being processed. </p> 14743 </div> 14744 14745 <div class="payment-status success"> 14746 ${dh.icon('ph-success', 40, 'all')} 14747 <h3> Transaction successful! </h3> 14748 <p> Your payment has been successfully processed. The money has been deposited into your wallet. </p> 14749 </div> 14750 14751 <div class="payment-status failed"> 14752 ${dh.icon('ph-failed', 40, 'all')} 14753 <h3> Transaction failed </h3> 14754 <p> The transaction could not be completed. Session expired after 120 seconds. </p> 14755 </div> 14756 14757 `; 14758 14759}); 14760mdEvents.ItemsAdd({ 14761 group: 'Forms', 14762 id: 'dh.forms.submit', 14763 name: 'On Form Submit', 14764 description: 'Executes when the form has been submitted.', 14765 role: ['website'] 14766}, { 14767 group: 'Forms', 14768 id: 'dh.forms.success', 14769 name: 'On Form Success', 14770 description: 'Executes when form submission success.', 14771 role: ['website'] 14772}, { 14773 group: 'Forms', 14774 id: 'dh.forms.fail', 14775 name: 'On Form Fail', 14776 description: 'Executes when form submission fails.', 14777 role: ['website'] 14778});admin.forms = Addons.create(admin.GetName() + '.forms', function(addon) 14779{ 14780 14781});$(document).on('submit', 'form[dh-form]', function(event) 14782{ 14783 event.preventDefault(); 14784 14785 let form = { 14786 id: parseInt($(this).attr('dh-form')), 14787 fields: {}, 14788 data: new FormData, 14789 url: window.location.pathname, 14790 event, 14791 target: $(this), 14792 }; 14793 14794 let tag = components.tags.ItemGet($(this).attr('class').split(' ')[0].replace('t', '')); 14795 14796 $.each($(this).serializeArray(), function(index, field) 14797 { 14798 form.fields[field.name] = field.value; 14799 form.data.append(field.name, field.value); 14800 }); 14801 14802 // Builder form validation 14803 if($(this).hasClass('dh-fb')) 14804 { 14805 let config = $(this).data('dh-fb-config'); 14806 14807 if(config) 14808 { 14809 let result = admin.forms.Fn('validate', $(this), config); 14810 14811 if(!result.valid) 14812 { 14813 return; 14814 } 14815 } 14816 } 14817 14818 if('dh-protection' in form.fields && form.fields['dh-protection']) 14819 { 14820 return; 14821 } 14822 14823 $.each($(this).find('input[name][type="file"]'), function() 14824 { 14825 let name = $(this).attr('name'); 14826 14827 $.each($(this).prop('files'), function(index, file) 14828 { 14829 form.data.append(name + '[]', file); 14830 }); 14831 }); 14832 14833 mdEvents.Fn('run', 'dh.forms.submit', {form, tag}); 14834 14835 if(form.id === null) 14836 { 14837 return; 14838 } 14839 14840 admin.forms.Fn('submit', form.id, form.data, form.url).success((response) => 14841 { 14842 if($(this).hasClass('dh-fb')) 14843 { 14844 let config = $(this).data('dh-fb-config'); 14845 let settings = config?.settings || {}; 14846 14847 if(settings.successAction === 'redirect' || settings.successAction === 'both') 14848 { 14849 if(settings.successRedirect) 14850 { 14851 window.location.href = settings.successRedirect; 14852 return; 14853 } 14854 } 14855 14856 let $success = $(this).find('.dh-fb-success'); 14857 14858 if($success.length) 14859 { 14860 $(this).find('.dh-fb-step, .dh-fb-actions, .dh-fb-progress').hide(); 14861 $success.show(); 14862 } 14863 else 14864 { 14865 $(this).html(response.message); 14866 } 14867 } 14868 else if($(this).find('.dh-form-success').length) 14869 { 14870 let html = $(this).find('.dh-form-success')[0].outerHTML; 14871 14872 $(this).html(html); 14873 $(this).find('.dh-form-success').removeClass('dh-form-success'); 14874 } 14875 else 14876 { 14877 $(this).html(response.message); 14878 } 14879 14880 mdEvents.Fn('run', 'dh.forms.success', {form, response, tag}); 14881 }) 14882 .fail((response) => 14883 { 14884 if($(this).hasClass('dh-fb')) 14885 { 14886 let $fail = $(this).find('.dh-fb-fail'); 14887 14888 if($fail.length) 14889 { 14890 $fail.show(); 14891 14892 setTimeout(function() 14893 { 14894 $fail.hide(); 14895 }, 5000); 14896 } 14897 14898 if(response.errors && typeof response.errors === 'object') 14899 {
14900 Object.keys(response.errors).forEach(function(fieldId) 14901 { 14902 let $field = $(this).find('[data-field="' + fieldId + '"]'); 14903 14904 $field.addClass('dh-fb-field-error'); 14905 $field.find('.dh-fb-error').text(response.errors[fieldId]).show(); 14906 }.bind(this)); 14907 } 14908 } 14909 else if($(this).find('.dh-form-fail').length) 14910 { 14911 let html = $(this).find('.dh-form-fail')[0].outerHTML; 14912 14913 $(this).html(html); 14914 $(this).find('.dh-form-fail').removeClass('dh-form-fail'); 14915 } 14916 else 14917 { 14918 $(this).html(response.message); 14919 } 14920 14921 mdEvents.Fn('run', 'dh.forms.fail', {form, response, tag}); 14922 }); 14923});admin.forms.FunctionCreate('conditions', function(addon, $form, config) 14924{ 14925 let allFields = []; 14926 14927 (config.steps || []).forEach(function(step) 14928 { 14929 (step.fields || []).forEach(function(field) 14930 { 14931 allFields.push(field); 14932 }); 14933 }); 14934 14935 let conditionalFields = allFields.filter(function(f) 14936 { 14937 return f.conditions && f.conditions.enabled && f.conditions.rules && f.conditions.rules.length; 14938 }); 14939 14940 if(!conditionalFields.length) 14941 { 14942 return; 14943 } 14944 14945 function evaluateConditions() 14946 { 14947 conditionalFields.forEach(function(field) 14948 { 14949 let conditions = field.conditions; 14950 let rules = conditions.rules || []; 14951 let logic = conditions.logic || 'and'; 14952 let results = []; 14953
14954 rules.forEach(function(rule) 14955 { 14956 let refField = allFields.find(f => f.id === rule.field) || allFields.find(f => f.name === rule.field); 14957 14958 if(!refField) 14959 { 14960 results.push(false); 14961 return; 14962 } 14963 14964 let value = addon.Fn('validate.getValue', $form, refField); 14965 let result = evaluateRule(rule.operator, value, rule.value); 14966 14967 results.push(result); 14968 }); 14969 14970 let visible; 14971 14972 if(logic === 'and') 14973 { 14974 visible = results.length > 0 && results.every(r => r); 14975 } 14976 else 14977 { 14978 visible = results.some(r => r); 14979 } 14980 14981 let $field = $form.find('[data-field="' + field.id + '"]'); 14982 14983 if(visible) 14984 { 14985 $field.removeClass('dh-fb-hidden').show(); 14986 } 14987 else 14988 { 14989 $field.addClass('dh-fb-hidden').hide(); 14990 14991 $field.find('.dh-fb-input, .dh-fb-textarea').val(''); 14992 $field.find('input[type="radio"], input[type="checkbox"]').prop('checked', false); 14993 $field.find('input[type="hidden"]').val(''); 14994 $field.find('.dh-fb-select-value').text($field.find('.dh-fb-select-trigger').data('placeholder') || 'Select...'); 14995 $field.find('.dh-fb-multiselect-value').text($field.closest('.dh-fb-multiselect').attr('data-placeholder') || 'Select...').addClass('placeholder'); 14996 $field.find('.dh-fb-multiselect-option').removeClass('checked'); 14997 14998 $field.removeClass('dh-fb-field-error'); 14999 $field.find('.dh-fb-error').text('').hide(); 15000 } 15001 }); 15002 } 15003 15004 function evaluateRule(operator, fieldValue, ruleValue) 15005 { 15006 fieldValue = (fieldValue || '').toString().toLowerCase(); 15007 ruleValue = (ruleValue || '').toString().toLowerCase(); 15008 15009 switch(operator) 15010 { 15011 case 'equals': 15012 return fieldValue === ruleValue; 15013 case 'not_equals': 15014 return fieldValue !== ruleValue; 15015 case 'contains': 15016 return fieldValue.indexOf(ruleValue) !== -1; 15017 case 'not_contains': 15018 return fieldValue.indexOf(ruleValue) === -1; 15019 case 'is_true': 15020 return fieldValue === '1' || fieldValue === 'true' || fieldValue === 'on' || fieldValue === 'yes'; 15021 case 'is_false': 15022 return fieldValue !== '1' && fieldValue !== 'true' && fieldValue !== 'on' && fieldValue !== 'yes'; 15023 case 'is_empty': 15024 return !fieldValue.length; 15025 case 'is_not_empty': 15026 return fieldValue.length > 0; 15027 case 'greater_than': 15028 return parseFloat(fieldValue) > parseFloat(ruleValue); 15029 case 'less_than': 15030 return parseFloat(fieldValue) < parseFloat(ruleValue); 15031 case 'starts_with': 15032 return fieldValue.indexOf(ruleValue) === 0; 15033 case 'ends_with': 15034 return fieldValue.indexOf(ruleValue, fieldValue.length - ruleValue.length) !== -1; 15035 default: 15036 return false; 15037 } 15038 } 15039 15040 $form.on('change input', '.dh-fb-input, .dh-fb-textarea, input[type="radio"], input[type="checkbox"], input[type="hidden"]', function() 15041 { 15042 evaluateConditions(); 15043 }); 15044 15045 evaluateConditions(); 15046}); 15047admin.forms.FunctionCreate('fileupload', function(addon, $wrapper) 15048{ 15049 let $drop = $wrapper.find('.dh-fb-file-drop'); 15050 let $input = $wrapper.find('input[type="file"]'); 15051 let $preview = $wrapper.find('.dh-fb-file-preview'); 15052 let $hidden = $wrapper.find('input[type="hidden"]'); 15053 let multiple = $wrapper.attr('data-multiple') === 'true'; 15054 15055 if(!$hidden.length) 15056 { 15057 let name = ($input.attr('name') || '').replace(/\[\]$/, ''); 15058 $hidden = $('<input type="hidden" name="' + name + '" value="">').appendTo($wrapper); 15059 $input.removeAttr('name'); 15060 } 15061 15062 let maxFiles = parseInt($wrapper.attr('data-max-files')) || 5; 15063 let maxSize = parseInt($wrapper.attr('data-max-size')) || 10; 15064 let accept = ($wrapper.attr('data-accept') || '').split(',').filter(
15064Boolean); 15065 let formId = $wrapper.closest('form[dh-form]').attr('dh-form'); 15066 15067 let files = []; 15068 15069 $input.on('click', function(e) 15070 { 15071 e.stopPropagation(); 15072 }); 15073 15074 $drop.on('click', function() 15075 { 15076 $input[0].click(); 15077 }); 15078 15079 $drop.on('dragover', function(e) 15080 { 15081 e.preventDefault(); 15082 $(this).addClass('dh-fb-file-drop-active'); 15083 }); 15084 15085 $drop.on('dragleave drop', function(e) 15086 { 15087 e.preventDefault(); 15088 $(this).removeClass('dh-fb-file-drop-active'); 15089 }); 15090 15091 $drop.on('drop', function(e) 15092 { 15093 handleFiles(e.originalEvent.dataTransfer.files); 15094 }); 15095 15096 $input.on('change', function() 15097 { 15098 handleFiles(this.files); 15099 this.value = ''; 15100 }); 15101 15102 function handleFiles(newFiles) 15103 { 15104 let $field = $wrapper.closest('.dh-fb-field'); 15105 15106 $field.find('.dh-fb-error').text('').hide(); 15107 $field.removeClass('dh-fb-field-error'); 15108 15109 for(let i = 0; i < newFiles.length; i++) 15110 { 15111 let file = newFiles[i]; 15112 15113 if(!multiple && files.length >= 1) 15114 { 15115 files = []; 15116 } 15117 15118 if(files.length >= maxFiles) 15119 { 15120 showError($field, 'Maximum ' + maxFiles + ' files allowed'); 15121 break; 15122 } 15123 15124 if(file.size > maxSize * 1024 * 1024) 15125 { 15126 showError($field, file.name + ' exceeds ' + maxSize + 'MB limit'); 15127 continue; 15128 } 15129 15130 if(accept.length) 15131 { 15132 let ext = '.' + file.name.split('.').pop().toLowerCase(); 15133 let typeMatch = accept.some(function(a) 15134 { 15135 a = a.trim().toLowerCase(); 15136 return a === ext || file.type.match(a.replace('*', '.*')); 15137 }); 15138 15139 if(!typeMatch) 15140 { 15141 showError($field, file.name + ' is not an accepted file type'); 15142 continue; 15143 } 15144 } 15145 15146 let entry = {file: file, link: null, uploading: true, error: null}; 15147 15148 files.push(entry); 15149 uploadFile(entry); 15150 } 15151 15152 renderPreview(); 15153 } 15154 15155 function uploadFile(entry) 15156 { 15157 let data = new FormData(); 15158 15159 data.append('upload', entry.file); 15160 data.append('id', formId); 15161 15162 mdAjax.Fn('post').form(data).url('/api/admin/forms.upload').run(null, 15163 function(response) 15164 { 15165 entry.uploading = false; 15166 entry.link = response.out.link; 15167 renderPreview(); 15168 updateHidden(); 15169 }, 15170 function(response) 15171 { 15172 entry.uploading = false; 15173 entry.error = response.message || 'Upload failed'; 15174 renderPreview(); 15175 updateHidden(); 15176 } 15177 ); 15178 } 15179 15180 function updateHidden() 15181 { 15182 let links = []; 15183 15184 files.forEach(function(entry) 15185 { 15186 if(entry.link) 15187 { 15188 links.push(entry.link); 15189 } 15190 }); 15191 15192 $hidden.val(links.join(',')); 15193 } 15194 15195 function showError($field, message) 15196 { 15197 $field.addClass('dh-fb-field-error'); 15198 $field.find('.dh-fb-error').text(message).show(); 15199 } 15200 15201 function getFileExtension(filename) 15202 { 15203 return filename.split('.').pop().toUpperCase(); 15204 } 15205 15206 function renderPreview() 15207 { 15208 let html = ''; 15209 15210 files.forEach(function(entry, index) 15211 { 15212 let file = entry.file; 15213 let isImage = file.type.startsWith('image/'); 15214 let size = (file.size / 1024).toFixed(1) + ' KB'; 15215 15216 if(file.size > 1024 * 1024) 15217 { 15218 size = (file.size / (1024 * 1024)).toFixed(1) + ' MB'; 15219 } 15220 15221 let statusClass = ''; 15222 let statusText = ''; 15223 15224 if(entry.uploading) 15225 { 15226 statusClass = ' dh-fb-file-item-uploading'; 15227 statusText = '<span class="dh-fb-file-item-status">Uploading...</span>'; 15228 } 15229 else if(entry.error) 15230 { 15231 statusClass = ' dh-fb-file-item-error'; 15232 statusText = '<span class="dh-fb-file-item-status">' + entry.error + '</span>'; 15233 } 15234 15235 html += '<div class="dh-fb-file-item' + statusClass + '" data-index="' + index + '">'; 15236 15237 if(isImage) 15238 { 15239 html += '<div class="dh-fb-file-item-image"></div>'; 15240 } 15241 else 15242 { 15243 html += '<div class="dh-fb-file-item-icon">' + getFileExtension(file.name) + '</div>'; 15244 } 15245 15246 html += '<div class="dh-fb-file-item-info">'; 15247 html += '<span class="name">' + file.name + '</span>'; 15248 html += '<span class="size">' + size + '</span>'; 15249 html += statusText; 15250 html += '</div>'; 15251 html += '<button type="button" class="dh-fb-file-item-remove" data-index="' + index + '">×</button>'; 15252 15253 if(entry.uploading) 15254 { 15255 html += '<div class="dh-fb-file-item-progress" style="width: 70%"></div>'; 15256 } 15257 15258 html += '</div>'; 15259 }); 15260 15261 $preview.html(html); 15262
15263 files.forEach(function(entry, index) 15264 { 15265 if(entry.file.type.startsWith('image/')) 15266 { 15267 let reader = new FileReader(); 15268 let $img = $preview.find('[data-index="' + index + '"] .dh-fb-file-item-image'); 15269 15270 reader.onload = function(e) 15271 { 15272 $img.css('background-image', 'url(' + e.target.result + ')'); 15273 }; 15274 15275 reader.readAsDataURL(entry.file); 15276 } 15277 }); 15278 15279 if(files.length && !files.some(function(f) { return f.uploading; })) 15280 { 15281 $drop.hide(); 15282 } 15283 else if(!files.length || files.some(function(f) { return f.uploading; })) 15284 { 15285 $drop.show(); 15286 } 15287 15288 if(files.length && multiple) 15289 { 15290 $drop.show(); 15291 } 15292 } 15293 15294 $preview.on('click', '.dh-fb-file-item-remove', function() 15295 { 15296 let index = parseInt($(this).attr('data-index')); 15297 15298 files.splice(index, 1); 15299 renderPreview(); 15300 updateHidden(); 15301 15302 if(!files.length) 15303 { 15304 $drop.show(); 15305 } 15306 }); 15307}); 15308admin.forms.FunctionCreate('init', function(addon) 15309{ 15310 let $forms = $('form.dh-fb[dh-form]'); 15311 15312 if(!$forms.length) 15313 { 15314 return; 15315 } 15316 15317 $forms.each(function() 15318 { 15319 let $form = $(this); 15320 let config; 15321 15322 try 15323 { 15324 let $script = $form.children('script.dh-fb-config'); 15325 15326 if($script.length) 15327 { 15328 config = JSON.parse($script.html()); 15329 } 15330 else 15331 { 15332 config = JSON.parse($form.attr('dh-form-config') || '{}'); 15333 } 15334 } 15335 catch(e) 15336 { 15337 return; 15338 } 15339 15340 if(!config || !config.steps) 15341 { 15342 return; 15343 } 15344 15345 $form.data('dh-fb-config', config); 15346 15347 addon.Fn('conditions', $form, config); 15348 15349 if(config.steps.length > 1) 15350 { 15351 addon.Fn('steps', $form, config); 15352 } 15353 15354 $form.find('.dh-fb-file').each(function() 15355 { 15356 addon.Fn('fileupload', $(this)); 15357 }); 15358 15359 $form.find('.dh-fb-repeater').each(function() 15360 { 15361 addon.Fn('repeater', $(this), config); 15362 }); 15363 15364 $form.find('.dh-fb-select').each(function() 15365 { 15366 addon.Fn('select', $(this)); 15367 }); 15368 15369 $form.find('.dh-fb-multiselect').each(function() 15370 { 15371 addon.Fn('multiselect', $(this)); 15372 }); 15373 15374 $form.find('input[type="date"], input[type="time"]').on('click', function() 15375 { 15376 try 15377 { 15378 this.showPicker(); 15379 } 15380 catch(e) {} 15381 }); 15382 15383 $form.on('blur', '.dh-fb-input, .dh-fb-textarea', function() 15384 { 15385 let $field = $(this).closest('.dh-fb-field'); 15386 let fieldId = $field.attr('data-field'); 15387 15388 addon.Fn('validate.field', $form, config, fieldId); 15389 }); 15390 }); 15391}); 15392 15393$(document).ready(function() 15394{ 15395 setTimeout(function() 15396 { 15397 admin.forms.Fn('init'); 15398 }, 100); 15399}); 15400admin.forms.FunctionCreate('multiselect', function(addon, $wrapper) 15401{ 15402 let $trigger = $wrapper.find('.dh-fb-multiselect-trigger'); 15403 let $dropdown = $wrapper.find('.dh-fb-multiselect-dropdown'); 15404 let $search = $wrapper.find('.dh-fb-multiselect-search'); 15405 let $hidden = $wrapper.find('input[type="hidden"]'); 15406 let placeholder = $wrapper.attr('data-placeholder') || 'Select...'; 15407 let searchable = $wrapper.attr('data-searchable') === 'true'; 15408 let selected = []; 15409 15410 // Fallback for legacy HTML (tags-based trigger) 15411 if(!$trigger.length) 15412 { 15413 let $legacy = $wrapper.find('.dh-fb-multiselect-tags'); 15414 15415 if($legacy.length) 15416 { 15417 let $newTrigger = $('<div class="dh-fb-multiselect-trigger" tabindex="0" role="combobox" aria-expanded="false" aria-haspopup="listbox"></div>'); 15418 $newTrigger.append('<span class="dh-fb-multiselect-value placeholder">' + placeholder + '</span>'); 15419 $newTrigger.append('<span class="dh-fb-multiselect-arrow"><svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>'); 15420 $legacy.replaceWith($newTrigger); 15421 $trigger = $newTrigger; 15422 } 15423 } 15424 15425 let $valueSpan = $wrapper.find('.dh-fb-multiselect-value'); 15426 15427 $trigger.on('click', function(e) 15428 { 15429 e.stopPropagation(); 15430 15431 let isOpen = $dropdown.is(':visible'); 15432 15433 closeAllDropdowns(); 15434 15435 if(!isOpen) 15436 { 15437 $dropdown.show(); 15438 $trigger.attr('aria-expanded', 'true'); 15439 15440 if(searchable && $search.length) 15441 { 15442 $search.val('').focus(); 15443 $dropdown.find('.dh-fb-multiselect-option').show(); 15444 } 15445 } 15446 }); 15447 15448 $dropdown.on('click', '.dh-fb-multiselect-option', function(e) 15449 { 15450 e.stopPropagation(); 15451 15452 let value = $(this).attr('data-value'); 15453 let index = selected.indexOf(value); 15454 15455 if(index !== -1) 15456 { 15457 selected.splice(index, 1); 15458 $(this).removeClass('checked'); 15459 } 15460 else 15461 { 15462 selected.push(value); 15463 $(this).addClass('checked'); 15464 } 15465 15466 updateDisplay(); 15467 updateHidden(); 15468 }); 15469 15470 function updateDisplay() 15471 { 15472 if(selected.length === 0) 15473 { 15474 $valueSpan.text(placeholder).addClass('placeholder'); 15475 } 15476 else if(selected.length === 1) 15477 { 15478 let $option = $dropdown.find('[data-value="' + selected[0] + '"]'); 15479 let label = $option.contents().filter(function() { return this.nodeType === 3; }).text().trim() 15480 || $option.text().trim(); 15481 15482 $valueSpan.text(label).removeClass('placeholder'); 15483 } 15484 else 15485 { 15486 $valueSpan.text(selected.length + ' items selected').removeClass('placeholder'); 15487 } 15488 } 15489 15490 function updateHidden() 15491 { 15492 $hidden.val(selected.join(',')).trigger('change'); 15493 } 15494 15495 if(searchable && $search.length) 15496 { 15497 $search.on('input', function() 15498 { 15499 let query = $(this).val().toLowerCase(); 15500 15501 $dropdown.find('.dh-fb-multiselect-option').each(function() 15502 { 15503 let text = $(this).text().toLowerCase(); 15504 15505 if(text.indexOf(query) !== -1) 15506 { 15507 $(this).show(); 15508 } 15509 else 15510 { 15511 $(this).hide(); 15512 } 15513 }); 15514 }); 15515 15516 $search.on('click', function(e) 15517 { 15518 e.stopPropagation(); 15519 }); 15520 } 15521 15522 $trigger.on('keydown', function(e) 15523 { 15524 let isOpen = $dropdown.is(':visible'); 15525 15526 if(e.key === 'Enter' || e.key === ' ') 15527 { 15528 e.preventDefault(); 15529 15530 if(!isOpen) 15531 { 15532 $trigger.trigger('click'); 15533 } 15534 else 15535 { 15536 let $active = $dropdown.find('.dh-fb-multiselect-option-active'); 15537 15538 if($active.length) 15539 { 15540 $active.trigger('click'); 15541 } 15542 } 15543 } 15544 else if(e.key === 'Escape') 15545 { 15546 $dropdown.hide(); 15547 $trigger.attr('aria-expanded', 'false'); 15548 } 15549 else if(e.key === 'ArrowDown') 15550 { 15551 e.preventDefault(); 15552 15553 if(!isOpen) 15554 { 15555 $trigger.trigger('click'); 15556 return; 15557 } 15558 15559 let $options = $dropdown.find('.dh-fb-multiselect-option:visible'); 15560 let $current = $dropdown.find('.dh-fb-multiselect-option-active'); 15561 let index = $options.index($current); 15562 15563 $options.removeClass('dh-fb-multiselect-option-active'); 15564 15565 if(index < $options.length - 1) 15566 { 15567 $options.eq(index + 1).addClass('dh-fb-multiselect-option-active'); 15568 } 15569 else 15570 { 15571 $options.eq(0).addClass('dh-fb-multiselect-option-active'); 15572 } 15573 } 15574 else if(e.key === 'ArrowUp') 15575 { 15576 e.preventDefault(); 15577 15578 let $options = $dropdown.find('.dh-fb-multiselect-option:visible'); 15579 let $current = $dropdown.find('.dh-fb-multiselect-option-active'); 15580 let index = $options.index($current); 15581 15582 $options.removeClass('dh-fb-multiselect-option-active'); 15583 15584 if(index > 0) 15585 { 15586 $options.eq(index - 1).addClass('dh-fb-multiselect-option-active'); 15587 } 15588 else 15589 { 15590 $options.eq($options.length - 1).addClass('dh-fb-multiselect-option-active'); 15591 } 15592 } 15593 }); 15594 15595 function closeAllDropdowns() 15596 { 15597 $('.dh-fb-select-dropdown').hide(); 15598 $('.dh-fb-select-trigger').attr('aria-expanded', 'false'); 15599 $('.dh-fb-multiselect-dropdown').hide(); 15600 $('.dh-fb-multiselect-trigger').attr('aria-expanded', 'false'); 15601 } 15602 15603 $(document).on('click.dhfbmulti', function() 15604 { 15605 $dropdown.hide(); 15606 $trigger.attr('aria-expanded', 'false'); 15607 }); 15608}); 15609admin.forms.FunctionCreate('repeater', function(addon, $wrapper, config) 15610{ 15611 let min = parseInt($wrapper.attr('data-min')) || 1; 15612 let max = parseInt($wrapper.attr('data-max')) || 10; 15613 let name = $wrapper.attr('data-name') || 'repeater'; 15614 15615 let $template = $wrapper.find('.dh-fb-repeater-item').first(); 15616 let templateHtml = $template.length ? $template[0].outerHTML : ''; 15617 15618 function getCount() 15619 { 15620 return $wrapper.find('.dh-fb-repeater-item').length; 15621 } 15622 15623 function updateIndices() 15624 { 15625 $wrapper.find('.dh-fb-repeater-item').each(function(index) 15626 { 15627 $(this).attr('data-index', index); 15628 $(this).find('.dh-fb-repeater-item-header span').first().text(name.charAt(0).toUpperCase() + name.slice(1) + ' #' + (index + 1)); 15629 15630 $(this).find('[name]').each(function() 15631 { 15632 let fieldName = $(this).attr('name'); 15633 15634 fieldName = fieldName.replace(/\[\d+\]/, '[' + index + ']'); 15635 $(this).attr('name', fieldName); 15636 }); 15637 }); 15638 15639 updateButtons(); 15640 } 15641 15642 function updateButtons() 15643 { 15644 let count = getCount(); 15645 let $addBtn = $wrapper.find('.dh-fb-repeater-add'); 15646 let $removeBtns = $wrapper.find('.dh-fb-repeater-remove'); 15647 15648 if(count >= max) 15649 { 15650 $addBtn.hide(); 15651 } 15652 else 15653 { 15654 $addBtn.show(); 15655 } 15656 15657 if(count <= min) 15658 { 15659 $removeBtns.hide(); 15660 } 15661 else 15662 { 15663 $removeBtns.show(); 15664 } 15665 } 15666 15667 $wrapper.on('click', '.dh-fb-repeater-add', function(e) 15668 { 15669 e.preventDefault(); 15670 15671 if(getCount() >= max) 15672 { 15673 return; 15674 } 15675 15676 let newIndex = getCount(); 15677 let $new = $(templateHtml); 15678 15679 $new.attr('data-index', newIndex); 15680 $new.find('[name]').each(function() 15681 { 15682 let fieldName = $(this).attr('name'); 15683 15684 fieldName = fieldName.replace(/\[\d+\]/, '[' + newIndex + ']'); 15685 $(this).attr('name', fieldName); 15686 }); 15687 15688 $new.find('.dh-fb-input, .dh-fb-textarea').val(''); 15689 $new.find('input[type="radio"], input[type="checkbox"]').prop('checked', false); 15690 $new.find('input[type="hidden"]').val(''); 15691 15692 $(this).before($new); 15693 updateIndices(); 15694 }); 15695 15696 $wrapper.on('click', '.dh-fb-repeater-remove', function(e) 15697 { 15698 e.preventDefault(); 15699 15700 if(getCount() <= min) 15701 { 15702 return; 15703 } 15704 15705 $(this).closest('.dh-fb-repeater-item').remove(); 15706 updateIndices(); 15707 }); 15708 15709 updateButtons(); 15710}); 15711admin.forms.FunctionCreate('select', function(addon, $wrapper) 15712{ 15713 let $trigger = $wrapper.find('.dh-fb-select-trigger'); 15714 let $dropdown = $wrapper.find('.dh-fb-select-dropdown'); 15715 let $search = $wrapper.find('.dh-fb-select-search'); 15716 let $hidden = $wrapper.find('input[type="hidden"]'); 15717 let $valueSpan = $wrapper.find('.dh-fb-select-value'); 15718 let placeholder = $valueSpan.text(); 15719 let searchable = $wrapper.attr('data-searchable') === 'true'; 15720 15721 $trigger.data('placeholder', placeholder); 15722 15723 $trigger.on('click', function(e) 15724 { 15725 e.stopPropagation(); 15726 15727 let isOpen = $dropdown.is(':visible'); 15728 15729 closeAllDropdowns(); 15730 15731 if(!isOpen) 15732 { 15733 $dropdown.show(); 15734 $trigger.attr('aria-expanded', 'true'); 15735 15736 if(searchable && $search.length) 15737 { 15738 $search.val('').focus(); 15739 $dropdown.find('.dh-fb-select-option').show(); 15740 } 15741 } 15742 }); 15743 15744 $dropdown.on('click', '.dh-fb-select-option', function(e) 15745 { 15746 e.stopPropagation(); 15747 15748 let value = $(this).attr('data-value'); 15749 let label = $(this).text(); 15750 15751 $hidden.val(value).trigger('change'); 15752 $valueSpan.text(label).removeClass('placeholder'); 15753 15754 $dropdown.find('.dh-fb-select-option').removeClass('dh-fb-select-option-active'); 15755 $(this).addClass('dh-fb-select-option-active'); 15756 15757 $dropdown.hide(); 15758 $trigger.attr('aria-expanded', 'false'); 15759 }); 15760 15761 if(searchable && $search.length) 15762 { 15763 $search.on('input', function() 15764 { 15765 let query = $(this).val().toLowerCase(); 15766 15767 $dropdown.find('.dh-fb-select-option').each(function() 15768 { 15769 let text = $(this).text().toLowerCase(); 15770 15771 if(text.indexOf(query) !== -1) 15772 { 15773 $(this).show(); 15774 } 15775 else 15776 { 15777 $(this).hide(); 15778 } 15779 }); 15780 }); 15781 15782 $search.on('click', function(e) 15783 { 15784 e.stopPropagation(); 15785 }); 15786 } 15787 15788 $trigger.on('keydown', function(e) 15789 { 15790 let isOpen = $dropdown.is(':visible'); 15791 15792 if(e.key === 'Enter' || e.key === ' ') 15793 { 15794 e.preventDefault(); 15795 15796 if(!isOpen) 15797 { 15798 $trigger.trigger('click'); 15799 } 15800 else 15801 { 15802 let $active = $dropdown.find('.dh-fb-select-option-active'); 15803 15804 if(!$active.length) 15805 { 15806 $active = $dropdown.find('.dh-fb-select-option:visible').first(); 15807 } 15808 15809 if($active.length) 15810 { 15811 $active.trigger('click'); 15812 } 15813 } 15814 } 15815 else if(e.key === 'Escape') 15816 { 15817 $dropdown.hide(); 15818 $trigger.attr('aria-expanded', 'false'); 15819 } 15820 else if(e.key === 'ArrowDown') 15821 { 15822 e.preventDefault(); 15823 15824 if(!isOpen) 15825 { 15826 $trigger.trigger('click'); 15827 return; 15828 } 15829 15830 let $options = $dropdown.find('.dh-fb-select-option:visible'); 15831 let $current = $dropdown.find('.dh-fb-select-option-active'); 15832 let index = $options.index($current); 15833 15834 $options.removeClass('dh-fb-select-option-active'); 15835 15836 if(index < $options.length - 1) 15837 { 15838 $options.eq(index + 1).addClass('dh-fb-select-option-active'); 15839 } 15840 else 15841 { 15842 $options.eq(0).addClass('dh-fb-select-option-active'); 15843 } 15844 } 15845 else if(e.key === 'ArrowUp') 15846 { 15847 e.preventDefault(); 15848 15849 let $options = $dropdown.find('.dh-fb-select-option:visible'); 15850 let $current = $dropdown.find('.dh-fb-select-option-active'); 15851 let index = $options.index($current); 15852 15853 $options.removeClass('dh-fb-select-option-active'); 15854 15855 if(index > 0) 15856 { 15857 $options.eq(index - 1).addClass('dh-fb-select-option-active'); 15858 } 15859 else 15860 { 15861 $options.eq($options.length - 1).addClass('dh-fb-select-option-active'); 15862 } 15863 } 15864 }); 15865 15866 function closeAllDropdowns() 15867 { 15868 $('.dh-fb-select-dropdown').h
15868ide(); 15869 $('.dh-fb-select-trigger').attr('aria-expanded', 'false'); 15870 $('.dh-fb-multiselect-dropdown').hide(); 15871 $('.dh-fb-multiselect-trigger').attr('aria-expanded', 'false'); 15872 } 15873 15874 $(document).on('click.dhfbselect', function() 15875 { 15876 $dropdown.hide(); 15877 $trigger.attr('aria-expanded', 'false'); 15878 }); 15879}); 15880admin.forms.FunctionCreate('steps', function(addon, $form, config) 15881{ 15882 let steps = config.steps || []; 15883 let totalSteps = steps.length; 15884 let currentStep = 0; 15885 15886 $form.data('dh-fb-step', currentStep); 15887 15888 // Upgrade legacy progress HTML to circle-based structure 15889 let $progressSteps = $form.find('.dh-fb-progress-step'); 15890 15891 if($progressSteps.length && !$progressSteps.first().find('.dh-fb-progress-step-circle').length) 15892 { 15893 $progressSteps.each(function(i) 15894 { 15895 let $step = $(this); 15896 let text = $step.text().replace(/^\d+\.\s*/, '').trim(); 15897 let num = i + 1; 15898 15899 $step.empty(); 15900 $step.append('<div class="dh-fb-progress-step-circle"><span class="dh-fb-progress-step-num">' + num + '</span></div>'); 15901 $step.append('<div class="dh-fb-progress-step-label">' + text + '</div>'); 15902 }); 15903 15904 // Remove old progress bar if present 15905 $form.find('.dh-fb-progress-bar').remove(); 15906 } 15907 15908 function showStep(index) 15909 { 15910 currentStep = index; 15911 $form.data('dh-fb-step', currentStep); 15912 15913 $form.find('.dh-fb-step').each(function(i) 15914 { 15915 if(i === index) 15916 { 15917 $(this).addClass('active').show(); 15918 } 15919 else 15920 { 15921 $(this).removeClass('active').hide(); 15922 } 15923 }); 15924 15925 let $prev = $form.find('.dh-fb-prev'); 15926 let $next = $form.find('.dh-fb-next'); 15927 let $submit = $form.find('.dh-fb-submit'); 15928 15929 if(index === 0) 15930 { 15931 $prev.hide(); 15932 } 15933 else 15934 { 15935 $prev.show(); 15936 } 15937 15938 if(index === totalSteps - 1) 15939 { 15940 $next.hide(); 15941 $submit.show(); 15942 } 15943 else 15944 { 15945 $next.show(); 15946 $submit.hide(); 15947 } 15948 15949 $form.find('.dh-fb-progress-step').each(function(i) 15950 { 15951 $(this).removeClass('active completed'); 15952 15953 if(i < index) 15954 { 15955 $(this).addClass('completed'); 15956 } 15957 else if(i === index) 15958 { 15959 $(this).addClass('active'); 15960 } 15961 }); 15962 } 15963 15964 $form.on('click', '.dh-fb-next', function(e) 15965 { 15966 e.preventDefault(); 15967 15968 let result = addon.Fn('validate', $form, config, currentStep); 15969 15970 if(!result.valid) 15971 { 15972 return; 15973 } 15974 15975 if(currentStep < totalSteps - 1) 15976 { 15977 showStep(currentStep + 1); 15978 15979 $form[0].scrollIntoView({behavior: 'smooth', block: 'start'}); 15980 } 15981 }); 15982 15983 $form.on('click', '.dh-fb-prev', function(e) 15984 { 15985 e.preventDefault(); 15986 15987 if(currentStep > 0) 15988 { 15989 showStep(currentStep - 1); 15990 15991 $form[0].scrollIntoView({behavior: 'smooth', block: 'start'}); 15992 } 15993 }); 15994 15995 showStep(0); 15996}); 15997admin.forms.FunctionCreate('submit', function(addon, id, data, url = null) 15998{ 15999 let success = null; 16000 let fail = null; 16001 16002 data.append('id', id); 16003 data.append('url', url); 16004 16005 mdAjax.Fn('post').form(data).url('/api/admin/forms.submission').run(null, (response) => 16006 { 16007 if(success) { success(response); } 16008 }, 16009 (response) => 16010 { 16011 if(fail) { fail(response); } 16012 }); 16013 16014 let self = { 16015 success: function(then) 16016 { 16017 if(typeof then === 'function') { success = then; } 16018 return self; 16019 }, 16020 fail: function(then) 16021 { 16022 if(typeof then === 'function') { fail = then; } 16023 return self; 16024 } 16025 }; 16026 16027 return self; 16028});admin.forms.FunctionCreate('validate', function(addon, $form, config, stepIndex) 16029{ 16030 let errors = {}; 16031 let repeaterErrors = []; 16032 let steps = config.steps || []; 16033 let fieldsToValidate = []; 16034 16035 if(stepIndex !== undefined && stepIndex !== null) 16036 { 16037 let step = steps[stepIndex]; 16038 16039 if(step) 16040 { 16041 fieldsToValidate = step.fields || []; 16042 } 16043 } 16044 else 16045 { 16046 steps.forEach(function(step) 16047 {
16048 (step.fields || []).forEach(function(field) 16049 { 16050 fieldsToValidate.push(field); 16051 }); 16052 }); 16053 } 16054 16055 let layoutTypes = ['heading', 'paragraph', 'divider']; 16056 16057 fieldsToValidate.forEach(function(field) 16058 { 16059 if(layoutTypes.includes(field.type)) 16060 { 16061 return; 16062 } 16063 16064 let $field = $form.find('[data-field="' + field.id + '"]'); 16065 16066 if(!$field.length || $field.hasClass('dh-fb-hidden')) 16067 { 16068 return; 16069 } 16070 16071 if(field.type === 'repeater') 16072 { 16073 let $wrapper = $field.find('.dh-fb-repeater'); 16074 let $items = $wrapper.find('.dh-fb-repeater-item'); 16075 16076 if(field.required && $items.length === 0) 16077 { 16078 errors[field.id] = field.requiredMessage || 'At least one item is required'; 16079 return; 16080 } 16081 16082 if($items.length === 0) 16083 { 16084 return; 16085 } 16086 16087 let childFields = field.fields || []; 16088 16089 $items.each(function() 16090 { 16091 let $item = $(this); 16092 let $children = $item.find('.dh-fb-repeater-item-grid > .dh-fb-field'); 16093 16094 childFields.forEach(function(childConfig, childIndex) 16095 { 16096 if(!childConfig.required) return; 16097 if(layoutTypes.includes(childConfig.type)) return; 16098 16099 let $child = $children.eq(childIndex); 16100 if(!$child.length) return; 16101 16102 let value = ''; 16103 16104 switch(childConfig.type) 16105 { 16106 case 'select': 16107 case 'multiselect': 16108 value = $child.find('input[type="hidden"]').val() || ''; 16109 break; 16110 16111 case 'checkbox': 16112 let checked = []; 16113 $child.find('input[type="checkbox"]:checked').each(function() 16114 { 16115 checked.push($(this).val()); 16116 }); 16117 value = checked.join(','); 16118 break; 16119 16120 case 'radio': 16121 value = $child.find('input[type="radio"]:checked').val() || ''; 16122 break; 16123 16124 case 'toggle': 16125 value = $child.find('input[type="checkbox"]').is(':checked') ? '1' : ''; 16126 break; 16127 16128 case 'file': 16129 value = $child.find('input[type="hidden"]').val() || ''; 16130 break; 16131 16132 default: 16133 value = $child.find('.dh-fb-input, .dh-fb-textarea').val() || ''; 16134 } 16135 16136 if(!value || !value.toString().trim().length) 16137 { 16138 repeaterErrors.push({ 16139 $field: $child, 16140 message: childConfig.requiredMessage || 'This field is required' 16141 }); 16142 } 16143 }); 16144 }); 16145 16146 return; 16147 } 16148 16149 let value = addon.Fn('validate.getValue', $form, field); 16150 let error = addon.Fn('validate.check', field, value); 16151 16152 if(error) 16153 { 16154 errors[field.id] = error; 16155 } 16156 }); 16157 16158 $form.find('.dh-fb-field').removeClass('dh-fb-field-error'); 16159 $form.find('.dh-fb-error').text('').hide(); 16160 16161 Object.keys(errors).forEach(function(fieldId) 16162 { 16163 let $field = $form.find('[data-field="' + fieldId + '"]'); 16164 16165 $field.addClass('dh-fb-field-error'); 16166 $field.children('.dh-fb-error').text(errors[fieldId]).show(); 16167 16168 let $input = $field.find('.dh-fb-input, .dh-fb-textarea, .dh-fb-select-trigger, .dh-fb-multiselect-trigger'); 16169 16170 $input.attr('aria-invalid', 'true'); 16171 }); 16172 16173 repeaterErrors.forEach(function(err) 16174 { 16175 err.$field.addClass('dh-fb-field-error'); 16176 err.$field.children('.dh-fb-error').text(err.message).show(); 16177 }); 16178 16179 let totalErrors = Object.keys(errors).length + repeaterErrors.length; 16180 16181 if(totalErrors) 16182 { 16183 let $first; 16184 16185 if(Object.keys(errors).length) 16186 { 16187 $first = $form.find('[data-field="' + Object.keys(errors)[0] + '"]'); 16188 } 16189 else 16190 { 16191 $first = repeaterErrors[0].$field; 16192 } 16193 16194 if($first && $first.length) 16195 { 16196 $first[0].scrollIntoView({behavior: 'smooth', block: 'center'}); 16197 } 16198 } 16199 16200 return {valid: !totalErrors, errors}; 16201}); 16202 16203admin.forms.FunctionCreate('validate.field', function(addon, $form, config, fieldId) 16204{ 16205 let field = null; 16206
16207 (config.steps || []).forEach(function(step) 16208 { 16209 let found = (step.fields || []).find(f => f.id === fieldId); 16210 16211 if(found) 16212 { 16213 field = found; 16214 } 16215 }); 16216 16217 if(!field) 16218 { 16219 return; 16220 } 16221 16222 let $field = $form.find('[data-field="' + fieldId + '"]'); 16223 16224 if($field.hasClass('dh-fb-hidden')) 16225 { 16226 return; 16227 } 16228 16229 let value = addon.Fn('validate.getValue', $form, field); 16230 let error = addon.Fn('validate.check', field, value); 16231 16232 $field.removeClass('dh-fb-field-error'); 16233 $field.children('.dh-fb-error').text('').hide(); 16234 $field.find('.dh-fb-input, .dh-fb-textarea').removeAttr('aria-invalid'); 16235 16236 if(error) 16237 { 16238 $field.addClass('dh-fb-field-error'); 16239 $field.children('.dh-fb-error').text(error).show(); 16240 $field.find('.dh-fb-input, .dh-fb-textarea').attr('aria-invalid', 'true'); 16241 } 16242}); 16243 16244admin.forms.FunctionCreate('validate.getValue', function(addon, $form, field) 16245{ 16246 let $field = $form.find('[data-field="' + field.id + '"]'); 16247 16248 if(!$field.length) 16249 { 16250 return ''; 16251 } 16252 16253 switch(field.type) 16254 { 16255 case 'select': 16256 return $field.find('input[type="hidden"]').val() || ''; 16257 16258 case 'multiselect': 16259 return $field.find('input[type="hidden"]').val() || ''; 16260 16261 case 'checkbox': 16262 let checked = []; 16263 $field.find('input[type="checkbox"]:checked').each(function() 16264 { 16265 checked.push($(this).val()); 16266 }); 16267 return checked.join(','); 16268 16269 case 'radio': 16270 return $field.find('input[type="radio"]:checked').val() || ''; 16271 16272 case 'toggle': 16273 return $field.find('input[type="checkbox"]').is(':checked') ? '1' : ''; 16274 16275 case 'file': 16276 return $field.find('.dh-fb-file input[type="hidden"]').val() || ''; 16277 16278 default: 16279 return $field.find('.dh-fb-input, .dh-fb-textarea').val() || ''; 16280 } 16281}); 16282 16283admin.forms.FunctionCreate('validate.check', function(addon, field, value) 16284{ 16285 if(field.required && (!value || !value.toString().trim().length)) 16286 { 16287 return field.requiredMessage || 'This field is required'; 16288 } 16289 16290 if(!value || !value.toString().trim().length) 16291 { 16292 return null; 16293 } 16294 16295 let validation = field.validation || {}; 16296 16297 switch(field.type) 16298 { 16299 case 'email': 16300 if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) 16301 { 16302 return validation.message || 'Please enter a valid email address'; 16303 } 16304 break; 16305 16306 case 'url': 16307 if(!/^https?:\/\/.+/.test(value)) 16308 { 16309 return validation.message || 'Please enter a valid URL'; 16310 } 16311 break; 16312 16313 case 'number': 16314 let num = parseFloat(value); 16315 16316 if(isNaN(num)) 16317 { 16318 return validation.message || 'Please enter a valid number'; 16319 } 16320 16321 if(field.min !== null && field.min !== undefined && num < field.min) 16322 { 16323 return validation.message || 'Value must be at least ' + field.min; 16324 } 16325 16326 if(field.max !== null && field.max !== undefined && num > field.max) 16327 { 16328 return validation.message || 'Value must be at most ' + field.max; 16329 } 16330 break; 16331 } 16332 16333 if(validation.minLength && value.length < validation.minLength) 16334 { 16335 return validation.message || 'Minimum ' + validation.minLength + ' characters required'; 16336 } 16337 16338 if(validation.maxLength && value.length > validation.maxLength) 16339 { 16340 return validation.message || 'Maximum ' + validation.maxLength + ' characters allowed'; 16341 } 16342 16343 if(validation.pattern) 16344 { 16345 try 16346 { 16347 let regex = new RegExp(validation.pattern); 16348 16349 if(!regex.test(value)) 16350 { 16351 return validation.message || 'Please enter a valid value'; 16352 } 16353 } 16354 catch(e) {} 16355 } 16356 16357 return null; 16358}); 16359mdEvents.ItemsAdd({ 16360 id: 'forms.submit', 16361 group: 'Forms', 16362}, { 16363 id: 'forms.success', 16364 group: 'Forms', 16365}, { 16366 id: 'forms.fail', 16367 group: 'Forms', 16368}); 16369builder.OnReady(function() 16370{ 16371 blocks.Ready(); 16372});blocks.OnReady(function(blocks) 16373{ 16374 blocks.ItemsAdd({ 16375 id: 'button', 16376 name: 'button', 16377 label: 'Button', 16378 category: 'action', 16379 parent: 'form', 16380 nest: ['span', 'i', 'img', 'p'], 16381 close: true, 16382 text: true, 16383 order: 10, 16384 empty: false, 16385 json: function() 16386 { 16387 return {name: 'button', label: 'Button', text: {1: 'Button Text'}, attributes: {1: {type: 'submit'}} }; 16388 } 16389 }, 16390 { 16391 id: 'a', 16392 name: 'a', 16393 label: 'Link Button', 16394 category: 'action', 16395 parent: false, 16396 nest: ['*'], 16397 close: true, 16398 text: true, 16399 order: 4, 16400 empty: false, 16401 json: function() 16402 { 16403 return {"tags":[],"symbol":null,"order":600,"loop":1,"name":"a","label":"Link Button","attributes":{"1":{"link-type":"page","href":"#"}},"classes":{},"conditions":{},"properties":{},"text":{"1":"Button Text"},"css":{},"Target":null}; 16404 } 16405 }, 16406 { 16407 id: 'object', 16408 name: 'object', 16409 label: 'Object', 16410 category: 'action', 16411 parent: false, 16412 nest: ['*'], 16413 hidden:true, 16414 close: true, 16415 text: false, 16416 order: 1, 16417 empty: true, 16418 json: function() 16419 { 16420 return {name: 'object', label: 'Object'}; 16421 } 16422 }); 16423});blocks.OnReady(function(blocks) 16424{ 16425 blocks.ItemsAdd( 16426 { 16427 id: 'hgroup', 16428 name: 'hgroup', 16429 label: 'Heading Group', 16430 category: 'advanced', 16431 parent: false, 16432 hidden: true, 16433 nest: ['*'], 16434 close: true, 16435 text: false, 16436 order: 10, 16437 empty: false, 16438 json: function() { 16439 return {name: 'hgroup', label: 'Heading Group'}; 16440 } 16441 }, 16442 { 16443 id: 'article', 16444 name: 'article', 16445 label: 'Article', 16446 category: 'advanced', 16447 parent: false, 16448 hidden: true, 16449 nest: ['*'], 16450 close: true, 16451 text: true, 16452 order: 10, 16453 empty: false, 16454 json: function() { 16455 return {name: 'article', label: 'Article'}; 16456 } 16457 }, 16458 { 16459 id: 'address', 16460 name: 'address', 16461 label: 'Address', 16462 category: 'advanced', 16463 parent: false, 16464 hidden: true, 16465 nest: ['span', 'p'], 16466 close: true, 16467 text: true, 16468 order: 10, 16469 empty: false, 16470 json: function() { 16471 return {name: 'address', label: 'Address'}; 16472 } 16473 }, 16474 { 16475 id: 'aside', 16476 name: 'aside', 16477 label: 'Aside', 16478 category: 'advanced', 16479 parent: false, 16480 hidden: true, 16481 nest: ['*'], 16482 close: true, 16483 text: false, 16484 order: 10, 16485 empty: false, 16486 json: function() { 16487 return {name: 'aside', label: 'Aside'}; 16488 } 16489 }, {
16490 id: 'blockquote', 16491 name: 'blockquote', 16492 label: 'Blockquote', 16493 category: 'advanced', 16494 parent: false, 16495 hidden: true, 16496 nest: ['*'], 16497 close: true, 16498 text: true, 16499 order: 10, 16500 empty: false, 16501 json: function() { 16502 return {name: 'blockquote', label: 'Blockquote'}; 16503 } 16504 }, 16505 { 16506 id: 'caption', 16507 name: 'caption', 16508 label: 'Caption', 16509 category: 'advanced', 16510 parent: false, 16511 hidden: true, 16512 nest: ['span', 'p'], 16513 close: true, 16514 text: true, 16515 order: 10, 16516 empty: false, 16517 json: function() { 16518 return {name: 'caption', label: 'Caption'}; 16519 } 16520 }, 16521 { 16522 id: 'pre', 16523 name: 'pre', 16524 label: 'Preformatted Text', 16525 category: 'advanced', 16526 parent: false, 16527 hidden: true, 16528 nest: ['*'], 16529 close: true, 16530 text: true, 16531 order: 10, 16532 empty: false, 16533 json: function() { 16534 return {name: 'pre', label: 'Preformatted Text'}; 16535 } 16536 }, 16537 { 16538 id: 'code', 16539 name: 'code', 16540 label: 'Code', 16541 category: 'advanced', 16542 parent: false, 16543 hidden: true, 16544 nest: ['*'], 16545 close: true, 16546 text: true, 16547 order: 10, 16548 empty: false, 16549 json: function() { 16550 return {name: 'code', label: 'Code'}; 16551 } 16552 }, 16553 { 16554 id: 'figcaption', 16555 name: 'figcaption', 16556 label: 'Figure Caption', 16557 category: 'advanced', 16558 parent: false, 16559 hidden: true, 16560 nest: false, 16561 close: true, 16562 text: true, 16563 order: 10, 16564 empty: false, 16565 json: function() { 16566 return {name: 'figcaption', label: 'Figure Caption'}; 16567 } 16568 }, 16569 { 16570 id: 'figure', 16571 name: 'figure', 16572 label: 'Figure', 16573 category: 'advanced', 16574 parent: false, 16575 hidden: true, 16576 nest: ['*'], 16577 close: true, 16578 text: false, 16579 order: 10, 16580 empty: false, 16581 json: function() { 16582 return {"tags":[{"tags":[],"symbol":null,"order":100,"loop":1,"name":"img","label":"Image","css":{},"text":{},"attributes":{"1":{"src":"https://static.divhunt.com/assets/media/Image-Sample.svg"}},"classes":{},"conditions":{},"properties":{},"Target":null},{"tags":[],"symbol":null,"order":200,"loop":1,"name":"figcaption","label":"Figure Caption","attributes":{},"classes":{},"conditions":{},"properties":{},"text":{},"css":{},"Target":null}],"symbol":null,"order":100,"loop":1,"name":"figure","label":"Figure","attributes":{},"classes":{},"conditions":{},"properties":{},"text":{},"css":{},"Target":null}; 16583 } 16584 }, 16585 16586 16587 { 16588 id: 'kbd', 16589 name: 'kbd', 16590 label: 'Keyboard Input', 16591 category: 'advanced', 16592 parent: false, 16593 hidden: true, 16594 nest: [], 16595 close: true, 16596 text: true, 16597 order: 10, 16598 empty: false, 16599 json: function() { 16600 return {"tags":[],"symbol":null,"order":1600,"loop":1,"name":"kbd","label":"Keyboard Input","css":{"1920":{"self":{"color":"rgb(0,0,0)","border":"1px solid #b4b4b4","background":"rgb(255,255,255)","box-shadow":"","line-height":"1","padding-top":"2px","white-space":"nowrap","padding-left":"4px","border-radius":"3px","padding-right":"4px","padding-bottom":"2px","display":"inline-block"}}},"text":{"1":"CTRL"},"attributes":{},"classes":{},"conditions":{},"properties":{},"Target":null}; 16601 } 16602 }, 16603 { 16604 id: 'header', 16605 name: 'header', 16606 label: 'Header', 16607 category: 'advanced', 16608 parent: false, 16609 hidden: true, 16610 nest: ['*'], 16611 close: true, 16612 text: false, 16613 order: 10, 16614 empty: false, 16615 json: function() { 16616 return {name: 'header', label: 'Header'}; 16617 } 16618 }, 16619 { 16620 id: 'footer', 16621 name: 'footer', 16622 label: 'Footer', 16623 category: 'advanced', 16624 parent: false, 16625 hidden: true, 16626 nest: ['*'], 16627 close: true, 16628 text: false, 16629 order: 10, 16630 empty: false, 16631 json: function() { 16632 return {name: 'footer', label: 'Footer'}; 16633 } 16634 }, 16635 { 16636 id: 'nav', 16637 name: 'nav', 16638 label: 'Navigation', 16639 category: 'advanced', 16640 parent: false, 16641 hidden: true, 16642 nest: ['*'], 16643 close: true, 16644 text: false, 16645 order: 10, 16646 empty: false, 16647 json: function() { 16648 return {name: 'nav', label: 'Navigation'}; 16649 } 16650 }, 16651 { 16652 id: 'summary', 16653 name: 'summary', 16654 label: 'Summary', 16655 category: 'advanced', 16656 parent: false, 16657 hidden: true, 16658 nest: ['*'], 16659 close: true, 16660 text: true, 16661 order: 10, 16662 empty: false, 16663 json: function() { 16664 return {name: 'summary', label: 'Summary'}; 16665 } 16666 }, 16667 { 16668 id: 'time', 16669 name: 'time', 16670 label: 'Time', 16671 category: 'advanced', 16672 parent: false, 16673 hidden: true, 16674 nest: ['p', 'span'], 16675 close: true, 16676 text: true, 16677 order: 10, 16678 empty: false, 16679 json: function() { 16680 return {name: 'time', label: 'Time'}; 16681 } 16682 }, 16683 { 16684 id: 'cite', 16685 name: 'cite', 16686 label: 'Citation', 16687 category: 'advanced', 16688 parent: false, 16689 hidden: true, 16690 nest: ['*'], 16691 close: true, 16692 text: true, 16693 order: 10, 16694 empty: false, 16695 json: function() { 16696 return {name: 'cite', label: 'Citation'}; 16697 } 16698 }); 16699});blocks.OnReady(function(blocks) 16700{ 16701 blocks.ItemsAdd({ 16702 id: 'json', 16703 icon: 'ph-brackets-curly-light', 16704 name: 'json', 16705 label: 'JSON', 16706 category: 'embed', 16707 parent: 'embed', 16708 nest: [], 16709 close: true, 16710 text: false, 16711 order: 20, 16712 empty: true, 16713 json: function() 16714 { 16715 return null; 16716 } 16717 }); 16718});blocks.OnReady(function(blocks) 16719{ 16720 16721 16722 blocks.ItemsAdd({ 16723 id: 'embed', 16724 name: 'embed', 16725 label: 'Embed', 16726 category: 'embed', 16727 parent: false, 16728 nest: [], 16729 close: true, 16730 text: true, 16731 order: 15, 16732 empty: true, 16733 json: function() 16734 { 16735 return {name: 'embed', label: 'Embed'}; 16736 } 16737 }); 16738 16739 16740 blocks.ItemsAdd({
16741 id: 'rich-text', 16742 name: 'rich-text', 16743 label: 'Rich Text', 16744 category: 'embed', 16745 parent: false, 16746 nest: [], 16747 close: true, 16748 text: true, 16749 order: 16, 16750 empty: true, 16751 json: function() 16752 { 16753 return {name: 'rich-text', label: 'Rich Text'}; 16754 } 16755 }); 16756 16757}); 16758 16759blocks.OnReady(function(blocks) 16760{ 16761 blocks.ItemsAdd({ 16762 id: 'form', 16763 name: 'form', 16764 label: 'Form', 16765 category: 'form', 16766 parent: false, 16767 nest: ['*'], 16768 close: true, 16769 text: true, 16770 order: 10, 16771 empty: true, 16772 events: ['dh.submit'], 16773 json: function() 16774 { 16775 return {name: 'form', label: 'Form'}; 16776 } 16777 }, 16778 { 16779 id: 'input', 16780 name: 'input', 16781 label: 'Input', 16782 category: 'form', 16783 parent: 'form', 16784 nest: [], 16785 close: false, 16786 text: false, 16787 order: 2, 16788 empty: false, 16789 events: ['dh.input', 'dh.change'], 16790 json: function() 16791 { 16792 return {name: 'input', label: 'Input'}; 16793 } 16794 }, 16795 { 16796 id: 'textarea', 16797 name: 'textarea', 16798 label: 'Textarea', 16799 category: 'form', 16800 parent: 'form', 16801 nest: [], 16802 close: true, 16803 text: true, 16804 order: 3, 16805 empty: false, 16806 events: ['dh.input', 'dh.change'], 16807 json: function() 16808 { 16809 return {name: 'textarea', label: 'Textarea'}; 16810 } 16811 }, 16812 { 16813 id: 'select', 16814 name: 'select', 16815 label: 'Select', 16816 category: 'form', 16817 parent: 'form', 16818 nest: ['option'], 16819 close: true, 16820 text: false, 16821 order: 4, 16822 empty: false, 16823 events: ['dh.change'], 16824 json: function() 16825 { 16826 return {"tags":[{"tags":[],"symbol":null,"order":200,"loop":3,"name":"option","label":"Option","css":{},"text":{"1":"Option 1","2":"Option 2","3":"Option 3"},"attributes":{},"classes":{},"conditions":{},"Target":null},{"tags":[],"symbol":null,"order":100,"loop":1,"name":"option","label":"Placeholder","css":{},"text":{"1":"Select value","2":"Option 2","3":"Option 3"},"attributes":{"1":{"disabled":"disabled","selected":"selected"}},"classes":{},"conditions":{},"Target":null}],"symbol":null,"order":200,"loop":1,"name":"select","label":"Select","css":{"1920":{"self":{"flex":"1 1 0%","color":"rgba(0,0,0,1.00)","border":"1px solid rgba(221,221,221,1.00)","cursor":"pointer","height":"44px","display":"block","background":"rgba(255,255,255,1.00)","min-height":"44px","padding-left":"10px","padding-right":"10px"},":focus":{"border":"1px solid rgba(0,0,0,1.00)","outline":"none"}}},"text":{},"attributes":{"100":{"placeholder":"Name"},"200":{"placeholder":"Email"},"300":{"placeholder":"Subject"}},"classes":{},"conditions":{},"Target":null}; 16827 } 16828 }, 16829 { 16830 id: 'option', 16831 name: 'option', 16832 label: 'Select Option', 16833 category: 'form', 16834 parent: 'form', 16835 nest: [], 16836 close: true, 16837 text: true, 16838 order: 5, 16839 empty: false, 16840 json: function() 16841 { 16842 return {name: 'option', label: 'Option'}; 16843 } 16844 }, 16845 { 16846 id: 'label', 16847 name: 'label', 16848 label: 'Label', 16849 category: 'form', 16850 parent: 'form', 16851 nest: ['*'], 16852 close: true, 16853 text: true, 16854 order: 1, 16855 empty: false, 16856 json: function() 16857 { 16858 return {name: 'label', label: 'Label', text: {1: 'Label Text'}}; 16859 } 16860 }, 16861 { 16862 id: 'element', 16863 name: 'Element', 16864 label: 'Element', 16865 category: 'form', 16866 parent: 'form', 16867 nest: [], 16868 close: true, 16869 text: false, 16870 order: 20, 16871 empty: false, 16872 hidden: true, 16873 json: function() 16874 { 16875 return {name: 'element', label: 'Element'}; 16876 }, 16877 onRender: (event) => 16878 { 16879 let configuration; 16880 16881 try 16882 { 16883 configuration = JSON.parse(event.tag.Fn('text.get')); 16884 16885 if(configuration && typeof configuration === 'object' && 'type' in configuration) 16886 { 16887 let element = elements.ItemGet(configuration.type); 16888 16889 if(element) 16890 { 16891 return event.html.replace('</element>', '') + element.Get('html')({key: null, values: {}, config: configuration.config, onChange: () => {}}) + '</element>'; 16892 } 16893 } 16894 } 16895 catch(error) 16896 { 16897 return event.html.replace('</element>', '') + 'Please configure element from settigns tab.' + '</element>'; 16898 } 16899 } 16900 }, 16901 { 16902 id: 'form-widget', 16903 name: 'form-widget', 16904 label: 'Form Widget', 16905 category: 'form', 16906 parent: 'form', 16907 nest: [], 16908 close: true, 16909 text: true, 16910 order: 30, 16911 empty: true, 16912 json: function() 16913 { 16914 return {name: 'form-widget', label: 'Form Widget'}; 16915 } 16916 }, 16917 { 16918 id: 'membership-form-widget', 16919 name: 'membership-form-widget', 16920 label: 'Membership Form', 16921 category: 'form', 16922 parent: 'form', 16923 nest: [], 16924 close: true, 16925 text: true, 16926 order: 31, 16927 empty: true, 16928 json: function() 16929 { 16930 return {name: 'membership-form-widget', label: 'Membership Form'}; 16931 } 16932 }); 16933});blocks.OnReady(function(blocks) 16934{ 16935 blocks.ItemsAdd({ 16936 id: 'h1', 16937 name: 'h1', 16938 label: 'Heading 1', 16939 category: 'heading', 16940 parent: false, 16941 nest: ['span', 'b', 'mark', 'i', 'img'], 16942 close: true, 16943 text: true, 16944 order: 2, 16945 empty: false, 16946 json: function() 16947 { 16948 return {name: 'h1', label: 'Heading 1', text: {1: 'Heading 1'}}; 16949 } 16950 }, 16951 { 16952 id: 'h2', 16953 name: 'h2', 16954 label: 'Heading 2', 16955 category: 'heading', 16956 parent: 'h1', 16957 nest: ['span', 'b', 'mark', 'i', 'img'], 16958 close: true, 16959 text: true, 16960 order: 1, 16961 empty: false, 16962 json: function() 16963 { 16964 return {name: 'h2', label: 'Heading 2', text: {1: 'Heading 2'}}; 16965 } 16966 }, 16967 { 16968 id: 'h3', 16969 name: 'h3', 16970 label: 'Heading 3', 16971 category: 'heading', 16972 parent: 'h1', 16973 nest: ['span', 'b', 'mark', 'i', 'img'], 16974 close: true, 16975 text: true, 16976 order: 2, 16977 empty: false, 16978 json: function() 16979 { 16980 return {name: 'h3', label: 'Heading 3', text: {1: 'Heading 3'}}; 16981 } 16982 }, 16983 { 16984 id: 'h4', 16985 name: 'h4', 16986 label: 'Heading 4', 16987 category: 'heading', 16988 parent: 'h1', 16989 nest: ['span', 'b', 'mark', 'i', 'img'], 16990 close: true, 16991 text: true, 16992 order: 3, 16993 empty: false, 16994 json: function() 16995 { 16996 return {name: 'h4', label: 'Heading 4', text: {1: 'Heading 4'}}; 16997 } 16998 }, 16999 { 17000 id: 'h5', 17001 name: 'h5', 17002 label: 'Heading 5', 17003 category: 'heading', 17004 parent: 'h1', 17005 nest: ['span', 'b', 'mark', 'i', 'img'], 17006 close: true, 17007 text: true, 17008 order: 4, 17009 empty: false, 17010 json: function() 17011 { 17012 return {name: 'h5', label: 'Heading 5', text: {1: 'Heading 5'}}; 17013 } 17014 }, 17015 { 17016 id: 'h6', 17017 name: 'h6', 17018 label: 'Heading 6', 17019 category: 'heading', 17020 parent: 'h1', 17021 nest: ['span', 'b', 'mark', 'i', 'img'], 17022 close: true, 17023 text: true, 17024 order: 5, 17025 empty: false, 17026 json: function() 17027 { 17028 return {name: 'h6', label: 'Heading 6', text: {1: 'Heading 6'}}; 17029 } 17030 }); 17031});blocks.OnReady(function(blocks) 17032{ 17033 blocks.ItemsAdd({ 17034 id: 'htmltojson', 17035 name: 'htmltojson', 17036 label: 'HTML to JSON', 17037 category: 'embed', 17038 parent: 'embed', 17039 nest: ['*'], 17040 close: true, 17041 text: false, 17042 order: 18, 17043 empty: true, 17044 icon: 'tag-htmltojson', 17045 json: function() 17046 { 17047 return null; 17048 } 17049 }); 17050}); 17051blocks.OnReady(function(blocks) 17052{ 17053 blocks.ItemsAdd({ 17054 id: 'ul', 17055 name: 'ul', 17056 label: 'Unordered List', 17057 category: 'list', 17058 parent: false, 17059 nest: ['li'], 17060 close: true, 17061 text: false, 17062 order: 8, 17063 empty: true, 17064 json: function() 17065 { 17066 return {name: 'ul', label: 'Unordered List'}; 17067 } 17068 }, 17069 { 17070 id: 'ol', 17071 name: 'ol', 17072 label: 'Ordered List', 17073 category: 'list', 17074 parent: 'ul', 17075 nest: ['li'], 17076 close: true, 17077 text: false, 17078 order: 1, 17079 empty: true, 17080 json: function() 17081 { 17082 return {name: 'ol', label: 'Ordered List'}; 17083 } 17084 }, 17085 { 17086 id: 'li', 17087 name: 'li', 17088 label: 'List Item', 17089 category: 'list', 17090 parent: 'ul', 17091 nest: ['*'], 17092 close: true, 17093 text: true, 17094 order: 2, 17095 empty: false, 17096 json: function() 17097 { 17098 return {name: 'li', label: 'List Item', text: {1: 'List Item'}}; 17099 } 17100 }); 17101});blocks.OnReady(function(blocks) 17102{ 17103 blocks.ItemsAdd({ 17104 id: 'div', 17105 name: 'div', 17106 label: 'Div', 17107 category: 'main', 17108 parent: false, 17109 nest: ['*'], 17110 close: true, 17111 text: false, 17112 order: 1, 17113 empty: true, 17114 json: function() 17115 { 17116 return {name: 'div', label: 'Div'}; 17117 } 17118 }, 17119 { 17120 id: 'section', 17121 name: 'section', 17122 label: 'Section', 17123 category: 'main', 17124 parent: 'div', 17125 nest: ['*'], 17126 close: true, 17127 text: false, 17128 order: 2, 17129 empty: true, 17130 json: function() 17131 { 17132 return {name: 'section', label: 'Section'}; 17133 } 17134 }, 17135 { 17136 id: 'container', 17137 name: 'container', 17138 label: 'Container', 17139 category: 'main', 17140 parent: 'div', 17141 nest: ['*'], 17142 close: true, 17143 text: false, 17144 order: 1, 17145 empty: true, 17146 json: function() 17147 { 17148 return {name: 'container', label: 'Container'}; 17149 } 17150 }, 17151 { 17152 id: 'body', 17153 name: 'body', 17154 label: 'Body', 17155 category: 'main', 17156 parent: false, 17157 nest: ['*'], 17158 close: true, 17159 text: false, 17160 order: -1, 17161 empty: true, 17162 hidden: true, 17163 json: function() 17164 { 17165 return {name: 'body', label: 'Body'}; 17166 } 17167 }, {
17168 id: 'component', 17169 name: 'component', 17170 label: 'Component', 17171 category: 'main', 17172 parent: false, 17173 nest: [], 17174 close: true, 17175 text: false, 17176 order: 100, 17177 empty: true, 17178 json: function() 17179 { 17180 return {name: 'component', label: 'Component'}; 17181 } 17182 }); 17183});blocks.OnReady(function(blocks) 17184{ 17185 blocks.ItemsAdd({ 17186 id: 'img', 17187 name: 'img', 17188 label: 'Image', 17189 category: 'media', 17190 parent: false, 17191 nest: [], 17192 close: false, 17193 text: false, 17194 order: 6, 17195 empty: false, 17196 json: function() 17197 { 17198 return {name: 'img', label: 'Image', attributes: {1: {src: 'https://static.divhunt.com/assets/media/Image-Sample.svg'}}}; 17199 } 17200 }, 17201 { 17202 id: 'video', 17203 name: 'video', 17204 label: 'Video', 17205 category: 'media', 17206 parent: false, 17207 nest: ['source'], 17208 close: true, 17209 text: false, 17210 order: 7, 17211 empty: false, 17212 json: function() 17213 { 17214 return {name: 'video', label: 'Video'}; 17215 } 17216 }, 17217 { 17218 id: 'source', 17219 name: 'source', 17220 label: 'Source', 17221 category: 'media', 17222 parent: 'video', 17223 nest: [], 17224 close: false, 17225 text: false, 17226 hidden: true, 17227 order: 1, 17228 empty: false, 17229 json: function() 17230 { 17231 return {name: 'source', label: 'Source'}; 17232 } 17233 }, 17234 { 17235 id: 'iframe', 17236 name: 'iframe', 17237 label: 'Iframe', 17238 category: 'media', 17239 parent: false, 17240 nest: [], 17241 hidden: true, 17242 close: true, 17243 text: false, 17244 order: 11, 17245 empty: true, 17246 json: function() 17247 { 17248 return {name: 'iframe', label: 'Iframe'}; 17249 } 17250 }); 17251});blocks.OnReady(function(blocks) 17252{ 17253 blocks.ItemsAdd({ 17254 id: 'p', 17255 name: 'p', 17256 label: 'Paragraph', 17257 category: 'text', 17258 parent: false, 17259 nest: ['span', 'b', 'i', 'img', 'mark', 'cite'], 17260 close: true, 17261 text: true, 17262 order: 3, 17263 empty: false, 17264 json: function() 17265 { 17266 return {name: 'p', label: 'Paragraph', text: {1: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.'}}; 17267 } 17268 }, 17269 { 17270 id: 'span', 17271 name: 'span', 17272 label: 'Span', 17273 category: 'text', 17274 parent: 'p', 17275 nest: ['span'], 17276 close: true, 17277 text: true, 17278 order: 1, 17279 empty: false, 17280 json: function() 17281 { 17282 return {name: 'span', label: 'Span', text: {1: 'Inline Text.'}}; 17283 } 17284 }, 17285 { 17286 id: 'b', 17287 name: 'b', 17288 label: 'Bold', 17289 category: 'text', 17290 parent: 'p', 17291 nest: [], 17292 hidden: true, 17293 close: true, 17294 text: true, 17295 order: 2, 17296 json: function() 17297 { 17298 return {name: 'b', label: 'Bold', text: {1: 'Bolded Text.'}}; 17299 } 17300 }, 17301 { 17302 id: 'small', 17303 name: 'small', 17304 label: 'Small', 17305 category: 'text', 17306 parent: 'p', 17307 nest: [], 17308 hidden: true, 17309 close: true, 17310 text: true, 17311 order: 3, 17312 empty: false, 17313 json: function() 17314 { 17315 return {name: 'small', label: 'Small', text: {1: 'Small Text.'}}; 17316 } 17317 }, 17318 { 17319 id: 'mark', 17320 name: 'mark', 17321 label: 'Mark', 17322 category: 'text', 17323 parent: 'p', 17324 hidden: true, 17325 nest: [], 17326 close: true, 17327 text: true, 17328 order: 4, 17329 empty: false, 17330 json: function() 17331 { 17332 return {name: 'mark', label: 'Mark', text: {1: 'Marked Text.'}}; 17333 } 17334 }); 17335});blocks.OnReady(function(blocks) 17336{ 17337 blocks.ItemsAdd({ 17338 id: 'i', 17339 name: 'i', 17340 label: 'Icon', 17341 category: 'visual', 17342 parent: false, 17343 nest: [], 17344 close: true, 17345 hidden:true, 17346 text: false, 17347 order: 10, 17348 empty: true, 17349 json: function() 17350 { 17351 return {name: 'i', label: 'Icon'}; 17352 } 17353 }); 17354});blocks.FieldAdd('name'); 17355blocks.FieldAdd('hidden'); 17356blocks.FieldAdd('parent'); 17357blocks.FieldAdd('category'); 17358blocks.FieldAdd('nest'); 17359blocks.FieldAdd('text'); 17360blocks.FieldAdd('close'); 17361blocks.FieldAdd('label'); 17362blocks.FieldAdd('order'); 17363blocks.FieldAdd('empty'); 17364blocks.FieldAdd('icon'); 17365blocks.FieldAdd('json'); 17366blocks.FieldAdd('onRender'); 17367builder.OnReady(function() 17368{ 17369 predefines.Ready(); 17370});$(document).on('click', '.dh-language', function() { 17371
17372 let language = $dh.language(); 17373 let name = $(this).attr('data-name'); 17374 let uri = window.location.pathname.split('/').filter(Boolean); 17375 17376 if(language.active !== language.default) 17377 { 17378 uri.shift(); 17379 } 17380 17381 if(name !== language.default) 17382 { 17383 uri.unshift(name.toLowerCase()); 17384 } 17385 17386 window.location.href = '/' + uri.join('/'); 17387}); 17388interactions.FieldAdd('name'); 17389interactions.FieldAdd('event'); 17390interactions.FieldAdd('once'); 17391interactions.FieldAdd('loop'); 17392interactions.FieldAdd('actions'); 17393interactions.FieldAdd('execution'); 17394interactions.FieldAdd('target'); 17395interactions.FieldAdd('Target', (value, item) => 17396{ 17397 return value === null ? $(item.Get('target')) : value; 17398}); 17399 17400interactions.FieldAdd('values', (value) => 17401{ 17402 return value && typeof value === 'object' ? value : {}; 17403}); 17404 17405interactions.FieldAdd('stagger', (value) => 17406{ 17407 value = value ? value : '0ms'; 17408 return parseInt((value + '').replace('ms', '')); 17409}); 17410 17411interactions.Ready();interactions.FunctionCreate('code.target', function(addon, target, functions) 17412{ 17413 if(!Array.isArray(functions)) 17414 { 17415 return target; 17416 } 17417 17418 functions.forEach((value) => 17419 { 17420 if(value.function === 'prev') 17421 { 17422 target = target.prev(); 17423 } 17424 else if(value.function === 'next') 17425 { 17426 target = target.next(); 17427 } 17428 else if(value.function === 'parent') 17429 { 17430 target = target.parent(); 17431 } 17432 else if(value.function === 'closest') 17433 { 17434 target = target.closest(value.value); 17435 } 17436 else if(value.function === 'find') 17437 { 17438 target = target.find(value.value); 17439 } 17440 else if(value.function === 'custom') 17441 { 17442 target = $(value.value); 17443 } 17444 }); 17445 17446 return target; 17447});interactions.FunctionCreate('run', function(addon, data, target, actions = [], callbacks = {}, identifier = 1, stagger = 0, initial = false, execution = 'asynchronous') 17448{ 17449 this.identifier = identifier; 17450 this.executions = addon.GetTemp(('executions.' + identifier), {}); 17451 this.timing = 0; 17452 17453 let {onActionFinish, onActionFail, onFinish} = callbacks; 17454 17455 this.init = () => 17456 { 17457 /* Initial */ 17458 if(initial) 17459 { 17460 actions.forEach((values, index) => 17461 { 17462 if('initial' in values && values.initial === 'yes') 17463 { 17464 this.run(values, index, target, true, () => {}); 17465 } 17466 }); 17467 17468 return; 17469 } 17470 17471 /* Asynchronous */ 17472 if(execution === 'asynchronous') 17473 { 17474 this.initAsynchronous(target); 17475 } 17476 17477 /* Synchronous */ 17478 if(execution === 'synchronous') 17479 { 17480 this.initSynchronous(target); 17481 } 17482 }; 17483 17484 this.initAsynchronous = (target) => 17485 { 17486 actions.forEach((values, index) => 17487 { 17488 this.run(values, index, target, false, (callback) => 17489 { 17490 if(callback.finished) 17491 { 17492 /* Action Callback */ 17493 if(onActionFinish) 17494 { 17495 onActionFinish($.extend(callback, {timing})); 17496 } 17497 17498 /* Interaction Callback */ 17499 if(index === (actions.length - 1)) 17500 { 17501 if(onFinish) 17502 { 17503 onFinish({timing: this.timing}); 17504 } 17505 } 17506 } 17507 }); 17508 }); 17509 }; 17510 17511 this.initSynchronous = (target, index = 0) => 17512 { 17513 if(!(index in actions)) 17514 { 17515 return; 17516 } 17517 17518 let action = actions[index]; 17519 17520 this.run(action, index, target, false, (callback, timing) => 17521 { 17522 if(callback.finished) 17523 { 17524 /* Action Callback */ 17525 if(onActionFinish) 17526 { 17527 onActionFinish($.extend(callback, {timing})); 17528 } 17529 17530 /* Interaction Callback */ 17531 if(index === (actions.length - 1)) 17532 { 17533 if(onFinish) 17534 { 17535 onFinish({timing: this.timing}); 17536 } 17537 } 17538 17539 this.initSynchronous(target, index + 1); 17540 } 17541 }) 17542 }; 17543 17544 this.run = (values, index, target, initial, callback) => 17545 { 17546 let item = mdActions.ItemGet(values.action); 17547 17548 /* Item Check */ 17549 if(!item) 17550 { 17551 callback({response: null, runs: 0, count: 0, timing: 0, finished: true}); 17552 return; 17553 } 17554 17555 /* Initial Check */ 17556 if(!item.Get('initial') && initial) 17557 { 17558 callback({response: null, runs: 0, count: 0, timing: 0, finished: true}); 17559 return; 17560 } 17561 17562 /* Target */ 17563 if(item.Get('target')) 17564 { 17565 if('dh-tag-target' in values && values['dh-tag-target']) 17566 { 17567 target = $('main .t' + values['dh-tag-target']); 17568 } 17569 17570 target = interactions.Fn('code.target', target, values.target); 17571 } 17572 17573 /* Manual */ 17574 if(item.Get('manual')) 17575 { 17576 return this.runManual(target, initial, values, item, index, callback); 17577 } 17578 17579 /* Scroll */ 17580 if(['dh.scroll', 'dh.scrolling'].includes(data.event.name)) 17581 { 17582 return this.runScroll(target, values, item, index, callback); 17583 } 17584 17585 /* Normal */ 17586 this.runNormal(target, values, item, index, callback); 17587 }; 17588 17589 this.runManual = (target, initial, values, item, index, callback) => 17590 { 17591 let runs = 0; 17592 let count = target.length; 17593 17594 $.each(target, (eachIndex, eachTarget) => 17595 { 17596 let identifier = (this.identifier + '-' + index + '-' + eachIndex); 17597 17598 if(initial) 17599 { 17600 return this.execute(values, item, initial, eachTarget, identifier, () => {}); 17601 } 17602 17603 dh.debounce(() => 17604 { 17605 this.execute(values, item, initial, eachTarget, identifier, (response, timing) => 17606 { 17607 runs++; 17608 17609 if(runs === count) 17610 { 17611 callback({response, runs, count, timing, finished: true}); 17612 } 17613 else 17614 { 17615 callback({response, runs, count, timing, finished: false}); 17616 } 17617 }); 17618 }, 17619 stagger * eachIndex, 17620 ('interactions.manual.' + identifier) 17621 ); 17622 }); 17623 }; 17624 17625 this.runScroll = (target, values, item, index, callback) => 17626 { 17627 let delay = parseInt(values.delay); 17628 17629 if(data.event.data.direction === 'down') 17630 { 17631 if(data.event.data.percent.top < delay / 100) 17632 { 17633 return; 17634 } 17635 else if(data.event.data.percent.top >= delay / 100) 17636 { 17637 if(this.executions[(identifier + '.' + index) + '.up']) 17638 { 17639 return; 17640 } 17641 17642 if(this.executions[identifier + '.down'] === index) 17643 { 17644 return; 17645 } 17646 17647 this.executions[identifier + '.up'] = index; 17648 this.executions[(identifier + '.' + index) + '.up'] = true; 17649 17650 delete this.executions[(identifier + '.' + index) + '.down']; 17651 delete this.executions[identifier + '.down']; 17652 } 17653 } 17654 else 17655 { 17656 if(data.event.data.percent.top > delay / 100) 17657 { 17658 return; 17659 } 17660 else if(data.event.data.percent.top <= delay / 100) 17661 { 17662 if(this.executions[(identifier + '.' + index) + '.down']) 17663 { 17664 return; 17665 } 17666 17667 if(this.executions[identifier + '.up'] === index) 17668 { 17669 return; 17670 } 17671 17672 this.executions[identifier + '.down'] = index; 17673 this.executions[(identifier + '.' + index) + '.down'] = true; 17674 17675 delete this.executions[(identifier + '.' + index) + '.up']; 17676 delete this.executions[identifier + '.up']; 17677 } 17678 } 17679 17680 this.executions[identifier] = index; 17681 17682 addon.SetTemp('executions.' + identifier, this.executions); 17683 17684 $.each(target, (eachIndex, eachTarget) => 17685 { 17686 let identifier = (this.identifier + '-' + index + '-' + eachIndex); 17687 this.execute(values, item, initial, eachTarget, identifier, () => {}); 17688 }); 17689 }; 17690 17691 this.runNormal = (target, values, item, index, callback) => 17692 { 17693 let runs = 0; 17694 let count = target.length; 17695 let delay = parseInt(values.delay); 17696 17697 $.each(target, (eachIndex, eachTarget) => 17698 { 17699 let identifier = (this.identifier + '-' + index + '-' + eachIndex); 17700 17701 dh.debounce(() => 17702 { 17703 this.execute(values, item, initial, eachTarget, identifier, (response, timing) => 17704 { 17705 runs++; 17706 17707 if(runs === count) 17708 { 17709 callback({response, runs, count, timing, finished: true}); 17710 } 17711 else 17712 { 17713 callback({response, runs, count, timing, finished: false}); 17714 } 17715 }); 17716 }, 17717 delay + (stagger * eachIndex), 17718 'interactions.normal.' + identifier 17719 ); 17720 }); 17721 } 17722 17723 this.execute = (values, item, initial, target, index, callback = false) => 17724 { 17725 let timing = 0; 17726 let interval = setInterval(() => 17727 { 17728 timing += 10; 17729 17730 if(timing > 10000) 17731 { 17732 clearInterval(interval); 17733 } 17734 }, 10); 17735 17736 new Promise((resolve, reject) => 17737 { 17738 let codeFn = item.Get('code'); 17739 let code; 17740 17741 if(typeof codeFn === 'function') 17742 { 17743 code = codeFn.bind({}); 17744 data = $.extend({}, data); 17745 17746 data.values = values; 17747 data.target = $(target); 17748 data.action = { 17749 name: values.name, 17750 item, 17751 initial, 17752 target: $(target) 17753 }; 17754 17755 let resolveFn = (data = null) => 17756 { 17757 resolve({data, item, variable: item.Get('variable') ? values.variable : null}); 17758 }; 17759 17760 code(data, resolveFn, reject, (value, variables = null) => 17761 { 17762 if(variables) 17763 { 17764 return mdVars.Fn('process', value, $.extend({data}, variables)); 17765 } 17766 17767 return mdVars.Fn('process', value, {data}); 17768 }, index); 17769 } 17770 }) 17771 .then((response) => 17772 { 17773 if(response.variable) 17774 { 17775 data.variables[response.variable] = response.data; 17776 } 17777 17778 clearInterval(interval); 17779 this.timing += timing; 17780 17781 if(callback) 17782 { 17783 callback(response, timing); 17784 } 17785 }) 17786 .catch((error) => 17787 { 17788 clearInterval(interval); 17789 this.timing += timing; 17790 17791 if(onActionFail) 17792 { 17793 onActionFail(error); 17794 } 17795 }); 17796 }; 17797 17798 return this.init(); 17799});interactions.ItemsKeys(['name', 'event', 'once', 'loop', 'execution', 'stagger', 'actions', 'target', 'values']);interactions.OnItemAdd((addon, item) => 17800{ 17801 item.Fn('process'); 17802}); 17803interactions.FunctionCreate('item.process', function(addon, item) 17804{ 17805 let run = (target, event, initial, loop) => 17806 { 17807 let data = {event, interaction: {item, initial, loop}, target, variables: $.extend({}, event.data)}; 17808 let callbacks = { 17809 onFinish: (event) => 17810 { 17811 if(item.Get('loop') && loop) 17812 { 17813 if(event.timing < 50) 17814 { 17815 dh.debounce(() => 17816 { 17817 run(target, event, initial, loop); 17818 }, 50, 'interactions.timing.' + item.Get('id')); 17819 } 17820 else 17821 { 17822 run(target, event, initial, loop); 17823 } 17824 } 17825 } 17826 }; 17827 17828 interactions.Fn('run', data, target, item.Get('actions'), callbacks, item.Get('id'), item.Get('stagger'), initial, item.Get('execution')); 17829 return true; 17830 }; 17831 17832 /* Initial */ 17833 mdEvents.Fn('catch', 'dh.pages.ready', (data, event) => 17834 { 17835 run($(item.Get('target')), {data, item: event, name: event.Get('id')}, true, false); 17836 }, 17837 null, 17838 false, 17839 $.extend(item.Get('values'), {target: item.Get('target')}) 17840 ); 17841 17842 /* Normal */ 17843 mdEvents.Fn('catch', item.Get('event'), (data, event) => 17844 { 17845 let target = 'target' in data ? data.target : $(item.Get('target')); 17846 17847 if(target.is(item.Get('target'))) 17848 { 17849 return run(target, {data, item: event, name: event.Get('id')}, false, true); 17850 } 17851 17852 if(target.closest(item.Get('target')).length) 17853 { 17854 return run(target.closest(item.Get('target')), {data, item: event, name: event.Get('id')}, false, true); 17855 } 17856 17857 return false;
17858 }, 17859 null, 17860 !!item.Get('once') && !['dh.scroll', 'dh.scrolling'].includes(item.Get('event')), 17861 $.extend(item.Get('values'), {target: item.Get('target')}) 17862 ); 17863}); 17864Addons.create('builder.transform', function(addon) 17865{ 17866 addon.FieldAdd('name'); 17867 addon.FieldAdd('group'); 17868 addon.FieldAdd('options'); 17869 addon.FieldAdd('code', function(value) 17870 { 17871 if(typeof value !== 'function') 17872 { 17873 return function() {}; 17874 } 17875 17876 return value; 17877 }); 17878});transform.FunctionCreate('run', function(addon, data = {}) 17879{ 17880 this.init = () => 17881 { 17882 let This = this; 17883 let i = 0; 17884 17885 $('[dh-transform]').each(function() 17886 { 17887 i++; 17888 This.target($(this), i); 17889 }); 17890 }; 17891 17892 this.target = (target, i) => 17893 { 17894 if(target.find('.dh-loader').length) 17895 { 17896 return setTimeout(() => 17897 { 17898 this.target(target, i); 17899 }, 10); 17900 } 17901 17902 let item = transform.ItemGet($(target).attr('dh-transform')); 17903 17904 if(item) 17905 { 17906 /* Get Tag */ 17907 let tag = components.tags.ItemGet($(target).attr('class').split(' ')[0].replace('t', '')); 17908 17909 /* Get Options */ 17910 let options = item.Fn('options', $(target).attr('dh-transform-options')); 17911 17912 /* Run Code */ 17913 try 17914 { 17915 let codeFn = item.Get('code'); 17916 let code; 17917 17918 if(typeof codeFn === 'function') 17919 { 17920 code = codeFn.bind({}); 17921 code = code(item, tag, $(target), options, data, i); 17922 17923 if(code && typeof code === 'object' && ('init' in code)) 17924 { 17925 code.init(); 17926 } 17927 } 17928 } 17929 catch(error) 17930 { 17931 console.log(error); 17932 } 17933 } 17934 else 17935 { 17936 let name = $(target).attr('dh-transform'); 17937 console.log('Failed to find transform: "' + name + '"', 'Element:', target[0], 'Classes:', target.attr('class')); 17938 17939 if(typeof admin !== 'undefined' && admin && name) 17940 { 17941 let missing = addon.GetTemp('missing_transforms') || []; 17942 if(missing.indexOf(name) === -1) missing.push(name); 17943 addon.SetTemp('missing_transforms', missing); 17944 17945 clearTimeout(addon.GetTemp('missing_transforms_timer')); 17946 addon.SetTemp('missing_transforms_timer', setTimeout(function() 17947 { 17948 let $icon = $('#dh-diagnostics-icon'); 17949 if($icon.length) $icon.addClass('has-issues'); 17950 17951 if(!addon.GetTemp('diagnostics_opened')) 17952 { 17953 addon.SetTemp('diagnostics_opened', true); 17954 setTimeout(function() { $('#dh-diagnostics-icon').trigger('click'); }, 300); 17955 } 17956 }, 1000)); 17957 } 17958 } 17959 17960 $(target).removeAttr('dh-transform'); 17961 $(target).removeAttr('dh-transform-options'); 17962 }; 17963 17964 return this; 17965});transform.FunctionCreate('item.options', function(addon, item, attribute, merge = true) 17966{ 17967 this.init = () => 17968 { 17969 let options = item.Get('options'); 17970 let data = this.data(attribute, options); 17971 17972 if(!merge) 17973 { 17974 return data; 17975 } 17976 17977 return this.merge(options, data) 17978 }; 17979 17980 this.data = (attribute, options) => 17981 { 17982 let data = {}; 17983 17984 $.each(String(attribute).split('|'), function(key, value) 17985 { 17986 value = value.split(':'); if(!('0' in value) || !('1' in value)) { return true; } 17987 17988 let id = value[0]; value.shift(); 17989 17990 if(!(id in options)) 17991 { 17992 return true; 17993 } 17994 17995 data[id] = value.join(':'); 17996 }); 17997 17998 return data; 17999 }; 18000 18001 this.merge = (options, data) => 18002 { 18003 $.each(options, function(id, option) 18004 { 18005 if(!(id in data)) 18006 { 18007 data[id] = 'value' in option ? option.value : null; 18008 } 18009 18010 if(!(data[id] + '').startsWith('$') && 'value' in option && typeof data[id] !== typeof option.value) 18011 { 18012 let type = typeof option.value; 18013 18014 if(type === 'number') 18015 { 18016 data[id] = !isNaN(parseInt(data[id])) ? parseInt(data[id]) : option.value; 18017 } 18018 else if(type === 'boolean') 18019 { 18020 if(data[id] === 'true') 18021 { 18022 data[id] = true; 18023 } 18024 else if(data[id] === 'false') 18025 { 18026 data[id] = false;
18027 } 18028 else 18029 { 18030 data[id] = option.value; 18031 } 18032 } 18033 else 18034 { 18035 data[id] = option.value; 18036 } 18037 } 18038 18039 option.type = option.type.toLowerCase(); 18040 18041 if(option.type === 'component' && data[id]) 18042 { 18043 data[id] = components.ItemGet(data[id].replace(/\D/g, '')); 18044 } 18045 else if(['parent', 'child', 'tag', 'sibling'].includes(option.type) && data[id]) 18046 { 18047 data[id] = components.tags.ItemGet(data[id].replace(/\D/g, '')); 18048 } 18049 }); 18050 18051 return data; 18052 }; 18053 18054 return this; 18055});mdEvents.Fn('catch', 'dh.pages.ready', (data) => 18056{ 18057 let {page} = data; 18058 18059 transform.Fn('run', {mode: 'website', page}); 18060}); 18061 18062mdEvents.Fn('catch', 'dh.pages.load', (data) => 18063{ 18064 let {page} = data; 18065 18066 transform.Fn('run', {mode: 'website', page}); 18067}); 18068/** 18069 * builder.gsapcode — "GSAP Vibe Code" 18070 * 18071 * Runs AI-authored GSAP per section. Storage lives on the section tag 18072 * (component_tag.properties.gsap); the build compiles it into a flat registry 18073 * that is injected on every page via gsapcode.ItemsArrayAdd([...]). 18074 * 18075 * Named "gsapcode" (NOT "gsap") on purpose: the front-addon global is the id's 18076 * last segment, and "gsap" is owned by the GSAP library (window.gsap). 18077 * 18078 * Registry item shape (added at build time, see build/js.php): 18079 * { target: '.tNN', code: '<gsap source string>' } 18080 * 18081 * Global named presets (Phase 4) are kept in temp state and applied to elements 18082 * carrying a dh-gsap-preset="<name>" attribute. 18083 */ 18084Addons.create('builder.gsapcode', function(addon) 18085{ 18086 addon.FieldAdd('target'); 18087 addon.FieldAdd('code'); 18088 addon.FieldAdd('config'); 18089 18090 // name -> gsap source string, e.g. {'title-reveal': '...'} 18091 addon.SetTemp('presets', {}); 18092 18093 // Addon globals are consts in the bundle scope, NOT window properties. The 18094 // builder's GSAP view drives this runtime cross-realm through the preview 18095 // iframe (contentWindow.gsapcode.Fn('apply'|'replay'|...)), so expose it. 18096 window.gsapcode = addon; 18097}); 18098/** 18099 * Execute every registered section animation. 18100 * 18101 * Runs in any realm where the addon JS is present — a visitor page, the builder 18102 * canvas, or the GSAP-view preview iframe. GSAP is lazy-loaded once via mdLibraries. 18103 * 18104 * Registry item: { target: '.tNN', code: '<gsap source>', config: {trigger, speed, mobile, start} } 18105 * config (all optional, saved in tag properties.gsap_config): 18106 * trigger — 'view' (default: play once when section enters viewport) 18107 * 'load' (play immediately) | 'reenter' (replay every time it enters) 18108 * 'scrub' (timeline progress is driven by scroll position) 18109 * speed — timeScale multiplier (0.5 = half speed), default 1 18110 * mobile — false disables the animation under 768px 18111 * start — viewport % the section top must cross to count as "in view", default 80 18112 */ 18113gsapcode.FunctionCreate('run', function(addon, config) 18114{ 18115 config = config || {}; 18116 18117 mdLibraries.Fn('load', ['gsap'], function() 18118 { 18119 Object.values(addon.ItemsGet()).forEach(function(item) 18120 { 18121 addon.Fn('section', item, config); 18122 }); 18123 18124 // Global code — runs once, scoped to the whole page. 18125 let global = addon.GetTemp('global', ''); 18126 18127 if(global) 18128 { 18129 let root = document.querySelector('#dh-website > main') || document.body; 18130 let win = root && root.ownerDocument.defaultView; 18131 18132 if(win && win.gsap && !(win.matchMedia && win.matchMedia('(prefers-reduced-motion: reduce)').matches)) 18133 { 18134 let gtl = addon.Fn('exec', root, root, global, win); 18135 18136 if(gtl) gtl.play(); // global runs page-wide immediately (not viewport-gated) 18137 } 18138 } 18139 }); 18140}); 18141 18142// Store site-wide global GSAP code (set at build time). 18143gsapcode.FunctionCreate('global.set', function(addon, code) 18144{ 18145 addon.SetTemp('global', code); 18146}); 18147 18148/** 18149 * Apply one section: global presets first (tagged descendants), then its own code. 18150 * 18151 * querySelectorAll, not querySelector: a reusable COMPONENT's source tag renders 18152 * its .t{newID} class on EVERY instance — the same animation must run on all. 18153 */ 18154gsapcode.FunctionCreate('section', function(addon, item, config) 18155{
18156 document.querySelectorAll(item.Get('target')).forEach(function(root) 18157 { 18158 addon.Fn('section.root', root, item, config); 18159 }); 18160}); 18161 18162gsapcode.FunctionCreate('section.root', function(addon, root, item, config) 18163{ 18164 let win = root.ownerDocument.defaultView; 18165 18166 if(!win || !win.gsap) 18167 { 18168 return; 18169 } 18170 18171 // Honor reduced-motion — no animation, leave the static layout. 18172 if(win.matchMedia && win.matchMedia('(prefers-reduced-motion: reduce)').matches) 18173 { 18174 return; 18175 } 18176 18177 let cfg = item.Get('config') || {}; 18178 18179 // Per-section, per-device opt-out. devices = {desktop, tablet, mobile}; 18180 // cfg.mobile is the legacy single toggle, honored when devices is absent. 18181 let width = win.innerWidth; 18182 let device = width <= 767 ? 'mobile' : (width <= 1024 ? 'tablet' : 'desktop'); 18183 18184 if(cfg.devices && typeof cfg.devices === 'object') 18185 { 18186 if(cfg.devices[device] === false) 18187 { 18188 return; 18189 } 18190 } 18191 else if(cfg.mobile === false && device === 'mobile') 18192 { 18193 return; 18194 } 18195 18196 let code = item.Get('code'); 18197 let tls = []; 18198 18199 // Build the animations NOW (paused). `.from()` renders the start/hidden state 18200 // immediately as it's added — so elements are hidden from the first frame 18201 // (no flash) — but the timeline doesn't advance until we play it. 18202 let presets = addon.GetTemp('presets', {}); 18203 18204 root.querySelectorAll('[dh-gsap-preset]').forEach(function(element) 18205 { 18206 let preset = presets[element.getAttribute('dh-gsap-preset')]; 18207 18208 if(preset) tls.push(addon.Fn('exec', root, element, preset, win)); 18209 }); 18210 18211 if(code) 18212 { 18213 let tl = addon.Fn('exec', root, root, code, win); 18214 18215 if(tl) 18216 { 18217 tls.push(tl); 18218 $(root).data('dh-gsap-tl', tl); // the section's main timeline (devtools/replay) 18219 } 18220 } 18221 18222 tls = tls.filter(Boolean); 18223 18224 if(tls.length) 18225 { 18226 addon.Fn('wire', root, tls, cfg, win); 18227 } 18228}); 18229 18230/** 18231 * Wire built (paused) timelines to their trigger, honoring the section config. 18232 * Cleans up whatever the previous wire on this root created. 18233 */ 18234gsapcode.FunctionCreate('wire', function(addon, root, tls, cfg, win) 18235{ 18236 cfg = cfg || {}; 18237 18238 addon.Fn('unwire', root); 18239 18240 let speed = parseFloat(cfg.speed) || 1; 18241 18242 tls.forEach(function(tl) { if(tl.timeScale) tl.timeScale(speed); }); 18243 18244 let wire = {observers: [], triggers: []}; 18245 let start = Math.min(Math.max(parseInt(cfg.start) || 80, 1), 100); 18246 let markers = !!addon.GetTemp('markers', false); 18247 let play = function() { tls.forEach(function(tl) { tl.play(); }); }; 18248 let restart = function() { tls.forEach(function(tl) { tl.restart(); }); }; 18249 18250 let ST = win.ScrollTrigger 18251 || (win.gsap.core && win.gsap.core.globals && win.gsap.core.globals().ScrollTrigger) 18252 || null; 18253 18254 // load — play right away. 18255 if(cfg.trigger === 'load') 18256 { 18257 play(); 18258 $(root).data('dh-gsap-wire', wire); 18259 return; 18260 } 18261 18262 // scrub — scroll position drives the timeline (needs ScrollTrigger; falls through to view otherwise). 18263 if(cfg.trigger === 'scrub' && ST) 18264 { 18265 tls.forEach(function(tl) 18266 { 18267 tl.pause(0); 18268 18269 wire.triggers.push(ST.create({ 18270 trigger: root, 18271 start: 'top ' + start + '%', 18272 end: 'bottom 40%', 18273 scrub: 1, 18274 animation: tl, 18275 markers: markers 18276 })); 18277 }); 18278 18279 $(root).data('dh-gsap-wire', wire); 18280 return; 18281 } 18282 18283 // view (default) / reenter — IntersectionObserver gating. 18284 if(win.IntersectionObserver) 18285 { 18286 let margin = '0px 0px -' + (100 - start) + '% 0px'; 18287 let reenter = (cfg.trigger === 'reenter'); 18288 18289 let observer = new win.IntersectionObserver(function(entries, obs) 18290 { 18291 entries.forEach(function(entry) 18292 { 18293 if(!entry.isIntersecting) return; 18294 18295 if(reenter) 18296 { 18297 restart(); 18298 } 18299 else 18300 { 18301 obs.disconnect(); 18302 play(); 18303 } 18304 }); 18305 }, {rootMargin: margin, threshold: 0}); 18306 18307 observer.observe(root); 18308 wire.observers.push(observer); 18309 } 18310 else 18311 { 18312 play(); 18313 } 18314 18315 $(root).data('dh-gsap-wire', wire); 18316}); 18317 18318gsapcode.FunctionCreate('unwire', function(addon, root) 18319{ 18320 let wire = $(root).data('dh-gsap-wire'); 18321 18322 if(!wire) return; 18323
18324 (wire.observers || []).forEach(function(observer) { observer.disconnect(); }); 18325 (wire.triggers || []).forEach(function(trigger) { if(trigger.kill) trigger.kill(); }); 18326 18327 $(root).removeData('dh-gsap-wire'); 18328}); 18329 18330/** 18331 * Compile + run a GSAP code string, scoped to a section root, with clean teardown. 18332 * 18333 * - new win.Function(...) builds the function in the SITE realm, so any bare 18334 * document/window the AI writes resolves to the page, not the builder UI. 18335 * - q = root-scoped selector, so animations can never reach into another section. 18336 * - gsap.context(fn, root) scopes selectors and gives one-call revert(). 18337 * - the previous context on this element is reverted first, so a re-prompt or a 18338 * live edit replaces the running animation instead of stacking on top of it. 18339 */ 18340gsapcode.FunctionCreate('exec', function(addon, root, scopeEl, code, win) 18341{ 18342 let previous = $(scopeEl).data('dh-gsap-ctx'); 18343 18344 if(previous) 18345 { 18346 previous.revert(); 18347 } 18348 18349 // Resolve ScrollTrigger reliably: the GSAP bundle may expose it as 18350 // win.ScrollTrigger OR only register it inside gsap. The AI's code guards on 18351 // the `ScrollTrigger` argument — if it's falsy, scroll animations fall back to 18352 // playing on load. So find it either way and register it (idempotent). 18353 let ST = win.ScrollTrigger 18354 || (win.gsap.core && win.gsap.core.globals && win.gsap.core.globals().ScrollTrigger) 18355 || null; 18356 18357 if(ST && win.gsap.registerPlugin) 18358 { 18359 win.gsap.registerPlugin(ST); 18360 } 18361 18362 let tl = null; 18363 18364 try 18365 { 18366 let q = win.gsap.utils.selector(root); 18367 let fn = new win.Function('root', 'gsap', 'ScrollTrigger', 'q', code); 18368 18369 // The code builds a PAUSED timeline (with its own defaults) and RETURNS it. 18370 // `.from()` renders the hidden start state immediately (no flash); we play 18371 // it when the section is in view. Run inside a context for clean teardown. 18372 let context = win.gsap.context(function() 18373 { 18374 tl = fn(root, win.gsap, ST, q); 18375 }, root); 18376 18377 $(scopeEl).data('dh-gsap-ctx', context); 18378 } 18379 catch(error) 18380 { 18381 // A single broken section must never take down the rest of the page. 18382 // Record it so the builder panel can surface it (and offer an AI fix). 18383 let message = (error && error.message) ? error.message : String(error); 18384 let errors = addon.GetTemp('errors', []); 18385 18386 errors.push({target: $(root).attr('dh-gsap-section') || '', message: message}); 18387 addon.SetTemp('errors', errors); 18388 18389 console.error('[dh-gsap] section error', error); 18390 } 18391 18392 if(ST && ST.refresh) 18393 { 18394 ST.refresh(); 18395 } 18396 18397 return (tl && typeof tl.play === 'function') ? tl : null; 18398}); 18399 18400/** 18401 * Panel API — hot-swap one section's code without a reload. Runs in THIS realm 18402 * (the preview iframe) but is called from the builder panel via contentWindow: 18403 * iframe.contentWindow.gsapcode.Fn('apply', '[dh-gsap-section="123"]', code, config, then) 18404 * 18405 * Plays immediately (the user is looking at it) — except scrub, which wires 18406 * scroll-driving so it can be scroll-tested. `then` gets {ok, error}. 18407 */ 18408gsapcode.FunctionCreate('apply', function(addon, selector, code, config, then) 18409{ 18410 then = then || function() {}; 18411 18412 // All instances — a component's section renders once per usage. 18413 let roots = Array.prototype.slice.call(document.querySelectorAll(selector)); 18414 18415 if(!roots.length) return then({ok: false, error: 'Section not found in the preview.'}); 18416 18417 let win = roots[0].ownerDocument.defaultView; 18418 18419 mdLibraries.Fn('load', ['gsap'], function() 18420 { 18421 addon.SetTemp('errors', []); 18422 18423 roots.forEach(function(root) 18424 { 18425 addon.Fn('unwire', root); 18426 18427 let tl = addon.Fn('exec', root, root, code, win); 18428 18429 if(!tl) return; 18430 18431 $(root).data('dh-gsap-tl', tl); 18432 18433 let cfg = config || {}; 18434 let speed = parseFloat(cfg.speed) || 1; 18435 18436 tl.timeScale(speed); 18437 18438 if(cfg.trigger === 'scrub') 18439 { 18440 addon.Fn('wire', root, [tl], cfg, win); 18441 } 18442 else 18443 { 18444 tl.play(0); 18445 } 18446 }); 18447 18448 let errors = addon.GetTemp('errors', []); 18449 18450 if(errors.length) 18451 { 18452 return then({ok: false, error: errors[0].message}); 18453 } 18454 18455 // Keep the registry in sync so a later full re-run replays the NEW code. 18456 let item = Object.values(addon.ItemsGet()).find(function(entry) 18457 { 18458 return document.querySelector(entry.Get('target')) === roots[0]; 18459 }); 18460 18461 if(item) 18462 { 18463 item.Set('code', code, false); 18464 item.Set('config', config || null, false); 18465 } 18466 18467 then({ok: true}); 18468 }); 18469}); 18470 18471/** 18472 * Panel API — collect (and clear) runtime errors recorded during the last run. 18473 * 18474 * NOTE: any function added here shares its name with the builder view's addon 18475 * functions — this file is #global, so it ALSO loads in the admin realm, and a 18476 * duplicate name silently clobbers the view's version. Keep names distinct. 18477 */ 18478gsapcode.FunctionCreate('errors.pull', function(addon) 18479{ 18480 let errors = addon.GetTemp('errors', []); 18481 18482 addon.SetTemp('errors', []); 18483 18484 return errors; 18485}); 18486 18487/** 18488 * Panel API — toggle ScrollTrigger markers for scrub debugging, then re-run. 18489 */ 18490gsapcode.FunctionCreate('markers.set', function(addon, on) 18491{ 18492 addon.SetTemp('markers', !!on); 18493 addon.Fn('run'); 18494}); 18495 18496/** 18497 * Register global named presets (Phase 4). Merges into existing presets. 18498 */ 18499gsapcode.FunctionCreate('presets.add', function(addon, presets) 18500{
18501 let current = addon.GetTemp('presets', {}); 18502 18503 addon.SetTemp('presets', Object.assign(current, presets || {})); 18504}); 18505 18506/** 18507 * SPA teardown — runs on dh.pages.unload, BEFORE the page DOM is replaced. 18508 * 18509 * Contexts and element data die with the old DOM, but ScrollTriggers and 18510 * IntersectionObservers are registered globally and would keep piling up on 18511 * every navigation (page1 → page2 → page1 …). Revert everything while the old 18512 * DOM is still queryable, then sweep any remaining ScrollTriggers — at this 18513 * moment every trigger belongs to the outgoing page, so a full sweep is safe. 18514 */ 18515gsapcode.FunctionCreate('teardown', function(addon) 18516{ 18517 let revert = function(el) 18518 { 18519 let ctx = $(el).data('dh-gsap-ctx'); 18520 18521 if(ctx && ctx.revert) 18522 { 18523 ctx.revert(); 18524 $(el).removeData('dh-gsap-ctx'); 18525 } 18526 }; 18527 18528 Object.values(addon.ItemsGet()).forEach(function(item) 18529 { 18530 document.querySelectorAll(item.Get('target')).forEach(function(root) 18531 { 18532 addon.Fn('unwire', root); 18533 18534 root.querySelectorAll('[dh-gsap-preset]').forEach(revert); 18535 revert(root); 18536 18537 $(root).removeData('dh-gsap-tl'); 18538 }); 18539 }); 18540 18541 // Global code context (page-scoped). 18542 revert(document.querySelector('#dh-website > main') || document.body); 18543 18544 // Sweep stragglers (e.g. per-element triggers created by global code). 18545 let win = window; 18546 let ST = win.ScrollTrigger 18547 || (win.gsap && win.gsap.core && win.gsap.core.globals && win.gsap.core.globals().ScrollTrigger) 18548 || null; 18549 18550 if(ST && ST.getAll) 18551 { 18552 ST.getAll().forEach(function(trigger) { trigger.kill(); }); 18553 } 18554}); 18555// SPA navigation: unload fires BEFORE the DOM swap — tear down ScrollTriggers, 18556// observers and contexts of the outgoing page (they're registered globally and 18557// would accumulate across page1 → page2 → page1 hops). ready/load then rebuild 18558// everything against the fresh DOM, exactly like a first visit. 18559mdEvents.Fn('catch', 'dh.pages.unload', () => 18560{ 18561 gsapcode.Fn('teardown'); 18562}); 18563 18564mdEvents.Fn('catch', 'dh.pages.ready', (data) => 18565{ 18566 let {page} = data; 18567 18568 gsapcode.Fn('run', {mode: 'website', page}); 18569}); 18570 18571mdEvents.Fn('catch', 'dh.pages.load', (data) => 18572{ 18573 let {page} = data; 18574 18575 gsapcode.Fn('run', {mode: 'website', page}); 18576}); 18577 18578$dh.Fn = (name, data = {}) => 18579{ 18580 let functions = files.GetTemp('functions', {}); 18581 18582 let item = files.ItemGet(functions[name]); 18583 18584 if(item) 18585 { 18586 return item.Fn('run', data); 18587 } 18588 18589 return null; 18590}; 18591 18592$dh.Module = (name) => 18593{ 18594 let modules = files.GetTemp('modules', {}); 18595 18596 let item = files.ItemGet(modules[name]); 18597 18598 if(item) 18599 { 18600 return item.Fn('run'); 18601 } 18602 18603 return null; 18604}; 18605 18606$dh.Class = (name, ...constructors) => 18607{ 18608 let classes = files.GetTemp('classes', {}); 18609 18610 let item = files.ItemGet(classes[name]); 18611 18612 if(item) 18613 { 18614 let cls = item.Fn('run') 18615 return new cls(...constructors); 18616 } 18617 18618 return null; 18619}; 18620 18621$dh.Get = (key, _default = null, save = false) => 18622{ 18623 if(save) 18624 { 18625 return files.GetSave(key, _default); 18626 } 18627 18628 return files.GetTemp(key, _default); 18629}; 18630 18631$dh.Set = (key, value, save = false) => 18632{ 18633 if(save) 18634 { 18635 files.SetSave(key, value); 18636 } 18637 18638 return files.SetTemp(key, value); 18639};mdEvents.Fn('catch', 'dh.pages.ready', (data) => 18640{ 18641 mdEvents.Fn('run', 'dh.ready', data); 18642}, null, true); 18643 18644mdEvents.Fn('catch', 'dh.pages.load', (data) => 18645{ 18646 mdEvents.Fn('run', 'dh.load', data); 18647}, null, true); 18648 18649mdEvents.Fn('catch', 'dh.pages.preload', (data) => 18650{ 18651 mdEvents.Fn('run', 'dh.preload', data); 18652}, null, true);files.OnItemAdd(function(addon, item) 18653{ 18654 /* Store Functions in Temp */ 18655 if(item.Get('extension') === 'function') 18656 { 18657 let temp = files.GetTemp('functions', {}); 18658 18659 temp[item.Get('name')] = item.Get('id'); 18660 18661 files.SetTemp('functions', temp); 18662 } 18663 18664 /* Store Classes in Temp */ 18665 else if(item.Get('extension') === 'class') 18666 { 18667 let temp = files.GetTemp('classes', {}); 18668 18669 temp[item.Get('name')] = item.Get('id'); 18670 18671 files.SetTemp('classes', temp); 18672 } 18673 18674 /* Store Modules in Temp */ 18675 else if(item.Get('extension') === 'module') 18676 { 18677 let temp = files.GetTemp('modules', {}); 18678 18679 temp[item.Get('name')] = item.Get('id'); 18680 18681 files.SetTemp('modules', temp); 18682 } 18683});files.FunctionCreate('item.run', function(addon, item, data = {}) 18684{ 18685 let fn = item.Get('function'); 18686 18687 try 18688 { 18689 return fn(data); 18690 } 18691 catch(error) 18692 { 18693 console.error(`Item: ${item.Get('name')} - File: ${item.Get('event')} - Error: ${error}`); 18694 } 18695});mdFunctions.ItemsAdd({ 18696 id: 'function', 18697 group: 'Files', 18698 description: 'Call a custom function built using files module.', 18699 parameters: { 18700 name: {type: 'INPUT', 'value': ''}, 18701 }, 18702 function: function(data, value, name) 18703 { 18704 return $dh.Fn(name, {data, value}); 18705 } 18706});files.ItemsKeys(['extension', 'event', 'name', 'code']); 18707 18708files.FieldAdd('extension'); 18709files.FieldAdd('code'); 18710files.FieldAdd('name'); 18711 18712files.FieldAdd('event', function(value, item) 18713{ 18714 if(item.data.extension !== 'js') 18715 { 18716 return null; 18717 } 18718 18719 if(value === 'onPageReady') 18720 { 18721 return 'dh.pages.ready'; 18722 } 18723 else if(value === 'onPageScroll') 18724 { 18725 return 'dh.scroll'; 18726 } 18727 else if(value === 'onReady') 18728 { 18729 return 'dh.ready'; 18730 } 18731 else if(value === 'onClick') 18732 { 18733 return 'dh.click'; 18734 } 18735 18736 return value ? value : 'dh.ready'; 18737}); 18738 18739files.FieldAdd('function', function(value, item) 18740{ 18741 if(typeof value !== 'function') 18742 { 18743 item.data.function = value = new Function('data', item.data.code); 18744 } 18745 18746 return value; 18747});files.OnItemAdd(function(addon, item) 18748{ 18749 /* Run Plugins */ 18750 if(item.Get('extension') === 'plugin' && $dh.query('plugins') !== 'off') 18751 { 18752 setTimeout(() => 18753 { 18754 item.Fn('run', {mode: 'website'}); 18755 }); 18756 } 18757 18758 /* Run Events */ 18759 if(item.Get('extension') === 'js') 18760 { 18761 mdEvents.Fn('catch', item.Get('event'), (data) => 18762 { 18763 item.Fn('run', data); 18764 }); 18765 } 18766}); 18767components.FunctionCreate('cache.clear', function(components, tag = null) 18768{ 18769 if(tag === null) 18770 { 18771 return components.SetTemp('cache.source', null); 18772 } 18773
18774 let cache = components.GetTemp('cache.source', {}); 18775 18776 delete cache[tag.Get('id')]; 18777 components.SetTemp('cache.source', cache); 18778});mdActions.OnReady(() => 18779{ 18780 18781}); 18782mdEvents.ItemsAdd({ 18783 id: 'dh.components.render', 18784 group: 'builder', 18785 name: 'On Components Render', 18786 description: 'Executes each time a component has been rendered.', 18787 role: ['builder'] 18788});components.tags = Addons.create(components.GetName() + '.tags', function(addon) 18789{ 18790 addon.ItemsKeys(['id', 'component', 'name', 'parent', 'symbol', 'loop', 'text', 'attributes', 'classes', 'source', 'iterable', 'conditions', 'order', 'properties', 'interactions']); 18791 18792 addon.FieldAdd('temp', false, function() 18793 { 18794 return {}; 18795 }); 18796 18797 addon.FieldAdd('component'); 18798 addon.FieldAdd('parent'); 18799 addon.FieldAdd('symbol'); 18800 addon.FieldAdd('order'); 18801 addon.FieldAdd('loop'); 18802 addon.FieldAdd('name'); 18803 addon.FieldAdd('source'); 18804 addon.FieldAdd('iterable'); 18805 addon.FieldAdd('attributes'); 18806 addon.FieldAdd('classes'); 18807 addon.FieldAdd('conditions'); 18808 addon.FieldAdd('properties', null, function(value, item) 18809 { 18810 if(!value || typeof value !== 'object') 18811 { 18812 return {}; 18813 } 18814 18815 return value; 18816 }); 18817 18818 addon.FieldAdd('interactions'); 18819 addon.FieldAdd('data'); 18820 18821 addon.FieldAdd('text', function(value, item) 18822 { 18823 let language = $dh.language(); 18824 18825 if(value && typeof value === 'object' && language.active in value) 18826 { 18827 return value[language.active]; 18828 } 18829 18830 return {}; 18831 }); 18832 18833 addon.FieldAdd('Block', function(value, item) 18834 { 18835 return blocks.ItemGet(item.Get('name', false)); 18836 }); 18837 18838 addon.FieldAdd('Symbol', function(value, item) 18839 { 18840 if(item.Get('symbol')) 18841 { 18842 return components.ItemGet(item.Get('symbol')); 18843 } 18844 }); 18845 18846 addon.FieldAdd('Component', function(value, item) 18847 { 18848 if(item.Get('component')) 18849 { 18850 return components.ItemGet({id: item.Get('component')}); 18851 } 18852 }); 18853 18854 addon.FieldAdd('Parent', function(value, item) 18855 { 18856 if(item.Get('parent')) 18857 { 18858 return addon.ItemGet({id: item.Get('parent')}); 18859 } 18860 }); 18861 18862 addon.FieldAdd('parents', function(value, item) 18863 { 18864 let parents = []; 18865 18866 while(item.Get('parent')) 18867 { 18868 parents.push( item.Get('parent')); 18869 item = item.Get('Parent'); 18870 } 18871 18872 return parents; 18873 }); 18874 18875 addon.FieldAdd('Parents', function(value, item) 18876 { 18877 let parents = []; 18878 18879 while(item.Get('parent')) 18880 { 18881 parents.push(item.Get('Parent')); 18882 item = item.Get('Parent'); 18883 } 18884 18885 return parents; 18886 }); 18887 18888 addon.FieldAdd('tags', null, function(value, item) 18889 { 18890 if(!value || typeof value !== 'object') 18891 { 18892 return {}; 18893 } 18894 18895 return value; 18896 }); 18897});$(window).on('scroll', function () 18898{ 18899 $('.dh-lazy').each(function() 18900 { 18901 components.tags.Fn('lazy', $(this)); 18902 }); 18903}); 18904 18905mdEvents.Fn('catch', 'dh.pages.ready', () => 18906{ 18907 $('.dh-lazy').each(function() 18908 { 18909 components.tags.Fn('lazy', $(this)); 18910 }); 18911});mdEvents.Fn('catch', 'dh.pages.unload', () => 18912{ 18913 components.SetTemp('cache.source', null); 18914});components.tags.FunctionCreate('lazy', (addon, target) => 18915{ 18916 let tag = components.tags.ItemGet(target.attr('data-id')); 18917 18918 if(!tag) 18919 { 18920 return; 18921 } 18922 18923 if(dh.inView(target, -100)) 18924 { 18925 if(target.attr('dh-lazy') === 'loaded') 18926 { 18927 return true; 18928 } 18929 18930 let html = $(tag.Fn('render.single', {symbols: []}, null)); 18931 18932 target.replaceWith(html); 18933 18934 html.addClass('dh-lazy'); 18935 18936 html.attr('dh-lazy', 'loaded'); 18937 html.attr('data-id', tag.Get('id')); 18938 18939 transform.Fn('run', {mode: 'website'}); 18940 } 18941 else 18942 { 18943 if(target.attr('dh-lazy') !== 'loaded') 18944 { 18945 return true; 18946 } 18947 18948 let height = target.outerHeight(); 18949 let html = $(`<section class="dh-lazy" data-id="${tag.Get('id')}" style="height: 10px; opacity: 0"></section>`); 18950 18951 target.replaceWith(html); 18952 18953 html.css('height', height); 18954 } 18955});components.tags.OnItemAdd(function(tags, item, type) 18956{ 18957 if(item.Get('parent')) 18958 { 18959 item.Get('Parent').FieldAdd('tags', item); 18960 } 18961 else 18962 { 18963 item.Get('Component').FieldAdd('tags', item); 18964 } 18965});components.tags.OnItemRemove(function(tags, item) 18966{ 18967 if(item.Get('parent')) 18968 { 18969 item.Get('Parent').FieldRemove('tags', item); 18970 } 18971 else 18972 { 18973 item.Get('Component').FieldRemove('tags', item); 18974 } 18975 18976 /* Remove Child Tags */ 18977 $.each(item.Get('tags'), function(id, child) 18978 { 18979 child.Remove(); 18980 }); 18981});components.tags.FunctionCreate('item.attributes.main.get', function(tags, tag) 18982{ 18983 return tag.Fn('attributes.get', '1'); 18984});components.tags.FunctionCreate('item.attributes.main.get.key', function(tags, tag, key, _default = '') 18985{ 18986 return tag.Fn('attributes.get.key', key, _default, '1'); 18987});components.tags.FunctionCreate('item.attributes.get', function(tags, item, identifier = null) 18988{ 18989 if(!identifier) 18990 { 18991 identifier = '1'; 18992 } 18993 18994 if(identifier in item.data.attributes) 18995 { 18996 return item.data.attributes[identifier]; 18997 } 18998 18999 return {}; 19000});components.tags.FunctionCreate('item.attributes.get.key', function(tags, item, key, _default = '', identifier = null) 19001{ 19002 if(!identifier) 19003 { 19004 identifier = '1'; 19005 } 19006 19007 if(!(identifier in item.data.attributes)) 19008 { 19009 return _default; 19010 } 19011 19012 if(key in item.data.attributes[identifier]) 19013 { 19014 return item.data.attributes[identifier][key]; 19015 } 19016 19017 return _default; 19018});components.tags.FunctionCreate('item.classes.get', function(tags, tag, items = false, identifier = null) 19019{ 19020 if(!identifier) 19021 { 19022 identifier = '1'; 19023 } 19024 19025 if(!(identifier in tag.data.classes)) 19026 { 19027 return []; 19028 } 19029 19030 if(!items) 19031 { 19032 return tag.data.classes[identifier]; 19033 } 19034 19035 return tag.data.classes[identifier]; 19036});components.tags.FunctionCreate('item.classes.main.get', function(tags, tag, items = false) 19037{ 19038 return tag.Fn('classes.get', items, '1'); 19039});components.tags.FunctionCreate('item.render.children', function(tags, tag, data, loop) 19040{ 19041 let html = ''; 19042 let array = $.map(tag.Get('tags'), function(value) { return [value]; }); 19043 19044 if(loop !== null) 19045 { 19046 loop = parseInt(loop + '00'); 19047 } 19048 19049 array.sort((a, b) => a.Get('order') - b.Get('order')); 19050 array.forEach((children) => 19051 { 19052 html += children.Fn('render.html', data, loop); 19053 }); 19054 19055 return html; 19056});components.tags.FunctionCreate('item.render.close', function(tags, tag, data) 19057{ 19058 if(['embed', 'body', 'rich-text', 'form-widget', 'membership-form-widget'].includes(tag.Get('name', false))) 19059 { 19060 return '</div>'; 19061 } 19062 19063 return `</${tag.Get('name')}>`; 19064});components.tags.FunctionCreate('item.render.html', function(tags, tag, data = {}, loop = null) 19065{ 19066 if(!('symbols' in data)) 19067 { 19068 data.symbols = []; 19069 } 19070 19071 let html = ''; 19072 19073 if(!$dh.prerender() && tag.Get('name') === 'section') 19074 { 19075 let properties = tag.Get('properties'); 19076 19077 if('lazy' in properties && properties.lazy) 19078 { 19079 return `<section class="dh-lazy" data-id="${tag.Get('id')}" style="height: 10px; opacity: 0"></section>`; 19080 } 19081 } 19082
19083 let conditions = tag.Get('conditions'); 19084 19085 tag.Fn('render.repeat', data, loop, function(data, loop, loader) 19086 { 19087 if(loader) 19088 { 19089 html += loader; 19090 } 19091 else if(conditions && conditions.visibility && !mdOperators.Fn('process', conditions.visibility, data)) 19092 { 19093 html += ''; 19094 } 19095 else 19096 { 19097 html += tag.Fn('render.single', data, loop); 19098 } 19099 }); 19100 19101 return html; 19102});components.tags.FunctionCreate('item.render.open', function(tags, tag, data, loop) 19103{ 19104 this.loop = (loop === null) ? 1 : loop; 19105 this.identifier = this.loop.toString(); 19106 19107 /* Symbol */ 19108 if('symbol' in data) 19109 { 19110 this.identifier = data.symbol.Get('id') + '-' + this.identifier; 19111 } 19112 19113 this.init = () => 19114 { 19115 return `<${this.name()} ${this.attributes()}>`; 19116 }; 19117 19118 this.name = () => 19119 { 19120 if(['embed', 'body', 'rich-text', 'form-widget', 'membership-form-widget'].includes(tag.Get('name', false))) 19121 { 19122 return 'div'; 19123 } 19124 19125 return tag.Get('name'); 19126 }; 19127 19128 this.attributes = (attributes = {}) => 19129 { 19130 if(this.identifier !== '1') 19131 { 19132 $.each(tag.Fn('attributes.main.get'), (key, value) => 19133 { 19134 attributes[key] = mdVars.Fn('process', value, data); 19135 }); 19136 } 19137 19138 /* Non Symbol Attributes */ 19139 if('symbol' in data) 19140 { 19141 $.each(tag.Fn('attributes.get', this.identifier.split('-')[1]), (key, value) => 19142 { 19143 attributes[key] = mdVars.Fn('process', value, data); 19144 }); 19145 } 19146 19147 /* Add Source Attribute for Dynamic Filtering */ 19148 let source = tag.Get('source'); 19149 if(source && source.id) 19150 { 19151 attributes['dh-source'] = source.id; 19152 19153 // Save dynamic filters config if exists 19154 if(source.dynamicFilters && source.dynamicFilters.length) 19155 { 19156 let configId = 'filters.' + source.id; 19157 mdSources.SetTemp(configId, { 19158 dynamicFilters: source.dynamicFilters, 19159 dynamicFiltersMode: source.dynamicFiltersMode || 'change', 19160 dynamicFiltersTriggerButton: source.dynamicFiltersTriggerButton || null, 19161 pagination: source.pagination // Include pagination here too 19162 }); 19163 19164 attributes['dh-filter-config'] = configId; 19165 } 19166 19167 // Save pagination config separately if no filters 19168 if(source.pagination && source.pagination.enabled && (!source.dynamicFilters || !source.dynamicFilters.length)) 19169 { 19170 let paginationConfigId = 'pagination.' + source.id; 19171 mdSources.SetTemp(paginationConfigId, source.pagination); 19172 19173 attributes['dh-pagination-config'] = paginationConfigId; 19174 } 19175 } 19176 19177 $.each(tag.Fn('attributes.get', this.identifier), (key, value) => 19178 { 19179 attributes[key] = mdVars.Fn('process', value, data); 19180 }); 19181 19182 this.attributesLive(attributes); 19183 19184 /* Handle Page */ 19185 if(tag.Get('name') === 'a') 19186 { 19187 if('page-id' in attributes) 19188 { 19189 let page = pages.ItemGet(attributes['page-id']); 19190 19191 if(page) 19192 { 19193 let vars = dh.query(attributes['dh-vars'] ? ('?' + attributes['dh-vars']) : '', (query) => 19194 { 19195 query.value = mdVars.Fn('process', query.value, data); 19196 }); 19197 19198 let query = dh.query(attributes['dh-query'] ? ('?' + attributes['dh-query']) : '', (query) => 19199 { 19200 query.value = mdVars.Fn('process', query.value, data); 19201 }); 19202 19203 attributes['href'] = page.Fn('route.build', vars, query); 19204 } 19205 19206 delete attributes['dh-vars']; 19207 delete attributes['dh-query']; 19208 } 19209 } 19210 19211 /* Transform */ 19212 if('dh-transform-options' in attributes) 19213 { 19214 let options = String(attributes['dh-transform-options']).split('|'); 19215 19216 $.each(options, (index, option) => 19217 { 19218 option = option.split(':'); 19219 19220 let id = option[0]; option.shift(); 19221 let value = mdVars.Fn('process', option.join(':'), data); 19222 19223 if(typeof value === 'string') 19224 { 19225 value = value.replace(/\|/g, ''); 19226 } 19227 19228 options[index] = id + ':' + value; 19229 }); 19230 19231 attributes['dh-transform-options'] = options.join('|'); 19232 } 19233 19234 /* Component */ 19235 if(tag.Get('symbol')) 19236 { 19237 attributes['component'] = tag.Get('symbol'); 19238 } 19239 19240 /* Classes */ 19241 this.classes(attributes); 19242 19243 return dh.attributes(attributes); 19244 }; 19245 19246 this.attributesLive = (attributes) => 19247 { 19248 /* IMG Lazy */ 19249 if(tag.Get('name') === 'img') 19250 { 19251 if(!('loading' in attributes)) 19252 { 19253 attributes['loading'] = 'lazy'; 19254 } 19255 19256 if(!('alt' in attributes)) 19257 { 19258 attributes['alt'] = tag.Get('label'); 19259 } 19260 } 19261 19262 return attributes; 19263 }; 19264 19265 this.classes = (attributes, classes = []) => 19266 { 19267 classes = this.classesLive(classes); 19268 19269 if('href' in attributes && window.location.href.replace(window.location.origin, '') === attributes['href']) 19270 { 19271 classes.push('dh-active'); 19272 } 19273 19274 if(tag.Get('name') === 'embed') 19275 { 19276 classes.push('dh-embed'); 19277 } 19278 else if(tag.Get('name') === 'body') 19279 { 19280 classes.push('dh-body'); 19281 } 19282 else if(tag.Get('name') === 'rich-text') 19283 { 19284 classes.push('dh-rich-text'); 19285 } 19286 else if(tag.Get('name') === 'form-widget') 19287 { 19288 classes.push('dh-form-widget'); 19289 } 19290 else if(tag.Get('name') === 'membership-form-widget') 19291 { 19292 classes.push('dh-form-widget'); 19293 classes.push('dh-membership-form-widget'); 19294 } 19295 19296 if('class' in attributes) 19297 { 19298 attributes['class'] = (classes.length ? classes.join(' ') + ' ' : '') + attributes['class']; 19299 } 19300 else if(classes.length) 19301 { 19302 attributes['class'] = classes.join(' '); 19303 } 19304 }; 19305 19306 this.classesLive = (classes = []) => 19307 { 19308 classes.push('t' + tag.Get('id')); 19309 19310 if(this.identifier !== '1') 19311 { 19312 $.each(tag.Fn('classes.get', true, this.identifier), (key, value) => 19313 { 19314 classes.push(value); 19315 }); 19316 } 19317 19318 /* Non Symbol Classes */ 19319 if('symbol' in data) 19320 { 19321 $.each(tag.Fn('classes.get', this.identifier.split('-')[1]), (key, value) => 19322 { 19323 classes.push(value); 19324 }); 19325 } 19326 19327 $.each(tag.Fn('classes.main.get', true), (key, value) => 19328 { 19329 classes.push(value); 19330 }); 19331 19332 return classes; 19333 }; 19334 19335 return this; 19336});components.tags.FunctionCreate('item.render.repeat', function(tags, tag, data, loop, c
19336allback) 19337{ 19338 this.repeat = 1; 19339 this.loader = null; 19340 this.find = false; 19341 this.key = 'value'; 19342 this.data = []; 19343 19344 this.init = () => 19345 { 19346 if(!this.find) 19347 { 19348 this.source(); 19349 } 19350 19351 if(!this.find) 19352 { 19353 this.iterable(); 19354 } 19355 19356 if(!this.find) 19357 { 19358 this.repeat = tag.Get('loop'); 19359 } 19360 19361 this.each(); 19362 }; 19363 19364 this.source = () => 19365 { 19366 let src = tag.Get('source'); 19367 19368 if(!src) 19369 { 19370 return; 19371 } 19372 19373 let cache = components.GetTemp('cache.source', {}); 19374 19375 this.key = 'key' in src ? (src.key ? src.key : 'value') : 'value'; 19376 19377 if('source' in tag.data.temp) 19378 { 19379 if(typeof tag.data.temp.source === 'object' && Array.isArray(tag.data.temp.source) === true) 19380 { 19381 this.data = tag.data.temp.source; 19382 this.repeat = this.data.length; 19383 } 19384 else if(typeof tag.data.temp.source === 'object') 19385 { 19386 this.data.push(tag.data.temp.source); 19387 this.repeat = 1; 19388 } 19389 19390 tag.data.temp['source.temp'] = true; 19391 19392 this.find = true; 19393 return true; 19394 } 19395 19396 if(tag.Get('id') in cache) 19397 { 19398 if(Array.isArray(cache[tag.Get('id')])) 19399 { 19400 this.data = cache[tag.Get('id')]; 19401 this.repeat = this.data.length; 19402 } 19403 else if (typeof cache[tag.Get('id')] === 'object') 19404 { 19405 this.data.push(cache[tag.Get('id')]); 19406 this.repeat = 1 19407 } 19408 19409 this.find = true; 19410 return true; 19411 } 19412 19413 let id = dh.increaser('source'); 19414 19415 this.find = true; 19416 this.loader = dh.loader(id, 6); 19417 19418 let fetchSource = () => 19419 { 19420 $dh.source(src.id, src.properties, src.filters, src.sort, data, function(json) 19421 { 19422 if('repeat' in src && !src.repeat) 19423 { 19424 json = [{items: json, count: json.length}]; 19425 } 19426 19427 cache[tag.Get('id')] = json; 19428 components.SetTemp('cache.source', cache); 19429 19430 let target = $(`.dh-loader-${id}`); 19431 19432 $(target).replaceWith(tag.Fn('render.html', data, loop)); 19433 transform.Fn('run', {mode: 'website'}); 19434 }); 19435 }; 19436 19437 src.delay ? setTimeout(fetchSource, src.delay) : fetchSource(); 19438 }; 19439 19440 this.iterable = () => 19441 { 19442 let iterable = tag.Get('iterable') 19443 19444 if(!iterable) 19445 { 19446 return; 19447 } 19448 19449 let command = dh.command(iterable); 19450 19451 if(command.options.key && '0' in command.options.key) 19452 { 19453 this.key = command.options.key[0]; 19454 } 19455 19456 iterable = mdVars.Fn('process', command.name, data); 19457 19458 if(typeof iterable === 'object' && Array.isArray(iterable) === true) 19459 { 19460 this.data = iterable; 19461 this.repeat = this.data.length; 19462 } 19463 else if (typeof iterable === 'object') 19464 { 19465 this.data.push(iterable); 19466 this.repeat = 1 19467 } 19468 19469 this.find = true; 19470 return true; 19471 }; 19472 19473 this.each = () => 19474 { 19475 let i = 1; 19476 19477 if(this.data.length) 19478 { 19479 while(i <= this.repeat) 19480 { 19481 let extend = {repeat: true}; 19482 19483 extend[this.key + '_index'] = i - 1; 19484 extend[this.key] = this.data[i -1]; 19485 19486 callback($.extend(extend, data), '1', this.loader); 19487 loop++; i++; 19488 } 19489 } 19490 else 19491 { 19492 if(data.repeat === true) 19493 { 19494 loop = '1'; 19495 } 19496 else if(this.repeat > 1) 19497 { 19498 loop = (loop === null) ? 1 : loop; 19499 } 19500 19501 while(i <= this.repeat) 19502 { 19503 callback(data, loop, this.loader); 19504 loop++; i++; 19505 } 19506 } 19507 }; 19508 19509 return this; 19510});components.tags.FunctionCreate('item.render.single', function(tags, tag, data, loop) 19511{ 19512 let html = ''; 19513 19514 tag.data.data = data; 19515 19516 if(tag.Get('symbol')) 19517 { 19518 html += tag.Fn('render.open', data, loop); 19519 html += tag.Fn('render.symbol', $.extend({}, data), loop); 19520 } 19521 else if(dh.count(tag.Get('tags'))) 19522 { 19523 html += tag.Fn('render.open', data, loop); 19524 html += tag.Fn('render.children', $.extend({}, data), loop); 19525 } 19526 else 19527 { 19528 html += tag.Fn('render.open', data, loop); 19529 html += tag.Fn('render.text', data, loop); 19530 } 19531 19532 return html + tag.Fn('render.close', data, loop); 19533});components.tags.FunctionCreate('item.render.symbol', function(tags, tag, data, loop) 19534{ 19535 let symbol = tag.Get('Symbol'); 19536 let properties = tag.Get('properties'); 19537 19538 if(symbol.Get('variables')) 19539 { 19540 let identifier = (loop === null) ? '1' : loop; 19541 19542 /* Symbol */ 19543 if('symbol' in data) 19544 { 19545 identifier = data.symbol.Get('id') + '-' + identifier; 19546 } 19547 19548 /* Get */ 19549 let text = tag.Fn('text.get', null, identifier); 19550 19551 try 19552 { 19553 let merge = mdVars.Fn('merge', symbol.Get('variables'), JSON.parse(text), data); 19554 data.widget = 'widget' in data ? $.extend(data.widget, merge) : merge; 19555 } 19556 catch(error) 19557 { 19558 data.widget = 'widget' in data ? data.widget : {}; 19559 } 19560 } 19561 19562 if(!('instance' in properties) || properties.instance) 19563 { 19564 if(!('symbol' in data)) 19565 { 19566 data.symbol = tag; 19567 } 19568 } 19569 19570 if(data.symbols.includes(tag.Get('symbol'))) 19571 { 19572 return ''; 19573 } 19574 19575 if(data.symbols.length > 500) 19576 { 19577 return 'Failed to render component.'; 19578 } 19579 19580 data.symbols.push(tag.Get('symbol')); 19581 19582 let result = symbol.Fn('render.html', data, loop, false); 19583 19584 data.symbols.pop(); 19585 19586 return result; 19587});components.tags.FunctionCreate('item.render.text', function(tags, tag, data, loop, wrap = true) 19588{ 19589 /* Block */ 19590 let block = tag.Get('Block'); 19591 19592 /* Can Have Text? */ 19593 if(!block.Get('text')) { return ''; } 19594 19595 /* Loop */ 19596 loop = (loop === null) ? 1 : loop; 19597 19598 /* Identifier */ 19599 let identifier = loop; 19600 19601 /* Symbol */ 19602 if('symbol' in data) 19603 { 19604 identifier = data.symbol.Get('id') + '-' + identifier; 19605 } 19606 19607 /* Get */ 19608 let text = tag.Fn('text.get', null, identifier, false); 19609 19610 /* Non Symbol Text */ 19611 if(text === null && 'symbol' in data) 19612 { 19613 text = tag.Fn('text.get', null, identifier.split('-')[1], false); 19614 } 19615 19616 /* Main Text */ 19617 if(text === null && identifier !== '1') 19618 { 19619 text = tag.Fn('text.main.get', null, false); 19620 } 19621 19622 /* Var */ 19623 text = mdVars.Fn('process', text, data); 19624 19625 /* Generate */ 19626 let generate = (text) => 19627 { 19628 /* Embed / Form Widget / Membership Form Widget */ 19629 if(block.Get('name') === 'embed' || block.Get('name') === 'form-widget' || block.Get('name') === 'membership-form-widget') 19630 { 19631 return text !== null ? text : ''; 19632 } 19633 /* Rich Text */ 19634 else if(block.Get('name') === 'rich-text') 19635 { 19636 if(text !== null && text[0] !== '$') 19637 { 19638 try { text = JSON.parse(text); } catch(error) { console.log(error); } 19639 } 19640 19641 if(!Array.isArray(text)) 19642 { 19643 return 'Invalid rich-text data.'; 19644 } 19645 19646 return mdLibraries.Fn('editorjs.html', text); 19647 } 19648 /* Form */ 19649 else if(block.Get('name') === 'form') 19650 { 19651 if(text !== null) 19652 { 19653 try { text = JSON.parse(text); } catch(error) { console.log(error); } 19654 } 19655 19656 console.log(text); 19657 19658 if(!text || typeof text !== 'object') 19659 { 19660 return 'Invalid form configuration.'; 19661 } 19662 19663 if(text.type === 'function') 19664 { 19665 text.value = $dh.Fn(text.value); 19666 } 19667 19668 return elements.Render('form', { 19669 config: text.value 19670 }); 19671 } 19672 19673 return (text !== null ? dh.sanitize(text, true) : ''); 19674 }; 19675 19676 /* Promise */ 19677 if(text instanceof Promise) 19678 { 19679 let id = dh.increaser('promise', 100000); 19680 19681 text.then((response) => 19682 { 19683 if(response && (typeof response === 'string' || typeof response === 'number')) 19684 { 19685 $('.dh-loader-dh-promise-' + id).replaceWith(generate(response)); 19686 } 19687 else 19688 { 19689 $('.dh-loader-dh-promise-' + id).replaceWith('Expected string or number. Got: ' + typeof response); 19690 } 19691 }); 19692 19693 return dh.loader('dh-promise-' + id, 1); 19694 } 19695 19696 return generate(text); 19697});components.tags.FunctionCreate('item.text.get', function(tags, item, _default = '', identifier = null) 19698{ 19699 if(identifier === null) 19700 { 19701 identifier = '1'; 19702 } 19703 19704 let text = item.Get('text'); 19705 19706 if(identifier in text) 19707 { 19708 return text[identifier]; 19709 } 19710 19711 return _default; 19712});components.tags.FunctionCreate('item.text.main.get', function(tags, tag, _default = '', sanitize = true) 19713{ 19714 return tag.Fn('text.get', _default, '1', sanitize); 19715});components.ItemsKeys(['id', 'variables', 'name']); 19716 19717components.FieldAdd('variables'); 19718components.FieldAdd('name'); 19719 19720components.FieldAdd('tags', null, function(value, item) 19721{ 19722 if(!value || typeof value !== 'object') 19723 { 19724 return {}; 19725 } 19726 19727 return value; 19728});$dh.Component = (name, data = {}, render = true, wrap = false) => 19729{ 19730 if(typeof name !== 'string') 19731 { 19732 return null; 19733 } 19734
19735 let component = Object.values(components.ItemsGet()).find(object => object.Get('name') === name); 19736 19737 if(!component || !render) 19738 { 19739 return component; 19740 } 19741 19742 return component.Fn('render.html', data, null, wrap); 19743};builder.OnReady(function() 19744{ 19745 components.Ready(); 19746});components.FunctionCreate('item.remove.tags', (addon, item) => 19747{ 19748 $.each(item.Get('tags'), (item, tag) => 19749 { 19750 tag.Remove(); 19751 }); 19752});components.FunctionCreate('item.render.html', (addon, item, data = {}, loop = null, main = true) => 19753{ 19754 if(!('symbols' in data)) 19755 { 19756 data.symbols = []; 19757 } 19758 19759 let html = ''; 19760 let array = $.map(item.Get('tags'), function(value) { return [value]; }); 19761 19762 array.sort((a, b) => a.Get('order') - b.Get('order')); 19763 array.forEach((tag) => 19764 { 19765 html += tag.Fn('render.html', data, loop); 19766 }); 19767 19768 if(!main) 19769 { 19770 return html; 19771 } 19772 19773 return `<main component="${item.Get('id')}">${html}</main>`; 19774}); 19775mdActions.OnReady(() => 19776{ 19777 mdActions.ItemAdd({ 19778 id: 'page', 19779 name: 'Page', 19780 description: 'Change page', 19781 icon: 'https://global.divhunt.com/61d0d3555152071583621560481d0217_855.svg', 19782 config: () => 19783 { 19784 return { 19785 'dh-page': { 19786 label: 'Page', 19787 description: 'Select page to open', 19788 width: 4, 19789 type: 'SELECT', 19790 values: () => 19791 { 19792 return pages.Fn('list'); 19793 }, 19794 }, 19795 query: { 19796 type: 'REPEATER', 19797 width:4, 19798 order: 20, 19799 description: 'Add URL parameters', 19800 label: 'Queries', 19801 style: '', 19802 config: { 19803 name: { 19804 type: 'INPUT', 19805 width: 6, 19806 placeholder: 'Name' 19807 }, 19808 value: { 19809 type: 'INPUT', 19810 width: 6, 19811 placeholder: 'Value' 19812 } 19813 }, 19814 onEdit: () => {}, 19815 onRemove: () => {}, 19816 onAdd: (event) => 19817 { 19818 event.edit(); 19819 }, 19820 }, 19821 vars: { 19822 type: 'REPEATER', 19823 order: 20, 19824 width:4, 19825 description: 'Set page dynamic variables', 19826 label: 'Variables', 19827 style: '', 19828 config: { 19829 name: { 19830 type: 'INPUT', 19831 width: 6, 19832 placeholder: 'Name' 19833 }, 19834 value: { 19835 type: 'INPUT', 19836 width: 6, 19837 placeholder: 'Value' 19838 } 19839 }, 19840 onEdit: () => {}, 19841 onRemove: () => {}, 19842 onAdd: (event) => 19843 { 19844 event.edit(); 19845 }, 19846 }, 19847 }; 19848 }, 19849 code: (data, resolve, reject) => 19850 { 19851 pages.ItemGet(data.values['dh-page'], (page) => 19852 { 19853 let query = {}; 19854 let vars = {}; 19855 19856 if(Array.isArray(data.values.query)) 19857 { 19858 data.values.query.forEach((item) => 19859 { 19860 query[item.name] = item.value; 19861 }) 19862 } 19863 19864 if(Array.isArray(data.values.vars)) 19865 { 19866 data.values.vars.forEach((item) => 19867 { 19868 vars[item.name] = item.value; 19869 }) 19870 } 19871
19872 page.Fn('load', vars, query); 19873 resolve(); 19874 }); 19875 } 19876 }); 19877}); 19878mdEvents.ItemsAdd({ 19879 group: 'pages', 19880 id: 'dh.pages.unload', 19881 name: 'On Page Unload', 19882 description: 'Executes when the page is being unloaded.', 19883 debug: (item, data) => 19884 { 19885 return [ 19886 'Hit' 19887 ]; 19888 }, 19889 config: () => 19890 { 19891 return { 19892 delay: { 19893 type: 'INPUT', 19894 units: ['ms'], 19895 width: 12, 19896 label: 'Delay', 19897 value: '0ms', 19898 } 19899 } 19900 }, 19901 role: ['website'], 19902}, { 19903 group: 'pages', 19904 id: 'dh.pages.preload', 19905 name: 'On Page Preload', 19906 description: 'Executes just before the page starts loading.', 19907 debug: (item, data) => 19908 { 19909 return [ 19910 'Hit' 19911 ]; 19912 }, 19913 role: ['website'], 19914}, { 19915 group: 'pages', 19916 id: 'dh.pages.ready', 19917 name: 'On Page Ready', 19918 description: 'Executes when the page is ready for interaction.', 19919 debug: (item, data) => 19920 { 19921 return [ 19922 'Hit' 19923 ]; 19924 }, 19925 role: ['website'], 19926}, { 19927 group: 'pages', 19928 id: 'dh.pages.load', 19929 name: 'On Page Load', 19930 description: 'Executes after the page has fully loaded.', 19931 debug: (item, data) => 19932 { 19933 return [ 19934 'Hit' 19935 ]; 19936 }, 19937 debug: (item, data) => 19938 { 19939 return [data.page.Get('name')]; 19940 }, 19941 role: ['website'], 19942});pages.ItemsKeys(['id', 'component', 'route', 'source', 'title', '404', 'order', 'name', 'domains']); 19943 19944pages.FieldAdd('notFound'); 19945pages.FieldAdd('fetch'); 19946pages.FieldAdd('component'); 19947pages.FieldAdd('source'); 19948pages.FieldAdd('title'); 19949pages.FieldAdd('404'); 19950pages.FieldAdd('order'); 19951pages.FieldAdd('name'); 19952pages.FieldAdd('domains', (value) => 19953{ 19954 return Array.isArray(value) ? value : []; 19955}); 19956 19957pages.FieldAdd('Component', function (value, page) 19958{ 19959 return components.ItemGet(page.Get('component')); 19960}); 19961 19962pages.FieldAdd('data', false, function() 19963{ 19964 return {}; 19965}); 19966 19967pages.FieldAdd('route', function(value) 19968{ 19969 let language = $dh.language(); 19970 let prefix = $dh.var('settings')?.['dh-prefix']; 19971 19972 if(language.active !== language.default) 19973 { 19974 value = '/' + language.active.toLowerCase() + value; 19975 } 19976 19977 if(prefix) 19978 { 19979 value = '/' + prefix + value; 19980 } 19981 19982 return value; 19983});$dh.Page = (name, vars = {}, query = {}, callback = null, load = true) => 19984{ 19985 if(typeof name !== 'string') 19986 { 19987 return null; 19988 } 19989 19990 let page = Object.values(pages.ItemsGet()).find(object => object.Get('name') === name); 19991 19992 if(!page || !load) 19993 { 19994 return page; 19995 } 19996 19997 return page.Fn('load', vars, query, false, callback); 19998}; 19999 20000$dh.Reload = () => 20001{ 20002 let page = $dh.page(); 20003 20004 if(!page) 20005 { 20006 return; 20007 } 20008 20009 pages.ItemGet(page.id, (page) => 20010 { 20011 page.Fn('load'); 20012 }); 20013};builder.OnReady(function() 20014{ 20015 /* Check Page */ 20016 let page = $dh.page(); if(!page || !pages.ItemGet(page.id)) { return; } 20017 20018 /* Get Active */ 20019 let active = pages.ItemGet(page.id); 20020 20021 /* Set Data */ 20022 active.data.data = page['data']; 20023 20024 if(page['404']) 20025 { 20026 active.Fn('load', {}, {}, true); 20027 } 20028 else 20029 { 20030 active.Fn('route.match', window.location.pathname, function(find, vars) 20031 { 20032 find.Fn('load', vars, Object.fromEntries(new URLSearchParams(window.location.search)), true); 20033 }); 20034 } 20035}); 20036 20037builder.OnReady(function() 20038{ 20039 20040 /* Check Page */ 20041 let page = $dh.page(); if(page) { return; } 20042 20043 /* Match */ 20044 let match = false; 20045 20046 $.each(pages.Fn('get.reordered'), (index, page) => 20047 { 20048 if(match) 20049 { 20050 return false; 20051 } 20052 20053 page.Fn('route.match', window.location.pathname, function(find, vars) 20054 { 20055 find.Fn('load', vars, Object.fromEntries(new URLSearchParams(window.location.search))); 20056 }); 20057 }); 20058});$(document).on('submit', 'form[dh-page]', function(event) 20059{ 20060 event.preventDefault(); 20061 20062 let page = pages.ItemGet($(this).attr('dh-page')); 20063 20064 if(page) 20065 { 20066 let query = {}; 20067 let vars = {}; 20068 20069 $.each($(this).serializeArray(), (index, field) => 20070 { 20071 if($(this).find('[name="' + field.name + '"]').attr('type') === 'hidden') 20072 { 20073 vars[field.name] = field.value; 20074 } 20075 else 20076 { 20077 query[field.name] = field.value; 20078 } 20079 }); 20080 20081 page.Fn('route.match', page.Fn('route.build', vars), (page, vars) => 20082 { 20083 let focus = { 20084 target: $(':focus'), 20085 index: null, 20086 class: null 20087 }; 20088 20089 if(focus.target.length) 20090 { 20091 focus.index = focus.target.index(); 20092 focus.class = focus.target.attr('class').split(' ')[0]; 20093 } 20094 20095 page.Fn('load', vars, query, false, (settings) => 20096 { 20097 if(focus.class) 20098 { 20099 settings.scroll = false;
20100 20101 focus.target = $('.' + focus.class).eq(focus.index - 1); 20102 focus.target.focus(); 20103 focus.value = focus.target.val(); 20104 focus.target.val(''); 20105 focus.target.val(focus.value); 20106 } 20107 }); 20108 }); 20109 } 20110});mdEvents.Fn('catch', 'dh.pages.load', () => 20111{ 20112 let hash = window.location.hash; 20113 20114 if(hash) 20115 { 20116 let element = $(hash); 20117 20118 if(element.length) 20119 { 20120 setTimeout(() => 20121 { 20122 $('html, body').animate({ 20123 scrollTop: element.offset().top 20124 }, 10); 20125 }, 100); 20126 } 20127 } 20128});$(document).on('click', 'a[page-id]', function(event) 20129{ 20130 if($(this).attr('target') === '_BLANK') 20131 { 20132 return; 20133 } 20134 20135 if($(event.target).attr('dh-spa') === 'prevent') 20136 { 20137 event.preventDefault(); 20138 return; 20139 } 20140 20141 if ($dh.var(`settings->get('dh-mpa')`)) { 20142 return; 20143 } 20144 20145 let page = pages.ItemGet($(this).attr('page-id')); 20146 20147 if(page) 20148 { 20149 page.Fn('route.match', $(this).attr('href').split('?')[0], (page, vars) => 20150 { 20151 page.Fn('load', vars, dh.query($(this).attr('href'))); 20152 20153 event.preventDefault(); 20154 event.stopPropagation(); 20155 }); 20156 } 20157 20158 event.preventDefault(); 20159}); 20160 20161$(document).on('click', 'a:not([page-id])', function(event) 20162{ 20163 if($(this).attr('target') === '_BLANK') 20164 { 20165 return; 20166 } 20167 20168 if($(event.target).attr('dh-spa') === 'prevent') 20169 { 20170 event.preventDefault(); 20171 return; 20172 } 20173 20174 if ($dh.var(`settings->get('dh-mpa')`)) { 20175 return; 20176 } 20177 20178 let href = $(this).attr('href'); 20179 let find = false; 20180 20181 if(!href?.startsWith('/')) 20182 { 20183 return; 20184 } 20185 20186 $.each(pages.Fn('get.reordered'), (index, page) => 20187 { 20188 if(find) 20189 { 20190 return false; 20191 } 20192 20193 page.Fn('route.match', href, function(page, vars) 20194 { 20195 page.Fn('load', vars, dh.query(href)); 20196 20197 event.preventDefault(); 20198 event.stopPropagation(); 20199 20200 find = true; 20201 }); 20202 }); 20203});pages.FunctionCreate('get.reordered', () => 20204{ 20205 let array = $.map(pages.ItemsGet(), function(value) { return [value]; }); 20206 20207 array.sort((a, b) => a.Get('order') - b.Get('order')); 20208 20209 return array; 20210});pages.FunctionCreate('item.fetch', function(pages, page, then = null) 20211{ 20212 if(page.Get('fetch')) 20213 { 20214 page.Set('fetch', false); 20215 then(); return; 20216 } 20217 20218 let site = $dh.site(); 20219 20220 if($dh.client()['admin']) 20221 { 20222 then(); return; 20223 } 20224 20225 $.get(`https://divhunt-site.b-cdn.net/w/${site.id}/${site.backup}/${page.Get('id')}.js`, (data) => 20226 { 20227 data = data.substring(30); 20228 data = data.slice(0, -1); 20229 20230 try 20231 { 20232 components.tags.ItemsArrayAdd(JSON.parse(data)); 20233 then(); 20234 } 20235 catch(error) 20236 { 20237 console.log('Failed to fetch page.') 20238 } 20239 }); 20240});pages.FunctionCreate('item.load', function(pages, page, vars = {}, query = {}, first = false, callback = null) 20241{ 20242 this.settings = { 20243 scroll: true, 20244 unload: null, 20245 vars, 20246 query, 20247 first, 20248 prevent: false, 20249 stop: () => 20250 { 20251 this.settings.prevent = true; 20252 }, 20253 continue: () => 20254 { 20255 this.vars(); 20256 this.query(); 20257 20258 page.Fn('fetch', () => 20259 { 20260 this.render(() => 20261 { 20262 if(callback) 20263 { 20264 callback(this.settings); 20265 } 20266 20267 this.scroll(); 20268 }); 20269 }); 20270 } 20271 }; 20272 20273 this.init = () => 20274 { 20275 if(first) 20276 { 20277 page.Set('fetch', true); 20278 } 20279 20280 if(!first) 20281 { 20282 this.eventUnload(); 20283 } 20284 20285 this.eventPreload(); 20286 20287 if(this.settings.prevent) 20288 { 20289 return true; 20290 } 20291 20292 this.settings.continue(); 20293 }; 20294 20295 this.vars = () => 20296 { 20297 mdVars.ItemAdd({id: 'route', value: vars}); 20298 }; 20299 20300 this.query = () => 20301 { 20302 mdVars.ItemAdd({id: 'query', value: query}); 20303 }; 20304 20305 this.render = (then) => 20306 { 20307 page.Fn('render', vars, query, first, (html) => 20308 { 20309 $('body').removeAttr('class'); 20310 $('body').removeAttr('style'); 20311 20312 $('body > div#dh-website').html(html); 20313 dh.trigger('dh/pages/load', 'page, vars, query', page, vars, query, false); 20314 20315 this.eventReady(); 20316 this.eventLoad(); 20317 20318 then(); 20319 }); 20320 }; 20321 20322 this.scroll = () => 20323 { 20324 if(!this.settings.scroll) 20325 { 20326 return; 20327 } 20328 20329 if(!first) 20330 { 20331 $('html')[0].scrollTo({ 20332 top: 0, 20333 behavior: 'auto' 20334 }); 20335 } 20336 }; 20337 20338 this.eventUnload = () => 20339 {
20340 let active = pages.ItemGet($dh.page()?.id); 20341 20342 if(!active) 20343 { 20344 return; 20345 } 20346 20347 if(!$dh.client()['admin']) 20348 { 20349 active.Get('Component').Fn('remove.tags'); 20350 } 20351 20352 this.settings.unload = active; 20353 20354 dh.trigger('dh/pages/unload', 'page', active); 20355 20356 mdEvents.Fn('run', 'dh.pages.unload', {page, settings: this.settings}, (intercept) => 20357 { 20358 let delay = (intercept.callback.values.delay + '').replace(/\D/g, ''); 20359 20360 if(delay) 20361 { 20362 intercept.data.settings.prevent = true; 20363 20364 setTimeout(() => 20365 { 20366 intercept.data.settings.continue(); 20367 }, delay); 20368 } 20369 }); 20370 }; 20371 20372 this.eventPreload = () => 20373 { 20374 mdEvents.Fn('run', 'dh.pages.preload', {page, settings: this.settings}); 20375 }; 20376 20377 this.eventReady = () => 20378 { 20379 mdEvents.Fn('run', 'dh.pages.ready', {page, settings: this.settings}); 20380 mdEvents.Fn('run', 'pages.ready', {page, settings: this.settings}); 20381 } 20382 20383 this.eventLoad = (i = 0) => 20384 { 20385 if(i < 250 && $('body > div#dh-website').find('.dh-loader').length) 20386 { 20387 return setTimeout(() => 20388 { 20389 i++ 20390 this.eventLoad(i); 20391 }, 25); 20392 } 20393 20394 mdEvents.Fn('run', 'dh.pages.load', {page, settings: this.settings}); 20395 mdEvents.Fn('run', 'pages.load', {page, settings: this.settings}); 20396 } 20397 20398 return this; 20399}); 20400pages.FunctionCreate('item.render', function(addon, page, vars, query, first = false, callback) 20401{ 20402 this.init = () => 20403 { 20404 if(first) 20405 { 20406 this.variable(); 20407 this.history(); 20408 20409 callback(page.Get('Component').Fn('render.html')); 20410 } 20411 else if(!page.Get('source')) 20412 { 20413 page.data.data = null; 20414 20415 this.variable(); 20416 this.history(); 20417 20418 callback(page.Get('Component').Fn('render.html')); 20419 } 20420 else 20421 { 20422 vars.limit = 1; 20423 20424 $dh.source(page.Get('source'), vars, [], '', {}, (data) => 20425 { 20426 if(!Array.isArray(data)) 20427 { 20428 return callback('Failed to fetch data.'); 20429 } 20430 20431 page.data.data = (0 in data) ? data[0] : null; 20432 20433 this.variable(); 20434 this.history(); 20435 20436 callback(page.Get('Component').Fn('render.html')); 20437 }); 20438 } 20439 }; 20440 20441 this.variable = () => 20442 { 20443 mdVars.ItemAdd({ 20444 id: 'page', 20445 value: { 20446 id: page.data.id, 20447 title: page.data.title, 20448 route: page.data.route, 20449 data: page.data.data 20450 } 20451 }); 20452 }; 20453 20454 this.history = () => 20455 { 20456 let title = mdVars.Fn('process', page.Get('title'), page.data.data); 20457 let route = page.Fn('route.build', vars) + (Object.keys(query).length ? ('?' + $.param(query)) : ''); 20458 20459 $(document).prop('title', title); 20460 20461 if(route !== (window.location.pathname + window.location.search)) 20462 { 20463 try 20464 { 20465 window.history.pushState({page: page.Get('id')}, title, route); 20466 } 20467 catch(error) { 20468 20469 } 20470 } 20471 }; 20472 20473 return this; 20474});pages.FunctionCreate('item.route.build', function(pages, page, vars = {}, query = {}) 20475{ 20476 let route = page.Get('route'); 20477 20478 $.each(vars, function(key, value) 20479 { 20480 route = route.replaceAll(':' + key, value); 20481 }); 20482 20483 return route.replace('//', '/').replace('/*', '/') + (Object.keys(query).length ? ('?' + $.param(query)) : ''); 20484});pages.FunctionCreate('item.route.match', function(pages, page, uri, then) 20485{ 20486 let route = page.Get('route'); 20487 let vars = {}; 20488 let match = true; 20489 20490 route = route.split('/').filter(n => n); 20491 uri = uri.split('/').filter(n => n); 20492 20493 if(route.length !== uri.length) 20494 { 20495 return false; 20496 } 20497 20498 if(page.Get('domains').length && !page.Get('domains').includes($dh.domain().name)) 20499 { 20500 return false;
20501 } 20502 20503 route.forEach(function(value, index) 20504 { 20505 if(!match) 20506 { 20507 return; 20508 } 20509 20510 if(value.startsWith(':')) 20511 { 20512 route[index] = uri[index]; 20513 vars[value.slice(1)] = value === uri[index] ? null : uri[index]; 20514 } 20515 20516 if(route[index] !== uri[index]) 20517 { 20518 match = false; 20519 } 20520 }); 20521 20522 if(match && then) 20523 { 20524 then(page, vars); 20525 } 20526 20527 return match; 20528}); 20529mdEvents.ItemsAdd({ 20530 group: 'website', 20531 id: 'dh.preload', 20532 name: 'On Preload', 20533 description: 'Executes just before the website starts loading.', 20534 role: ['website'] 20535}, { 20536 group: 'website', 20537 id: 'dh.ready', 20538 name: 'On Ready', 20539 description: 'Executes when the website is ready for interaction.', 20540 role: ['website'] 20541}, { 20542 group: 'website', 20543 id: 'dh.load', 20544 name: 'On Load', 20545 description: 'Executes after the website has fully loaded.', 20546 role: ['website'] 20547}); 20548 20549mdEvents.ItemsAdd({ 20550 group: 'window', 20551 id: 'dh.scroll', 20552 name: 'On Scroll', 20553 description: 'Executes when the website has been scrolled.', 20554 debug: (item, data) => 20555 { 20556 return [ 20557 (data.percent.top.toFixed(2) + '%'), 20558 (data.pixels.top.toFixed(2) + 'px') 20559 ]; 20560 }, 20561 role: ['website'] 20562}, { 20563 group: 'window', 20564 id: 'dh.resize', 20565 name: 'On Resize', 20566 description: 'Executes when the website has been resized.', 20567 role: ['website'] 20568}, { 20569 group: 'window', 20570 id: 'dh.blur', 20571 name: 'On Blur', 20572 description: 'Executes when the website has been focused out.', 20573 debug: (item, data) => 20574 { 20575 return [ 20576 'Hit' 20577 ]; 20578 }, 20579 role: ['website'] 20580}, { 20581 group: 'window', 20582 id: 'dh.focus', 20583 name: 'On Focus', 20584 description: 'Executes when the website has been focused in.', 20585 debug: (item, data) => 20586 { 20587 return [ 20588 'Hit' 20589 ]; 20590 }, 20591 role: ['website'] 20592}); 20593 20594mdEvents.ItemsAdd({ 20595 group: 'element', 20596 id: 'dh.click', 20597 name: 'On Click', 20598 description: 'Executes when element is clicked.', 20599 debug: (item, data) => 20600 { 20601 return [ 20602 'Hit' 20603 ]; 20604 }, 20605 role: ['website'] 20606}, { 20607 group: 'element', 20608 id: 'dh.dblclick', 20609 name: 'On Double Click', 20610 description: 'Executes when element is clicked twice.', 20611 role: ['website'] 20612}, { 20613 group: 'element', 20614 id: 'dh.hover', 20615 name: 'On Hover', 20616 description: 'Executes when the element is hovered over or hovered out.', 20617 role: ['website'], 20618 config: () => 20619 { 20620 return { 20621 triggerOn: { 20622 type: 'SELECT', 20623 label: 'Trigger On', 20624 value: 'both', 20625 values: () => 20626 { 20627 return [ 20628 { 20629 title: 'Both', 20630 value: 'both', 20631 description: 'The event will trigger for both hover-in and hover-out actions.' 20632 }, 20633 { 20634 title: 'In', 20635 value: 'in', 20636 description: 'The event will trigger only when the pointer enters the element (hover-in).' 20637 }, 20638 { 20639 title: 'Out', 20640 value: 'out', 20641 description: 'The event will trigger only when the pointer leaves the element (hover-out).' 20642 } 20643 ]; 20644 } 20645 } 20646 } 20647 }, 20648}, { 20649 group: 'element', 20650 id: 'dh.input', 20651 name: 'On Input', 20652 description: 'Executes on element input.', 20653 role: ['website'] 20654}, { 20655 group: 'element', 20656 id: 'dh.change', 20657 name: 'On Change', 20658 description: 'Executes on element change.', 20659 role: ['website'] 20660}, { 20661 group: 'element', 20662 id: 'dh.submit', 20663 name: 'On Submit', 20664 description: 'Executes on element submit (form).', 20665 role: ['website'], 20666 config: () => 20667 { 20668 return { 20669 preventDefault: { 20670 type: 'SELECT', 20671 label: 'Prevent Default', 20672 value: 'yes', 20673 values: () => 20674 { 20675 return [ 20676 {title: 'Yes', value: 'yes', description: 'Form won\'t trigger page reload as browser default behaviour.'}, 20677 {title: 'No', value: 'no', description: 'Form will trigger page reload as broweser default behaviour.'}, 20678 ]; 20679 } 20680 } 20681 } 20682 }, 20683}, { 20684 group: 'element', 20685 id: 'dh.view', 20686 name: 'On View', 20687 description: 'Executes on element view.', 20688 once: false, 20689 debug: (item, data) => 20690 { 20691 return [ 20692 'Hit' 20693 ]; 20694 }, 20695 config: () => 20696 { 20697 return { 20698 startWhen: { 20699 type: 'SELECT', 20700 width: 12, 20701 label: 'Start When', 20702 value: 'topHitsBottom', 20703 values: () => 20704 { 20705 return [ 20706 {group: 'Top', title: 'Top Hits Top', value: 'topHitsTop', description: 'Top of a target hits the top of the screen.'}, 20707 {group: 'Top', title: 'Top Hits Middle', value: 'topHitsMiddle', description: 'Top of a target hits the middle of the screen.'}, 20708 {group: 'Top', title: 'Top Hits Bottom', value: 'topHitsBottom', description: 'Top of a target hits the bottom of the screen.'}, 20709 {group: 'Middle', title: 'Middle Hits Top', value: 'middleHitsTop', description: 'Middle of a target hits the top of the screen.'}, 20710 {group: 'Middle', title: 'Middle Hits Middle', value: 'middleHitsMiddle', description: 'Middle of a target hits the middle of the screen.'}, 20711 {group: 'Middle', title: 'Middle Hits Bottom', value: 'middleHitsBottom', description: 'Middle of a target hits the bottom of the screen.'}, 20712 {group: 'Bottom', title: 'Bottom Hits Top', value: 'bottomHitsTop', description: 'Bottom of a target hits the top of the screen.'}, 20713 {group: 'Bottom', title: 'Bottom Hits Middle', value: 'bottomHitsMiddle', description: 'Bottom of a target hits the middle of the screen.'}, 20714 {group: 'Bottom', title: 'Bottom Hits Bottom', value: 'bottomHits
20714Bottom', description: 'Bottom of a target hits the bottom of the screen.'}, 20715 ]; 20716 } 20717 } 20718 } 20719 }, 20720 role: ['website'] 20721}, { 20722 group: 'element', 20723 id: 'dh.scrolling', 20724 name: 'While Scrolling', 20725 description: 'Executes on element scrolling.', 20726 debug: (item, data) => 20727 { 20728 return [ 20729 (data.percent.top.toFixed(2) + '%'), 20730 (data.pixels.top.toFixed(2) + 'px') 20731 ]; 20732 }, 20733 config: () => 20734 { 20735 return { 20736 startWhen: { 20737 type: 'SELECT', 20738 width: 12, 20739 label: 'Start When', 20740 value: 'topHitsBottom', 20741 values: () => 20742 { 20743 return [ 20744 {group: 'Top', title: 'Top Hits Top', value: 'topHitsTop', description: 'Top of a target hits the top of the screen.'}, 20745 {group: 'Top', title: 'Top Hits Middle', value: 'topHitsMiddle', description: 'Top of a target hits the middle of the screen.'}, 20746 {group: 'Top', title: 'Top Hits Bottom', value: 'topHitsBottom', description: 'Top of a target hits the bottom of the screen.'}, 20747 {group: 'Middle', title: 'Middle Hits Top', value: 'middleHitsTop', description: 'Middle of a target hits the top of the screen.'}, 20748 {group: 'Middle', title: 'Middle Hits Middle', value: 'middleHitsMiddle', description: 'Middle of a target hits the middle of the screen.'}, 20749 {group: 'Middle', title: 'Middle Hits Bottom', value: 'middleHitsBottom', description: 'Middle of a target hits the bottom of the screen.'}, 20750 {group: 'Bottom', title: 'Bottom Hits Top', value: 'bottomHitsTop', description: 'Bottom of a target hits the top of the screen.'}, 20751 {group: 'Bottom', title: 'Bottom Hits Middle', value: 'bottomHitsMiddle', description: 'Bottom of a target hits the middle of the screen.'}, 20752 {group: 'Bottom', title: 'Bottom Hits Bottom', value: 'bottomHitsBottom', description: 'Bottom of a target hits the bottom of the screen.'}, 20753 ]; 20754 } 20755 }, 20756 endWhen: { 20757 type: 'SELECT', 20758 width: 12, 20759 label: 'End When', 20760 value: 'bottomHitsTop', 20761 values: () => 20762 { 20763 return [ 20764 {group: 'Top', title: 'Top Hits Top', value: 'topHitsTop', description: 'Top of a target hits the top of the screen.'}, 20765 {group: 'Top', title: 'Top Hits Middle', value: 'topHitsMiddle', description: 'Top of a target hits the middle of the screen.'}, 20766 {group: 'Top', title: 'Top Hits Bottom', value: 'topHitsBottom', description: 'Top of a target hits the bottom of the screen.'}, 20767 {group: 'Middle', title: 'Middle Hits Top', value: 'middleHitsTop', description: 'Middle of a target hits the top of the screen.'}, 20768 {group: 'Middle', title: 'Middle Hits Middle', value: 'middleHitsMiddle', description: 'Middle of a target hits the middle of the screen.'}, 20769 {group: 'Middle', title: 'Middle Hits Bottom', value: 'middleHitsBottom', description: 'Middle of a target hits the bottom of the screen.'}, 20770 {group: 'Bottom', title: 'Bottom Hits Top', value: 'bottomHitsTop', description: 'Bottom of a target hits the top of the screen.'}, 20771 {group: 'Bottom', title: 'Bottom Hits Middle', value: 'bottomHitsMiddle', description: 'Bottom of a target hits the middle of the screen.'}, 20772 {group: 'Bottom', title: 'Bottom Hits Bottom', value: 'bottomHitsBottom', description: 'Bottom of a target hits the bottom of the screen.'}, 20773 ]; 20774 } 20775 } 20776 } 20777 }, 20778 role: ['website'] 20779 });$(window).on('blur', function() 20780{ 20781 mdEvents.Fn('run', 'dh.blur'); 20782});$(document).on('change', 'input, textarea, select', function(event) 20783{ 20784 let id = ($(this).attr('class') + ' ').split(' ')[0].replace('t', ''); 20785 let tag = components.tags.ItemGet(id); 20786 20787 mdEvents.Fn('run', 'dh.change', {event, target: $(this), tag, value: $(this).val()}); 20788});$(document).on('click', '*', function(event) 20789{ 20790 let id = ($(this).attr('class') + ' ').split(' ')[0].replace('t', ''); 20791 let tag = components.tags.ItemGet(id); 20792 20793 mdEvents.Fn('run', 'dh.click', {event, target: $(this), tag}); 20794});$(document).on('dblclick', '*', function(event) 20795{ 20796 let id = ($(this).attr('class') + ' ').split(' ')[0].replace('t', ''); 20797 let tag = components.tags.ItemGet(id); 20798 20799 mdEvents.Fn('run', 'dh.dblclick', {event, target: $(this), tag}); 20800}); 20801$(window).focus(function() 20802{ 20803 mdEvents.Fn('run', 'dh.focus'); 20804});$(document).on('mouseenter', '*[class]', function(event) 20805{ 20806 if(event.target !== this) 20807 { 20808 return; 20809 } 20810 20811 let id = ($(this).attr('class') + ' ').split(' ')[0].replace('t', ''); 20812 20813 components.tags.ItemGet(id, (tag) => 20814 { 20815 mdEvents.Fn('run', 'dh.hover', {event, target: $(this), tag, type: 'in'}, (intercept) => 20816 { 20817 const triggerOn = intercept.callback.values.triggerOn; 20818 20819 if(!['both', 'in'].includes(triggerOn)) 20820 { 20821 intercept.prevent = true; 20822 } 20823 }); 20824 }); 20825}); 20826 20827$(document).on('mouseleave', '*[class]', function(event) 20828{ 20829 if(event.target !== this) 20830 { 20831 return; 20832 } 20833 20834 let id = ($(this).attr('class') + ' ').split(' ')[0].replace('t', ''); 20835 20836 components.tags.ItemGet(id, (tag) => 20837 { 20838 mdEvents.Fn('run', 'dh.hover', {event, target: $(this), tag, type: 'out'}, (intercept) => 20839 { 20840 const triggerOn = intercept.callback.values.triggerOn; 20841 20842 if(!['both', 'out'].includes(triggerOn)) 20843 { 20844 intercept.prevent = true; 20845 } 20846 }); 20847 }); 20848});$(document).on('input', 'input, textarea, select', function(event) 20849{ 20850 let id = ($(this).attr('class') + ' ').split(' ')[0].replace('t', ''); 20851 let tag = components.tags.ItemGet(id); 20852 20853 mdEvents.Fn('run', 'dh.input', {event, target: $(this), tag, value: $(this).val()}); 20854});$(document).ready(() => 20855{ 20856 files.SetTemp('resize.width', $(window).width()); 20857 files.SetTemp('resize.height', $(window).height()); 20858 20859 $(window).resize(function(event) 20860 { 20861 let width = $(window).width(); 20862 let height = $(window).height(); 20863 20864 let direction; 20865 20866 if(files.GetTemp('resize.width') > width) 20867 { 20868 direction = 'inwards'; 20869 } 20870 else if(files.GetTemp('resize.width') < width) 20871 { 20872 direction = 'outwards'; 20873 } 20874 20875 if(files.GetTemp('resize.height') > height) 20876 { 20877 direction = 'inwards'; 20878 } 20879 else if(files.GetTemp('resize.height') < height) 20880 { 20881 direction = 'outwards'; 20882 } 20883 20884 files.SetTemp('resize.width', width); 20885 files.SetTemp('resize.height', height); 20886 20887 mdEvents.Fn('run', 'dh.resize', {event, width, height, direction}); 20888 }); 20889}); 20890mdEvents.Fn('catch', 'dh.ready', () => 20891{ 20892 files.SetTemp('scroll', $(window).scrollTop()); 20893 20894 $(window).scroll(function(event) 20895 { 20896 let pixels = {}; 20897 let percent = {}; 20898 let direction; 20899 20900 pixels.top = $(window).scrollTop(); 20901 pixels.max = $(document).height() - $(window).height(); 20902 pixels.left = pixels.max - pixels.top; 20903 20904 percent.top = pixels.top / pixels.max * 100; 20905 percent.max = 100; 20906 percent.left = 100 - percent.top; 20907 20908 if(files.GetTemp('scroll') > pixels.top) 20909 { 20910 direction = 'up'; 20911 } 20912 else if(files.GetTemp('scroll') < pixels.top) 20913 { 20914 direction = 'down'; 20915 } 20916 20917 files.SetTemp('scroll', $(window).scrollTop()); 20918 20919 mdEvents.Fn('run', 'dh.scroll', {event, pixels, percent, direction}) 20920 }); 20921});mdEvents.Fn('catch', 'dh.ready', function() 20922{ 20923 this.init = () => 20924 { 20925 $(window).scroll(dh.throttle(this.scroll, 50)); 20926 20927 mdEvents.Fn('catch', 'dh.pages.ready', this.scroll); 20928 mdEvents.Fn('catch', 'dh.pages.load', this.scroll); 20929 }; 20930 20931 this.scroll = (event) => 20932 { 20933 mdEvents.Fn('run', 'dh.scrolling', {}, (intercept) => 20934 { 20935 let process = this.process(intercept.callback.values, intercept.index); 20936 20937 if(process) 20938 { 20939 intercept.data = $.extend(intercept.data, process); 20940 } 20941 else 20942 { 20943 intercept.prevent = true; 20944 } 20945 }); 20946 }; 20947 20948 this.process = (values, index, validate = true) => 20949 { 20950 if(!('target' in values)) 20951 { 20952 return null; 20953 } 20954 20955 let section = $(values.target); 20956 20957 if(!section.length) 20958 { 20959 return null; 20960 } 20961
20962 let viewportHeight = window.innerHeight; 20963 let sectionOffset = section.offset(); 20964 let sectionHeight = section.outerHeight(); 20965 let scrollTop = window.scrollY; 20966 20967 let startOffsetTop = this.startWhen(values.startWhen, sectionOffset.top, sectionHeight, viewportHeight); 20968 let endOffsetTop = this.endWhen(values.endWhen, sectionOffset.top, sectionHeight, viewportHeight); 20969 20970 if(!validate || (scrollTop >= startOffsetTop && scrollTop <= endOffsetTop)) 20971 { 20972 let pixels = this.calculatePixels(scrollTop, startOffsetTop, endOffsetTop); 20973 let percent = this.calculatePercent(pixels); 20974 let direction = this.scrollDirection(scrollTop, index); 20975 20976 return {pixels, percent, direction, height: sectionHeight, recalculate: () => 20977 { 20978 return this.process(values, index, false); 20979 }}; 20980 } 20981 20982 return null; 20983 }; 20984 20985 this.calculatePixels = (scrollTop, startOffsetTop, endOffsetTop) => 20986 { 20987 let maxPixels = Math.max(endOffsetTop - startOffsetTop, 1); 20988 20989 return { 20990 top: Math.max(scrollTop - startOffsetTop, 0), 20991 max: maxPixels, 20992 left: Math.max(maxPixels - (scrollTop - startOffsetTop), 0) 20993 }; 20994 }; 20995 20996 this.calculatePercent = (pixels) => 20997 { 20998 return { 20999 top: Math.min((pixels.top / pixels.max) * 100, 100), 21000 max: 100, 21001 left: 100 - Math.min((pixels.top / pixels.max) * 100, 100) 21002 }; 21003 }; 21004 21005 this.scrollDirection = (scrollTop, index) => 21006 { 21007 let previousScroll = files.GetTemp('scrolling.' + index); 21008 let direction = 'none'; 21009 21010 if(previousScroll > scrollTop) 21011 { 21012 direction = 'up'; 21013 } 21014 else if(previousScroll < scrollTop) 21015 { 21016 direction = 'down'; 21017 } 21018 21019 files.SetTemp('scrolling.' + index, scrollTop); 21020 return direction; 21021 }; 21022 21023 this.startWhen = (startWhen, sectionOffsetTop, sectionHeight, viewportHeight) => 21024 { 21025 switch (startWhen) 21026 { 21027 case 'topHitsTop': 21028 return sectionOffsetTop; 21029 case 'topHitsMiddle': 21030 return sectionOffsetTop - viewportHeight / 2; 21031 case 'topHitsBottom': 21032 return sectionOffsetTop - viewportHeight; 21033 21034 case 'middleHitsTop': 21035 return sectionOffsetTop + sectionHeight / 2; 21036 case 'middleHitsMiddle': 21037 return sectionOffsetTop + sectionHeight / 2 - viewportHeight / 2; 21038 case 'middleHitsBottom': 21039 return sectionOffsetTop + sectionHeight / 2 - viewportHeight; 21040 21041 case 'bottomHitsTop': 21042 return sectionOffsetTop + sectionHeight; 21043 case 'bottomHitsMiddle': 21044 return sectionOffsetTop + sectionHeight - viewportHeight / 2; 21045 case 'bottomHitsBottom': 21046 return sectionOffsetTop + sectionHeight - viewportHeight; 21047 21048 default: 21049 return sectionOffsetTop; 21050 } 21051 }; 21052 21053 this.endWhen = (endWhen, sectionOffsetTop, sectionHeight, viewportHeight) => 21054 { 21055 switch (endWhen) 21056 { 21057 case 'topHitsTop': 21058 return sectionOffsetTop; 21059 case 'topHitsMiddle': 21060 return sectionOffsetTop - viewportHeight / 2; 21061 case 'topHitsBottom': 21062 return sectionOffsetTop - viewportHeight; 21063 21064 case 'middleHitsTop': 21065 return sectionOffsetTop + sectionHeight / 2; 21066 case 'middleHitsMiddle': 21067 return sectionOffsetTop + sectionHeight / 2 - viewportHeight / 2; 21068 case 'middleHitsBottom': 21069 return sectionOffsetTop + sectionHeight / 2 - viewportHeight; 21070 21071 case 'bottomHitsTop': 21072 return sectionOffsetTop + sectionHeight; 21073 case 'bottomHitsMiddle': 21074 return sectionOffsetTop + sectionHeight - viewportHeight / 2; 21075 case 'bottomHitsBottom': 21076 return sectionOffsetTop + sectionHeight - viewportHe
21076ight; 21077 21078 default: 21079 return sectionOffsetTop + sectionHeight; 21080 } 21081 }; 21082 21083 this.init(); 21084}); 21085$(document).on('submit', 'form', function(event) 21086{ 21087 this.init = () => 21088 { 21089 let id = ($(this).attr('class') + ' ').split(' ')[0].replace('t', ''); 21090 let tag = components.tags.ItemGet(id); 21091 21092 let data = this.data(); 21093 21094 mdEvents.Fn('run', 'dh.submit', {event, target: $(this), tag, fields: data.fields, formData: data.form}, (intercept) => 21095 { 21096 intercept.prevent = true; 21097 21098 if(intercept.run(intercept.data)) 21099 { 21100 intercept.data.event.preventDefault(); 21101 } 21102 }); 21103 } 21104 21105 this.data = () => 21106 { 21107 let fields = {}; 21108 let form = new FormData; 21109 21110 $.each($(this).serializeArray(), (index, field) => 21111 { 21112 fields[field.name] = field.value; 21113 form.append(field.name, field.value); 21114 }); 21115 21116 $.each($(this).find('input[name][type="file"]'), function() 21117 { 21118 $.each($(this).prop('files'), function(index, file) 21119 { 21120 form.append($(this).attr('name') + '[]', file); 21121 }); 21122 }); 21123 21124 return {fields, form}; 21125 }; 21126 21127 this.init(); 21128});mdEvents.Fn('catch', 'dh.ready', function() 21129{ 21130 this.clear = []; 21131 21132 this.init = () => 21133 { 21134 $(window).scroll(this.scroll); 21135 21136 mdEvents.Fn('catch', 'dh.pages.ready', this.scroll); 21137 mdEvents.Fn('catch', 'dh.pages.load', this.scroll); 21138 }; 21139 21140 this.scroll = (event) => 21141 { 21142 mdEvents.Fn('run', 'dh.view', {}, (intercept) => 21143 { 21144 if(!('target' in intercept.callback.values)) 21145 { 21146 intercept.callback.remove(); 21147 return; 21148 } 21149 21150 intercept.prevent = true; 21151 21152 let targets = $(intercept.callback.values.target); 21153 21154 $.each(targets, (index, target) => 21155 { 21156 let data = this.process(intercept.callback.values, $(target)); 21157 21158 if(data === null) 21159 { 21160 return true; 21161 } 21162 21163 if($(target).attr('dh-view') === 'initiated') 21164 { 21165 return true; 21166 } 21167 21168 $(target).attr('dh-view', 'initiated'); 21169 21170 data = $.extend(data, intercept.data); 21171 data = $.extend(data, {target: $(target)}); 21172 21173 intercept.run(data); 21174 }); 21175 }); 21176 }; 21177 21178 this.process = (values, section) => 21179 { 21180 let viewportHeight = $(window).height(); 21181 let sectionOffsetTop = section.offset().top; 21182 let sectionHeight = section.outerHeight(); 21183 let scrollTop = $(window).scrollTop(); 21184 21185 let startOffsetTop = this.startWhen(values.startWhen, sectionOffsetTop, sectionHeight, viewportHeight); 21186 21187 if(scrollTop >= startOffsetTop) 21188 { 21189 return { 21190 window: {scroll: scrollTop, height: viewportHeight}, 21191 element: {offset: sectionOffsetTop, height: sectionHeight} 21192 }; 21193 } 21194 21195 return null; 21196 }; 21197 21198 this.startWhen = (startWhen, sectionOffsetTop, sectionHeight, viewportHeight) => 21199 { 21200 switch (startWhen) 21201 { 21202 case 'topHitsTop': 21203 return sectionOffsetTop; 21204 case 'topHitsMiddle': 21205 return sectionOffsetTop - viewportHeight / 2; 21206 case 'topHitsBottom': 21207 return sectionOffsetTop - viewportHeight; 21208 21209 case 'middleHitsTop': 21210 return sectionOffsetTop + sectionHeight / 2; 21211 case 'middleHitsMiddle': 21212 return sectionOffsetTop + sectionHeight / 2 - viewportHeight / 2; 21213 case 'middleHitsBottom': 21214 return sectionOffsetTop + sectionHeight / 2 - viewportHeight; 21215 21216 case 'bottomHitsTop': 21217 return sectionOffsetTop + sectionHeight; 21218 case 'bottomHitsMiddle': 21219 return sectionOffsetTop + sectionHeight - viewportHeight / 2;
21220 case 'bottomHitsBottom': 21221 return sectionOffsetTop + sectionHeight - viewportHeight; 21222 21223 default: 21224 return sectionOffsetTop; 21225 } 21226 }; 21227 21228 this.endWhen = (endWhen, sectionOffsetTop, sectionHeight, viewportHeight) => 21229 { 21230 switch (endWhen) 21231 { 21232 case 'topHitsTop': 21233 return sectionOffsetTop; 21234 case 'topHitsMiddle': 21235 return sectionOffsetTop - viewportHeight / 2; 21236 case 'topHitsBottom': 21237 return sectionOffsetTop - viewportHeight; 21238 21239 case 'middleHitsTop': 21240 return sectionOffsetTop + sectionHeight / 2; 21241 case 'middleHitsMiddle': 21242 return sectionOffsetTop + sectionHeight / 2 - viewportHeight / 2; 21243 case 'middleHitsBottom': 21244 return sectionOffsetTop + sectionHeight / 2 - viewportHeight; 21245 21246 case 'bottomHitsTop': 21247 return sectionOffsetTop + sectionHeight; 21248 case 'bottomHitsMiddle': 21249 return sectionOffsetTop + sectionHeight - viewportHeight / 2; 21250 case 'bottomHitsBottom': 21251 return sectionOffsetTop + sectionHeight - viewportHeight; 21252 21253 default: 21254 return sectionOffsetTop + sectionHeight; 21255 } 21256 }; 21257 21258 this.init(); 21259}); 21260$(document).ready(function() 21261{ 21262 mdVars.ItemAdd({id: 'query', value: Object.fromEntries(new URLSearchParams(window.location.search))}); 21263 21264 transform.Ready(); 21265 21266 builder.SetTemp('mode', 'live'); 21267 builder.Ready(); 21268});$(document).ready(function() { 21269 $('body').on('click', 'a[href^="#"]', function(e) { 21270 e.preventDefault(); 21271 const hash = $(this).attr("href"); 21272 const $target = $(hash); 21273 21274 const offsetAttr = $(this).attr('scroll-offset'); 21275 const offset = parseInt(offsetAttr, 10) || 0; 21276 21277 const targetOffset = $target.offset().top - offset; 21278 const currentScroll = $(window).scrollTop(); 21279 const distanceToScroll = Math.abs(targetOffset - currentScroll); 21280 21281 const maxDuration = 1500; 21282 const minDuration = 200; 21283 const durationPerPx = maxDuration / Math.max($(document).height(), distanceToScroll); 21284 let duration = Math.max(Math.min(maxDuration, distanceToScroll * durationPerPx), minDuration); 21285 21286 $('html, body').animate({ 21287 scrollTop: targetOffset 21288 }, { 21289 duration: duration, 21290 easing: 'linear', 21291 step: function(now, fx) { 21292 const newOffset = $target.offset().top - offset; 21293 if (fx.end !== newOffset) 21294 fx.end = newOffset; 21295 } 21296 }); 21297 }); 21298}); 21299 21300 21301 21302mdActions.OnReady(() => 21303{ 21304 return; 21305 21306 mdActions.ItemAdd({ 21307 id: 'collections.create', 21308 group: 'Collections', 21309 name: 'Create', 21310 icon: 'https://global.divhunt.com/13c1a27fc562bc29e119efe8f85a4eb7_542.svg', 21311 description: 'Create new collection item.', 21312 config: () => 21313 { 21314 return { 21315 collection: { 21316 label: 'Collectione', 21317 type: 'SOURCE', 21318 values: () => 21319 { 21320 return [ 21321 {title: 'Add', value: 'add'}, 21322 {title: 'Remove', value: 'remove'}, 21323 {title: 'Toggle', value: 'toggle'} 21324 ] 21325 }, 21326 } 21327 }; 21328 }, 21329 code: (data, resolve, reject) => 21330 { 21331 switch(data.values.type) 21332 { 21333 case 'add': 21334 data.target.addClass(data.values.value); 21335 break; 21336 case 'remove': 21337 data.target.removeClass(data.values.value); 21338 break; 21339 case 'toggle': 21340 data.target.toggleClass(data.values.value); 21341 break; 21342 } 21343 21344 resolve(); 21345 } 21346 }); 21347}); 21348try { 21349transform.OnReady(function() { 21350 21351 this.effects = [ 21352 {value:'none', title: 'None'}, 21353 {value:'fade', title: 'Fade'}, 21354 {value:'slide', title: 'Slide'}, 21355 {value:'slideLeft', title: 'Slide Left'}, 21356 {value:'slideRight', title: 'Slide Right'} 21357 ]; 21358 21359 transform.ItemAdd({ 21360 id: 'hamburger', 21361 name: 'Hamburger Menu', 21362 options: { 21363 a: {'label': 'Menu ID', 'type': 'INPUT', 'value': ''}, 21364 b: {'label': 'Animation', 'type': 'SELECT', 'values': this.effects, 'value': 'fade'}, 21365 dur: { 21366 label: 'Duration', 21367 placeholder: '400', 21368 type: 'INPUT', 21369 units: ['ms'], 21370 condition: (option, values) => 21371 { 21372 return values.b === 'slideLeft' || values.b === 'slideRight' 21373 } 21374 }, 21375 21376 c: {'label': 'Show', 'type': 'TOGGLE', 'value': false}, 21377 d: {'label': 'Flex on show', 'type': 'TOGGLE', 'value': false}, 21378 }, 21379 21380 code: function(transform, tag, target, options, data, index) { 21381 21382 this.init = (bind, num) => { 21383 this.requirements(); 21384 this.render(); 21385 21386 if(data.mode === 'website') { 21387 this.click(); 21388 this.anchorClick(); 21389 } 21390 }; 21391 21392 this.requirements = () => { 21393 if(!options.a ) {
21394 console.log(`Menu ID is not set.`); 21395 return; 21396 } 21397 21398 if( !$(`#${options.a}`).length ) { 21399 console.log(`Menu with an id: ${options.a} doesn't exist.`); 21400 return; 21401 } 21402 }; 21403 21404 this.anchorClick = () => { 21405 21406 let transform = this; 21407 $(`#${options.a} a`).on('click', function (e) { 21408 var href = $(this).attr('href'); 21409 if (href && href.startsWith('#')) { 21410 if ($(target).is(":visible")) { 21411 transform.menuToggle(); 21412 } 21413 } 21414 }); 21415 21416 }; 21417 21418 this.render = () => { 21419 if (options.c ) { 21420 target.addClass('dh-active'); 21421 $(`#${options.a}`).addClass("dh-active"); 21422 if (options.d) { 21423 $(`#${options.a}`).css('display', 'flex'); 21424 return; 21425 } 21426 $(`#${options.a}`).show(); 21427 } 21428 }; 21429 21430 this.click = () => { 21431 let transform = this; 21432 $(target).on("click", function(){ 21433 transform.menuToggle($(this)); 21434 }); 21435 }; 21436 21437 this.menuToggle = (button) => { 21438 const menuElement = $(`#${options.a}`); 21439 const displayType = options.d ? 'flex' : null; 21440 target.toggleClass('dh-active'); 21441 if (options.b === "none") { 21442 if (menuElement.is(":visible")) { 21443 menuElement.hide().css("display",""); 21444 } else { 21445 menuElement.css('display', displayType || 'block'); 21446 } 21447 } else if (options.b === "fade") { 21448 if (menuElement.is(":visible")) { 21449 menuElement.fadeOut("fast", function(){ 21450 $(menuElement).css("display",""); 21451 }); 21452 } else { 21453 menuElement.css('display', displayType || 'block').hide().fadeIn("fast"); 21454 } 21455 } else if (options.b === "slide") { 21456 if (menuElement.is(":visible")) { 21457 menuElement.slideUp("fast",function(){ 21458 $(menuElement).css("display",""); 21459 }); 21460 } else { 21461 menuElement.css('display', displayType || 'block').hide().slideDown("fast"); 21462 } 21463 } else if (options.b === "slideLeft") { 21464 this.slideLeftToggle(menuElement, options.dur, displayType); 21465 } else if (options.b === "slideRight") { 21466 this.slideRightToggle(menuElement, options.dur, displayType); 21467 } 21468 } 21469 21470 21471 21472 this.slideLeftToggle = function(element, duration, displayType) { 21473 duration = (duration.match(/\d+/) ? parseInt(duration.match(/\d+/)[0], 10) : 400); 21474 21475 element.css('transition', `transform ${duration}ms linear`); 21476 21477 if (element.is(":visible")) { 21478 element.css('transform', 'translateX(-100%)'); 21479 setTimeout(function() { 21480 element.hide().css("display",""); 21481 element.css('transform', ''); 21482 }, duration); 21483 } else { 21484 element.css({ 21485 'transform': 'translateX(-100%)', 21486 'display': displayType || 'block' 21487 }); 21488 setTimeout(function() { 21489 element.css('transform', 'translateX(0)'); 21490 }, 1); 21491 } 21492 }; 21493 21494 this.slideRightToggle = function(element, duration, displayType) { 21495 21496 duration = (duration.match(/\d+/) ? parseInt(duration.match(/\d+/)[0], 10) : 400); 21497 element.css('transition', `transform ${duration}ms linear`); 21498 21499 21500 if (element.is(":visible")) { 21501 element.css('transform', 'translateX(100%)'); 21502 setTimeout(function() { 21503 element.hide().css("display",""); 21504 element.css('transform', ''); 21505 }, duration); 21506 } else { 21507 element.css({ 21508 'transform': 'translateX(100%)', 21509 'display': displayType || 'block' 21510 }); 21511 setTimeout(function() { 21512 element.css('transform', 'translateX(0)'); 21513 }, 1); 21514 } 21515 }; 21516 21517 21518 21519 21520 21521 return this; 21522 } 21523 });
21524}); 21525 21526} catch (error) { console.log(error); } 21527 21528try { 21529mdLibraries.ItemAdd({ 21530 id: 'gsap.js', 21531 js: ['https://static.divhunt.com/assets/library/Gsap.js', 'https://static.divhunt.com/assets/library/GsapScrollTrigger.js'], 21532}); 21533 21534transform.OnReady(function() { 21535 transform.ItemAdd({ 21536 id: 'gsap', 21537 name: 'GSAP Animation', 21538 options: { 21539 21540 targetType: { 21541 'label': 'Target', 21542 'type': 'SELECT', 21543 'values': [ 21544 { value: 'self', title: 'Self' }, 21545 { value: 'tag', title: 'Tag' }, 21546 { value: 'class', title: 'Class' }, 21547 ], 21548 'value': 'self', 21549 }, 21550 21551 21552 target: { 'label': 'Select tag', 'type': 'TAG', 'value': '', 21553 condition: (option, values) => { 21554 return values.targetType === "tag" 21555 } 21556 }, 21557 21558 cls: { 'label': 'Class', 'type': 'INPUT', 'value': '', 21559 condition: (option, values) => { 21560 return values.targetType === "class" 21561 } 21562 }, 21563 21564 event: { 21565 'label': 'Event', 21566 'type': 'SELECT', 21567 'values': [ 21568 { value: 'pageLoad', title: 'On Page Load'}, 21569 { value: 'scroll', title: 'Scroll into View' }, 21570 ], 21571 'value': 'pageLoad' 21572 }, 21573 21574 21575 pin: {'label': 'Pin', 'type': 'TOGGLE', 'value': false, 21576 condition: (option, values) => { 21577 return values.scrub 21578 } 21579 }, 21580 21581 markers: {'label': 'View Markers', 'type': 'TOGGLE', 'value': false, 21582 condition: (option, values) => { 21583 return values.event === "scroll" 21584 } 21585 }, 21586 21587 scrub: {'label': 'Follow Scroll', 'type': 'TOGGLE', 'value': false}, 21588 21589 duration: { 'label': 'Duration (ms)', 'type': 'INPUT', 'value': '1000', 21590 condition: (option, values) => { 21591 return !values.scrub 21592 } 21593 }, 21594 21595 delay: { 'label': 'delay (ms)', 'type': 'INPUT', 'value': '0', 21596 condition: (option, values) => { 21597 return !values.scrub 21598 } 21599 }, 21600 21601 reverse: {'label': 'Reverse', 'type': 'TOGGLE', 'value': false, 21602 condition: (option, values) => { 21603 return !values.scrub && values.event === "scroll" 21604 } 21605 }, 21606 21607 easing: { 21608 'label': 'Easing', 21609 'type': 'SELECT', 21610 'values': [ 21611 { value: 'linear', title: 'Linear' }, 21612 { value: 'power1.easeInOut', title: 'Power1.easeInOut' }, 21613 { value: 'power2.easeInOut', title: 'Power2.easeInOut' }, 21614 { value: 'power3.easeInOut', title: 'Power3.easeInOut' }, 21615 { value: 'power4.easeInOut', title: 'Power4.easeInOut' }, 21616 { value: 'power5.easeInOut', title: 'Power5.easeInOut' }, 21617 { value: 'quad.easeInOut', title: 'Quad.easeInOut' }, 21618 { value: 'cubic.easeInOut', title: 'Cubic.easeInOut' }, 21619 { value: 'quart.easeInOut', title: 'Quart.easeInOut' }, 21620 { value: 'quint.easeInOut', title: 'Quint.easeInOut' }, 21621 { value: 'sine.easeInOut', title: 'Sine.easeInOut' }, 21622 { value: 'expo.easeInOut', title: 'Expo.easeInOut' }, 21623 { value: 'circ.easeInOut', title: 'Circ.easeInOut' }, 21624 { value: 'back.easeInOut', title: 'Back.easeInOut' }, 21625 { value: 'elastic.easeInOut', title: 'Elastic.easeInOut' }, 21626 { value: 'bounce.easeInOut', title: 'Bounce.easeInOut' }, 21627 ], 21628 condition: (option, values) => { 21629 return !values.scrub 21630 }, 21631 'value': 'linear', 21632 }, 21633 21634 resp1: { 21635 label: 'Desktop', 21636 type: 'TOGGLE', 21637 value: true, 21638 group: 'Devices' 21639 }, 21640 21641 resp2: { 21642 label: 'Tablet', 21643 type: 'TOGGLE', 21644 value: true, 21645 group: 'Devices' 21646 }, 21647 21648 resp3: { 21649 label: 'Landscape Mobile', 21650 type: 'TOGGLE', 21651 value: true, 21652 group: 'Devices' 21653 }, 21654 21655 resp4: { 21656 label: 'Mobile', 21657 type: 'TOGGLE', 21658 value: true, 21659 group: 'Devices' 21660 }, 21661 21662 startEl: { 21663 'label': 'Element position', 21664 'type': 'SELECT', 21665 'values': [ 21666 { value: 'top', title: 'Top'}, 21667 { value: 'center', title: 'Center' }, 21668 { value: 'bottom', title: 'Bottom' }, 21669 { value: 'custom', title: 'Custom' }, 21670 ], 21671 'value': 'top', 21672 'group': 'Start when', 21673 condition: (option, values) => values.event === 'scroll' 21674 }, 21675 21676 startElCustom: { 'label': 'Element Position', 'type': 'INPUT', 'units':['%', 'px', 'vh'], 'value': '', 21677 condition: (option, values) => { 21678 return values.startEl === "custom" && values.event === 'scroll' 21679 }, 21680 'group': 'Start when' 21681 }, 21682 21683 startViewPort: {
21684 'label': 'Viewport position', 21685 'type': 'SELECT', 21686 'values': [ 21687 { value: 'top', title: 'Top'}, 21688 { value: 'center', title: 'Center' }, 21689 { value: 'bottom', title: 'Bottom' }, 21690 { value: 'custom', title: 'Custom' }, 21691 ], 21692 'value': 'bottom', 21693 'group': 'Start when', 21694 condition: (option, values) => values.event === 'scroll' 21695 }, 21696 21697 startViewPortCustom: { 'label': 'Viewport Position', 'type': 'INPUT', 'units':['%', 'px', 'vh'], 'value': '', 21698 condition: (option, values) => { 21699 return values.startViewPort === "custom" && values.event === 'scroll' 21700 }, 21701 'group': 'Start when' 21702 }, 21703 21704 21705 endEl: { 21706 'label': 'Element position', 21707 'type': 'SELECT', 21708 'values': [ 21709 { value: 'top', title: 'Top'}, 21710 { value: 'center', title: 'Center' }, 21711 { value: 'bottom', title: 'Bottom' }, 21712 { value: 'custom', title: 'Custom' }, 21713 ], 21714 'value': 'top', 21715 'group': 'End when', 21716 condition: (option, values) => values.event === 'scroll' && (values.reverse || values.scrub) 21717 }, 21718 21719 endElCustom: { 'label': 'Element Position', 'type': 'INPUT', 'units':['%', 'px', 'vh'], 'value': '', 21720 condition: (option, values) => { 21721 return values.endEl === "custom" && (values.reverse || values.scrub) 21722 }, 21723 'group': 'End when' 21724 }, 21725 21726 endViewPort: { 21727 'label': 'Viewport position', 21728 'type': 'SELECT', 21729 'values': [ 21730 { value: 'top', title: 'Top'}, 21731 { value: 'center', title: 'Center' }, 21732 { value: 'bottom', title: 'Bottom' }, 21733 { value: 'custom', title: 'Custom' }, 21734 ], 21735 'value': 'top', 21736 'group': 'End when', 21737 condition: (option, values) => values.event === 'scroll' && (values.reverse || values.scrub) 21738 }, 21739 21740 endViewPortCustom: { 'label': 'Viewport Position', 'type': 'INPUT', 'units':['%', 'px', 'vh'], 'value': '', 21741 condition: (option, values) => { 21742 return values.endViewPort === "custom" && values.event === 'scroll' && (values.reverse || values.scrub) 21743 }, 21744 'group': 'End when' 21745 }, 21746 21747 21748 fromTo: { 21749 label: 'Animate', 21750 type: 'OPTIONS', 21751 options: [ 21752 {title: 'From', value: 'from'}, 21753 {title: 'To', value: 'to'}, 21754 ], 21755 value: 'from', 21756 group: 'Animation' 21757 }, 21758 21759 from_opacity: { label: 'Opacity', type: 'INPUT', units: ['%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21760 to_opacity: { label: 'Opacity', type: 'INPUT', units: ['%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21761 21762 from_x: { label: 'X Position', type: 'INPUT', units: ['px', '%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21763 to_x: { label: 'X Position', type: 'INPUT', units: ['px', '%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21764 21765 from_y: { label: 'Y Position', type: 'INPUT', units: ['px', '%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21766 to_y: { label: 'Y Position', type: 'INPUT', units: ['px', '%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21767 21768 from_top: { label: 'Top', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw', '%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21769 to_top: { label: 'Top', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw', '%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21770 21771 from_left: { label: 'Left', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw', '%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21772 to_left: { label: 'Left', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw', '%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21773 21774 from_right: { label: 'Right', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw', '%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21775 to_right: { label: 'Right', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw', '%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21776 21777 from_bottom: { label: 'Bottom', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw', '%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21778 to_bottom: { label: 'Bottom', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw', '%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21779 21780 from_color: { label: 'Text Color', placeholder: 'Select color', type: 'COLOR', value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21781 to_color: { label: 'Text Color', type: 'COLOR', placeholder: 'Select color', value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21782
21783 from_bg_color: { label: 'Background Color', type: 'COLOR', value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21784 to_bg_color: { label: 'Background Color', type: 'COLOR', value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21785 21786 from_scale: { label: 'Scale', type: 'INPUT', value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21787 to_scale: { label: 'Scale', type: 'INPUT', value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21788 21789 from_rotation: { label: 'Rotation', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21790 to_rotation: { label: 'Rotation', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21791 21792 from_rotationX: { label: 'Rotation X', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21793 to_rotationX: { label: 'Rotation X', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21794 21795 from_rotationY: { label: 'Rotation Y', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21796 to_rotationY: { label: 'Rotation Y', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21797 21798 from_skew_x: { label: 'Skew X', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21799 to_skew_x: { label: 'Skew X', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21800 21801 from_skew_y: { label: 'Skew Y', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21802 to_skew_y: { label: 'Skew Y', type: 'INPUT', units: ['deg'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21803 21804 from_width: { label: 'Width', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21805 to_width: { label: 'Width', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21806 21807 from_height: { label: 'Height', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21808 to_height: { label: 'Height', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21809 21810 from_border_width: { label: 'Border Width', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21811 to_border_width: { label: 'Border Width', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21812 21813 from_border_radius: { label: 'Border Radius', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21814 to_border_radius: { label: 'Border Radius', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21815 21816 from_blur: { label: 'Blur', type: 'INPUT', units: ['px'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21817 to_blur: { label: 'Blur', type: 'INPUT', units: ['px'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21818 21819 from_brightness: { label: 'Brightness', type: 'INPUT', units: ['%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21820 to_brightness: { label: 'Brightness', type: 'INPUT', units: ['%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21821 21822 from_contrast: { label: 'Contrast', type: 'INPUT', units: ['%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21823 to_contrast: { label: 'Contrast', type: 'INPUT', units: ['%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21824 21825 from_grayscale: { label: 'Grayscale', type: 'INPUT', units: ['%'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21826 to_grayscale: { label: 'Grayscale', type: 'INPUT', units: ['%'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21827 21828 from_font_size: { label: 'Font Size', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21829 to_font_size: { label: 'Font Size', type: 'INPUT', units: ['px', 'em', 'rem', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21830
21831 from_line_height: { label: 'Line Height', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21832 to_line_height: { label: 'Line Height', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21833 21834 from_letter_spacing: { label: 'Letter Spacing', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'from', group: 'Animation' }, 21835 to_letter_spacing: { label: 'Letter Spacing', type: 'INPUT', units: ['px', 'em', 'rem', '%', 'vh', 'vw'], value: '', condition: (option, values) => values.fromTo === 'to', group: 'Animation' }, 21836 }, 21837 21838 code: function(transform, tag, target, options, data, index) { 21839 21840 21841 const filterProperties = ['from_brightness', 'to_brightness', 'from_contrast', 'to_contrast', 'from_grayscale', 'to_grayscale', 'from_blur', 'to_blur']; 21842 21843 this.init = () => { 21844 21845 if (data.mode === "builder") {return;} 21846 21847 if (!options.resp1 && window.innerWidth > 991) { return; } 21848 if (!options.resp2 && window.innerWidth <= 991 && window.innerWidth > 767 ) { return; } 21849 if (!options.resp3 && window.innerWidth <= 767 && window.innerWidth > 480 ) { return; } 21850 if (!options.resp4 && window.innerWidth <= 480 ) { return; } 21851 21852 mdLibraries.Fn('load', ['gsap.js'], () => { 21853 21854 let animEl = target; 21855 if ( options.targetType === "tag" ) { 21856 console.log("target", options.target); 21857 console.log("target", options.target.Get('id')); 21858 animEl = $(`.t${options.target.Get('id')}`); 21859 } else if ( options.targetType === "class" ) { 21860 animEl = $(`.${options.cls}`); 21861 } 21862 21863 if (animEl.length === 0) { 21864 console.warn("Animation element not found."); 21865 return; 21866 } 21867 21868 gsap.registerPlugin(ScrollTrigger); 21869 21870 const toValues = this.to(); 21871 21872 if (options.event === 'pageLoad') { 21873 gsap.fromTo( 21874 animEl, 21875 this.from(), { 21876 duration: options.duration / 1000, 21877 delay: options.delay / 1000, 21878 ease: options.easing, 21879 ...toValues, 21880 } 21881 ); 21882 } else { 21883 gsap.fromTo( 21884 animEl, 21885 this.from(), { 21886 scrollTrigger: { 21887 markers: options.markers, 21888 trigger: target, 21889 start: this.start(), 21890 end: this.end(), 21891 scrub: options.scrub, 21892 pin: options.pin, 21893 toggleActions: options.reverse ? "play reverse play reverse" : "play none none none" 21894 }, 21895 duration: options.duration / 1000, 21896 delay: options.delay / 1000, 21897 ease: options.easing, 21898 ...toValues, 21899 } 21900 ); 21901 } 21902 21903 document.addEventListener('DOMContentLoaded', (event) => { 21904 ScrollTrigger.refresh(); 21905 }); 21906 21907 }); 21908 } 21909 21910 this.start = () => { 21911 const startEl = options.startEl === 'custom' ? options.startElCustom : options.startEl; 21912 const startView = options.startViewPort === 'custom' ? options.startViewPortCustom : options.startViewPort; 21913 return startEl + " " + startView; 21914 } 21915 21916 this.end = () => { 21917 const endEl = options.endEl === 'custom' ? options.endElCustom : options.endEl; 21918 const endtView = options.endViewPort === 'custom' ? options.endViewPortCustom : options.endViewPort; 21919 return endEl + " " + endtView; 21920 } 21921 21922 21923 this.from = () => { 21924 const propertyMap = { 21925 from_opacity: 'opacity', 21926 from_x: 'x', 21927 from_y: 'y', 21928 from_top: 'top', 21929 from_bottom: 'bottom', 21930 from_right: 'right', 21931 from_left: 'left', 21932 from_scale: 'scale', 21933 from_rotation: 'rotation', 21934 from_rotationX: 'rotationX', 21935 from_rotationY: 'rotationY', 21936 from_skew_x: 'skewX', 21937 from_skew_y: 'skewY', 21938 from_width: 'width', 21939 from_height: 'height', 21940 from_border_width: 'borderWidth', 21941 from_border_radius: 'borderRadius', 21942 from_brightness: 'brightness', 21943 from_contrast: 'contrast', 21944 from_font_size: 'fontSize', 21945 from_line_height: 'lineHeight', 21946 from_letter_spacing: 'letterSpacing', 21947 from_color: 'color', 21948 from_bg_color: 'backgroundColor', 21949 from_grayscale: 'filter', 21950 from_blur: 'blur', 21951 }; 21952 21953 const values = {}; 21954 21955 for (const option in propertyMap) { 21956 if (options[option]) { 21957 if ( filterProperties.includes(option) ) { 21958 21959 values.filter = ''; 21960 21961 if (options.from_brightness) { 21962 values.filter += ` brightness(${options.from_brightness})`; 21963 } 21964 if (options.from_contrast) { 21965 values.filter += ` contrast(${options.from_contrast})`; 21966 } 21967 if (options.from_grayscale) { 21968 values.filter += ` grayscale(${options.from_grayscale})`; 21969 } 21970 if (options.from_blur) { 21971 values.filter += ` blur(${options.from_blur})`; 21972 } 21973 values.filter = values.filter.trim(); 21974 21975 } else { 21976 values[propertyMap[option]] = options[option]; 21977 } 21978 } 21979 } 21980 21981 return values; 21982 }; 21983 21984 21985 this.to = () => { 21986 const propertyMap = { 21987 to_opacity: 'opacity', 21988 to_x: 'x', 21989 to_y: 'y', 21990 to_top: 'top', 21991 to_bottom: 'bottom', 21992 to_right: 'right', 21993 to_left: 'left', 21994 to_scale: 'scale', 21995 to_rotation: 'rotation', 21996 to_rotationX: 'rotationX', 21997 to_rotationY: 'rotationY', 21998 to_skew_x: 'skewX', 21999 to_skew_y: 'skewY', 22000 to_width: 'width', 22001 to_height: 'height', 22002 to_border_width: 'borderWidth', 22003 to_border_radius: 'borderRadius', 22004 to_brightness: 'brightness', 22005 to_contrast: 'contrast', 22006 to_font_size: 'fontSize', 22007 to_line_height: 'lineHeight', 22008 to_letter_spacing: 'letterSpacing', 22009 to_color: 'color', 22010 to_bg_color: 'backgroundColor', 22011 to_grayscale: 'filter', 22012 to_blur: 'blur', 22013 }; 22014 22015 const values = {}; 22016 22017 for (const option in propertyMap) { 22018 if (options[option]) { 22019 22020 if (filterProperties.includes(option)) { 22021 22022 values.filter = ''; 22023 22024 if (options.to_brightness) { 22025 values.filter += ` brightness(${options.to_brightness})`; 22026 } 22027 if (options.to_contrast) { 22028 values.filter += ` contrast(${options.to_contrast})`; 22029 } 22030 if (options.to_grayscale) { 22031 values.filter += ` grayscale(${options.to_grayscale})`; 22032 } 22033 if (options.to_blur) { 22034 values.filter += ` blur(${options.to_blur})`; 22035 } 22036 values.filter = values.filter.trim(); 22037 22038 } else { 22039 values[propertyMap[option]] = options[option]; 22040 } 22041 } 22042 } 22043 22044 return values; 22045 }; 22046 22047 return this.init(); 22048 }, 22049 }); 22050}); 22051 22052} catch (error) { console.log(error); } 22053 22054try { 22055transform.OnReady(function() 22056{ 22057 transform.ItemAdd({ 22058 id: 'pretty-forms', 22059 name: 'Pretty Forms', 22060 options: { 22061 a: {'label': 'Preview in Builder', 'type': 'TOGGLE', 'value': true}, 22062 22063 f1: {'label': 'Enable', 'type': 'TOGGLE', 'value': true, 'group':'Select'},
22064 sa: {'label': 'Background', 'type': 'COLOR', 'value': '#FFFFFF', 'group':'Select'}, 22065 sb: {'label': 'Border Color', 'type': 'COLOR', 'value': '#EAEAEA', 'group':'Select'}, 22066 sc: {'label': 'Border Width', 'type': 'INPUT', 'value': '1px', 'group':'Select', 'units':'px,%'}, 22067 sd: {'label': 'Border Radius', 'type': 'INPUT', 'value': '5px', 'group':'Select', 'units':'px,%'}, 22068 scol: {'label': 'Color', 'type': 'COLOR', 'value': '#000000', 'group':'Select'}, 22069 sfs: {'label': 'Font Size', 'type': 'INPUT', 'value': '16px', 'group':'Select', 'units':'px'}, 22070 se: {'label': 'Height', 'type': 'INPUT', 'value': '50px', 'group':'Select', 'units':'px,%'}, 22071 sf: {'label': 'Sides Padding', 'type': 'INPUT', 'value': '15px', 'group':'Select', 'units':'px,%'}, 22072 sg: {'label': 'Arrow Image', 'type': 'FILE', 'value': 'https://global.divhunt.com/88492b469a358146de29bf51e44133f8_212.svg', 'group':'Select'}, 22073 22074 sda: {'label': 'Background', 'type': 'COLOR', 'value': '#FFFFFF', 'group':'Select Dropdown'}, 22075 sdb: {'label': 'Border Color', 'type': 'COLOR', 'value': '#EAEAEA', 'group':'Select Dropdown'}, 22076 sdc: {'label': 'Border Width', 'type': 'INPUT', 'value': '1px', 'group':'Select Dropdown', 'units':'px,%'}, 22077 sdd: {'label': 'Border Radius', 'type': 'INPUT', 'value': '5px', 'group':'Select Dropdown', 'units':'px,%'}, 22078 sde: {'label': 'Color', 'type': 'COLOR', 'value': '#000000', 'group':'Select Dropdown'}, 22079 sdf: {'label': 'Font Size', 'type': 'INPUT', 'value': '13px', 'group':'Select Dropdown', 'units':'px'}, 22080 sdg: {'label': 'Hover Color', 'type': 'COLOR', 'value': '#000000', 'group':'Select Dropdown'}, 22081 sdh: {'label': 'Hover Background', 'type': 'COLOR', 'value': '#f8f8f8', 'group':'Select Dropdown'}, 22082 22083 22084 f2: {'label': 'Enable', 'type': 'TOGGLE', 'value': true, 'group':'Checkbox'}, 22085 ca: {'label': 'Width', 'type': 'INPUT', 'value': '20px', 'group':'Checkbox', 'units':'px,%'}, 22086 cb: {'label': 'Height', 'type': 'INPUT', 'value': '20px', 'group':'Checkbox', 'units':'px,%'}, 22087 cc: {'label': 'Check Icon Color', 'type': 'COLOR', 'value': '#FFFFFF', 'group':'Checkbox'}, 22088 ci: {'label': 'Border Width', 'type': 'INPUT', 'value': '1px', 'group':'Checkbox', 'units':'px,%'}, 22089 cd: {'label': 'Border Color', 'type': 'COLOR', 'value': '#EAEAEA', 'group':'Checkbox'}, 22090 ce: {'label': 'Border Color (checked)', 'type': 'COLOR', 'value': '#1397FF', 'group':'Checkbox'}, 22091 cf: {'label': 'Background', 'type': 'COLOR', 'value': '#FFFFFF', 'group':'Checkbox'}, 22092 cg: {'label': 'Background (checked)', 'type': 'COLOR', 'value': '#1397FF', 'group':'Checkbox'}, 22093 ch: {'label': 'Border Radius', 'type': 'INPUT', 'value': '2px', 'group':'Checkbox', 'units':'px,%'}, 22094 22095 22096 f3: {'label': 'Enable', 'type': 'TOGGLE', 'value': true, 'group':'Radio'}, 22097 ra: {'label': 'Width', 'type': 'INPUT', 'value': '20px', 'group':'Radio', 'units':'px,%'}, 22098 rb: {'label': 'Height', 'type': 'INPUT', 'value': '20px', 'group':'Radio', 'units':'px,%'}, 22099 ri: {'label': 'Border Width', 'type': 'INPUT', 'value': '1px', 'group':'Radio', 'units':'px,%'}, 22100 rd: {'label': 'Border Color', 'type': 'COLOR', 'value': '#EAEAEA', 'group':'Radio'}, 22101 re: {'label': 'Border Color (checked)', 'type': 'COLOR', 'value': '#1397FF', 'group':'Radio'}, 22102 rf: {'label': 'Background', 'type': 'COLOR', 'value': '#FFFFFF', 'group':'Radio'}, 22103 rg: {'label': 'Background (checked)', 'type': 'COLOR', 'value': '#FFFFFF', 'group':'Radio'}, 22104 rm: {'label': 'Circle Color', 'type': 'COLOR', 'value': '#1397FF', 'group':'Radio'}, 22105 rn: {'label': 'Circle Width', 'type': 'INPUT', 'value': '50%', 'group':'Radio', 'units':'px,%'}, 22106 ro: {'label': 'Circle Height', 'type': 'INPUT', 'value': '50%', 'group':'Radio', 'units':'px,%'}, 22107 }, 22108 22109 code: function(transform, tag, target, options, data, index) 22110 { 22111 this.init = () => { 22112 if (options.a || data.mode === "website") { 22113 this.selects(); 22114 this.multiselects(); 22115 this.checkboxes(); 22116 this.radios(); 22117 } 22118 } 22119 22120 this.checkboxes = () => { 22121 22122 if (!options.f2) {return;} 22123 22124 $(target).find("input[type='checkbox']").each(function(){ 22125 if ($(this).hasClass("cancel-pretty")) {return;} 22126 /* requirements - parent needs to be div, and inside we need exactly checkbox + label */ 22127 var parent = $(this).parent(); 22128 let children = parent.children();
22129 if ( !parent.is('div') ) {return;} 22130 if ( children.length != 2 ) {return;} 22131 if ( children.filter('input').length != 1 && children.filter('label').length != 1 ) {return;} 22132 22133 22134 /* Append fake checkbox (span), which we acutally can edit */ 22135 $(parent).find("label").prepend( 22136 `<span> 22137 <svg viewBox="0 0 87 73" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> 22138 <path d="M84.6741 6.04233L79.0401 1.56395C76.2631 -0.640438 74.649 -0.614913 72.2714 2.2856L31.3228 52.1952L12.2665 36.558C9.63745 34.3768 7.99284 34.4928 5.83135 37.1613L1.48253 42.7535C-0.723604 45.4962 -0.441671 47.0532 2.16621 49.2042L29.3258 71.392C32.1217 73.7124 33.6958 73.4734 35.8573 70.8768L85.3813 12.7251C87.7072 9.96382 87.5663 8.31401 84.6741 6.04233Z" fill="currentColor"/> 22139 </svg> 22140 </span>`); 22141 $(parent).addClass("plugin_form_prettify_checkbox"); 22142 }); 22143 22144 22145 /* Create style for this transform, based on options above */ 22146 let tagId = tag.Get('id'); 22147 let cls = `.t${tagId}`; 22148 let css_id = `plugin_pretty-forms_checkboxes_${tagId}`; 22149 $("#"+css_id).remove(); 22150 22151 let style = ` 22152 <style id="${css_id}"> 22153 ${cls} .plugin_form_prettify_checkbox input[type='checkbox'] + label > span{ 22154 width: ${options.ca}; 22155 height: ${options.cb}; 22156 background: ${options.cf}; 22157 border: ${options.ci} solid ${options.cd}; 22158 border-radius: ${options.ch}; 22159 } 22160 22161 ${cls} .plugin_form_prettify_checkbox input[type='checkbox'] + label > span { 22162 color:${options.cc}; 22163 } 22164 22165 ${cls} .plugin_form_prettify_checkbox input[type='checkbox']:checked + label > span{ 22166 border: 1px solid ${options.ce}; 22167 background: ${options.cg}; 22168 } 22169 22170 </style> 22171 `; 22172 22173 $("body").append(style); 22174 } 22175 22176 22177 this.radios = () => { 22178 22179 if (!options.f3) {return;} 22180 22181 $(target).find("input[type='radio']").each(function(){ 22182 if ($(this).hasClass("cancel-pretty")) {return;} 22183 /* requirements - parent needs to be div, and inside we need exactly radio + label */ 22184 var parent = $(this).parent(); 22185 let children = parent.children(); 22186 if ( !parent.is('div') ) {return;} 22187 if ( children.length != 2 ) {return;} 22188 if ( children.filter('input').length != 1 && children.filter('label').length != 1 ) {return;} 22189 22190 /* Append fake radio (span), which we acutally can edit */ 22191 $(parent).find("label").prepend(`<span> <dot> </dot> </span>`); 22192 $(parent).addClass("plugin_form_prettify_radio"); 22193 }); 22194 22195 22196 /* Create style for this transform, based on options above */ 22197 let tagId = tag.Get('id'); 22198 let cls = `.t${tagId}`; 22199 let css_id = `plugin_pretty-forms_radios_${tagId}`; 22200 $("#"+css_id).remove(); 22201 22202 let style = ` 22203 <style id="${css_id}"> 22204 ${cls} .plugin_form_prettify_radio input[type='radio'] + label > span{ 22205 width: ${options.ra}; 22206 height: ${options.rb}; 22207 background: ${options.rf}; 22208 border: ${options.ri} solid ${options.rd}; 22209 } 22210 22211 ${cls} .plugin_form_prettify_radio input[type='radio']:checked + label > span { 22212 border: ${options.ri}
22212 solid ${options.re}; 22213 background: ${options.rg}; 22214 } 22215 22216 ${cls} .plugin_form_prettify_radio input[type='radio'] + label > span > dot { 22217 background:${options.rm}; 22218 } 22219 22220 ${cls} .plugin_form_prettify_radio input[type='radio']:checked + label > span > dot { 22221 width: ${options.rn}; 22222 height: ${options.ro}; 22223 } 22224 22225 </style> 22226 `; 22227 22228 $("body").append(style); 22229 22230 } 22231 22232 22233 22234 this.selects = () => { 22235 22236 if (!options.f1) {return;} 22237 22238 $(target).find("select:not([multiple])").each(function() { 22239 22240 if ($(this).hasClass("cancel-pretty")) {return;} 22241 22242 let currentVal = $(this)?.find("option[selected]")?.text(); 22243 if (!currentVal) { 22244 currentVal = $(this).attr("placeholder") ?? "Select value"; 22245 } 22246 22247 let $this = $(this); 22248 let $options = $this.find("option"); 22249 let $customSelect = $('<div class="plugin_form_prettify_select"></div>'); 22250 let $opener = $(`<div class="opener"><selected>${currentVal}</selected><img alt="open select arrow" src="${options.sg}"/> </div>`); 22251 let $optionsDropdown = $('<div class="options-dropdown"></div>'); 22252 22253 $customSelect.append($opener); 22254 $customSelect.append($optionsDropdown); 22255 22256 // add options to custom select 22257 $options.each(function() { 22258 let $thisOption = $(this); 22259 let value = $thisOption.val(); 22260 let text = $thisOption.text(); 22261 22262 let $customOption = $('<span value="' + value + '">
22262' + text + '</span>'); 22263 $customOption.data('select', $this); 22264 $customOption.data('selectedText', text); 22265 $customOption.data('selectedValue', value); 22266 22267 $optionsDropdown.append($customOption); 22268 }); 22269 22270 $this.after($customSelect); 22271 $this.hide(); 22272 }); 22273 22274 22275 $(target).find(".plugin_form_prettify_select .opener").on('click', function() { 22276 let wasOpened = $(this).parent().hasClass("opened"); 22277 $(".plugin_form_prettify_select").removeClass("opened"); 22278 $(".plugin_form_prettify_multiselect").removeClass("opened"); 22279 if (wasOpened) { 22280 $(this).parent().removeClass("opened"); 22281 } else { 22282 $(this).parent().addClass("opened"); 22283 22284 } 22285 }); 22286 22287 $(target).find(".plugin_form_prettify_select .options-dropdown span").on('click', function() { 22288 let $this = $(this); 22289 let $select = $this.data('select'); 22290 let selectedText = $this.data('selectedText'); 22291 let selectedValue = $this.data('selectedValue'); 22292 $(this).closest(".plugin_form_prettify_select").removeClass("opened"); 22293 22294 $this.parents('.plugin_form_prettify_select').find('.opener selected').text(selectedText); 22295 $select.val(selectedValue).trigger('change'); 22296 }); 22297 22298 22299 /* Create style for this transform, based on options above */ 22300 let tagId = tag.Get('id'); 22301 let cls = `.t${tagId}`; 22302 let css_id = `plugin_pretty-forms_selects_${tagId}`; 22303 $("#"+css_id).remove(); 22304 22305 let style = ` 22306 <style id="${css_id}"> 22307 22308 22309 ${cls} .plugin_form_prettify_select > .opener { 22310 padding: 0 ${options.sf}; 22311 border-radius: ${options.sd}; 22312 height: ${options.se}; 22313 background: ${options.sa}; 22314 border: ${options.sc} solid ${options.sb}; 22315 color: ${options.scol}; 22316 font-size: ${options.sfs}; 22317 } 22318 22319 ${cls} .plugin_form_prettify_select .options-dropdown { 22320 background: ${options.sda}; 22321 border: ${options.sdc} solid ${options.sdb}; 22322 border-radius: ${options.sdd}; 22323 } 22324 22325 22326 ${cls} .plugin_form_prettify_select .options-dropdown > span { 22327 font-size: ${options.sdf}; 22328 color: ${options.sde}; 22329 } 22330 22331 ${cls} .plugin_form_prettify_select.opened .options-dropdown > span:hover { 22332 background: ${options.sdh}; 22333 color: ${options.sdg}; 22334 } 22335 22336 </style> 22337 `; 22338 22339 $("body").append(style); 22340 22341 } 22342 22343 this.multiselects = () => { 22344 22345 if (!options.f1) {return;} 22346 22347 $(target).find("select[multiple]").each(function() { 22348 22349 if ($(this).hasClass("cancel-pretty")) {return;} 22350 22351 let $this = $(this); 22352 let $options = $this.find("option"); 22353 let $customSelect = $('<div class="plugin_form_prettify_multiselect"></div>'); 22354 let $opener = $(`<div class="opener"><selected>Select value</selected><img alt="open select arrow" src="${options.sg}"/> </div>`); 22355 let $optionsDropdown = $('<div class="options-dropdown"></div>'); 22356 22357 $customSelect.append($opener); 22358 $customSelect.append($optionsDropdown); 22359 22360 // add options to custom select 22361 $options.each(function() { 22362 let $thisOption = $(this); 22363 let value = $thisOption.val(); 22364 let text = $thisOption.text(); 22365 let $customOption = $('<span value="' + value + '">
22365' + text + '</span>'); 22366 22367 $customOption.data('select', $this); 22368 $customOption.data('selectedText', text); 22369 $customOption.data('selectedValue', value); 22370 22371 $optionsDropdown.append($customOption); 22372 }); 22373 22374 $this.after($customSelect); 22375 $this.hide(); 22376 }); 22377 22378 $(target).find(".plugin_form_prettify_multiselect .opener").on('click', function() { 22379 let wasOpened = $(this).parent().hasClass("opened"); 22380 $(".plugin_form_prettify_multiselect").removeClass("opened"); 22381 $(".plugin_form_prettify_select").removeClass("opened"); 22382 if (wasOpened) { 22383 $(this).parent().removeClass("opened"); 22384 } else { 22385 $(this).parent().addClass("opened"); 22386 } 22387 }); 22388 22389 $(target).find(".plugin_form_prettify_multiselect .options-dropdown span").on('click', function() { 22390 let $this = $(this); 22391 let $select = $this.data('select'); 22392 let selectedValue = $this.data('selectedValue'); 22393 22394 22395 $this.toggleClass("dh-active"); 22396 $select.find('option[value="'+selectedValue+'"]').prop('selected', $this.hasClass("dh-active")).trigger('change'); 22397 22398 22399 let $activeOptions = $this.closest('.options-dropdown').find('.dh-active'); 22400 22401 let selectedItemsText = ''; 22402 if($activeOptions.length > 1) { 22403 selectedItemsText = $activeOptions.length + ' Selected items'; 22404 } else if($activeOptions.length == 1) { 22405 selectedItemsText = $activeOptions.first().data('selectedText'); 22406 } else { 22407 selectedItemsText = 'Select value'; 22408 } 22409 $(this).parents('.plugin_form_prettify_multiselect').find('.opener selected').text(selectedItemsText); 22410 22411 }); 22412 22413 22414 22415 22416 /* Create style for this transform, based on options above */ 22417 let tagId = tag.Get('id'); 22418 let cls = `.t${tagId}`; 22419 let css_id = `plugin_pretty-forms_multiselects_${tagId}`; 22420 $("#"+css_id).remove(); 22421 22422 22423 let style = ` 22424 <style id="${css_id}"> 22425 22426 ${cls} .plugin_form_prettify_multiselect > .opener { 22427 padding: 0 ${options.sf}; 22428 border-radius: ${options.sd}; 22429 height: ${options.se}; 22430 background: ${options.sa}; 22431 border: ${options.sc} solid ${options.sb}; 22432 } 22433 22434 ${cls} .plugin_form_prettify_multiselect .options-dropdown { 22435 background: ${options.sda}; 22436 border: ${options.sdc} solid ${options.sdb}; 22437 border-radius: ${options.sdd}; 22438 } 22439 22440 22441 ${cls} .plugin_form_prettify_multiselect .options-dropdown > span { 22442 font-size: ${options.sdf}; 22443 color: ${options.sde}; 22444 } 22445 22446 ${cls} .plugin_form_prettify_multiselect.opened .options-dropdown > span:hover { 22447 background: ${options.sdh}; 22448 color: ${options.sdg}; 22449 } 22450 22451 22452 ${cls} .plugin_form_prettify_multiselect .options-dropdown > span.dh-active { 22453 background: ${options.sdh}; 22454 color: ${options.sdg}; 22455 } 22456 </style> 22457 `; 22458 22459 $("body").append(style); 22460 22461 } 22462 22463 22464 22465 return this; 22466 }, 22467 });
22468}); 22469} catch (error) { console.log(error); } 22470 22471try { 22472$(document).mouseup(function(e) { 22473 var container = $(".plugin_form_prettify_select"); 22474 if (!container.is(e.target) && container.has(e.target).length === 0) { 22475 container.removeClass("opened"); 22476 } 22477}); 22478 22479$(document).mouseup(function(e) { 22480 var container = $(".plugin_form_prettify_multiselect"); 22481 if (!container.is(e.target) && container.has(e.target).length === 0) { 22482 container.removeClass("opened"); 22483 } 22484}); 22485} catch (error) { console.log(error); } 22486 22487try { 22488$(document).on('click','.plugin_form_prettify_checkbox label, .plugin_form_prettify_checkbox span',function(){ 22489 $(this).parent().find("input").trigger("click"); 22490}); 22491 22492$(document).on('click','.plugin_form_prettify_radio label, .plugin_form_prettify_radio span',function(){ 22493 $(this).parent().find("input").trigger("click"); 22494}); 22495} catch (error) { console.log(error); } 22496 22497try { 22498transform.OnReady(function() 22499{ 22500 22501 22502 this.animations = [ 22503 {value: 'fadeIn', title: 'Fade In'}, 22504 {value: 'fadeInDown', title: 'Fade In Down'}, 22505 {value: 'fadeInUp', title: 'Fade In Up'}, 22506 {value: 'fadeInLeft', title: 'Fade In Left'}, 22507 {value: 'fadeInRight', title: 'Fade In Right'}, 22508 ]; 22509 22510 let callbacks = []; 22511 $(window).on('scroll', () => 22512 { 22513 callbacks.forEach((callback) => 22514 { 22515 callback(); 22516 }); 22517 }); 22518 22519 $(document).on('dh/pages/unload', (event, page) => 22520 { 22521 callbacks = []; 22522 }); 22523 22524 22525 transform.ItemAdd({ 22526 id: 'simple-animations', 22527 name: 'Simple Animation', 22528 options: { 22529 a: {'label': 'Type', 'type': 'SELECT', 'value': 'fadeIn', 'values': this.animations}, 22530 b: {'label': 'Delay (ms)', 'type': 'INPUT', 'value': '200'}, 22531 c: {'label': 'Offset (px)', 'type': 'INPUT', 'value': '100'}, 22532 d: {'label': 'Duration (ms)', 'type': 'INPUT', 'value': '600'}, 22533 }, 22534 code: function(transform, tag, target, options, data, index) 22535 { 22536 22537 this.init = () => { 22538 22539 if (data.mode === "website") { 22540 $(target).css("opacity", "0"); 22541 this.startAnimation(target); 22542 callbacks.push(() => { 22543 if(!$(target).hasClass('plugin_simpleScroll-is-animated')) { 22544 this.startAnimation(target); 22545 } 22546 }); 22547 } 22548 22549 } 22550 22551 this.startAnimation = (target) => { 22552 22553 let offset = options.c; 22554 if (!offset) {offset = 0} 22555 const elPos = parseInt ( $(target).offset().top ) + parseInt ( offset ); 22556 const topOfWindow = $(window).scrollTop(); 22557 if (elPos < topOfWindow + $(window).height()) { 22558 $(target).addClass('plugin_simpleScroll-is-animated'); 22559 $(target).css("animation", `${options.a} ${options.d}ms ease ${options.b}ms forwards`); 22560 } 22561 } 22562 22563 22564 22565 22566 return this; 22567 }, 22568 }); 22569}); 22570} catch (error) { console.log(error); } 22571
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.