From a58a2e7ca4700d3e23341fde767060a4c2c704a3 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 30 Aug 2026 21:04:37 +0000
Subject: [PATCH] =Minor tweaks to MenuBlock.php
---
assets/css/nav.min.css | 2
build/summary/render.php | 31 ++---
activate.php | 11 -
assets/js/min/uploader.min.js | 2
build/glossary/render.php | 4
build/simple-calendar/render.php | 4
inc/meta/Meta.php | 13 ++
build/menu/style-index-rtl.css | 2
jvb.php | 106 ++++++++--------
assets/js/min/favouritesManager.min.js | 2
inc/managers/IconsManager.php | 36 -----
build/drawer-menu/render.php | 4
inc/helpers/media.php | 1
/dev/null | 2
assets/js/min/form.min.js | 2
src/menu/style.scss | 23 +++
inc/blocks/CustomBlocks.php | 3
build/menu/render.php | 4
assets/js/min/auth.min.js | 2
assets/js/min/notifications.min.js | 2
build/forms/render.php | 52 --------
build/menu/style-index.css | 2
inc/blocks/MenuBlock.php | 45 ++++++-
build/gmbreviews/render.php | 4
24 files changed, 170 insertions(+), 189 deletions(-)
diff --git a/activate.php b/activate.php
index e1453f7..f792f57 100644
--- a/activate.php
+++ b/activate.php
@@ -244,17 +244,6 @@
}
}
-// Optional: Add upgrade routine
-function jvbMaybeUpgrade():void
-{
- $current_version = get_option(BASE.'db_version', '0');
- if (version_compare($current_version, '1.0.0', '<')) {
- jvbActivatePlugin();
- }
-}
-add_action('plugins_loaded', 'jvbMaybeUpgrade');
-
-
add_action('jvb_daily_reset', 'jvbDailyResetAction');
function jvbDailyResetAction(): void
{
diff --git a/assets/css/nav.min.css b/assets/css/nav.min.css
index aa87b57..ea1e0f3 100644
--- a/assets/css/nav.min.css
+++ b/assets/css/nav.min.css
@@ -1 +1 @@
-nav,nav ol,nav ul{--padding:0 1rem;--wrap:nowrap;font-family:var(--heading)}nav,nav a,nav li,nav ol,nav ul,ul.socials{display:flex;flex-direction:var(--dir,row);justify-content:var(--justify,flex-start);align-items:var(--align,center);gap:var(--gap,0);flex-wrap:var(--wrap,nowrap);height:var(--btn,3rem);max-width:100%;padding:0;margin:0}nav.col,nav.col ul{height:max-content}nav>ul{width:100%;overflow:auto hidden}nav li{width:max-content;--justify:center;max-inline-size:none;padding:0;list-style:none}nav.fill li{width:100%}nav a,nav button{--justify:center;width:100%;white-space:nowrap;text-transform:uppercase;border-radius:0;background-color:transparent;text-decoration:none}nav a{padding:var(--padding)}nav .toggle{aspect-ratio:1;border:1px solid rgb(var(--base));color:rgb(var(--contrast))}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav a:focus:hover,nav a:focus:visited:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.toggle .icon-caret-down{transform:rotate(0);transition:transform var(--trans-base)}.open>.row>.toggle .icon-caret-down,.open>.toggle .icon-caret-down{transform:rotate(900deg)}.has-submenu{position:relative}ul.submenu{--dir:column;height:max-content;position:absolute;top:100%;right:0;max-height:0;transform:scaleY(0);transform-origin:top;width:100%;min-width:max-content;background-color:rgba(var(--base),var(--op-3));border:2px solid rgba(var(--base),var(--op-3));transition:max-height var(--trans-base),transform var(--trans-base);box-shadow:var(--shdw-none);overflow:hidden}.submenu li{background-color:rgba(var(--base),var(--op-6));border:1px solid rgb(var(--base-50))}.submenu a{height:var(--chipchip)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}nav a:focus:not(:focus-visible){outline:0}nav a:focus-visible{outline:1px solid rgb(var(--action-0));outline-offset:1px}nav.always{overflow:visible;transition:width var(--trans-base);width:max-content}nav.always.fixed{z-index:var(--z-10)}nav.always>ul{--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;max-height:100vh;position:fixed;right:-300vw;bottom:0;width:100vw;padding:var(--btn) 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{width:100%;height:max-content}nav.always.fixed.open{width:100vw;height:100vh;inset:0;z-index:var(--z-10)}nav.always a{height:max-content;min-height:var(--chipchip)}nav.always li:not(.has-submenu)>a{padding-right:calc(var(--btn) + 1rem)}nav.always .has-submenu{--dir:row;--wrap:wrap;height:max-content}nav.always .has-submenu a{max-width:calc(100% - var(--btn))}nav.always .has-submenu .toggle{max-width:var(--btn)}.has-submenu.open>a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.has-submenu.open>ul{border:1px solid rgb(var(--action-0));background-color:rgb(var(--action-0));--align:flex-start}nav.always ul.submenu{position:relative;top:0}nav.always .submenu a{padding-left:var(--btn)}nav.always.fixed{width:var(--btn);height:var(--btn);bottom:0;right:0;overflow:hidden}nav.always.fixed .toggle.main{background-color:rgb(var(--base))}nav.always.fixed .toggle.main:focus,nav.always.fixed .toggle.main:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.mobile .toggle.main{width:var(--btn);transition:width var(--trans-base)}nav.mobile .icon-list,nav.mobile .icon-x{--w:32px}nav.mobile .icon-x,nav.mobile.open .icon-list{display:none}nav.mobile .icon-list,nav.mobile.open .icon-x{display:block}nav.mobile.open>ul{--dir:column;z-index:var(--z-9);background-color:rgba(var(--base),var(--op-6));width:100vw;height:100vh;overflow:hidden auto;right:0;bottom:0;position:fixed;padding:var(--btn) 0}nav.always>ul::before,nav.mobile.open>ul::before{content:'';z-index:-1;position:absolute;inset:0;filter:blur(5px)}nav.always.open .main.toggle,nav.mobile.open .main.toggle{position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-10);aspect-ratio:unset}nav.always>ul,nav.always>ul:before,nav.mobile.open>ul,nav.mobile.open>ul::before{background-color:rgba(var(--base),var(--op-6))}@media (max-width:767px){nav.col{height:var(--btn)}nav.mobile>ul{--dir:column;--align:center;--justify:flex-end;--gap:0;height:100%;max-height:none;position:relative;right:-300vw;width:100vw;padding:var(--btn) 0 0;overflow:hidden auto}nav.mobile.open>ul{right:0}}@media (min-width:768px){nav.mobile:not(.always) .toggle.main{display:none}}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap;--justify:flex-start}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color:rgb(var(--contrast-200));padding:0 .25rem}nav#breadcrumbs li:last-of-type::after{display:none}nav#breadcrumbs a{height:var(--chip)}nav#breadcrumbs a,nav#breadcrumbs span{padding:0 .125rem;color:rgb(var(--contrast));text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:rgb(var(--action-0))}nav.fixed{position:fixed;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-6)}nav.fixed.bottom{left:0;bottom:0;width:calc(100vw - var(--btn))}nav.fixed:not(.always) ul{--justify:space-between;width:100%;background-color:rgb(var(--base));padding:0 .25rem}nav.fixed:not(.always) li{flex:1}nav.fixed a{--align:center;--gap:1rem;--w:var(--chip_);color:rgb(var(--contrast));font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base),var(--op-45));max-width:none;margin:0!important}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}nav.on-this-page button{order:3;padding:0 1rem;width:max-content;aspect-ratio:unset}nav.on-this-page.open button{order:0}nav.on-this-page ul{width:100%;--gap:0}nav.on-this-page a{padding:0}nav.on-this-page .active a{background-color:rgba(var(--base),var(--op-6));color:rgb(var(--action-contrast))}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.letters,nav.letters a,nav.letters li,nav.letters ul{min-height:var(--chip);height:max-content}nav.letters li{width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{width:inherit}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base),var(--op-6))}nav.index,nav.index a,nav.index button,nav.index li,nav.index ul{min-height:var(--chipchip);height:var(--chipchip)}nav.index ul{width:100%}nav.index li{flex-shrink:0;transform:scaleX(0);max-width:0;overflow:hidden}nav.index li.active,nav.index li.adj{transform:scaleX(1);width:100%;flex-shrink:1;max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open ul{min-height:max-content;--dir:column}nav.index.open li{width:100%;max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:center;padding:0 2rem 0 0}nav.condensed,nav.condensed a,nav.condensed li,nav.condensed ul{height:max-content;width:max-content;--wrap:wrap;min-height:var(--chip)}.condensed ul{--justify:center;--dir:row}nav.condensed{--gap:0 .25rem;width:100%;--justify:center}nav.condensed li+li::before{content:'·';padding:0 .25em}nav.condensed a{font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:transparent;color:rgb(var(--contrast));border-color:rgb(var(--action-0))}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:flex-end;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x;width:100%}ul.socials li{list-style:none}.always ul.socials{width:100vw;--justify:stretch;--gap:0}.always ul.socials li{flex:1;--justify:center;--align:center}.always ul.socials a{display:inline-flex}ul.socials li{flex:1}ul.socials a{display:inline-flex;font-size:var(--txt-x-small);padding:.25rem .5rem;max-width:none;justify-content:center;width:100%}ul.socials .icon{margin:0}ul.socials .icon+span:not(.screen-reader-text){margin-left:.5rem}nav.tabs.tabs.row{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden;--dir:row}nav.tabs button.active{cursor:default}nav.tabs button{font-family:var(--heading);font-size:var(--txt-x-small);border-bottom:4px solid transparent}nav.tabs button.active,nav.tabs button.active:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast));border-color:rgb(var(--base))}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:rgb(var(--base));--gap:0}.tab-content .tab-content nav.tabs{background-color:rgb(var(--base-100))}.tab-content .tab-content .tab-content nav.tabs{background-color:rgb(var(--base-200))}.tab-content nav.tabs button.active h2{color:rgb(var(--action-0))}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}.wp-site-blocks>header,body>header{--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:var(--align,center);justify-content:var(--justify,space-between);padding:0 .5rem;background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}.dashboard-nav{width:100%}nav.filters{--dir:row;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}nav.term-navigation:has([hidden]){display:none}nav.pagination{width:100%}nav.pagination>a{min-width:var(--chipchip)}nav.pagination>ul{margin:0 auto}nav.pagination:not(:has(a+ul)){margin-left:var(--chipchip)}nav.pagination:not(:has(ul+a)){margin-right:var(--chipchip)}.pagination.condensed li+li::before{display:none}.pagination li.current{width:var(--chip_);height:var(--chip_);background-color:rgb(var(--action-0));border-radius:var(--radius);line-height:1}.pagination.condensed a{font-size:var(--txt-medium);width:var(--chip_);height:var(--chip_)}
\ No newline at end of file
+nav,nav ol,nav ul{--padding:0 1rem;--wrap:nowrap;font-family:var(--heading)}nav,nav a,nav li,nav ol,nav ul,ul.socials{display:flex;flex-direction:var(--dir,row);justify-content:var(--justify,flex-start);align-items:var(--align,center);gap:var(--gap,0);flex-wrap:var(--wrap,nowrap);height:var(--btn,3rem);max-width:100%;padding:0;margin:0}nav.col,nav.col ul{height:max-content}nav>ul{width:100%;overflow:auto hidden}nav li{width:max-content;--justify:center;max-inline-size:none;padding:0;list-style:none}nav.fill li{width:100%}nav a,nav button{--justify:center;width:100%;white-space:nowrap;text-transform:uppercase;border-radius:0;background-color:transparent;text-decoration:none}nav a{padding:var(--padding)}nav .toggle{aspect-ratio:1;border:1px solid rgb(var(--base));color:rgb(var(--contrast))}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav a:focus:hover,nav a:focus:visited:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.toggle .icon-caret-down{transform:rotate(0);transition:transform var(--trans-base)}.open>.row>.toggle .icon-caret-down,.open>.toggle .icon-caret-down{transform:rotate(900deg)}.has-submenu{position:relative}ul.submenu{--dir:column;height:max-content;position:absolute;top:100%;right:0;max-height:0;transform:scaleY(0);transform-origin:top;width:100%;min-width:max-content;background-color:rgba(var(--base),var(--op-3));border:2px solid rgba(var(--base),var(--op-3));transition:max-height var(--trans-base),transform var(--trans-base);box-shadow:var(--shdw-none);overflow:hidden}.submenu li{background-color:rgba(var(--base),var(--op-6));border:1px solid rgb(var(--base-50))}.submenu a{height:var(--chipchip)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}nav a:focus:not(:focus-visible){outline:0}nav a:focus-visible{outline:1px solid rgb(var(--action-0));outline-offset:1px}nav.always{overflow:visible;transition:width var(--trans-base);width:max-content}nav.always.fixed{z-index:var(--z-10)}nav.always>ul{--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;max-height:100vh;position:fixed;right:-300vw;bottom:0;width:100vw;padding:var(--btn) 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{width:100%;height:max-content}nav.always.fixed.open{width:100vw;height:100vh;inset:0;z-index:var(--z-10)}nav.always a{height:max-content;min-height:var(--chipchip)}nav.always li:not(.has-submenu)>a{padding-right:calc(var(--btn) + 1rem)}nav.always .has-submenu{--dir:row;--wrap:wrap;height:max-content}nav.always .has-submenu a{max-width:calc(100% - var(--btn))}nav.always .has-submenu .toggle{max-width:var(--btn)}.has-submenu.open>a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.has-submenu.open>ul{border:1px solid rgb(var(--action-0));background-color:rgb(var(--action-0));--align:flex-start}nav.always ul.submenu{position:relative;top:0}nav.always .submenu a{padding-left:var(--btn)}nav.always.fixed{width:var(--btn);height:var(--btn);bottom:0;right:0;overflow:hidden}nav.always.fixed .toggle.main{background-color:rgb(var(--base))}nav.always.fixed .toggle.main:focus,nav.always.fixed .toggle.main:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.mobile .toggle.main{width:var(--btn);transition:width var(--trans-base)}nav.mobile .icon-list,nav.mobile .icon-x{--w:32px}nav.mobile .icon-x,nav.mobile.open .icon-list{display:none}nav.mobile .icon-list,nav.mobile.open .icon-x{display:block}nav.mobile.open>ul{--dir:column;z-index:var(--z-9);background-color:rgba(var(--base),var(--op-6));width:100vw;height:100vh;overflow:hidden auto;right:0;bottom:0;position:fixed;padding:var(--btn) 0}nav.always>ul::before,nav.mobile.open>ul::before{content:'';z-index:-1;position:absolute;inset:0;filter:blur(5px)}nav.always.open .main.toggle,nav.mobile.open .main.toggle{position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-10);aspect-ratio:unset}nav.always>ul,nav.always>ul:before,nav.mobile.open>ul,nav.mobile.open>ul::before{background-color:rgba(var(--base),var(--op-6))}@media (max-width:767px){nav.col{height:var(--btn)}nav.mobile>ul{--dir:column;--align:center;--justify:flex-end;--gap:0;height:100%;max-height:none;position:relative;right:-300vw;width:100vw;padding:var(--btn) 0 0;overflow:hidden auto}nav.mobile.open>ul{right:0}}@media (min-width:768px){nav.mobile:not(.always) .toggle.main{display:none}}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap;--justify:flex-start}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color:rgb(var(--contrast-200));padding:0 .25rem}nav#breadcrumbs li:last-of-type::after{display:none}nav#breadcrumbs a{height:var(--chip)}nav#breadcrumbs a,nav#breadcrumbs span{padding:0 .125rem;color:rgb(var(--contrast));text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:rgb(var(--action-0))}nav.fixed{position:fixed;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-6)}nav.fixed.bottom{left:2rem;bottom:0;width:calc(100vw - var(--btn) - 2rem)}nav.fixed:not(.always) ul{--justify:space-between;width:100%;background-color:rgb(var(--base));padding:0 .25rem}nav.fixed:not(.always) li{flex:1}nav.fixed a{--align:center;--gap:1rem;--w:var(--chip_);color:rgb(var(--contrast));font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base),var(--op-45));max-width:none;margin:0!important}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}nav.on-this-page button{order:3;padding:0 1rem;width:max-content;aspect-ratio:unset}nav.on-this-page.open button{order:0}nav.on-this-page ul{width:100%;--gap:0}nav.on-this-page a{padding:0}nav.on-this-page .active a{background-color:rgba(var(--base),var(--op-6));color:rgb(var(--action-contrast))}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.letters,nav.letters a,nav.letters li,nav.letters ul{min-height:var(--chip);height:max-content}nav.letters li{width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{width:inherit}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base),var(--op-6))}nav.index,nav.index a,nav.index button,nav.index li,nav.index ul{min-height:var(--chipchip);height:var(--chipchip)}nav.index ul{width:100%}nav.index li{flex-shrink:0;transform:scaleX(0);max-width:0;overflow:hidden}nav.index li.active,nav.index li.adj{transform:scaleX(1);width:100%;flex-shrink:1;max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open ul{min-height:max-content;--dir:column}nav.index.open li{width:100%;max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:center;padding:0 2rem 0 0}nav.condensed,nav.condensed a,nav.condensed li,nav.condensed ul{height:max-content;width:max-content;--wrap:wrap;min-height:var(--chip)}.condensed ul{--justify:center;--dir:row}nav.condensed{--gap:0 .25rem;width:100%;--justify:center}nav.condensed li+li::before{content:'·';padding:0 .25em}nav.condensed a{font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:transparent;color:rgb(var(--contrast));border-color:rgb(var(--action-0))}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:flex-end;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x;width:100%}ul.socials li{list-style:none}.always ul.socials{width:100vw;--justify:stretch;--gap:0}.always ul.socials li{flex:1;--justify:center;--align:center}.always ul.socials a{display:inline-flex}ul.socials li{flex:1}ul.socials a{display:inline-flex;font-size:var(--txt-x-small);padding:.25rem .5rem;max-width:none;justify-content:center;width:100%}ul.socials .icon{margin:0}ul.socials .icon+span:not(.screen-reader-text){margin-left:.5rem}nav.tabs.tabs.row{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden;--dir:row}nav.tabs button.active{cursor:default}nav.tabs button{font-family:var(--heading);font-size:var(--txt-x-small);border-bottom:4px solid transparent}nav.tabs button.active,nav.tabs button.active:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast));border-color:rgb(var(--base))}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:rgb(var(--base));--gap:0}.tab-content .tab-content nav.tabs{background-color:rgb(var(--base-100))}.tab-content .tab-content .tab-content nav.tabs{background-color:rgb(var(--base-200))}.tab-content nav.tabs button.active h2{color:rgb(var(--action-0))}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}.wp-site-blocks>header,body>header{--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:var(--align,center);justify-content:var(--justify,space-between);padding:0 .5rem;background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}.dashboard-nav{width:100%}nav.filters{--dir:row;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}nav.term-navigation:has([hidden]){display:none}nav.pagination{width:100%}nav.pagination>a{min-width:var(--chipchip)}nav.pagination>ul{margin:0 auto}nav.pagination:not(:has(a+ul)){margin-left:var(--chipchip)}nav.pagination:not(:has(ul+a)){margin-right:var(--chipchip)}.pagination.condensed li+li::before{display:none}.pagination li.current{width:var(--chip_);height:var(--chip_);background-color:rgb(var(--action-0));border-radius:var(--radius);line-height:1}.pagination.condensed a{font-size:var(--txt-medium);width:var(--chip_);height:var(--chip_)}
\ No newline at end of file
diff --git a/assets/js/min/auth.min.js b/assets/js/min/auth.min.js
index bf093ef..bd60fd3 100644
--- a/assets/js/min/auth.min.js
+++ b/assets/js/min/auth.min.js
@@ -1 +1 @@
-window.auth=new class{constructor(){this.initialized=!1,this.isAuthenticating=!1,this.authenticated=!1,this.user=!1,this.nonces={},this.subscribers=new Set,this.cacheExpiry=3e5,this.init()}async init(){if(!this.isAuthenticating){this.isAuthenticating=!0;try{if("undefined"!=typeof jvbAuth)return this.setAuthData(jvbAuth),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1});await this.fetchAuth()}catch(t){this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}}async refreshNonce(t="wp_rest"){try{return await this.fetchAuth(),this.getNonce(t)}catch(t){return console.error("Failed to refresh nonce:",t),null}}async fetch(t,e={}){const i=async(s=0)=>{const n=e.body instanceof FormData,h={...!n&&{"Content-Type":"application/json"},...e.headers,"X-WP-Nonce":this.getNonce()};n||"object"!=typeof e.body||Object.hasOwn(e.body,"user")||(e.body.user=this.getUser());let a={...e,credentials:"same-origin",headers:h};n||"object"!=typeof a.body||(a.body=JSON.stringify(a.body));const r=await fetch(t,a);if((403===r.status||401===r.status)&&0===s){const t=await r.clone().json();if("rest_cookie_invalid_nonce"===t.code||t.message?.includes("Cookie check"))return console.log("Nonce invalid, refreshing auth..."),await this.refresh(),i(s+1)}return r};return i()}async fetchAuth(){const t=await fetch(`${jvbSettings.api}auth/status`,{method:"GET",credentials:"same-origin",headers:{"Content-Type":"application/json"}});if(!t.ok)throw new Error("Auth check failed");const e=await t.json();this.setAuthData(e),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-loaded",{fromCache:!1})}setAuthData(t){const e=this.initialized&&this.authenticated;this.authenticated=t.authenticated||!1,this.user=t.user||!1,this.nonces=t.nonces||{},console.log(this.nonces),e&&!this.authenticated&&(window.location.href=`/login?redirect_to=${encodeURIComponent(window.location.href)}`)}clearAuthData(){this.authenticated=!1,this.user=null,this.nonces={}}async refresh(){this.isAuthenticating=!0,this.initialized=!1;try{await this.fetchAuth(),this.notify("auth-refreshed",{})}catch(t){console.error("Failed to refresh auth:",t),this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}getNonce(t="wp_rest"){return this.nonces[t]||""}getUser(){return this.user}isAuthenticated(){return this.authenticated}async handleLogin(t=null){if(t)return this.setAuthData(t),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1,fromLogin:!0});await this.refresh()}handleLogout(){this.clearAuthData(),this.notify("logged-out",{})}subscribe(t){return this.subscribers.add(t),this.initialized&&t("auth-loaded",{fromCache:!1,immediate:!0}),()=>this.subscribers.delete(t)}notify(t,e){this.subscribers.forEach(i=>{try{i(t,e)}catch(t){console.error("Subscriber error:",t)}})}getHeader(t){switch(t){case"dash":return{"X-Action-Nonce":this.getNonce("dash")};case"default":return{"X-WP-Nonce":this.getNonce()};default:return{}}}};
\ No newline at end of file
+window.auth=new class{constructor(){this.initialized=!1,this.isAuthenticating=!1,this.authenticated=!1,this.user=!1,this.nonces={},this.subscribers=new Set,this.cacheExpiry=3e5,this.init()}async init(){if(!this.isAuthenticating){this.isAuthenticating=!0;try{if("undefined"!=typeof jvbAuth)return this.setAuthData(jvbAuth),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1});await this.fetchAuth()}catch(t){this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}}async refreshNonce(t="wp_rest"){try{return await this.fetchAuth(),this.getNonce(t)}catch(t){return console.error("Failed to refresh nonce:",t),null}}async fetch(t,i={}){const e=async(s=0)=>{const n=i.body instanceof FormData,h={...!n&&{"Content-Type":"application/json"},...i.headers,"X-WP-Nonce":this.getNonce()};let a={...i,credentials:"same-origin",headers:h};n||"object"!=typeof a.body||(a.body=JSON.stringify(a.body));const r=await fetch(t,a);if((403===r.status||401===r.status)&&0===s){const t=await r.clone().json();if("rest_cookie_invalid_nonce"===t.code||t.message?.includes("Cookie check"))return console.log("Nonce invalid, refreshing auth..."),await this.refresh(),e(s+1)}return r};return e()}async fetchAuth(){const t=await fetch(`${jvbSettings.api}auth/status`,{method:"GET",credentials:"same-origin",headers:{"Content-Type":"application/json"}});if(!t.ok)throw new Error("Auth check failed");const i=await t.json();this.setAuthData(i),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-loaded",{fromCache:!1})}setAuthData(t){const i=this.initialized&&this.authenticated;this.authenticated=t.authenticated||!1,this.user=t.user||!1,this.nonces=t.nonces||{},console.log(this.nonces),i&&!this.authenticated&&(window.location.href=`/login?redirect_to=${encodeURIComponent(window.location.href)}`)}clearAuthData(){this.authenticated=!1,this.user=null,this.nonces={}}async refresh(){this.isAuthenticating=!0,this.initialized=!1;try{await this.fetchAuth(),this.notify("auth-refreshed",{})}catch(t){console.error("Failed to refresh auth:",t),this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}getNonce(t="wp_rest"){return this.nonces[t]||""}getUser(){return this.user}isAuthenticated(){return this.authenticated}async handleLogin(t=null){if(t)return this.setAuthData(t),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1,fromLogin:!0});await this.refresh()}handleLogout(){this.clearAuthData(),this.notify("logged-out",{})}subscribe(t){return this.subscribers.add(t),this.initialized&&t("auth-loaded",{fromCache:!1,immediate:!0}),()=>this.subscribers.delete(t)}notify(t,i){this.subscribers.forEach(e=>{try{e(t,i)}catch(t){console.error("Subscriber error:",t)}})}getHeader(t){switch(t){case"dash":return{"X-Action-Nonce":this.getNonce("dash")};case"default":return{"X-WP-Nonce":this.getNonce()};default:return{}}}};
\ No newline at end of file
diff --git a/assets/js/min/favouritesManager.min.js b/assets/js/min/favouritesManager.min.js
index ddf9442..edab0ea 100644
--- a/assets/js/min/favouritesManager.min.js
+++ b/assets/js/min/favouritesManager.min.js
@@ -1 +1 @@
-window.favouritesManager=class{constructor(){this.queue=window.jvbQueue,this.loadingManager=window.jvbLoading,this.cache=window.jvbCache,this.a11y=window.jvbA11y,this.error=window.jvbError,this.tabs=new window.jvbTabs(document.querySelector(".replace")),this.config={endpoints:{favourites:"favourites",lists:"favourites/lists",shares:"favourites/lists/shares"},selectors:{container:".favourites.container",itemsTab:'.tab-content[data-tab="items"]',listsTab:'.tab-content[data-tab="lists"]',grid:".item-grid",typeFilters:".type-filters",viewControls:".view-controls",bulkControls:".bulk-controls",selectAll:"#select-all",createListModal:".create-list-modal",addToListModal:".add-to-list-modal",shareListModal:".share-list-modal",noItems:".no-favourites",listContainer:".lists-container",listDetails:".list-details",loader:".favourites-loader"},defaultPage:1,defaultPerPage:24,defaultViewMode:"grid",refreshInterval:6e4,toastDuration:3e3},document.addEventListener("keydown",this.handleKeyDown.bind(this)),this.state={selectedItems:new Set,page:this.config.defaultPage,filter:{type:"all",order:"desc",orderBy:"date_added"},view:{mode:localStorage.getItem("favourites_view")||this.config.defaultViewMode,activeTab:"items"},pagination:{hasMore:!1,totalItems:0,totalPages:0},currentListId:null,loading:!1,initialized:!1},this.initDom(),this.initEvents(),this.loadInitialData(),this.state.initialized=!0}initDom(){this.container=document.querySelector(this.config.selectors.container),this.container?(this.grid=this.container.querySelector(this.config.selectors.grid),this.typeFilters=this.container.querySelector(this.config.selectors.typeFilters),this.viewControls=this.container.querySelector(this.config.selectors.viewControls),this.bulkControls=this.container.querySelector(this.config.selectors.bulkControls),this.listContainer=this.container.querySelector(this.config.selectors.listContainer),this.listDetails=this.container.querySelector(this.config.selectors.listDetails),this.loader=this.container.querySelector(this.config.selectors.loader),this.createListModal=document.querySelector(this.config.selectors.createListModal),this.addToListModal=document.querySelector(this.config.selectors.addToListModal),this.shareListModal=document.querySelector(this.config.selectors.shareListModal),this.grid&&this.state.view.mode&&this.grid.classList.add(`${this.state.view.mode}-view`)):console.warn("Favourites container not found")}initEvents(){if(this.typeFilters&&this.typeFilters.addEventListener("click",t=>{const e=t.target.closest(".type-filter");e&&this.setFilterType(e.dataset.type)}),this.viewControls&&this.viewControls.addEventListener("click",t=>{const e=t.target.closest(".view-toggle");e&&this.setView(e.dataset.view)}),this.container){const t=this.container.querySelector(this.config.selectors.selectAll);t&&t.addEventListener("change",()=>{this.toggleSelectAll(t.checked)}),this.container.addEventListener("change",t=>{t.target.matches(".item-select input[type=checkbox]")&&this.handleItemSelection(t.target)});const e=this.container.querySelector(".bulk-action-select"),s=this.container.querySelector(".apply-bulk");e&&s&&s.addEventListener("click",()=>{this.applyBulkAction(e.value)});const i=this.container.querySelector(".cancel-bulk");i&&i.addEventListener("click",()=>{this.clearSelection()})}this.initModalEvents(),this.container.addEventListener("click",this.handleItemActions.bind(this)),this.grid&&this.setupInfiniteScroll()}initModalEvents(){if(this.createListModal){const t=this.createListModal.querySelector("form");t&&t.addEventListener("submit",e=>{e.preventDefault(),this.handleCreateList(new FormData(t))});const e=this.createListModal.querySelector(".cancel");e&&e.addEventListener("click",()=>{this.createListModal.close()})}if(this.addToListModal){const t=this.addToListModal.querySelector("form");t&&t.addEventListener("submit",e=>{e.preventDefault(),this.handleAddToList(new FormData(t))});const e=this.addToListModal.querySelector(".cancel");e&&e.addEventListener("click",()=>{this.addToListModal.close()})}if(this.shareListModal){const t=this.shareListModal.querySelector("form");t&&t.addEventListener("submit",e=>{e.preventDefault(),this.handleShareList(new FormData(t))});const e=this.shareListModal.querySelector(".cancel");e&&e.addEventListener("click",()=>{this.shareListModal.close()});const s=this.shareListModal.querySelector(".add-email");s&&s.addEventListener("click",()=>{const e=this.shareListModal.querySelector("#share-email");e&&e.value&&this.handleShareList(new FormData(t))})}}setupInfiniteScroll(){let t=this.container.querySelector(".scroll-sentinel");t||(t=document.createElement("div"),t.className="scroll-sentinel",t.setAttribute("aria-hidden","true"),this.grid.parentNode.appendChild(t)),new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting&&this.state.pagination.hasMore&&!this.state.loading&&(this.state.page++,this.loadFavourites())})},{rootMargin:"200px"}).observe(t)}async loadInitialData(){this.loadingManager.show();try{await this.loadFavourites(),this.loadLists().catch(t=>{console.error("Error loading lists:",t)})}catch(t){this.handleError(t,"loading initial data")}finally{this.loadingManager.hide()}}async loadFavourites(t=!0){if(!this.state.loading)try{this.state.loading=!0,this.loadingManager.show();const e=new URLSearchParams({page:this.state.page,per_page:this.config.defaultPerPage,type:"all"!==this.state.filter.type?this.state.filter.type:"",order:this.state.filter.order,orderby:this.state.filter.orderBy});t&&(this.state.page=1,removeChildren(this.grid),this.grid.classList.remove("empty"));const s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.favourites}?${e}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"favouritesManager",forceRefresh:!0});return this.renderFavourites(s.favourites||[],this.state.page>1),s.counts&&this.updateTypeFilters(s.counts),s.pagination&&(this.state.pagination={hasMore:s.pagination.has_more,totalItems:s.pagination.total_items,totalPages:s.pagination.total_pages}),s}catch(t){throw this.handleError(t,"loading favourites"),t}finally{this.state.loading=!1,this.loadingManager.hide()}}renderFavourites(t,e=!1){this.grid&&(0!==t.length||e?(this.hideEmptyState(),e||removeChildren(this.grid),t.forEach(t=>{const e=this.createItemElement(t);this.grid.appendChild(e),this.initItemFunctionality(e,t)}),window.jvbA11y&&window.jvbA11y.announce(`${e?"Added":"Loaded"} ${t.length} favourites`)):this.showEmptyState())}createItemElement(t){const e=document.createElement("div");e.className=`item ${t.type} favourited`,e.dataset.id=t.target_id,e.dataset.type=t.type;const s=sanitizeHtml(t.title||!1),i=sanitizeHtml(t.notes||"");let a="";return t.thumbnail&&(a=`\n <div class="item-thumbnail">\n <a href="${t.url}">${t.thumbnail}</a>\n </div>\n `),e.innerHTML=`\n <div class="item-select">\n <input type="checkbox"\n class="favourite-checkbox"\n id="select-${t.target_id}"\n value="${t.target_id}">\n <label for="select-${t.target_id}"><span class="screen-reader-text">Select this ${t.type}</span</label>\n </div>\n\n <button type="button" class="favourite-button favourited"\n onclick="toggleFavourite(this)"\n data-id="${t.target_id}"\n data-type="${t.type}"\n title="Remove from favourites">\n ${jvbSettings.icons["heart-filled"]}\n </button>\n\n ${a}\n\n <div class="item-info">\n ${s?`<h3><a href="${t.url}">${s}</a></h3>`:`<a href="${t.url}">View Item</a>`}\n\n ${t.author?`\n <div class="item-artist">\n <span>By ${t.author.name}</span>\n </div>`:""}\n\n ${t.taxonomies?.length?`\n <div class="taxonomy-lists">\n ${t.taxonomies.map(t=>`\n <div class="taxonomy-group">\n ${jvbSettings.icons[t.icon]}\n <ul>\n ${t.terms.slice(0,3).map(t=>`\n <li>\n <a href="${t.url}" ${t.umami_click}>\n ${t.title}\n </a>\n </li>\n `).join("")}\n </ul>\n </div>\n `).join("")}\n </div>\n `:""}\n\n <div class="notes-section">\n <button type="button" class="toggle-notes" aria-expanded="false">\n ${jvbSettings.icons.note||"Notes"}\n <span>Notes</span>\n </button>\n\n <div class="notes-content" hidden>\n <textarea class="notes-input"\n placeholder="Add notes about this item..."\n data-id="${t.target_id}"\n data-type="${t.type}">${i}</textarea>\n <button type="button" class="save-notes">Save Notes</button>\n </div>\n </div>\n </div>\n `,e}initItemFunctionality(t,e){const s=t.querySelector(".toggle-notes"),i=t.querySelector(".notes-content");s&&i&&s.addEventListener("click",()=>{const t="true"===s.ariaExpanded;s.ariaExpanded=!t.toString(),i.hidden=t,t||i.querySelector("textarea")?.focus()});const a=t.querySelector(".save-notes"),n=t.querySelector(".notes-input");a&&n&&(a.addEventListener("click",()=>{this.saveNotes(n)}),n.addEventListener("keydown",t=>{"Enter"===t.key&&(t.ctrlKey||t.metaKey)&&(t.preventDefault(),this.saveNotes(n))}))}saveNotes(t){if(!t)return;const e=t.value.trim(),s=t.dataset.id,i=t.dataset.type;s&&i&&(this.queue.addToQueue({type:"favourite_notes",data:{type:i,target_id:parseInt(s),notes:e}}),showToast("Notes saved"),this.a11y.announce("Notes saved"))}showEmptyState(t=!1){const e=this.container.querySelector(this.config.selectors.noItems)??this.createEmptyElement;e&&(e.hidden=!1),this.grid&&this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}hideEmptyState(){const t=this.container.querySelector(".no-favourites");t&&t.remove(),this.grid&&this.grid.classList.remove("empty")}createEmptyElement(t=!1){const e=document.createElement("div");e.className="no-favourites",e.innerHTML="\n <h3>♡ BLANK CANVAS ♡</h3>\n <p>You haven't fallen in love with any pieces... yet!</p>\n <p>Hit that heart icon when something stops your scroll.</p>\n <p>Your dream collection is waiting to start.</p>\n ",this.grid.after(e)}showEmptyListState(t=!1){const e=document.createElement("div");e.className="no-favourites",e.innerHTML="\n <h3>♡ FULL OF POSSIBILITY ♡</h3>\n <p>There's nothing in this list yet.</p>\n <p>Add some gap fillers from the main favourites tab.</p>\n ",this.grid.after(e),this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}async loadMoreItems(){!this.state.loading&&this.state.pagination.hasMore&&(this.state.page+=1,await this.loadFavourites())}updateTypeFilters(t){this.typeFilters&&this.typeFilters.querySelectorAll(".type-filter").forEach(e=>{const s=e.querySelector(".count");if(!s)return;const i=e.dataset.type;if("all"===i){const e=Object.values(t).reduce((t,e)=>t+(parseInt(e)||0),0);s.textContent=`(${e})`}else s.textContent=`(${t[i]||0})`})}setFilterType(t){t!==this.state.filter.type&&(this.typeFilters&&this.typeFilters.querySelectorAll(".type-filter").forEach(e=>{e.classList.toggle("active",e.dataset.type===t),e.setAttribute("aria-selected",e.dataset.type===t)}),this.state.filter.type=t,this.state.page=1,this.loadFavourites(),window.jvbA11y&&window.jvbA11y.announce(`Filtered to show ${"all"===t?"all":t} items`))}setView(t){t!==this.state.view.mode&&(this.viewControls&&this.viewControls.querySelectorAll(".view-toggle").forEach(e=>{const s=e.dataset.view===t;e.setAttribute("aria-pressed",s)}),this.grid&&(this.grid.classList.remove("grid-view","list-view"),this.grid.classList.add(`${t}-view`)),this.state.view.mode=t,localStorage.setItem("favourites_view",t),window.jvbA11y&&window.jvbA11y.announce(`Changed to ${t} view`))}toggleSelectAll(t){const e=this.getVisibleItems();e.forEach(e=>{const s=e.querySelector('.item-select input[type="checkbox"]');s&&(s.checked=t,this.toggleItemSelection(s.value,t))}),this.updateBulkControls(),window.jvbA11y&&window.jvbA11y.announce(t?`Selected all ${e.length} items`:"Deselected all items")}getVisibleItems(){return this.grid?Array.from(this.grid.querySelectorAll(".item:not([hidden])")):[]}toggleItemSelection(t,e){e?this.state.selectedItems.add(t):this.state.selectedItems.delete(t);const s=this.grid.querySelector(`.item[data-id="${t}"]`);s&&s.classList.toggle("selected",e)}handleItemSelection(t){const e=t.checked,s=t.value;if(this.toggleItemSelection(s,e),this.updateBulkControls(),this.updateSelectAllState(),window.jvbA11y){const s=t.closest(".item"),i=s&&s.querySelector("h3")?.textContent||"item";window.jvbA11y.announce(e?`Selected ${i}`:`Deselected ${i}`)}}updateSelectAllState(){const t=this.container.querySelector(this.config.selectors.selectAll);if(!t)return;const e=this.getVisibleItems();if(0===e.length)return t.checked=!1,void(t.indeterminate=!1);const s=e.filter(t=>{const e=t.querySelector('.item-select input[type="checkbox"]');return e&&e.checked}).length;0===s?(t.checked=!1,t.indeterminate=!1):s===e.length?(t.checked=!0,t.indeterminate=!1):(t.checked=!1,t.indeterminate=!0)}updateBulkControls(){if(!this.bulkControls)return;const t=this.bulkControls.querySelector(".bulk-actions");if(!t)return;const e=this.state.selectedItems.size>0;t.hidden=!e;const s=this.bulkControls.querySelector(".selected-count");s&&(s.textContent=e?`${this.state.selectedItems.size} selected`:"")}handleKeyDown(t){"Escape"===t.key&&this.state.selectedItems.size>0&&(t.preventDefault(),this.clearSelection(),window.jvbA11y&&window.jvbA11y.announce("Selection cleared using Escape key"))}clearSelection(){this.state.selectedItems.clear(),this.getVisibleItems().forEach(t=>{const e=t.querySelector('.item-select input[type="checkbox"]');e&&(e.checked=!1),t.classList.remove("selected")});const t=this.container.querySelector(this.config.selectors.selectAll);t&&(t.checked=!1,t.indeterminate=!1),this.updateBulkControls(),window.jvbA11y&&window.jvbA11y.announce("Selection cleared")}applyBulkAction(t){if(!t||0===this.state.selectedItems.size)return;switch(t){case"unfavourite":this.bulkUnfavourite();break;case"add-to-list":this.showAddToListModal();break;case"create-list":this.showCreateListModal();break;case"add-notes":this.showBulkNotesModal()}const e=this.container.querySelector(".bulk-action-select");e&&(e.value="")}handleItemActions(t){if(t.target.closest(".toggle-notes")){const e=t.target.closest(".toggle-notes"),s="true"===e.getAttribute("aria-expanded"),i=e.closest(".notes-section").querySelector(".notes-content");return e.setAttribute("aria-expanded",!s),i.hidden=s,!s&&i&&i.querySelector("textarea")?.focus(),void t.preventDefault()}if(t.target.closest(".save-notes")){const e=t.target.closest(".save-notes").closest(".notes-content").querySelector("textarea");return e&&this.saveNotes(e),void t.preventDefault()}if(t.target.closest(".view-list")){const e=t.target.closest(".view-list").closest(".list-card");return e&&e.dataset.id&&this.viewList(e.dataset.id),void t.preventDefault()}if(t.target.closest(".share-list")){const e=t.target.closest(".share-list").closest(".list-card");return e&&e.dataset.id&&this.showShareModal(e.dataset.id),void t.preventDefault()}if(t.target.closest(".delete-list")){const e=t.target.closest(".delete-list").closest(".list-card");return e&&e.dataset.id&&this.deleteList(e.dataset.id),void t.preventDefault()}if(t.target.closest(".back-to-lists"))return this.exitListView(),void t.preventDefault()}async loadLists(t=!0){try{this.state.loading=!0,this.loadingManager.show("Loading lists...");const t=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"favourite-lists",forceRefresh:!1});return t.lists&&this.renderLists(t.lists),t}catch(t){throw this.handleError(t,"loading lists"),t}finally{this.state.loading=!1,this.loadingManager.hide()}}renderLists(t){if(!this.listContainer)return;if(removeChildren(this.listContainer),!t||0===t.length)return void(this.listContainer.innerHTML='\n <div class="no-lists">\n <h3>No Lists Yet</h3>\n <p>Select favourites from the main tab to organize into lists.</p>\n </div>\n ');const e=t.owned,s=t.shared;if(e.length>0){const t=document.createElement("details");t.className="lists-section owned-lists",t.open=!0,t.innerHTML="<summary>Your Lists:</summary>",e.forEach(e=>{const s=this.createListCard(e);t.appendChild(s)}),this.listContainer.appendChild(t)}if(s.length>0){const t=document.createElement("details");t.className="lists-section shared-lists",t.innerHTML="<summary>Lists Shared with You:</summary>",s.forEach(e=>{const s=this.createListCard(e);t.appendChild(s)}),this.listContainer.appendChild(t)}}createListCard(t){const e=document.createElement("div");e.className="list-card",e.dataset.id=t.id;const s="1"===t.is_shared;s&&e.classList.add("shared"),t.is_temp&&e.classList.add("temp"),t.is_owner;const i=sanitizeHtml(t.name||"Untitled List"),a=sanitizeHtml(t.description||"");return e.innerHTML=`\n <div class="list-header">\n <h3>${i}</h3>\n <div class="list-actions">\n <button type="button" class="view-list" title="View List">\n ${jvbSettings.icons?.show||"View"}\n </button>\n ${s?"":`\n <button type="button" class="share-list" title="Share List">\n ${jvbSettings.icons?.share||"Share"}\n </button>\n <button type="button" class="delete-list" title="Delete List">\n ${jvbSettings.icons?.delete||"Delete"}\n </button>\n `}\n </div>\n </div>\n\n ${a?`<p class="list-description">${a}</p>`:""}\n\n <div class="list-meta">\n <div class="meta-stats">\n <span class="item-count">${t.item_count||0} items</span>\n <span class="date">${formatDate(t.created_at)}</span>\n </div>\n\n\n ${s?`\n <div class="owner-info">\n Shared by ${t.owner_name||"another user"}\n </div>\n `:t.share_count>0?`\n <div class="share-info">\n Shared with ${t.share_count} ${1===t.share_count?"person":"people"}\n </div>\n `:""}\n </div>\n `,e}async viewList(t){try{this.state.loading=!0,this.loadingManager.show("Loading list..."),this.state.currentListId=t;const e=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}?id=${t}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"list-item",forceRefresh:!1});if(!e.list)throw new Error("List not found");this.showListDetails(e.list)}catch(t){this.handleError(t,"viewing list")}finally{this.state.loading=!1,this.loadingManager.hide()}}showListDetails(t){this.listDetails&&this.listContainer&&(console.log(t),this.listDetails.querySelector(".list-title").value=t.name||"Untitled List",this.listDetails.querySelector(".list-description").value=t.description||"",t.is_owner?this.listDetails.querySelector(".list-actions")||this.createListActions():this.listDetails.querySelector(".list-actions")?.remove(),removeChildren(this.grid),this.renderFavourites(t.items||[],!1),0===t.items.length&&this.showEmptyListState(),window.jvbA11y&&window.jvbA11y.announce(`Viewing list: ${t.name} with ${t.items?.length||0} items`))}createListActions(){const t=document.createElement("div");t.className="list-actions",t.innerHTML='\n <button type="button" class="share-list" title="Share List">\n <i class="icon icon-share-fat"></i>\n <span>Share</span>\n </button>\n <button type="button" class="duplicate-list" title="Duplicate List">\n <i class="icon icon-copy"></i>\n <span>Duplicate</span>\n </button>\n <button type="button" class="delete-list" title="Delete List">\n <i class="icon icon-trash"></i>\n <span>Delete</span>\n </button>\n ',this.listDetails.insertBefore(t,this.listDetails.querySelector(".bulk-controls"))}exitListView(){this.listDetails&&this.listContainer&&(this.listDetails.hidden=!0,this.listContainer.hidden=!1,this.container.classList.remove("viewing-list"),this.state.currentListId=null,window.jvbA11y&&window.jvbA11y.announce("Returned to lists view"))}showCreateListModal(){this.createListModal&&(this.createListModal.querySelector("form")?.reset(),this.createListModal.showModal(),setTimeout(()=>{this.createListModal.querySelector("#list-name")?.focus()},100),window.jvbA11y&&window.jvbA11y.announce("Create list dialog opened"))}showAddToListModal(){this.addToListModal&&(this.populateAddToListModal(),this.addToListModal.showModal(),window.jvbA11y&&window.jvbA11y.announce("Add to list dialog opened"))}async populateAddToListModal(){if(!this.addToListModal)return;const t=this.addToListModal.querySelector(".lists-options");if(t){removeChildren(t);try{const e=(await this.loadLists()).lists.owned;if(0===e.length)return t.innerHTML='\n <div class="no-lists">\n <p>You don\'t have any lists yet.</p>\n <button type="button" class="create-list-button">Create a list</button>\n </div>\n ',void t.querySelector(".create-list-button")?.addEventListener("click",()=>{this.addToListModal.close(),this.showCreateListModal()});e.forEach(e=>{const s=document.createElement("div");s.className="list-option",s.innerHTML=`\n <input type="checkbox" id="${e.id}" name="list_ids[]" value="${e.id}">\n <label for="${e.id}">\n\n <span class="list-name">${sanitizeHtml(e.name)}</span>\n <span class="item-count">( ${e.item_count||0} items )</span>\n </label>\n `,t.appendChild(s)})}catch(e){t.innerHTML='\n <div class="error-message">\n <p>Error loading lists. Please try again.</p>\n </div>\n ',console.error("Error loading lists for modal:",e)}}}async handleCreateList(t){const e=t.get("list_name"),s=t.get("list_description");if(e)try{this.showLoader("Creating list...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})}),this.queue.addToQueue({type:"favourite_list_create",data:{name:e,description:s,items:t}}),showToast(`List "${e}" created`),this.a11y.announce(`List ${e} created with ${t.length} items`),this.createListModal.close(),this.clearSelection(),this.switchTab("lists")}catch(t){this.handleError(t,"creating list")}finally{this.hideLoader()}else showToast("Please enter a list name","error")}async handleAddToList(t){const e=t.getAll("list_ids[]");if(e.length)try{this.showLoader("Adding to list...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})}),this.queue.addToQueue({type:"favourite_list_add",data:{list_id:e.join(","),items:t}}),showToast(`Added to ${e.length} ${1===e.length?"list":"lists"}`),this.a11y.announce(`Added ${t.length} items to ${e.length} ${1===e.length?"list":"lists"}`),this.addToListModal.close(),this.clearSelection()}catch(t){this.handleError(t,"adding to list")}finally{this.hideLoader()}else showToast("Please select at least one list","error")}async handleRemoveFromList(t){const e=t.getAll("list_ids[]");if(e.length)try{this.showLoader("Removing from list...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})}),this.queue.addToQueue({type:"favourite_list_remove",data:{list_id:e.join(","),items:t}}),showToast(`Removed from ${e.length} ${1===e.length?"list":"lists"}`),this.a11y.announce(`Removed ${t.length} items to ${e.length} ${1===e.length?"list":"lists"}`),this.addToListModal.close(),this.clearSelection()}catch(t){this.handleError(t,"remove from list")}finally{this.hideLoader()}else showToast("Please select at least one list","error")}showShareModal(t){this.shareListModal&&(this.state.currentListId=t,this.shareListModal.querySelector("form")?.reset(),this.loadSharedUsers(t),this.shareListModal.showModal(),setTimeout(()=>{this.shareListModal.querySelector("#share-email")?.focus()},100),window.jvbA11y&&window.jvbA11y.announce("Share list dialog opened"))}async loadSharedUsers(t){try{const e=this.shareListModal.querySelector(".shared-users");if(!e)return;e.innerHTML='<div class="loading">Loading shared users...</div>';const s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}?id=${t}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"list-item",forceRefresh:!1});removeChildren(e),s.list&&s.list.shared_users&&s.list.shared_users.length>0?(s.list.shared_users.forEach(t=>{const s=document.createElement("div");s.className=`shared-user ${t.status}`,s.innerHTML=`\n <span class="user-email">${t.email}</span>\n ${"pending"===t.status?'<span class="pending-badge">Invitation sent</span>':`<span class="permission-badge">${t.permission_type||"view"}</span>`}\n <button type="button" class="remove-share" data-email="${t.email}">\n ${jvbSettings.icons?.delete||"Remove"}\n </button>\n `,e.appendChild(s)}),e.querySelectorAll(".remove-share").forEach(t=>{t.addEventListener("click",()=>{this.unshareList(t.dataset.email)})})):e.innerHTML='<div class="no-shares">This list is not shared with anyone yet.</div>'}catch(t){console.error("Error loading shared users:",t)}}async unshareList(t){if(confirm(`Remove ${t}'s access to this list?`))if(this.state.currentListId)try{this.showLoader("Removing access..."),this.queue.addToQueue({type:"favourite_list_unshare",data:{list_id:parseInt(this.state.currentListId),email:t}});const e=Array.from(this.shareListModal.querySelectorAll(".shared-user")).find(e=>e.querySelector(".user-email")?.textContent===t);e&&(e.classList.add("removing"),setTimeout(()=>{if(e.remove(),0===this.shareListModal.querySelectorAll(".shared-user").length){const t=this.shareListModal.querySelector(".shared-users");t&&(t.innerHTML='<div class="no-shares">This list is not shared with anyone yet.</div>')}},300)),showToast(`Removed ${t}'s access`),this.a11y.announce(`Removed ${t}'s access to list`)}catch(t){this.handleError(t,"removing share access")}finally{this.hideLoader()}else showToast("No list selected","error")}async deleteList(t){if(confirm("Are you sure you want to delete this list? This cannot be undone."))try{this.showLoader("Deleting list..."),this.queue.addToQueue({type:"favourite_list_delete",data:{list_id:parseInt(t)}});const e=this.container.querySelector(`.list-card[data-id="${t}"]`);e&&(e.classList.add("removing"),setTimeout(()=>{e.remove(),0===this.container.querySelectorAll(".list-card").length&&(this.listContainer.innerHTML='\n <div class="no-lists">\n <h3>No Lists Yet</h3>\n <p>Create your first list to organize your favourites!</p>\n </div>\n ')},300)),showToast("List deleted"),this.a11y.announce("List deleted")}catch(t){this.handleError(t,"deleting list")}finally{this.hideLoader()}}showBulkNotesModal(){let t=document.querySelector(".bulk-notes-modal");t||(t=document.createElement("dialog"),t.className="bulk-notes-modal",t.innerHTML='\n <form method="dialog" data-save="favourites">\n <h2>Add Notes to Selected Items</h2>\n\n <div class="field">\n <label for="bulk-notes">Notes (will be applied to all selected items)</label>\n <textarea id="bulk-notes" name="bulk_notes" rows="5"></textarea>\n </div>\n\n <div class="actions">\n <button type="button" class="cancel">Cancel</button>\n <button type="submit" class="save">Save Notes</button>\n </div>\n </form>\n ',document.body.appendChild(t),t.querySelector("form").addEventListener("submit",e=>{e.preventDefault();const s=t.querySelector("#bulk-notes").value;this.saveBulkNotes(s),t.close()}),t.querySelector(".cancel").addEventListener("click",()=>{t.close()})),t.querySelector("form")?.reset(),t.showModal(),setTimeout(()=>{t.querySelector("#bulk-notes")?.focus()},100),window.jvbA11y&&window.jvbA11y.announce("Add notes dialog opened")}saveBulkNotes(t){if(t)try{this.showLoader("Saving notes...");let e=Array.from(this.state.selectedItems.values());this.queue.addToQueue({type:"favourite_notes",data:{target_id:e.join(","),notes:t}}),showToast(`Notes saved for ${e.length} items`),this.a11y.announce(`Notes saved for ${e.length} items`),this.clearSelection()}catch(t){this.handleError(t,"saving bulk notes")}finally{this.hideLoader()}}async bulkUnfavourite(){if(confirm("Are you sure you want to remove these items from your favourites?"))try{this.showLoader("Removing from favourites...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);if(!s)return;const i=s.dataset.type;t.push({target_id:parseInt(e),type:i,action:"remove"})}),this.queue.addToQueue({type:"favourite_toggle",data:t});const e=[];this.state.selectedItems.forEach(t=>{const s=this.grid.querySelector(`.item[data-id="${t}"]`);if(!s)return;s.style.opacity="0",s.style.transform="scale(0.9)",s.style.transition="opacity 0.3s ease, transform 0.3s ease";const i=new Promise(t=>{setTimeout(()=>{s.remove(),t()},300)});e.push(i)}),await Promise.all(e),0===this.grid.children.length&&this.showEmptyState(),this.clearSelection(),showToast(`Removed ${t.length} items from favourites`),this.a11y.announce(`Removed ${t.length} items from favourites`)}catch(t){this.handleError(t,"removing favourites")}finally{this.hideLoader()}}async handleShareList(t){const e=t.get("share_email");if(e)if(this.validateEmail(e))try{this.showLoader("Sharing list..."),this.queue.addToQueue({type:"favourite_list_share",data:{list_id:parseInt(this.state.currentListId),email:e,permission_type:"view"}}),this.shareListModal.querySelector("#share-email").value="",this.loadSharedUsers(this.state.currentListId),showToast(`Invitation sent to ${e}`),this.a11y.announce(`Invitation sent to ${e}`)}catch(t){this.handleError(t,"sharing list")}finally{this.hideLoader()}else showToast("Please enter a valid email address","error");else showToast("Please enter an email address","error")}showLoader(t="Loading..."){if(!this.loader)return;const e=this.loader.querySelector(".loader-message");e&&(e.textContent=t),this.loader.hidden=!1}hideLoader(){this.loader&&(this.loader.hidden=!0)}showToast(t,e){window.jvbNotifications.showToast(t,e)}handleError(t,e){console.error(`Favourites error (${e}):`,t),showToast(`Error ${e}: ${t.message||"Something went wrong"}`,"error"),window.jvbError&&window.jvbError.log(t,{component:"FavouritesManager",action:e}),window.jvbA11y&&window.jvbA11y.announce(`Error ${e}. ${t.message||"Please try again."}`)}validateEmail(t){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}};
\ No newline at end of file
+window.favouritesManager=class{constructor(){this.queue=window.jvbQueue,this.loadingManager=window.jvbLoading,this.cache=window.jvbCache,this.a11y=window.jvbA11y,this.error=window.jvbError,this.tabs=new window.jvbTabs(document.querySelector(".replace")),this.config={endpoints:{favourites:"favourites",lists:"favourites/lists",shares:"favourites/lists/shares"},selectors:{container:".favourites.container",itemsTab:'.tab-content[data-tab="items"]',listsTab:'.tab-content[data-tab="lists"]',grid:".item-grid",typeFilters:".type-filters",viewControls:".view-controls",bulkControls:".bulk-controls",selectAll:"#select-all",createListModal:".create-list-modal",addToListModal:".add-to-list-modal",shareListModal:".share-list-modal",noItems:".no-favourites",listContainer:".lists-container",listDetails:".list-details",loader:".favourites-loader"},defaultPage:1,defaultPerPage:24,defaultViewMode:"grid",refreshInterval:6e4,toastDuration:3e3},document.addEventListener("keydown",this.handleKeyDown.bind(this)),this.state={selectedItems:new Set,page:this.config.defaultPage,filter:{type:"all",order:"desc",orderBy:"date_added"},view:{mode:localStorage.getItem("favourites_view")||this.config.defaultViewMode,activeTab:"items"},pagination:{hasMore:!1,totalItems:0,totalPages:0},currentListId:null,loading:!1,initialized:!1},this.initDom(),this.initEvents(),this.loadInitialData(),this.state.initialized=!0}initDom(){this.container=document.querySelector(this.config.selectors.container),this.container?(this.grid=this.container.querySelector(this.config.selectors.grid),this.typeFilters=this.container.querySelector(this.config.selectors.typeFilters),this.viewControls=this.container.querySelector(this.config.selectors.viewControls),this.bulkControls=this.container.querySelector(this.config.selectors.bulkControls),this.listContainer=this.container.querySelector(this.config.selectors.listContainer),this.listDetails=this.container.querySelector(this.config.selectors.listDetails),this.loader=this.container.querySelector(this.config.selectors.loader),this.createListModal=document.querySelector(this.config.selectors.createListModal),this.addToListModal=document.querySelector(this.config.selectors.addToListModal),this.shareListModal=document.querySelector(this.config.selectors.shareListModal),this.grid&&this.state.view.mode&&this.grid.classList.add(`${this.state.view.mode}-view`)):console.warn("Favourites container not found")}initEvents(){if(this.typeFilters&&this.typeFilters.addEventListener("click",t=>{const e=t.target.closest(".type-filter");e&&this.setFilterType(e.dataset.type)}),this.viewControls&&this.viewControls.addEventListener("click",t=>{const e=t.target.closest(".view-toggle");e&&this.setView(e.dataset.view)}),this.container){const t=this.container.querySelector(this.config.selectors.selectAll);t&&t.addEventListener("change",()=>{this.toggleSelectAll(t.checked)}),this.container.addEventListener("change",t=>{t.target.matches(".item-select input[type=checkbox]")&&this.handleItemSelection(t.target)});const e=this.container.querySelector(".bulk-action-select"),s=this.container.querySelector(".apply-bulk");e&&s&&s.addEventListener("click",()=>{this.applyBulkAction(e.value)});const i=this.container.querySelector(".cancel-bulk");i&&i.addEventListener("click",()=>{this.clearSelection()})}this.initModalEvents(),this.container.addEventListener("click",this.handleItemActions.bind(this)),this.grid&&this.setupInfiniteScroll()}initModalEvents(){if(this.createListModal){const t=this.createListModal.querySelector("form");t&&t.addEventListener("submit",e=>{e.preventDefault(),this.handleCreateList(new FormData(t))});const e=this.createListModal.querySelector(".cancel");e&&e.addEventListener("click",()=>{this.createListModal.close()})}if(this.addToListModal){const t=this.addToListModal.querySelector("form");t&&t.addEventListener("submit",e=>{e.preventDefault(),this.handleAddToList(new FormData(t))});const e=this.addToListModal.querySelector(".cancel");e&&e.addEventListener("click",()=>{this.addToListModal.close()})}if(this.shareListModal){const t=this.shareListModal.querySelector("form");t&&t.addEventListener("submit",e=>{e.preventDefault(),this.handleShareList(new FormData(t))});const e=this.shareListModal.querySelector(".cancel");e&&e.addEventListener("click",()=>{this.shareListModal.close()});const s=this.shareListModal.querySelector(".add-email");s&&s.addEventListener("click",()=>{const e=this.shareListModal.querySelector("#share-email");e&&e.value&&this.handleShareList(new FormData(t))})}}setupInfiniteScroll(){let t=this.container.querySelector(".scroll-sentinel");t||(t=document.createElement("div"),t.className="scroll-sentinel",t.setAttribute("aria-hidden","true"),this.grid.parentNode.appendChild(t)),new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting&&this.state.pagination.hasMore&&!this.state.loading&&(this.state.page++,this.loadFavourites())})},{rootMargin:"200px"}).observe(t)}async loadInitialData(){this.loadingManager.show();try{await this.loadFavourites(),this.loadLists().catch(t=>{console.error("Error loading lists:",t)})}catch(t){this.handleError(t,"loading initial data")}finally{this.loadingManager.hide()}}async loadFavourites(t=!0){if(!this.state.loading)try{this.state.loading=!0,this.loadingManager.show();const e=new URLSearchParams({page:this.state.page,per_page:this.config.defaultPerPage,type:"all"!==this.state.filter.type?this.state.filter.type:"",order:this.state.filter.order,orderby:this.state.filter.orderBy});t&&(this.state.page=1,removeChildren(this.grid),this.grid.classList.remove("empty"));const s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.favourites}?${e}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"favouritesManager",forceRefresh:!0});return this.renderFavourites(s.favourites||[],this.state.page>1),s.counts&&this.updateTypeFilters(s.counts),s.pagination&&(this.state.pagination={hasMore:s.pagination.has_more,totalItems:s.pagination.total_items,totalPages:s.pagination.total_pages}),s}catch(t){throw this.handleError(t,"loading favourites"),t}finally{this.state.loading=!1,this.loadingManager.hide()}}renderFavourites(t,e=!1){this.grid&&(0!==t.length||e?(this.hideEmptyState(),e||removeChildren(this.grid),t.forEach(t=>{const e=this.createItemElement(t);this.grid.appendChild(e),this.initItemFunctionality(e,t)}),window.jvbA11y&&window.jvbA11y.announce(`${e?"Added":"Loaded"} ${t.length} favourites`)):this.showEmptyState())}createItemElement(t){const e=document.createElement("div");e.className=`item ${t.type} favourited`,e.dataset.id=t.target_id,e.dataset.type=t.type;const s=sanitizeHtml(t.title||!1),i=sanitizeHtml(t.notes||"");let a="";return t.thumbnail&&(a=`\n <div class="item-thumbnail">\n <a href="${t.url}">${t.thumbnail}</a>\n </div>\n `),e.innerHTML=`\n <div class="item-select">\n <input type="checkbox"\n class="favourite-checkbox"\n id="select-${t.target_id}"\n value="${t.target_id}">\n <label for="select-${t.target_id}"><span class="screen-reader-text">Select this ${t.type}</span</label>\n </div>\n\n <button type="button" class="favourite-button favourited"\n onclick="toggleFavourite(this)"\n data-id="${t.target_id}"\n data-type="${t.type}"\n title="Remove from favourites">\n ${jvbSettings.icons["heart-filled"]}\n </button>\n\n ${a}\n\n <div class="item-info">\n ${s?`<h3><a href="${t.url}">${s}</a></h3>`:`<a href="${t.url}">View Item</a>`}\n\n ${t.author?`\n <div class="item-artist">\n <span>By ${t.author.name}</span>\n </div>`:""}\n\n ${t.taxonomies?.length?`\n <div class="taxonomy-lists">\n ${t.taxonomies.map(t=>`\n <div class="taxonomy-group">\n ${jvbSettings.icons[t.icon]}\n <ul>\n ${t.terms.slice(0,3).map(t=>`\n <li>\n <a href="${t.url}" ${t.umami_click}>\n ${t.title}\n </a>\n </li>\n `).join("")}\n </ul>\n </div>\n `).join("")}\n </div>\n `:""}\n\n <div class="notes-section">\n <button type="button" class="toggle-notes" aria-expanded="false">\n ${jvbSettings.icons.note||"Notes"}\n <span>Notes</span>\n </button>\n\n <div class="notes-content" hidden>\n <textarea class="notes-input"\n placeholder="Add notes about this item..."\n data-id="${t.target_id}"\n data-type="${t.type}">${i}</textarea>\n <button type="button" class="save-notes">Save Notes</button>\n </div>\n </div>\n </div>\n `,e}initItemFunctionality(t,e){const s=t.querySelector(".toggle-notes"),i=t.querySelector(".notes-content");s&&i&&s.addEventListener("click",()=>{const t="true"===s.ariaExpanded;s.ariaExpanded=!t.toString(),i.hidden=t,t||i.querySelector("textarea")?.focus()});const a=t.querySelector(".save-notes"),n=t.querySelector(".notes-input");a&&n&&(a.addEventListener("click",()=>{this.saveNotes(n)}),n.addEventListener("keydown",t=>{"Enter"===t.key&&(t.ctrlKey||t.metaKey)&&(t.preventDefault(),this.saveNotes(n))}))}saveNotes(t){if(!t)return;const e=t.value.trim(),s=t.dataset.id,i=t.dataset.type;s&&i&&(this.queue.addToQueue({type:"favourite_notes",data:{type:i,target_id:parseInt(s),notes:e}}),showToast("Notes saved"),this.a11y.announce("Notes saved"))}showEmptyState(t=!1){const e=this.container.querySelector(this.config.selectors.noItems)??this.createEmptyElement;e&&(e.hidden=!1),this.grid&&this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}hideEmptyState(){const t=this.container.querySelector(".no-favourites");t&&t.remove(),this.grid&&this.grid.classList.remove("empty")}createEmptyElement(t=!1){const e=document.createElement("div");e.className="no-favourites",e.innerHTML="\n <h3>♡ BLANK CANVAS ♡</h3>\n <p>You haven't fallen in love with any pieces... yet!</p>\n <p>Hit that heart icon when something stops your scroll.</p>\n <p>Your dream collection is waiting to start.</p>\n ",this.grid.after(e)}showEmptyListState(t=!1){const e=document.createElement("div");e.className="no-favourites",e.innerHTML="\n <h3>♡ FULL OF POSSIBILITY ♡</h3>\n <p>There's nothing in this list yet.</p>\n <p>Add some gap fillers from the main favourites tab.</p>\n ",this.grid.after(e),this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}async loadMoreItems(){!this.state.loading&&this.state.pagination.hasMore&&(this.state.page+=1,await this.loadFavourites())}updateTypeFilters(t){this.typeFilters&&this.typeFilters.querySelectorAll(".type-filter").forEach(e=>{const s=e.querySelector(".count");if(!s)return;const i=e.dataset.type;if("all"===i){const e=Object.values(t).reduce((t,e)=>t+(parseInt(e)||0),0);s.textContent=`(${e})`}else s.textContent=`(${t[i]||0})`})}setFilterType(t){t!==this.state.filter.type&&(this.typeFilters&&this.typeFilters.querySelectorAll(".type-filter").forEach(e=>{e.classList.toggle("active",e.dataset.type===t),e.setAttribute("aria-selected",e.dataset.type===t)}),this.state.filter.type=t,this.state.page=1,this.loadFavourites(),window.jvbA11y&&window.jvbA11y.announce(`Filtered to show ${"all"===t?"all":t} items`))}setView(t){t!==this.state.view.mode&&(this.viewControls&&this.viewControls.querySelectorAll(".view-toggle").forEach(e=>{const s=e.dataset.view===t;e.setAttribute("aria-pressed",s)}),this.grid&&(this.grid.classList.remove("grid-view","list-view"),this.grid.classList.add(`${t}-view`)),this.state.view.mode=t,localStorage.setItem("favourites_view",t),window.jvbA11y&&window.jvbA11y.announce(`Changed to ${t} view`))}toggleSelectAll(t){const e=this.getVisibleItems();e.forEach(e=>{const s=e.querySelector('.item-select input[type="checkbox"]');s&&(s.checked=t,this.toggleItemSelection(s.value,t))}),this.updateBulkControls(),window.jvbA11y&&window.jvbA11y.announce(t?`Selected all ${e.length} items`:"Deselected all items")}getVisibleItems(){return this.grid?Array.from(this.grid.querySelectorAll(".item:not([hidden])")):[]}toggleItemSelection(t,e){e?this.state.selectedItems.add(t):this.state.selectedItems.delete(t);const s=this.grid.querySelector(`.item[data-id="${t}"]`);s&&s.classList.toggle("selected",e)}handleItemSelection(t){const e=t.checked,s=t.value;if(this.toggleItemSelection(s,e),this.updateBulkControls(),this.updateSelectAllState(),window.jvbA11y){const s=t.closest(".item"),i=s&&s.querySelector("h3")?.textContent||"item";window.jvbA11y.announce(e?`Selected ${i}`:`Deselected ${i}`)}}updateSelectAllState(){const t=this.container.querySelector(this.config.selectors.selectAll);if(!t)return;const e=this.getVisibleItems();if(0===e.length)return t.checked=!1,void(t.indeterminate=!1);const s=e.filter(t=>{const e=t.querySelector('.item-select input[type="checkbox"]');return e&&e.checked}).length;0===s?(t.checked=!1,t.indeterminate=!1):s===e.length?(t.checked=!0,t.indeterminate=!1):(t.checked=!1,t.indeterminate=!0)}updateBulkControls(){if(!this.bulkControls)return;const t=this.bulkControls.querySelector(".bulk-actions");if(!t)return;const e=this.state.selectedItems.size>0;t.hidden=!e;const s=this.bulkControls.querySelector(".selected-count");s&&(s.textContent=e?`${this.state.selectedItems.size} selected`:"")}handleKeyDown(t){"Escape"===t.key&&this.state.selectedItems.size>0&&(t.preventDefault(),this.clearSelection(),window.jvbA11y&&window.jvbA11y.announce("Selection cleared using Escape key"))}clearSelection(){this.state.selectedItems.clear(),this.getVisibleItems().forEach(t=>{const e=t.querySelector('.item-select input[type="checkbox"]');e&&(e.checked=!1),t.classList.remove("selected")});const t=this.container.querySelector(this.config.selectors.selectAll);t&&(t.checked=!1,t.indeterminate=!1),this.updateBulkControls(),window.jvbA11y&&window.jvbA11y.announce("Selection cleared")}applyBulkAction(t){if(!t||0===this.state.selectedItems.size)return;switch(t){case"unfavourite":this.bulkUnfavourite();break;case"add-to-list":this.showAddToListModal();break;case"create-list":this.showCreateListModal();break;case"add-notes":this.showBulkNotesModal()}const e=this.container.querySelector(".bulk-action-select");e&&(e.value="")}handleItemActions(t){if(t.target.closest(".toggle-notes")){const e=t.target.closest(".toggle-notes"),s="true"===e.getAttribute("aria-expanded"),i=e.closest(".notes-section").querySelector(".notes-content");return e.setAttribute("aria-expanded",!s),i.hidden=s,!s&&i&&i.querySelector("textarea")?.focus(),void t.preventDefault()}if(t.target.closest(".save-notes")){const e=t.target.closest(".save-notes").closest(".notes-content").querySelector("textarea");return e&&this.saveNotes(e),void t.preventDefault()}if(t.target.closest(".view-list")){const e=t.target.closest(".view-list").closest(".list-card");return e&&e.dataset.id&&this.viewList(e.dataset.id),void t.preventDefault()}if(t.target.closest(".share-list")){const e=t.target.closest(".share-list").closest(".list-card");return e&&e.dataset.id&&this.showShareModal(e.dataset.id),void t.preventDefault()}if(t.target.closest(".delete-list")){const e=t.target.closest(".delete-list").closest(".list-card");return e&&e.dataset.id&&this.deleteList(e.dataset.id),void t.preventDefault()}if(t.target.closest(".back-to-lists"))return this.exitListView(),void t.preventDefault()}async loadLists(t=!0){try{this.state.loading=!0,this.loadingManager.show("Loading lists...");const t=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"favourite-lists",forceRefresh:!1});return t.lists&&this.renderLists(t.lists),t}catch(t){throw this.handleError(t,"loading lists"),t}finally{this.state.loading=!1,this.loadingManager.hide()}}renderLists(t){if(!this.listContainer)return;if(removeChildren(this.listContainer),!t||0===t.length)return void(this.listContainer.innerHTML='\n <div class="no-lists">\n <h3>No Lists Yet</h3>\n <p>Select favourites from the main tab to organize into lists.</p>\n </div>\n ');const e=t.owned,s=t.shared;if(e.length>0){const t=document.createElement("details");t.className="lists-section owned-lists",t.open=!0,t.innerHTML="<summary>Your Lists:</summary>",e.forEach(e=>{const s=this.createListCard(e);t.appendChild(s)}),this.listContainer.appendChild(t)}if(s.length>0){const t=document.createElement("details");t.className="lists-section shared-lists",t.innerHTML="<summary>Lists Shared with You:</summary>",s.forEach(e=>{const s=this.createListCard(e);t.appendChild(s)}),this.listContainer.appendChild(t)}}createListCard(t){const e=document.createElement("div");e.className="list-card",e.dataset.id=t.id;const s="1"===t.is_shared;s&&e.classList.add("shared"),t.is_temp&&e.classList.add("temp"),t.is_owner;const i=sanitizeHtml(t.name||"Untitled List"),a=sanitizeHtml(t.description||"");return e.innerHTML=`\n <div class="list-header">\n <h3>${i}</h3>\n <div class="list-actions">\n <button type="button" class="view-list" title="View List">\n ${jvbSettings.icons?.show||"View"}\n </button>\n ${s?"":`\n <button type="button" class="share-list" title="Share List">\n ${jvbSettings.icons?.share||"Share"}\n </button>\n <button type="button" class="delete-list" title="Delete List">\n ${jvbSettings.icons?.delete||"Delete"}\n </button>\n `}\n </div>\n </div>\n\n ${a?`<p class="list-description">${a}</p>`:""}\n\n <div class="list-meta">\n <div class="meta-stats">\n <span class="item-count">${t.item_count||0} items</span>\n <span class="date">${formatDate(t.created_at)}</span>\n </div>\n\n\n ${s?`\n <div class="owner-info">\n Shared by ${t.owner_name||"another user"}\n </div>\n `:t.share_count>0?`\n <div class="share-info">\n Shared with ${t.share_count} ${1===t.share_count?"person":"people"}\n </div>\n `:""}\n </div>\n `,e}async viewList(t){try{this.state.loading=!0,this.loadingManager.show("Loading list..."),this.state.currentListId=t;const e=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}?id=${t}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"list-item",forceRefresh:!1});if(!e.list)throw new Error("List not found");this.showListDetails(e.list)}catch(t){this.handleError(t,"viewing list")}finally{this.state.loading=!1,this.loadingManager.hide()}}showListDetails(t){this.listDetails&&this.listContainer&&(console.log(t),this.listDetails.querySelector(".list-title").value=t.name||"Untitled List",this.listDetails.querySelector(".list-description").value=t.description||"",t.is_owner?this.listDetails.querySelector(".list-actions")||this.createListActions():this.listDetails.querySelector(".list-actions")?.remove(),removeChildren(this.grid),this.renderFavourites(t.items||[],!1),0===t.items.length&&this.showEmptyListState(),window.jvbA11y&&window.jvbA11y.announce(`Viewing list: ${t.name} with ${t.items?.length||0} items`))}createListActions(){const t=document.createElement("div");t.className="list-actions",t.innerHTML='\n <button type="button" class="share-list" title="Share List">\n <i class="icon icon-share-fat"></i>\n <span>Share</span>\n </button>\n <button type="button" class="duplicate-list" title="Duplicate List">\n <i class="icon icon-copy"></i>\n <span>Duplicate</span>\n </button>\n <button type="button" class="delete-list" title="Delete List">\n <i class="icon icon-trash"></i>\n <span>Delete</span>\n </button>\n ',this.listDetails.insertBefore(t,this.listDetails.querySelector(".bulk-controls"))}exitListView(){this.listDetails&&this.listContainer&&(this.listDetails.hidden=!0,this.listContainer.hidden=!1,this.container.classList.remove("viewing-list"),this.state.currentListId=null,window.jvbA11y&&window.jvbA11y.announce("Returned to lists view"))}showCreateListModal(){this.createListModal&&(this.createListModal.querySelector("form")?.reset(),this.createListModal.showModal(),setTimeout(()=>{this.createListModal.querySelector("#list-name")?.focus()},100),window.jvbA11y&&window.jvbA11y.announce("Create list dialog opened"))}showAddToListModal(){this.addToListModal&&(this.populateAddToListModal(),this.addToListModal.showModal(),window.jvbA11y&&window.jvbA11y.announce("Add to list dialog opened"))}async populateAddToListModal(){if(!this.addToListModal)return;const t=this.addToListModal.querySelector(".lists-options");if(t){removeChildren(t);try{const e=(await this.loadLists()).lists.owned;if(0===e.length)return t.innerHTML='\n <div class="no-lists">\n <p>You don\'t have any lists yet.</p>\n <button type="button" class="create-list-button">Create a list</button>\n </div>\n ',void t.querySelector(".create-list-button")?.addEventListener("click",()=>{this.addToListModal.close(),this.showCreateListModal()});e.forEach(e=>{const s=document.createElement("div");s.className="list-option",s.innerHTML=`\n <input type="checkbox" id="${e.id}" name="list_ids[]" value="${e.id}">\n <label for="${e.id}">\n\n <span class="list-name">${sanitizeHtml(e.name)}</span>\n <span class="item-count">( ${e.item_count||0} items )</span>\n </label>\n `,t.appendChild(s)})}catch(e){t.innerHTML='\n <div class="error-message">\n <p>Error loading lists. Please try again.</p>\n </div>\n ',console.error("Error loading lists for modal:",e)}}}async handleCreateList(t){const e=t.get("list_name"),s=t.get("list_description");if(e)try{this.showLoader("Creating list...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})}),this.queue.addToQueue({type:"favourite_list_create",data:{name:e,description:s,items:t}}),showToast(`List "${e}" created`),this.a11y.announce(`List ${e} created with ${t.length} items`),this.createListModal.close(),this.clearSelection(),this.switchTab("lists")}catch(t){this.handleError(t,"creating list")}finally{this.hideLoader()}else showToast("Please enter a list name","error")}async handleAddToList(t){const e=t.getAll("list_ids[]");if(e.length)try{this.showLoader("Adding to list...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})}),this.queue.addToQueue({type:"favourite_list_add",data:{list_id:e.join(","),items:t}}),showToast(`Added to ${e.length} ${1===e.length?"list":"lists"}`),this.a11y.announce(`Added ${t.length} items to ${e.length} ${1===e.length?"list":"lists"}`),this.addToListModal.close(),this.clearSelection()}catch(t){this.handleError(t,"adding to list")}finally{this.hideLoader()}else showToast("Please select at least one list","error")}async handleRemoveFromList(t){const e=t.getAll("list_ids[]");if(e.length)try{this.showLoader("Removing from list...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})}),this.queue.addToQueue({type:"favourite_list_remove",data:{list_id:e.join(","),items:t}}),showToast(`Removed from ${e.length} ${1===e.length?"list":"lists"}`),this.a11y.announce(`Removed ${t.length} items to ${e.length} ${1===e.length?"list":"lists"}`),this.addToListModal.close(),this.clearSelection()}catch(t){this.handleError(t,"remove from list")}finally{this.hideLoader()}else showToast("Please select at least one list","error")}showShareModal(t){this.shareListModal&&(this.state.currentListId=t,this.shareListModal.querySelector("form")?.reset(),this.loadSharedUsers(t),this.shareListModal.showModal(),setTimeout(()=>{this.shareListModal.querySelector("#share-email")?.focus()},100),window.jvbA11y&&window.jvbA11y.announce("Share list dialog opened"))}async loadSharedUsers(t){try{const e=this.shareListModal.querySelector(".shared-users");if(!e)return;e.innerHTML='<div class="loading">Loading shared users...</div>';const s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}?id=${t}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"list-item",forceRefresh:!1});removeChildren(e),s.list&&s.list.shared_users&&s.list.shared_users.length>0?(s.list.shared_users.forEach(t=>{const s=document.createElement("div");s.className=`shared-user ${t.status}`,s.innerHTML=`\n <span class="user-email">${t.email}</span>\n ${"pending"===t.status?'<span class="pending-badge">Invitation sent</span>':`<span class="permission-badge">${t.permission_type||"view"}</span>`}\n <button type="button" class="remove-share" data-email="${t.email}">\n ${jvbSettings.icons?.delete||"Remove"}\n </button>\n `,e.appendChild(s)}),e.querySelectorAll(".remove-share").forEach(t=>{t.addEventListener("click",()=>{this.unshareList(t.dataset.email)})})):e.innerHTML='<div class="no-shares">This list is not shared with anyone yet.</div>'}catch(t){console.error("Error loading shared users:",t)}}async unshareList(t){if(confirm(`Remove ${t}'s access to this list?`))if(this.state.currentListId)try{this.showLoader("Removing access..."),this.queue.addToQueue({type:"favourite_list_unshare",data:{list_id:parseInt(this.state.currentListId),email:t}});const e=Array.from(this.shareListModal.querySelectorAll(".shared-user")).find(e=>e.querySelector(".user-email")?.textContent===t);e&&(e.classList.add("removing"),setTimeout(()=>{if(e.remove(),0===this.shareListModal.querySelectorAll(".shared-user").length){const t=this.shareListModal.querySelector(".shared-users");t&&(t.innerHTML='<div class="no-shares">This list is not shared with anyone yet.</div>')}},300)),showToast(`Removed ${t}'s access`),this.a11y.announce(`Removed ${t}'s access to list`)}catch(t){this.handleError(t,"removing share access")}finally{this.hideLoader()}else showToast("No list selected","error")}async deleteList(t){if(confirm("Are you sure you want to delete this list? This cannot be undone."))try{this.showLoader("Deleting list..."),this.queue.addToQueue({type:"favourite_list_delete",data:{list_id:parseInt(t)}});const e=this.container.querySelector(`.list-card[data-id="${t}"]`);e&&(e.classList.add("removing"),setTimeout(()=>{e.remove(),0===this.container.querySelectorAll(".list-card").length&&(this.listContainer.innerHTML='\n <div class="no-lists">\n <h3>No Lists Yet</h3>\n <p>Create your first list to organize your favourites!</p>\n </div>\n ')},300)),showToast("List deleted"),this.a11y.announce("List deleted")}catch(t){this.handleError(t,"deleting list")}finally{this.hideLoader()}}showBulkNotesModal(){let t=document.querySelector(".bulk-notes-modal");t||(t=document.createElement("dialog"),t.className="bulk-notes-modal",t.innerHTML='\n <form method="dialog" data-save="favourites">\n <h2>Add Notes to Selected Items</h2>\n\n <div class="field">\n <label for="bulk-notes">Notes (will be applied to all selected items)</label>\n <textarea id="bulk-notes" name="bulk_notes" rows="5"></textarea>\n </div>\n\n <div class="actions">\n <button type="button" class="cancel">Cancel</button>\n <button type="submit" class="save">Save Notes</button>\n </div>\n </form>\n ',document.body.appendChild(t),t.querySelector("form").addEventListener("submit",e=>{e.preventDefault();const s=t.querySelector("#bulk-notes").value;this.saveBulkNotes(s),t.close()}),t.querySelector(".cancel").addEventListener("click",()=>{t.close()})),t.querySelector("form")?.reset(),t.showModal(),setTimeout(()=>{t.querySelector("#bulk-notes")?.focus()},100),window.jvbA11y&&window.jvbA11y.announce("Add notes dialog opened")}saveBulkNotes(t){if(t)try{this.showLoader("Saving notes...");let e=Array.from(this.state.selectedItems.values());this.queue.addToQueue({type:"favourite_notes",data:{target_id:e.join(","),notes:t}}),showToast(`Notes saved for ${e.length} items`),this.a11y.announce(`Notes saved for ${e.length} items`),this.clearSelection()}catch(t){this.handleError(t,"saving bulk notes")}finally{this.hideLoader()}}async bulkUnfavourite(){if(confirm("Are you sure you want to remove these items from your favourites?"))try{this.showLoader("Removing from favourites...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);if(!s)return;const i=s.dataset.type;t.push({target_id:parseInt(e),type:i,action:"remove"})}),this.queue.addToQueue({type:"favourite_toggle",data:t});const e=[];this.state.selectedItems.forEach(t=>{const s=this.grid.querySelector(`.item[data-id="${t}"]`);if(!s)return;s.style.opacity="0",s.style.transform="scale(0.9)",s.style.transition="opacity 0.3s ease, transform 0.3s ease";const i=new Promise(t=>{setTimeout(()=>{s.remove(),t()},300)});e.push(i)}),await Promise.all(e),0===this.grid.children.length&&this.showEmptyState(),this.clearSelection(),showToast(`Removed ${t.length} items from favourites`),this.a11y.announce(`Removed ${t.length} items from favourites`)}catch(t){this.handleError(t,"removing favourites")}finally{this.hideLoader()}}async handleShareList(t){const e=t.get("share_email");if(e)if(this.validateEmail(e))try{this.showLoader("Sharing list..."),this.queue.addToQueue({type:"favourite_list_share",data:{list_id:parseInt(this.state.currentListId),email:e,permission_type:"view"}}),this.shareListModal.querySelector("#share-email").value="",await this.loadSharedUsers(this.state.currentListId),showToast(`Invitation sent to ${e}`),this.a11y.announce(`Invitation sent to ${e}`)}catch(t){this.handleError(t,"sharing list")}finally{this.hideLoader()}else showToast("Please enter a valid email address","error");else showToast("Please enter an email address","error")}showLoader(t="Loading..."){if(!this.loader)return;const e=this.loader.querySelector(".loader-message");e&&(e.textContent=t),this.loader.hidden=!1}hideLoader(){this.loader&&(this.loader.hidden=!0)}showToast(t,e){window.jvbNotifications.showToast(t,e)}handleError(t,e){console.error(`Favourites error (${e}):`,t),showToast(`Error ${e}: ${t.message||"Something went wrong"}`,"error"),window.jvbError&&window.jvbError.log(t,{component:"FavouritesManager",action:e}),window.jvbA11y&&window.jvbA11y.announce(`Error ${e}. ${t.message||"Please try again."}`)}validateEmail(t){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}};
\ No newline at end of file
diff --git a/assets/js/min/form.min.js b/assets/js/min/form.min.js
index d2c6637..2caf580 100644
--- a/assets/js/min/form.min.js
+++ b/assets/js/min/form.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.queue=window.jvbQueue,this.populate=window.jvbPopulate,this.changes=new Map,this.forms=new Map,this.inputs=new Map,this.repeaters=new Map,this.tagLists=new Map,this.charLimits=new Map,this.quantityFields=new Map,this.quillInstances=new Map,this.dependencies=new Map,this.subscribers=new Set,this.isRestoring=!1,this.hasListeners=!1,this.hasUploads=!1,this.summaryTemplate=!1,this.init()}init(){this.templates=window.jvbTemplates,this.defineSummaryTemplate(),this.initElements(),this.initListeners(),this.initStore(),this.initValidators(),this.initUploadSubscription()}initUploadSubscription(){window.jvbUploads.subscribe((e,t)=>{if(this.hasUploads&&"upload-received"===e){let e=this.getForm(t.field);e&&this.updateItem(`${t.field.dataset.field}_tempUpload`,t.id,e)}})}initElements(){this.inputSelectors="input, textarea, select",this.selectors={tabs:{nav:"nav.tabs",sections:".tab-content",progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},buttons:"nav.tabs button"},dependsOn:"[data-depends-on]",forms:{status:{status:".fstatus",message:".fstatus .message",icon:".fstatus .icon",actions:".fstatus .actions"},restore:{container:".restore-form",restore:'[data-action="restore"]',clear:'[data-action="clear"]'}},inputs:this.inputSelectors,fields:{field:".field",label:"label",success:".success",error:".error",message:".validation-message"},repeater:{repeater:".repeater",row:".repeater-row",handle:".drag-handle",header:".repeater-row-header",remove:".remove-row",add:".add-repeater-row",template:"template",items:".repeater-items",inputs:this.inputSelectors},tagList:{tagList:".field.tag-list",input:".row",add:".add-tag",remove:".remove-tag",label:".tag-label",items:".tag-items",item:".tag-item",inputs:this.inputSelectors,value:'input[type="hidden"]'},tag:{label:".tag-label"},number:{number:".field div.quantity",increase:"button.increase",decrease:"button.decrease",input:'input[type="number"]'},limits:{hasLimit:"[data-maxlength]",limit:".limit",current:".current"}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.blurHandler=this.handleBlur.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleSubmit.bind(this),this.quantityClick=this.handleQuantityClick.bind(this),this.repeaterClick=this.handleRepeaterClick.bind(this),this.tagListClick=this.handleTagListClick.bind(this),this.tagListInput=this.handleTagListInput.bind(this)}addFormListeners(e){e.addEventListener("click",this.clickHandler),e.addEventListener("change",this.changeHandler),e.addEventListener("input",this.inputHandler),e.addEventListener("blur",this.blurHandler),e.addEventListener("submit",this.submitHandler)}removeFormListeners(e){e.removeEventListener("click",this.clickHandler),e.removeEventListener("change",this.changeHandler),e.removeEventListener("input",this.inputHandler),e.removeEventListener("blur",this.blurHandler),e.removeEventListener("submit",this.submitHandler)}initStore(){const e=window.jvbStore.register("forms",{storeName:"forms",keyPath:"id",indexes:[{name:"src",keyPath:"src"},{name:"timestamp",keyPath:"timestamp"},{name:"formType",keyPath:"type"}],TTL:1008e4});this.store=e.forms,this.store.subscribe((e,t)=>{if("data-ready"===e){let e=this.store.getFiltered().filter(e=>e.src===window.location.pathname);for(let t of e)this.showPendingNotification(t.id,t.changes)}else"operation-status"===e&&"completed"===t.status&&t.config&&this.store.delete(t.config.id)})}showPendingNotification(e,t){let s=this.forms.get(e);if(!s)return;let i=s.element;if(!i)return void console.warn(`Form element not found for: ${e}`);s.ui.restore.container.hidden=!1;const a=async(e,t)=>{this.isRestoring=!0;let i={fields:e};await this.checkStoredUploads(e,t),this.populate.populate(t,i),this.a11y.announce("Previous changes restored"),this.isRestoring=!1,s.ui.restore.container.remove()},r=async e=>{await this.checkStoredUploads(t,i,!1),await this.store.delete(e),this.a11y.announce("Previous changes discarded"),s.ui.restore.container.remove()};s.ui.restore.restore.addEventListener("click",()=>a(t,i)),s.ui.restore.clear.addEventListener("click",async()=>r(e))}async checkStoredUploads(e,t,s=!0){let i=this.forms.get(t.dataset.formId);if(!i)return;let a=[];for(let[t,s]of Object.entries(e))if(t.includes("_tempUpload")){let e=t.replace("_tempUpload","");Object.hasOwn(i.ui.uploads,e)&&(a=[...a,...s])}a.length>0&&(s?await window.jvbUploads.restoreUploads(a):await window.jvbUploads.clearUploads(a))}initValidators(){this.validators={email:{pattern:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,message:"Please enter a valid email address"},url:{pattern:/^https?:\/\/.+\..+/,message:"Please enter a valid URL starting with https://"},phone:{pattern:/^[\d\s\-+().]+$/,message:"Please enter a valid phone number"},number:{test:(e,t)=>{const s=parseFloat(e);if(isNaN(s))return"Please enter a valid number";const i=t.dataset.min,a=t.dataset.max;return void 0!==i&&s<parseFloat(i)?`Value must be at least ${i}`:!(void 0!==a&&s>parseFloat(a))||`Value must be at most ${a}`}},text:{test:(e,t)=>{const s=t.dataset.minlength,i=t.dataset.maxlength;return s&&e.length<parseInt(s)?`Must be at least ${s} characters`:!(i&&e.length>parseInt(i))||`Must be no more than ${i} characters`}}}}validateField(e){const t=this.performValidation(e);return this.updateValidationUI(e,t),t.isValid}performValidation(e){const t=e.closest(".field"),s=this.getFieldCheckedValue(e);if(!s&&!e.required)return{isValid:!0,message:""};if(e.required)if("checkbox"===e.type){if(!e.checked)return{isValid:!1,message:"This field is required"}}else if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`);if(!Array.from(t).some(e=>e.checked))return{isValid:!1,message:"Please select an option"}}else if(!s)return{isValid:!1,message:"This field is required"};if(e.checkValidity&&!e.checkValidity())return{isValid:!1,message:e.validationMessage};if(s&&Object.hasOwn(t.dataset,"pattern")){if(!new RegExp(t.dataset.pattern).test(s))return{isValid:!1,message:t.dataset.validationMessage||"Invalid format"}}if(Object.hasOwn(t.dataset,"validate")||e.type){const i=this.validators[t.dataset.validate||e.type];if(i&&i.pattern&&!i.pattern.test(s))return{isValid:!1,message:i.message};if(i&&i.test){const e=i.test(s,t);if(!0!==e)return{isValid:!1,message:e}}}return{isValid:!0,message:""}}updateValidationUI(e,t){t.isValid?this.showSuccess(e,t.message):this.showError(e,t.message)}handleClick(e){let t=this.getForm(e.target);if(!t)return;const s=window.targetCheck(e,"[data-action]");if(s){switch(s.dataset.action){case"clear-form":this.store.delete(t.id),t.element.reset(),t.ui.status.status.hidden=!0,this.a11y.announce("Form cleared, starting fresh");break;case"dismiss-restore":t.ui.status.status.hidden=!0;break;case"next-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s+1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}});break;case"prev-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s-1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}})}}}handleChange(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getField(e.target);const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');if(s){if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}const e=s.dataset.field;return this.maybeUpdateCollectionDisplay(t),void window.debouncer.schedule(`collection:${e}`,()=>this.updateCollectionField(s),150)}if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}let i=this.getForm(e.target);this.updateItem(t.dataset.field,this.getFieldValue(e.target),i)}handleBlur(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target).dataset.field;window.debouncer.cancel(`form:${t.id}:validate:${s}`),this.validateField(e.target);const i=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');i?this.updateCollectionField(i):this.updateItem(s,this.getFieldValue(e.target),t)}handleInput(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target);if(!s)return;const i=e.target,a=s.dataset.field;this.showFormStatus(t.id,"pending"),window.debouncer.schedule(`form:${t.id}:validate:${a}`,()=>this.validateField(i),500)}async handleSubmit(e){let t=this.getForm(e.target);if(t){if(this.subscribers.size>0)if(e.preventDefault(),t.options.cache){this.cancelBackup(),await this.backup();const e=await this.store.get(t.id);this.notify("form-submit",{config:t,data:e.changes})}else this.notify("form-submit",{config:t,data:this.changes.get(t.id)?.changes??{}});if(t.options.endpoint&&!t.options.handled&&(e.preventDefault(),console.log("Submitting form...",this.changes.get(t.id)?.changes??{}),await this.handleServerSave(t,this.changes.get(t.id)?.changes??{})),t.options.showSummary){const e=await this.store.get(t.id);this.showSummary({config:t,changes:e?.changes})}}}updateItem(e,t,s){if(void 0===t)return;this.changes.has(s.id)||this.changes.set(s.id,{id:s.id,timestamp:Date.now(),src:window.location.pathname,changes:{}});let i=this.changes.get(s.id);e.includes("_tempUpload")?(Object.hasOwn(i.changes,e)||(i.changes[e]=[]),i.changes[e].push(t)):i.changes[e]=t,s.tabs&&this.updateStepProgress(s),this.changes.set(s.id,i),s.options.cache&&this.scheduleBackup(),s.options.endpoint&&!s.options.handled&&this.scheduleServerSave(s,i)}updateStepProgress(e){if(console.log("First check in updateStepProgress"),!e.tabs)return;let t=this.getCurrentStep(e);if(console.log("Current step: ",t),!t)return;t-=1;let s=this.getCurrentSection(e);if(console.log("Current section: ",s),!s)return;let i=0,a=0;if(s.childNodes.forEach(e=>{if(console.log("Child node: ",e),e.classList.contains("field")){i++;let t=this.getFieldValue(e.querySelector(this.inputSelectors));console.log("Value",t),t.length>0&&a++}}),0===i)return;let r=t+a/i,n=e.ui.tabs.sections.length;window.showProgress(e.ui.tabs.progress,r,n,`Step ${t+1} of ${n}`)}getCurrentStep(e){let t=null;return e.tabs.ui.buttons.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s.dataset.step)}),t}getCurrentSection(e){console.log("Getting current section: ",e.tabs.ui.sections);let t=null;return e.tabs.ui.sections.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s)}),t}scheduleBackup(){window.debouncer.schedule("form_changes",async()=>{this.changes.size>0&&await this.backup()},2e3)}cancelBackup(){window.debouncer.cancel("form_changes")}async backup(){const e=new Map;for(let[t,s]of this.changes.entries()){const i=await this.store.get(t);i?e.set(t,{...i,...s,changes:{...i.changes,...s.changes},timestamp:Date.now()}):e.set(t,s)}await this.store.saveMany(e);for(let e of this.changes.keys())this.showFormStatus(e,"autosaved");this.changes.clear()}saveCache(e){if(!this.changes.has(e))return;let t=this.changes.get(e);0!==t.size&&(this.store.save(t).then(()=>{}),this.changes.delete(e))}scheduleServerSave(e,t){window.debouncer.schedule(`form_${e.id}_server_save`,async()=>{await this.handleServerSave(e,t)},1500)}cancelServerSave(e){window.debouncer.cancel(`form_${e}_server_save`)}async handleServerSave(e,t){if(this.cancelServerSave(e.id),e.useQueue&&window.jvbQueue)t.user=window.auth.getUser(),window.jvbQueue.addToQueue({endpoint:e.options.endpoint,data:t,headers:e.options.headers??{}});else try{let s=await window.auth.fetch(jvbSettings.api+e.options.endpoint,{body:t,headers:e.options.headers??{},method:"POST"});if(!s.ok){let t;try{t=await s.json()}catch{t={message:s.statusText||"Unknown HTTP error"}}return void this.notify("form-error",{config:e,data:t})}let i=await s.json();this.notify("form-success",{config:e,result:i})}catch(t){t.status?this.notify("form-error",{config:e,data:t}):t instanceof SyntaxError?(this.notify("form-error",{config:e,data:{status:"error",success:!1,message:"Something went wrong"}}),console.error("Data Error: Server returned invalid JSON format.")):this.notify("form-error",{config:e,data:{status:"network_error",message:"Network error: could not connect to the server"}})}}registerForm(e,t){if(t={autoUpload:!1,imageMeta:!0,delay:1500,useQueue:!0,endpoint:Object.hasOwn(e.dataset,"save")?e.dataset.save:"",showStatus:!0,showSummary:!1,handled:Object.hasOwn(e.dataset,"handled"),cache:!0,ignore:[],...t},Object.hasOwn(e.dataset,"formId")&&this.forms.has(e.dataset.formId))return;Object.hasOwn(e.dataset,"formId")||(e.dataset.formId=window.generateID("form_")),Object.hasOwn(e.dataset,"action")&&(t.headers=window.auth.getHeader(e.dataset.action));const s=e.dataset.formId;this.addFormListeners(e);const i={element:e,id:s,status:"",options:t,ui:window.uiFromSelectors(this.selectors.forms,e)};return i.ui.fields={},e.querySelectorAll("[data-field]").forEach(e=>{i.ui.fields[e.dataset.field]=e}),this.initializeFields(e,i),this.forms.set(s,i),i}clearForm(e){const t=this.forms.get(e);if(!t)return;t.unsubscribeTabs&&t.unsubscribeTabs(),t.tabs&&window.jvbTabs.removeTab(t.element),t.cache&&this.changes.has(e)&&this.saveCache(e);for(let[t,s]of this.inputs.entries())s.form===e&&this.inputs.delete(t);if(this.dependencies.forEach((t,s)=>{0===(t=t.filter(t=>t.form!==e)).length&&this.dependencies.delete(s)}),Object.hasOwn(t,"hasQuill")&&this.quillInstances.has(e)){this.quillInstances.get(e).forEach(e=>{e.disable(),e.off("text-change"),e.off("selection-change");const t=e.container.parentElement,s=t?.querySelector(".ql-toolbar");if(s&&s.remove(),e.setText(""),t&&t.classList.contains("editor-container")){const e=t.nextElementSibling;"TEXTAREA"===e?.tagName&&(e.style.display=""),t.remove()}}),this.quillInstances.delete(e)}let s={repeater:this.repeaters,tagList:this.tagLists,charLimit:this.charLimits,quantity:this.quantityFields};for(let[t,i]of Object.entries(s)){if(0===i.size)continue;let s=Array.from(i.values()).filter(t=>t.form===e);s.length>0&&s.forEach(e=>{switch(t){case"repeater":this.removeRepeaterListeners(e.element);break;case"tagList":this.removeTagListListeners(e.element);break;case"charLimit":this.removeCharacterLimitListeners(e.element);break;case"quantity":this.removeQuantityListeners(e.element)}i.has(e.id)&&i.delete(e.id)})}this.removeFormListeners(t.element),this.forms.delete(e),window.debouncer.cancel("form_changes")}defineSummaryTemplate(){this.summaryTemplate=!0;let e=this;this.templates.define("formSummary",{refs:{result:".result",h3:"h3",p:"p"},setup({el:t,refs:s,manyRefs:i,data:a}){const r=["sendAll",...a?.config?.options?.ignore??[]];for(let[i,n]of Object.entries(a.changes)){if(r.includes(i)||e.isEmptyValue(n))continue;let a=Array.from(e.inputs.values()).find(e=>e.field?.dataset.field===i);if(!a)continue;let o=s.result.cloneNode(!0),l=o.querySelector("h3"),d=o.querySelector("p");const c=a.field?.querySelector("legend");l.textContent=c?c.textContent.replace("*","").trim():a.ui.label?.textContent.replace("*","").trim();const u=e.formatValueForSummary(n,a);u instanceof HTMLElement?d.replaceWith(u):d.textContent=u,t.append(o)}let n=a.config?.element?.querySelectorAll("[data-upload-field]");n&&n.forEach(e=>{let i=e.querySelector("h2")?.textContent??"Upload:",a=e.querySelectorAll(".item-grid.preview img"),r=s.result.cloneNode(!0);if(a){let e=s.result.cloneNode(!0),n=r.querySelector("h3"),o=r.querySelector("p");o?.remove(),n&&(n.textContent=i),a.forEach(t=>{t=t.cloneNode(!0),e.append(t)}),t.append(e)}}),s.result?.remove()}})}initializeFields(e,t=null){const s={"[data-editor]":()=>this.checkForQuill(e,t),"div.quantity":()=>this.checkForQuantity(e),".repeater":()=>this.checkForRepeaters(e,t),".field.tag-list":()=>this.checkForTagLists(e),"[data-depends-on]":()=>this.checkForConditionalFields(e),"[data-limit]":()=>this.checkForCharacterLimits(e),"[data-uploader],[data-upload-field]":()=>this.checkForImageUploads(e,t),"nav.tabs":()=>this.checkForTabs(e,t),'[data-type="selector"]':()=>this.checkForSelectors(e)};for(const[t,i]of Object.entries(s))e.querySelector(t)&&i();Array.from(e.querySelectorAll(this.inputSelectors)).filter(e=>!e.closest(".ql-clipboard")).map(e=>{this.getItem(e,t?.id)})}checkForQuill(e,t){if(!e.querySelector("[data-editor]"))return;t&&!Object.hasOwn(t,"hasQuill")&&(t.hasQuill=!0,this.forms.set(t.id,t)),this.quillInstances.has(t.id)||this.quillInstances.set(t.id,new Set);window.jvbQuill(e).forEach(e=>{this.quillInstances.get(t.id).add(e)})}checkForQuantity(e){e.querySelector(this.selectors.number.number)&&e.querySelectorAll(this.selectors.number.number).forEach(t=>{let s={id:window.generateID("quant"),form:e.dataset.formId,ui:window.uiFromSelectors(this.selectors.number,t),element:t};t.dataset.numId=s.id,this.quantityFields.set(s.id,s),this.addQuantityListeners(t)})}addQuantityListeners(e){e.addEventListener("click",this.quantityClick)}removeQuantityListeners(e){e.removeEventListener("click",this.quantityClick)}handleQuantityClick(e){let t=this.quantityFields.get(e.target.closest("[data-num-id]")?.dataset.numId);if(!t)return;let s=0;if(t.ui.increase.contains(e.target)?s++:t.ui.decrease.contains(e.target)&&s--,0===s)return;this.getField(e.target);let i=t.ui.input.step;i=Math.max(i,1),e.ctrlKey&&e.shiftKey?i*=50:e.ctrlKey?i*=5:e.shiftKey&&(i*=10);let a=""===t.ui.input.value?0:parseFloat(t.ui.input.value);t.ui.input.value=a+i*s,a=parseFloat(t.ui.input.value),t.ui.input.min&&a<t.ui.input.min?(t.ui.input.value=t.ui.input.min,t.ui.decrease.disabled=!0):t.ui.input.max&&a>t.ui.input.max?(t.ui.input.value=t.ui.input.max,t.ui.increase.disabled=!0):(t.ui.decrease.disabled&&(t.ui.decrease.disabled=!1),t.ui.increase.disabled&&(t.ui.increase.disabled=!1))}checkForRepeaters(e){e.querySelector(this.selectors.repeater.repeater)&&e.querySelectorAll(this.selectors.repeater.repeater).forEach(t=>{let s={id:t.querySelector("template").className??window.generateID("repeater"),ui:window.uiFromSelectors(this.selectors.repeater,t),form:e.dataset.formId,element:t,field:this.getField(t),sortable:!1,rows:[]};if(!s.ui.add)return;let i=t.querySelector("template");this.templates.define(i.className,{manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach(t=>{window.prefixInput(t,`${a.repeater.dataset.field}:${r}:`,e,!1,!0)})}}),window.Sortable&&(s.sortable=new Sortable(s.ui.items,{animation:150,draggable:this.selectors.repeater.row,handle:this.selectors.repeater.handle,ghostClass:"repeater-row-ghost",chosenClass:"repeater-row-chosen",dragClass:"dragging",onEnd:()=>{this.reindexList(s.ui.items)}})),t.dataset.repeaterId=s.id,this.addRepeaterListeners(t),this.repeaters.set(s.id,s)})}addRepeaterListeners(e){e.addEventListener("click",this.repeaterClick)}removeRepeaterListeners(e){e.removeEventListener("click",this.repeaterClick)}handleRepeaterClick(e){e.target.matches(this.selectors.repeater.add)?this.addRepeaterRow(e.target.closest("[data-repeater-id]")):e.target.matches(this.selectors.repeater.remove)&&this.removeRepeaterRow(e.target.closest("[data-index]"))}addRepeaterRow(e){let t={};t.repeater=e;let s=this.repeaters.get(e.dataset.repeaterId),i=this.templates.create(e.dataset.repeaterId,t);s.rows.push({element:i,fields:Array.from(i.querySelectorAll("[data-field]"))}),this.repeaters.set(s.id,s),s.ui.items.append(i);let a=this.getForm(e);this.initializeFields(e,a),this.reindexList(s.ui.items),this.a11y.announce("Row added")}removeRepeaterRow(e){let t=e.closest("[data-repeater-id]");e.remove();let s=this.repeaters.get(t);s&&this.reindexList(s.ui.items),this.a11y.announce("Row removed")}checkForTagLists(e){e.querySelectorAll(this.selectors.tagList.tagList)?.forEach(t=>{let s={id:t.querySelector("template").className??window.generateID("tagList"),ui:window.uiFromSelectors(this.selectors.tagList,t),element:t,form:e.dataset.formId,format:t.dataset.tagFormat??"first_field"};if(!s.ui.input||!s.ui.add||!s.ui.items)return;t.dataset.tagListId=s.id,s.fieldName=t.dataset.field;let i=t.querySelector("template");this.templates.define(i.className,{refs:{label:this.selectors.tagList.label},manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach(e=>{let t=e.closest(".tag-item");window.prefixInput(e,`${a.fieldName}:${r}:`,t,!1,!0)}),t.label&&(t.label.textContent=a.label)}}),s.ui.inputs=Array.from(t.querySelectorAll(this.selectors.tagList.inputs)),s.ui.value=Array.from(t.querySelectorAll(this.selectors.tagList.value)),this.tagLists.set(s.id,s),this.addTagListListeners(t)})}addTagListListeners(e){e.addEventListener("click",this.tagListClick),e.addEventListener("keypress",this.tagListInput)}removeTagListListeners(e){e.removeEventListener("click",this.tagListClick),e.removeEventListener("keypress",this.tagListInput)}handleTagListClick(e){window.targetCheck(e,this.selectors.tagList.add)?this.addTagListItem(e.target.closest("[data-tag-list-id]")):window.targetCheck(e,this.selectors.tagList.remove)&&this.removeTagListItem(e.target.closest(this.selectors.tagList.item))}addTagListItem(e){let t=this.tagLists.get(e.dataset.tagListId);if(!t)return;let s,i={},a=!1,r=!0;for(let e of t.ui.inputs){const t=e.required||"true"===e.dataset.required,s=this.getFieldValue(e);s&&(a=!0);const n=this.validateField(e);t&&!s?(this.showError(e,"This field is required"),r=!1):n||(r=!1);const o=e.name.replace("new_","");i[o]=s}if(!r){this.a11y.announce("Please correct the errors before adding");const e=t.ui.inputs.find(e=>(e.required||"true"===e.dataset.required)&&!this.getFieldValue(e));return void(e&&e.focus())}if(!a)return this.a11y.announce("Please fill in at least one field"),void t.ui.inputs[0].focus();switch(t.format){case"first_field":s=Object.values(i)[0];break;case"all_fields":s=Object.values(i).join(", ");break;default:if(t.format.includes("{")){s=t.format;for(const[e,t]of Object.entries(i))s=s.replace(`{${e}}`,t)}else s=i[t.format]??Object.values(i)[0]}let n=this.templates.create(e.dataset.tagListId,{label:s,fieldName:t.fieldName});const o=t.ui.items?.children?.length??0;n?.querySelectorAll("input[type=hidden]")?.forEach(e=>{const s=e.dataset.field;e.name=`${t.fieldName}:${o}:${s}`,e.id=`${t.fieldName}:${o}:${s}`,e.value=i[s]||""}),t.ui.items.append(n);for(let e of t.ui.inputs)["checkbox","radio"].includes(e.type)?e.checked=!1:e.value="",this.clearValidation(e);t.ui.inputs[0]?.focus(),this.updateCollectionField(e),this.a11y.announce("Item added")}removeTagListItem(e){let t=e.closest("[data-tag-list-id]");t&&(e.remove(),this.reindexList(t),this.updateCollectionField(t),this.a11y.announce("Item removed"))}handleTagListInput(e){let t=e.target,s=t.closest("[data-tag-list-id]");if(!s)return;let i=this.tagLists.get(s.dataset.tagListId);if(i&&"Enter"===e.key)if(t===i.ui.inputs[i.ui.inputs.length-1])e.preventDefault(),this.addTagListItem(t.closest("[data-tag-list-id]"));else{e.preventDefault();let s=i.ui.inputs.indexOf(t);i.ui.inputs[s+1].focus()}}checkForConditionalFields(e){e.querySelectorAll(this.selectors.dependsOn).forEach(t=>{const s=t.dataset.dependsOn,i=t.dataset.dependsValue,a=t.dataset.dependsOperatior??"==";let r=this.forms.get(e.dataset.formId);this.dependencies.has(s)||Object.hasOwn(r.ui.fields,s)&&this.dependencies.set(s,[]);let n=this.dependencies.get(s);n&&(n.push({field:t,form:e.dataset.formId,requiredValue:i,operator:a}),this.dependencies.set(s,n)),this.checkFieldDependency(t,s)})}checkFieldDependency(e,t){const s=this.getForm(e);if(!this.dependencies.get(t))return;const i=this.getFieldValue(s.ui.fields[t]),a=this.evaluateCondition(i,e.dataset.dependsValue,e.dataset.dependsOperatior);this.toggleFieldVisibility(e,a)}evaluateCondition(e,t,s){const i=String(e||""),a=String(t||"");switch(s){case"==":default:return i===a;case"!=":return i!==a;case">":return parseFloat(i)>parseFloat(a);case"<":return parseFloat(i)<parseFloat(a);case">=":return parseFloat(i)>=parseFloat(a);case"<=":return parseFloat(i)<=parseFloat(a);case"contains":return i.includes(a);case"empty":return""===i;case"not_empty":return""!==i}}toggleFieldVisibility(e,t){const s=e.closest(".field, fieldset");s&&(s.hidden=!t,s.querySelectorAll("input, select, textarea").forEach(e=>{e.disabled=!t,!t&&e.hasAttribute("required")?(e.dataset.wasRequired="true",e.removeAttribute("required")):t&&"true"===e.dataset.wasRequired&&(e.setAttribute("required",""),delete e.dataset.wasRequired)}))}checkForCharacterLimits(e){e.querySelector(this.selectors.limits.hasLimit)&&(this.countUpdaters=this.updateCount.bind(this),e.querySelectorAll(this.selectors.limits.hasLimit).forEach(t=>{const s=this.getFieldInput(t);if(!s)return;let i=window.generateID("limit");s.dataset.charLimitId=i,s.dataset.limit=t.dataset.maxlength;let a={element:s,form:e.dataset.formId,ui:window.uiFromSelectors(this.selectors.limits,t)};a.ui.limit&&(a.ui.limit.textContent=t.dataset.maxlength),this.charLimits.set(i,a),this.addCharacterLimitListeners(s)}))}addCharacterLimitListeners(e){e.addEventListener("input",this.countUpdaters,{passive:!0})}removeCharacterLimitListeners(e){e.removeEventListener("input",this.countUpdaters,{passive:!0})}updateCount(e){let t=e.target,s=this.charLimits.get(t.dataset.charLimitId);if(!s)return;let i=t.value.length,a=t.dataset.limit;s.ui.current&&(s.ui.current.textContent=i,s.ui.current.classList.toggle("exceeded",i>=a)),i>a&&(t.value=t.value.slice(0,a))}checkForImageUploads(e,t){this.hasUploads=!0,window.jvbUploads.scanFields(e,t.options.autoUpload,t.options.imageMeta);let s=e.querySelectorAll('[data-field-type="upload"]');s&&(t.ui.uploads={},s.forEach(e=>{t.ui.uploads[e.dataset.field]=e}))}checkForTabs(e,t){window.jvbTabs&&e.querySelector("nav.tabs")&&(t.tabs=window.jvbTabs.registerTab(e,{preCheck:(e,t,s)=>this.validateStep(e,t,s)}),t.ui.tabs=window.uiFromSelectors(this.selectors.tabs,e),t.ui.tabs.sections=Array.from(e.querySelectorAll(this.selectors.tabs.sections)),t.ui.tabs.inputs={},t.ui.tabs.sections.forEach(e=>{t.ui.tabs.inputs[e.dataset.tab]=Array.from(e.querySelectorAll(this.inputSelectors))}),t.ui.tabs.buttons=Array.from(e.querySelectorAll(this.selectors.tabs.buttons)),t.unsubscribeTabs=window.jvbTabs.subscribe((e,s)=>{if("tab-switched"===e&&t.ui.tabs.progress){const e=t.ui.tabs.sections.filter(e=>e.dataset.tab===s.current)[0]??!1;if(!e)return;const i=e.dataset.step-1,a=t.ui.tabs.sections.length;window.showProgress(t.ui.tabs.progress,i,a,`Step ${i+1} of ${a}`)}}),this.forms.set(t.id,t))}validateStep(e,t,s){let i=!1;if(s.ui.sections&&s.ui.sections.forEach((a,r)=>{if(a===e){let e=r-1;if(e>=0){let a=s.ui.sections[e];a&&a.dataset.tab===t&&(i=!0)}}}),i)return!0;const a=e.closest("[data-form-id]")?.dataset.formId;if(!a)return!0;if(!this.forms.get(a))return!0;return Array.from(this.inputs.values()).filter(t=>t&&t.form===a&&t.section===e.dataset.tab&&!t.element.closest("[hidden]")).every(e=>!0===this.validateField(e.element))}checkForSelectors(e){window.jvbSelector&&window.jvbSelector.scanExistingFields(e)}reindexList(e){let t=e.dataset.field||e.dataset.repeaterId||e.dataset.tagListId;if(!t){let s=e.closest("[data-field]");t=s.dataset.field||s.dataset.repeaterId||s.dataset.tagListId}Array.from(e.children).forEach((e,s)=>{e.dataset.index=`${s}`;let i=e.querySelector(".row-number");i&&(i.textContent=`#${s+1}`);let a=e.querySelector(".row-title"),r=a?a.dataset.label:null;e.querySelectorAll("input, select, textarea").forEach(i=>{if("file"===i.type)return;const n=Object.hasOwn(i.dataset,"field")?i.dataset.field:i.name.split(":").pop();n===r&&(a.textContent=window.escapeHtml(i.value)),window.prefixInput(i,`${t}:${s}:${n}`,e,!0,!0)})}),this.updateCollectionField(e)}updateCollectionField(e){const t=e.closest("[data-field]");if(!t)return;const s=t.dataset.fieldType;if(!["repeater","tag-list"].includes(s))return;const i=this.getForm(e);if(!i)return;const a=this.getFieldValue(t);this.updateItem(t.dataset.field,a,i)}maybeUpdateCollectionDisplay(e){let t=e.closest("[data-repeater-id],[data-tag-list-id]");if(t&&"repeater"===t.dataset.fieldType){let t=e.closest(".repeater-row");if(!t)return;let s=t.querySelector(".row-title");if(!s||!s.dataset.label)return;console.log(e.dataset.field.split(":").pop()),console.log(s.dataset.label),e.dataset.field.split(":").pop()===s.dataset.label&&(s.textContent=this.getFieldValue(e.querySelector(this.inputSelectors)))}}clearValidation(e){let t=this.getField(e);if(!t)return;let s=this.getItem(e);s&&(t.classList.remove("has-error","has-success"),s.ui.success&&(s.ui.success.hidden=!0),s.ui.error&&(s.ui.error.hidden=!0),s.ui.message&&(s.ui.message.hidden=!0,s.ui.message.textContent=""))}showError(e,t="Invalid field"){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-success"),s.classList.add("has-error"),s.scrollIntoView({behavior:"smooth",block:"center",inline:"nearest"}),s.querySelector(this.inputSelectors)?.focus(),i.ui.message&&(i.ui.message.hidden=!1,i.ui.message.textContent=t))}showSuccess(e,t=""){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-error"),s.classList.add("has-success"),i.ui.message&&(i.ui.message.hidden=""===t,i.ui.message.textContent=t))}handleFormSuccess(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error").forEach(e=>e.classList.remove("field-error")),e.classList.add("form-success"),t.message){const s=document.createElement("div");s.className="form-success-message success-message",s.textContent=t.message,e.insertBefore(s,e.firstChild);const i=window.getIcon?.("check-circle");i&&(i.classList.add("success-icon"),s.prepend(i))}if(t.title||t.description){const s=document.createElement("div");if(s.className="success-box",t.title){const e=document.createElement("h3");e.textContent=t.title,s.appendChild(e)}if(t.description){(Array.isArray(t.description)?t.description:[t.description]).forEach(e=>{const t=document.createElement("p");t.textContent=e,s.appendChild(t)})}e.insertBefore(s,e.firstChild)}if(e.dataset.formId){this.store.delete(e.dataset.formId).catch(e=>{console.warn("Failed to clear form cache:",e)});const t=this.forms.get(e.dataset.formId);t&&(t.isDirty=!1,t.lastSaved=Date.now(),t.data={})}window.jvbA11y&&window.jvbA11y.announce(t.message||"Form submitted successfully")}handleFormError(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error, .has-error").forEach(e=>{e.classList.remove("field-error","has-error")}),e.querySelectorAll(".field").forEach(e=>{this.clearValidation(e)}),t.field){const s=e.querySelector(`[data-field="${t.field}"]`);if(s){this.showError(s,t.message),s.scrollIntoView({behavior:"smooth",block:"center"});const e=s.querySelector("input, textarea, select");e&&e.focus()}}else{const s=document.createElement("div");s.className="form-error error-message",s.textContent=t.message;const i=window.getIcon?.("close-circle");i&&(i.classList.add("error-icon"),s.prepend(i)),e.insertBefore(s,e.firstChild),e.scrollIntoView({behavior:"smooth",block:"start"})}if(window.jvbA11y){const e=t.field?`Error in ${t.field}: ${t.message}`:`Form error: ${t.message}`;window.jvbA11y.announce(e)}e.dispatchEvent(new CustomEvent("jvb-form-error",{detail:t}))}showFormStatus(e,t,s=""){let i=this.forms.get(e);i&&i.options.showStatus&&i.ui?.status?.status&&i.status!==t&&(i.status=t,i.ui.status.status.hidden=!1,i.ui.status.status.classList.toggle("loading",["uploading","saving"].includes(t)),i.ui.status.message.textContent=""===s?this.getDefaultMessage(t):s,i.ui.status.icon.className="icon icon-"+this.getDefaultIcon(t),setTimeout(()=>i.ui.status.status.hidden=!0,"submitted"===t?3e3:1e4))}getDefaultMessage(e){return{saving:"Saving changes...",autosaved:"Changes saved locally. Submit form to send to server.",uploading:"Uploading your form to server",submitted:"Successfully sent to server",pending:"Unsaved changes",restored:"Welcome back! We've restored your previous entry.",error:"Failed to save changes. Refresh and try again?",offline:"Changes will be saved when online"}[e]??e}getDefaultIcon(e){return{autosaved:"check-circle",submitted:"check-circle",restored:"history",error:"close-circle",offline:"cloud-slash",pending:"exclamation-mark"}[e]??""}showSummary(e){let t=this.templates.create("formSummary",e);e.config.element.after(t),window.fade(e.config.element,!1)}getForm(e){let t=e.closest("[data-form-id]");if(!t)return!1;let s=t.dataset.formId;if(!s)return!1;let i=this.forms.get(s);return i||!1}getField(e){return e.closest("[data-field]")}getFieldType(e){let t=this.getField(e);if(t)return t.dataset.fieldType}getFieldValue(e){let t=this.getFieldType(e),s=this.getItem(e),i=s.field?.dataset.field??!1;if(!i)return!1;switch(t){case"repeater":return this.getRepeaterValue(e,s);case"tag-list":return this.getTagListValue(e,s);case"group":return null;case"location":return this.getLocationValue(e,s);case"selector":case"upload":case"gallery":case"image":return this.getHiddenInputValue(e,s,i);case"true-false":case"toggle-text":return e.checked;case"checkbox":return e.name.endsWith("[]")?this.getCheckboxGroupValue(e,s):e.checked?e.value:"";default:return e.value}}getCheckboxGroupValue(e,t){return t.checkboxGroup||(t.checkboxGroup=t.field?.querySelectorAll(`input[type="checkbox"][name="${e.name}"]`),this.saveItem(t)),Array.from(t.checkboxGroup).filter(e=>e.checked).map(e=>e.value)}getFieldCheckedValue(e){if("checkbox"===e.type){return"true-false"===this.getFieldType(e)?e.checked:e.checked?e.value:""}if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`),s=Array.from(t).find(e=>e.checked);return s?s.value:""}return this.getFieldValue(e)}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}getRepeaterValue(e,t){const s=e.querySelector(".repeater-items");if(!s)return[];let i=["image_data","image-title","image-caption","image-description","image-alt-text"],a=[];return Array.from(s.children).forEach(e=>{let t={};e.querySelectorAll('input[type="hidden"]').forEach(e=>{t[e.name]=e.value}),e.querySelectorAll("[data-field]").forEach(e=>{if(!i.includes(e.dataset.field)){const s=this.getFieldInput(e);s&&(t[e.dataset.field]=this.getFieldValue(s))}}),a.push(t)}),a}getFieldInput(e){const t=e.querySelector("textarea[data-editor]");return t||e.querySelector(this.inputSelectors)}getTagListValue(e,t){t.container||(t.container=t.field?.querySelector(".tag-items"),this.saveItem(t));let s=[];return Array.from(t.container.children).forEach(e=>{let t=e.querySelectorAll('input[type="hidden"]'),i={};t.forEach(e=>{i[e.dataset.field]=e.value}),s.push(i)}),s}getLocationValue(e,t){t.values||(t.values=Array.from(t.field?.querySelectorAll("[data-location-field]")),this.saveItem(t));let s={};return t.values.forEach(e=>{s[e.dataset.locationField]=e.value}),s}getHiddenInputValue(e,t,s){return"INPUT"===e.tagName&&"hidden"===e.type||(e=e.querySelector('input[type="hidden"][name="'+s+'"]'))?(void 0!==t.value&&t.value===e.value||(t.value=e.value,this.saveItem(t)),t.value):null}formatValueForSummary(e,t){const s=this.getFieldType(t.element);if(this.isEmptyValue(e))return"";switch(s){case"repeater":return this.formatRepeaterForSummary(e,t);case"tag-list":return this.formatTagListForSummary(e,t);case"location":return this.formatLocationForSummary(e);case"true-false":return e?"Yes":"No";case"checkbox":return Array.isArray(e)?this.formatCheckboxGroupForSummary(e,t):this.getDisplayLabel(t,e);case"selector":case"upload":case"image":case"gallery":return this.formatHiddenFieldForSummary(e,t,s);default:return"string"==typeof e?this.getDisplayLabel(t,e):"string"==typeof e&&e.includes("\n")?this.convertLineBreaks(e):e}}formatCheckboxGroupForSummary(e,t){return e.map(e=>this.getDisplayLabel(t,e)).join(", ")}convertLineBreaks(e){const t=document.createElement("span");return t.innerHTML=e.split("\n").join("<br>"),t}formatRepeaterForSummary(e,t){const s=document.createElement("div");return s.className="summary-repeater",e.forEach((e,i)=>{const a=document.createElement("div");a.className="summary-repeater-row";const r=document.createElement("strong");r.textContent=`Entry ${i+1}:`,a.appendChild(r);const n=document.createElement("ul");n.className="summary-repeater-fields";for(const[s,i]of Object.entries(e)){if(this.isEmptyValue(i))continue;const e=document.createElement("li"),a=t.field?.querySelector(`[data-field="${s}"]`),r=a?.closest(".field")?.querySelector("label")?.textContent.replace("*","").trim()||s;e.innerHTML=`<span class="field-label">${r}:</span> <span class="field-value">${i}</span>`,n.appendChild(e)}a.appendChild(n),s.appendChild(a)}),s}formatTagListForSummary(e,t){const s=document.createElement("div");s.className="summary-taglist";const i=document.createElement("ul");return i.className="summary-tags",e.forEach(e=>{const t=document.createElement("li");t.className="summary-tag";const s=Object.values(e).find(e=>!this.isEmptyValue(e))||"",a=Object.entries(e).filter(([e,t])=>!this.isEmptyValue(t));a.length>1?t.textContent=a.map(([e,t])=>t).join(", "):t.textContent=s,i.appendChild(t)}),s.appendChild(i),s}formatLocationForSummary(e){const t=[];return e.street&&t.push(e.street),e.city&&t.push(e.city),e.province&&t.push(e.province),e.postal_code&&t.push(e.postal_code),e.country&&t.push(e.country),t.length>0?t.join(", "):e.address||""}formatHiddenFieldForSummary(e,t,s){if(["upload","gallery","image"].includes(s)){const s=t.field?.querySelector("[data-upload-field]");if(s){const e=s.querySelectorAll(".item-grid.preview img");if(e.length>0){const t=document.createElement("div");return t.className="summary-uploads",e.forEach(e=>{const s=e.cloneNode(!0);s.style.maxWidth="100px",s.style.maxHeight="100px",t.appendChild(s)}),t}}return`${e.split(",").length} file(s) uploaded`}return e}getDisplayLabel(e,t){if(!e.element)return t;const s=e.element.type;if("radio"===s){const s=e.field.querySelectorAll(`input[type="radio"][name="${e.element.name}"]`),i=Array.from(s).find(e=>e.value===t);if(i){const t=i.closest("label")||e.field.querySelector(`label[for="${i.id}"]`);if(t)return t.textContent.replace("*","").trim()}}if("checkbox"===s&&"true-false"!==this.getFieldType(e.element)){const s=e.field.querySelector(`input[type="checkbox"][value="${t}"]`);if(s){const t=s.closest("label")||e.field.querySelector(`label[for="${s.id}"]`);if(t){const e=t.querySelector("span");return e?e.textContent.trim():t.textContent.replace("*","").trim()}}}return t}getItem(e,t=null){const s=Object.hasOwn(e.dataset,"ref");let i=s?e.dataset.ref:window.generateID("input");if(s||(e.dataset.ref=i),!this.inputs.has(i)){t||(t=e.closest("[data-form-id]")?.dataset.formId??!1);let s=this.getField(e);this.inputs.set(i,{id:i,element:e,form:t,field:s,section:e.closest("[data-tab]")?.dataset.tab??!1,ui:window.uiFromSelectors(this.selectors.fields,s)})}return this.inputs.get(i)}saveItem(e){this.inputs.set(e.id,e)}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("HandleSelection subscriber error:",e)}})}destroy(){this.forms.size>0&&(Array.from(this.forms.values()).forEach(e=>{this.removeFormListeners(e)}),this.forms.clear()),this.repeaters.size>0&&(Array.from(this.repeaters.values()).forEach(e=>{this.removeRepeaterListeners(e.element),e.sortable?.destroy()}),this.repeaters.clear()),this.quantityFields.size>0&&(Array.from(this.quantityFields.values()).forEach(e=>{this.removeQuantityListeners(e.element)}),this.quantityFields.clear()),this.tagLists.size>0&&(Array.from(this.tagLists.values()).forEach(e=>{this.removeTagListListeners(e.element)}),this.tagLists.clear()),this.charLimits.size>0&&Array.from(this.charLimits.values()).forEach(e=>{e.element.removeEventListener("input",this.countUpdaters)}),this.inputs.clear(),this.forms.clear(),this.charLimits.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbForm=new e,document.querySelectorAll("form[data-auto]").forEach(e=>{window.jvbForm.registerForm(e,{})}))})})})();
\ No newline at end of file
+(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.queue=window.jvbQueue,this.populate=window.jvbPopulate,this.changes=new Map,this.forms=new Map,this.inputs=new Map,this.repeaters=new Map,this.tagLists=new Map,this.charLimits=new Map,this.quantityFields=new Map,this.quillInstances=new Map,this.dependencies=new Map,this.subscribers=new Set,this.isRestoring=!1,this.hasListeners=!1,this.hasUploads=!1,this.summaryTemplate=!1,this.init()}init(){this.templates=window.jvbTemplates,this.defineSummaryTemplate(),this.initElements(),this.initListeners(),this.initStore(),this.initValidators(),this.initUploadSubscription()}initUploadSubscription(){window.jvbUploads.subscribe((e,t)=>{if(this.hasUploads&&"upload-received"===e){let e=this.getForm(t.field);e&&this.updateItem(`${t.field.dataset.field}_tempUpload`,t.id,e)}})}initElements(){this.inputSelectors="input, textarea, select",this.selectors={tabs:{nav:"nav.tabs",sections:".tab-content",progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},buttons:"nav.tabs button"},dependsOn:"[data-depends-on]",forms:{status:{status:".fstatus",message:".fstatus .message",icon:".fstatus .icon",actions:".fstatus .actions"},restore:{container:".restore-form",restore:'[data-action="restore"]',clear:'[data-action="clear"]'}},inputs:this.inputSelectors,fields:{field:".field",label:"label",success:".success",error:".error",message:".validation-message"},repeater:{repeater:".repeater",row:".repeater-row",handle:".drag-handle",header:".repeater-row-header",remove:".remove-row",add:".add-repeater-row",template:"template",items:".repeater-items",inputs:this.inputSelectors},tagList:{tagList:".field.tag-list",input:".row",add:".add-tag",remove:".remove-tag",label:".tag-label",items:".tag-items",item:".tag-item",inputs:this.inputSelectors,value:'input[type="hidden"]'},tag:{label:".tag-label"},number:{number:".field div.quantity",increase:"button.increase",decrease:"button.decrease",input:'input[type="number"]'},limits:{hasLimit:"[data-maxlength]",limit:".limit",current:".current"}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.blurHandler=this.handleBlur.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleSubmit.bind(this),this.quantityClick=this.handleQuantityClick.bind(this),this.repeaterClick=this.handleRepeaterClick.bind(this),this.tagListClick=this.handleTagListClick.bind(this),this.tagListInput=this.handleTagListInput.bind(this)}addFormListeners(e){e.addEventListener("click",this.clickHandler),e.addEventListener("change",this.changeHandler),e.addEventListener("input",this.inputHandler),e.addEventListener("blur",this.blurHandler),e.addEventListener("submit",this.submitHandler)}removeFormListeners(e){e.removeEventListener("click",this.clickHandler),e.removeEventListener("change",this.changeHandler),e.removeEventListener("input",this.inputHandler),e.removeEventListener("blur",this.blurHandler),e.removeEventListener("submit",this.submitHandler)}initStore(){const e=window.jvbStore.register("forms",{storeName:"forms",keyPath:"id",indexes:[{name:"src",keyPath:"src"},{name:"timestamp",keyPath:"timestamp"},{name:"formType",keyPath:"type"}],TTL:1008e4});this.store=e.forms,this.store.subscribe((e,t)=>{if("data-ready"===e){let e=this.store.getFiltered().filter(e=>e.src===window.location.pathname);for(let t of e)this.showPendingNotification(t.id,t.changes)}else"operation-status"===e&&"completed"===t.status&&t.config&&this.store.delete(t.config.id)})}showPendingNotification(e,t){let s=this.forms.get(e);if(!s)return;let i=s.element;if(!i)return void console.warn(`Form element not found for: ${e}`);s.ui.restore.container.hidden=!1;const a=async(e,t)=>{this.isRestoring=!0;let i={fields:e};await this.checkStoredUploads(e,t),this.populate.populate(t,i),this.a11y.announce("Previous changes restored"),this.isRestoring=!1,s.ui.restore.container.remove()},r=async e=>{await this.checkStoredUploads(t,i,!1),await this.store.delete(e),this.a11y.announce("Previous changes discarded"),s.ui.restore.container.remove()};s.ui.restore.restore.addEventListener("click",()=>a(t,i)),s.ui.restore.clear.addEventListener("click",async()=>r(e))}async checkStoredUploads(e,t,s=!0){let i=this.forms.get(t.dataset.formId);if(!i)return;let a=[];for(let[t,s]of Object.entries(e))if(t.includes("_tempUpload")){let e=t.replace("_tempUpload","");Object.hasOwn(i.ui.uploads,e)&&(a=[...a,...s])}a.length>0&&(s?await window.jvbUploads.restoreUploads(a):await window.jvbUploads.clearUploads(a))}initValidators(){this.validators={email:{pattern:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,message:"Please enter a valid email address"},url:{pattern:/^https?:\/\/.+\..+/,message:"Please enter a valid URL starting with https://"},phone:{pattern:/^[\d\s\-+().]+$/,message:"Please enter a valid phone number"},number:{test:(e,t)=>{const s=parseFloat(e);if(isNaN(s))return"Please enter a valid number";const i=t.dataset.min,a=t.dataset.max;return void 0!==i&&s<parseFloat(i)?`Value must be at least ${i}`:!(void 0!==a&&s>parseFloat(a))||`Value must be at most ${a}`}},text:{test:(e,t)=>{const s=t.dataset.minlength,i=t.dataset.maxlength;return s&&e.length<parseInt(s)?`Must be at least ${s} characters`:!(i&&e.length>parseInt(i))||`Must be no more than ${i} characters`}}}}validateField(e){const t=this.performValidation(e);return this.updateValidationUI(e,t),t.isValid}performValidation(e){const t=e.closest(".field"),s=this.getFieldCheckedValue(e);if(!s&&!e.required)return{isValid:!0,message:""};if(e.required)if("checkbox"===e.type){if(!e.checked)return{isValid:!1,message:"This field is required"}}else if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`);if(!Array.from(t).some(e=>e.checked))return{isValid:!1,message:"Please select an option"}}else if(!s)return{isValid:!1,message:"This field is required"};if(e.checkValidity&&!e.checkValidity())return{isValid:!1,message:e.validationMessage};if(s&&Object.hasOwn(t.dataset,"pattern")){if(!new RegExp(t.dataset.pattern).test(s))return{isValid:!1,message:t.dataset.validationMessage||"Invalid format"}}if(Object.hasOwn(t.dataset,"validate")||e.type){const i=this.validators[t.dataset.validate||e.type];if(i&&i.pattern&&!i.pattern.test(s))return{isValid:!1,message:i.message};if(i&&i.test){const e=i.test(s,t);if(!0!==e)return{isValid:!1,message:e}}}return{isValid:!0,message:""}}updateValidationUI(e,t){t.isValid?this.showSuccess(e,t.message):this.showError(e,t.message)}handleClick(e){let t=this.getForm(e.target);if(!t)return;const s=window.targetCheck(e,"[data-action]");if(s){switch(s.dataset.action){case"clear-form":this.store.delete(t.id),t.element.reset(),t.ui.status.status.hidden=!0,this.a11y.announce("Form cleared, starting fresh");break;case"dismiss-restore":t.ui.status.status.hidden=!0;break;case"next-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s+1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}});break;case"prev-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s-1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}})}}}handleChange(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getField(e.target);const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');if(s){if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}const e=s.dataset.field;return this.maybeUpdateCollectionDisplay(t),void window.debouncer.schedule(`collection:${e}`,()=>this.updateCollectionField(s),150)}if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}let i=this.getForm(e.target);this.updateItem(t.dataset.field,this.getFieldValue(e.target),i)}handleBlur(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target).dataset.field;window.debouncer.cancel(`form:${t.id}:validate:${s}`),this.validateField(e.target);const i=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');i?this.updateCollectionField(i):this.updateItem(s,this.getFieldValue(e.target),t)}handleInput(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target);if(!s)return;const i=e.target,a=s.dataset.field;this.showFormStatus(t.id,"pending"),window.debouncer.schedule(`form:${t.id}:validate:${a}`,()=>this.validateField(i),500)}async handleSubmit(e){let t=this.getForm(e.target);if(t){if(this.subscribers.size>0)if(e.preventDefault(),t.options.cache){this.cancelBackup(),await this.backup();const e=await this.store.get(t.id);this.notify("form-submit",{config:t,data:e.changes})}else this.notify("form-submit",{config:t,data:this.changes.get(t.id)?.changes??{}});if(t.options.endpoint&&!t.options.handled&&(e.preventDefault(),console.log("Submitting form...",this.changes.get(t.id)?.changes??{}),await this.handleServerSave(t,this.changes.get(t.id)?.changes??{})),t.options.showSummary){const e=await this.store.get(t.id);this.showSummary({config:t,changes:e?.changes})}}}updateItem(e,t,s){if(void 0===t)return;this.changes.has(s.id)||this.changes.set(s.id,{id:s.id,timestamp:Date.now(),src:window.location.pathname,changes:{}});let i=this.changes.get(s.id);e.includes("_tempUpload")?(Object.hasOwn(i.changes,e)||(i.changes[e]=[]),i.changes[e].push(t)):i.changes[e]=t,s.tabs&&this.updateStepProgress(s),this.changes.set(s.id,i),s.options.cache&&this.scheduleBackup(),s.options.endpoint&&!s.options.handled&&this.scheduleServerSave(s,i)}updateStepProgress(e){if(console.log("First check in updateStepProgress"),!e.tabs)return;let t=this.getCurrentStep(e);if(console.log("Current step: ",t),!t)return;t-=1;let s=this.getCurrentSection(e);if(console.log("Current section: ",s),!s)return;let i=0,a=0;if(s.childNodes.forEach(e=>{if(console.log("Child node: ",e),e.classList.contains("field")){i++;let t=this.getFieldValue(e.querySelector(this.inputSelectors));console.log("Value",t),t.length>0&&a++}}),0===i)return;let r=t+a/i,n=e.ui.tabs.sections.length;window.showProgress(e.ui.tabs.progress,r,n,`Step ${t+1} of ${n}`)}getCurrentStep(e){let t=null;return e.tabs.ui.buttons.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s.dataset.step)}),t}getCurrentSection(e){console.log("Getting current section: ",e.tabs.ui.sections);let t=null;return e.tabs.ui.sections.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s)}),t}scheduleBackup(){window.debouncer.schedule("form_changes",async()=>{this.changes.size>0&&await this.backup()},2e3)}cancelBackup(){window.debouncer.cancel("form_changes")}async backup(){const e=new Map;for(let[t,s]of this.changes.entries()){const i=await this.store.get(t);i?e.set(t,{...i,...s,changes:{...i.changes,...s.changes},timestamp:Date.now()}):e.set(t,s)}await this.store.saveMany(e);for(let e of this.changes.keys())this.showFormStatus(e,"autosaved");this.changes.clear()}saveCache(e){if(!this.changes.has(e))return;let t=this.changes.get(e);0!==t.size&&(this.store.save(t).then(()=>{}),this.changes.delete(e))}scheduleServerSave(e,t){window.debouncer.schedule(`form_${e.id}_server_save`,async()=>{await this.handleServerSave(e,t)},1500)}cancelServerSave(e){window.debouncer.cancel(`form_${e}_server_save`)}async handleServerSave(e,t){if(this.cancelServerSave(e.id),e.useQueue&&window.jvbQueue)window.jvbQueue.addToQueue({endpoint:e.options.endpoint,data:t,headers:e.options.headers??{}});else try{let s=await window.auth.fetch(jvbSettings.api+e.options.endpoint,{body:t,headers:e.options.headers??{},method:"POST"});if(!s.ok){let t;try{t=await s.json()}catch{t={message:s.statusText||"Unknown HTTP error"}}return void this.notify("form-error",{config:e,data:t})}let i=await s.json();this.notify("form-success",{config:e,result:i})}catch(t){t.status?this.notify("form-error",{config:e,data:t}):t instanceof SyntaxError?(this.notify("form-error",{config:e,data:{status:"error",success:!1,message:"Something went wrong"}}),console.error("Data Error: Server returned invalid JSON format.")):this.notify("form-error",{config:e,data:{status:"network_error",message:"Network error: could not connect to the server"}})}}registerForm(e,t){if(t={autoUpload:!1,imageMeta:!0,delay:1500,useQueue:!0,endpoint:Object.hasOwn(e.dataset,"save")?e.dataset.save:"",showStatus:!0,showSummary:!1,handled:Object.hasOwn(e.dataset,"handled"),cache:!0,ignore:[],...t},Object.hasOwn(e.dataset,"formId")&&this.forms.has(e.dataset.formId))return;Object.hasOwn(e.dataset,"formId")||(e.dataset.formId=window.generateID("form_")),Object.hasOwn(e.dataset,"action")&&(t.headers=window.auth.getHeader(e.dataset.action));const s=e.dataset.formId;this.addFormListeners(e);const i={element:e,id:s,status:"",options:t,ui:window.uiFromSelectors(this.selectors.forms,e)};return i.ui.fields={},e.querySelectorAll("[data-field]").forEach(e=>{i.ui.fields[e.dataset.field]=e}),this.initializeFields(e,i),this.forms.set(s,i),i}clearForm(e){const t=this.forms.get(e);if(!t)return;t.unsubscribeTabs&&t.unsubscribeTabs(),t.tabs&&window.jvbTabs.removeTab(t.element),t.cache&&this.changes.has(e)&&this.saveCache(e);for(let[t,s]of this.inputs.entries())s.form===e&&this.inputs.delete(t);if(this.dependencies.forEach((t,s)=>{0===(t=t.filter(t=>t.form!==e)).length&&this.dependencies.delete(s)}),Object.hasOwn(t,"hasQuill")&&this.quillInstances.has(e)){this.quillInstances.get(e).forEach(e=>{e.disable(),e.off("text-change"),e.off("selection-change");const t=e.container.parentElement,s=t?.querySelector(".ql-toolbar");if(s&&s.remove(),e.setText(""),t&&t.classList.contains("editor-container")){const e=t.nextElementSibling;"TEXTAREA"===e?.tagName&&(e.style.display=""),t.remove()}}),this.quillInstances.delete(e)}let s={repeater:this.repeaters,tagList:this.tagLists,charLimit:this.charLimits,quantity:this.quantityFields};for(let[t,i]of Object.entries(s)){if(0===i.size)continue;let s=Array.from(i.values()).filter(t=>t.form===e);s.length>0&&s.forEach(e=>{switch(t){case"repeater":this.removeRepeaterListeners(e.element);break;case"tagList":this.removeTagListListeners(e.element);break;case"charLimit":this.removeCharacterLimitListeners(e.element);break;case"quantity":this.removeQuantityListeners(e.element)}i.has(e.id)&&i.delete(e.id)})}this.removeFormListeners(t.element),this.forms.delete(e),window.debouncer.cancel("form_changes")}defineSummaryTemplate(){this.summaryTemplate=!0;let e=this;this.templates.define("formSummary",{refs:{result:".result",h3:"h3",p:"p"},setup({el:t,refs:s,manyRefs:i,data:a}){const r=["sendAll",...a?.config?.options?.ignore??[]];for(let[i,n]of Object.entries(a.changes)){if(r.includes(i)||e.isEmptyValue(n))continue;let a=Array.from(e.inputs.values()).find(e=>e.field?.dataset.field===i);if(!a)continue;let o=s.result.cloneNode(!0),l=o.querySelector("h3"),d=o.querySelector("p");const c=a.field?.querySelector("legend");l.textContent=c?c.textContent.replace("*","").trim():a.ui.label?.textContent.replace("*","").trim();const u=e.formatValueForSummary(n,a);u instanceof HTMLElement?d.replaceWith(u):d.textContent=u,t.append(o)}let n=a.config?.element?.querySelectorAll("[data-upload-field]");n&&n.forEach(e=>{let i=e.querySelector("h2")?.textContent??"Upload:",a=e.querySelectorAll(".item-grid.preview img"),r=s.result.cloneNode(!0);if(a){let e=s.result.cloneNode(!0),n=r.querySelector("h3"),o=r.querySelector("p");o?.remove(),n&&(n.textContent=i),a.forEach(t=>{t=t.cloneNode(!0),e.append(t)}),t.append(e)}}),s.result?.remove()}})}initializeFields(e,t=null){const s={"[data-editor]":()=>this.checkForQuill(e,t),"div.quantity":()=>this.checkForQuantity(e),".repeater":()=>this.checkForRepeaters(e,t),".field.tag-list":()=>this.checkForTagLists(e),"[data-depends-on]":()=>this.checkForConditionalFields(e),"[data-limit]":()=>this.checkForCharacterLimits(e),"[data-uploader],[data-upload-field]":()=>this.checkForImageUploads(e,t),"nav.tabs":()=>this.checkForTabs(e,t),'[data-type="selector"]':()=>this.checkForSelectors(e)};for(const[t,i]of Object.entries(s))e.querySelector(t)&&i();Array.from(e.querySelectorAll(this.inputSelectors)).filter(e=>!e.closest(".ql-clipboard")).map(e=>{this.getItem(e,t?.id)})}checkForQuill(e,t){if(!e.querySelector("[data-editor]"))return;t&&!Object.hasOwn(t,"hasQuill")&&(t.hasQuill=!0,this.forms.set(t.id,t)),this.quillInstances.has(t.id)||this.quillInstances.set(t.id,new Set);window.jvbQuill(e).forEach(e=>{this.quillInstances.get(t.id).add(e)})}checkForQuantity(e){e.querySelector(this.selectors.number.number)&&e.querySelectorAll(this.selectors.number.number).forEach(t=>{let s={id:window.generateID("quant"),form:e.dataset.formId,ui:window.uiFromSelectors(this.selectors.number,t),element:t};t.dataset.numId=s.id,this.quantityFields.set(s.id,s),this.addQuantityListeners(t)})}addQuantityListeners(e){e.addEventListener("click",this.quantityClick)}removeQuantityListeners(e){e.removeEventListener("click",this.quantityClick)}handleQuantityClick(e){let t=this.quantityFields.get(e.target.closest("[data-num-id]")?.dataset.numId);if(!t)return;let s=0;if(t.ui.increase.contains(e.target)?s++:t.ui.decrease.contains(e.target)&&s--,0===s)return;this.getField(e.target);let i=t.ui.input.step;i=Math.max(i,1),e.ctrlKey&&e.shiftKey?i*=50:e.ctrlKey?i*=5:e.shiftKey&&(i*=10);let a=""===t.ui.input.value?0:parseFloat(t.ui.input.value);t.ui.input.value=a+i*s,a=parseFloat(t.ui.input.value),t.ui.input.min&&a<t.ui.input.min?(t.ui.input.value=t.ui.input.min,t.ui.decrease.disabled=!0):t.ui.input.max&&a>t.ui.input.max?(t.ui.input.value=t.ui.input.max,t.ui.increase.disabled=!0):(t.ui.decrease.disabled&&(t.ui.decrease.disabled=!1),t.ui.increase.disabled&&(t.ui.increase.disabled=!1))}checkForRepeaters(e){e.querySelector(this.selectors.repeater.repeater)&&e.querySelectorAll(this.selectors.repeater.repeater).forEach(t=>{let s={id:t.querySelector("template").className??window.generateID("repeater"),ui:window.uiFromSelectors(this.selectors.repeater,t),form:e.dataset.formId,element:t,field:this.getField(t),sortable:!1,rows:[]};if(!s.ui.add)return;let i=t.querySelector("template");this.templates.define(i.className,{manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach(t=>{window.prefixInput(t,`${a.repeater.dataset.field}:${r}:`,e,!1,!0)})}}),window.Sortable&&(s.sortable=new Sortable(s.ui.items,{animation:150,draggable:this.selectors.repeater.row,handle:this.selectors.repeater.handle,ghostClass:"repeater-row-ghost",chosenClass:"repeater-row-chosen",dragClass:"dragging",onEnd:()=>{this.reindexList(s.ui.items)}})),t.dataset.repeaterId=s.id,this.addRepeaterListeners(t),this.repeaters.set(s.id,s)})}addRepeaterListeners(e){e.addEventListener("click",this.repeaterClick)}removeRepeaterListeners(e){e.removeEventListener("click",this.repeaterClick)}handleRepeaterClick(e){e.target.matches(this.selectors.repeater.add)?this.addRepeaterRow(e.target.closest("[data-repeater-id]")):e.target.matches(this.selectors.repeater.remove)&&this.removeRepeaterRow(e.target.closest("[data-index]"))}addRepeaterRow(e){let t={};t.repeater=e;let s=this.repeaters.get(e.dataset.repeaterId),i=this.templates.create(e.dataset.repeaterId,t);s.rows.push({element:i,fields:Array.from(i.querySelectorAll("[data-field]"))}),this.repeaters.set(s.id,s),s.ui.items.append(i);let a=this.getForm(e);this.initializeFields(e,a),this.reindexList(s.ui.items),this.a11y.announce("Row added")}removeRepeaterRow(e){let t=e.closest("[data-repeater-id]");e.remove();let s=this.repeaters.get(t);s&&this.reindexList(s.ui.items),this.a11y.announce("Row removed")}checkForTagLists(e){e.querySelectorAll(this.selectors.tagList.tagList)?.forEach(t=>{let s={id:t.querySelector("template").className??window.generateID("tagList"),ui:window.uiFromSelectors(this.selectors.tagList,t),element:t,form:e.dataset.formId,format:t.dataset.tagFormat??"first_field"};if(!s.ui.input||!s.ui.add||!s.ui.items)return;t.dataset.tagListId=s.id,s.fieldName=t.dataset.field;let i=t.querySelector("template");this.templates.define(i.className,{refs:{label:this.selectors.tagList.label},manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach(e=>{let t=e.closest(".tag-item");window.prefixInput(e,`${a.fieldName}:${r}:`,t,!1,!0)}),t.label&&(t.label.textContent=a.label)}}),s.ui.inputs=Array.from(t.querySelectorAll(this.selectors.tagList.inputs)),s.ui.value=Array.from(t.querySelectorAll(this.selectors.tagList.value)),this.tagLists.set(s.id,s),this.addTagListListeners(t)})}addTagListListeners(e){e.addEventListener("click",this.tagListClick),e.addEventListener("keypress",this.tagListInput)}removeTagListListeners(e){e.removeEventListener("click",this.tagListClick),e.removeEventListener("keypress",this.tagListInput)}handleTagListClick(e){window.targetCheck(e,this.selectors.tagList.add)?this.addTagListItem(e.target.closest("[data-tag-list-id]")):window.targetCheck(e,this.selectors.tagList.remove)&&this.removeTagListItem(e.target.closest(this.selectors.tagList.item))}addTagListItem(e){let t=this.tagLists.get(e.dataset.tagListId);if(!t)return;let s,i={},a=!1,r=!0;for(let e of t.ui.inputs){const t=e.required||"true"===e.dataset.required,s=this.getFieldValue(e);s&&(a=!0);const n=this.validateField(e);t&&!s?(this.showError(e,"This field is required"),r=!1):n||(r=!1);const o=e.name.replace("new_","");i[o]=s}if(!r){this.a11y.announce("Please correct the errors before adding");const e=t.ui.inputs.find(e=>(e.required||"true"===e.dataset.required)&&!this.getFieldValue(e));return void(e&&e.focus())}if(!a)return this.a11y.announce("Please fill in at least one field"),void t.ui.inputs[0].focus();switch(t.format){case"first_field":s=Object.values(i)[0];break;case"all_fields":s=Object.values(i).join(", ");break;default:if(t.format.includes("{")){s=t.format;for(const[e,t]of Object.entries(i))s=s.replace(`{${e}}`,t)}else s=i[t.format]??Object.values(i)[0]}let n=this.templates.create(e.dataset.tagListId,{label:s,fieldName:t.fieldName});const o=t.ui.items?.children?.length??0;n?.querySelectorAll("input[type=hidden]")?.forEach(e=>{const s=e.dataset.field;e.name=`${t.fieldName}:${o}:${s}`,e.id=`${t.fieldName}:${o}:${s}`,e.value=i[s]||""}),t.ui.items.append(n);for(let e of t.ui.inputs)["checkbox","radio"].includes(e.type)?e.checked=!1:e.value="",this.clearValidation(e);t.ui.inputs[0]?.focus(),this.updateCollectionField(e),this.a11y.announce("Item added")}removeTagListItem(e){let t=e.closest("[data-tag-list-id]");t&&(e.remove(),this.reindexList(t),this.updateCollectionField(t),this.a11y.announce("Item removed"))}handleTagListInput(e){let t=e.target,s=t.closest("[data-tag-list-id]");if(!s)return;let i=this.tagLists.get(s.dataset.tagListId);if(i&&"Enter"===e.key)if(t===i.ui.inputs[i.ui.inputs.length-1])e.preventDefault(),this.addTagListItem(t.closest("[data-tag-list-id]"));else{e.preventDefault();let s=i.ui.inputs.indexOf(t);i.ui.inputs[s+1].focus()}}checkForConditionalFields(e){e.querySelectorAll(this.selectors.dependsOn).forEach(t=>{const s=t.dataset.dependsOn,i=t.dataset.dependsValue,a=t.dataset.dependsOperatior??"==";let r=this.forms.get(e.dataset.formId);this.dependencies.has(s)||Object.hasOwn(r.ui.fields,s)&&this.dependencies.set(s,[]);let n=this.dependencies.get(s);n&&(n.push({field:t,form:e.dataset.formId,requiredValue:i,operator:a}),this.dependencies.set(s,n)),this.checkFieldDependency(t,s)})}checkFieldDependency(e,t){const s=this.getForm(e);if(!this.dependencies.get(t))return;const i=this.getFieldValue(s.ui.fields[t]),a=this.evaluateCondition(i,e.dataset.dependsValue,e.dataset.dependsOperatior);this.toggleFieldVisibility(e,a)}evaluateCondition(e,t,s){const i=String(e||""),a=String(t||"");switch(s){case"==":default:return i===a;case"!=":return i!==a;case">":return parseFloat(i)>parseFloat(a);case"<":return parseFloat(i)<parseFloat(a);case">=":return parseFloat(i)>=parseFloat(a);case"<=":return parseFloat(i)<=parseFloat(a);case"contains":return i.includes(a);case"empty":return""===i;case"not_empty":return""!==i}}toggleFieldVisibility(e,t){const s=e.closest(".field, fieldset");s&&(s.hidden=!t,s.querySelectorAll("input, select, textarea").forEach(e=>{e.disabled=!t,!t&&e.hasAttribute("required")?(e.dataset.wasRequired="true",e.removeAttribute("required")):t&&"true"===e.dataset.wasRequired&&(e.setAttribute("required",""),delete e.dataset.wasRequired)}))}checkForCharacterLimits(e){e.querySelector(this.selectors.limits.hasLimit)&&(this.countUpdaters=this.updateCount.bind(this),e.querySelectorAll(this.selectors.limits.hasLimit).forEach(t=>{const s=this.getFieldInput(t);if(!s)return;let i=window.generateID("limit");s.dataset.charLimitId=i,s.dataset.limit=t.dataset.maxlength;let a={element:s,form:e.dataset.formId,ui:window.uiFromSelectors(this.selectors.limits,t)};a.ui.limit&&(a.ui.limit.textContent=t.dataset.maxlength),this.charLimits.set(i,a),this.addCharacterLimitListeners(s)}))}addCharacterLimitListeners(e){e.addEventListener("input",this.countUpdaters,{passive:!0})}removeCharacterLimitListeners(e){e.removeEventListener("input",this.countUpdaters,{passive:!0})}updateCount(e){let t=e.target,s=this.charLimits.get(t.dataset.charLimitId);if(!s)return;let i=t.value.length,a=t.dataset.limit;s.ui.current&&(s.ui.current.textContent=i,s.ui.current.classList.toggle("exceeded",i>=a)),i>a&&(t.value=t.value.slice(0,a))}checkForImageUploads(e,t){this.hasUploads=!0,window.jvbUploads.scanFields(e,t.options.autoUpload,t.options.imageMeta);let s=e.querySelectorAll('[data-field-type="upload"]');s&&(t.ui.uploads={},s.forEach(e=>{t.ui.uploads[e.dataset.field]=e}))}checkForTabs(e,t){window.jvbTabs&&e.querySelector("nav.tabs")&&(t.tabs=window.jvbTabs.registerTab(e,{preCheck:(e,t,s)=>this.validateStep(e,t,s)}),t.ui.tabs=window.uiFromSelectors(this.selectors.tabs,e),t.ui.tabs.sections=Array.from(e.querySelectorAll(this.selectors.tabs.sections)),t.ui.tabs.inputs={},t.ui.tabs.sections.forEach(e=>{t.ui.tabs.inputs[e.dataset.tab]=Array.from(e.querySelectorAll(this.inputSelectors))}),t.ui.tabs.buttons=Array.from(e.querySelectorAll(this.selectors.tabs.buttons)),t.unsubscribeTabs=window.jvbTabs.subscribe((e,s)=>{if("tab-switched"===e&&t.ui.tabs.progress){const e=t.ui.tabs.sections.filter(e=>e.dataset.tab===s.current)[0]??!1;if(!e)return;const i=e.dataset.step-1,a=t.ui.tabs.sections.length;window.showProgress(t.ui.tabs.progress,i,a,`Step ${i+1} of ${a}`)}}),this.forms.set(t.id,t))}validateStep(e,t,s){let i=!1;if(s.ui.sections&&s.ui.sections.forEach((a,r)=>{if(a===e){let e=r-1;if(e>=0){let a=s.ui.sections[e];a&&a.dataset.tab===t&&(i=!0)}}}),i)return!0;const a=e.closest("[data-form-id]")?.dataset.formId;if(!a)return!0;if(!this.forms.get(a))return!0;return Array.from(this.inputs.values()).filter(t=>t&&t.form===a&&t.section===e.dataset.tab&&!t.element.closest("[hidden]")).every(e=>!0===this.validateField(e.element))}checkForSelectors(e){window.jvbSelector&&window.jvbSelector.scanExistingFields(e)}reindexList(e){let t=e.dataset.field||e.dataset.repeaterId||e.dataset.tagListId;if(!t){let s=e.closest("[data-field]");t=s.dataset.field||s.dataset.repeaterId||s.dataset.tagListId}Array.from(e.children).forEach((e,s)=>{e.dataset.index=`${s}`;let i=e.querySelector(".row-number");i&&(i.textContent=`#${s+1}`);let a=e.querySelector(".row-title"),r=a?a.dataset.label:null;e.querySelectorAll("input, select, textarea").forEach(i=>{if("file"===i.type)return;const n=Object.hasOwn(i.dataset,"field")?i.dataset.field:i.name.split(":").pop();n===r&&(a.textContent=window.escapeHtml(i.value)),window.prefixInput(i,`${t}:${s}:${n}`,e,!0,!0)})}),this.updateCollectionField(e)}updateCollectionField(e){const t=e.closest("[data-field]");if(!t)return;const s=t.dataset.fieldType;if(!["repeater","tag-list"].includes(s))return;const i=this.getForm(e);if(!i)return;const a=this.getFieldValue(t);this.updateItem(t.dataset.field,a,i)}maybeUpdateCollectionDisplay(e){let t=e.closest("[data-repeater-id],[data-tag-list-id]");if(t&&"repeater"===t.dataset.fieldType){let t=e.closest(".repeater-row");if(!t)return;let s=t.querySelector(".row-title");if(!s||!s.dataset.label)return;console.log(e.dataset.field.split(":").pop()),console.log(s.dataset.label),e.dataset.field.split(":").pop()===s.dataset.label&&(s.textContent=this.getFieldValue(e.querySelector(this.inputSelectors)))}}clearValidation(e){let t=this.getField(e);if(!t)return;let s=this.getItem(e);s&&(t.classList.remove("has-error","has-success"),s.ui.success&&(s.ui.success.hidden=!0),s.ui.error&&(s.ui.error.hidden=!0),s.ui.message&&(s.ui.message.hidden=!0,s.ui.message.textContent=""))}showError(e,t="Invalid field"){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-success"),s.classList.add("has-error"),s.scrollIntoView({behavior:"smooth",block:"center",inline:"nearest"}),s.querySelector(this.inputSelectors)?.focus(),i.ui.message&&(i.ui.message.hidden=!1,i.ui.message.textContent=t))}showSuccess(e,t=""){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-error"),s.classList.add("has-success"),i.ui.message&&(i.ui.message.hidden=""===t,i.ui.message.textContent=t))}handleFormSuccess(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error").forEach(e=>e.classList.remove("field-error")),e.classList.add("form-success"),t.message){const s=document.createElement("div");s.className="form-success-message success-message",s.textContent=t.message,e.insertBefore(s,e.firstChild);const i=window.getIcon?.("check-circle");i&&(i.classList.add("success-icon"),s.prepend(i))}if(t.title||t.description){const s=document.createElement("div");if(s.className="success-box",t.title){const e=document.createElement("h3");e.textContent=t.title,s.appendChild(e)}if(t.description){(Array.isArray(t.description)?t.description:[t.description]).forEach(e=>{const t=document.createElement("p");t.textContent=e,s.appendChild(t)})}e.insertBefore(s,e.firstChild)}if(e.dataset.formId){this.store.delete(e.dataset.formId).catch(e=>{console.warn("Failed to clear form cache:",e)});const t=this.forms.get(e.dataset.formId);t&&(t.isDirty=!1,t.lastSaved=Date.now(),t.data={})}window.jvbA11y&&window.jvbA11y.announce(t.message||"Form submitted successfully")}handleFormError(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error, .has-error").forEach(e=>{e.classList.remove("field-error","has-error")}),e.querySelectorAll(".field").forEach(e=>{this.clearValidation(e)}),t.field){const s=e.querySelector(`[data-field="${t.field}"]`);if(s){this.showError(s,t.message),s.scrollIntoView({behavior:"smooth",block:"center"});const e=s.querySelector("input, textarea, select");e&&e.focus()}}else{const s=document.createElement("div");s.className="form-error error-message",s.textContent=t.message;const i=window.getIcon?.("close-circle");i&&(i.classList.add("error-icon"),s.prepend(i)),e.insertBefore(s,e.firstChild),e.scrollIntoView({behavior:"smooth",block:"start"})}if(window.jvbA11y){const e=t.field?`Error in ${t.field}: ${t.message}`:`Form error: ${t.message}`;window.jvbA11y.announce(e)}e.dispatchEvent(new CustomEvent("jvb-form-error",{detail:t}))}showFormStatus(e,t,s=""){let i=this.forms.get(e);i&&i.options.showStatus&&i.ui?.status?.status&&i.status!==t&&(i.status=t,i.ui.status.status.hidden=!1,i.ui.status.status.classList.toggle("loading",["uploading","saving"].includes(t)),i.ui.status.message.textContent=""===s?this.getDefaultMessage(t):s,i.ui.status.icon.className="icon icon-"+this.getDefaultIcon(t),setTimeout(()=>i.ui.status.status.hidden=!0,"submitted"===t?3e3:1e4))}getDefaultMessage(e){return{saving:"Saving changes...",autosaved:"Changes saved locally. Submit form to send to server.",uploading:"Uploading your form to server",submitted:"Successfully sent to server",pending:"Unsaved changes",restored:"Welcome back! We've restored your previous entry.",error:"Failed to save changes. Refresh and try again?",offline:"Changes will be saved when online"}[e]??e}getDefaultIcon(e){return{autosaved:"check-circle",submitted:"check-circle",restored:"history",error:"close-circle",offline:"cloud-slash",pending:"exclamation-mark"}[e]??""}showSummary(e){let t=this.templates.create("formSummary",e);e.config.element.after(t),window.fade(e.config.element,!1)}getForm(e){let t=e.closest("[data-form-id]");if(!t)return!1;let s=t.dataset.formId;if(!s)return!1;let i=this.forms.get(s);return i||!1}getField(e){return e.closest("[data-field]")}getFieldType(e){let t=this.getField(e);if(t)return t.dataset.fieldType}getFieldValue(e){let t=this.getFieldType(e),s=this.getItem(e),i=s.field?.dataset.field??!1;if(!i)return!1;switch(t){case"repeater":return this.getRepeaterValue(e,s);case"tag-list":return this.getTagListValue(e,s);case"group":return null;case"location":return this.getLocationValue(e,s);case"selector":case"upload":case"gallery":case"image":return this.getHiddenInputValue(e,s,i);case"true-false":case"toggle-text":return e.checked;case"checkbox":return e.name.endsWith("[]")?this.getCheckboxGroupValue(e,s):e.checked?e.value:"";default:return e.value}}getCheckboxGroupValue(e,t){return t.checkboxGroup||(t.checkboxGroup=t.field?.querySelectorAll(`input[type="checkbox"][name="${e.name}"]`),this.saveItem(t)),Array.from(t.checkboxGroup).filter(e=>e.checked).map(e=>e.value)}getFieldCheckedValue(e){if("checkbox"===e.type){return"true-false"===this.getFieldType(e)?e.checked:e.checked?e.value:""}if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`),s=Array.from(t).find(e=>e.checked);return s?s.value:""}return this.getFieldValue(e)}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}getRepeaterValue(e,t){const s=e.querySelector(".repeater-items");if(!s)return[];let i=["image_data","image-title","image-caption","image-description","image-alt-text"],a=[];return Array.from(s.children).forEach(e=>{let t={};e.querySelectorAll('input[type="hidden"]').forEach(e=>{t[e.name]=e.value}),e.querySelectorAll("[data-field]").forEach(e=>{if(!i.includes(e.dataset.field)){const s=this.getFieldInput(e);s&&(t[e.dataset.field]=this.getFieldValue(s))}}),a.push(t)}),a}getFieldInput(e){const t=e.querySelector("textarea[data-editor]");return t||e.querySelector(this.inputSelectors)}getTagListValue(e,t){t.container||(t.container=t.field?.querySelector(".tag-items"),this.saveItem(t));let s=[];return Array.from(t.container.children).forEach(e=>{let t=e.querySelectorAll('input[type="hidden"]'),i={};t.forEach(e=>{i[e.dataset.field]=e.value}),s.push(i)}),s}getLocationValue(e,t){t.values||(t.values=Array.from(t.field?.querySelectorAll("[data-location-field]")),this.saveItem(t));let s={};return t.values.forEach(e=>{s[e.dataset.locationField]=e.value}),s}getHiddenInputValue(e,t,s){return"INPUT"===e.tagName&&"hidden"===e.type||(e=e.querySelector('input[type="hidden"][name="'+s+'"]'))?(void 0!==t.value&&t.value===e.value||(t.value=e.value,this.saveItem(t)),t.value):null}formatValueForSummary(e,t){const s=this.getFieldType(t.element);if(this.isEmptyValue(e))return"";switch(s){case"repeater":return this.formatRepeaterForSummary(e,t);case"tag-list":return this.formatTagListForSummary(e,t);case"location":return this.formatLocationForSummary(e);case"true-false":return e?"Yes":"No";case"checkbox":return Array.isArray(e)?this.formatCheckboxGroupForSummary(e,t):this.getDisplayLabel(t,e);case"selector":case"upload":case"image":case"gallery":return this.formatHiddenFieldForSummary(e,t,s);default:return"string"==typeof e?this.getDisplayLabel(t,e):"string"==typeof e&&e.includes("\n")?this.convertLineBreaks(e):e}}formatCheckboxGroupForSummary(e,t){return e.map(e=>this.getDisplayLabel(t,e)).join(", ")}convertLineBreaks(e){const t=document.createElement("span");return t.innerHTML=e.split("\n").join("<br>"),t}formatRepeaterForSummary(e,t){const s=document.createElement("div");return s.className="summary-repeater",e.forEach((e,i)=>{const a=document.createElement("div");a.className="summary-repeater-row";const r=document.createElement("strong");r.textContent=`Entry ${i+1}:`,a.appendChild(r);const n=document.createElement("ul");n.className="summary-repeater-fields";for(const[s,i]of Object.entries(e)){if(this.isEmptyValue(i))continue;const e=document.createElement("li"),a=t.field?.querySelector(`[data-field="${s}"]`),r=a?.closest(".field")?.querySelector("label")?.textContent.replace("*","").trim()||s;e.innerHTML=`<span class="field-label">${r}:</span> <span class="field-value">${i}</span>`,n.appendChild(e)}a.appendChild(n),s.appendChild(a)}),s}formatTagListForSummary(e,t){const s=document.createElement("div");s.className="summary-taglist";const i=document.createElement("ul");return i.className="summary-tags",e.forEach(e=>{const t=document.createElement("li");t.className="summary-tag";const s=Object.values(e).find(e=>!this.isEmptyValue(e))||"",a=Object.entries(e).filter(([e,t])=>!this.isEmptyValue(t));a.length>1?t.textContent=a.map(([e,t])=>t).join(", "):t.textContent=s,i.appendChild(t)}),s.appendChild(i),s}formatLocationForSummary(e){const t=[];return e.street&&t.push(e.street),e.city&&t.push(e.city),e.province&&t.push(e.province),e.postal_code&&t.push(e.postal_code),e.country&&t.push(e.country),t.length>0?t.join(", "):e.address||""}formatHiddenFieldForSummary(e,t,s){if(["upload","gallery","image"].includes(s)){const s=t.field?.querySelector("[data-upload-field]");if(s){const e=s.querySelectorAll(".item-grid.preview img");if(e.length>0){const t=document.createElement("div");return t.className="summary-uploads",e.forEach(e=>{const s=e.cloneNode(!0);s.style.maxWidth="100px",s.style.maxHeight="100px",t.appendChild(s)}),t}}return`${e.split(",").length} file(s) uploaded`}return e}getDisplayLabel(e,t){if(!e.element)return t;const s=e.element.type;if("radio"===s){const s=e.field.querySelectorAll(`input[type="radio"][name="${e.element.name}"]`),i=Array.from(s).find(e=>e.value===t);if(i){const t=i.closest("label")||e.field.querySelector(`label[for="${i.id}"]`);if(t)return t.textContent.replace("*","").trim()}}if("checkbox"===s&&"true-false"!==this.getFieldType(e.element)){const s=e.field.querySelector(`input[type="checkbox"][value="${t}"]`);if(s){const t=s.closest("label")||e.field.querySelector(`label[for="${s.id}"]`);if(t){const e=t.querySelector("span");return e?e.textContent.trim():t.textContent.replace("*","").trim()}}}return t}getItem(e,t=null){const s=Object.hasOwn(e.dataset,"ref");let i=s?e.dataset.ref:window.generateID("input");if(s||(e.dataset.ref=i),!this.inputs.has(i)){t||(t=e.closest("[data-form-id]")?.dataset.formId??!1);let s=this.getField(e);this.inputs.set(i,{id:i,element:e,form:t,field:s,section:e.closest("[data-tab]")?.dataset.tab??!1,ui:window.uiFromSelectors(this.selectors.fields,s)})}return this.inputs.get(i)}saveItem(e){this.inputs.set(e.id,e)}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("HandleSelection subscriber error:",e)}})}destroy(){this.forms.size>0&&(Array.from(this.forms.values()).forEach(e=>{this.removeFormListeners(e)}),this.forms.clear()),this.repeaters.size>0&&(Array.from(this.repeaters.values()).forEach(e=>{this.removeRepeaterListeners(e.element),e.sortable?.destroy()}),this.repeaters.clear()),this.quantityFields.size>0&&(Array.from(this.quantityFields.values()).forEach(e=>{this.removeQuantityListeners(e.element)}),this.quantityFields.clear()),this.tagLists.size>0&&(Array.from(this.tagLists.values()).forEach(e=>{this.removeTagListListeners(e.element)}),this.tagLists.clear()),this.charLimits.size>0&&Array.from(this.charLimits.values()).forEach(e=>{e.element.removeEventListener("input",this.countUpdaters)}),this.inputs.clear(),this.forms.clear(),this.charLimits.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbForm=new e,document.querySelectorAll("form[data-auto]").forEach(e=>{window.jvbForm.registerForm(e,{})}))})})})();
\ No newline at end of file
diff --git a/assets/js/min/notifications.min.js b/assets/js/min/notifications.min.js
index 2876308..8d39b95 100644
--- a/assets/js/min/notifications.min.js
+++ b/assets/js/min/notifications.min.js
@@ -1 +1 @@
-(()=>{class t{constructor(t={}){this.popupQueue=[],this.isLoading=!1,this.cache=window.jvbCache,this.isProcessingQueue=!1,this.options={maxVisibleNotifications:5,displayDuration:{high:7e3,medium:5e3,low:3e3},position:"bottom-right",pollingInterval:6e4,...t},this.button=document.querySelector(".toggle.notifications"),this.submenu=document.querySelector(".notifications-preview"),this.toasts=document.querySelector(".toasts"),this.notificationsLoaded=!1,this.pollTimer=null,this.lastCheck=null,this.button&&this.submenu&&this.init(),this.clickListeners=this.checkClicks.bind(this),this.updateListeners()}init(){this.submenu.addEventListener("click",t=>{const e=t.target.closest(".mark-read");if(e){const t=e.closest(".notification-preview");t&&this.markAsRead(t.dataset.id)}}),this.loadNotifications(),this.initializePolling()}checkClicks(t){if(t.target.closest(".close-toast")){let e=t.target.closest(".toast");e.classList.add("hiding"),setTimeout(()=>{e.remove(),this.updateListeners()},300)}}updateListeners(){this.toasts.addEventListener("click",this.clickListeners)}toggleDropdown(){this.notificationsLoaded||this.loadNotifications()}async loadNotifications(t=!1){if(!this.isLoading)try{this.isLoading=!0;const t=new URLSearchParams({user:window.auth.getUser(),status:"unread",limit:5}),e=await this.cache.fetchWithCache(`${jvbSettings.api}notifications?${t.toString()}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("notifications")}},{context:"notifications",forceRefresh:!0});this.renderPreviewNotifications(e.notifications),this.updateUnreadCount(e.total),this.notificationsLoaded=!0,this.lastCheck=(new Date).toUTCString()}catch(t){console.error("Error loading notifications:",t),this.renderErrorState(t.message)}}renderErrorState(t){const e=this.submenu.querySelector("#view-all");this.submenu.querySelectorAll("li:not(#view-all)").forEach(t=>t.remove());const i=document.createElement("li");i.className="error-state",i.innerHTML=`\n <p>${t}</p>\n <button onclick="window.jvbNotifications.loadNotifications()">\n Try Again\n </button>\n `,this.submenu.insertBefore(i,e)}renderPreviewNotifications(t){this.submenu.querySelector("#view-all");this.submenu.querySelectorAll("li:not(#view-all)").forEach(t=>t.remove()),t.forEach(t=>{let e=window.getTemplate("notificationItem");e.classList.add(t.status,`priority-${t.priority}`),e.dataset.id=t.id,e.prepend(getIcon(t.icon));let i=e.querySelector("p"),o=e.querySelector("time");[i.textContent,o.datetime,o.textContent]=[t.message,new Date(t.created_at).toISOString(),formatTimeAgo(t.created_at)];let s=window.getTemplate("notificationActions"),n=s.querySelector("button");t.actions.length>0&&(t.actions.forEach(e=>{let i=n.cloneNode(!0);e.primary&&i.classList.add("primary"),[i.dataset.id,i.dataset.action,i.textContent]=[t.id,e.label.toLowerCase(),e.label],s.append(i)}),n.remove()),e.append(s),this.submenu.prepend(e)}),0===t.length&&this.submenu.prepend(window.getTemplate("emptyNotification"))}queuePopupNotification(t){this.popupQueue.push(t),this.processPopupQueue()}async processPopupQueue(){if(!this.isProcessingQueue&&0!==this.popupQueue.length){for(this.isProcessingQueue=!0;this.popupQueue.length>0;){const t=this.popupQueue.shift();await this.showToast(t.message,t.type,t.actions),this.popupQueue.length>0&&await new Promise(t=>setTimeout(t,300))}this.isProcessingQueue=!1}}showToast(t,e="success",i={}){let o=window.getTemplate("notificationPopup");if(o.classList.add(e),o.querySelector("p").textContent=t,Object.entries(i).length>0){let t=window.getTemplate("notificationActions"),e=i.querySelector("button");notification.actions.forEach(i=>{let o=e.cloneNode(!0);i.primary&&o.classList.add("primary"),[o.dataset.action,o.textContent]=[i.label.toLowerCase(),i.label],t.prepend(o)})}this.toasts.append(o),setTimeout(()=>{o.classList.add("show")},10),setTimeout(()=>{o.classList.add("hiding"),setTimeout(()=>{o.remove()},300)},3e3)}createNotificationElement(t){this.showToast(t.message),this.renderPreviewNotifications([t])}removePopupNotification(t){t.classList.remove("show"),setTimeout(()=>{t.remove()},300)}updateUnreadCount(t){let e=this.button.querySelector("span");this.button.classList.remove("has"),[e.textContent,e.ariaLabel]=["","Notifications"],t&&!isNaN(t)&&(t=parseInt(t,10))>0&&(this.button.classList.add("has"),[e.textContent,e.ariaLabel]=[t,t+" unread notification"+(t>1?"s":"")])}async markAsRead(t){try{const e=this.submenu.querySelector(`[data-id="${t}"]`);if(!e)return;e.classList.add("slide-out");const i=await fetch(`${jvbSettings.api}notifications`,{method:"POST",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")},body:{notification:t,user:window.auth.getUser()}});if(!i.ok)throw new Error(notificationSettings.strings.error);const o=await i.json();o.success&&(setTimeout(()=>{e.remove();if(0===this.submenu.querySelectorAll(".notification-preview").length){const t=this.submenu.querySelector("#view-all"),e=document.createElement("li");e.className="empty-state fade-in",e.textContent=notificationSettings.strings.noNotifications,this.submenu.insertBefore(e,t),requestAnimationFrame(()=>{e.classList.remove("fade-in")})}},300),this.updateUnreadCount(o.total))}catch(t){console.error("Error marking notification as read:",t)}}initializePolling(){this.pollTimer=setInterval(()=>{this.checkNotifications()},this.options.pollingInterval),document.addEventListener("visibilitychange",()=>{document.hidden||this.checkNotifications()})}async checkNotifications(){try{const t=new URLSearchParams({user:window.auth.getUser(),status:"unread"}),e=await fetch(`${jvbSettings.api}notifications?${t.toString()}`,{headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash"),"If-Modified-Since":this.lastCheck}});if(!e.ok)return;(await e.json()).has_new&&await this.loadNotifications(!0)}catch(t){console.error("Check notifications error:",t)}}destroy(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(e=>{"auth-loaded"===e&&(window.jvbNotifications=new t({position:"bottom-right",maxVisibleNotifications:5,displayDuration:5e3}))})}),window.addNotification=function(t,e="info"){window.jvbNotifications.showToast(t,e)}})();
\ No newline at end of file
+(()=>{class t{constructor(t={}){this.popupQueue=[],this.isLoading=!1,this.cache=window.jvbCache,this.isProcessingQueue=!1,this.options={maxVisibleNotifications:5,displayDuration:{high:7e3,medium:5e3,low:3e3},position:"bottom-right",pollingInterval:6e4,...t},this.button=document.querySelector(".toggle.notifications"),this.submenu=document.querySelector(".notifications-preview"),this.toasts=document.querySelector(".toasts"),this.notificationsLoaded=!1,this.pollTimer=null,this.lastCheck=null,this.button&&this.submenu&&this.init(),this.clickListeners=this.checkClicks.bind(this),this.updateListeners()}init(){this.submenu.addEventListener("click",t=>{const e=t.target.closest(".mark-read");if(e){const t=e.closest(".notification-preview");t&&this.markAsRead(t.dataset.id)}}),this.loadNotifications(),this.initializePolling()}checkClicks(t){if(t.target.closest(".close-toast")){let e=t.target.closest(".toast");e.classList.add("hiding"),setTimeout(()=>{e.remove(),this.updateListeners()},300)}}updateListeners(){this.toasts.addEventListener("click",this.clickListeners)}toggleDropdown(){this.notificationsLoaded||this.loadNotifications()}async loadNotifications(t=!1){if(!this.isLoading)try{this.isLoading=!0;const t=new URLSearchParams({user:window.auth.getUser(),status:"unread",limit:5}),e=await this.cache.fetchWithCache(`${jvbSettings.api}notifications?${t.toString()}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("notifications")}},{context:"notifications",forceRefresh:!0});this.renderPreviewNotifications(e.notifications),this.updateUnreadCount(e.total),this.notificationsLoaded=!0,this.lastCheck=(new Date).toUTCString()}catch(t){console.error("Error loading notifications:",t),this.renderErrorState(t.message)}}renderErrorState(t){const e=this.submenu.querySelector("#view-all");this.submenu.querySelectorAll("li:not(#view-all)").forEach(t=>t.remove());const i=document.createElement("li");i.className="error-state",i.innerHTML=`\n <p>${t}</p>\n <button onclick="window.jvbNotifications.loadNotifications()">\n Try Again\n </button>\n `,this.submenu.insertBefore(i,e)}renderPreviewNotifications(t){this.submenu.querySelector("#view-all");this.submenu.querySelectorAll("li:not(#view-all)").forEach(t=>t.remove()),t.forEach(t=>{let e=window.getTemplate("notificationItem");e.classList.add(t.status,`priority-${t.priority}`),e.dataset.id=t.id,e.prepend(getIcon(t.icon));let i=e.querySelector("p"),o=e.querySelector("time");[i.textContent,o.datetime,o.textContent]=[t.message,new Date(t.created_at).toISOString(),formatTimeAgo(t.created_at)];let s=window.getTemplate("notificationActions"),n=s.querySelector("button");t.actions.length>0&&(t.actions.forEach(e=>{let i=n.cloneNode(!0);e.primary&&i.classList.add("primary"),[i.dataset.id,i.dataset.action,i.textContent]=[t.id,e.label.toLowerCase(),e.label],s.append(i)}),n.remove()),e.append(s),this.submenu.prepend(e)}),0===t.length&&this.submenu.prepend(window.getTemplate("emptyNotification"))}queuePopupNotification(t){this.popupQueue.push(t),this.processPopupQueue()}async processPopupQueue(){if(!this.isProcessingQueue&&0!==this.popupQueue.length){for(this.isProcessingQueue=!0;this.popupQueue.length>0;){const t=this.popupQueue.shift();this.showToast(t.message,t.type,t.actions),this.popupQueue.length>0&&await new Promise(t=>setTimeout(t,300))}this.isProcessingQueue=!1}}showToast(t,e="success",i={}){let o=window.getTemplate("notificationPopup");if(o.classList.add(e),o.querySelector("p").textContent=t,Object.entries(i).length>0){let t=window.getTemplate("notificationActions"),e=i.querySelector("button");notification.actions.forEach(i=>{let o=e.cloneNode(!0);i.primary&&o.classList.add("primary"),[o.dataset.action,o.textContent]=[i.label.toLowerCase(),i.label],t.prepend(o)})}this.toasts.append(o),setTimeout(()=>{o.classList.add("show")},10),setTimeout(()=>{o.classList.add("hiding"),setTimeout(()=>{o.remove()},300)},3e3)}createNotificationElement(t){this.showToast(t.message),this.renderPreviewNotifications([t])}removePopupNotification(t){t.classList.remove("show"),setTimeout(()=>{t.remove()},300)}updateUnreadCount(t){let e=this.button.querySelector("span");this.button.classList.remove("has"),[e.textContent,e.ariaLabel]=["","Notifications"],t&&!isNaN(t)&&(t=parseInt(t,10))>0&&(this.button.classList.add("has"),[e.textContent,e.ariaLabel]=[t,t+" unread notification"+(t>1?"s":"")])}async markAsRead(t){try{const e=this.submenu.querySelector(`[data-id="${t}"]`);if(!e)return;e.classList.add("slide-out");const i=await fetch(`${jvbSettings.api}notifications`,{method:"POST",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")},body:{notification:t,user:window.auth.getUser()}});if(!i.ok)throw new Error(notificationSettings.strings.error);const o=await i.json();o.success&&(setTimeout(()=>{e.remove();if(0===this.submenu.querySelectorAll(".notification-preview").length){const t=this.submenu.querySelector("#view-all"),e=document.createElement("li");e.className="empty-state fade-in",e.textContent=notificationSettings.strings.noNotifications,this.submenu.insertBefore(e,t),requestAnimationFrame(()=>{e.classList.remove("fade-in")})}},300),this.updateUnreadCount(o.total))}catch(t){console.error("Error marking notification as read:",t)}}initializePolling(){this.pollTimer=setInterval(()=>{this.checkNotifications()},this.options.pollingInterval),document.addEventListener("visibilitychange",()=>{document.hidden||this.checkNotifications()})}async checkNotifications(){try{const t=new URLSearchParams({user:window.auth.getUser(),status:"unread"}),e=await fetch(`${jvbSettings.api}notifications?${t.toString()}`,{headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash"),"If-Modified-Since":this.lastCheck}});if(!e.ok)return;(await e.json()).has_new&&await this.loadNotifications(!0)}catch(t){console.error("Check notifications error:",t)}}destroy(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(e=>{"auth-loaded"===e&&(window.jvbNotifications=new t({position:"bottom-right",maxVisibleNotifications:5,displayDuration:5e3}))})}),window.addNotification=function(t,e="info"){window.jvbNotifications.showToast(t,e)}})();
\ No newline at end of file
diff --git a/assets/js/min/uploader.min.js b/assets/js/min/uploader.min.js
index a77d6d4..2f0ac9c 100644
--- a/assets/js/min/uploader.min.js
+++ b/assets/js/min/uploader.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.queue=window.jvbQueue,this.error=window.jvbError,this.templates=window.jvbTemplates,this.subscribers=new Set,this.initStores(),this.initWorker(),this.fields=new Map,this.uploads=new Map,this.groups=new Map,this.selected=new Map,this.selectionHandlers=new Map,this.sortables=new Map,this.changes=new Map,this.previewUrls=new Set,this.initElements(),this.initListeners(),this.defineTemplates()}defineTemplates(){const e=this.templates,t=this;e.define("uploadItem",{refs:{select:'[name="select-item"]',featured:'[name="featured"]',img:"img",video:"video",file:"label > span",details:"details",alt:'[name="image-alt-text"]',title:'[name="image-title"]',description:'[name="image-caption"]'},manyRefs:{inputs:"input, select, textarea"},setup({el:e,refs:s,manyRefs:i,data:a}){let r,o,l,d=!1;switch(Object.hasOwn(a,"file")?(e.dataset.uploadId=a.uploadId,r=t.getSubtypeFromMime(a.file.type)||"image",o="document"!==r&&t.createPreviewUrl(a.file),d=o,l=a.file.name||""):(e.dataset.id=a.id,r=t.getSubtypeFromURL(a.medium??a.src),o=a.medium??a.src,l=a["image-alt-text"]??""),e.dataset.subtype=r,s.featured&&(s.featured.value=a.uploadId),r){case"image":s.img&&(s.img.src=o,s.img.alt=l,d&&(s.img.dataset.previewUrl=d)),s.video&&s.video.remove(),s.file&&s.file.remove();break;case"video":s.video&&(s.video.src=o,s.video.alt=l,d&&(s.video.dataset.previewUrl=d)),s.img&&s.img.remove(),s.file&&s.file.remove();break;case"document":if(s.preview){let e=a.file.name.split(".").pop()?.toLowerCase()??"",t={pdf:"file-pdf",csv:"file-csv",doc:"file-doc",docx:"file-doc",txt:"file-txt",xls:"file-xls",xlsx:"file-xls"},i=window.getIcon(t[e]??"file");s.preview.innerText=a.file.name??a.title,s.preview.prepend(i)}s.img&&s.img.remove(),s.video&&s.video.remove()}if(s.details&&(Object.hasOwn(a,"field")&&Object.hasOwn(a.field,"config")&&Object.hasOwn(a.field.config,"showMeta")&&!a.field.config.showMeta?s.details.remove():(Object.hasOwn(a,"id")?s.details.dataset.attachmentId=a.id:Object.hasOwn(a,"uploadId")&&(s.details.dataset.uploadId=a.uploadId),s.details.setAttribute("data-ignore",""),"image"!==r&&s.alt?s.alt.closest(".field")?.remove():Object.hasOwn(a,"image-alt-text")&&s.alt&&(s.alt.value=a["image-alt-text"]),(Object.hasOwn(a,"title")||Object.hasOwn(a,"file"))&&s.title&&(s.title.value=a.title||a.file.name),Object.hasOwn(a,"image-caption")&&s.description&&(s.description.value=a["image-caption"]))),e.draggable="single"!==e.dataset.mode,i.inputs)for(let t of i.inputs){let s=t.closest("[data-field]")??t.closest(".radio-button")??e;window.prefixInput(t,`${a.id??a.uploadId}-`,s)}}}),e.define("imageGroup",{refs:{selectAll:"[data-select-all]",fields:".fields",details:"details",grid:".item-grid"},setup({el:t,refs:s,manyRefs:i,data:a}){if(t.dataset.groupId=a.groupId,s.selectAll){let e=s.selectAll.closest(".field");window.prefixInput(s.selectAll,`select-all-${a.groupId}`,e,!0)}let r=e.create("groupMetadata",{groupId:a.groupId});r?s.fields.append(r):s.details.remove(),s.grid&&(s.grid.dataset.groupId=a.groupId)}}),e.define("groupMetadata",{manyRefs:{inputs:"input,textarea,select"},setup({el:e,refs:t,manyRefs:s,data:i}){s.inputs&&s.inputs.forEach(e=>{let t=e.closest("[data-field]");e.dataset.groupId=i.groupId,window.prefixInput(e,`${i.groupId}-`,t)})}}),e.define("restoreNotification",{refs:{details:".details",wrap:".wrap"},setup({el:t,refs:s,manyRefs:i,data:a}){if(s.details){let e=a.bySource.size>1?` across ${a.bySource.size} pages`:"",t=a.pendingUploads.length>1?"uploads":"upload";s.details.textContent=`${a.pendingUploads.length} ${t} can be recovered${e}`}if(!s.wrap)return void console.warn("No wrap element in template");let r=1;for(const[t,i]of a.bySource){let a={index:r,isCurrent:t===window.location.href,src:t,uploads:i};s.wrap.append(e.create("restoreField",a)),r++}}}),e.define("restoreField",{refs:{h3:"h3",a:"h3 a",grid:".item-grid"},async setup({el:e,refs:s,manyRefs:i,data:a}){let r=t.registerField(e,!1,!1,`recovery_${a.index}`);a.isCurrent?(e.open=!0,s.a?.remove(),s.h3&&(s.h3.textContent="From this page:")):s.a&&(s.a.href=a.src,s.a.title="Navigate to page and restore",s.a.textContent=a.src);let o=[...new Set(a.uploads.map(e=>e.group??"preview"))];for(let e of o){let i="preview"===e||t.stores.groups.get(e);if(!i)continue;let o=await t.createGroupElement(e,r),l=o.querySelector(".item-grid"),d=a.uploads.filter(t=>t.group===("preview"===e)?null:e);for(const[e,t]of Object.entries(i.fields??{})){let s=o.querySelector(`input[name*="${e}"]`);s&&(s.value=t)}for(let e of d){let s=await t.createUpload(e.id,t.formatFile(e),r);l.append(s)}s.grid.append(o)}}})}initStores(){const{uploads:e,groups:t}=window.jvbStore.register("uploads",[{storeName:"uploads",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"status",keyPath:"status"},{name:"group",keyPath:"group"},{name:"src",keyPath:"src"}]},{storeName:"groups",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"src",keyPath:"src"}]}]);this.stores={uploads:e,groups:t,ready:[]},this.stores.uploads.subscribe(this.handleStores.bind(this,"uploads")),this.stores.groups.subscribe(this.handleStores.bind(this,"groups")),this.queue.subscribe((e,t)=>{if(("operation-status"===e||"cancel-operation"===e)&&["image_upload","video_upload","document_upload"].includes(t.type)){let s=[];if(t.data)if(t.data instanceof FormData){const e=this.stores.uploads.formDataToObject(t.data);s=e.upload_ids||[]}else s=t.data.upload_ids||[];if(0===s.length&&t.result&&t.result.upload_ids&&(s=t.result.upload_ids),!s||0===s.length)return void console.warn("[UploadManager] No upload_ids found for operation:",{id:t.id,type:t.type,status:t.status,hasData:!!t.data,hasResult:!!t.result});if("cancel-operation"===e)return this.handleOperationCancelled(s);this.setBulkUpload(s,"status",t.status).then(()=>{console.log(`[UploadManager] Updated ${s.length} uploads to status: ${t.status}`)}),"completed"===t.status&&("process_upload_groups"===t.type?(s.forEach(e=>{this.setBulkUpload([e],"serverProcessed",!0).then(()=>{})}),t.result&&t.result.created_posts&&console.log("[UploadManager] Created posts:",t.result.created_posts),setTimeout(()=>{s.forEach(e=>{this.removeUpload(e).then(()=>{})})},2e3)):s.forEach(e=>{this.removeUpload(e).then(()=>{})})),"failed"!==t.status&&"failed_permanent"!==t.status||console.error("[UploadManager] Operation failed:",{id:t.id,type:t.type,uploadIds:s,error:t.error_message})}})}storesReady(){return 2===this.stores.ready.length}handleStores(e,t){"data-ready"===t&&(this.stores.ready.push(e),this.storesReady()&&this.checkRecovery().then(()=>{}))}initWorker(){this.worker=null,this.workerState={worker:null,tasks:new Map,restart:{count:0,max:3},settings:{timeout:3e3,maxConcurrent:3,restartAfterTimeout:!0}}}initElements(){this.selectors={fields:{field:"[data-upload-field]",input:'input[type="file"]',dropZone:".file-upload-wrapper",preview:".preview-wrap",grid:".item-grid.preview",progress:{progress:".file-upload-container .progress",fill:".file-upload-container .progress .fill",details:".file-upload-container .progress .details",icon:".file-upload-container .progress .icon"},selectAll:"[data-select-all]",actions:".selection-actions",count:".selected .info",hidden:'input[type="hidden"]'},groups:{container:".group-display",grid:".item-grid.groups",empty:".empty-group",header:".sidebar .header"},group:{item:".upload-group",actions:".selection-actions",selectAll:'[name="select-all-group"]',count:".group-header .info",fields:"details .fields",grid:".item-grid.group",total:".group-content .group-count"},items:{item:".item.upload",checkbox:'[name="select-item"]',featured:'[name="featured"]',image:"img",details:"details",progress:{progress:".progress",fill:".fill",details:".details",icon:".icon"}}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.dragEnterHandler=this.handleDragEnter.bind(this),this.dragLeaveHandler=this.handleDragLeave.bind(this),this.dragOverHandler=this.handleDragOver.bind(this),this.dropHandler=this.handleDrop.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("dragenter",this.dragEnterHandler),document.addEventListener("dragleave",this.dragLeaveHandler),document.addEventListener("dragover",this.dragOverHandler),document.addEventListener("drop",this.dropHandler),window.addEventListener("beforeunload",()=>{this.cleanupAllPreviewUrls()})}async setUpload(e,t){const s={...{id:e,attachment:null,group:null,field:null,src:window.location.href,blob:null,status:"local_processing",operationId:null,fields:{}},...t};if(Object.preventExtensions(s),await this.stores.uploads.save(s),this.fields.has(s.field)){let e=this.fields.get(s.field);if("local_processing"===s.status)this.notify("upload-received",{field:e.element,id:s.id})}return s}createPreviewUrl(e){const t=URL.createObjectURL(e);return this.previewUrls.add(t),t}revokePreviewUrl(e){e?.startsWith("blob:")&&(URL.revokeObjectURL(e),this.previewUrls.delete(e))}formatFile(e){return e.blob?new File([e.blob],e.fields.originalName||"file",{type:e.fields.type||e.blob.type,lastModified:e.fields.lastModified||Date.now()}):null}handleClick(e){if(!window.targetCheck(e,this.selectors.fields.field))return;let t=window.targetCheck(e,this.selectors.fields.dropZone);t&&!e.target.matches("input, button, a")&&t.querySelector(this.selectors.fields.input)?.click();const s=window.targetCheck(e,"[data-action]");s&&this.handleAction(s)}handleAction(e){const t=e.dataset.action,s=this.getFieldIdFromElement(e);switch(t){case"add-to-group":this.handleAddToGroup(s).then(()=>{});break;case"delete-group":this.handleDeleteGroup(e);break;case"delete-upload":case"remove-from-group":this.handleRemoveItem(e).then(()=>{});break;case"upload":this.queueUploads("uploads/groups",s).then(()=>{});break;case"restore":this.handleRestoreSelected().then(()=>{});break;case"restore-all":this.handleRestoreAll().then(()=>{});break;case"clear-cache":this.handleClearCache().then(()=>{})}}handleChange(e){let t=this.getFieldIdFromElement(e.target);if(!t){return void(e.target.closest("[data-upload-id], [data-attachment-id]")&&this.queueUploadMeta(e))}if(e.target.matches(this.selectors.fields.input)){const s=Array.from(e.target.files);return void(s.length>0&&this.processFiles(t,s).then(()=>{}))}e.target.matches(this.selectors.items.checkbox)||e.target.matches(this.selectors.items.featured)||e.target.matches('[name*="select-"]')||("post_group"===this.fields.get(t).config.destination?this.handleGroupMetaChange(e.target):this.queueUploadMeta(e))}handleGroupMetaChange(e){const t=e.dataset.groupId;if(!t)return;const s=e.name;if(!s)return;const i=e.value,a=s.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");window.debouncer.schedule(`group-meta-${t}-${a}`,async()=>{const e=this.stores.groups.get(t);e&&(e.fields||(e.fields={}),e.fields[a]=i,await this.setGroup(t,e))},300)}handleDragEnter(e){if(!e.dataTransfer.types.includes("Files"))return;const t=e.target.closest(this.selectors.fields.dropZone);t&&(e.preventDefault(),t.classList.add("dragover"))}handleDragLeave(e){const t=e.target.closest(this.selectors.fields.dropZone);t&&!t.contains(e.relatedTarget)&&t.classList.remove("dragover")}handleDragOver(e){if(!e.dataTransfer.types.includes("Files"))return;e.target.closest(this.selectors.fields.dropZone)&&(e.preventDefault(),e.dataTransfer.dropEffect="copy")}handleDrop(e){const t=e.target.closest(this.selectors.fields.dropZone);if(!t)return;e.preventDefault(),t.classList.remove("dragover"),t.classList.add("uploading");const s=Array.from(e.dataTransfer.files);if(0===s.length)return;const i=this.getFieldIdFromElement(t);i&&(this.processFiles(i,s).then(()=>{this.updateHandlerItems(i)}),this.a11y.announce(`${s.length} file(s) dropped for upload`))}async queueUploads(e,t,s=null){let i=new FormData;const a=this.fields.get(t);if(!a)return;let r=this.stores.uploads.filterByIndex({field:t});if(0===r.length)return;const[o,l]=["uploads"===e,"uploads/groups"===e];let d,n,u,p,c;i.append("fieldId",a.id),i.append("content",a.config.content),o&&(i.append("mode",a.config.mode),i.append("field_name",a.config.repeaterPath||a.config.name),i.append("fieldId",a.id),i.append("field_type",a.config.type),i.append("subtype",a.config.subtype),i.append("item_id",a.config.itemID),i.append("destination",a.config.destination),s&&i.append("depends_on",s)),l?({posts:d,uploadMap:n,files:u}=this.collectGroups(t)):o&&({uploadMap:n,files:u}=this.collectUploads(t)),l&&i.append("posts",JSON.stringify(d)),u.forEach(e=>{i.append("files[]",e)}),i.append("upload_ids",JSON.stringify(n)),o?(p=`Uploading ${r.length} file${r.length>1?"s":""} to server...`,c=`Uploading ${r.length} file${r.length>1?"s":""}...`):l&&(p=`Creating ${d.length} ${a.config.content}${d.length>1?"s":""} from uploads...`,c=`Creating ${d.length} post${d.length>1?"s":""}...`),await this.setBulkUpload(r,"status","queued");let h=this.sendToQueue(e,i,p,c);if("uploads/groups"===e){let e=a.element.closest("details");e&&(e.open=!1),this.notify("groups_uploaded",{fieldId:t,posts:d,content:a.config.content})}return h?(a.operationId=h,await this.setBulkUpload(r,"operationId",h),await this.setBulkUpload(r,"status","uploading"),await this.setBulkGroup(t,"operationId",h),this.fields.set(a.id,a),this.notify("sent-to-queue",{field:a,operation:h})):await this.setBulkUpload(r,"status","failed"),h}async sendToQueue(e,t,s="",i="",a=!1){""===i&&(i=s);const r={endpoint:e,method:"POST",data:t,title:s,popup:i,canMerge:a,sendNow:"uploads/groups"===e,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},append:"_upload"};try{return await this.queue.addToQueue(r)}catch(e){return this.error.log(e,{component:"UploadManager",action:"sentToQueue"}),!1}}collectGroups(e){let t=this.stores.uploads.filterByIndex({field:e}),s=[],i=[],a=[];const r=this.stores.groups.filterByIndex({field:e}).filter(e=>{const t=this.getGroupUploadsInOrder(e);return t.length>0&&t.some(e=>this.formatFile(e))});for(const e of r){const t=this.groups.get(e.id)?.element,r=this.collectGroupFieldsFromDOM(t,e.id),o={groupId:e.id,images:[],fields:r},l=this.getGroupUploadsInOrder(e);for(const t of l){const s=this.formatFile(t);if(s){a.push(s);const r={upload_id:t.id,index:i.length},l=this.uploads.get(t.id),d=l?.element?.querySelector(`input[name="${e.id}_featured"]`);d?.checked&&(o.fields.featured=t.id),o.images.push(r),i.push(t.id)}}o.images.length>0&&s.push(o)}const o=t.filter(e=>!e.group);for(const e of o){const t={groupId:window.generateID("group"),images:[],fields:{}},r=this.formatFile(e);if(r){a.push(r);const s={upload_id:e.id,index:i.length};t.images.push(s),i.push(e.id)}t.images.length>0&&s.push(t)}return{posts:s,uploadMap:i,files:a}}getGroupUploadsInOrder(e){return e.uploads&&0!==e.uploads.length?e.uploads.map(e=>this.stores.uploads.get(e)).filter(Boolean):[]}collectGroupFieldsFromDOM(e,t){if(!e)return{};const s={};return e.querySelectorAll("input, textarea, select").forEach(e=>{const i=e.name.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");["featured","select-all"].some(e=>i.includes(e))||e.value&&(s[i]=e.value)}),s}collectUploads(e){let t=this.stores.uploads.filterByIndex({field:e});if(0===t.length)return;let s=[],i=[];for(const e of t){const t=this.formatFile(e);t&&(i.push(t),s.push(e.id))}return{uploadMap:s,files:i}}queueUploadMeta(e){let t=e.target.closest("[data-attachment-id]")?.dataset.attachmentId,s=!1;if(!t&&(t=e.target.closest("[data-upload-id]")?.dataset.uploadId,s=!0,!t))return;if(!this.changes.has(t)){let e={};s?e.uploadId=t:e.attachmentId=t,this.changes.set(t,e)}let i=e.target.closest("[data-field]").dataset.field;this.changes.get(t)[i]=e.target.value,this.scheduleSave()}scheduleSave(){window.debouncer.schedule("upload-meta",async()=>{if(this.changes.size>0){let e={};for(let[t,s]of this.changes.entries())console.log(t,s),e[t]=s;let t={user:window.auth.getUser(),items:e};await this.sendToQueue("uploads/meta",t,"Uploading Meta","Uploading Meta",!0),this.changes.clear()}},2e3)}scanFields(e,t=!0,s=!0){e.querySelectorAll(this.selectors.fields.field).forEach(e=>this.registerField(e,t,s))}registerField(e,t=!0,s=!0,i=null){const a={element:e,id:i||this.determineFieldId(e),config:this.extractFieldConfig(e,t,s),uploads:new Set,operationId:null,groups:[],ui:window.uiFromSelectors(this.selectors.fields,e),groupUI:window.uiFromSelectors(this.selectors.groups,e)};return this.fields.set(a.id,a),e.dataset.uploader=a.id,this.getSelectionHandler(a.id),"single"!==a.config.type&&this.initSortable(a.id),this.maybeLockUploads(a.id),a.id}extractFieldConfig(e,t,s){const i={autoUpload:t,showMeta:s,destination:e.dataset.destination||"meta",content:this.extractFieldContent(e),mode:e.dataset.mode||"direct",type:e.dataset.type||"single",name:e.dataset.field,itemID:this.extractFieldItemId(e)??0,maxFiles:"max-files"in e.dataset?parseInt(e.dataset.maxFiles):0,subType:e.dataset.subtype??"image",repeaterPath:null},a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a&&(i.repeaterPath=`${r.dataset.field}:${a.dataset.index}:${i.name}`),i}extractFieldContent(e){return e.dataset.content||e.closest("dialog")?.dataset.content||e.closest("form")?.dataset.save||null}extractFieldItemId(e){return e.dataset.itemId||e.closest("dialog")?.dataset.itemId||null}determineFieldId(e){let t=this.extractFieldContent(e);t=null===t?"":t+"_";let s=this.extractFieldItemId(e);s=null===s?"":s+"_";const i=e.dataset.field||"",a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a?`${t}${s}${r.dataset.field}_${a.dataset.index}_${i}`:`${t}${s}${i}`}getFieldIdFromElement(e){const t=e.closest(this.selectors.fields.field);return t?.dataset.uploader||null}updateFieldProgress(e,t,s,i){const a=this.fields.get(e);a&&window.showProgress(a.ui.progress,t,s,i)}getWorker(){if(!this.workerState.worker&&"undefined"!=typeof OffscreenCanvas){const e=new Blob(["\n self.onmessage = function(e) {\n const { id, imageBitmap, width, height, type, quality } = e.data;\n const canvas = new OffscreenCanvas(width, height);\n const ctx = canvas.getContext('2d');\n ctx.drawImage(imageBitmap, 0, 0, width, height);\n canvas.convertToBlob({ type, quality }).then(blob => {\n self.postMessage({ id, blob });\n });\n };\n "],{type:"application/javascript"}),t=URL.createObjectURL(e);this.workerState.worker=new Worker(t),URL.revokeObjectURL(t),this.workerState.worker.onmessage=e=>this.handleWorkerMessage(e),this.workerState.worker.onerror=e=>this.handleWorkerError(e)}return this.workerState.worker}handleWorkerMessage(e){const{id:t,blob:s}=e.data,i=this.workerState.tasks.get(t);i&&(clearTimeout(i.timeoutId),i.resolve(s),this.workerState.tasks.delete(t))}handleWorkerError(e){this.workerState.tasks.forEach(t=>{clearTimeout(t.timeoutId),t.reject(e)}),this.workerState.tasks.clear(),this.restartWorker()}restartWorker(){this.workerState.worker&&(this.workerState.worker.terminate(),this.workerState.worker=null),this.workerState.restart.count++}async processImages(e,t=2200,s=2200){const i=[],a=[...e],r=this.workerState.settings.maxConcurrent,o=async()=>{for(;a.length>0;){const e=a.shift(),r=await this.processImage(e.file,t,s);i.push({uploadId:e.uploadId,blob:r})}};return await Promise.all(Array.from({length:Math.min(r,e.length)},()=>o())),i}async processImage(e,t=2200,s=2200,i=3e3){if("undefined"==typeof OffscreenCanvas)return this.resizeImage(e,t,s);try{return await this.withTimeout(this.workerImage(e,t,s),i)}catch(i){return this.resizeImage(e,t,s)}}withTimeout(e,t){return Promise.race([e,new Promise((e,s)=>setTimeout(()=>s(new Error("Timeout")),t))])}async workerImage(e,t=2200,s=2200){const{settings:i,restart:a}=this.workerState;if(a.count>=a.max)throw new Error("Worker max restarts exceeded");const r=await createImageBitmap(e);let{width:o,height:l}=r;if(o>t||l>s){const e=Math.min(t/o,s/l);o=Math.round(o*e),l=Math.round(l*e)}const d=this.getWorker(),n=crypto.randomUUID();return new Promise((t,s)=>{const a=setTimeout(()=>{this.workerState.tasks.delete(n),i.restartAfterTimeout&&this.restartWorker(),s(new Error("Timeout"))},i.timeout);this.workerState.tasks.set(n,{resolve:t,reject:s,timeoutId:a}),d.postMessage({id:n,imageBitmap:r,width:o,height:l,type:e.type,quality:.9},[r])})}resizeImage(e,t,s){return new Promise(i=>{const a=new Image;a.onload=()=>{URL.revokeObjectURL(a.src);let{width:r,height:o}=a;if(r>t||o>s){const e=Math.min(t/r,s/o);r=Math.round(r*e),o=Math.round(o*e)}const l=document.createElement("canvas");l.width=r,l.height=o,l.getContext("2d").drawImage(a,0,0,r,o),l.toBlob(i,e.type,.9)},a.src=URL.createObjectURL(e)})}async processFiles(e,t){let s=this.fields.get(e);if(!s)return;s.groupUI.container&&(s.groupUI.container.hidden=!1);const i=t.length;let a=0;this.updateFieldProgress(e,0,i,"Processing files...");const r=await Promise.all(t.map(async t=>{const s=window.generateID("upload"),i=await this.setUpload(s,{id:s,field:e,status:"local_processing",fields:{originalName:t.name,originalSize:t.size,type:t.type,lastModified:t.lastModified}}),a=await this.createUpload(s,t,e);return this.uploads.set(s,{element:a,ui:window.uiFromSelectors(this.selectors.items,a)}),await this.addToGroup(s,null),{uploadId:s,upload:i,file:t}})),o=r.filter(e=>e.file.type.startsWith("image/")),l=r.filter(e=>!e.file.type.startsWith("image/")),d=await this.processImages(o.map(e=>({file:e.file,uploadId:e.uploadId})));for(const{uploadId:t,blob:s}of d){const r=o.find(e=>e.uploadId===t);r&&(r.upload.blob=s,r.upload.fields.size=s.size,r.upload.status="queued",await this.setUpload(t,r.upload),a++,this.updateFieldProgress(e,a,i,"Processing files..."))}for(const{uploadId:t,upload:s,file:r}of l)s.blob=r,s.status="queued",await this.setUpload(t,s),a++,this.updateFieldProgress(e,a,i,"Processing files...");this.maybeLockUploads(e),s.config.autoUpload&&"post_group"!==s.config.destination&&await this.queueUploads("uploads",e)}async checkRecovery(){const e=Array.from(this.stores.groups.data.values());for(const t of e){this.stores.uploads.filterByIndex({group:t.id}).length>0||await this.stores.groups.delete(t.id)}}async restoreSelectedUploads(e){let t=window.location.href,s=Array.from(this.stores.uploads.data.values()).filter(s=>e.includes(s.id)&&s.src===t),i=[...new Set(s.map(e=>e.group))].filter(Boolean),a=s[0].field,r=document.querySelector(`[data-uploader="${a}"]`);if(!r){if(!("crudManager"in window)||!a.startsWith(window.crudManager.content))return void console.log("No field found for "+a);{let[e,t,s]=a.split("_");if(!(parseInt(t)>0))return void console.log("No field found for "+a);window.crudManager.openEditModal(t),r=document.querySelector(`[data-uploader="${a}"]`)}}let o=this.fields.get(a);o.groupUI.container&&(o.groupUI.container.hidden=!1);let l=[];for(let e of i){let t=this.stores.groups.get(e);await this.createGroup(a,e);let i=this.groups.get(e),r=s.filter(t=>t.group===e);if(t&&this.groups.has(e)){let e=t.fields;for(const[t,s]of Object.entries(e)){let e=i.element.querySelector(`input[name*="${t}"]`);e&&(e.value=s)}}else e=null;for(let t of r){let s=await this.createUpload(t.id,this.formatFile(t),a);this.uploads.set(t.id,{element:s,ui:window.uiFromSelectors(this.selectors.items,s)}),await this.addToGroup(t.id,e),l.push(t.id)}}let d=s.filter(e=>!l.includes(e.id));for(let e of d){let t=await this.createUpload(e.id,this.formatFile(e),a);this.uploads.set(e.id,{element:t,ui:window.uiFromSelectors(this.selectors.items,t)}),await this.addToGroup(e.id,null)}}async restoreUploads(e){const t=e.map(e=>this.stores.uploads.get(e)).filter(Boolean);0!==t.length&&await this.restoreSelectedUploads(t.map(e=>e.id))}async clearUploads(e){await Promise.all(e.map(e=>this.clearUpload(e)))}getStatusText(e){return{received:"Image Received",local_processing:"Processing Image...",queued:"Waiting to upload...",uploading:"Uploading to Server",pending:"Successfully sent to server. In line for further processing.",processing:"Processing on server...",completed:"Upload complete!",failed:"Upload failed (will retry)",failed_permanent:"Upload failed permanently"}[e]||e}getStatusProgress(e){return{local_processing:28,queued:50,uploading:66,pending:75,processing:89,completed:100}[e]??0}async createUpload(e,t,s){let i=this.fields.get(s);if(!i)return null;let a={uploadId:e,file:t,field:i};return this.templates.create("uploadItem",a)}getSubtypeFromURL(e){if(!e||""===e)return"";const t=e.split("?")[0].toLowerCase();return[".webp",".jpg",".jpeg",".png",".gif",".svg"].some(e=>t.endsWith(e))?"image":[".mp4",".ogg",".mov",".webm",".avi"].some(e=>t.endsWith(e))?"video":"document"}getSubtypeFromMime(e){return e.startsWith("image/")?"image":e.startsWith("video/")?"video":"document"}async handleRemoveItem(e){console.log("Handling remove upload");const t=e.closest(this.selectors.items.item);if(!t)return;const s=t.dataset.uploadId,i=t.dataset.id;if((s||i)&&confirm("Remove this item?")){if(s)await this.removeUpload(s);else{const s=this.getFieldIdFromElement(e);t.remove(),s&&(this.updateHiddenInput(s),this.maybeLockUploads(s))}this.a11y.announce("Item removed")}}updateHiddenInput(e){const t=this.fields.get(e);if(!t?.ui.hidden)return;const s=Array.from(t.ui.grid?.querySelectorAll(this.selectors.items.item)||[]).map(e=>Object.hasOwn(e.dataset,"id")&&e.dataset.id>0?e.dataset.id:Object.hasOwn(e.dataset,"upload-id")&&e.dataset.uploadId>0?e.dataset.uploadId:e.dataset.itemId).filter(Boolean).join(",");t.ui.hidden.value!==s&&(t.ui.hidden.value=s,t.ui.hidden.dispatchEvent(new Event("change",{bubbles:!0})))}async setBulkUpload(e,t,s){const i=Array.from(e).map(async e=>{if("string"==typeof e&&(e=await this.stores.uploads.get(e)),e)return"status"===t&&await this.setUploadStatus(e,s),e[t]=s,this.stores.uploads.save(e)});await Promise.all(i)}async setUploadStatus(e,t){"string"==typeof e&&(e=await this.stores.uploads.get(e)),e&&e.progress&&window.showProgress(e.progress,this.getStatusProgress(t),100,this.getStatusText(t),this.queue.icons[t]??"")}async removeUpload(e){let t=this.stores.uploads.get(e);if(!t)return;const s=t.field;if(t.group){let s=this.stores.groups.get(t.group);s.uploads=s.uploads.filter(t=>t!==e),0===s.uploads.length?await this.removeGroup(s.id,!1):await this.stores.groups.save(s)}await this.clearUpload(e),this.updateHiddenInput(s),this.maybeLockUploads(s);let i=this.selectionHandlers.get(s);i&&i.deselect(e),this.a11y.announce("Upload removed")}async clearUpload(e){const t=this.uploads.get(e);if(t&&(this.revokePreviewUrl(t.preview),t.element)){const e=t.element.dataset.previewUrl;this.revokePreviewUrl(e),t.element.remove()}this.uploads.delete(e),await this.stores.uploads.delete(e)}async handleAddToGroup(e){const t=this.selected.get(e);if(!t||0===t.size)return;let s=await this.createGroup(e);s&&(await Promise.all(Array.from(t).map(e=>this.addToGroup(e,s))),this.selectionHandlers.get(e)?.clearSelection(),this.a11y.announce(`Created group with ${t.size} items`))}async createGroup(e,t=null){let s=this.fields.get(e);if(!s)return;t||(t=window.generateID("group"));const i=this.createGroupElement(t,e);if(!i)return null;const a=s.groupUI.empty;a?.nextSibling?s.groupUI.grid.insertBefore(i,a.nextSibling):s.groupUI.grid.append(i);const r=i.querySelector(".item-grid");r&&(r.dataset.groupId=t,this.createSortable(e,r,t));let o=this.stores.groups.data.has(t)?this.stores.groups.data.get(t):{};return await this.setGroup(t,{...o,id:t,field:e}),t}createGroupElement(e,t=null){let s={groupId:e,fieldId:t},i=this.templates.create("imageGroup",s);return this.groups.set(e,{element:i,ui:window.uiFromSelectors(this.selectors.group,i)}),this.getSelectionHandler(t)?.addWrapper(i),i}async setGroup(e,t){const s={...{id:e,src:window.location.href,uploads:[],operationId:null,field:null,fields:{}},...t};Object.preventExtensions(s),await this.stores.groups.save(s)}async setBulkGroup(e,t,s){let i=this.stores.groups.filterByIndex({field:e});if(0===i.length)return;let a=i.map(e=>{e[t]=s,this.stores.groups.save(e)});await Promise.all(a)}async addToGroup(e,t=null){const s=this.stores.uploads.get(e),i=this.uploads.get(e);if(!s||!i)return;const a=this.fields.get(s.field);if(!a)return;if(null!==i.element?.parentElement&&(!t&&null===s.group||t===s.group))return void this.handleReorder(s.field,t);if(s.group){const t=this.stores.groups.get(s.group);t&&(t.uploads=t.uploads.filter(t=>t!==e),0===t.uploads.length?await this.removeGroup(t.id,!1):await this.stores.groups.save(t))}i.ui.checkbox&&(i.ui.checkbox.checked=!1);const r=this.selectionHandlers.get(s.field);if(r&&r.isSelected(e)&&r.deselect(e),this.selected.get(s.field)?.has(e)&&this.selected.get(s.field).delete(e),i.ui.featured&&(i.ui.featured.hidden=!t),t){i.ui.featured&&(i.ui.featured.name=`${t}_featured`);let a=this.stores.groups.get(t);a&&(a.uploads.push(e),s.group=t,await this.stores.groups.save(a))}else s.group=null;let o=t?this.groups.get(t)?.ui.grid:a.ui.grid;o&&(o.append(i.element),t&&await this.handleReorder(s.field,t)),await this.stores.uploads.save(s)}handleDeleteGroup(e){const t=e.closest(this.selectors.group.item);if(!t)return;let s=t.dataset.groupId;if(!confirm("Delete this group? Items will be moved back to the upload area."))return;let i=this.stores.uploads.filterByIndex({group:s});Promise.all(i.map(e=>this.addToGroup(e.id,null))).then(()=>{this.removeGroup(s,!1).then(()=>{}),this.a11y.announce("Group deleted. Items returned to upload area")})}async removeGroup(e,t=!0){let s=this.groups.get(e),i=this.stores.groups.get(e);if(!i)return;let a=!0;t&&i.uploads.length>0&&(a=window.confirm("Keep uploads in this group?")),await Promise.all(i.uploads.map(e=>a?this.addToGroup(e,null):this.removeUpload(e)));if(this.fields.get(i.field)){const t=this.getGroupKey(i.field,e),a=this.selectionHandlers.get(t);if(a?.destroy&&a.destroy(),this.selectionHandlers.get(i.field)&&s&&s.element&&this.selectionHandlers.get(i.field).removeWrapper(s.element),this.sortables.has(t)){const e=this.sortables.get(t);e?.destroy&&e.destroy(),this.sortables.delete(t)}}s?.element&&s.element.remove(),this.groups.delete(e),await this.stores.groups.delete(e),this.a11y.announce("Group removed")}maybeLockUploads(e){let t=this.fields.get(e);if(!t||!t.ui.dropZone)return;let s=this.stores.uploads.filterByIndex({field:e}).length,i=t.config.maxFiles??0;t.ui.dropZone.hidden=i>0&&s>=i}async handleOperationCancelled(e){0!==e.length&&e.forEach(e=>{this.removeUpload(e)})}getGroupKey(e,t=null){return t?`${e}_${t}`:`${e}`}getSelectionHandler(e){let t=this.getGroupKey(e);if(!this.selectionHandlers.has(t)){let s=this.fields.get(e);if(!s)return;if("post_group"!==s.config.destination)return;let i=new window.jvbHandleSelection(s.element,{selectAll:{checkbox:this.selectors.fields.selectAll,count:this.selectors.fields.count,bulkControls:this.selectors.fields.actions},item:{item:this.selectors.items.item,checkbox:this.selectors.items.checkbox,idAttribute:"uploadId"},wrapper:{wrapper:".preview-wrap, .upload-group",id:"groupId"}});i.subscribe((t,s)=>{this.selected.set(e,s.selectedItems)}),this.selectionHandlers.set(t,i)}return this.selectionHandlers.get(t)}updateHandlerItems(e){let t=this.getSelectionHandler(e);t&&t.collectItems()}initSortable(e){if(!window.Sortable)return;const t=this.fields.get(e);t&&(!Sortable._multiDragMounted&&Sortable.MultiDrag&&(Sortable.mount(new Sortable.MultiDrag),Sortable._multiDragMounted=!0),this.createSortable(e,t.ui.grid,null),this.initEmptyGroupDropZone(e))}createSortable(e,t,s){if(!t)return null;const i=this.getGroupKey(e,s);if(this.sortables.has(i))return this.sortables.get(i);const a=new Sortable(t,{animation:150,draggable:".item",multiDrag:!0,selectedClass:"selected",avoidImplicitDeselect:!0,group:{name:e,pull:!0,put:!0},dragClass:"dragging",ignore:".empty-group",onStart:t=>{const s=t.item,i=s?.dataset.uploadId,a=this.selected.get(e);if(i&&(!a||!a.has(i))){const t=this.selectionHandlers.get(e);t&&t.select(i)}},onEnd:t=>this.sortableDrop(t,e)});return this.sortables.set(i,a),a}initEmptyGroupDropZone(e){const t=this.fields.get(e),s=t?.groupUI?.empty;s&&(s.addEventListener("dragover",e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="move",s.classList.add("drag-over")}),s.addEventListener("dragleave",e=>{s.contains(e.relatedTarget)||s.classList.remove("drag-over")}),s.addEventListener("drop",async t=>{t.preventDefault(),t.stopPropagation(),s.classList.remove("drag-over");const i=this.selected.get(e);if(!i||0===i.size)return;const a=await this.createGroup(e);a&&(await Promise.all(Array.from(i).map(e=>this.addToGroup(e,a))),this.selectionHandlers.get(e)?.clearSelection())}))}async sortableDrop(e,t){const s=e.to,i=(e.items?.length>0?Array.from(e.items):[e.item]).map(e=>e.dataset.uploadId).filter(Boolean);if(0===i.length)return;const a=s.dataset.groupId||null;for(const e of i)await this.addToGroup(e,a);await this.handleReorder(t,a),this.selectionHandlers.get(t)?.clearSelection()}handleReorder(e,t=null){let s=t?this.groups.get(t)?.ui.grid:this.fields.get(e)?.ui.grid;if(s){if(t){let e=Array.from(s.children).filter(e=>e.matches(this.selectors.items.item)&&!e.classList.contains("ghost")).map(e=>e.dataset.uploadId).filter(e=>e),i=this.stores.groups.get(t);i&&(i.uploads=e,this.stores.groups.save(i).then(()=>{}))}else this.updateHiddenInput(e);this.a11y.announce("Items reordered")}else console.log("Couldn't Reorder items...")}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t={}){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("Subscriber error:",e)}})}destroy(){this.subscribers.clear(),this.previewUrls.forEach(e=>{this.revokePreviewUrl(e)}),this.previewUrls.clear()}cleanupAllPreviewUrls(){this.previewUrls.forEach(e=>this.revokePreviewUrl(e)),this.previewUrls.clear()}async handleClearCache(){const e=window.location.href,t=this.stores.uploads.filterByIndex({src:e}),s=this.stores.groups.filterByIndex({src:e});await Promise.all([...t.map(e=>this.clearUpload(e.id)),...s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id)))]),this.restoreModal&&this.cleanupRestore(),this.a11y.announce("Cache cleared for this page")}async getFilesForForm(e){const t=e.querySelectorAll(this.selectors.fields.field),s=[];for(const e of t){const t=this.determineFieldId(e),i=e.dataset.field,a=this.stores.uploads.filterByIndex({field:t});for(const e of a){const t=this.formatFile(e);t&&s.push({file:t,fieldName:i,uploadId:e.id,meta:e.fields||{}})}}return s}async clearFieldFromStores(e){const t=this.stores.uploads.filterByIndex({field:e}),s=this.stores.groups.filterByIndex({field:e});await Promise.all(t.map(e=>this.clearUpload(e.id))),await Promise.all(s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id))))}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbUploads=new e)})})})();
\ No newline at end of file
+(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.queue=window.jvbQueue,this.error=window.jvbError,this.templates=window.jvbTemplates,this.subscribers=new Set,this.initStores(),this.initWorker(),this.fields=new Map,this.uploads=new Map,this.groups=new Map,this.selected=new Map,this.selectionHandlers=new Map,this.sortables=new Map,this.changes=new Map,this.previewUrls=new Set,this.initElements(),this.initListeners(),this.defineTemplates()}defineTemplates(){const e=this.templates,t=this;e.define("uploadItem",{refs:{select:'[name="select-item"]',featured:'[name="featured"]',img:"img",video:"video",file:"label > span",details:"details",alt:'[name="image-alt-text"]',title:'[name="image-title"]',description:'[name="image-caption"]'},manyRefs:{inputs:"input, select, textarea"},setup({el:e,refs:s,manyRefs:i,data:a}){let r,o,l,d=!1;switch(Object.hasOwn(a,"file")?(e.dataset.uploadId=a.uploadId,r=t.getSubtypeFromMime(a.file.type)||"image",o="document"!==r&&t.createPreviewUrl(a.file),d=o,l=a.file.name||""):(e.dataset.id=a.id,r=t.getSubtypeFromURL(a.medium??a.src),o=a.medium??a.src,l=a["image-alt-text"]??""),e.dataset.subtype=r,s.featured&&(s.featured.value=a.uploadId),r){case"image":s.img&&(s.img.src=o,s.img.alt=l,d&&(s.img.dataset.previewUrl=d)),s.video&&s.video.remove(),s.file&&s.file.remove();break;case"video":s.video&&(s.video.src=o,s.video.alt=l,d&&(s.video.dataset.previewUrl=d)),s.img&&s.img.remove(),s.file&&s.file.remove();break;case"document":if(s.preview){let e=a.file.name.split(".").pop()?.toLowerCase()??"",t={pdf:"file-pdf",csv:"file-csv",doc:"file-doc",docx:"file-doc",txt:"file-txt",xls:"file-xls",xlsx:"file-xls"},i=window.getIcon(t[e]??"file");s.preview.innerText=a.file.name??a.title,s.preview.prepend(i)}s.img&&s.img.remove(),s.video&&s.video.remove()}if(s.details&&(Object.hasOwn(a,"field")&&Object.hasOwn(a.field,"config")&&Object.hasOwn(a.field.config,"showMeta")&&!a.field.config.showMeta?s.details.remove():(Object.hasOwn(a,"id")?s.details.dataset.attachmentId=a.id:Object.hasOwn(a,"uploadId")&&(s.details.dataset.uploadId=a.uploadId),s.details.setAttribute("data-ignore",""),"image"!==r&&s.alt?s.alt.closest(".field")?.remove():Object.hasOwn(a,"image-alt-text")&&s.alt&&(s.alt.value=a["image-alt-text"]),(Object.hasOwn(a,"title")||Object.hasOwn(a,"file"))&&s.title&&(s.title.value=a.title||a.file.name),Object.hasOwn(a,"image-caption")&&s.description&&(s.description.value=a["image-caption"]))),e.draggable="single"!==e.dataset.mode,i.inputs)for(let t of i.inputs){let s=t.closest("[data-field]")??t.closest(".radio-button")??e;window.prefixInput(t,`${a.id??a.uploadId}-`,s)}}}),e.define("imageGroup",{refs:{selectAll:"[data-select-all]",fields:".fields",details:"details",grid:".item-grid"},setup({el:t,refs:s,manyRefs:i,data:a}){if(t.dataset.groupId=a.groupId,s.selectAll){let e=s.selectAll.closest(".field");window.prefixInput(s.selectAll,`select-all-${a.groupId}`,e,!0)}let r=e.create("groupMetadata",{groupId:a.groupId});r?s.fields.append(r):s.details.remove(),s.grid&&(s.grid.dataset.groupId=a.groupId)}}),e.define("groupMetadata",{manyRefs:{inputs:"input,textarea,select"},setup({el:e,refs:t,manyRefs:s,data:i}){s.inputs&&s.inputs.forEach(e=>{let t=e.closest("[data-field]");e.dataset.groupId=i.groupId,window.prefixInput(e,`${i.groupId}-`,t)})}}),e.define("restoreNotification",{refs:{details:".details",wrap:".wrap"},setup({el:t,refs:s,manyRefs:i,data:a}){if(s.details){let e=a.bySource.size>1?` across ${a.bySource.size} pages`:"",t=a.pendingUploads.length>1?"uploads":"upload";s.details.textContent=`${a.pendingUploads.length} ${t} can be recovered${e}`}if(!s.wrap)return void console.warn("No wrap element in template");let r=1;for(const[t,i]of a.bySource){let a={index:r,isCurrent:t===window.location.href,src:t,uploads:i};s.wrap.append(e.create("restoreField",a)),r++}}}),e.define("restoreField",{refs:{h3:"h3",a:"h3 a",grid:".item-grid"},async setup({el:e,refs:s,manyRefs:i,data:a}){let r=t.registerField(e,!1,!1,`recovery_${a.index}`);a.isCurrent?(e.open=!0,s.a?.remove(),s.h3&&(s.h3.textContent="From this page:")):s.a&&(s.a.href=a.src,s.a.title="Navigate to page and restore",s.a.textContent=a.src);let o=[...new Set(a.uploads.map(e=>e.group??"preview"))];for(let e of o){let i="preview"===e||t.stores.groups.get(e);if(!i)continue;let o=await t.createGroupElement(e,r),l=o.querySelector(".item-grid"),d=a.uploads.filter(t=>t.group===("preview"===e)?null:e);for(const[e,t]of Object.entries(i.fields??{})){let s=o.querySelector(`input[name*="${e}"]`);s&&(s.value=t)}for(let e of d){let s=await t.createUpload(e.id,t.formatFile(e),r);l.append(s)}s.grid.append(o)}}})}initStores(){const{uploads:e,groups:t}=window.jvbStore.register("uploads",[{storeName:"uploads",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"status",keyPath:"status"},{name:"group",keyPath:"group"},{name:"src",keyPath:"src"}]},{storeName:"groups",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"src",keyPath:"src"}]}]);this.stores={uploads:e,groups:t,ready:[]},this.stores.uploads.subscribe(this.handleStores.bind(this,"uploads")),this.stores.groups.subscribe(this.handleStores.bind(this,"groups")),this.queue.subscribe((e,t)=>{if(("operation-status"===e||"cancel-operation"===e)&&["image_upload","video_upload","document_upload"].includes(t.type)){let s=[];if(t.data)if(t.data instanceof FormData){const e=this.stores.uploads.formDataToObject(t.data);s=e.upload_ids||[]}else s=t.data.upload_ids||[];if(0===s.length&&t.result&&t.result.upload_ids&&(s=t.result.upload_ids),!s||0===s.length)return void console.warn("[UploadManager] No upload_ids found for operation:",{id:t.id,type:t.type,status:t.status,hasData:!!t.data,hasResult:!!t.result});if("cancel-operation"===e)return this.handleOperationCancelled(s);this.setBulkUpload(s,"status",t.status).then(()=>{console.log(`[UploadManager] Updated ${s.length} uploads to status: ${t.status}`)}),"completed"===t.status&&("process_upload_groups"===t.type?(s.forEach(e=>{this.setBulkUpload([e],"serverProcessed",!0).then(()=>{})}),t.result&&t.result.created_posts&&console.log("[UploadManager] Created posts:",t.result.created_posts),setTimeout(()=>{s.forEach(e=>{this.removeUpload(e).then(()=>{})})},2e3)):s.forEach(e=>{this.removeUpload(e).then(()=>{})})),"failed"!==t.status&&"failed_permanent"!==t.status||console.error("[UploadManager] Operation failed:",{id:t.id,type:t.type,uploadIds:s,error:t.error_message})}})}storesReady(){return 2===this.stores.ready.length}handleStores(e,t){"data-ready"===t&&(this.stores.ready.push(e),this.storesReady()&&this.checkRecovery().then(()=>{}))}initWorker(){this.worker=null,this.workerState={worker:null,tasks:new Map,restart:{count:0,max:3},settings:{timeout:3e3,maxConcurrent:3,restartAfterTimeout:!0}}}initElements(){this.selectors={fields:{field:"[data-upload-field]",input:'input[type="file"]',dropZone:".file-upload-wrapper",preview:".preview-wrap",grid:".item-grid.preview",progress:{progress:".file-upload-container .progress",fill:".file-upload-container .progress .fill",details:".file-upload-container .progress .details",icon:".file-upload-container .progress .icon"},selectAll:"[data-select-all]",actions:".selection-actions",count:".selected .info",hidden:'input[type="hidden"]'},groups:{container:".group-display",grid:".item-grid.groups",empty:".empty-group",header:".sidebar .header"},group:{item:".upload-group",actions:".selection-actions",selectAll:'[name="select-all-group"]',count:".group-header .info",fields:"details .fields",grid:".item-grid.group",total:".group-content .group-count"},items:{item:".item.upload",checkbox:'[name="select-item"]',featured:'[name="featured"]',image:"img",details:"details",progress:{progress:".progress",fill:".fill",details:".details",icon:".icon"}}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.dragEnterHandler=this.handleDragEnter.bind(this),this.dragLeaveHandler=this.handleDragLeave.bind(this),this.dragOverHandler=this.handleDragOver.bind(this),this.dropHandler=this.handleDrop.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("dragenter",this.dragEnterHandler),document.addEventListener("dragleave",this.dragLeaveHandler),document.addEventListener("dragover",this.dragOverHandler),document.addEventListener("drop",this.dropHandler),window.addEventListener("beforeunload",()=>{this.cleanupAllPreviewUrls()})}async setUpload(e,t){const s={...{id:e,attachment:null,group:null,field:null,src:window.location.href,blob:null,status:"local_processing",operationId:null,fields:{}},...t};if(Object.preventExtensions(s),await this.stores.uploads.save(s),this.fields.has(s.field)){let e=this.fields.get(s.field);if("local_processing"===s.status)this.notify("upload-received",{field:e.element,id:s.id})}return s}createPreviewUrl(e){const t=URL.createObjectURL(e);return this.previewUrls.add(t),t}revokePreviewUrl(e){e?.startsWith("blob:")&&(URL.revokeObjectURL(e),this.previewUrls.delete(e))}formatFile(e){return e.blob?new File([e.blob],e.fields.originalName||"file",{type:e.fields.type||e.blob.type,lastModified:e.fields.lastModified||Date.now()}):null}handleClick(e){if(!window.targetCheck(e,this.selectors.fields.field))return;let t=window.targetCheck(e,this.selectors.fields.dropZone);t&&!e.target.matches("input, button, a")&&t.querySelector(this.selectors.fields.input)?.click();const s=window.targetCheck(e,"[data-action]");s&&this.handleAction(s)}handleAction(e){const t=e.dataset.action,s=this.getFieldIdFromElement(e);switch(t){case"add-to-group":this.handleAddToGroup(s).then(()=>{});break;case"delete-group":this.handleDeleteGroup(e);break;case"delete-upload":case"remove-from-group":this.handleRemoveItem(e).then(()=>{});break;case"upload":this.queueUploads("uploads/groups",s).then(()=>{});break;case"restore":this.handleRestoreSelected().then(()=>{});break;case"restore-all":this.handleRestoreAll().then(()=>{});break;case"clear-cache":this.handleClearCache().then(()=>{})}}handleChange(e){let t=this.getFieldIdFromElement(e.target);if(!t){return void(e.target.closest("[data-upload-id], [data-attachment-id]")&&this.queueUploadMeta(e))}if(e.target.matches(this.selectors.fields.input)){const s=Array.from(e.target.files);return void(s.length>0&&this.processFiles(t,s).then(()=>{}))}e.target.matches(this.selectors.items.checkbox)||e.target.matches(this.selectors.items.featured)||e.target.matches('[name*="select-"]')||("post_group"===this.fields.get(t).config.destination?this.handleGroupMetaChange(e.target):this.queueUploadMeta(e))}handleGroupMetaChange(e){const t=e.dataset.groupId;if(!t)return;const s=e.name;if(!s)return;const i=e.value,a=s.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");window.debouncer.schedule(`group-meta-${t}-${a}`,async()=>{const e=this.stores.groups.get(t);e&&(e.fields||(e.fields={}),e.fields[a]=i,await this.setGroup(t,e))},300)}handleDragEnter(e){if(!e.dataTransfer.types.includes("Files"))return;const t=e.target.closest(this.selectors.fields.dropZone);t&&(e.preventDefault(),t.classList.add("dragover"))}handleDragLeave(e){const t=e.target.closest(this.selectors.fields.dropZone);t&&!t.contains(e.relatedTarget)&&t.classList.remove("dragover")}handleDragOver(e){if(!e.dataTransfer.types.includes("Files"))return;e.target.closest(this.selectors.fields.dropZone)&&(e.preventDefault(),e.dataTransfer.dropEffect="copy")}handleDrop(e){const t=e.target.closest(this.selectors.fields.dropZone);if(!t)return;e.preventDefault(),t.classList.remove("dragover"),t.classList.add("uploading");const s=Array.from(e.dataTransfer.files);if(0===s.length)return;const i=this.getFieldIdFromElement(t);i&&(this.processFiles(i,s).then(()=>{this.updateHandlerItems(i)}),this.a11y.announce(`${s.length} file(s) dropped for upload`))}async queueUploads(e,t,s=null){let i=new FormData;const a=this.fields.get(t);if(!a)return;let r=this.stores.uploads.filterByIndex({field:t});if(0===r.length)return;const[o,l]=["uploads"===e,"uploads/groups"===e];let d,n,u,p,c;i.append("fieldId",a.id),i.append("content",a.config.content),o&&(i.append("mode",a.config.mode),i.append("field_name",a.config.repeaterPath||a.config.name),i.append("fieldId",a.id),i.append("field_type",a.config.type),i.append("subtype",a.config.subtype),i.append("item_id",a.config.itemID),i.append("destination",a.config.destination),s&&i.append("depends_on",s)),l?({posts:d,uploadMap:n,files:u}=this.collectGroups(t)):o&&({uploadMap:n,files:u}=this.collectUploads(t)),l&&i.append("posts",JSON.stringify(d)),u.forEach(e=>{i.append("files[]",e)}),i.append("upload_ids",JSON.stringify(n)),o?(p=`Uploading ${r.length} file${r.length>1?"s":""} to server...`,c=`Uploading ${r.length} file${r.length>1?"s":""}...`):l&&(p=`Creating ${d.length} ${a.config.content}${d.length>1?"s":""} from uploads...`,c=`Creating ${d.length} post${d.length>1?"s":""}...`),await this.setBulkUpload(r,"status","queued");let h=this.sendToQueue(e,i,p,c);if("uploads/groups"===e){let e=a.element.closest("details");e&&(e.open=!1),this.notify("groups_uploaded",{fieldId:t,posts:d,content:a.config.content})}return h?(a.operationId=h,await this.setBulkUpload(r,"operationId",h),await this.setBulkUpload(r,"status","uploading"),await this.setBulkGroup(t,"operationId",h),this.fields.set(a.id,a),this.notify("sent-to-queue",{field:a,operation:h})):await this.setBulkUpload(r,"status","failed"),h}async sendToQueue(e,t,s="",i="",a=!1){""===i&&(i=s);const r={endpoint:e,method:"POST",data:t,title:s,popup:i,canMerge:a,sendNow:"uploads/groups"===e,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},append:"_upload"};try{return await this.queue.addToQueue(r)}catch(e){return this.error.log(e,{component:"UploadManager",action:"sentToQueue"}),!1}}collectGroups(e){let t=this.stores.uploads.filterByIndex({field:e}),s=[],i=[],a=[];const r=this.stores.groups.filterByIndex({field:e}).filter(e=>{const t=this.getGroupUploadsInOrder(e);return t.length>0&&t.some(e=>this.formatFile(e))});for(const e of r){const t=this.groups.get(e.id)?.element,r=this.collectGroupFieldsFromDOM(t,e.id),o={groupId:e.id,images:[],fields:r},l=this.getGroupUploadsInOrder(e);for(const t of l){const s=this.formatFile(t);if(s){a.push(s);const r={upload_id:t.id,index:i.length},l=this.uploads.get(t.id),d=l?.element?.querySelector(`input[name="${e.id}_featured"]`);d?.checked&&(o.fields.featured=t.id),o.images.push(r),i.push(t.id)}}o.images.length>0&&s.push(o)}const o=t.filter(e=>!e.group);for(const e of o){const t={groupId:window.generateID("group"),images:[],fields:{}},r=this.formatFile(e);if(r){a.push(r);const s={upload_id:e.id,index:i.length};t.images.push(s),i.push(e.id)}t.images.length>0&&s.push(t)}return{posts:s,uploadMap:i,files:a}}getGroupUploadsInOrder(e){return e.uploads&&0!==e.uploads.length?e.uploads.map(e=>this.stores.uploads.get(e)).filter(Boolean):[]}collectGroupFieldsFromDOM(e,t){if(!e)return{};const s={};return e.querySelectorAll("input, textarea, select").forEach(e=>{const i=e.name.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");["featured","select-all"].some(e=>i.includes(e))||e.value&&(s[i]=e.value)}),s}collectUploads(e){let t=this.stores.uploads.filterByIndex({field:e});if(0===t.length)return;let s=[],i=[];for(const e of t){const t=this.formatFile(e);t&&(i.push(t),s.push(e.id))}return{uploadMap:s,files:i}}queueUploadMeta(e){let t=e.target.closest("[data-attachment-id]")?.dataset.attachmentId,s=!1;if(!t&&(t=e.target.closest("[data-upload-id]")?.dataset.uploadId,s=!0,!t))return;if(!this.changes.has(t)){let e={};s?e.uploadId=t:e.attachmentId=t,this.changes.set(t,e)}let i=e.target.closest("[data-field]").dataset.field;this.changes.get(t)[i]=e.target.value,this.scheduleSave()}scheduleSave(){window.debouncer.schedule("upload-meta",async()=>{if(this.changes.size>0){let e={};for(let[t,s]of this.changes.entries())console.log(t,s),e[t]=s;let t={user:window.auth.getUser(),items:e};await this.sendToQueue("uploads/meta",t,"Uploading Meta","Uploading Meta",!0),this.changes.clear()}},2e3)}scanFields(e,t=!0,s=!0){e.querySelectorAll(this.selectors.fields.field).forEach(e=>this.registerField(e,t,s))}registerField(e,t=!0,s=!0,i=null){const a={element:e,id:i||this.determineFieldId(e),config:this.extractFieldConfig(e,t,s),uploads:new Set,operationId:null,groups:[],ui:window.uiFromSelectors(this.selectors.fields,e),groupUI:window.uiFromSelectors(this.selectors.groups,e)};return this.fields.set(a.id,a),e.dataset.uploader=a.id,this.getSelectionHandler(a.id),"single"!==a.config.type&&this.initSortable(a.id),this.maybeLockUploads(a.id),a.id}extractFieldConfig(e,t,s){const i={autoUpload:t,showMeta:s,destination:e.dataset.destination||"meta",content:this.extractFieldContent(e),mode:e.dataset.mode||"direct",type:e.dataset.type||"single",name:e.dataset.field,itemID:this.extractFieldItemId(e)??0,maxFiles:"max-files"in e.dataset?parseInt(e.dataset.maxFiles):0,subType:e.dataset.subtype??"image",repeaterPath:null},a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a&&(i.repeaterPath=`${r.dataset.field}:${a.dataset.index}:${i.name}`),i}extractFieldContent(e){return e.dataset.content||e.closest("dialog")?.dataset.content||e.closest("form")?.dataset.save||null}extractFieldItemId(e){return e.dataset.itemId||e.closest("dialog")?.dataset.itemId||null}determineFieldId(e){let t=this.extractFieldContent(e);t=null===t?"":t+"_";let s=this.extractFieldItemId(e);s=null===s?"":s+"_";const i=e.dataset.field||"",a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a?`${t}${s}${r.dataset.field}_${a.dataset.index}_${i}`:`${t}${s}${i}`}getFieldIdFromElement(e){const t=e.closest(this.selectors.fields.field);return t?.dataset.uploader||null}updateFieldProgress(e,t,s,i){const a=this.fields.get(e);a&&window.showProgress(a.ui.progress,t,s,i)}getWorker(){if(!this.workerState.worker&&"undefined"!=typeof OffscreenCanvas){const e=new Blob(["\n self.onmessage = function(e) {\n const { id, imageBitmap, width, height, type, quality } = e.data;\n const canvas = new OffscreenCanvas(width, height);\n const ctx = canvas.getContext('2d');\n ctx.drawImage(imageBitmap, 0, 0, width, height);\n canvas.convertToBlob({ type, quality }).then(blob => {\n self.postMessage({ id, blob });\n });\n };\n "],{type:"application/javascript"}),t=URL.createObjectURL(e);this.workerState.worker=new Worker(t),URL.revokeObjectURL(t),this.workerState.worker.onmessage=e=>this.handleWorkerMessage(e),this.workerState.worker.onerror=e=>this.handleWorkerError(e)}return this.workerState.worker}handleWorkerMessage(e){const{id:t,blob:s}=e.data,i=this.workerState.tasks.get(t);i&&(clearTimeout(i.timeoutId),i.resolve(s),this.workerState.tasks.delete(t))}handleWorkerError(e){this.workerState.tasks.forEach(t=>{clearTimeout(t.timeoutId),t.reject(e)}),this.workerState.tasks.clear(),this.restartWorker()}restartWorker(){this.workerState.worker&&(this.workerState.worker.terminate(),this.workerState.worker=null),this.workerState.restart.count++}async processImages(e,t=2200,s=2200){const i=[],a=[...e],r=this.workerState.settings.maxConcurrent,o=async()=>{for(;a.length>0;){const e=a.shift(),r=await this.processImage(e.file,t,s);i.push({uploadId:e.uploadId,blob:r})}};return await Promise.all(Array.from({length:Math.min(r,e.length)},()=>o())),i}async processImage(e,t=2200,s=2200,i=3e3){if("undefined"==typeof OffscreenCanvas)return this.resizeImage(e,t,s);try{return await this.withTimeout(this.workerImage(e,t,s),i)}catch(i){return this.resizeImage(e,t,s)}}withTimeout(e,t){return Promise.race([e,new Promise((e,s)=>setTimeout(()=>s(new Error("Timeout")),t))])}async workerImage(e,t=2200,s=2200){const{settings:i,restart:a}=this.workerState;if(a.count>=a.max)throw new Error("Worker max restarts exceeded");const r=await createImageBitmap(e);let{width:o,height:l}=r;if(o>t||l>s){const e=Math.min(t/o,s/l);o=Math.round(o*e),l=Math.round(l*e)}const d=this.getWorker(),n=crypto.randomUUID();return new Promise((t,s)=>{const a=setTimeout(()=>{this.workerState.tasks.delete(n),i.restartAfterTimeout&&this.restartWorker(),s(new Error("Timeout"))},i.timeout);this.workerState.tasks.set(n,{resolve:t,reject:s,timeoutId:a}),d.postMessage({id:n,imageBitmap:r,width:o,height:l,type:e.type,quality:.9},[r])})}resizeImage(e,t,s){return new Promise(i=>{const a=new Image;a.onload=()=>{URL.revokeObjectURL(a.src);let{width:r,height:o}=a;if(r>t||o>s){const e=Math.min(t/r,s/o);r=Math.round(r*e),o=Math.round(o*e)}const l=document.createElement("canvas");l.width=r,l.height=o,l.getContext("2d").drawImage(a,0,0,r,o),l.toBlob(i,e.type,.9)},a.src=URL.createObjectURL(e)})}async processFiles(e,t){let s=this.fields.get(e);if(!s)return;s.groupUI.container&&(s.groupUI.container.hidden=!1);const i=t.length;let a=0;this.updateFieldProgress(e,0,i,"Processing files...");const r=await Promise.all(t.map(async t=>{const s=window.generateID("upload"),i=await this.setUpload(s,{id:s,field:e,status:"local_processing",fields:{originalName:t.name,originalSize:t.size,type:t.type,lastModified:t.lastModified}}),a=await this.createUpload(s,t,e);return this.uploads.set(s,{element:a,ui:window.uiFromSelectors(this.selectors.items,a)}),await this.addToGroup(s,null),{uploadId:s,upload:i,file:t}})),o=r.filter(e=>e.file.type.startsWith("image/")),l=r.filter(e=>!e.file.type.startsWith("image/")),d=await this.processImages(o.map(e=>({file:e.file,uploadId:e.uploadId})));for(const{uploadId:t,blob:s}of d){const r=o.find(e=>e.uploadId===t);r&&(r.upload.blob=s,r.upload.fields.size=s.size,r.upload.status="queued",await this.setUpload(t,r.upload),a++,this.updateFieldProgress(e,a,i,"Processing files..."))}for(const{uploadId:t,upload:s,file:r}of l)s.blob=r,s.status="queued",await this.setUpload(t,s),a++,this.updateFieldProgress(e,a,i,"Processing files...");this.maybeLockUploads(e),s.config.autoUpload&&"post_group"!==s.config.destination&&await this.queueUploads("uploads",e)}async checkRecovery(){const e=Array.from(this.stores.groups.data.values());for(const t of e){this.stores.uploads.filterByIndex({group:t.id}).length>0||await this.stores.groups.delete(t.id)}}async restoreSelectedUploads(e){let t=window.location.href,s=Array.from(this.stores.uploads.data.values()).filter(s=>e.includes(s.id)&&s.src===t),i=[...new Set(s.map(e=>e.group))].filter(Boolean),a=s[0].field,r=document.querySelector(`[data-uploader="${a}"]`);if(!r){if(!("crudManager"in window)||!a.startsWith(window.crudManager.content))return void console.log("No field found for "+a);{let[e,t,s]=a.split("_");if(!(parseInt(t)>0))return void console.log("No field found for "+a);window.crudManager.openEditModal(t),r=document.querySelector(`[data-uploader="${a}"]`)}}let o=this.fields.get(a);o.groupUI.container&&(o.groupUI.container.hidden=!1);let l=[];for(let e of i){let t=this.stores.groups.get(e);await this.createGroup(a,e);let i=this.groups.get(e),r=s.filter(t=>t.group===e);if(t&&this.groups.has(e)){let e=t.fields;for(const[t,s]of Object.entries(e)){let e=i.element.querySelector(`input[name*="${t}"]`);e&&(e.value=s)}}else e=null;for(let t of r){let s=await this.createUpload(t.id,this.formatFile(t),a);this.uploads.set(t.id,{element:s,ui:window.uiFromSelectors(this.selectors.items,s)}),await this.addToGroup(t.id,e),l.push(t.id)}}let d=s.filter(e=>!l.includes(e.id));for(let e of d){let t=await this.createUpload(e.id,this.formatFile(e),a);this.uploads.set(e.id,{element:t,ui:window.uiFromSelectors(this.selectors.items,t)}),await this.addToGroup(e.id,null)}}async restoreUploads(e){const t=e.map(e=>this.stores.uploads.get(e)).filter(Boolean);0!==t.length&&await this.restoreSelectedUploads(t.map(e=>e.id))}async clearUploads(e){await Promise.all(e.map(e=>this.clearUpload(e)))}getStatusText(e){return{received:"Image Received",local_processing:"Processing Image...",queued:"Waiting to upload...",uploading:"Uploading to Server",pending:"Successfully sent to server. In line for further processing.",processing:"Processing on server...",completed:"Upload complete!",failed:"Upload failed (will retry)",failed_permanent:"Upload failed permanently"}[e]||e}getStatusProgress(e){return{local_processing:28,queued:50,uploading:66,pending:75,processing:89,completed:100}[e]??0}async createUpload(e,t,s){let i=this.fields.get(s);if(!i)return null;let a={uploadId:e,file:t,field:i};return this.templates.create("uploadItem",a)}getSubtypeFromURL(e){if(!e||""===e)return"";const t=e.split("?")[0].toLowerCase();return[".webp",".jpg",".jpeg",".png",".gif",".svg"].some(e=>t.endsWith(e))?"image":[".mp4",".ogg",".mov",".webm",".avi"].some(e=>t.endsWith(e))?"video":"document"}getSubtypeFromMime(e){return e.startsWith("image/")?"image":e.startsWith("video/")?"video":"document"}async handleRemoveItem(e){console.log("Handling remove upload");const t=e.closest(this.selectors.items.item);if(!t)return;const s=t.dataset.uploadId,i=t.dataset.id;if((s||i)&&confirm("Remove this item?")){if(s)await this.removeUpload(s);else{const s=this.getFieldIdFromElement(e);t.remove(),s&&(this.updateHiddenInput(s),this.maybeLockUploads(s))}this.a11y.announce("Item removed")}}updateHiddenInput(e){const t=this.fields.get(e);if(!t?.ui.hidden)return;const s=Array.from(t.ui.grid?.querySelectorAll(this.selectors.items.item)||[]).map(e=>Object.hasOwn(e.dataset,"id")&&e.dataset.id>0?e.dataset.id:Object.hasOwn(e.dataset,"upload-id")&&e.dataset.uploadId>0?e.dataset.uploadId:e.dataset.itemId).filter(Boolean).join(",");t.ui.hidden.value!==s&&(t.ui.hidden.value=s,t.ui.hidden.dispatchEvent(new Event("change",{bubbles:!0})))}async setBulkUpload(e,t,s){const i=Array.from(e).map(async e=>{if("string"==typeof e&&(e=await this.stores.uploads.get(e)),e)return"status"===t&&await this.setUploadStatus(e,s),e[t]=s,this.stores.uploads.save(e)});await Promise.all(i)}async setUploadStatus(e,t){"string"==typeof e&&(e=await this.stores.uploads.get(e)),e&&e.progress&&window.showProgress(e.progress,this.getStatusProgress(t),100,this.getStatusText(t),this.queue.icons[t]??"")}async removeUpload(e){let t=this.stores.uploads.get(e);if(!t)return;const s=t.field;if(t.group){let s=this.stores.groups.get(t.group);s.uploads=s.uploads.filter(t=>t!==e),0===s.uploads.length?await this.removeGroup(s.id,!1):await this.stores.groups.save(s)}await this.clearUpload(e),this.updateHiddenInput(s),this.maybeLockUploads(s);let i=this.selectionHandlers.get(s);i&&i.deselect(e),this.a11y.announce("Upload removed")}async clearUpload(e){const t=this.uploads.get(e);if(t&&(this.revokePreviewUrl(t.preview),t.element)){const e=t.element.dataset.previewUrl;this.revokePreviewUrl(e),t.element.remove()}this.uploads.delete(e),await this.stores.uploads.delete(e)}async handleAddToGroup(e){const t=this.selected.get(e);if(!t||0===t.size)return;let s=await this.createGroup(e);s&&(await Promise.all(Array.from(t).map(e=>this.addToGroup(e,s))),this.selectionHandlers.get(e)?.clearSelection(),this.a11y.announce(`Created group with ${t.size} items`))}async createGroup(e,t=null){let s=this.fields.get(e);if(!s)return;t||(t=window.generateID("group"));const i=this.createGroupElement(t,e);if(!i)return null;const a=s.groupUI.empty;a?.nextSibling?s.groupUI.grid.insertBefore(i,a.nextSibling):s.groupUI.grid.append(i);const r=i.querySelector(".item-grid");r&&(r.dataset.groupId=t,this.createSortable(e,r,t));let o=this.stores.groups.data.has(t)?this.stores.groups.data.get(t):{};return await this.setGroup(t,{...o,id:t,field:e}),t}createGroupElement(e,t=null){let s={groupId:e,fieldId:t},i=this.templates.create("imageGroup",s);return this.groups.set(e,{element:i,ui:window.uiFromSelectors(this.selectors.group,i)}),this.getSelectionHandler(t)?.addWrapper(i),i}async setGroup(e,t){const s={...{id:e,src:window.location.href,uploads:[],operationId:null,field:null,fields:{}},...t};Object.preventExtensions(s),await this.stores.groups.save(s)}async setBulkGroup(e,t,s){let i=this.stores.groups.filterByIndex({field:e});if(0===i.length)return;let a=i.map(e=>{e[t]=s,this.stores.groups.save(e)});await Promise.all(a)}async addToGroup(e,t=null){const s=this.stores.uploads.get(e),i=this.uploads.get(e);if(!s||!i)return;const a=this.fields.get(s.field);if(!a)return;if(null!==i.element?.parentElement&&(!t&&null===s.group||t===s.group))return void this.handleReorder(s.field,t);if(s.group){const t=this.stores.groups.get(s.group);t&&(t.uploads=t.uploads.filter(t=>t!==e),0===t.uploads.length?await this.removeGroup(t.id,!1):await this.stores.groups.save(t))}i.ui.checkbox&&(i.ui.checkbox.checked=!1);const r=this.selectionHandlers.get(s.field);if(r&&r.isSelected(e)&&r.deselect(e),this.selected.get(s.field)?.has(e)&&this.selected.get(s.field).delete(e),i.ui.featured&&(i.ui.featured.hidden=!t),t){i.ui.featured&&(i.ui.featured.name=`${t}_featured`);let a=this.stores.groups.get(t);a&&(a.uploads.push(e),s.group=t,await this.stores.groups.save(a))}else s.group=null;let o=t?this.groups.get(t)?.ui.grid:a.ui.grid;o&&(o.append(i.element),t&&this.handleReorder(s.field,t)),await this.stores.uploads.save(s)}handleDeleteGroup(e){const t=e.closest(this.selectors.group.item);if(!t)return;let s=t.dataset.groupId;if(!confirm("Delete this group? Items will be moved back to the upload area."))return;let i=this.stores.uploads.filterByIndex({group:s});Promise.all(i.map(e=>this.addToGroup(e.id,null))).then(()=>{this.removeGroup(s,!1).then(()=>{}),this.a11y.announce("Group deleted. Items returned to upload area")})}async removeGroup(e,t=!0){let s=this.groups.get(e),i=this.stores.groups.get(e);if(!i)return;let a=!0;t&&i.uploads.length>0&&(a=window.confirm("Keep uploads in this group?")),await Promise.all(i.uploads.map(e=>a?this.addToGroup(e,null):this.removeUpload(e)));if(this.fields.get(i.field)){const t=this.getGroupKey(i.field,e),a=this.selectionHandlers.get(t);if(a?.destroy&&a.destroy(),this.selectionHandlers.get(i.field)&&s&&s.element&&this.selectionHandlers.get(i.field).removeWrapper(s.element),this.sortables.has(t)){const e=this.sortables.get(t);e?.destroy&&e.destroy(),this.sortables.delete(t)}}s?.element&&s.element.remove(),this.groups.delete(e),await this.stores.groups.delete(e),this.a11y.announce("Group removed")}maybeLockUploads(e){let t=this.fields.get(e);if(!t||!t.ui.dropZone)return;let s=this.stores.uploads.filterByIndex({field:e}).length,i=t.config.maxFiles??0;t.ui.dropZone.hidden=i>0&&s>=i}async handleOperationCancelled(e){0!==e.length&&e.forEach(e=>{this.removeUpload(e)})}getGroupKey(e,t=null){return t?`${e}_${t}`:`${e}`}getSelectionHandler(e){let t=this.getGroupKey(e);if(!this.selectionHandlers.has(t)){let s=this.fields.get(e);if(!s)return;if("post_group"!==s.config.destination)return;let i=new window.jvbHandleSelection(s.element,{selectAll:{checkbox:this.selectors.fields.selectAll,count:this.selectors.fields.count,bulkControls:this.selectors.fields.actions},item:{item:this.selectors.items.item,checkbox:this.selectors.items.checkbox,idAttribute:"uploadId"},wrapper:{wrapper:".preview-wrap, .upload-group",id:"groupId"}});i.subscribe((t,s)=>{this.selected.set(e,s.selectedItems)}),this.selectionHandlers.set(t,i)}return this.selectionHandlers.get(t)}updateHandlerItems(e){let t=this.getSelectionHandler(e);t&&t.collectItems()}initSortable(e){if(!window.Sortable)return;const t=this.fields.get(e);t&&(!Sortable._multiDragMounted&&Sortable.MultiDrag&&(Sortable.mount(new Sortable.MultiDrag),Sortable._multiDragMounted=!0),this.createSortable(e,t.ui.grid,null),this.initEmptyGroupDropZone(e))}createSortable(e,t,s){if(!t)return null;const i=this.getGroupKey(e,s);if(this.sortables.has(i))return this.sortables.get(i);const a=new Sortable(t,{animation:150,draggable:".item",multiDrag:!0,selectedClass:"selected",avoidImplicitDeselect:!0,group:{name:e,pull:!0,put:!0},dragClass:"dragging",ignore:".empty-group",onStart:t=>{const s=t.item,i=s?.dataset.uploadId,a=this.selected.get(e);if(i&&(!a||!a.has(i))){const t=this.selectionHandlers.get(e);t&&t.select(i)}},onEnd:t=>this.sortableDrop(t,e)});return this.sortables.set(i,a),a}initEmptyGroupDropZone(e){const t=this.fields.get(e),s=t?.groupUI?.empty;s&&(s.addEventListener("dragover",e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="move",s.classList.add("drag-over")}),s.addEventListener("dragleave",e=>{s.contains(e.relatedTarget)||s.classList.remove("drag-over")}),s.addEventListener("drop",async t=>{t.preventDefault(),t.stopPropagation(),s.classList.remove("drag-over");const i=this.selected.get(e);if(!i||0===i.size)return;const a=await this.createGroup(e);a&&(await Promise.all(Array.from(i).map(e=>this.addToGroup(e,a))),this.selectionHandlers.get(e)?.clearSelection())}))}async sortableDrop(e,t){const s=e.to,i=(e.items?.length>0?Array.from(e.items):[e.item]).map(e=>e.dataset.uploadId).filter(Boolean);if(0===i.length)return;const a=s.dataset.groupId||null;for(const e of i)await this.addToGroup(e,a);this.handleReorder(t,a),this.selectionHandlers.get(t)?.clearSelection()}handleReorder(e,t=null){let s=t?this.groups.get(t)?.ui.grid:this.fields.get(e)?.ui.grid;if(s){if(t){let e=Array.from(s.children).filter(e=>e.matches(this.selectors.items.item)&&!e.classList.contains("ghost")).map(e=>e.dataset.uploadId).filter(e=>e),i=this.stores.groups.get(t);i&&(i.uploads=e,this.stores.groups.save(i).then(()=>{}))}else this.updateHiddenInput(e);this.a11y.announce("Items reordered")}else console.log("Couldn't Reorder items...")}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t={}){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("Subscriber error:",e)}})}destroy(){this.subscribers.clear(),this.previewUrls.forEach(e=>{this.revokePreviewUrl(e)}),this.previewUrls.clear()}cleanupAllPreviewUrls(){this.previewUrls.forEach(e=>this.revokePreviewUrl(e)),this.previewUrls.clear()}async handleClearCache(){const e=window.location.href,t=this.stores.uploads.filterByIndex({src:e}),s=this.stores.groups.filterByIndex({src:e});await Promise.all([...t.map(e=>this.clearUpload(e.id)),...s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id)))]),this.restoreModal&&this.cleanupRestore(),this.a11y.announce("Cache cleared for this page")}async getFilesForForm(e){const t=e.querySelectorAll(this.selectors.fields.field),s=[];for(const e of t){const t=this.determineFieldId(e),i=e.dataset.field,a=this.stores.uploads.filterByIndex({field:t});for(const e of a){const t=this.formatFile(e);t&&s.push({file:t,fieldName:i,uploadId:e.id,meta:e.fields||{}})}}return s}async clearFieldFromStores(e){const t=this.stores.uploads.filterByIndex({field:e}),s=this.stores.groups.filterByIndex({field:e});await Promise.all(t.map(e=>this.clearUpload(e.id))),await Promise.all(s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id))))}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbUploads=new e)})})})();
\ No newline at end of file
diff --git a/build/drawer-menu/index.php b/build/drawer-menu/index.php
deleted file mode 100644
index 6220032..0000000
--- a/build/drawer-menu/index.php
+++ /dev/null
@@ -1,2 +0,0 @@
-<?php
-// Silence is golden.
diff --git a/build/drawer-menu/render.php b/build/drawer-menu/render.php
index b3d9bbc..ea6680d 100644
--- a/build/drawer-menu/render.php
+++ b/build/drawer-menu/render.php
@@ -1 +1,5 @@
<?php
+
+if (!defined('ABSPATH')) {
+ exit; // Exit if accessed directly
+}
diff --git a/build/forms/render.php b/build/forms/render.php
index 9865bf3..96762d8 100644
--- a/build/forms/render.php
+++ b/build/forms/render.php
@@ -1,55 +1,5 @@
<?php
+
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
-/**
- * Form Block Render
- *
- * @package Edmonton_Ink
- */
-
-/**
- * Renders the form block on the frontend
- *
- * @param array $attributes The block attributes.
- * @param string $content The block content.
- * @param WP_Block $block The block instance.
- * @return string The rendered output.
- */
-function jvbRenderFormBlock(array $attributes, string $content, WP_Block $block):string
-{
- // Get form type from attributes
- $form_type = isset($attributes['formType']) ? $attributes['formType'] : '';
-
- // If no form type selected, return a message
- if (empty($form_type)) {
- return '<div class="jvb-form-error">No form type selected. Please edit this block and select a form type.</div>';
- }
-
- // Get other attributes
- $show_labels = isset($attributes['showLabels']) ? $attributes['showLabels'] : true;
- $custom_email_to = isset($attributes['customEmailTo']) ? $attributes['customEmailTo'] : '';
-
- // Set custom options for the form
- $form_options = array();
-
- // Set custom email recipient if provided
- if (!empty($custom_email_to)) {
- $form_options['email_to'] = $custom_email_to;
- }
-
- // Render the form with the specified options
- $form_output = JVB()->forms()->renderForm($form_type);
-
- // Get block classes
- $wrapper_attributes = get_block_wrapper_attributes([
- 'class' => 'jvb-forms'
- ]);
-
- // Return the wrapped form
- return sprintf(
- '<div %1$s>%2$s</div>',
- $wrapper_attributes,
- $form_output
- );
-}
diff --git a/build/glossary/render.php b/build/glossary/render.php
index 52aded4..44c57fb 100644
--- a/build/glossary/render.php
+++ b/build/glossary/render.php
@@ -1,4 +1,8 @@
<?php
+
+if (!defined('ABSPATH')) {
+ exit; // Exit if accessed directly
+}
/**
* @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
*/
diff --git a/build/gmbreviews/render.php b/build/gmbreviews/render.php
index b3d9bbc..ea6680d 100644
--- a/build/gmbreviews/render.php
+++ b/build/gmbreviews/render.php
@@ -1 +1,5 @@
<?php
+
+if (!defined('ABSPATH')) {
+ exit; // Exit if accessed directly
+}
diff --git a/build/menu/render.php b/build/menu/render.php
index 52aded4..44c57fb 100644
--- a/build/menu/render.php
+++ b/build/menu/render.php
@@ -1,4 +1,8 @@
<?php
+
+if (!defined('ABSPATH')) {
+ exit; // Exit if accessed directly
+}
/**
* @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
*/
diff --git a/build/menu/style-index-rtl.css b/build/menu/style-index-rtl.css
index 8007299..02f8bc1 100644
--- a/build/menu/style-index-rtl.css
+++ b/build/menu/style-index-rtl.css
@@ -1 +1 @@
-.info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity.quantity>label{right:var(--offScreen);position:absolute}nav.menu{max-width:100%}nav.menu ul{--justify:flex-start}nav.menu ul li{padding:0 .5rem}main>header{grid-column:full}main>header h1{max-width:var(--wide)}main>section *{max-width:var(--wide)!important}.menu-item.menu-item{margin:var(--sp2);max-width:none}.menu-item.menu-item+.menu-item{border-top:1px solid rgb(var(--base-200));padding-top:var(--sp4)}.menu-item.menu-item .header{flex-wrap:nowrap}.menu-item.menu-item .header h3,.menu-item.menu-item .header p{margin:0;width:-moz-max-content;width:max-content}
+.info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity.quantity>label{right:var(--offScreen);position:absolute}nav.menu{max-width:100%}nav.menu ul{--justify:flex-start}nav.menu ul li{padding:0 .5rem}main>header{grid-column:full}main>header h1{max-width:var(--wide)}main>section *{max-width:var(--wide)!important}.menu-item.menu-item{margin:var(--sp2);max-width:none}.menu-item.menu-item:target{background:transparent;border:1rem solid rgb(var(--action-0));outline:none}.menu-item.menu-item:target h3{color:rgb(var(--action-0))}.menu-item.menu-item+.menu-item{border-top:1px solid rgb(var(--base-200));padding-top:var(--sp4)}.menu-item.menu-item .header{flex-wrap:nowrap}.menu-item.menu-item .header figure{aspect-ratio:1;max-width:30vw!important;-o-object-fit:cover;object-fit:cover}.menu-item.menu-item .header h3,.menu-item.menu-item .header p{margin:0;width:-moz-max-content;width:max-content}.menu-item.menu-item .gallery figure{aspect-ratio:1;max-width:15vw!important;-o-object-fit:cover;object-fit:cover}.menu-item.menu-item figcaption{display:none}
diff --git a/build/menu/style-index.css b/build/menu/style-index.css
index 531238d..208619a 100644
--- a/build/menu/style-index.css
+++ b/build/menu/style-index.css
@@ -1 +1 @@
-.info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity.quantity>label{left:var(--offScreen);position:absolute}nav.menu{max-width:100%}nav.menu ul{--justify:flex-start}nav.menu ul li{padding:0 .5rem}main>header{grid-column:full}main>header h1{max-width:var(--wide)}main>section *{max-width:var(--wide)!important}.menu-item.menu-item{margin:var(--sp2);max-width:none}.menu-item.menu-item+.menu-item{border-top:1px solid rgb(var(--base-200));padding-top:var(--sp4)}.menu-item.menu-item .header{flex-wrap:nowrap}.menu-item.menu-item .header h3,.menu-item.menu-item .header p{margin:0;width:-moz-max-content;width:max-content}
+.info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity.quantity>label{left:var(--offScreen);position:absolute}nav.menu{max-width:100%}nav.menu ul{--justify:flex-start}nav.menu ul li{padding:0 .5rem}main>header{grid-column:full}main>header h1{max-width:var(--wide)}main>section *{max-width:var(--wide)!important}.menu-item.menu-item{margin:var(--sp2);max-width:none}.menu-item.menu-item:target{background:transparent;border:1rem solid rgb(var(--action-0));outline:none}.menu-item.menu-item:target h3{color:rgb(var(--action-0))}.menu-item.menu-item+.menu-item{border-top:1px solid rgb(var(--base-200));padding-top:var(--sp4)}.menu-item.menu-item .header{flex-wrap:nowrap}.menu-item.menu-item .header figure{aspect-ratio:1;max-width:30vw!important;-o-object-fit:cover;object-fit:cover}.menu-item.menu-item .header h3,.menu-item.menu-item .header p{margin:0;width:-moz-max-content;width:max-content}.menu-item.menu-item .gallery figure{aspect-ratio:1;max-width:15vw!important;-o-object-fit:cover;object-fit:cover}.menu-item.menu-item figcaption{display:none}
diff --git a/build/simple-calendar/render.php b/build/simple-calendar/render.php
index b3d9bbc..ea6680d 100644
--- a/build/simple-calendar/render.php
+++ b/build/simple-calendar/render.php
@@ -1 +1,5 @@
<?php
+
+if (!defined('ABSPATH')) {
+ exit; // Exit if accessed directly
+}
diff --git a/build/summary/render.php b/build/summary/render.php
index b5645eb..3be4bb3 100644
--- a/build/summary/render.php
+++ b/build/summary/render.php
@@ -19,12 +19,10 @@
// Buffer output
if (is_tax()) {
- switch (get_queried_object()->taxonomy) {
- case BASE.'shop':
- return jvbRenderShopSummary();
- default:
- return jvbRenderTermSummary();
- }
+ return match (get_queried_object()->taxonomy) {
+ BASE . 'shop' => jvbRenderShopSummary(),
+ default => jvbRenderTermSummary(),
+ };
} elseif (is_singular()) {
return jvbRenderArtistSummary();
}
@@ -152,7 +150,7 @@
<h2>Contact <?=$artist['name']?></h2>
<?php
echo jvbRenderContactInfo($current->ID, 'post');
- echo jvbRenderLinks($current->ID, 'post');
+ echo jvbRenderLinks($current->ID);
?>
</section>
<?php
@@ -165,7 +163,7 @@
{
$current = get_queried_object();
- $cache = Cache::for('shop_bio', WEEK_IN_SECONDS)->connect('taxonomy');
+ $cache = Cache::for('shop_bio', 'term',WEEK_IN_SECONDS);
$key = $current->term_id;
$cached = $cache->get($key);
if ($cached !== false) {
@@ -285,7 +283,7 @@
function jvbRenderTermSummary()
{
$current = get_queried_object();
- $cache = Cache::for('term_summary', WEEK_IN_SECONDS)->connect('taxonomy');
+ $cache = Cache::for('term_summary', 'term',WEEK_IN_SECONDS);
$key = $current->ID;
$cached = $cache->get($key);
if ($cached !== false) {
@@ -294,16 +292,11 @@
ob_start();
$tax = jvbNoBase($current->taxonomy);
- switch ($tax) {
- case 'style':
- $title = 'Tattoo Artists';
- break;
- case 'theme':
- $title = 'Tattoos';
- break;
- default:
- $title = '';
- }
+ $title = match ($tax) {
+ 'style' => 'Tattoo Artists',
+ 'theme' => 'Tattoos',
+ default => '',
+ };
$meta = Meta::forTerm($current->ID);
$fields = $meta->getAll();
diff --git a/inc/blocks/CustomBlocks.php b/inc/blocks/CustomBlocks.php
index 123b707..2443f94 100644
--- a/inc/blocks/CustomBlocks.php
+++ b/inc/blocks/CustomBlocks.php
@@ -181,13 +181,12 @@
wp_enqueue_style('jvb-form');
break;
case 'jvb/timeline':
+ case 'jvb/menu':
wp_enqueue_script('jvb-gallery');
add_action('wp_footer', 'jvbRenderGallery');
break;
}
-
-
return $this->checkMethods($content, $block);
}
diff --git a/inc/blocks/MenuBlock.php b/inc/blocks/MenuBlock.php
index 66edde2..264e41e 100644
--- a/inc/blocks/MenuBlock.php
+++ b/inc/blocks/MenuBlock.php
@@ -32,7 +32,6 @@
public function __construct()
{
$this->cache = Cache::for('menu', 'post', WEEK_IN_SECONDS);
- $this->cache->flush();
if (Site::hasIntegration('square')) {
$this->base = '_square_';
} else if (Site::hasIntegration('helcim')) {
@@ -40,6 +39,7 @@
}
add_action('init', [ $this, 'registerBlock' ]);
add_action('save_post', [$this, 'flushCache'], 10, 2);
+ add_action('wp_footer', [$this, 'renderGallery']);
}
public function flushCache(int $postID, WP_Post $post):void
@@ -212,7 +212,7 @@
$excerpt = get_the_excerpt($ID);
$isVariable = !empty($fields['variations']??[]);
- $priceRange = $fields['price'];
+ $priceRange = jvbCurrency($fields['price']);
if ($isVariable) {
$prices = array_map('floatval', array_column($fields['variations'], 'price'));
$min = min($prices);
@@ -229,16 +229,40 @@
// jvbDump($product->get_image_id(), 'product image');
// jvbDump($product->get_gallery_image_ids(), 'Gallery images');
if ($fields['post_title'] === 'Breakfast Poutine') {
- jvbDump($ID);
- jvbDump($fields);
+// jvbDump($ID);
+// jvbDump($fields);
}
+ $gallery = '';
+ if (!empty($fields['gallery_image_ids'])) {
+ $gallery = implode(array_map(function($id) use ($fields){
+ $id = absint($id);
+ $caption = wp_get_attachment_caption($id);
+ return sprintf(
+ '<figure>%s<figcaption>%s</figcaption></figure>',
+ jvbFormatImage($id,'tiny','medium',true,'menu'),
+ empty($caption) ? $fields['post_title'] : $caption
+ );
+ }, $fields['gallery_image_ids']));
+ }
+
+ $caption = '';
+ if (!empty($fields['image_id'])) {
+ $caption = wp_get_attachment_caption((int)$fields['image_id']);
+ $caption = empty($caption) ? $fields['post_title'] : $caption;
+ }
+
+
$out = sprintf(
'<div id="%s" class="menu-item%s" data-section="%s" data-label="%s" data-id="%s">
<div class="header row x-btw">
- <h3>%s</h3>
- <p class="price">%s</p>
+ <figure>%s<figcaption>%s</figcaption></figure>
+ <div class="text row x-btw">
+ <h3>%s</h3>
+ <p class="price">%s</p>
+ </div>
</div>
+ <div class="gallery row%s">%s</div>
<div class="description">%s</div>
<div class="info col right">',
sanitize_title($fields['post_title']),
@@ -246,8 +270,12 @@
$slug,
$fields['post_title'],
$ID,
+ !empty($fields['image_id']) ? jvbFormatImage((int)$fields['image_id'],'tiny','medium',true,'menu') : '',
+ $caption,
$fields['post_title'],
$priceRange,
+ empty($gallery) ? '' : ' has',
+ $gallery,
$excerpt
);
@@ -465,4 +493,9 @@
}
echo jvbOnThisPage(array_keys($this->details));
}
+
+ public function renderGallery():void
+ {
+ jvbRenderGallery();
+ }
}
diff --git a/inc/helpers/media.php b/inc/helpers/media.php
index 6edb4f2..499972d 100644
--- a/inc/helpers/media.php
+++ b/inc/helpers/media.php
@@ -39,7 +39,6 @@
jvbIcon('caret-left'),
jvbIcon('caret-right')
);
-
if ($output) {
echo $gallery;
}
diff --git a/inc/managers/IconsManager.php b/inc/managers/IconsManager.php
index bc56566..a3bc9ff 100644
--- a/inc/managers/IconsManager.php
+++ b/inc/managers/IconsManager.php
@@ -487,41 +487,6 @@
}
}
- protected function regenerateCSS(array $sourcesToUpdate = []): void
- {
- error_log('[IconsManager]:regenerateCSS');
- $css_dir = JVB_CHILD_DIR.'/assets/css/';
-
- if (!file_exists($css_dir)) {
- wp_mkdir_p($css_dir);
- }
-
- // If no specific sources provided, regenerate all
- if (empty($sourcesToUpdate)) {
- $sourcesToUpdate = array_fill_keys(array_keys(self::$instances), true);
- }
-
- // Generate CSS only for sources that need it
- foreach (self::$instances as $source => $instance) {
- if (!isset($sourcesToUpdate[$source])) {
- continue; // Skip this source
- }
-
- $css = $instance->generateIconCSS();
- $css_path = $css_dir . $source . '.css';
-
- // Archive current version before overwriting
- $instance->archiveCurrentVersion($css);
-
- if (file_put_contents($css_path, $css) !== false) {
- Cache::for('icons_'.$source)->flush();
- error_log("[IconsManager] Updated {$source}.css");
- } else {
- error_log("[IconsManager] Could not write {$source}.css");
- }
- }
- }
-
/**
* Prevent cloning
*/
@@ -529,6 +494,7 @@
/**
* Prevent unserialization
+ * @throws Exception
*/
public function __wakeup()
{
diff --git a/inc/meta/Meta.php b/inc/meta/Meta.php
index 0482cbc..c0454a6 100644
--- a/inc/meta/Meta.php
+++ b/inc/meta/Meta.php
@@ -598,6 +598,19 @@
return jvbCheckBase($name);
}
+ /**
+ * Get item ID
+ */
+ public function id(): int|string|null
+ {
+ return $this->ID;
+ }
+
+ public function config(string $name):?array
+ {
+ return $this->getFieldConfig($name);
+ }
+
protected function log(string $message, array $context = []):void
{
$caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'] ?? '';
diff --git a/jvb.php b/jvb.php
index ec283ed..1c7c522 100644
--- a/jvb.php
+++ b/jvb.php
@@ -53,57 +53,57 @@
/**
* Track REST API errors by wrapping request execution
*/
-add_filter('rest_pre_dispatch', function($result, $server, $request) {
- // Store request details globally for error logging
- $GLOBALS['jvb_rest_request'] = [
- 'route' => $request->get_route(),
- 'method' => $request->get_method(),
- 'params' => $request->get_params(),
- 'time' => microtime(true),
- ];
-
- // Set up error handler for this request
- set_error_handler(function($errno, $errstr, $errfile, $errline) {
- if (!(error_reporting() & $errno)) {
- return false;
- }
-
- $context = array_merge($GLOBALS['jvb_rest_request'] ?? [], [
- 'error_type' => $errno,
- 'error_message' => $errstr,
- 'file' => $errfile,
- 'line' => $errline,
- 'user_id' => get_current_user_id(),
- ]);
-
- error_log('JVB REST Error: ' . json_encode($context));
-
- return false; // Let PHP handle it normally too
- });
-
- return $result;
-}, 10, 3);
-
-add_filter('rest_post_dispatch', function($result, $server, $request) {
- // Restore default error handler
- restore_error_handler();
-
- // Check for fatal errors that occurred during request
- $error = error_get_last();
- if ($error && in_array($error['type'], [E_ERROR, E_USER_ERROR, E_PARSE, E_COMPILE_ERROR])) {
- $context = array_merge($GLOBALS['jvb_rest_request'] ?? [], [
- 'fatal_error' => $error,
- 'user_id' => get_current_user_id(),
- ]);
-
- error_log('JVB REST Fatal: ' . json_encode($context));
- }
-
- // Clean up
- unset($GLOBALS['jvb_rest_request']);
-
- return $result;
-}, 10, 3);
+//add_filter('rest_pre_dispatch', function($result, $server, $request) {
+// // Store request details globally for error logging
+// $GLOBALS['jvb_rest_request'] = [
+// 'route' => $request->get_route(),
+// 'method' => $request->get_method(),
+// 'params' => $request->get_params(),
+// 'time' => microtime(true),
+// ];
+//
+// // Set up error handler for this request
+// set_error_handler(function($errno, $errstr, $errfile, $errline) {
+// if (!(error_reporting() & $errno)) {
+// return false;
+// }
+//
+// $context = array_merge($GLOBALS['jvb_rest_request'] ?? [], [
+// 'error_type' => $errno,
+// 'error_message' => $errstr,
+// 'file' => $errfile,
+// 'line' => $errline,
+// 'user_id' => get_current_user_id(),
+// ]);
+//
+// error_log('JVB REST Error: ' . json_encode($context));
+//
+// return false; // Let PHP handle it normally too
+// });
+//
+// return $result;
+//}, 10, 3);
+//
+//add_filter('rest_post_dispatch', function($result, $server, $request) {
+// // Restore default error handler
+// restore_error_handler();
+//
+// // Check for fatal errors that occurred during request
+// $error = error_get_last();
+// if ($error && in_array($error['type'], [E_ERROR, E_USER_ERROR, E_PARSE, E_COMPILE_ERROR])) {
+// $context = array_merge($GLOBALS['jvb_rest_request'] ?? [], [
+// 'fatal_error' => $error,
+// 'user_id' => get_current_user_id(),
+// ]);
+//
+// error_log('JVB REST Fatal: ' . json_encode($context));
+// }
+//
+// // Clean up
+// unset($GLOBALS['jvb_rest_request']);
+//
+// return $result;
+//}, 10, 3);
@@ -117,7 +117,7 @@
//const JVB_TESTING = false;
//if (!JVB_TESTING) {
- add_filter('show_admin_bar', '__return_false');
+// add_filter('show_admin_bar', '__return_false');
//}
@@ -247,7 +247,7 @@
/**
* Utilities
*/
-require(JVB_DIR . '/cleanup.php');
+//require(JVB_DIR . '/cleanup.php');
require(JVB_DIR . '/inc/admin/_setup.php');
require(JVB_DIR . '/JVBase.php');
diff --git a/src/menu/style.scss b/src/menu/style.scss
index f2858ff..f54b2a5 100644
--- a/src/menu/style.scss
+++ b/src/menu/style.scss
@@ -34,6 +34,14 @@
.menu-item.menu-item {
margin: var(--sp2);
max-width: none;
+ &:target {
+ outline: none;
+ background: transparent;
+ border: 1rem solid rgb(var(--action-0));
+ h3 {
+ color: rgb(var(--action-0));
+ }
+ }
+.menu-item {
padding-top: var(--sp4);
border-top: 1px solid rgb(var(--base-200));
@@ -41,10 +49,23 @@
.header {
flex-wrap:nowrap;
-
+ figure {
+ max-width: 30vw!important;
+ aspect-ratio: 1;
+ object-fit: cover;
+ }
h3, p {
margin: 0;
width: max-content;
}
}
+
+ .gallery figure {
+ max-width: 15vw!important;
+ aspect-ratio: 1;
+ object-fit: cover;
+ }
+ figcaption {
+ display: none;
+ }
}
--
Gitblit v1.10.0