PageSourceSearch

https://www.bruenigmegasafe.ch/assets/visitor-js?v=1790670592.9074

js bruenigmegasafe.ch collected 2026-09-29 08:30:17 UTC 652,271 bytes, 22,571 lines download raw bytes

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 = {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'};
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 `&lt;script&gt;${p1}&lt;/script&gt;`;
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 + '">&times;</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.