Jake Vanderwerf
5 hours ago 56a9a1ccf764ff7a6af8f8a2292cb07443cb4aa7
=New Gitbit setpu
1 files added
52 files modified
2976 ■■■■ changed files
assets/css/dash.min.css 2 ●●● patch | view | raw | blame | history
assets/css/forms.min.css 2 ●●● patch | view | raw | blame | history
assets/css/nav.min.css 2 ●●● patch | view | raw | blame | history
assets/js/concise/CRUD.js 2 ●●● patch | view | raw | blame | history
assets/js/concise/DataStore.js 18 ●●●● patch | view | raw | blame | history
assets/js/concise/FormController.js 52 ●●●●● patch | view | raw | blame | history
assets/js/concise/Queue.js 2 ●●● patch | view | raw | blame | history
assets/js/concise/TaxonomySelector.js 28 ●●●● patch | view | raw | blame | history
assets/js/min/crud.min.js 2 ●●● patch | view | raw | blame | history
assets/js/min/dataStore.min.js 2 ●●● patch | view | raw | blame | history
assets/js/min/form.min.js 2 ●●● patch | view | raw | blame | history
assets/js/min/queue.min.js 2 ●●● patch | view | raw | blame | history
assets/js/min/selector.min.js 2 ●●● patch | view | raw | blame | history
base/SchemaHelper.php 4 ●●●● patch | view | raw | blame | history
build/drawer-menu/render.php 3 ●●●●● patch | view | raw | blame | history
build/drawer-menu/style-index-rtl.css 2 ●●● patch | view | raw | blame | history
build/drawer-menu/style-index.css 2 ●●● patch | view | raw | blame | history
build/feed/style-index-rtl.css 2 ●●● patch | view | raw | blame | history
build/feed/style-index.css 2 ●●● patch | view | raw | blame | history
build/feed/view.asset.php 2 ●●● patch | view | raw | blame | history
build/feed/view.js 2 ●●● patch | view | raw | blame | history
build/summary/style-index-rtl.css 2 ●●● patch | view | raw | blame | history
build/summary/style-index.css 2 ●●● patch | view | raw | blame | history
inc/blocks/CustomBlocks.php 30 ●●●●● patch | view | raw | blame | history
inc/blocks/FeedBlock.php 932 ●●●●● patch | view | raw | blame | history
inc/blocks/FeedBlockOld.php 507 ●●●●● patch | view | raw | blame | history
inc/blocks/SummaryBlock.php 4 ●●●● patch | view | raw | blame | history
inc/forms/TaxonomySelector.php 13 ●●●●● patch | view | raw | blame | history
inc/helpers/media.php 24 ●●●●● patch | view | raw | blame | history
inc/helpers/terms.php 2 ●●● patch | view | raw | blame | history
inc/helpers/ui.php 8 ●●●● patch | view | raw | blame | history
inc/managers/CRUDManager.php 4 ●●●● patch | view | raw | blame | history
inc/managers/DashboardManager.php 12 ●●●●● patch | view | raw | blame | history
inc/managers/DirectoryManager.php 5 ●●●●● patch | view | raw | blame | history
inc/managers/SEO/BreadcrumbManager.php 8 ●●●● patch | view | raw | blame | history
inc/managers/SEO/render/SchemaOutput.php 7 ●●●● patch | view | raw | blame | history
inc/managers/SEO/render/Traits/ThingSchema.php 2 ●●● patch | view | raw | blame | history
inc/managers/ScriptLoader.php 2 ●●● patch | view | raw | blame | history
inc/meta/Form.php 6 ●●●● patch | view | raw | blame | history
inc/meta/Meta.php 24 ●●●●● patch | view | raw | blame | history
inc/registrar/Registrar.php 124 ●●●● patch | view | raw | blame | history
inc/registrar/config/seo/Resolver.php 33 ●●●●● patch | view | raw | blame | history
inc/registrar/config/seo/Schema.php 20 ●●●●● patch | view | raw | blame | history
inc/registrar/fields/Field.php 19 ●●●●● patch | view | raw | blame | history
inc/rest/routes/NotificationsRoutes.php 2 ●●● patch | view | raw | blame | history
inc/ui/CRUDSkeleton.php 81 ●●●● patch | view | raw | blame | history
inc/utility/Image.php 26 ●●●●● patch | view | raw | blame | history
jvb.php 1 ●●●● patch | view | raw | blame | history
src/drawer-menu/render.php 3 ●●●●● patch | view | raw | blame | history
src/drawer-menu/style.scss 4 ●●●● patch | view | raw | blame | history
src/feed/style.scss 916 ●●●●● patch | view | raw | blame | history
src/feed/view.js 10 ●●●● patch | view | raw | blame | history
src/summary/style.scss 6 ●●●●● patch | view | raw | blame | history
assets/css/dash.min.css
@@ -1 +1 @@
header>a{margin:0 auto}nav.sidebar{position:fixed;top:var(--btn);bottom:0;left:0;z-index:var(--z-4);background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);width:var(--btn);transition:var(--trans-size);overflow:hidden auto;height:100%;padding-bottom:var(--btn)}nav.sidebar>ul{--gap:0}nav.sidebar .toggle.main.main{padding:0;position:fixed;left:unset;bottom:0;right:0;z-index:var(--z-8);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);width:var(--btn);height:var(--btn)}nav.sidebar .toggle.main .icon{--w:1.2em}nav.sidebar .toggle:not(.main){display:none}nav.sidebar.open .toggle:not(.main){display:inline-flex}nav.sidebar .icon{--w:var(--chip_);width:var(--btn);min-width:var(--chip);transition:var(--trans-size),transform var(--trans-base)}nav.sidebar.open .icon{--w:var(--chip);width:var(--w)}nav.sidebar.open{width:fit-content;max-width:100%}nav.sidebar ul{height:max-content;width:100%;--gap:0}nav.sidebar .toggle{width:var(--btn);height:var(--chipchip);box-shadow:none;background-color:transparent;min-height:0}nav.sidebar .toggle:focus,nav.sidebar .toggle:hover{background-color:rgb(var(--action-0));color:var(--action-contrast)}nav.sidebar .title{max-width:0;opacity:0;transition:max-width var(--trans-base)}nav.sidebar.open .title{white-space:nowrap;max-width:max-content;opacity:1}nav.sidebar .icon{min-width:var(--chip_)}nav.sidebar li{--justify:center;--wrap:nowrap;--align:flex-start;overflow:hidden}nav.sidebar.open li>div{width:100%;padding-right:var(--btn)}nav.sidebar .has-submenu{height:max-content}nav.sidebar li ul{width:0}nav.sidebar li.open ul{width:max-content}nav.sidebar.open li.has-submenu>div{padding-right:0}nav.sidebar.open li.has-submenu>ul{padding-left:var(--chip)}nav.sidebar .a{color:rgb(var(--contrast-200))}nav.sidebar .a,nav.sidebar a{height:var(--chipchip);display:flex;justify-content:center;align-items:center;transition:none;padding-left:0}nav.sidebar.open .a,nav.sidebar.open a{width:100%;justify-content:flex-start}nav.sidebar .has-submenu ul{max-height:0;height:0;overflow:hidden;transition:var(--trans-size)}nav.sidebar .has-submenu.open>ul{height:100%;max-height:fit-content}aside.main.main#queue{bottom:0}button.qtoggle.sticky{bottom:0;left:0}section.replace{margin:0 var(--btn_) 0 calc(var(--btn) + var(--chipchip))}section.replace>*{max-width:100%}.dashboard :is(h1,h2,h3,h4,h5,h6){text-transform:none;margin:.5em 0 1em}ul.dashboard{margin:0;padding:0;display:flex;justify-content:flex-start;gap:.5rem;flex-wrap:wrap}ul.dashboard li{list-style:none}ul.dashboard p{margin:0}.dashboard .main-actions,.dashboard main>footer{position:absolute;left:var(--offScreen);width:0;padding:0}.all-filters{margin:1rem 0;padding:1rem 0;border:1px solid rgb(var(--base-100));border-width:1px 0;--gap:0;width:100%}.all-filters summary{width:100%}.all-filters summary:hover{color:rgb(var(--action-0))}.all-filters .row{--justify:flex-start}.all-filters+[data-action=clear-filters]{--w:1em;width:max-content;font-size:var(--txt-x-small);min-height:var(--chip);margin-left:auto;display:block;position:relative;top:-1rem}.all-filters [data-action=refresh]{margin-left:auto;--w:1em!important;flex-wrap:nowrap;width:max-content;justify-content:flex-start;transition:var(--trans-size);min-height:var(--chip);font-size:var(--txt-x-small)}.all-filters [data-action=refresh]:focus,.all-filters [data-action=refresh]:hover{width:max-content}.all-filters [data-action=refresh] span{max-width:0;transition:var(--trans-size);overflow:hidden;white-space:nowrap}.all-filters [data-action=refresh]:focus span,.all-filters [data-action=refresh]:hover span{max-width:max-content}.all-filters .btn+label{box-shadow:var(--shdw-none);color:rgb(var(--contrast-200))}.all-filters .radio-options input:not(.ch):checked+label{box-shadow:rgba(var(--base),var(--op-6)) var(--shdw-inset);color:rgb(var(--contrast-50));border-color:rgb(var(--contrast-50))}.all-filters .filters{width:100%}.controls .radio-options,.filters.row.start{--align:center;--justify:flex-start;--gap:.5rem}.all-filters span.label{text-transform:uppercase;font-size:var(--txt-small);font-weight:900;width:15vw;display:inline-flex;align-items:center;padding-right:2rem}@media (max-width:767px){.all-filters>.row{padding:.5rem 0}.all-filters span.label{padding-top:.5rem;width:100%;border-top:1px solid rgb(var(--base-200))}}.controls .icon{--w:1.4rem}.all-filters .btn+label,.all-filters button{height:var(--chip_);padding:.125rem!important;min-width:0;min-height:var(--chip_);width:var(--chip_)}.all-filters>.row{padding:.25rem 0}.all-filters .btn+label:focus,.all-filters .btn+label:hover,.all-filters button:focus,.all-filters button:hover{background-color:transparent;color:rgb(var(--action-0));border-color:rgb(var(--action-0))}.search-container:not(.open) .clear-search,.search-container:not(.open) input[type=search]{transform:scaleX(0);transform-origin:left;width:0;padding:0;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.search-container button{padding:.5rem}.search-container .icon{--w:1.5rem}.search-container.open .clear-search,.search-container.open input[type=search]{transform:scaleX(1);transform-origin:left;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.all-filters>.search,.search-container,input[type=search]{width:100%}nav.tabs{position:fixed;z-index:var(--z-4);width:calc(100% - var(--btn));top:var(--btn);left:var(--btn);background-color:rgba(var(--base),var(--op-45));box-shadow:rgba(var(--base),var(--op-6)) var(--shdw-down)}.replace:has(nav.tabs){margin-top:var(--btn)}
.replace{margin-left:var(--btn_)!important}.dashboard aside.main.left{bottom:0}.dashboard .qtoggle{left:0;bottom:0;margin:0!important}nav.sidebar{--wrap:nowrap;--align:flex-start;position:fixed;bottom:0;top:var(--btn);z-index:var(--z-4);height:calc(100% - var(--btn));background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);width:var(--btn);transition:var(--trans-size);overflow:hidden auto;margin-left:0!important;padding-bottom:var(--btn)}nav.sidebar.left{left:0}nav.sidebar.right{right:0}nav.sidebar .icon{--w:var(--chip_);width:var(--w);transition:var(--trans-size)}nav.sidebar.open{width:fit-content;max-width:85vw}nav.sidebar.open .icon{--w:var(--chip)}nav.sidebar ul{height:max-content;width:100%;--gap:0;--dir:column}nav.sidebar li{--justify:center;--wrap:nowrap;--align:flex-start;overflow:hidden;height:max-content}nav.sidebar ul ul{max-height:0;overflow:hidden;transform:scaleY(0);transform-origin:top;transition:var(--trans-base)}nav.sidebar li.open>ul{max-height:max-content;transform:scaleY(1)}nav.sidebar.open li>div{width:100%;padding-right:var(--btn)}nav.sidebar.open li.has-submenu>div{padding-right:0}nav.sidebar.open li.has-submenu>ul{padding-left:var(--chip)}nav.sidebar .title{display:none}nav.sidebar.open .title{display:block;white-space:nowrap}nav.sidebar a{--justify:flex-start}nav.sidebar .a{gap:.5rem;display:flex;width:100%;justify-content:flex-start;align-items:center;min-height:var(--btn);padding:var(--padding)}nav.sidebar .toggle:not(.main){display:none;width:var(--btn);height:var(--chipchip)}nav.sidebar.open .toggle{display:flex}nav.sidebar .toggle.main{position:fixed;left:unset;bottom:0;right:0;width:var(--btn);height:var(--btn);z-index:var(--z-8);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.all-filters{font-size:var(--txt-x-small)}.all-filters[open]{border:2px solid rgb(var(--action-0));padding:0;border-radius:0 0 var(--radius-outer) var(--radius-outer)}.all-filters summary:hover,.all-filters[open] summary{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.all-filters summary:hover::after{background-color:rgb(var(--action-contrast))}.all-filters summary{width:100%}.all-filters>.row.row{padding:0 .75rem;width:var(--content);position:relative}.all-filters>.row>.label,.all-filters>.row>.row>.label{font-family:var(--heading);font-weight:var(--fw-h-bold);text-transform:uppercase}.all-filters>.row>.label{width:20%}.all-filters>.row>.row>.label{white-space:nowrap}.all-filters .btn+label,.all-filters button{width:var(--chipchip);min-height:var(--chipchip);padding:0}.all-filters .btn+label,.all-filters button{position:unset}.all-filters .row:has(>.btn:not(:checked)+label:hover) :checked+label .label,.all-filters button .label,.btn+label .label{position:absolute;top:2rem;left:1rem;width:max-content;white-space:nowrap;opacity:0;z-index:var(--z-4)}.all-filters .radio-options.order>.row{position:relative;padding-bottom:2rem}.all-filters .radio-options .row .btn+label .label{bottom:0;top:unset;left:0;height:max-content}.all-filters button:hover .label,.btn+label:hover .label,.btn:checked+label .label{opacity:1}.all-filters .radio-options.order>.row{max-width:49%}.all-filters .radio-options.order{margin-top:1rem}.all-filters .filters select,.all-filters .filters>.row{width:max-content}.search-container:not(.open) .clear-search,.search-container:not(.open) input[type=search]{transform:scaleX(0);transform-origin:left;width:0;padding:0;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.search-container button{padding:.5rem}.search-container .icon{--w:1.5rem}.search-container.open .clear-search,.search-container.open input[type=search]{transform:scaleX(1);transform-origin:left;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.all-filters>.search,.search-container,input[type=search]{width:100%}section.main-actions.main-actions{padding:0;position:absolute;left:var(--offScreen)}.dashboard main>footer{padding:0;margin:0;position:absolute;left:var(--offScreen)}.item .select-item-label{width:100%;height:100%;aspect-ratio:1}
assets/css/forms.min.css
@@ -1 +1 @@
.field{width:100%;margin:.5em 0;padding:.5em 0}.field+.field{border-top:1px solid var(--base-200)}.field .wrapper{width:100%;position:relative}.field .validation{flex-shrink:0;max-width:0;transition:var(--trans-size)}.field.has-error .validation.error,.field.has-success .validation.success{max-width:var(--btn)}.field.has-error .error{color:var(--error)}.field.has-error input,.field.has-error select,.field.has-error textarea{border-color:var(--error);background-color:var(--errorBack)}.field.has-error input:focus,.field.has-error select:focus,.field.has-error textarea:focus{outline-color:var(--error);box-shadow:rgba(var(--error-rgb),.2)}.field.has-success .success{color:var(--success)}.validation-message{color:var(--errorText);font-size:var(--txt-small);margin-top:.25rem;display:block}.field[data-field=post_status] .wrapper{--justify:flex-start}.field[data-field=post_status] .btn+label{width:var(--chipchip);min-height:var(--chipchip);padding:0}.field[data-field=post_status] .btn+label:hover,.field[data-field=post_status] .btn:focus+label{color:var(--action-contrast)}.date-wrapper{position:relative;display:inline-block}input[type=date]{padding:8px 36px 8px 8px;border-radius:4px}input[type=date]::-webkit-calendar-picker-indicator{opacity:0;width:100%;height:100%;position:absolute;top:0;left:0;cursor:pointer}input[type=date]+.icon{--w:20px;position:absolute;right:10px;top:50%;transform:translateY(-50%);pointer-events:none}input:is([type=time],[type=datetime-local],[type=date]){padding:.5rem;border:1px solid var(--contrast-200);border-radius:4px;font-size:14px;min-width:180px;background:var(--base);color:var(--contrast);cursor:pointer}.date-wrapper input[type=date]:focus,.datetime-wrapper input[type=datetime-local]:focus,.time-wrapper input[type=time]:focus,.wrapper input:is([type=time],[type=datetime-local],[type=date]):focus{border-color:var(--action-0);box-shadow:0 0 0 2px rgba(var(--action-0),.1)}.date-wrapper .icon,.datetime-wrapper .icon,.time-wrapper .icon,.wrapper .icon{width:18px;height:18px;background-color:var(--contrast);opacity:.7}.quantity{margin:0;display:inline-flex;width:fit-content;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;position:relative}.quantity:focus-within{border-color:var(--action-0)}.quantity label{margin:0;font-size:var(--txt-small)}.quantity button{background:var(--base);padding:0;width:var(--chip_);height:var(--chip_);min-height:0;z-index:0;position:relative;border:1px solid var(--base-200);color:var(--contrast-200)}.quantity button:hover:not(:disabled){color:var(--action-0);border-color:var(--action-0);background-color:var(--base)}.quantity button:active:not(:disabled){background-color:var(--action-0);color:var(--light-0);transform:scale(.95)}.quantity button:disabled{opacity:.5;cursor:not-allowed}.quantity input[type=number]{z-index:1;border:1px solid var(--base-200);background:var(--base);text-align:center;font-size:1.1rem;width:60px;height:48px;margin:0;padding:0!important;appearance:textfield}.quantity input[type=number]::-webkit-inner-spin-button,.quantity input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.quantity input[type=number]:focus{background-color:var(--base-50)}.quantity button.increase{left:-2px;border-radius:0 4px 4px 0}.quantity button.decrease{right:-2px;border-radius:4px 0 0 4px}details.uploader .file-upload-container{margin:1rem 0;max-width:100%}.field.upload{position:relative}.field.upload .progress{display:none}.field.upload.uploading .progress{display:block}.field.upload .actions{position:absolute;top:0;right:0}.empty-group,.file-upload-wrapper,.preview-wrap .item-grid{border:2px dashed var(--action-0);border-radius:4px;padding:2rem;text-align:center;transition:all .3s ease;background:rgba(var(--action-0),var(--op-1));position:relative;cursor:pointer;user-select:none}.file-upload-wrapper{max-width:var(--content);margin:1rem auto}.file-upload-wrapper h2{margin:0;font-size:var(--txt-large)}.dragover,.empty-group:hover,.file-upload-wrapper:hover,.preview-wrap .item-grid:hover{background:rgba(var(--action-0),var(--op-2));border-color:var(--action-0)}.preview-wrap:has(.item-grid:empty) .selection-controls{display:none}.preview-wrap .item-grid{min-height:20vh}.preview-wrap .item-grid:empty::before{content:'Unsorted images become their own posts.';display:block}.file-upload-wrapper input[type=file]{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;cursor:pointer}.empty-group p,.file-upload-text{margin:0}.empty-group p strong,.file-upload-text strong{color:var(--action-0);text-decoration:underline}.item-grid.groups{grid-template-columns:repeat(1,1fr)}.item-grid.group{margin-bottom:0}.item-grid:is(.restore,.group,.preview) .item{display:block;--w:1.1em}.item-grid:is(.restore,.group,.preview) button{padding:.25rem .5rem}.item-grid:is(.restore,.group,.preview) .preview>input[type=checkbox]:not(.label-button)+label{padding-left:0;margin:0}.item-grid:is(.restore,.group,.preview) .item .item-actions{position:absolute;top:0;right:0;left:var(--chipchip)}.item-grid:is(.restore,.group,.preview) summary{padding:.5rem}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked){padding:.5rem;background-color:rgba(var(--action-0),var(--op-4));opacity:1}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) img{filter:none}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) .item img{filter:var(--filter)}.item-grid.preview summary span,.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) details{display:none}[type=radio].featured:checked+label .icon-star,[type=radio].featured:not(:checked)+label .icon-star-fi{display:none}[type=radio].featured:checked+label .icon-star-fi,[type=radio].featured:not(:checked)+label .icon-star{display:inline-block}.item:is(.restore,.upload){border-radius:var(--radius);aspect-ratio:unset;overflow:hidden;background:var(--base);border:1px solid var(--base-200)}.item:is(.restore,.upload) [for=select-item]{aspect-ratio:1}.item.upload:has(details[open]){grid-column:1/-1;padding:.5rem 10%;margin:1rem 0;background-color:transparent;border:2px dashed var(--action-200)}.item.upload:has(details[open]) details[open]{background-color:transparent}.item:is(.restore,.upload) img{transform:scale(1);transition:transform var(--trans-base)}.item:is(.upload,.restore):hover img{transform:scale(1.02)}.upload-group{padding:5px;border-radius:var(--radius);background-color:rgba(var(--action-0),var(--op-1))}.upload-group .selected .field{margin:0}.upload-group .selection-actions button{aspect-ratio:unset}.submit-uploads{position:fixed;bottom:0;left:var(--btn_);z-index:var(--z-6);height:var(--btn);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);border-radius:var(--radius);animation:pulse-color 5s infinite;animation-delay:1s;background-color:var(--action-0);color:var(--action-contrast)}.submit-uploads:hover{background-color:var(--base-200);color:var(--contrast-200)}.empty-group{order:-1;grid-column:1/-1;padding:20px;border-radius:var(--radius);margin:10px 0;cursor:pointer;transition:all var(--trans-base);text-align:center;background-color:rgba(var(--action-0),var(--op-1))}.group-display:not([hidden])~.file-upload-container{display:none}.dragging,.upload.item.dragging{opacity:.7;transform:scale(.95) rotate(3deg);z-index:var(--z-7);box-shadow:0 8px 25px rgba(var(--contrast),var(--op-2))}.dragover{background:rgba(var(--action-0),var(--op-3))!important;border-color:var(--action-0)!important;transform:scale(1.05);animation:drop-pulse .8s infinite ease-in-out}.drag-preview{position:fixed;z-index:var(--z-9);width:fit-content;overflow:visible;pointer-events:none;opacity:.9;transform:scale(1.05);transition:transform .2s ease}.drag-preview .drag-items{width:max-content;height:max-content;position:relative}.drag-preview .drag-items .dragi-item{width:120px;height:120px;position:absolute;top:0;left:0;background:var(--base);border-radius:var(--radius-outer);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.drag-preview .drag-items .drag-item:nth-child(1){transform:rotate(-3deg);z-index:3}.drag-preview .drag-items .drag-item:nth-child(2){left:8px;top:-4px;transform:rotate(4deg);z-index:2;transition-delay:30ms}.drag-preview .drag-items .drag-item:nth-child(3){left:-6px;top:-8px;transform:rotate(-5deg);z-index:1;transition-delay:60ms}.drag-preview .drag-items .drag-item:nth-child(4){left:12px;top:-12px;transform:rotate(3deg);z-index:0;transition-delay:90ms}.drag-preview .drag-items .drag-item:nth-child(n+5){left:-10px;top:-16px;transform:rotate(-4deg);z-index:0;opacity:.8}.drag-preview .drag-items img,.drag-preview .drag-items video{width:100%;height:100%;object-fit:cover;display:block}.drag-preview .drag-count{position:absolute;top:-8px;right:-8px;background:var(--base-200);color:var(--contrast);border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-3)}.item.dragging{opacity:.5;transform:scale(.95);filter:grayscale(50%);transition:opacity .2s ease,transform .2s ease,filter .2s ease}@keyframes drop-pulse{0%,100%{background-color:rgba(var(--action-0),var(--op-3));transform:scale(1.02)}50%{background-color:var(rgba(var(--action-0),var(--op-4)));transform:scale(1.04)}}.selection-actions{display:flex;gap:.25rem}@media (max-width:767px){body:not(.uploading):has(.group-display:not([hidden])){overflow:hidden}body:not(.uploading):has(.group-display:not([hidden])) .qtoggle{z-index:var(--z-1)}.group-display.group-display{position:fixed;top:var(--btn);bottom:var(--btn);left:0;right:0;max-height:var(--maxHeight);overflow:hidden;z-index:var(--z-6);width:calc(100% - 1rem);height:calc(100% - 1rem);padding:0 0 3rem;--justify:flex-start;--align:flex-start;--gap:0}.group-display::before{content:'';display:block;z-index:-1;top:-.5rem;bottom:-.5rem;left:-.5rem;right:-.5rem;position:absolute;background-color:rgba(var(--base),var(--op-6));filter:blur(5px)}.group-display .preview-wrap,.group-display .sidebar{--wrap:nowrap;height:50%;overflow:hidden auto;position:relative;padding:.5rem}.group-display .preview-wrap{top:0}.group-display .preview-wrap .selected{display:flex;justify-content:space-between;align-items:center}.group-display .sidebar{bottom:0;flex-wrap:nowrap;overflow:hidden auto;background-color:var(--contrast-200);color:var(--base)}.group-display .sidebar>.hint{color:var(--contrast)}.group-display .sidebar .header{display:none}.group-display .preview-actions{top:0;flex-shrink:0}.group-display .preview-wrap>.hint,.group-display .sidebar>.hint{bottom:0;margin:0;text-align:center}.group-display .preview-actions,.group-display .preview-wrap>.hint,.group-display .sidebar>.hint{position:absolute;left:0;right:0;background-color:rgba(var(--base),var(--op-6));z-index:var(--z-3);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.group-display .item-grid{height:100%;overflow:hidden auto;grid-template-columns:repeat(3,1fr);padding:2rem 0}.group-display .sidebar>.item-grid{grid-template-columns:repeat(1,1fr);gap:1rem;padding:0}.group-display .sidebar .empty-group{order:0;position:sticky;height:fit-content;top:0;z-index:var(--z-3);background-color:rgba(var(--action-0),var(--op-6))}.group-display .sidebar .upload-group{order:1}.group-display .sidebar .empty-group p{margin:0}.group-display .field,.group-display .field label{margin:0;padding:0}.group-display .sidebar h4{margin:.25rem}.group-display .item{width:100%;height:max-content}.submit-uploads{bottom:var(--btn);left:0;right:0;width:100%;height:3rem}body.uploading .group-display.group-display{position:relative;top:unset;bottom:unset;right:unset;left:unset}}@media (min-width:768px){.group-display.group-display{--wrap:nowrap;--dir:row;--gap:1rem;--align:flex-start}.group-display .preview-wrap,.group-display .sidebar{--justify:flex-start;--wrap:nowrap;max-height:calc(100vh - var(--btnbtn));overflow:hidden auto}.group-display .preview-wrap,.group-display .sidebar{width:50%}.preview-actions,.preview-wrap .hint{position:sticky;z-index:var(--z-3);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);background-color:var(--base);width:100%}.preview-actions{top:0;left:0;right:0}.preview-actions .field{margin:0}.preview-wrap .hint,.sidebar>.hint{bottom:-1rem;padding-bottom:1rem;margin:0;left:0;right:0;text-align:center}}.item-grid.restore{grid-template-columns:repeat(1,1fr)}.editor-container .ql-toolbar{display:flex;background-color:var(--base-50);justify-content:flex-start;flex-wrap:wrap;padding:.25rem;gap:.5rem 1rem;border-top-left-radius:var(--radius);border-top-right-radius:var(--radius);border-bottom:4px solid var(--base-50)}.ql-toolbar button{min-height:0;padding:.5rem}.ql-toolbar .ql-formats{display:flex;gap:.25rem}.editor-container .ql-container{--padding:1rem;background-color:var(--base);border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius);height:fit-content;padding:2px;border:1px solid var(--base-200)}.editor-container .ql-container .ql-editor{padding:var(--padding);width:100%;height:100%}.ql-editor img{max-width:50%;height:auto}.ql-clipboard{left:-100000px;height:1px;overflow-y:hidden;position:absolute;top:50%}.ql-hidden{display:none}.ql-tooltip{position:absolute;transform:translateY(10px);background-color:var(--base-100);border:1px solid var(--base);box-shadow:0 0 5px rgba(var(--base),var(--op-6));color:var(--contrast);padding:5px 12px;white-space:nowrap}[data-type=single] .item-grid{display:flex}.repeater-row details summary::after{margin-left:0}.repeater-row details summary button{margin-left:auto}.repeater .field-input-wrapper{flex-direction:column}.repeater .repeater-items{width:100%}.add-repeater-row,.remove-row{margin-left:auto;min-height:0;height:var(--chipchip);background-color:var(--action-0)}.field.tag-list .row{margin-bottom:1rem}.field.tag-list .row .field{flex:1;min-width:150px;margin:0}.field.tag-list .tag .add-tag-item{flex-shrink:0;white-space:nowrap;margin-top:calc(var(--txt-medium) + 1rem)}.field.tag-list .tag-items{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1rem;min-height:2rem}.field.tag-list .tag-item{background:var(--base-200);padding:.4rem .75rem;border-radius:4px;display:inline-flex;align-items:center;gap:.5rem;font-size:.9rem;line-height:1.2}.field.tag-list .tag-item:hover{background:var(--base-100)}.field.tag-list .tag-label{max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field.tag-list .remove-tag{min-height:0;padding:.25rem;color:var(--contrast);transition:transform .2s;box-shadow:none}.field.tag-list .remove-tag:hover{transform:scale(1.2)}@media (max-width:768px){.field.tag-list .tag{flex-direction:column;align-items:stretch}.field.tag-list .tag .field{min-width:100%}}.form-progress{padding:0 1rem}.form-progress .progress{background:var(--base-100);border-radius:var(--radius);padding:1rem}.form-progress .bar{height:6px;background:var(--base-200);border-radius:3px;overflow:hidden;margin-bottom:.5rem}.form-progress .fill{height:100%;background:linear-gradient(90deg,var(--action-0),var(--action-200));width:0%;transition:width .4s ease;border-radius:3px}.form-progress .step-text{font-size:var(--txt-small);font-weight:600;color:var(--contrast-200)}form nav.tabs{position:relative;top:0;left:0;right:0;padding:1rem 0;gap:0;z-index:0}form nav.tabs button{position:relative;background:0 0;border:none;padding:.5rem 1rem .5rem 3rem;z-index:1}form nav.tabs .step-number{width:2.5rem;height:100%;border-radius:50% 0 0 50%;position:absolute;left:0;top:0;background:var(--base-200);color:var(--contrast-50);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:var(--txt-small);border:3px solid var(--base)}form nav.tabs button.pending .step-number{background:var(--base-100);color:var(--contrast-200)}form nav.tabs button.active .step-number,form nav.tabs button.current .step-number{background:var(--action-0);color:var(--action-contrast);border-color:var(--action-200)}form nav.tabs button.completed .step-number{background:var(--successBack);color:var(--successBack);border-color:var(--successText)}form nav.tabs button.completed .step-number::before{content:'✓';font-size:1.2rem;color:var(--successText);position:absolute}form nav.tabs button.completed h2{color:var(--contrast-200)}.step-navigation{margin-top:2rem;padding-top:2rem;border-top:1px solid var(--base-200);gap:1rem}.step-navigation .prev-step{background:var(--base-100)}.step-navigation .next-step,.step-navigation button[type=submit]{margin-left:auto}@media (max-width:768px){form nav.tabs button{min-width:80px;font-size:var(--txt-small)}form nav.tabs button h2{font-size:var(--txt-small)}form{--step-size:2rem}}.field input.error,.field select.error,.field textarea.error{border-color:var(--errorBack)}.error-message{color:var(--errorText);font-size:var(--txt-small);margin-top:.25rem;display:block}.form-summary{padding:2rem;border-radius:8px;margin-top:2rem;border:2px dashed var(--contrast-200)}.form-summary .message{margin-bottom:2rem}.form-summary .result+.result{position:relative;margin-top:1.5rem;padding-top:1.5rem}.form-summary .result+.result::before{position:absolute;top:0;left:16.5%;content:'';width:67%;height:1px;border-bottom:1px solid var(--base-200)}.form-summary h2{margin:1rem 0}.form-summary h4{background-color:var(--base-100);padding:.5rem 2rem;position:relative;left:-2rem;color:var(--contrast-200);font-size:.875rem;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.75rem}.form-summary p{color:var(--text);margin:0}.group-summary,.repeater-summary{background:var(--base-100);padding:1rem;border-radius:4px;margin-top:.5rem}.repeater-row{margin-bottom:1rem}.repeater-row:last-child{margin-bottom:0}.selected-item{border:1px solid var(--base-200);border-radius:var(--radius);font-size:var(--txt-x-small);background-color:var(--base);padding:.25rem .5rem}.selected-item button{--w:.5em;min-height:1em;width:1em;padding:0}.selector .auto-wrapper,.selector .selected-items{flex:1;width:100%}.fstatus{z-index:var(--z-5);background-color:rgba(var(--base),var(--op-6));border-radius:var(--radius);padding:0 .5rem;position:fixed;right:.5rem;top:var(--btnbtn);--w:1em;box-shadow:rgba(var(--base),var(--op-6)) var(--shdw);--wrap:nowrap;--gap:1rem}.fstatus .spinner{display:none}.fstatus.loading .spinner{display:inline-block}.fstatus p{margin:0;padding:.25rem}.restore-uploads .item-grid.group .field.group,.restore-uploads .upload-group .selection-actions{display:none}.upload-group .item-grid.group{grid-template-columns:repeat(2,1fr)}.restore-uploads .item-grid.group{grid-template-columns:repeat(3,1fr)}fieldset{width:100%;border-color:var(--base-200)}.restore-form.restore-form[hidden]{display:block!important;position:fixed;bottom:var(--offScreen);right:var(--btnbtn);transition:bottom var(--trans-base);transition-duration:2s}.restore-form.restore-form:not([hidden]){width:50vw;padding:1rem;z-index:var(--z-7);background-color:rgba(var(--base),var(--op-6));border-radius:var(--radius);box-shadow:rgba(var(--action-0),var(--op-6)) var(--shdw);position:fixed;right:var(--btnbtn);bottom:0;transition:bottom var(--trans-base)}.restore-form h3{font-size:var(--txt-medium)}body:has(nav.fixed.bottom) .restore-form.restore-form:not([hidden]){bottom:var(--btn)}.restore-form .actions{display:flex;width:100%}.restore-form .actions button{min-height:var(--chip);font-size:var(--txt-x-small);width:100%}
.field{width:100%;margin:.5em 0;padding:.5em 0}.field+.field{border-top:1px solid rgb(var(--base-200))}.field .wrapper{width:100%;position:relative}.field .validation{flex-shrink:0;max-width:0;transition:var(--trans-size)}.field.has-error .validation.error,.field.has-success .validation.success{max-width:var(--btn)}.field.has-error .error{color:rgb(var(--error))}.field.has-error input,.field.has-error select,.field.has-error textarea{border-color:rgb(var(--error));background-color:var(--errorBack)}.field.has-error input:focus,.field.has-error select:focus,.field.has-error textarea:focus{outline-color:rgb(var(--error));box-shadow:rgba(var(--error),.2) var(--shdw)}.field.has-success .success{color:var(--success)}.validation-message{color:var(--errorText);font-size:var(--txt-small);margin-top:.25rem;display:block}.field[data-field=post_status] .wrapper{--justify:flex-start}.field[data-field=post_status] .btn+label{width:var(--chipchip);min-height:var(--chipchip);padding:0}.field[data-field=post_status] .btn+label:hover,.field[data-field=post_status] .btn:focus+label{color:rgb(var(--action-contrast))}.date-wrapper{position:relative;display:inline-block}input[type=date]{padding:8px 36px 8px 8px;border-radius:4px}input[type=date]::-webkit-calendar-picker-indicator{opacity:0;width:100%;height:100%;position:absolute;top:0;left:0;cursor:pointer}input[type=date]+.icon{--w:20px;position:absolute;right:10px;top:50%;transform:translateY(-50%);pointer-events:none}input:is([type=time],[type=datetime-local],[type=date]){padding:.5rem;border:1px solid rgb(var(--contrast-200));border-radius:4px;font-size:14px;min-width:180px;background:rgb(var(--base));color:rgb(var(--contrast));cursor:pointer}.date-wrapper input[type=date]:focus,.datetime-wrapper input[type=datetime-local]:focus,.time-wrapper input[type=time]:focus,.wrapper input:is([type=time],[type=datetime-local],[type=date]):focus{border-color:rgb(var(--action-0));box-shadow:0 0 0 2px rgba(var(--action-0),.1)}.date-wrapper .icon,.datetime-wrapper .icon,.time-wrapper .icon,.wrapper .icon{width:18px;height:18px;background-color:rgb(var(--contrast));opacity:.7}.quantity{margin:0;display:inline-flex;width:fit-content;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;position:relative}.quantity:focus-within{border-color:rgb(var(--action-0))}.quantity label{margin:0;font-size:var(--txt-small)}.quantity button{background:rgb(var(--base));padding:0;width:var(--chip_);height:var(--chip_);min-height:0;z-index:0;position:relative;border:1px solid rgb(var(--base-200));color:rgb(var(--contrast-200))}.quantity button:hover:not(:disabled){color:rgb(var(--action-0));border-color:rgb(var(--action-0));background-color:rgb(var(--base))}.quantity button:active:not(:disabled){background-color:rgb(var(--action-0));color:rgb(var(--light-0));transform:scale(.95)}.quantity button:disabled{opacity:.5;cursor:not-allowed}.quantity input[type=number]{z-index:1;border:1px solid rgb(var(--base-200));background:rgb(var(--base));text-align:center;font-size:1.1rem;width:60px;height:48px;margin:0;padding:0!important;appearance:textfield}.quantity input[type=number]::-webkit-inner-spin-button,.quantity input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.quantity input[type=number]:focus{background-color:rgb(var(--base-50))}.quantity button.increase{left:-2px;border-radius:0 4px 4px 0}.quantity button.decrease{right:-2px;border-radius:4px 0 0 4px}details.uploader .file-upload-container{margin:1rem 0;max-width:100%}.field.upload{position:relative}.field.upload .progress{display:none}.field.upload.uploading .progress{display:block}.field.upload .actions{position:absolute;top:0;right:0}.empty-group,.file-upload-wrapper,.preview-wrap .item-grid{border:2px dashed rgb(var(--action-0));border-radius:4px;padding:2rem;text-align:center;transition:all .3s ease;background:rgba(var(--action-0),var(--op-1));position:relative;cursor:pointer;user-select:none}.file-upload-wrapper{max-width:var(--content);margin:1rem auto}.file-upload-wrapper h2{margin:0;font-size:var(--txt-large)}.dragover,.empty-group:hover,.file-upload-wrapper:hover,.preview-wrap .item-grid:hover{background:rgba(var(--action-0),var(--op-2));border-color:rgb(var(--action-0))}.preview-wrap:has(.item-grid:empty) .selection-controls{display:none}.preview-wrap .item-grid{min-height:20vh}.preview-wrap .item-grid:empty::before{content:'Unsorted images become their own posts.';display:block}.file-upload-wrapper input[type=file]{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;cursor:pointer}.empty-group p,.file-upload-text{margin:0}.empty-group p strong,.file-upload-text strong{color:rgb(var(--action-0));text-decoration:underline}.item-grid.groups{grid-template-columns:repeat(1,1fr)}.item-grid.group{margin-bottom:0}.item-grid:is(.restore,.group,.preview) .item{display:block;--w:1.1em}.item-grid:is(.restore,.group,.preview) button{padding:.25rem .5rem}.item-grid:is(.restore,.group,.preview) .preview>input[type=checkbox]:not(.label-button)+label{padding-left:0;margin:0}.item-grid:is(.restore,.group,.preview) .item .item-actions{position:absolute;top:0;right:0;left:var(--chipchip)}.item-grid:is(.restore,.group,.preview) summary{padding:.5rem}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked){padding:.5rem;background-color:rgba(var(--action-0),var(--op-4));opacity:1}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) img{filter:none}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) .item img{filter:var(--filter)}.item-grid.preview summary span,.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) details{display:none}[type=radio].featured:checked+label .icon-star,[type=radio].featured:not(:checked)+label .icon-star-fi{display:none}[type=radio].featured:checked+label .icon-star-fi,[type=radio].featured:not(:checked)+label .icon-star{display:inline-block}.item:is(.restore,.upload){border-radius:var(--radius);aspect-ratio:unset;overflow:hidden;background:rgb(var(--base));border:1px solid rgb(var(--base-200))}.item:is(.restore,.upload) [for=select-item]{aspect-ratio:1}.item.upload:has(details[open]){grid-column:1/-1;padding:.5rem 10%;margin:1rem 0;background-color:transparent;border:2px dashed rgb(var(--action-200))}.item.upload:has(details[open]) details[open]{background-color:transparent}.item:is(.restore,.upload) img{transform:scale(1);transition:transform var(--trans-base)}.item:is(.upload,.restore):hover img{transform:scale(1.02)}.upload-group{padding:5px;border-radius:var(--radius);background-color:rgba(var(--action-0),var(--op-1))}.upload-group .selected .field{margin:0}.upload-group .selection-actions button{aspect-ratio:unset}.submit-uploads{position:fixed;bottom:0;left:var(--btn_);z-index:var(--z-6);height:var(--btn);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);border-radius:var(--radius);animation:pulse-color 5s infinite;animation-delay:1s;background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.submit-uploads:hover{background-color:rgb(var(--base-200));color:rgb(var(--contrast-200))}.empty-group{order:-1;grid-column:1/-1;padding:20px;border-radius:var(--radius);margin:10px 0;cursor:pointer;transition:all var(--trans-base);text-align:center;background-color:rgba(var(--action-0),var(--op-1))}.group-display:not([hidden])~.file-upload-container{display:none}.dragging,.upload.item.dragging{opacity:.7;transform:scale(.95) rotate(3deg);z-index:var(--z-7);box-shadow:0 8px 25px rgba(var(--contrast),var(--op-2))}.dragover{background:rgba(var(--action-0),var(--op-3))!important;border-color:rgb(var(--action-0))!important;transform:scale(1.05);animation:drop-pulse .8s infinite ease-in-out}.drag-preview{position:fixed;z-index:var(--z-9);width:fit-content;overflow:visible;pointer-events:none;opacity:.9;transform:scale(1.05);transition:transform .2s ease}.drag-preview .drag-items{width:max-content;height:max-content;position:relative}.drag-preview .drag-items .dragi-item{width:120px;height:120px;position:absolute;top:0;left:0;background:rgb(var(--base));border-radius:var(--radius-outer);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.drag-preview .drag-items .drag-item:nth-child(1){transform:rotate(-3deg);z-index:3}.drag-preview .drag-items .drag-item:nth-child(2){left:8px;top:-4px;transform:rotate(4deg);z-index:2;transition-delay:30ms}.drag-preview .drag-items .drag-item:nth-child(3){left:-6px;top:-8px;transform:rotate(-5deg);z-index:1;transition-delay:60ms}.drag-preview .drag-items .drag-item:nth-child(4){left:12px;top:-12px;transform:rotate(3deg);z-index:0;transition-delay:90ms}.drag-preview .drag-items .drag-item:nth-child(n+5){left:-10px;top:-16px;transform:rotate(-4deg);z-index:0;opacity:.8}.drag-preview .drag-items img,.drag-preview .drag-items video{width:100%;height:100%;object-fit:cover;display:block}.drag-preview .drag-count{position:absolute;top:-8px;right:-8px;background:rgb(var(--base-200));color:rgb(var(--contrast));border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-3)}.item.dragging{opacity:.5;transform:scale(.95);filter:grayscale(50%);transition:opacity .2s ease,transform .2s ease,filter .2s ease}@keyframes drop-pulse{0%,100%{background-color:rgba(var(--action-0),var(--op-3));transform:scale(1.02)}50%{background-color:var(rgba(var(--action-0),var(--op-4)));transform:scale(1.04)}}.selection-actions{display:flex;gap:.25rem}@media (max-width:767px){body:not(.uploading):has(.group-display:not([hidden])){overflow:hidden}body:not(.uploading):has(.group-display:not([hidden])) .qtoggle{z-index:var(--z-1)}.group-display.group-display{position:fixed;top:var(--btn);bottom:var(--btn);left:0;right:0;max-height:var(--maxHeight);overflow:hidden;z-index:var(--z-6);width:calc(100% - 1rem);height:calc(100% - 1rem);padding:0 0 3rem;--justify:flex-start;--align:flex-start;--gap:0}.group-display::before{content:'';display:block;z-index:-1;top:-.5rem;bottom:-.5rem;left:-.5rem;right:-.5rem;position:absolute;background-color:rgba(var(--base),var(--op-6));filter:blur(5px)}.group-display .preview-wrap,.group-display .sidebar{--wrap:nowrap;height:50%;overflow:hidden auto;position:relative;padding:.5rem}.group-display .preview-wrap{top:0}.group-display .preview-wrap .selected{display:flex;justify-content:space-between;align-items:center}.group-display .sidebar{bottom:0;flex-wrap:nowrap;overflow:hidden auto;background-color:rgb(var(--contrast-200));color:rgb(var(--base))}.group-display .sidebar>.hint{color:rgb(var(--contrast))}.group-display .sidebar .header{display:none}.group-display .preview-actions{top:0;flex-shrink:0}.group-display .preview-wrap>.hint,.group-display .sidebar>.hint{bottom:0;margin:0;text-align:center}.group-display .preview-actions,.group-display .preview-wrap>.hint,.group-display .sidebar>.hint{position:absolute;left:0;right:0;background-color:rgba(var(--base),var(--op-6));z-index:var(--z-3);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.group-display .item-grid{height:100%;overflow:hidden auto;grid-template-columns:repeat(3,1fr);padding:2rem 0}.group-display .sidebar>.item-grid{grid-template-columns:repeat(1,1fr);gap:1rem;padding:0}.group-display .sidebar .empty-group{order:0;position:sticky;height:fit-content;top:0;z-index:var(--z-3);background-color:rgba(var(--action-0),var(--op-6))}.group-display .sidebar .upload-group{order:1}.group-display .sidebar .empty-group p{margin:0}.group-display .field,.group-display .field label{margin:0;padding:0}.group-display .sidebar h4{margin:.25rem}.group-display .item{width:100%;height:max-content}.submit-uploads{bottom:var(--btn);left:0;right:0;width:100%;height:3rem}body.uploading .group-display.group-display{position:relative;top:unset;bottom:unset;right:unset;left:unset}}@media (min-width:768px){.group-display.group-display{--wrap:nowrap;--dir:row;--gap:1rem;--align:flex-start}.group-display .preview-wrap,.group-display .sidebar{--justify:flex-start;--wrap:nowrap;max-height:calc(100vh - var(--btnbtn));overflow:hidden auto}.group-display .preview-wrap,.group-display .sidebar{width:50%}.preview-actions,.preview-wrap .hint{position:sticky;z-index:var(--z-3);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);background-color:rgb(var(--base));width:100%}.preview-actions{top:0;left:0;right:0}.preview-actions .field{margin:0}.preview-wrap .hint,.sidebar>.hint{bottom:-1rem;padding-bottom:1rem;margin:0;left:0;right:0;text-align:center}}.item-grid.restore{grid-template-columns:repeat(1,1fr)}.editor-container .ql-toolbar{display:flex;background-color:rgb(var(--base-50));justify-content:flex-start;flex-wrap:wrap;padding:.25rem;gap:.5rem 1rem;border-top-left-radius:var(--radius);border-top-right-radius:var(--radius);border-bottom:4px solid rgb(var(--base-50))}.ql-toolbar button{min-height:0;padding:.5rem}.ql-toolbar .ql-formats{display:flex;gap:.25rem}.editor-container .ql-container{--padding:1rem;background-color:rgb(var(--base));border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius);height:fit-content;padding:2px;border:1px solid rgb(var(--base-200))}.editor-container .ql-container .ql-editor{padding:var(--padding);width:100%;height:100%;max-width:90vw}.ql-editor img{max-width:50%;height:auto}.ql-clipboard{left:-100000px;height:1px;overflow-y:hidden;position:absolute;top:50%}.ql-hidden{display:none}.ql-tooltip{position:absolute;transform:translateY(10px);background-color:rgb(var(--base-100));border:1px solid rgb(var(--base));box-shadow:0 0 5px rgba(var(--base),var(--op-6));color:rgb(var(--contrast));padding:5px 12px;white-space:nowrap}[data-type=single] .item-grid{display:flex}.repeater-row details summary::after{margin-left:0}.repeater-row details summary button{margin-left:auto}.repeater .field-input-wrapper{flex-direction:column}.repeater .repeater-items{width:100%}.add-repeater-row,.remove-row{margin-left:auto;min-height:0;height:var(--chipchip);background-color:rgb(var(--action-0))}.field.tag-list .row{margin-bottom:1rem}.field.tag-list .row .field{flex:1;min-width:150px;margin:0}.field.tag-list .tag .add-tag-item{flex-shrink:0;white-space:nowrap;margin-top:calc(var(--txt-medium) + 1rem)}.field.tag-list .tag-items{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1rem;min-height:2rem}.field.tag-list .tag-item{background:rgb(var(--base-200));padding:.4rem .75rem;border-radius:4px;display:inline-flex;align-items:center;gap:.5rem;font-size:.9rem;line-height:1.2}.field.tag-list .tag-item:hover{background:rgb(var(--base-100))}.field.tag-list .tag-label{max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field.tag-list .remove-tag{min-height:0;padding:.25rem;color:rgb(var(--contrast));transition:transform .2s;box-shadow:none}.field.tag-list .remove-tag:hover{transform:scale(1.2)}@media (max-width:768px){.field.tag-list .tag{flex-direction:column;align-items:stretch}.field.tag-list .tag .field{min-width:100%}}.form-progress{padding:0 1rem}.form-progress .progress{background:rgb(var(--base-100));border-radius:var(--radius);padding:1rem}.form-progress .bar{height:6px;background:rgb(var(--base-200));border-radius:3px;overflow:hidden;margin-bottom:.5rem}.form-progress .fill{height:100%;background:linear-gradient(90deg,rgb(var(--action-0)),rgb(var(--action-200)));width:0%;transition:width .4s ease;border-radius:3px}.form-progress .step-text{font-size:var(--txt-small);font-weight:600;color:rgb(var(--contrast-200))}form nav.tabs{position:relative;top:0;left:0;right:0;padding:1rem 0;gap:0;z-index:0}form nav.tabs button{position:relative;background:0 0;border:none;padding:.5rem 1rem .5rem 3rem;z-index:1}form nav.tabs .step-number{width:2.5rem;height:100%;border-radius:50% 0 0 50%;position:absolute;left:0;top:0;background:rgb(var(--base-200));color:rgb(var(--contrast-50));display:flex;align-items:center;justify-content:center;font-weight:700;font-size:var(--txt-small);border:3px solid rgb(var(--base))}form nav.tabs button.pending .step-number{background:rgb(var(--base-100));color:rgb(var(--contrast-200))}form nav.tabs button.active .step-number,form nav.tabs button.current .step-number{background:rgb(var(--action-0));color:rgb(var(--action-contrast));border-color:rgb(var(--action-200))}form nav.tabs button.completed .step-number{background:var(--successBack);color:var(--successBack);border-color:var(--successText)}form nav.tabs button.completed .step-number::before{content:'✓';font-size:1.2rem;color:var(--successText);position:absolute}form nav.tabs button.completed h2{color:rgb(var(--contrast-200))}.step-navigation{margin-top:2rem;padding-top:2rem;border-top:1px solid rgb(var(--base-200));gap:1rem}.step-navigation .prev-step{background:rgb(var(--base-100))}.step-navigation .next-step,.step-navigation button[type=submit]{margin-left:auto}@media (max-width:768px){form nav.tabs button{min-width:80px;font-size:var(--txt-small)}form nav.tabs button h2{font-size:var(--txt-small)}form{--step-size:2rem}}.field input.error,.field select.error,.field textarea.error{border-color:var(--errorBack)}.error-message{color:var(--errorText);font-size:var(--txt-small);margin-top:.25rem;display:block}.form-summary{padding:2rem;border-radius:8px;margin-top:2rem;border:2px dashed rgb(var(--contrast-200))}.form-summary .message{margin-bottom:2rem}.form-summary .result+.result{position:relative;margin-top:1.5rem;padding-top:1.5rem}.form-summary .result+.result::before{position:absolute;top:0;left:16.5%;content:'';width:67%;height:1px;border-bottom:1px solid rgb(var(--base-200))}.form-summary h2{margin:1rem 0}.form-summary h4{background-color:rgb(var(--base-100));padding:.5rem 2rem;position:relative;left:-2rem;color:rgb(var(--contrast-200));font-size:.875rem;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.75rem}.form-summary p{color:var(--text);margin:0}.group-summary,.repeater-summary{background:rgb(var(--base-100));padding:1rem;border-radius:4px;margin-top:.5rem}.repeater-row{margin-bottom:1rem}.repeater-row:last-child{margin-bottom:0}.selected-item{border:1px solid rgb(var(--base-200));border-radius:var(--radius);font-size:var(--txt-x-small);background-color:rgb(var(--base));padding:.25rem .5rem}.selected-item button{--w:.5em;min-height:1em;width:1em;padding:0}.selector .auto-wrapper,.selector .selected-items{flex:1;width:100%}.fstatus{z-index:var(--z-5);background-color:rgba(var(--base),var(--op-6));border-radius:var(--radius);padding:0 .5rem;position:fixed;right:.5rem;top:var(--btnbtn);--w:1em;box-shadow:rgba(var(--base),var(--op-6)) var(--shdw);--wrap:nowrap;--gap:1rem}.fstatus .spinner{display:none}.fstatus.loading .spinner{display:inline-block}.fstatus p{margin:0;padding:.25rem}.restore-uploads .item-grid.group .field.group,.restore-uploads .upload-group .selection-actions{display:none}.upload-group .item-grid.group{grid-template-columns:repeat(2,1fr)}.restore-uploads .item-grid.group{grid-template-columns:repeat(3,1fr)}fieldset{width:100%;border-color:rgb(var(--base-200))}.restore-form.restore-form[hidden]{display:block!important;position:fixed;bottom:var(--offScreen);right:var(--btnbtn);transition:bottom var(--trans-base);transition-duration:2s}.restore-form.restore-form:not([hidden]){width:50vw;padding:1rem;z-index:var(--z-7);background-color:rgba(var(--base),var(--op-6));border-radius:var(--radius);box-shadow:rgba(var(--action-0),var(--op-6)) var(--shdw);position:fixed;right:var(--btnbtn);bottom:0;transition:bottom var(--trans-base)}.restore-form h3{font-size:var(--txt-medium)}body:has(nav.fixed.bottom) .restore-form.restore-form:not([hidden]){bottom:var(--btn)}.restore-form .actions{display:flex;width:100%}.restore-form .actions button{min-height:var(--chip);font-size:var(--txt-x-small);width:100%}
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: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>ul{--dir:column;--align:center;--justify:flex-end;--gap:0;height:100vh;max-height:none;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%}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: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}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-9)}nav.fixed.bottom{left:0;bottom:0;width:calc(100% - 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{--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}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: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:var(--action-contrast)}nav.letters,nav.letters a,nav.letters li,nav.letters ul{height:var(--chip)}nav.letters li{max-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{max-width:none}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base),var(--op-6))}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:calc(100% - var(--btn_));flex-shrink:1;max-width:none}nav.index li:first-of-type{flex-shrink:1;transform:scaleX(1);order:9999;width:var(--btn);height:var(--btn);max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index a{border-bottom:4px solid transparent}nav.index .active a{border-color:rgb(var(--action-0));color:rgb(var(--contrast))}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 li{width:100%;height:var(--btn);max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:flex-end;padding:0 2rem 0 0;background-color:transparent}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}.always ul.socials li{flex:1;--justify:center;--align:center}.always ul.socials a{display:inline-flex}ul.socials a{display:inline-block;font-size:var(--txt-x-small);padding:.25rem .5rem;max-width:none}ul.socials .icon{margin:0}ul.socials .icon+span:not(.screen-reader-text){margin-left:.5rem}nav.tabs{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden}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: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_)}
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))}.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>ul{--dir:column;--align:center;--justify:flex-end;--gap:0;height:100vh;max-height:none;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%}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}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-9)}nav.fixed.bottom{left:0;bottom:0;width:calc(100% - 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{height:var(--chip)}nav.letters li{max-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{max-width:none}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}.always ul.socials li{flex:1;--justify:center;--align:center}.always ul.socials a{display:inline-flex}ul.socials a{display:inline-block;font-size:var(--txt-x-small);padding:.25rem .5rem;max-width:none}ul.socials .icon{margin:0}ul.socials .icon+span:not(.screen-reader-text){margin-left:.5rem}nav.tabs{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden}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_)}
assets/js/concise/CRUD.js
@@ -558,7 +558,7 @@
                }
            }
            if (event === 'sent-to-server') {
            if (event === 'sent-to-server' && data.type === 'content_update') {
                if (data instanceof FormData) return;
                for ( let [id, changes] of Object.entries(data.posts)) {
assets/js/concise/DataStore.js
@@ -1127,7 +1127,12 @@
        // Handle other filters
        for (const [key, value] of Object.entries(store.filters)) {
            if (key === 'taxonomy') continue;
            if (key === 'taxonomy') {
                if (typeof value === 'string' && !value.includes(',')) {
                    filterPredicates.push(item => item.taxonomy === value);
                }
                continue;
            }
            if (store.ignoreFilters.has(key)) {
                continue;
            }
@@ -1285,14 +1290,9 @@
        }
        if (store.lastResponse.has_more === false) {
            // Check if new filters are a subset of what we have
            const isSubsetFilter = Object.entries(updates).every(([key, value]) => {
                if (store.ignoreFilters.has(key)) return true;
                if (key === 'page') return true; // Handle pagination locally
                return true; // We have all data, can filter locally
            });
            if (isSubsetFilter) return false;
            if (this.hasCompleteData(store, store.filters)) {
                return false;
            }
        }
        if ('page' in updates) {
assets/js/concise/FormController.js
@@ -371,7 +371,7 @@
            // Dependencies still need checking
            if (this.dependencies.has(field.dataset.field)) {
                let dependency = this.dependencies.get(field.dataset.field);
                dependency.items.forEach(item => {
                dependency.forEach(item => {
                    this.checkFieldDependency(item, field.dataset.field);
                });
            }
@@ -387,7 +387,7 @@
        //Dependencies
        if (this.dependencies.has(field.dataset.field)) {
            let dependency = this.dependencies.get(field.dataset.field);
            dependency.items.forEach(item => {
            dependency.forEach(item => {
                this.checkFieldDependency(item, field.dataset.field);
            });
        }
@@ -591,6 +591,11 @@
            ui: window.uiFromSelectors(this.selectors.forms, form)
        };
        config.ui.fields = {};
        form.querySelectorAll('[data-field]').forEach((field) => {
            config.ui.fields[field.dataset.field] = field;
        });
        this.initializeFields(form, config);
        this.forms.set(formId, config);
@@ -617,10 +622,10 @@
        }
        // Clean up dependencies for this form
        this.dependencies.forEach((dependency, fieldName) => {
            dependency.items = dependency.items.filter(item => item.form !== formId);
            dependency = dependency.filter(item => item.form !== formId);
            // Remove the dependency entry entirely if no items left
            if (dependency.items.length === 0) {
            if (dependency.length === 0) {
                this.dependencies.delete(fieldName);
            }
        });
@@ -1147,38 +1152,41 @@
            const requiredValue = field.dataset.dependsValue;
            const operator = field.dataset.dependsOperatior??'==';
            let formData = this.forms.get(form.dataset.formId);
            if (!this.dependencies.has(dependsOn)) {
                let element = document.querySelector(`[field="${dependsOn}"]`);
                if (element) {
                    this.dependencies.set(dependsOn, {
                        element: element,
                        items: []
                    });
                if (Object.hasOwn(formData.ui.fields, dependsOn)) {
                    this.dependencies.set(dependsOn, []);
                }
            }
            let dependency = this.dependencies.get(dependsOn);
            dependency.items.push({
                field: field,
                form: form.dataset.formId,
                requiredValue: requiredValue,
                operator: operator
            });
            this.dependencies.set(dependsOn, dependency);
            this.checkFieldDependency(dependency, dependsOn);
            if (dependency) {
                dependency.push({
                    field:  field,
                    form: form.dataset.formId,
                    requiredValue: requiredValue,
                    operator: operator
                });
                this.dependencies.set(dependsOn, dependency);
            }
            this.checkFieldDependency(field, dependsOn);
        });
    }
    checkFieldDependency(dependentField, controlFieldName) {
        const form = this.getForm(dependentField);
        const controlField = this.dependencies.get(controlFieldName);
        if (!controlField) return;
        const controlValue = this.getFieldCheckedValue(controlField.element);
        const controlValue = this.getFieldValue(form.ui.fields[controlFieldName]);
        const shouldShow = this.evaluateCondition(
            controlValue,
            dependentField.requiredValue,
            dependentField.operator
            dependentField.dataset.dependsValue,
            dependentField.dataset.dependsOperatior
        );
        this.toggleFieldVisibility(dependentField.field, shouldShow);
        this.toggleFieldVisibility(dependentField, shouldShow);
    }
    evaluateCondition(value, requiredValue, operator) {
        const fieldStr = String(value || '');
assets/js/concise/Queue.js
@@ -626,7 +626,7 @@
                if (result.id && operation.id !== result.id) {
                    operation = await this.handleServerMerge(operation, result);
                } else {
                    operation.status = result.status??'failed';
                    operation.status = result.status??'pending';
                    operation.serverData = result;
                    this.updateOperationStatus(operation.id, operation.status);
                }
assets/js/concise/TaxonomySelector.js
@@ -1135,11 +1135,31 @@
    handleFetchError(error) {
        const field = this.currentField();
        const message = field
            ? `Failed to load ${field.plural}`
            : 'Failed to load data';
        this.setMessage(field, true, 'Something went wrong.', false);
        this.setMessage(field,true, message, false);
        const conf = this.container.open || field?.isFilter ? this.ui : field?.ui;
        const p = conf?.message?.message;
        if (p && !p.querySelector('.clear-cache-btn')) {
            const btn = document.createElement('button');
            btn.className = 'clear-cache-btn';
            btn.type = 'button';
            btn.textContent = 'Clear cache and try again';
            btn.addEventListener('click', async () => {
                btn.remove();
                this.store.clearCache();
                if (this.activeField && field) {
                    await this.store.setFilters({
                        taxonomy: field.taxonomy,
                        page: 1,
                        search: '',
                        parent: 0
                    });
                }
            });
            p.appendChild(btn);
        }
        console.error('Store fetch error:', error);
    }
    async batchFetchTaxonomies() {
assets/js/min/crud.min.js
@@ -1 +1 @@
(()=>{class e{constructor(){this.container=document.querySelector(".crud[data-content]:not([data-ignore])"),this.container&&(this.content=this.container.dataset.content,this.endpoint=this.container.dataset.endpoint??"content",this.singular=this.container.dataset.singular,this.plural=this.container.dataset.plural,this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.error=window.jvbError,this.populate=window.jvbPopulate,this.cache=new window.jvbCache(this.content),this.activeItem=null,this.isTimeline=!1,this.isPopulating=!1,this.changes=new Map,this.items=new Map,this.init())}init(){this.initElements(),this.initListeners(),this.defineTemplates();let e=this.initSettings();this.initStore(e),this.checkHideFilters(),this.initIntegrations(),this.initUploader(),this.initModals()}defineTemplates(){const e=window.jvbTemplates,t=this,s=(e,s,i)=>{e.dataset.itemId=i.id;let a=s.checkbox.closest(".preview");window.prefixInput(s.checkbox,`select-${i.id}`,a,!0),s.checkbox.value=i.id,s.checkbox.checked=t.selected.has(parseInt(i.id)),s.selectLabel&&(s.selectLabel.htmlFor=`select-${i.id}`),s.edit&&(s.edit.dataset.id=i.id),s.trash&&(s.trash.dataset.id=i.id)},i=function(e,t,s){let i=s?.fields?.post_thumbnail||s?.fields?.thumbnail;if(i){const e=s.images[i]??{};t.img.src=e.medium??"",t.img.alt=e.alt??s.fields.post_title??""}};e.define("gridView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},setup({el:e,refs:t,manyRefs:a,data:l}){s(e,t,l),i(0,t,l)}}),e.define("listView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},manyRefs:{attrs:"[data-attr]",fields:"[data-field]"},setup({el:e,refs:t,manyRefs:a,data:l}){s(e,t,l),i(0,t,l),a?.attrs?.forEach(e=>{const t=l[e.dataset.attr];t&&""!==t?e.textContent=t:e.remove()}),a?.fields?.forEach(e=>{const t=l.fields?.[e.dataset.field];t&&""!==t?"DIV"===e.tagName?e.innerHTML=t:e.textContent=t:e.remove()})}});let a={};this.isTimeline&&(a.sharedRow="tr.shared",a.point="tr.timeline-point"),e.define("tableView",{refs:{checkbox:".select-item",selectLabel:"label.select-item-label",...a},manyRefs:{inputs:"input,select,textarea",status:'input[name="post_status"]',selectors:'[data-type="selector"]',fields:"[data-field]"},setup({el:e,refs:i,manyRefs:a,data:l}){if(s(e,i,l),a?.inputs?.forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)}),a?.status?.forEach(e=>{e.value===l.status&&(e.checked=!0)}),t.isTimeline)i.sharedRow&&(i.sharedRow.querySelectorAll("input,select,textarea").forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)}),t.populate.populate(i.sharedRow,l),i.sharedRow.querySelectorAll('input[name="post_status"]').forEach(e=>{e.value===l.status&&(e.checked=!0)})),i.point&&l.fields?.timeline&&(Object.entries(l.fields.timeline).forEach(([s,a],n)=>{const o=i.point.cloneNode(!0);o.dataset.index=`${n}`,o.dataset.itemId=a.id,o.querySelectorAll("input,select,textarea").forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${a.id}-`,t)}),t.populate.populate(o,{fields:a,images:l.images,taxonomies:l.taxonomies});const r=l.images?.[a.post_thumbnail];r&&o.querySelector(".field.upload")?.setAttribute("title",r["image-title"]??""),e.insertBefore(o,i.point)}),i.point.remove());else if(void 0!==t.ui.table.form?.dataset.edit)a?.inputs?.forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)}),a?.status?.forEach(e=>{e.value===l.status&&(e.checked=!0)}),t.populate.populate(e,l);else{const e=Object.hasOwn(l,"fields")?l.fields:l;a?.fields?.forEach(t=>{if(Object.hasOwn(e,t.dataset.field)&&""!==e[t.dataset.field]){let s=e[t.dataset.field],i=e.children[0];i&&(i.textContent="date"===t.dataset.field?window.formatTimeAgo(s):s)}})}a?.selectors?.forEach(e=>e.setAttribute("data-lazy",""))}}),e.define("emptyState"),e.define("bulkItem",{refs:{checkbox:"input",img:"img",label:"label"},setup({el:e,refs:t,manyRefs:s,data:i}){t.checkbox&&(t.checkbox.id=`bulk_${i.id}`,t.checkbox.value=i.id,t.checkbox.checked=!0,t.checkbox.name="selected[]");let a=i?.images[i?.fields?.post_thumnbail]??{};t.img&&Object.keys(a).length>0&&(t.img.src=a.medium??"",t.img.alt=a.alt??""),t.label&&(t.label.title=item.fields.post_title)}}),e.define("trashOptions"),e.define("notTrashOptions"),e.define("contentTable")}initElements(){this.allowedFilters=["status","orderby","order","search","date-filter","dateFrom","dateTo"],this.selectors={buttons:{create:".create-item",clearFilters:'[data-action="clear-filters"]'},views:{grid:'input[data-view="grid"]',list:'input[data-view="list"]',table:'input[data-view="table"]'},modals:{create:{modal:"dialog.create",form:"dialog.create form",h2:"dialog.create h2"},edit:{modal:"dialog.edit",form:"dialog.edit form",h2:"dialog.edit h2"},bulkEdit:{modal:"dialog.bulkEdit",selected:"dialog.bulkEdit .selected",h2:"dialog.bulkEdit h2 span",form:"dialog.bulkEdit form"},date:{modal:"dialog.date-range",start:"dialog.date-range .date-start",end:"dialog.date-range .date-end",month:"dialog.date-range .month-select"}},grid:`.${this.content}.item-grid`,table:{nav:"#vertical",form:"form.table",table:"form.table table",body:"form.table body",head:"form.table thead",foot:"form.table tfoot",selectedColumns:".all-filters .multi-select",columns:"thead th"},bulk:{action:".bulk-action-select",count:".bulk-controls .selected-count",control:".bulk-controls .bulk-actions",select:".bulk-controls select",selectAll:".select-all"},filters:{container:"details.all-filters",search:'.all-filters input[type="search"]',status:{all:'[name="status"]#all',publish:'[name="status"]#publish',draft:'[name="status"]#draft',trash:'[name="status"]#trash'},orderby:{date:'[name="orderby"]#date',alphabetical:'[name="orderby"]#alphabetical'},order:{asc:'[name="order"][value="asc"]',desc:'[name="order"][value="desc"]'},date:'[data-filter="date"]'},uploader:{details:"details.uploader",form:"details.uploader form",uploader:'details.uploader [data-field-type="upload"]'}},this.ui=window.uiFromSelectors(this.selectors);const e=document.querySelectorAll('[data-filter="taxonomies"]');e.length>0&&(this.ui.filters.taxonomies={},e.forEach(e=>{const t=e.dataset.taxonomy;this.ui.filters.taxonomies[t]=e,this.allowedFilters.push(`tax_${t}`)})),this.isTimeline=!!document.querySelector("[data-timeline]")}initUploader(){this.ui.uploader.form&&(this.uploadForm=this.forms.registerForm(this.ui.uploader.form).id??!1,window.jvbUploads.subscribe((e,t)=>{if("sent-to-queue"===e&&t.field.id===this.ui.uploader.uploader.dataset.uploader&&(this.uploadForm&&this.forms.store.delete(this.uploadForm),window.debouncer.schedule("crud-complete",()=>{this.store.clearCache()})),"sent-to-queue"===e&&t.field){const e=t.field.config.name,s=t.field.config.itemID;s&&e&&this.changes.has(s)&&delete this.changes.get(s)[e]}}))}initModals(){this.modals={};for(let[e,t]of Object.entries(this.ui.modals))t.modal&&(this.modals[e]=new window.jvbModal(t.modal),this.modals[e].subscribe((t,s)=>{if("modal-close"===t){const t=this.ui.modals[e].form.dataset.formId;t&&this.forms.clearForm(t),this.resetForm(this.ui.modals[e].form),"date"===e&&this.handleCustomDateSelection(),["edit","bulkEdit","create"].includes(e)&&window.debouncer.timeouts.has(`save-${this.content}`)&&this.scheduleSave(0)}}))}initStore(e){let t={...this.defaults,...e};const s=window.jvbStore.register(this.content,[{storeName:this.content,keyPath:"id",endpoint:this.endpoint??"content",headers:{"X-Action-Nonce":window.auth.getNonce("dash")},indexes:[{name:"id",keyPath:"id"},{name:"status",keyPath:"status"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"}],isAuth:!0,filters:t,ignore:["content","user"],TTL:36e5,showLoading:!0},{storeName:"changes",keyPath:"id"}]);this.changesStore=s.changes,this.store=s[this.content],this.store.subscribe((e,t)=>{if("data-loaded"===e)this.render(),this.selectionHandler.collectItems()}),this.changesStore.subscribe((e,t)=>{if("data-ready"===e){let e=this.changesStore.getAll();e.length>0&&(e.forEach(e=>{this.changes.set(e.id,e)}),this.savePosts("",!1).then(()=>{}))}})}initIntegrations(){this.selected=new Set,this.selectionHandler=new window.jvbHandleSelection(this.container,{selectAll:{checkbox:"#select-all",label:".bulk-select label",span:".bulk-select label span"},wrapper:{wrapper:".wrap"},item:{idAttribute:"itemId"}}),this.selectionHandler.subscribe((e,t)=>{this.selected=new Set([...t.selectedItems].map(e=>parseInt(e))),this.ui.bulk.control.hidden=0===this.selected.size,this.ui.bulk.count.hidden=0===this.selected.size,this.ui.bulk.count.textContent=`${this.selected.size} ${this.plural} selected`}),this.forms=window.jvbForm,window.jvbUploads&&window.jvbUploads.subscribe((e,t)=>{"groups_uploaded"===e&&t.content===this.content&&this.handleGroupsUploaded(t)}),this.queue.subscribe((e,t)=>{if(["image_upload","video_upload","document_upload"].includes(t.type)&&"operation-status"===e&&"completed"===t.status&&this.store.clearCache(),"operation-status"===e&&"completed"===t.status&&"uploads/groups"===t.endpoint&&(t.result&&t.result.group_mappings&&(console.log("Handling group mapping from queue response"),this.handleGroupMappings(t.result.group_mappings)),this.store.clearCache()),"operation-status"===e&&"completed"===t.status&&"content_update"===t.type){if(this.store.clearCache(),!t.result||!t.result.success||!t.result.errors)return void console.warn("Content update completed but no results",t);if(Object.keys(t.result.success).length>0&&this.checkCompletedChanges(Object.entries(t.result.success)),Object.keys(t.result.errors).length>0)return void this.checkFailedChanges(Object.entries(t.result.errors));0===Object.keys(t.result.success).length&&(console.log(t.result.success),t.result.success.forEach(e=>this.changesStore.delete(e)),this.store.clearCache())}if("sent-to-server"===e){if(t instanceof FormData)return;for(let[e,s]of Object.entries(t.posts))this.compareStored(e,s)}})}checkCompletedChanges(e){for(let[t,s]of e)this.compareStored(t,s)}compareStored(e,t){let s=this.changesStore.get(e);if(!s)return;for(let[e,i]of Object.entries(t))if(Object.hasOwn(s,e)){let t=window.getDifferences.map(s[e],i);t?s[e]=t:delete s[e]}let i=Object.hasOwn(s,"id"),a=Object.hasOwn(s,"content");i&&a&&2===Object.keys(s).length||(i||a)&&1===Object.keys(s).length||0===Object.keys(s).length?(this.changesStore.delete(e),this.store.clearCache()):this.changesStore.save(s)}checkFailedChanges(e){}initSettings(){this.defaults={content:this.content,user:window.auth.getUser(),page:1,status:"all",orderby:"date",order:"desc",search:""};let e={},t=this.container.dataset.view??"grid";this.view=this.cache.get("view")??t,this.view!==t&&(this.ui.views[this.view].checked=!0),this.status=this.cache.get("status")??this.defaults.status,this.status!==this.defaults.status&&(this.ui.filters.status[this.status].checked=!0,e.status=this.status),this.orderby=this.cache.get("orderby")??this.defaults.orderby,this.orderby!==this.defaults.orderby&&(this.ui.filters.orderby[this.orderby].checked=!0,e.orderBy=this.orderby),this.order=this.cache.get("order")??this.defaults.order,this.order!==this.defaults.order&&(this.ui.filters.order[this.order].checked=!0,e.order=this.order),this.ui.filters.taxonomies&&Object.entries(this.ui.filters.taxonomies).forEach(([t,s])=>{const i=`tax_${t}`,a=this.cache.get(i);a&&(s.value=a,e[i]=a)});let s=this.cache.get("tabNav")??"horizontal";this.ui.table.nav&&"vertical"===s&&(this.ui.table.nav.checked=!0);let i={showFilters:{element:this.ui.filters.container,default:"closed"},showUploader:{element:this.ui.uploader.details,default:"open"}};for(let[e,t]of Object.entries(i))if(t.element){let s=this.cache.get(e)??t.default;t.element.open="open"===s,t.element.addEventListener("toggle",()=>{this.cache.set(e,t.element.open?"open":"closed")})}return e}initListeners(){this.changeHandler=this.handleChange.bind(this),this.clickHandler=this.handleClick.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleModalSubmit.bind(this),document.addEventListener("change",this.changeHandler),document.addEventListener("click",this.clickHandler),this.ui.filters.search&&this.ui.filters.search.addEventListener("input",this.inputHandler);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.addEventListener("submit",this.submitHandler)}handleModalSubmit(e){e.preventDefault();const t=e.target.closest("dialog");if(!t)return;if(t.classList.contains("create"))return void this.handleCreateSubmit(t);this.plural;this.scheduleSave(0),this.modals.edit.handleClose()}async handleCreateSubmit(e){e.dataset.itemId;this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());const t=await this.changesStore.getAll();if(0===t.length)return;let s={};t.forEach(e=>{const{id:t,...i}=e;s[t]=i});let i=this.queue.addToQueue({endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:s},popup:`Creating your new ${this.singular}`,title:`Creating your new ${this.singular}`});if(!i)return;const a=e.querySelectorAll("[data-upload-field]");for(const e of a){const t=e.dataset.uploader;if(!t)continue;0!==window.jvbUploads.stores.uploads.filterByIndex({field:t}).length&&await window.jvbUploads.queueUploads("uploads",t,i)}}handleChange(e){const t=e.target.closest("[data-item-id]"),s=e.target.matches("[data-filter]"),i=e.target.matches(".bulk-action-select"),a=e.target.matches("[data-view]");if(t||s||i||a)if(this.isPopulating||!t||e.target.closest("[data-ignore], .select-item")){if(a)return this.items.clear(),void this.handleViewChange(e.target);if(i)this.handleBulkAction(e.target);else if(s)this.handleFilterChange(e.target);else if("table"===this.view){if(e.target.matches("details.multi-select"))return void this.toggleColumn(e.target.id,e.target.checked);e.target.matches(this.selectors.table.nav)&&(this.tabNav=e.target.checked,this.cache.set("tabNav",e.target.checked?"vertical":"horizontal"))}}else this.handleItemUpdate(e)}handleBulkAction(e){if(e.value.startsWith("tax-")){const t=e.options[e.selectedIndex],s=t.dataset.taxonomy,i=t.dataset.single,a=t.dataset.plural;return window.jvbSelector.openEmpty(s,i,a,e=>this.handleBulkTaxonomy(e)),void(e.value="")}switch(e.value){case"edit":this.openBulkEditModal();break;case"publish":case"trash":case"delete":this.setBulkStatus(e.value);break;case"draft":case"restore":this.setBulkStatus("draft")}}handleBulkTaxonomy(e){e.termIds.length&&this.selected.size&&(this.selected.forEach(t=>{const s=this.store.get(t);if(!s)return;const i=(s.taxonomies?.[e.taxonomy]||[]).map(e=>e.id),a=[...new Set([...i,...e.termIds])];this.updateItem(t,e.taxonomy,a)}),this.savePosts(`Adding ${e.terms.length} ${e.taxonomy} to ${this.selected.size} ${this.plural}...`).then(()=>{}),this.selectionHandler.clearSelection())}handleItemUpdate(e){let t=window.targetCheck(e,"[data-item-id]");if(!t)return;const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');let i,a;if(s)i=s.dataset.field,a=this.forms.getFieldValue(s);else{let t=e.target.closest("[data-field]");i=t.dataset.field,a=this.forms.getFieldValue(e.target)}t.dataset.itemId.split(",").forEach(e=>{this.updateItem(e,i,a)})}updateItem(e,t,s){if(this.isPopulating)return;t.replace(`[${e}]`,"");const i=this.store.get(e);if(i){const a=i.fields?.[t]??i[t];if(null===window.getDifferences.map(a,s)){if(this.changes.has(e)){delete this.changes.get(e)[t];0===Object.keys(this.changes.get(e)).filter(e=>"id"!==e&&"content"!==e).length&&(this.changes.delete(e),this.changesStore.delete(e))}return}}this.changes.has(e)||this.changes.set(e,{id:e,content:this.content}),this.changes.get(e)[t]=s,this.scheduleBackup(),"number"!=typeof e&&String(e).includes("group")||this.scheduleSave()}scheduleBackup(){window.debouncer.schedule(`changes-${this.content}`,async()=>{this.changes.size>0&&await this.handleBackup()},2e3)}cancelBackup(){window.debouncer.cancel(`changes-${this.content}`)}async handleBackup(){const e=Array.from(this.changes.values());this.changes.clear();const t=e.map(e=>e.id),s=await Promise.all(t.map(e=>this.changesStore.get(e))),i=e.map((e,t)=>s[t]?window.deepMerge(s[t],e):e);await this.changesStore.saveMany(i)}scheduleSave(e=1e4){window.debouncer.schedule(`save-${this.content}`,async()=>{this.changes.size>0&&(this.cancelBackup(),await this.handleBackup()),await this.savePosts("",!1)},e)}handleFilterChange(e){let t=e.dataset.filter;return"date"===t&&"custom"===e.value?(e.value="",void this.modals.date.handleOpen()):"date"===t&&""!==e.value?(this.setFilter("date-filter",e.value),this.deleteFilter("dateFrom"),this.deleteFilter("dateTo"),void this.checkHideFilters()):("taxonomies"===t&&(t=`tax_${e.dataset.taxonomy}`),void this.setFilter(t,e.value))}checkHideFilters(){const e=this.store.filters,t=Object.entries(e).some(([e,t])=>!["content","user","page"].includes(e)&&(this.defaults[e]!==t&&""!==t&&null!==t));this.ui.buttons.clearFilters.hidden=!t}clearAllFilters(){let e=this.store.filters;this.store.clearFilters();for(let[t,s]of Object.entries(e))this.cache.remove(t),this.deleteFilter(t,s);this.a11y.announce("All filters cleared")}handleCustomDateSelection(){if(this.ui.modals.date.month&&this.ui.modals.date.month.value){const[e,t]=this.ui.modals.date.month.value.split("-"),s=`${e}-${t}-01`,i=new Date(e,parseInt(t),0).getDate(),a=`${e}-${t}-${String(i).padStart(2,"0")}`;this.setFilter("dateFrom",s),this.setFilter("dateTo",a),this.deleteFilter("date-filter"),this.ui.modals.date.month.value=""}else this.ui.modals.date.start&&this.ui.modals.date.start.value&&this.ui.modals.date.end&&this.ui.modals.date.end.value&&(this.setFilter("dateFrom",this.ui.modals.date.start.value),this.setFilter("dateTo",this.ui.modals.date.end.value),this.deleteFilter("date-filter"),this.ui.modals.date.start.value="",this.ui.modals.date.end.value="");this.checkHideFilters()}handleViewChange(e){this.view=e.dataset.view,this.cache.set("view",this.view),this.render()}handleClick(e){if(e.target.matches(".clear-search"))return void this.deleteFilter("search","");const t=e.target.closest("[data-action]");return t?(e.preventDefault(),void this.handleActionButton(t)):e.target.matches(".apply-date-filter")?(this.handleCustomDateSelection(),void this.modals.date.handleClose()):void((e.target.matches(this.selectors.buttons.create)||e.target.closest(this.selectors.buttons.create))&&this.openCreateModal())}openCreateModal(){this.forms.registerForm(this.ui.modals.create.form,{cache:!1}),this.ui.modals.create.modal.dataset.itemId=window.generateID("new"),this.modals.create.handleOpen()}handleActionButton(e){const t=e.dataset.id;switch(e.dataset.action){case"edit":this.openEditModal(t);break;case"delete":confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then(()=>{}),this.store.delete(t));break;case"trash":"trash"===this.status?confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then(()=>{}),this.store.delete(t)):(this.updateItem(t,"post_status","trash"),window.fade(e.closest(".item"),!1),this.savePosts(`Sending ${this.singular} to trash...`).then(()=>{}));break;case"bulk-edit":this.selected.size>0&&this.openBulkEditModal();break;case"bulk-delete":this.handleBulkDelete();break;case"refresh":this.store.clearCache(),this.store.fetch();break;case"clear-filters":this.clearAllFilters()}}handleBulkDelete(){let e="trash"===this.status;if(this.selected.size>0&&confirm(`${e?"Permanently delete":"Send"} ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}${e?"":"to trash"}?`)){this.selected.forEach(t=>{this.store.delete(t),this.updateItem(t,"post_status",e?"delete":"trash")});let t=e?`Permanently deleting ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}`:`Sending ${this.selected.size} ${1===this.selected.size?this.singular:this.plural} to trash`;this.savePosts(t).then(()=>{}),this.selectionHandler.clearSelection()}}handleInput(e){e.preventDefault(),e.stopPropagation();let t=e.target.value.trim(),s=`${this.content}-search`;0!==t.length?window.debouncer.schedule(s,()=>{this.a11y.announce(`Searching for "${t}"...`),this.store.setFilters({search:t,page:1})},300):this.deleteFilter("search","")}handleKeys(e){if(this.tabNav&&"Tab"===e.key){e.preventDefault();const t=e.target.closest("[data-field]"),s=e.target.closest("tr");if(!t||!s)return;const i=t.dataset.field,a=e.shiftKey;let l=this.findNextEditableRow(s,a);l||(l=this.wrapToRow(s,a)),l&&this.focusFieldInRow(l,i,a)}}findNextEditableRow(e,t=!1){let s=t?e.previousElementSibling:e.nextElementSibling;for(;s&&!this.isEditableRow(s);)s=t?s.previousElementSibling:s.nextElementSibling;return s}wrapToRow(e,t=!1){if(this.isTimeline){const s=e.closest("tbody");if(!s)return null;const i=Array.from(s.querySelectorAll("tr")).filter(e=>this.isEditableRow(e));return t?i[i.length-1]:i[0]}{if(!this.ui.table.body)return null;const e=Array.from(this.ui.table.body.querySelectorAll("tr")).filter(e=>this.isEditableRow(e));return t?e[e.length-1]:e[0]}}isEditableRow(e){return!e.closest("thead")&&!e.closest("tfoot")&&(this.isTimeline?e.classList.contains("shared")||e.classList.contains("timeline-point"):!!e.dataset.itemId)}focusFieldInRow(e,t,s=!1){const i=e.querySelector(`[data-field="${t}"]`);if(!i)return;const a=this.findFocusableInput(i);if(a){a.focus(),a.select&&"text"===a.type&&a.select();const e=s?"next":"previous";this.a11y?.announce(`Moved to ${t} in ${e} row`)}}findFocusableInput(e){const t=['input:not([type="hidden"]):not([disabled])',"textarea:not([disabled])","select:not([disabled])","button:not([disabled])"];for(const s of t){const t=e.querySelector(s);if(t)return t}return null}openEditModal(e){let t,s=this.store.get(parseInt(e));s&&(this.activeItem=s.id,this.ui.modals.edit.modal.dataset.itemId=e,this.ui.modals.edit.modal.dataset.content=this.content,Object.hasOwn(s.fields,"post_title")?t=s.fields.post_title:Object.hasOwn(s.fields,"name")&&(t=s.fields.name),this.ui.modals.edit.h2.textContent=`Editing ${""===t?this.singular:t}`,this.ui.modals.edit.form.dataset.formId=`edit-${e}`,this.modals.edit.handleOpen(),this.forms.registerForm(this.ui.modals.edit.form,{cache:!1,autoUpload:!0}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,s),requestAnimationFrame(()=>{requestAnimationFrame(()=>{this.isPopulating=!1})}))}openBulkEditModal(){window.removeChildren(this.ui.modals.bulkEdit.selected),this.ui.modals.edit.form.reset(),window.chunkIt(this.selected,t=>{let s=this.store.get(parseInt(t));if(s)return e.push(s.id),window.jvbTemplates.create("bulkItem",s)},e=>this.ui.modals.bulkEdit.selected.append(e)).then(()=>{});let e=Array.from(this.selected).map(e=>this.store.get(parseInt(e))).filter(Boolean);this.ui.modals.bulkEdit.modal.dataset.itemId=e.join(","),this.ui.modals.bulkEdit.h2&&(this.ui.modals.bulkEdit.h2.textContent=this.selected.size),this.modals.bulkEdit.handleOpen(),this.forms.registerForm(this.ui.modals.bulkEdit.form,{cache:!1}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,item),requestAnimationFrame(()=>{requestAnimationFrame(()=>{this.isPopulating=!1})})}async savePosts(e="",t=!1){this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());let s=await this.changesStore.getAll();if(0===s.length)return;if(s=this.validateChanges(s),0===s.length)return;""===e&&(e=`Saving ${s.length} ${1===s.length?this.singular:this.plural}`);let i={},a=[];s.forEach(e=>{let t=e.id;const{id:s,...l}=e;i[t]=l,e.post_status&&this.shouldRemoveItemUI(e.post_status)&&a.push(t)}),a.length>0&&this.removeItems(a);let l={endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:i},delay:t,popup:"Saving changes",title:e};this.queue.addToQueue(l)}validateChanges(e){return e.reduce((e,t)=>{const{id:s,content:i,...a}=t,l=this.store.get(s);if(!l)return e.push(t),e;const n={id:s,content:i};let o=!1;for(const[e,t]of Object.entries(a)){const s=l.fields?.[e]??l[e];null!==window.getDifferences.map(s,t)&&(n[e]=t,o=!0)}return o?e.push(n):(this.changes.delete(s),this.changesStore.delete(s)),e},[])}setBulkStatus(e){if(!["publish","draft","trash","delete"].includes(e))return;let t,s=[];if(this.selected.forEach(t=>{s.push(t),this.updateItem(t,"post_status",e)}),"delete"===e)t="Deleting";else t=window.uppercaseFirst(e)+"ing";this.shouldRemoveItemUI(e)&&this.removeItems(s),this.selectionHandler.clearSelection(),this.savePosts(`${t} ${s.length} ${1===s.length?this.singular:this.plural}...`).then(()=>{})}render(){const e=this.store.getFiltered();if(0!==e.length){switch(this.view){case"grid":this.renderGrid(e);break;case"table":this.renderTable(e).then(()=>{});break;case"list":this.renderList(e)}this.updateUI()}else this.renderEmpty()}updateUI(){if(this.ui.bulk.action){let e=!1,t=this.ui.bulk.action.querySelector('[value="edit"]'),s=this.status;"trash"===s&&t?(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("trashOptions")):"trash"===s||t||(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("notTrashOptions")),e&&e.querySelectorAll("option").forEach((e,t)=>{0===t&&(e.checked=!0),this.ui.bulk.action.append(e)}),this.ui.bulk.action.value=""}this.selected.size>0&&this.selectionHandler.updateSelectionUI()}renderEmpty(){this.toggleTable(!1),window.removeChildren(this.ui.grid);const e=window.jvbTemplates.create("emptyState");e&&(this.ui.grid.append(e),this.a11y.announceItems(0,!1,!1))}toggleTable(e=!0){if(this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e),e&&!this.ui.table.form){let e=window.jvbTemplates.create("contentTable");this.container.append(e),this.ui.table=window.uiFromSelectors(this.selectors.table),this.ui.table.columns=this.container.querySelectorAll(this.selectors.table.columns)}this.ui.table.form&&(this.ui.table.form.hidden=!e,e||this.forms.clearForm(this.ui.table.form.dataset.formId),this.ui.table.body&&window.removeChildren(this.ui.table.body)),this.keyHandler=this.handleKeys.bind(this),e?document.addEventListener("keydown",this.keyHandler):document.removeEventListener("keydown",this.keyHandler)}renderGrid(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("list-view"),this.ui.grid.classList.add("grid-view"),window.chunkIt(e,e=>this.renderGridItem(e),e=>this.ui.grid.append(e)).then(()=>{})}renderList(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("grid-view"),this.ui.grid.classList.add("list-view"),window.chunkIt(e,e=>this.renderListItem(e),e=>this.ui.grid.append(e)).then(()=>{})}async renderTable(e){this.toggleTable(),window.removeChildren(this.ui.grid),await window.chunkIt(e,e=>this.renderTableItem(e),e=>{this.ui.table.body?this.ui.table.body.append(e):this.ui.table.table.insertBefore(e,this.ui.table.foot)},5),requestAnimationFrame(()=>{window.jvbSelector?.scanExistingFields(this.ui.table.table)})}renderGridItem(e){let t=window.jvbTemplates.create("gridView",e);return this.items.set(e.id,t),t}renderListItem(e){let t=window.jvbTemplates.create("listView",e);return this.items.set(e.id,t),t}renderTableItem(e){let t=window.jvbTemplates.create("tableView",e);return this.items.set(e.id,t),t}toggleColumn(e,t){this.ui.table.table.querySelectorAll(`.${e}`).forEach(e=>{e.hidden=!t})}handleGroupsUploaded(e){const{posts:t,fieldId:s}=e;let i=window.jvbUploads,a=(i.fields.get(s),[]);t.forEach(e=>{const t={id:e.groupId,title:e.fields.post_title||`New ${this.singular}`,status:"draft",date:(new Date).toISOString(),modified:(new Date).toISOString(),thumbnail:null,icon:this.content,taxonomies:{},fields:e.fields,images:{}};e.images.forEach((e,s)=>{let a=e.upload_id;0===s&&(t.fields.post_thumbnail=e);let l=i.stores.uploads.get(a);l&&(t.images[a]={"image-alt-text":"","image-caption":"","image-title":l.fields.originalName,medium:i.createPreviewUrl(i.formatFile(l))})}),a.push(t)}),this.store.saveMany(a).then(()=>this.render()),this.a11y.announce(`${t.length} ${1===t.length?this.singular:this.plural} created. Waiting for server confirmation...`)}handleGroupMappings(e){for(const[t,s]of Object.entries(e)){let e={};this.changes.has(t)&&(e=this.changes.get(t),this.changes.delete(t));let i=this.changesStore.get(t)??{};(e.size>0||i.size>0)&&(e=window.deepMerge(i,e),this.changes.set(s,e),this.scheduleBackup())}}shouldRemoveItemUI(e){return"all"===this.status&&!["publish","draft"].includes(e)||e!==this.store.filters.status}removeItems(e){e.forEach(e=>{if(this.items.has(e)){let t=this.items.get(e);t&&window.fade(t,!1)}})}setFilters(e){for(let[t,s]of Object.entries(e)){if(!this.allowedFilters.includes(t)){delete e[t];continue}this.cache.set(t,s);let i=this.findFilterEl(t);this.setElValue(i,s)}this.store.setFilters(e)}setFilter(e,t){if(!this.allowedFilters.includes(e))return;this.cache.set(e,t),"status"===e&&(this.status=t),"orderby"===e&&(this.orderby=t),"order"===e&&(this.order=t);let s=this.findFilterEl(e,t);this.setElValue(s,t),this.store.setFilter(e,t)}deleteFilter(e,t){if(!this.allowedFilters.includes(e))return;if(Object.hasOwn(this.defaults,e))return void this.setFilter(e,this.defaults[e]);let s=this.findFilterEl(e,t);this.setElValue(s,!1),this.cache.remove(e),this.setFilter(e,"")}setElValue(e,t){if(e){if(!t)return["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=""),["text","search"].includes(e.type)&&(e.value=""),void("radio"===e.type&&(e.checked=!1));["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=t),["text","search"].includes(e.type)&&(e.value=t),"radio"===e.type&&(e.checked=!0)}}findFilterEl(e,t){if(["date-filter","dateFrom","dateTo"].includes(e)){switch(e){case"date-filter":e="month";break;case"dateFrom":e="start";break;case"dateTo":e="end"}return this.ui.modals.date[e]}if(e.includes("tax_")){const t=e.replace("tax_",""),s=this.ui.filters.taxonomies?.[t];return s||(console.warn("Taxonomy filter element not found:",t),null)}if(!Object.hasOwn(this.ui.filters,e))return console.warn("Filter el not found: ",e),!1;let s=this.ui.filters[e];if("object"==typeof s){if(!Object.hasOwn(this.ui.filters[e],t))return!1;s=this.ui.filters[e][t]}return s}resetForm(e){e.querySelectorAll('input[type="hidden"], input[type="text"], input[type="number"], input[type="email"], input[type="url"], textarea').forEach(e=>{e.value=""}),e.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(e=>{e.checked=!1}),e.querySelectorAll("select").forEach(e=>{e.selectedIndex=0}),e.querySelectorAll(".selected-items").forEach(e=>{window.removeChildren(e)}),e.querySelectorAll(".item-grid.preview").forEach(e=>{window.removeChildren(e)})}destroy(){window.debouncer.cancel(`changes-${this.content}`),this.changes.size>0&&(this.changesStore.saveMany(this.changes).then(()=>{}),this.changes.clear()),this.timelineSortables&&(this.timelineSortables.forEach(e=>e.destroy()),this.timelineSortables=[]);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.removeEventListener("submit",this.submitHandler);document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),this.ui.filters.search&&this.ui.filters.search.removeEventListener("input",this.handleInput)}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{if("auth-loaded"===t){let t=document.querySelector("[data-content]");t&&!Object.hasOwn(t.dataset,"ignore")&&(window.crudManager=new e({content:t.dataset.content}))}})})})();
(()=>{class e{constructor(){this.container=document.querySelector(".crud[data-content]:not([data-ignore])"),this.container&&(this.content=this.container.dataset.content,this.endpoint=this.container.dataset.endpoint??"content",this.singular=this.container.dataset.singular,this.plural=this.container.dataset.plural,this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.error=window.jvbError,this.populate=window.jvbPopulate,this.cache=new window.jvbCache(this.content),this.activeItem=null,this.isTimeline=!1,this.isPopulating=!1,this.changes=new Map,this.items=new Map,this.init())}init(){this.initElements(),this.initListeners(),this.defineTemplates();let e=this.initSettings();this.initStore(e),this.checkHideFilters(),this.initIntegrations(),this.initUploader(),this.initModals()}defineTemplates(){const e=window.jvbTemplates,t=this,s=(e,s,i)=>{e.dataset.itemId=i.id;let a=s.checkbox.closest(".preview");window.prefixInput(s.checkbox,`select-${i.id}`,a,!0),s.checkbox.value=i.id,s.checkbox.checked=t.selected.has(parseInt(i.id)),s.selectLabel&&(s.selectLabel.htmlFor=`select-${i.id}`),s.edit&&(s.edit.dataset.id=i.id),s.trash&&(s.trash.dataset.id=i.id)},i=function(e,t,s){let i=s?.fields?.post_thumbnail||s?.fields?.thumbnail;if(i){const e=s.images[i]??{};t.img.src=e.medium??"",t.img.alt=e.alt??s.fields.post_title??""}};e.define("gridView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},setup({el:e,refs:t,manyRefs:a,data:l}){s(e,t,l),i(0,t,l)}}),e.define("listView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},manyRefs:{attrs:"[data-attr]",fields:"[data-field]"},setup({el:e,refs:t,manyRefs:a,data:l}){s(e,t,l),i(0,t,l),a?.attrs?.forEach(e=>{const t=l[e.dataset.attr];t&&""!==t?e.textContent=t:e.remove()}),a?.fields?.forEach(e=>{const t=l.fields?.[e.dataset.field];t&&""!==t?"DIV"===e.tagName?e.innerHTML=t:e.textContent=t:e.remove()})}});let a={};this.isTimeline&&(a.sharedRow="tr.shared",a.point="tr.timeline-point"),e.define("tableView",{refs:{checkbox:".select-item",selectLabel:"label.select-item-label",...a},manyRefs:{inputs:"input,select,textarea",status:'input[name="post_status"]',selectors:'[data-type="selector"]',fields:"[data-field]"},setup({el:e,refs:i,manyRefs:a,data:l}){if(s(e,i,l),a?.inputs?.forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)}),a?.status?.forEach(e=>{e.value===l.status&&(e.checked=!0)}),t.isTimeline)i.sharedRow&&(i.sharedRow.querySelectorAll("input,select,textarea").forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)}),t.populate.populate(i.sharedRow,l),i.sharedRow.querySelectorAll('input[name="post_status"]').forEach(e=>{e.value===l.status&&(e.checked=!0)})),i.point&&l.fields?.timeline&&(Object.entries(l.fields.timeline).forEach(([s,a],n)=>{const o=i.point.cloneNode(!0);o.dataset.index=`${n}`,o.dataset.itemId=a.id,o.querySelectorAll("input,select,textarea").forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${a.id}-`,t)}),t.populate.populate(o,{fields:a,images:l.images,taxonomies:l.taxonomies});const r=l.images?.[a.post_thumbnail];r&&o.querySelector(".field.upload")?.setAttribute("title",r["image-title"]??""),e.insertBefore(o,i.point)}),i.point.remove());else if(void 0!==t.ui.table.form?.dataset.edit)a?.inputs?.forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)}),a?.status?.forEach(e=>{e.value===l.status&&(e.checked=!0)}),t.populate.populate(e,l);else{const e=Object.hasOwn(l,"fields")?l.fields:l;a?.fields?.forEach(t=>{if(Object.hasOwn(e,t.dataset.field)&&""!==e[t.dataset.field]){let s=e[t.dataset.field],i=e.children[0];i&&(i.textContent="date"===t.dataset.field?window.formatTimeAgo(s):s)}})}a?.selectors?.forEach(e=>e.setAttribute("data-lazy",""))}}),e.define("emptyState"),e.define("bulkItem",{refs:{checkbox:"input",img:"img",label:"label"},setup({el:e,refs:t,manyRefs:s,data:i}){t.checkbox&&(t.checkbox.id=`bulk_${i.id}`,t.checkbox.value=i.id,t.checkbox.checked=!0,t.checkbox.name="selected[]");let a=i?.images[i?.fields?.post_thumnbail]??{};t.img&&Object.keys(a).length>0&&(t.img.src=a.medium??"",t.img.alt=a.alt??""),t.label&&(t.label.title=item.fields.post_title)}}),e.define("trashOptions"),e.define("notTrashOptions"),e.define("contentTable")}initElements(){this.allowedFilters=["status","orderby","order","search","date-filter","dateFrom","dateTo"],this.selectors={buttons:{create:".create-item",clearFilters:'[data-action="clear-filters"]'},views:{grid:'input[data-view="grid"]',list:'input[data-view="list"]',table:'input[data-view="table"]'},modals:{create:{modal:"dialog.create",form:"dialog.create form",h2:"dialog.create h2"},edit:{modal:"dialog.edit",form:"dialog.edit form",h2:"dialog.edit h2"},bulkEdit:{modal:"dialog.bulkEdit",selected:"dialog.bulkEdit .selected",h2:"dialog.bulkEdit h2 span",form:"dialog.bulkEdit form"},date:{modal:"dialog.date-range",start:"dialog.date-range .date-start",end:"dialog.date-range .date-end",month:"dialog.date-range .month-select"}},grid:`.${this.content}.item-grid`,table:{nav:"#vertical",form:"form.table",table:"form.table table",body:"form.table body",head:"form.table thead",foot:"form.table tfoot",selectedColumns:".all-filters .multi-select",columns:"thead th"},bulk:{action:".bulk-action-select",count:".bulk-controls .selected-count",control:".bulk-controls .bulk-actions",select:".bulk-controls select",selectAll:".select-all"},filters:{container:"details.all-filters",search:'.all-filters input[type="search"]',status:{all:'[name="status"]#all',publish:'[name="status"]#publish',draft:'[name="status"]#draft',trash:'[name="status"]#trash'},orderby:{date:'[name="orderby"]#date',alphabetical:'[name="orderby"]#alphabetical'},order:{asc:'[name="order"][value="asc"]',desc:'[name="order"][value="desc"]'},date:'[data-filter="date"]'},uploader:{details:"details.uploader",form:"details.uploader form",uploader:'details.uploader [data-field-type="upload"]'}},this.ui=window.uiFromSelectors(this.selectors);const e=document.querySelectorAll('[data-filter="taxonomies"]');e.length>0&&(this.ui.filters.taxonomies={},e.forEach(e=>{const t=e.dataset.taxonomy;this.ui.filters.taxonomies[t]=e,this.allowedFilters.push(`tax_${t}`)})),this.isTimeline=!!document.querySelector("[data-timeline]")}initUploader(){this.ui.uploader.form&&(this.uploadForm=this.forms.registerForm(this.ui.uploader.form).id??!1,window.jvbUploads.subscribe((e,t)=>{if("sent-to-queue"===e&&t.field.id===this.ui.uploader.uploader.dataset.uploader&&(this.uploadForm&&this.forms.store.delete(this.uploadForm),window.debouncer.schedule("crud-complete",()=>{this.store.clearCache()})),"sent-to-queue"===e&&t.field){const e=t.field.config.name,s=t.field.config.itemID;s&&e&&this.changes.has(s)&&delete this.changes.get(s)[e]}}))}initModals(){this.modals={};for(let[e,t]of Object.entries(this.ui.modals))t.modal&&(this.modals[e]=new window.jvbModal(t.modal),this.modals[e].subscribe((t,s)=>{if("modal-close"===t){const t=this.ui.modals[e].form.dataset.formId;t&&this.forms.clearForm(t),this.resetForm(this.ui.modals[e].form),"date"===e&&this.handleCustomDateSelection(),["edit","bulkEdit","create"].includes(e)&&window.debouncer.timeouts.has(`save-${this.content}`)&&this.scheduleSave(0)}}))}initStore(e){let t={...this.defaults,...e};const s=window.jvbStore.register(this.content,[{storeName:this.content,keyPath:"id",endpoint:this.endpoint??"content",headers:{"X-Action-Nonce":window.auth.getNonce("dash")},indexes:[{name:"id",keyPath:"id"},{name:"status",keyPath:"status"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"}],isAuth:!0,filters:t,ignore:["content","user"],TTL:36e5,showLoading:!0},{storeName:"changes",keyPath:"id"}]);this.changesStore=s.changes,this.store=s[this.content],this.store.subscribe((e,t)=>{if("data-loaded"===e)this.render(),this.selectionHandler.collectItems()}),this.changesStore.subscribe((e,t)=>{if("data-ready"===e){let e=this.changesStore.getAll();e.length>0&&(e.forEach(e=>{this.changes.set(e.id,e)}),this.savePosts("",!1).then(()=>{}))}})}initIntegrations(){this.selected=new Set,this.selectionHandler=new window.jvbHandleSelection(this.container,{selectAll:{checkbox:"#select-all",label:".bulk-select label",span:".bulk-select label span"},wrapper:{wrapper:".wrap"},item:{idAttribute:"itemId"}}),this.selectionHandler.subscribe((e,t)=>{this.selected=new Set([...t.selectedItems].map(e=>parseInt(e))),this.ui.bulk.control.hidden=0===this.selected.size,this.ui.bulk.count.hidden=0===this.selected.size,this.ui.bulk.count.textContent=`${this.selected.size} ${this.plural} selected`}),this.forms=window.jvbForm,window.jvbUploads&&window.jvbUploads.subscribe((e,t)=>{"groups_uploaded"===e&&t.content===this.content&&this.handleGroupsUploaded(t)}),this.queue.subscribe((e,t)=>{if(["image_upload","video_upload","document_upload"].includes(t.type)&&"operation-status"===e&&"completed"===t.status&&this.store.clearCache(),"operation-status"===e&&"completed"===t.status&&"uploads/groups"===t.endpoint&&(t.result&&t.result.group_mappings&&(console.log("Handling group mapping from queue response"),this.handleGroupMappings(t.result.group_mappings)),this.store.clearCache()),"operation-status"===e&&"completed"===t.status&&"content_update"===t.type){if(this.store.clearCache(),!t.result||!t.result.success||!t.result.errors)return void console.warn("Content update completed but no results",t);if(Object.keys(t.result.success).length>0&&this.checkCompletedChanges(Object.entries(t.result.success)),Object.keys(t.result.errors).length>0)return void this.checkFailedChanges(Object.entries(t.result.errors));0===Object.keys(t.result.success).length&&(console.log(t.result.success),t.result.success.forEach(e=>this.changesStore.delete(e)),this.store.clearCache())}if("sent-to-server"===e&&"content_update"===t.type){if(t instanceof FormData)return;for(let[e,s]of Object.entries(t.posts))this.compareStored(e,s)}})}checkCompletedChanges(e){for(let[t,s]of e)this.compareStored(t,s)}compareStored(e,t){let s=this.changesStore.get(e);if(!s)return;for(let[e,i]of Object.entries(t))if(Object.hasOwn(s,e)){let t=window.getDifferences.map(s[e],i);t?s[e]=t:delete s[e]}let i=Object.hasOwn(s,"id"),a=Object.hasOwn(s,"content");i&&a&&2===Object.keys(s).length||(i||a)&&1===Object.keys(s).length||0===Object.keys(s).length?(this.changesStore.delete(e),this.store.clearCache()):this.changesStore.save(s)}checkFailedChanges(e){}initSettings(){this.defaults={content:this.content,user:window.auth.getUser(),page:1,status:"all",orderby:"date",order:"desc",search:""};let e={},t=this.container.dataset.view??"grid";this.view=this.cache.get("view")??t,this.view!==t&&(this.ui.views[this.view].checked=!0),this.status=this.cache.get("status")??this.defaults.status,this.status!==this.defaults.status&&(this.ui.filters.status[this.status].checked=!0,e.status=this.status),this.orderby=this.cache.get("orderby")??this.defaults.orderby,this.orderby!==this.defaults.orderby&&(this.ui.filters.orderby[this.orderby].checked=!0,e.orderBy=this.orderby),this.order=this.cache.get("order")??this.defaults.order,this.order!==this.defaults.order&&(this.ui.filters.order[this.order].checked=!0,e.order=this.order),this.ui.filters.taxonomies&&Object.entries(this.ui.filters.taxonomies).forEach(([t,s])=>{const i=`tax_${t}`,a=this.cache.get(i);a&&(s.value=a,e[i]=a)});let s=this.cache.get("tabNav")??"horizontal";this.ui.table.nav&&"vertical"===s&&(this.ui.table.nav.checked=!0);let i={showFilters:{element:this.ui.filters.container,default:"closed"},showUploader:{element:this.ui.uploader.details,default:"open"}};for(let[e,t]of Object.entries(i))if(t.element){let s=this.cache.get(e)??t.default;t.element.open="open"===s,t.element.addEventListener("toggle",()=>{this.cache.set(e,t.element.open?"open":"closed")})}return e}initListeners(){this.changeHandler=this.handleChange.bind(this),this.clickHandler=this.handleClick.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleModalSubmit.bind(this),document.addEventListener("change",this.changeHandler),document.addEventListener("click",this.clickHandler),this.ui.filters.search&&this.ui.filters.search.addEventListener("input",this.inputHandler);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.addEventListener("submit",this.submitHandler)}handleModalSubmit(e){e.preventDefault();const t=e.target.closest("dialog");if(!t)return;if(t.classList.contains("create"))return void this.handleCreateSubmit(t);this.plural;this.scheduleSave(0),this.modals.edit.handleClose()}async handleCreateSubmit(e){e.dataset.itemId;this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());const t=await this.changesStore.getAll();if(0===t.length)return;let s={};t.forEach(e=>{const{id:t,...i}=e;s[t]=i});let i=this.queue.addToQueue({endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:s},popup:`Creating your new ${this.singular}`,title:`Creating your new ${this.singular}`});if(!i)return;const a=e.querySelectorAll("[data-upload-field]");for(const e of a){const t=e.dataset.uploader;if(!t)continue;0!==window.jvbUploads.stores.uploads.filterByIndex({field:t}).length&&await window.jvbUploads.queueUploads("uploads",t,i)}}handleChange(e){const t=e.target.closest("[data-item-id]"),s=e.target.matches("[data-filter]"),i=e.target.matches(".bulk-action-select"),a=e.target.matches("[data-view]");if(t||s||i||a)if(this.isPopulating||!t||e.target.closest("[data-ignore], .select-item")){if(a)return this.items.clear(),void this.handleViewChange(e.target);if(i)this.handleBulkAction(e.target);else if(s)this.handleFilterChange(e.target);else if("table"===this.view){if(e.target.matches("details.multi-select"))return void this.toggleColumn(e.target.id,e.target.checked);e.target.matches(this.selectors.table.nav)&&(this.tabNav=e.target.checked,this.cache.set("tabNav",e.target.checked?"vertical":"horizontal"))}}else this.handleItemUpdate(e)}handleBulkAction(e){if(e.value.startsWith("tax-")){const t=e.options[e.selectedIndex],s=t.dataset.taxonomy,i=t.dataset.single,a=t.dataset.plural;return window.jvbSelector.openEmpty(s,i,a,e=>this.handleBulkTaxonomy(e)),void(e.value="")}switch(e.value){case"edit":this.openBulkEditModal();break;case"publish":case"trash":case"delete":this.setBulkStatus(e.value);break;case"draft":case"restore":this.setBulkStatus("draft")}}handleBulkTaxonomy(e){e.termIds.length&&this.selected.size&&(this.selected.forEach(t=>{const s=this.store.get(t);if(!s)return;const i=(s.taxonomies?.[e.taxonomy]||[]).map(e=>e.id),a=[...new Set([...i,...e.termIds])];this.updateItem(t,e.taxonomy,a)}),this.savePosts(`Adding ${e.terms.length} ${e.taxonomy} to ${this.selected.size} ${this.plural}...`).then(()=>{}),this.selectionHandler.clearSelection())}handleItemUpdate(e){let t=window.targetCheck(e,"[data-item-id]");if(!t)return;const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');let i,a;if(s)i=s.dataset.field,a=this.forms.getFieldValue(s);else{let t=e.target.closest("[data-field]");i=t.dataset.field,a=this.forms.getFieldValue(e.target)}t.dataset.itemId.split(",").forEach(e=>{this.updateItem(e,i,a)})}updateItem(e,t,s){if(this.isPopulating)return;t.replace(`[${e}]`,"");const i=this.store.get(e);if(i){const a=i.fields?.[t]??i[t];if(null===window.getDifferences.map(a,s)){if(this.changes.has(e)){delete this.changes.get(e)[t];0===Object.keys(this.changes.get(e)).filter(e=>"id"!==e&&"content"!==e).length&&(this.changes.delete(e),this.changesStore.delete(e))}return}}this.changes.has(e)||this.changes.set(e,{id:e,content:this.content}),this.changes.get(e)[t]=s,this.scheduleBackup(),"number"!=typeof e&&String(e).includes("group")||this.scheduleSave()}scheduleBackup(){window.debouncer.schedule(`changes-${this.content}`,async()=>{this.changes.size>0&&await this.handleBackup()},2e3)}cancelBackup(){window.debouncer.cancel(`changes-${this.content}`)}async handleBackup(){const e=Array.from(this.changes.values());this.changes.clear();const t=e.map(e=>e.id),s=await Promise.all(t.map(e=>this.changesStore.get(e))),i=e.map((e,t)=>s[t]?window.deepMerge(s[t],e):e);await this.changesStore.saveMany(i)}scheduleSave(e=1e4){window.debouncer.schedule(`save-${this.content}`,async()=>{this.changes.size>0&&(this.cancelBackup(),await this.handleBackup()),await this.savePosts("",!1)},e)}handleFilterChange(e){let t=e.dataset.filter;return"date"===t&&"custom"===e.value?(e.value="",void this.modals.date.handleOpen()):"date"===t&&""!==e.value?(this.setFilter("date-filter",e.value),this.deleteFilter("dateFrom"),this.deleteFilter("dateTo"),void this.checkHideFilters()):("taxonomies"===t&&(t=`tax_${e.dataset.taxonomy}`),void this.setFilter(t,e.value))}checkHideFilters(){const e=this.store.filters,t=Object.entries(e).some(([e,t])=>!["content","user","page"].includes(e)&&(this.defaults[e]!==t&&""!==t&&null!==t));this.ui.buttons.clearFilters.hidden=!t}clearAllFilters(){let e=this.store.filters;this.store.clearFilters();for(let[t,s]of Object.entries(e))this.cache.remove(t),this.deleteFilter(t,s);this.a11y.announce("All filters cleared")}handleCustomDateSelection(){if(this.ui.modals.date.month&&this.ui.modals.date.month.value){const[e,t]=this.ui.modals.date.month.value.split("-"),s=`${e}-${t}-01`,i=new Date(e,parseInt(t),0).getDate(),a=`${e}-${t}-${String(i).padStart(2,"0")}`;this.setFilter("dateFrom",s),this.setFilter("dateTo",a),this.deleteFilter("date-filter"),this.ui.modals.date.month.value=""}else this.ui.modals.date.start&&this.ui.modals.date.start.value&&this.ui.modals.date.end&&this.ui.modals.date.end.value&&(this.setFilter("dateFrom",this.ui.modals.date.start.value),this.setFilter("dateTo",this.ui.modals.date.end.value),this.deleteFilter("date-filter"),this.ui.modals.date.start.value="",this.ui.modals.date.end.value="");this.checkHideFilters()}handleViewChange(e){this.view=e.dataset.view,this.cache.set("view",this.view),this.render()}handleClick(e){if(e.target.matches(".clear-search"))return void this.deleteFilter("search","");const t=e.target.closest("[data-action]");return t?(e.preventDefault(),void this.handleActionButton(t)):e.target.matches(".apply-date-filter")?(this.handleCustomDateSelection(),void this.modals.date.handleClose()):void((e.target.matches(this.selectors.buttons.create)||e.target.closest(this.selectors.buttons.create))&&this.openCreateModal())}openCreateModal(){this.forms.registerForm(this.ui.modals.create.form,{cache:!1}),this.ui.modals.create.modal.dataset.itemId=window.generateID("new"),this.modals.create.handleOpen()}handleActionButton(e){const t=e.dataset.id;switch(e.dataset.action){case"edit":this.openEditModal(t);break;case"delete":confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then(()=>{}),this.store.delete(t));break;case"trash":"trash"===this.status?confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then(()=>{}),this.store.delete(t)):(this.updateItem(t,"post_status","trash"),window.fade(e.closest(".item"),!1),this.savePosts(`Sending ${this.singular} to trash...`).then(()=>{}));break;case"bulk-edit":this.selected.size>0&&this.openBulkEditModal();break;case"bulk-delete":this.handleBulkDelete();break;case"refresh":this.store.clearCache(),this.store.fetch();break;case"clear-filters":this.clearAllFilters()}}handleBulkDelete(){let e="trash"===this.status;if(this.selected.size>0&&confirm(`${e?"Permanently delete":"Send"} ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}${e?"":"to trash"}?`)){this.selected.forEach(t=>{this.store.delete(t),this.updateItem(t,"post_status",e?"delete":"trash")});let t=e?`Permanently deleting ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}`:`Sending ${this.selected.size} ${1===this.selected.size?this.singular:this.plural} to trash`;this.savePosts(t).then(()=>{}),this.selectionHandler.clearSelection()}}handleInput(e){e.preventDefault(),e.stopPropagation();let t=e.target.value.trim(),s=`${this.content}-search`;0!==t.length?window.debouncer.schedule(s,()=>{this.a11y.announce(`Searching for "${t}"...`),this.store.setFilters({search:t,page:1})},300):this.deleteFilter("search","")}handleKeys(e){if(this.tabNav&&"Tab"===e.key){e.preventDefault();const t=e.target.closest("[data-field]"),s=e.target.closest("tr");if(!t||!s)return;const i=t.dataset.field,a=e.shiftKey;let l=this.findNextEditableRow(s,a);l||(l=this.wrapToRow(s,a)),l&&this.focusFieldInRow(l,i,a)}}findNextEditableRow(e,t=!1){let s=t?e.previousElementSibling:e.nextElementSibling;for(;s&&!this.isEditableRow(s);)s=t?s.previousElementSibling:s.nextElementSibling;return s}wrapToRow(e,t=!1){if(this.isTimeline){const s=e.closest("tbody");if(!s)return null;const i=Array.from(s.querySelectorAll("tr")).filter(e=>this.isEditableRow(e));return t?i[i.length-1]:i[0]}{if(!this.ui.table.body)return null;const e=Array.from(this.ui.table.body.querySelectorAll("tr")).filter(e=>this.isEditableRow(e));return t?e[e.length-1]:e[0]}}isEditableRow(e){return!e.closest("thead")&&!e.closest("tfoot")&&(this.isTimeline?e.classList.contains("shared")||e.classList.contains("timeline-point"):!!e.dataset.itemId)}focusFieldInRow(e,t,s=!1){const i=e.querySelector(`[data-field="${t}"]`);if(!i)return;const a=this.findFocusableInput(i);if(a){a.focus(),a.select&&"text"===a.type&&a.select();const e=s?"next":"previous";this.a11y?.announce(`Moved to ${t} in ${e} row`)}}findFocusableInput(e){const t=['input:not([type="hidden"]):not([disabled])',"textarea:not([disabled])","select:not([disabled])","button:not([disabled])"];for(const s of t){const t=e.querySelector(s);if(t)return t}return null}openEditModal(e){let t,s=this.store.get(parseInt(e));s&&(this.activeItem=s.id,this.ui.modals.edit.modal.dataset.itemId=e,this.ui.modals.edit.modal.dataset.content=this.content,Object.hasOwn(s.fields,"post_title")?t=s.fields.post_title:Object.hasOwn(s.fields,"name")&&(t=s.fields.name),this.ui.modals.edit.h2.textContent=`Editing ${""===t?this.singular:t}`,this.ui.modals.edit.form.dataset.formId=`edit-${e}`,this.modals.edit.handleOpen(),this.forms.registerForm(this.ui.modals.edit.form,{cache:!1,autoUpload:!0}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,s),requestAnimationFrame(()=>{requestAnimationFrame(()=>{this.isPopulating=!1})}))}openBulkEditModal(){window.removeChildren(this.ui.modals.bulkEdit.selected),this.ui.modals.edit.form.reset(),window.chunkIt(this.selected,t=>{let s=this.store.get(parseInt(t));if(s)return e.push(s.id),window.jvbTemplates.create("bulkItem",s)},e=>this.ui.modals.bulkEdit.selected.append(e)).then(()=>{});let e=Array.from(this.selected).map(e=>this.store.get(parseInt(e))).filter(Boolean);this.ui.modals.bulkEdit.modal.dataset.itemId=e.join(","),this.ui.modals.bulkEdit.h2&&(this.ui.modals.bulkEdit.h2.textContent=this.selected.size),this.modals.bulkEdit.handleOpen(),this.forms.registerForm(this.ui.modals.bulkEdit.form,{cache:!1}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,item),requestAnimationFrame(()=>{requestAnimationFrame(()=>{this.isPopulating=!1})})}async savePosts(e="",t=!1){this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());let s=await this.changesStore.getAll();if(0===s.length)return;if(s=this.validateChanges(s),0===s.length)return;""===e&&(e=`Saving ${s.length} ${1===s.length?this.singular:this.plural}`);let i={},a=[];s.forEach(e=>{let t=e.id;const{id:s,...l}=e;i[t]=l,e.post_status&&this.shouldRemoveItemUI(e.post_status)&&a.push(t)}),a.length>0&&this.removeItems(a);let l={endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:i},delay:t,popup:"Saving changes",title:e};this.queue.addToQueue(l)}validateChanges(e){return e.reduce((e,t)=>{const{id:s,content:i,...a}=t,l=this.store.get(s);if(!l)return e.push(t),e;const n={id:s,content:i};let o=!1;for(const[e,t]of Object.entries(a)){const s=l.fields?.[e]??l[e];null!==window.getDifferences.map(s,t)&&(n[e]=t,o=!0)}return o?e.push(n):(this.changes.delete(s),this.changesStore.delete(s)),e},[])}setBulkStatus(e){if(!["publish","draft","trash","delete"].includes(e))return;let t,s=[];if(this.selected.forEach(t=>{s.push(t),this.updateItem(t,"post_status",e)}),"delete"===e)t="Deleting";else t=window.uppercaseFirst(e)+"ing";this.shouldRemoveItemUI(e)&&this.removeItems(s),this.selectionHandler.clearSelection(),this.savePosts(`${t} ${s.length} ${1===s.length?this.singular:this.plural}...`).then(()=>{})}render(){const e=this.store.getFiltered();if(0!==e.length){switch(this.view){case"grid":this.renderGrid(e);break;case"table":this.renderTable(e).then(()=>{});break;case"list":this.renderList(e)}this.updateUI()}else this.renderEmpty()}updateUI(){if(this.ui.bulk.action){let e=!1,t=this.ui.bulk.action.querySelector('[value="edit"]'),s=this.status;"trash"===s&&t?(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("trashOptions")):"trash"===s||t||(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("notTrashOptions")),e&&e.querySelectorAll("option").forEach((e,t)=>{0===t&&(e.checked=!0),this.ui.bulk.action.append(e)}),this.ui.bulk.action.value=""}this.selected.size>0&&this.selectionHandler.updateSelectionUI()}renderEmpty(){this.toggleTable(!1),window.removeChildren(this.ui.grid);const e=window.jvbTemplates.create("emptyState");e&&(this.ui.grid.append(e),this.a11y.announceItems(0,!1,!1))}toggleTable(e=!0){if(this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e),e&&!this.ui.table.form){let e=window.jvbTemplates.create("contentTable");this.container.append(e),this.ui.table=window.uiFromSelectors(this.selectors.table),this.ui.table.columns=this.container.querySelectorAll(this.selectors.table.columns)}this.ui.table.form&&(this.ui.table.form.hidden=!e,e||this.forms.clearForm(this.ui.table.form.dataset.formId),this.ui.table.body&&window.removeChildren(this.ui.table.body)),this.keyHandler=this.handleKeys.bind(this),e?document.addEventListener("keydown",this.keyHandler):document.removeEventListener("keydown",this.keyHandler)}renderGrid(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("list-view"),this.ui.grid.classList.add("grid-view"),window.chunkIt(e,e=>this.renderGridItem(e),e=>this.ui.grid.append(e)).then(()=>{})}renderList(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("grid-view"),this.ui.grid.classList.add("list-view"),window.chunkIt(e,e=>this.renderListItem(e),e=>this.ui.grid.append(e)).then(()=>{})}async renderTable(e){this.toggleTable(),window.removeChildren(this.ui.grid),await window.chunkIt(e,e=>this.renderTableItem(e),e=>{this.ui.table.body?this.ui.table.body.append(e):this.ui.table.table.insertBefore(e,this.ui.table.foot)},5),requestAnimationFrame(()=>{window.jvbSelector?.scanExistingFields(this.ui.table.table)})}renderGridItem(e){let t=window.jvbTemplates.create("gridView",e);return this.items.set(e.id,t),t}renderListItem(e){let t=window.jvbTemplates.create("listView",e);return this.items.set(e.id,t),t}renderTableItem(e){let t=window.jvbTemplates.create("tableView",e);return this.items.set(e.id,t),t}toggleColumn(e,t){this.ui.table.table.querySelectorAll(`.${e}`).forEach(e=>{e.hidden=!t})}handleGroupsUploaded(e){const{posts:t,fieldId:s}=e;let i=window.jvbUploads,a=(i.fields.get(s),[]);t.forEach(e=>{const t={id:e.groupId,title:e.fields.post_title||`New ${this.singular}`,status:"draft",date:(new Date).toISOString(),modified:(new Date).toISOString(),thumbnail:null,icon:this.content,taxonomies:{},fields:e.fields,images:{}};e.images.forEach((e,s)=>{let a=e.upload_id;0===s&&(t.fields.post_thumbnail=e);let l=i.stores.uploads.get(a);l&&(t.images[a]={"image-alt-text":"","image-caption":"","image-title":l.fields.originalName,medium:i.createPreviewUrl(i.formatFile(l))})}),a.push(t)}),this.store.saveMany(a).then(()=>this.render()),this.a11y.announce(`${t.length} ${1===t.length?this.singular:this.plural} created. Waiting for server confirmation...`)}handleGroupMappings(e){for(const[t,s]of Object.entries(e)){let e={};this.changes.has(t)&&(e=this.changes.get(t),this.changes.delete(t));let i=this.changesStore.get(t)??{};(e.size>0||i.size>0)&&(e=window.deepMerge(i,e),this.changes.set(s,e),this.scheduleBackup())}}shouldRemoveItemUI(e){return"all"===this.status&&!["publish","draft"].includes(e)||e!==this.store.filters.status}removeItems(e){e.forEach(e=>{if(this.items.has(e)){let t=this.items.get(e);t&&window.fade(t,!1)}})}setFilters(e){for(let[t,s]of Object.entries(e)){if(!this.allowedFilters.includes(t)){delete e[t];continue}this.cache.set(t,s);let i=this.findFilterEl(t);this.setElValue(i,s)}this.store.setFilters(e)}setFilter(e,t){if(!this.allowedFilters.includes(e))return;this.cache.set(e,t),"status"===e&&(this.status=t),"orderby"===e&&(this.orderby=t),"order"===e&&(this.order=t);let s=this.findFilterEl(e,t);this.setElValue(s,t),this.store.setFilter(e,t)}deleteFilter(e,t){if(!this.allowedFilters.includes(e))return;if(Object.hasOwn(this.defaults,e))return void this.setFilter(e,this.defaults[e]);let s=this.findFilterEl(e,t);this.setElValue(s,!1),this.cache.remove(e),this.setFilter(e,"")}setElValue(e,t){if(e){if(!t)return["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=""),["text","search"].includes(e.type)&&(e.value=""),void("radio"===e.type&&(e.checked=!1));["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=t),["text","search"].includes(e.type)&&(e.value=t),"radio"===e.type&&(e.checked=!0)}}findFilterEl(e,t){if(["date-filter","dateFrom","dateTo"].includes(e)){switch(e){case"date-filter":e="month";break;case"dateFrom":e="start";break;case"dateTo":e="end"}return this.ui.modals.date[e]}if(e.includes("tax_")){const t=e.replace("tax_",""),s=this.ui.filters.taxonomies?.[t];return s||(console.warn("Taxonomy filter element not found:",t),null)}if(!Object.hasOwn(this.ui.filters,e))return console.warn("Filter el not found: ",e),!1;let s=this.ui.filters[e];if("object"==typeof s){if(!Object.hasOwn(this.ui.filters[e],t))return!1;s=this.ui.filters[e][t]}return s}resetForm(e){e.querySelectorAll('input[type="hidden"], input[type="text"], input[type="number"], input[type="email"], input[type="url"], textarea').forEach(e=>{e.value=""}),e.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(e=>{e.checked=!1}),e.querySelectorAll("select").forEach(e=>{e.selectedIndex=0}),e.querySelectorAll(".selected-items").forEach(e=>{window.removeChildren(e)}),e.querySelectorAll(".item-grid.preview").forEach(e=>{window.removeChildren(e)})}destroy(){window.debouncer.cancel(`changes-${this.content}`),this.changes.size>0&&(this.changesStore.saveMany(this.changes).then(()=>{}),this.changes.clear()),this.timelineSortables&&(this.timelineSortables.forEach(e=>e.destroy()),this.timelineSortables=[]);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.removeEventListener("submit",this.submitHandler);document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),this.ui.filters.search&&this.ui.filters.search.removeEventListener("input",this.handleInput)}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{if("auth-loaded"===t){let t=document.querySelector("[data-content]");t&&!Object.hasOwn(t.dataset,"ignore")&&(window.crudManager=new e({content:t.dataset.content}))}})})})();
assets/js/min/dataStore.min.js
@@ -1 +1 @@
(()=>{class e{constructor(){if(e.instance)return e.instance;e.instance=this,this.dbConfig=new Map,this.databases=new Map,this.stores=new Map,this.subscribers=new Map,this.pendingInits=new Map,this.fetchQueue=[],this._initialized=!1,this.body=document.body,this.loading=document.querySelector("dialog.loading"),this.init()}async init(){this._initialized||(this._initialized=!0,"indexedDB"in window||console.warn("IndexedDB not supported"))}register(e,t=[],s=1.25){if(Array.isArray(t)||(t=[t]),0===t.length)return;this.dbConfig.has(e)||this.dbConfig.set(e,{dbName:`${jvbBase.base}${e}`,version:s,stores:{},_initialized:!1});let r=this.dbConfig.get(e);t.forEach(t=>{if(!t.storeName)throw new Error(`Store config for "${e}" missing storeName`);if(!t.keyPath)throw new Error(`Store "${t.storeName}" requires keyPath`);const s=`${e}_${t.storeName}`,i={config:{dbName:r.dbName,storeName:"items",keyPath:"id",indexes:[],endpoint:null,apiBase:jvbSettings.api,filters:{},ignore:[],required:null,isAuth:!1,TTL:36e5,useHttpCaching:!0,showLoading:!1,delayFetch:!0,validateData:!0,...t},dbKey:e,storeKey:s,data:new Map,cache:new Map,filters:{...t.filters||{}},isFetching:!1,currentRequest:null,lastResponse:null,_initialized:!1};i.ignoreFilters=new Set(["search","page","per_page","orderby","order","context","source",...i.config.ignore]),i.config.headers={"X-WP-Nonce":window.auth.getNonce(),...i.config.headers},r.stores[t.storeName]=s,this.stores.set(s,i),this.subscribers.has(s)||this.subscribers.set(s,new Set)}),this.initDB(e).catch(t=>{console.error(`Failed to initialize store "${e}":`,t)});const i={};for(const[e,t]of Object.entries(r.stores))i[e]=this.getStoreAPI(t);return i}getStoreAPI(e){const t={fetch:()=>this.fetch(e),save:t=>this.save(e,t),saveMany:t=>this.saveMany(e,t),delete:t=>this.delete(e,t),deleteMany:t=>this.deleteMany(e,t),get:t=>this.get(e,t),getMany:t=>this.getMany(e,t),getAll:()=>this.getAll(e),getAllByIndex:(t,s)=>this.getAllByIndex(e,t,s),filterByIndex:t=>this.filterByIndex(e,t),getFiltered:()=>this.getFiltered(e),clear:()=>this.clear(e),setFilter:(t,s)=>this.setFilter(e,t,s),setFilters:t=>this.setFilters(e,t),removeFilter:t=>this.removeFilter(e,t),clearFilters:()=>this.clearFilters(e),clearCache:()=>this.clearCache(e),subscribe:t=>this.subscribe(e,t),ensureInitialized:()=>this.ensureStoreInitialized(e),get filters(){return{...t.getStore().filters}},get lastResponse(){return t.getStore().lastResponse},get data(){return t.getStore().data},getStore:()=>this.stores.get(e)};return t}formDataToObject(e){const t={_isFormData:!0,entries:{}};for(const[s,r]of e.entries())r instanceof File||r instanceof Blob||(t.entries[s]?(Array.isArray(t.entries[s])||(t.entries[s]=[t.entries[s]]),t.entries[s].push(r)):t.entries[s]=r);return t}async objectToFormData(e){if(!e._isFormData)return e;const t=new FormData;for(const[s,r]of Object.entries(e.entries))Array.isArray(r)?r.forEach(e=>t.append(s,e)):t.append(s,r);if(window.jvbUploads&&e.entries.upload_ids){const s=JSON.parse(e.entries.upload_ids);for(const e of s){const s=await window.jvbUploads.getBlobData(e);s&&t.append("files[]",s)}}return t}async initDB(e){const t=this.dbConfig.get(e);if(!t||t._initialized)return;if(this.pendingInits.has(e))return this.pendingInits.get(e);const s=this._performDBInit(e);this.pendingInits.set(e,s);try{await s,t._initialized=!0}finally{this.pendingInits.delete(e)}}async _performDBInit(e){const t=this.dbConfig.get(e),{dbName:s,version:r}=t,i=Object.values(t.stores);try{if(!this.databases.has(s)){const e=await this.openDatabase(s,r,e=>{i.forEach(t=>{let s=this.stores.get(t);s&&this.setupStores(e,s.config)})});this.databases.set(s,e)}i.forEach(e=>{let t=this.stores.get(e);t&&(t.db=this.databases.get(s),t._initialized=!0,this.loadStoreDataInBackground(e),this.notify(e,"db-init"))})}catch(t){throw console.error(`Failed to initialize database for store "${e}":`,t),t}}openDatabase(e,t,s){return new Promise((r,i)=>{const a=indexedDB.open(e,t);a.onupgradeneeded=e=>{s&&s(e.target.result,e.oldVersion,e.newVersion)},a.onsuccess=e=>r(e.target.result),a.onerror=e=>i(e.target.error),a.onblocked=()=>{console.warn(`Database ${e} blocked. Close other tabs.`)}})}setupStores(e,t){if(!e.objectStoreNames.contains(t.storeName)){const s=e.createObjectStore(t.storeName,{keyPath:t.keyPath});t.indexes.forEach(e=>{s.createIndex(e.name,e.keyPath||e.name,{unique:e.unique||!1})})}if(t.endpoint&&!e.objectStoreNames.contains("cache")){e.createObjectStore("cache",{keyPath:"key"}).createIndex("timestamp","timestamp",{unique:!1})}}async loadFromObjectStore(e,t,s){const r=this.stores.get(e);return r?.db&&r.db.objectStoreNames.contains(t)?new Promise(e=>{const i=r.db.transaction([t],"readonly").objectStore(t).getAll();i.onsuccess=t=>{const r=t.target.result||[];r.forEach(s),e(r)},i.onerror=()=>e([])}):[]}loadStoreDataInBackground(e){const t=this.stores.get(e);t?.db&&Promise.all([this.loadFromObjectStore(e,t.config.storeName,e=>{const s=this.getItemKey(e,t.config.keyPath);t.data.set(s,e)}),this.loadFromObjectStore(e,"cache",e=>{this.isCacheValid(e,t.config.TTL)&&t.cache.set(e.key,e)})]).then(()=>{this.notify(e,"data-ready"),t.config.endpoint&&t.config.delayFetch?(this.fetchQueue.push(e),1===this.fetchQueue.length&&this.processFetchQueue()):t.config.endpoint&&!t.config.delayFetch&&("requestIdleCallback"in window?requestIdleCallback(()=>this.fetch(e),{timeout:2e3}):setTimeout(()=>this.fetch(e),100))}).catch(t=>{console.error(`Background load error for store "${e}":`,t)})}async processFetchQueue(){if(0===this.fetchQueue.length)return;const e=this.fetchQueue.shift();if(!this.stores.get(e))return this.processFetchQueue();try{await this.fetch(e)}catch(t){console.error(`Queue fetch error for "${e}":`,t)}this.fetchQueue.length>0&&("requestIdleCallback"in window?requestIdleCallback(()=>this.processFetchQueue(),{timeout:2e3}):setTimeout(()=>this.processFetchQueue(),50))}async ensureStoreInitialized(e){const t=this.stores.get(e);if(!t)throw new Error(`Store "${e}" not registered`);t._initialized||await this.initDB(t.dbKey)}async withTransaction(e,t,s,r){const i=this.stores.get(e);return i?.db?("string"==typeof t&&(t=[t]),new Promise((e,a)=>{const o=i.db.transaction(t,s),n=t.map(e=>o.objectStore(e)),c=1===n.length?n[0]:n;let h;o.oncomplete=()=>e(h),o.onerror=()=>{const e=o.error||new Error("Transaction failed with unknown error");a(e)};try{h=r(c,o)}catch(e){a(e||new Error("Callback failed with unknown error"))}})):null}async fetch(e){await this.ensureStoreInitialized(e);const t=this.stores.get(e);if(!t.isFetching){if(t.config.required){if((Array.isArray(t.config.required)?t.config.required:[t.config.required]).some(e=>!t.filters[e]||""===t.filters[e]))return}t.isFetching=!0;try{const s=this.generateCacheKey(t.filters),r=t.cache.get(s);if(r&&this.isCacheValid(r,t.config.TTL)){let t=r.items.map(t=>this.get(e,t));return this.notify(e,"data-loaded",{cached:!0,items:t??[]}),r}t.config.showLoading&&this.setLoading(!0);const i=this.buildFetchUrl(e),a={...t.config.headers};t.config.useHttpCaching&&r&&(r.etag&&(a["If-None-Match"]=r.etag),r.lastModified&&(a["If-Modified-Since"]=r.lastModified));const o=new AbortController;let n;if(t.currentRequest=o,n=t.isAuth?await window.auth.fetch(i,{method:"GET",headers:a,signal:o.signal}):await fetch(i,{method:"GET",headers:a,signal:o.signal}),!n.ok){const e=await n.text();throw new Error(`HTTP error! status: ${n.status}, message: ${e}`)}if(304===n.status)return r?(this.notify(e,"data-loaded",{cached:!0,notModified:!0,items:r.items||[]}),r):(this.notify(e,"data-loaded",{cached:!1,notModified:!0,items:[]}),t.lastResponse={has_more:!1,total:0,pages:1,queue_stats:{}},{items:[]});if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);const c=await n.json();return await this.processFetchedData(e,c,s,n),this.notify(e,"data-loaded",{cached:!1,items:c.items||[]}),c}catch(t){if(!("AbortError"===t?.name))throw console.error(`Fetch error for store "${e}":`,t.message),console.dir(t),this.notify(e,"fetch-error",{error:t}),t}finally{t.isFetching=!1,t.currentRequest=null,t.config.showLoading&&this.setLoading(!1)}}}buildFetchUrl(e){const t=this.stores.get(e),s=new URLSearchParams;Object.entries(t.filters).forEach(([e,t])=>{null!=t&&""!==t&&("object"==typeof t?s.set(e,JSON.stringify(t)):s.set(e,t))});const r=t.config.apiBase+t.config.endpoint;return s.toString()?`${r}?${s}`:r}async processFetchedData(e,t,s,r){const i=this.stores.get(e),a=(t.items||[]).filter(e=>e&&"object"==typeof e),o=[];i.db&&a.length>0&&await this.withTransaction(e,i.config.storeName,"readwrite",t=>{a.forEach(s=>{try{const r=this._saveItem(e,s);o.push(r),t.put(r.processed)}catch(e){console.error("Error processing item:",e)}})});const n={key:s,items:a.map(e=>this.getItemKey(e,i.config.keyPath)),timestamp:Date.now(),endpoint:i.config.endpoint,filters:{...i.filters},etag:r.headers.get("ETag"),lastModified:r.headers.get("Last-Modified"),has_more:t.has_more||!1};i.cache.set(s,n),i.db?.objectStoreNames.contains("cache")&&await this.withTransaction(e,"cache","readwrite",e=>{e.put(n)}),i.lastResponse={...t,has_more:t.has_more||!1,total:t.total||a.length,pages:t.pages||1,queue_stats:t.queue_stats||{}};for(let[t,s]of Object.entries(i.filters))"string"==typeof s&&s.includes(",")&&this.createSplitCacheEntries(e,a,t,i.filters,r);o.forEach(t=>{t.statusChanged&&this.notify(e,"item-saved",{item:t.item,key:t.key,previousItem:t.previousItem})})}createSplitCacheEntries(e,t,s,r,i){const a=this.stores.get(e);r[s].split(",").map(e=>e.trim()).forEach(t=>{let o={};o[s]=t;const n={...r,[s]:t},c=this.generateCacheKey(n);if(a.cache.has(c))return;let h=this.filterByIndex(e,o).map(e=>this.getItemKey(e,a.config.keyPath));const l={key:c,items:h,timestamp:Date.now(),endpoint:a.config.endpoint,filters:n,etag:i.headers.get("Etag"),lastModified:i.headers.get("Last-Modified"),has_more:20===h.length};a.cache.set(c,l),a.db?.objectStoreNames.contains("cache")&&this.withTransaction(e,"cache","readwrite",e=>{e.put(l)})})}_saveItem(e,t){const s=this.stores.get(e),r=this.processForStorage(t,s.config.validateData);if(!r.valid)throw new Error(`Non-serializable data: ${r.error}`);const i=r.data,a=this.getItemKey(i,s.config.keyPath),o=s.data.get(a);return s.data.set(a,t),{item:t,previousItem:o,key:a,processed:i,statusChanged:o&&o.status!==t.status}}async save(e,t){const s=this.stores.get(e),r=this._saveItem(e,t);return await this.withTransaction(e,s.config.storeName,"readwrite",e=>{e.put(r.processed)}),this.notify(e,"item-saved",{item:r.item,key:r.key,previousItem:r.previousItem}),r.key}async saveMany(e,t){const s=this.stores.get(e);if(!s)return[];const r=t instanceof Map?Array.from(t.values()):Array.isArray(t)?t:Object.values(t);if(0===r.length)return[];const i=[];return r.forEach(t=>{const s=this._saveItem(e,t);i.push(s)}),await this.withTransaction(e,s.config.storeName,"readwrite",e=>{i.forEach(t=>{e.put(t.processed)})}),this.notify(e,"items-saved",{count:i.length,keys:i.map(e=>e.key)}),i.map(e=>e.key)}processForStorage(e,t=!0,s="root"){if(null===e)return{valid:!0,data:null};if(void 0===e)return t?{valid:!1,error:`Undefined value at ${s}`}:{valid:!0,data:void 0};const r=typeof e;if(["string","number","boolean"].includes(r))return{valid:!0,data:e};if("function"===r)return t?{valid:!1,error:`Function at ${s}`}:{valid:!0,data:void 0};if(e instanceof HTMLElement||void 0!==e.nodeType)return t?{valid:!1,error:`DOM element at ${s}`}:{valid:!0,data:void 0};if(e instanceof FormData)return{valid:!0,data:this.formDataToObject(e)};if(e instanceof Date||e instanceof ArrayBuffer||ArrayBuffer.isView(e)||e instanceof Blob)return{valid:!0,data:e};if(e instanceof Set)return this.processForStorage(Array.from(e),t,s);if(e instanceof Map&&(e=Object.fromEntries(e)),Array.isArray(e)){const r=[];for(let i=0;i<e.length;i++){const a=this.processForStorage(e[i],t,`${s}[${i}]`);if(!a.valid)return a;void 0!==a.data&&r.push(a.data)}return{valid:!0,data:r}}if("object"===r){const r={};for(const[i,a]of Object.entries(e)){if(void 0===a)continue;const e=this.processForStorage(a,t,`${s}.${i}`);if(!e.valid)return e;void 0===e.data&&null!==a||(r[i]=e.data)}return{valid:!0,data:r}}return t?{valid:!1,error:`Unknown type at ${s}`}:{valid:!0,data:void 0}}async delete(e,t){const s=this.stores.get(e);s.data.delete(t),await this.withTransaction(e,s.config.storeName,"readwrite",e=>{e.delete(t)}),this.notify(e,"item-deleted",{id:t})}async deleteMany(e,t){const s=this.stores.get(e);if(!s)return[];const r=t instanceof Set?Array.from(t):Array.isArray(t)?t:Object.keys(t);return 0===r.length?[]:(r.forEach(e=>{s.data.delete(e)}),await this.withTransaction(e,s.config.storeName,"readwrite",e=>{r.forEach(t=>{e.delete(t)})}),this.notify(e,"items-deleted",{count:r.length,ids:r}),r)}get(e,t){return this.stores.get(e).data.get(t)}getMany(e,t,s=!0){const r=this.stores.get(e);if(!r)return[];const i=t instanceof Set?Array.from(t):Array.isArray(t)?t:Object.keys(t);return 0===i.length?[]:s?i.reduce((e,t)=>{const s=r.data.get(t);return s&&e.push(s),e},[]):i.map(e=>r.data.get(e)??null)}getAll(e){const t=this.stores.get(e);return Array.from(t.data.values())}filterByIndex(e,t){const s=this.stores.get(e);return s?Array.from(s.data.values()).filter(e=>!(!e||"object"!=typeof e)&&Object.entries(t).every(([t,s])=>(Array.isArray(s)?s:[s]).includes(e[t]))):[]}async getAllByIndex(e,t,s){const r=this.stores.get(e),i=Array.isArray(s)?s:[s];if(r.db&&r.db.objectStoreNames.contains(r.config.storeName))try{const e=r.db.transaction([r.config.storeName],"readonly").objectStore(r.config.storeName);if(e.indexNames.contains(t)){const s=e.index(t);return(await Promise.all(i.map(e=>new Promise((t,r)=>{const i=s.getAll(e);i.onsuccess=()=>t(i.result||[]),i.onerror=()=>r(i.error)})))).flat()}}catch(e){console.warn(`Index query failed for "${t}", falling back to filter:`,e)}return Array.from(r.data.values()).filter(e=>i.includes(e[t]))}getFiltered(e){const t=this.stores.get(e),s=this.generateCacheKey(t.filters),r=t.cache.get(s);if(r?.items){const e=r.items.reduce((e,s)=>{const r=t.data.get(s);return r&&e.push(r),e},[]);return this.applyOrdering(e,t)}const i=Array.from(t.data.values()),a=t.filters.search?.toLowerCase().trim()||"",o=[];t.filters.taxonomy&&"object"==typeof t.filters.taxonomy&&Object.entries(t.filters.taxonomy).forEach(([e,t])=>{const s=Array.isArray(t)?t:[t];o.push(t=>{if(!t.taxonomies||!t.taxonomies[e])return!1;const r=Object.keys(t.taxonomies[e]).map(e=>parseInt(e));return s.some(e=>r.includes(parseInt(e)))})});for(const[e,s]of Object.entries(t.filters))if("taxonomy"!==e&&!t.ignoreFilters.has(e)&&null!=s&&""!==s&&"all"!==s)if("string"==typeof s&&s.includes(",")){const t=s.split(",").map(e=>e.trim());o.push(s=>t.includes(String(s[e])))}else o.push(t=>String(t[e])===String(s));const n=i.filter(e=>{for(const t of o)if(!t(e))return!1;return!(a&&!this.searchObject(e,a))});return this.applyOrdering(n,t)}applyOrdering(e,t){if(Array.isArray(e)||(e=Array.from(e)),0===e.length)return e;const s=t.filters.orderby||"date",r=(t.filters.order||"desc").toLowerCase();return["random","rand"].includes(s)||["random","rand"].includes(r)?this.shuffle(e):(e.sort((e,t)=>{let i,a;switch(s){case"alphabetical":case"title":i=(e.title||e.name||"").toLowerCase(),a=(t.title||t.name||"").toLowerCase();break;case"modified":i=new Date(e.modified||e.date||0),a=new Date(t.modified||t.date||0);break;default:i=new Date(e.date||e.modified||0),a=new Date(t.date||t.modified||0)}return i<a?"asc"===r?-1:1:i>a?"asc"===r?1:-1:0}),e)}shuffle(e){const t=e.slice();for(let e=t.length-1;e>0;e--){const s=Math.floor(Math.random()*(e+1));[t[e],t[s]]=[t[s],t[e]]}return t}searchObject(e,t){if(!e||"object"!=typeof e)return"string"==typeof e&&e.toLowerCase().includes(t);for(const s of Object.values(e))if(null!=s)if("object"!=typeof s){if("string"==typeof s&&s.toLowerCase().includes(t))return!0}else if(this.searchObject(s,t))return!0;return!1}async clear(e){const t=this.stores.get(e);t.data.clear(),t.cache.clear(),await this.withTransaction(e,t.config.storeName,"readwrite",e=>{e.clear()}),this.notify(e,"data-cleared")}async updateFilters(e,t,s=!1){const r=this.stores.get(e),i={...r.filters};s&&(r.filters={...r.config.filters}),Object.entries(t).forEach(([e,t])=>{null==t||""===t?delete r.filters[e]:r.filters[e]=t}),this.notify(e,"filters-changed",{oldFilters:i,filters:r.filters,updates:t});const a=await this.shouldFetchWithFilters(e,t,i);if(r.config.endpoint&&a)await this.fetch(e);else{const t=this.getFiltered(e);this.notify(e,"data-loaded",{cached:!0,items:t})}}async shouldFetchWithFilters(e,t,s){const r=this.stores.get(e);if(!r.config.endpoint||!r.lastResponse)return!0;if(!1===r.lastResponse.has_more){if(Object.entries(t).every(([e,t])=>(r.ignoreFilters.has(e),!0)))return!1}if("page"in t){const e=t.page,i=s.page||1;if(e>i&&!r.lastResponse.has_more)return r.filters.page=i,!1}if("search"in t){const e=t.search?.trim()||"",i=s.search?.trim()||"";if(!e&&i){const e={...r.filters};if(delete e.search,e.page=1,this.hasCompleteData(r,e))return!1}if(e&&e!==i){const e={...r.filters};if(delete e.search,e.page=1,this.hasCompleteData(r,e))return!1}}return!0}hasCompleteData(e,t){const s=this.generateCacheKey(t),r=e.cache.get(s);return!!r&&(!1===r.has_more||!1===e.lastResponse?.has_more)}setFilter(e,t,s){return this.updateFilters(e,{[t]:s})}async setFilters(e,t){const s=this.stores.get(e);if(Object.keys(t).some(e=>s.filters[e]!==t[e])||Object.keys(s.filters).some(e=>!(e in t)&&t!==s.config.filters))return this.updateFilters(e,t)}removeFilter(e,t){return this.updateFilters(e,{[t]:null})}clearFilters(e){return this.updateFilters(e,{},!0)}clearCache(e){const t=this.stores.get(e);t.cache.clear(),t.db?.objectStoreNames.contains("cache")&&this.withTransaction(e,"cache","readwrite",e=>{e.clear()}),this.notify(e,"cache-cleared")}generateCacheKey(e){const t=Object.keys(e).sort().reduce((t,s)=>(t[s]=e[s],t),{});return JSON.stringify(t)}isCacheValid(e,t){if(!e||!e.timestamp)return!1;return Date.now()-e.timestamp<t}subscribe(e,t){this.subscribers.has(e)||this.subscribers.set(e,new Set);const s=this.subscribers.get(e);return s.add(t),()=>s.delete(t)}notify(e,t,s={}){const r=this.subscribers.get(e);r&&r.forEach(r=>{try{r(t,s)}catch(t){console.error(`Subscriber error for store "${e}":`,t)}})}getItemKey(e,t){if("function"==typeof t)return t(e);const s=t.split(".");let r=e;for(const e of s)r=r?.[e];return r}setLoading(e){this.body.classList.toggle("loading",e),e?this.loading?.showModal():this.loading?.close()}destroy(){this.stores.forEach(e=>{e.currentRequest&&e.currentRequest.abort()}),this.databases.forEach(e=>e.close()),this.stores.clear(),this.subscribers.clear(),this.databases.clear(),this.pendingInits.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbStore=new e)})})})();
(()=>{class e{constructor(){if(e.instance)return e.instance;e.instance=this,this.dbConfig=new Map,this.databases=new Map,this.stores=new Map,this.subscribers=new Map,this.pendingInits=new Map,this.fetchQueue=[],this._initialized=!1,this.body=document.body,this.loading=document.querySelector("dialog.loading"),this.init()}async init(){this._initialized||(this._initialized=!0,"indexedDB"in window||console.warn("IndexedDB not supported"))}register(e,t=[],s=1.25){if(Array.isArray(t)||(t=[t]),0===t.length)return;this.dbConfig.has(e)||this.dbConfig.set(e,{dbName:`${jvbBase.base}${e}`,version:s,stores:{},_initialized:!1});let r=this.dbConfig.get(e);t.forEach(t=>{if(!t.storeName)throw new Error(`Store config for "${e}" missing storeName`);if(!t.keyPath)throw new Error(`Store "${t.storeName}" requires keyPath`);const s=`${e}_${t.storeName}`,i={config:{dbName:r.dbName,storeName:"items",keyPath:"id",indexes:[],endpoint:null,apiBase:jvbSettings.api,filters:{},ignore:[],required:null,isAuth:!1,TTL:36e5,useHttpCaching:!0,showLoading:!1,delayFetch:!0,validateData:!0,...t},dbKey:e,storeKey:s,data:new Map,cache:new Map,filters:{...t.filters||{}},isFetching:!1,currentRequest:null,lastResponse:null,_initialized:!1};i.ignoreFilters=new Set(["search","page","per_page","orderby","order","context","source",...i.config.ignore]),i.config.headers={"X-WP-Nonce":window.auth.getNonce(),...i.config.headers},r.stores[t.storeName]=s,this.stores.set(s,i),this.subscribers.has(s)||this.subscribers.set(s,new Set)}),this.initDB(e).catch(t=>{console.error(`Failed to initialize store "${e}":`,t)});const i={};for(const[e,t]of Object.entries(r.stores))i[e]=this.getStoreAPI(t);return i}getStoreAPI(e){const t={fetch:()=>this.fetch(e),save:t=>this.save(e,t),saveMany:t=>this.saveMany(e,t),delete:t=>this.delete(e,t),deleteMany:t=>this.deleteMany(e,t),get:t=>this.get(e,t),getMany:t=>this.getMany(e,t),getAll:()=>this.getAll(e),getAllByIndex:(t,s)=>this.getAllByIndex(e,t,s),filterByIndex:t=>this.filterByIndex(e,t),getFiltered:()=>this.getFiltered(e),clear:()=>this.clear(e),setFilter:(t,s)=>this.setFilter(e,t,s),setFilters:t=>this.setFilters(e,t),removeFilter:t=>this.removeFilter(e,t),clearFilters:()=>this.clearFilters(e),clearCache:()=>this.clearCache(e),subscribe:t=>this.subscribe(e,t),ensureInitialized:()=>this.ensureStoreInitialized(e),get filters(){return{...t.getStore().filters}},get lastResponse(){return t.getStore().lastResponse},get data(){return t.getStore().data},getStore:()=>this.stores.get(e)};return t}formDataToObject(e){const t={_isFormData:!0,entries:{}};for(const[s,r]of e.entries())r instanceof File||r instanceof Blob||(t.entries[s]?(Array.isArray(t.entries[s])||(t.entries[s]=[t.entries[s]]),t.entries[s].push(r)):t.entries[s]=r);return t}async objectToFormData(e){if(!e._isFormData)return e;const t=new FormData;for(const[s,r]of Object.entries(e.entries))Array.isArray(r)?r.forEach(e=>t.append(s,e)):t.append(s,r);if(window.jvbUploads&&e.entries.upload_ids){const s=JSON.parse(e.entries.upload_ids);for(const e of s){const s=await window.jvbUploads.getBlobData(e);s&&t.append("files[]",s)}}return t}async initDB(e){const t=this.dbConfig.get(e);if(!t||t._initialized)return;if(this.pendingInits.has(e))return this.pendingInits.get(e);const s=this._performDBInit(e);this.pendingInits.set(e,s);try{await s,t._initialized=!0}finally{this.pendingInits.delete(e)}}async _performDBInit(e){const t=this.dbConfig.get(e),{dbName:s,version:r}=t,i=Object.values(t.stores);try{if(!this.databases.has(s)){const e=await this.openDatabase(s,r,e=>{i.forEach(t=>{let s=this.stores.get(t);s&&this.setupStores(e,s.config)})});this.databases.set(s,e)}i.forEach(e=>{let t=this.stores.get(e);t&&(t.db=this.databases.get(s),t._initialized=!0,this.loadStoreDataInBackground(e),this.notify(e,"db-init"))})}catch(t){throw console.error(`Failed to initialize database for store "${e}":`,t),t}}openDatabase(e,t,s){return new Promise((r,i)=>{const a=indexedDB.open(e,t);a.onupgradeneeded=e=>{s&&s(e.target.result,e.oldVersion,e.newVersion)},a.onsuccess=e=>r(e.target.result),a.onerror=e=>i(e.target.error),a.onblocked=()=>{console.warn(`Database ${e} blocked. Close other tabs.`)}})}setupStores(e,t){if(!e.objectStoreNames.contains(t.storeName)){const s=e.createObjectStore(t.storeName,{keyPath:t.keyPath});t.indexes.forEach(e=>{s.createIndex(e.name,e.keyPath||e.name,{unique:e.unique||!1})})}if(t.endpoint&&!e.objectStoreNames.contains("cache")){e.createObjectStore("cache",{keyPath:"key"}).createIndex("timestamp","timestamp",{unique:!1})}}async loadFromObjectStore(e,t,s){const r=this.stores.get(e);return r?.db&&r.db.objectStoreNames.contains(t)?new Promise(e=>{const i=r.db.transaction([t],"readonly").objectStore(t).getAll();i.onsuccess=t=>{const r=t.target.result||[];r.forEach(s),e(r)},i.onerror=()=>e([])}):[]}loadStoreDataInBackground(e){const t=this.stores.get(e);t?.db&&Promise.all([this.loadFromObjectStore(e,t.config.storeName,e=>{const s=this.getItemKey(e,t.config.keyPath);t.data.set(s,e)}),this.loadFromObjectStore(e,"cache",e=>{this.isCacheValid(e,t.config.TTL)&&t.cache.set(e.key,e)})]).then(()=>{this.notify(e,"data-ready"),t.config.endpoint&&t.config.delayFetch?(this.fetchQueue.push(e),1===this.fetchQueue.length&&this.processFetchQueue()):t.config.endpoint&&!t.config.delayFetch&&("requestIdleCallback"in window?requestIdleCallback(()=>this.fetch(e),{timeout:2e3}):setTimeout(()=>this.fetch(e),100))}).catch(t=>{console.error(`Background load error for store "${e}":`,t)})}async processFetchQueue(){if(0===this.fetchQueue.length)return;const e=this.fetchQueue.shift();if(!this.stores.get(e))return this.processFetchQueue();try{await this.fetch(e)}catch(t){console.error(`Queue fetch error for "${e}":`,t)}this.fetchQueue.length>0&&("requestIdleCallback"in window?requestIdleCallback(()=>this.processFetchQueue(),{timeout:2e3}):setTimeout(()=>this.processFetchQueue(),50))}async ensureStoreInitialized(e){const t=this.stores.get(e);if(!t)throw new Error(`Store "${e}" not registered`);t._initialized||await this.initDB(t.dbKey)}async withTransaction(e,t,s,r){const i=this.stores.get(e);return i?.db?("string"==typeof t&&(t=[t]),new Promise((e,a)=>{const o=i.db.transaction(t,s),n=t.map(e=>o.objectStore(e)),c=1===n.length?n[0]:n;let h;o.oncomplete=()=>e(h),o.onerror=()=>{const e=o.error||new Error("Transaction failed with unknown error");a(e)};try{h=r(c,o)}catch(e){a(e||new Error("Callback failed with unknown error"))}})):null}async fetch(e){await this.ensureStoreInitialized(e);const t=this.stores.get(e);if(!t.isFetching){if(t.config.required){if((Array.isArray(t.config.required)?t.config.required:[t.config.required]).some(e=>!t.filters[e]||""===t.filters[e]))return}t.isFetching=!0;try{const s=this.generateCacheKey(t.filters),r=t.cache.get(s);if(r&&this.isCacheValid(r,t.config.TTL)){let t=r.items.map(t=>this.get(e,t));return this.notify(e,"data-loaded",{cached:!0,items:t??[]}),r}t.config.showLoading&&this.setLoading(!0);const i=this.buildFetchUrl(e),a={...t.config.headers};t.config.useHttpCaching&&r&&(r.etag&&(a["If-None-Match"]=r.etag),r.lastModified&&(a["If-Modified-Since"]=r.lastModified));const o=new AbortController;let n;if(t.currentRequest=o,n=t.isAuth?await window.auth.fetch(i,{method:"GET",headers:a,signal:o.signal}):await fetch(i,{method:"GET",headers:a,signal:o.signal}),!n.ok){const e=await n.text();throw new Error(`HTTP error! status: ${n.status}, message: ${e}`)}if(304===n.status)return r?(this.notify(e,"data-loaded",{cached:!0,notModified:!0,items:r.items||[]}),r):(this.notify(e,"data-loaded",{cached:!1,notModified:!0,items:[]}),t.lastResponse={has_more:!1,total:0,pages:1,queue_stats:{}},{items:[]});if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);const c=await n.json();return await this.processFetchedData(e,c,s,n),this.notify(e,"data-loaded",{cached:!1,items:c.items||[]}),c}catch(t){if(!("AbortError"===t?.name))throw console.error(`Fetch error for store "${e}":`,t.message),console.dir(t),this.notify(e,"fetch-error",{error:t}),t}finally{t.isFetching=!1,t.currentRequest=null,t.config.showLoading&&this.setLoading(!1)}}}buildFetchUrl(e){const t=this.stores.get(e),s=new URLSearchParams;Object.entries(t.filters).forEach(([e,t])=>{null!=t&&""!==t&&("object"==typeof t?s.set(e,JSON.stringify(t)):s.set(e,t))});const r=t.config.apiBase+t.config.endpoint;return s.toString()?`${r}?${s}`:r}async processFetchedData(e,t,s,r){const i=this.stores.get(e),a=(t.items||[]).filter(e=>e&&"object"==typeof e),o=[];i.db&&a.length>0&&await this.withTransaction(e,i.config.storeName,"readwrite",t=>{a.forEach(s=>{try{const r=this._saveItem(e,s);o.push(r),t.put(r.processed)}catch(e){console.error("Error processing item:",e)}})});const n={key:s,items:a.map(e=>this.getItemKey(e,i.config.keyPath)),timestamp:Date.now(),endpoint:i.config.endpoint,filters:{...i.filters},etag:r.headers.get("ETag"),lastModified:r.headers.get("Last-Modified"),has_more:t.has_more||!1};i.cache.set(s,n),i.db?.objectStoreNames.contains("cache")&&await this.withTransaction(e,"cache","readwrite",e=>{e.put(n)}),i.lastResponse={...t,has_more:t.has_more||!1,total:t.total||a.length,pages:t.pages||1,queue_stats:t.queue_stats||{}};for(let[t,s]of Object.entries(i.filters))"string"==typeof s&&s.includes(",")&&this.createSplitCacheEntries(e,a,t,i.filters,r);o.forEach(t=>{t.statusChanged&&this.notify(e,"item-saved",{item:t.item,key:t.key,previousItem:t.previousItem})})}createSplitCacheEntries(e,t,s,r,i){const a=this.stores.get(e);r[s].split(",").map(e=>e.trim()).forEach(t=>{let o={};o[s]=t;const n={...r,[s]:t},c=this.generateCacheKey(n);if(a.cache.has(c))return;let h=this.filterByIndex(e,o).map(e=>this.getItemKey(e,a.config.keyPath));const l={key:c,items:h,timestamp:Date.now(),endpoint:a.config.endpoint,filters:n,etag:i.headers.get("Etag"),lastModified:i.headers.get("Last-Modified"),has_more:20===h.length};a.cache.set(c,l),a.db?.objectStoreNames.contains("cache")&&this.withTransaction(e,"cache","readwrite",e=>{e.put(l)})})}_saveItem(e,t){const s=this.stores.get(e),r=this.processForStorage(t,s.config.validateData);if(!r.valid)throw new Error(`Non-serializable data: ${r.error}`);const i=r.data,a=this.getItemKey(i,s.config.keyPath),o=s.data.get(a);return s.data.set(a,t),{item:t,previousItem:o,key:a,processed:i,statusChanged:o&&o.status!==t.status}}async save(e,t){const s=this.stores.get(e),r=this._saveItem(e,t);return await this.withTransaction(e,s.config.storeName,"readwrite",e=>{e.put(r.processed)}),this.notify(e,"item-saved",{item:r.item,key:r.key,previousItem:r.previousItem}),r.key}async saveMany(e,t){const s=this.stores.get(e);if(!s)return[];const r=t instanceof Map?Array.from(t.values()):Array.isArray(t)?t:Object.values(t);if(0===r.length)return[];const i=[];return r.forEach(t=>{const s=this._saveItem(e,t);i.push(s)}),await this.withTransaction(e,s.config.storeName,"readwrite",e=>{i.forEach(t=>{e.put(t.processed)})}),this.notify(e,"items-saved",{count:i.length,keys:i.map(e=>e.key)}),i.map(e=>e.key)}processForStorage(e,t=!0,s="root"){if(null===e)return{valid:!0,data:null};if(void 0===e)return t?{valid:!1,error:`Undefined value at ${s}`}:{valid:!0,data:void 0};const r=typeof e;if(["string","number","boolean"].includes(r))return{valid:!0,data:e};if("function"===r)return t?{valid:!1,error:`Function at ${s}`}:{valid:!0,data:void 0};if(e instanceof HTMLElement||void 0!==e.nodeType)return t?{valid:!1,error:`DOM element at ${s}`}:{valid:!0,data:void 0};if(e instanceof FormData)return{valid:!0,data:this.formDataToObject(e)};if(e instanceof Date||e instanceof ArrayBuffer||ArrayBuffer.isView(e)||e instanceof Blob)return{valid:!0,data:e};if(e instanceof Set)return this.processForStorage(Array.from(e),t,s);if(e instanceof Map&&(e=Object.fromEntries(e)),Array.isArray(e)){const r=[];for(let i=0;i<e.length;i++){const a=this.processForStorage(e[i],t,`${s}[${i}]`);if(!a.valid)return a;void 0!==a.data&&r.push(a.data)}return{valid:!0,data:r}}if("object"===r){const r={};for(const[i,a]of Object.entries(e)){if(void 0===a)continue;const e=this.processForStorage(a,t,`${s}.${i}`);if(!e.valid)return e;void 0===e.data&&null!==a||(r[i]=e.data)}return{valid:!0,data:r}}return t?{valid:!1,error:`Unknown type at ${s}`}:{valid:!0,data:void 0}}async delete(e,t){const s=this.stores.get(e);s.data.delete(t),await this.withTransaction(e,s.config.storeName,"readwrite",e=>{e.delete(t)}),this.notify(e,"item-deleted",{id:t})}async deleteMany(e,t){const s=this.stores.get(e);if(!s)return[];const r=t instanceof Set?Array.from(t):Array.isArray(t)?t:Object.keys(t);return 0===r.length?[]:(r.forEach(e=>{s.data.delete(e)}),await this.withTransaction(e,s.config.storeName,"readwrite",e=>{r.forEach(t=>{e.delete(t)})}),this.notify(e,"items-deleted",{count:r.length,ids:r}),r)}get(e,t){return this.stores.get(e).data.get(t)}getMany(e,t,s=!0){const r=this.stores.get(e);if(!r)return[];const i=t instanceof Set?Array.from(t):Array.isArray(t)?t:Object.keys(t);return 0===i.length?[]:s?i.reduce((e,t)=>{const s=r.data.get(t);return s&&e.push(s),e},[]):i.map(e=>r.data.get(e)??null)}getAll(e){const t=this.stores.get(e);return Array.from(t.data.values())}filterByIndex(e,t){const s=this.stores.get(e);return s?Array.from(s.data.values()).filter(e=>!(!e||"object"!=typeof e)&&Object.entries(t).every(([t,s])=>(Array.isArray(s)?s:[s]).includes(e[t]))):[]}async getAllByIndex(e,t,s){const r=this.stores.get(e),i=Array.isArray(s)?s:[s];if(r.db&&r.db.objectStoreNames.contains(r.config.storeName))try{const e=r.db.transaction([r.config.storeName],"readonly").objectStore(r.config.storeName);if(e.indexNames.contains(t)){const s=e.index(t);return(await Promise.all(i.map(e=>new Promise((t,r)=>{const i=s.getAll(e);i.onsuccess=()=>t(i.result||[]),i.onerror=()=>r(i.error)})))).flat()}}catch(e){console.warn(`Index query failed for "${t}", falling back to filter:`,e)}return Array.from(r.data.values()).filter(e=>i.includes(e[t]))}getFiltered(e){const t=this.stores.get(e),s=this.generateCacheKey(t.filters),r=t.cache.get(s);if(r?.items){const e=r.items.reduce((e,s)=>{const r=t.data.get(s);return r&&e.push(r),e},[]);return this.applyOrdering(e,t)}const i=Array.from(t.data.values()),a=t.filters.search?.toLowerCase().trim()||"",o=[];t.filters.taxonomy&&"object"==typeof t.filters.taxonomy&&Object.entries(t.filters.taxonomy).forEach(([e,t])=>{const s=Array.isArray(t)?t:[t];o.push(t=>{if(!t.taxonomies||!t.taxonomies[e])return!1;const r=Object.keys(t.taxonomies[e]).map(e=>parseInt(e));return s.some(e=>r.includes(parseInt(e)))})});for(const[e,s]of Object.entries(t.filters))if("taxonomy"!==e){if(!t.ignoreFilters.has(e)&&null!=s&&""!==s&&"all"!==s)if("string"==typeof s&&s.includes(",")){const t=s.split(",").map(e=>e.trim());o.push(s=>t.includes(String(s[e])))}else o.push(t=>String(t[e])===String(s))}else"string"!=typeof s||s.includes(",")||o.push(e=>e.taxonomy===s);const n=i.filter(e=>{for(const t of o)if(!t(e))return!1;return!(a&&!this.searchObject(e,a))});return this.applyOrdering(n,t)}applyOrdering(e,t){if(Array.isArray(e)||(e=Array.from(e)),0===e.length)return e;const s=t.filters.orderby||"date",r=(t.filters.order||"desc").toLowerCase();return["random","rand"].includes(s)||["random","rand"].includes(r)?this.shuffle(e):(e.sort((e,t)=>{let i,a;switch(s){case"alphabetical":case"title":i=(e.title||e.name||"").toLowerCase(),a=(t.title||t.name||"").toLowerCase();break;case"modified":i=new Date(e.modified||e.date||0),a=new Date(t.modified||t.date||0);break;default:i=new Date(e.date||e.modified||0),a=new Date(t.date||t.modified||0)}return i<a?"asc"===r?-1:1:i>a?"asc"===r?1:-1:0}),e)}shuffle(e){const t=e.slice();for(let e=t.length-1;e>0;e--){const s=Math.floor(Math.random()*(e+1));[t[e],t[s]]=[t[s],t[e]]}return t}searchObject(e,t){if(!e||"object"!=typeof e)return"string"==typeof e&&e.toLowerCase().includes(t);for(const s of Object.values(e))if(null!=s)if("object"!=typeof s){if("string"==typeof s&&s.toLowerCase().includes(t))return!0}else if(this.searchObject(s,t))return!0;return!1}async clear(e){const t=this.stores.get(e);t.data.clear(),t.cache.clear(),await this.withTransaction(e,t.config.storeName,"readwrite",e=>{e.clear()}),this.notify(e,"data-cleared")}async updateFilters(e,t,s=!1){const r=this.stores.get(e),i={...r.filters};s&&(r.filters={...r.config.filters}),Object.entries(t).forEach(([e,t])=>{null==t||""===t?delete r.filters[e]:r.filters[e]=t}),this.notify(e,"filters-changed",{oldFilters:i,filters:r.filters,updates:t});const a=await this.shouldFetchWithFilters(e,t,i);if(r.config.endpoint&&a)await this.fetch(e);else{const t=this.getFiltered(e);this.notify(e,"data-loaded",{cached:!0,items:t})}}async shouldFetchWithFilters(e,t,s){const r=this.stores.get(e);if(!r.config.endpoint||!r.lastResponse)return!0;if(!1===r.lastResponse.has_more&&this.hasCompleteData(r,r.filters))return!1;if("page"in t){const e=t.page,i=s.page||1;if(e>i&&!r.lastResponse.has_more)return r.filters.page=i,!1}if("search"in t){const e=t.search?.trim()||"",i=s.search?.trim()||"";if(!e&&i){const e={...r.filters};if(delete e.search,e.page=1,this.hasCompleteData(r,e))return!1}if(e&&e!==i){const e={...r.filters};if(delete e.search,e.page=1,this.hasCompleteData(r,e))return!1}}return!0}hasCompleteData(e,t){const s=this.generateCacheKey(t),r=e.cache.get(s);return!!r&&(!1===r.has_more||!1===e.lastResponse?.has_more)}setFilter(e,t,s){return this.updateFilters(e,{[t]:s})}async setFilters(e,t){const s=this.stores.get(e);if(Object.keys(t).some(e=>s.filters[e]!==t[e])||Object.keys(s.filters).some(e=>!(e in t)&&t!==s.config.filters))return this.updateFilters(e,t)}removeFilter(e,t){return this.updateFilters(e,{[t]:null})}clearFilters(e){return this.updateFilters(e,{},!0)}clearCache(e){const t=this.stores.get(e);t.cache.clear(),t.db?.objectStoreNames.contains("cache")&&this.withTransaction(e,"cache","readwrite",e=>{e.clear()}),this.notify(e,"cache-cleared")}generateCacheKey(e){const t=Object.keys(e).sort().reduce((t,s)=>(t[s]=e[s],t),{});return JSON.stringify(t)}isCacheValid(e,t){if(!e||!e.timestamp)return!1;return Date.now()-e.timestamp<t}subscribe(e,t){this.subscribers.has(e)||this.subscribers.set(e,new Set);const s=this.subscribers.get(e);return s.add(t),()=>s.delete(t)}notify(e,t,s={}){const r=this.subscribers.get(e);r&&r.forEach(r=>{try{r(t,s)}catch(t){console.error(`Subscriber error for store "${e}":`,t)}})}getItemKey(e,t){if("function"==typeof t)return t(e);const s=t.split(".");let r=e;for(const e of s)r=r?.[e];return r}setLoading(e){this.body.classList.toggle("loading",e),e?this.loading?.showModal():this.loading?.close()}destroy(){this.stores.forEach(e=>{e.currentRequest&&e.currentRequest.abort()}),this.databases.forEach(e=>e.close()),this.stores.clear(),this.subscribers.clear(),this.databases.clear(),this.pendingInits.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbStore=new e)})})})();
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",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}}}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).items.forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}const e=s.dataset.field;return void window.debouncer.schedule(`collection:${e}`,()=>this.updateCollectionField(s),150)}if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).items.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.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,this.changes.set(s.id,i),s.options.cache&&this.scheduleBackup()}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))}registerForm(e,t){if(t={autoUpload:!1,imageMeta:!0,delay:1500,endpoint:Object.hasOwn(e.dataset,"save")?e.dataset.save:"",showStatus:!0,showSummary:!1,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_"));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 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)=>{t.items=t.items.filter(t=>t.form!==e),0===t.items.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 l=s.result.cloneNode(!0),o=l.querySelector("h3"),d=l.querySelector("p");const c=a.field?.querySelector("legend");o.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(l)}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"),l=r.querySelector("p");l?.remove(),n&&(n.textContent=i),a.forEach(t=>{t=t.cloneNode(!0),e.append(t)}),t.append(e)}}),s.result?.remove(),a.config.element.after(t),window.fade(a.config.element,!1)}})}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(t,{handle:this.selectors.repeater.header,animation:150,onEnd:()=>{this.reindexList(t)}})),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.a11y.announce("Row added")}removeRepeaterRow(e){let t=e.closest("[data-repeater-id]");e.remove(),this.reindexList(t),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 l=e.name.replace("new_","");i[l]=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 l=t.ui.items?.children?.length??0;n?.querySelectorAll("input[type=hidden]")?.forEach(e=>{const s=e.dataset.field;e.name=`${t.fieldName}:${l}:${s}`,e.id=`${t.fieldName}:${l}:${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??"==";if(!this.dependencies.has(s)){let e=document.querySelector(`[field="${s}"]`);e&&this.dependencies.set(s,{element:e,items:[]})}let r=this.dependencies.get(s);r.items.push({field:t,form:e.dataset.formId,requiredValue:i,operator:a}),this.dependencies.set(s,r),this.checkFieldDependency(r,s)})}checkFieldDependency(e,t){const s=this.dependencies.get(t);if(!s)return;const i=this.getFieldCheckedValue(s.element),a=this.evaluateCondition(i,e.requiredValue,e.operator);this.toggleFieldVisibility(e.field,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,s)=>this.validateStep(e,t)}),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.inputs))}),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,a=t.ui.sections.length;window.showProgress(t.ui.tabs.progress,i,a)}}),this.forms.set(t.id,t))}validateStep(e,t){const s=e.closest("[data-form-id]")?.dataset.formId;if(!s)return!0;if(!this.forms.get(s))return!0;return Array.from(this.inputs.values()).filter(t=>t&&t.form===s&&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){const t=e.dataset.field||e.dataset.repeaterId||e.dataset.tagListId;Array.from(e.children).forEach((e,s)=>{e.dataset.index=`${s}`;e.querySelectorAll("input, select, textarea").forEach(i=>{if("file"===i.type)return;i.dataset.field||i.name.split(":").pop();window.prefixInput(i,`${t}:${s}:`,e,!1,!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)}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"),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("[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)})})})();
(()=>{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",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}}}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 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.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,this.changes.set(s.id,i),s.options.cache&&this.scheduleBackup()}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))}registerForm(e,t){if(t={autoUpload:!1,imageMeta:!0,delay:1500,endpoint:Object.hasOwn(e.dataset,"save")?e.dataset.save:"",showStatus:!0,showSummary:!1,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_"));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 l=s.result.cloneNode(!0),o=l.querySelector("h3"),d=l.querySelector("p");const c=a.field?.querySelector("legend");o.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(l)}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"),l=r.querySelector("p");l?.remove(),n&&(n.textContent=i),a.forEach(t=>{t=t.cloneNode(!0),e.append(t)}),t.append(e)}}),s.result?.remove(),a.config.element.after(t),window.fade(a.config.element,!1)}})}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(t,{handle:this.selectors.repeater.header,animation:150,onEnd:()=>{this.reindexList(t)}})),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.a11y.announce("Row added")}removeRepeaterRow(e){let t=e.closest("[data-repeater-id]");e.remove(),this.reindexList(t),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 l=e.name.replace("new_","");i[l]=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 l=t.ui.items?.children?.length??0;n?.querySelectorAll("input[type=hidden]")?.forEach(e=>{const s=e.dataset.field;e.name=`${t.fieldName}:${l}:${s}`,e.id=`${t.fieldName}:${l}:${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,s)=>this.validateStep(e,t)}),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.inputs))}),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,a=t.ui.sections.length;window.showProgress(t.ui.tabs.progress,i,a)}}),this.forms.set(t.id,t))}validateStep(e,t){const s=e.closest("[data-form-id]")?.dataset.formId;if(!s)return!0;if(!this.forms.get(s))return!0;return Array.from(this.inputs.values()).filter(t=>t&&t.form===s&&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){const t=e.dataset.field||e.dataset.repeaterId||e.dataset.tagListId;Array.from(e.children).forEach((e,s)=>{e.dataset.index=`${s}`;e.querySelectorAll("input, select, textarea").forEach(i=>{if("file"===i.type)return;i.dataset.field||i.name.split(":").pop();window.prefixInput(i,`${t}:${s}:`,e,!1,!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)}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"),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("[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)})})})();
assets/js/min/queue.min.js
@@ -1 +1 @@
(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.user=window.auth.getUser(),this.user&&(this.canUpdateUI=!0,this.isProcessing=!1,this.isPolling=!1,this.queue=new Map,this.items=new Map,this.subscribers=new Set,this.loadFromStorage=!1,this.api=jvbSettings.api,this.endpoint="queue",this.init())}init(){this.headers={"X-WP-Nonce":window.auth.getNonce()},this.initElements(),this.initListeners(),this.initStore(),this.canUpdateUI&&this.ui.panel&&(this.popup=window.jvbPopup.registerPopup({popup:this.ui.panel,toggle:this.ui.toggle.button,name:"Queue Panel"})),this.defineTemplates()}initElements(){this.panelStatuses=["syncing","synced","pending","offline"],this.statuses=["queued","localProcessing","uploading","pending","processing","completed","failed","failed_permanent"],this.pendingStatuses=["queued","localProcessing","uploading"],this.workingStatuses=["pending","processing"],this.completedStatuses=["completed","failed","failed_permanent"],this.icons={queued:"arrows-clockwise",localProcessing:"arrows-clockwise",uploading:"syncing",pending:"cloud",processing:"syncing",completed:"cloud-check",failed:"cloud-warning",failed_permanent:"cloud-warning"},this.selectors={panel:"aside#queue",toggle:{button:"button.qtoggle",indicator:".qtoggle .indicator",count:".qtoggle .count"},refresh:{button:"#queue .m-actions .refresh",countdown:"#queue .m-actions .refresh .countdown"},popup:{popup:"#queue .popup",message:"#queue .popup span"},items:{container:"#queue .qitems"},actions:{retry:"#queue .retry-all",clear:"#queue .dismiss-all"},filters:{filter:"#queue [data-filter]",all:{label:'#queue [for="qfilter-all"]',radio:'#queue [data-filter="all"]',count:'#queue [data-filter="all"] .count'},queued:{label:'#queue [for="qfilter-queued"]',input:'#queue [data-filter="queued"]',count:'#queue [for="qfilter-queued"] .count'},localProcessing:{label:'#queue [for="qfilter-localProcessing"]',input:'#queue [data-filter="localProcessing"]',count:'#queue [for="qfilter-localProcessing"] .count'},uploading:{label:'#queue [for="qfilter-uploading"]',input:'#queue [data-filter="uploading"]',count:'#queue [for="qfilter-uploading"] .count'},pending:{label:'#queue [for="qfilter-pending"]',input:'#queue [data-filter="pending"]',count:'#queue [for="qfilter-pending"] .count'},processing:{label:'#queue [for="qfilter-processing"]',input:'#queue [data-filter="processing"]',count:'#queue [for="qfilter-processing"] .count'},completed:{label:'#queue [for="qfilter-completed"]',input:'#queue [data-filter="completed"]',count:'#queue [for="qfilter-completed"] .count'},failed:{label:'#queue [for="qfilter-failed"]',input:'#queue [data-filter="failed"]',count:'#queue [for="qfilter-failed"] .count'}},item:{type:".type",status:".status",details:".info .details",icon:".status .icon",startedAt:".started time",completed:{wrap:".completed",label:".completed span",time:".completed time"},progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},actions:{cancel:"button.cancel",retry:"button.retry",refresh:"button.refresh",dismiss:"button.dismiss"}}},this.ui=window.uiFromSelectors(this.selectors),this.ui.panel||(this.canUpdateUI=!1)}defineTemplates(){const e=window.jvbTemplates;e.define("emptyState"),e.define("queueItem",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id}})}initListeners(){this.activityListeners=null,this.clickHandler=this.handleClick.bind(this),this.onlineHandler=this.handleOnline.bind(this),this.offlineHandler=this.handleOffline.bind(this),this.unloadHandler=this.handleBeforeUnload.bind(this),this.visibilityHandler=this.handleVisibilityChange.bind(this),document.addEventListener("click",this.clickHandler),window.addEventListener("online",this.onlineHandler),window.addEventListener("offline",this.offlineHandler),document.addEventListener("visibilitychange",this.visibilityHandler)}handleOnline(){this.updatePanel("synced"),this.getQueueByStatus(this.pendingStatuses).length>0&&this.processQueue()}handleOffline(){this.updatePanel("offline")}handleVisibilityChange(e){this.isPolling&&document.hidden?this.stopPolling():this.maybeStartPolling()}handleBeforeUnload(e){if(!this.ui.panel)return;return this.getQueueByStatus(this.pendingStatuses).length>0?(e.preventDefault(),e.returnValue="",""):void 0}handleClick(e){if(!window.targetCheck(e,this.selectors.panel+", "+this.selectors.toggle.button))return;if(window.targetCheck(e,this.selectors.refresh.button))return this.ui.refresh.button.classList.add("fetching"),this.store.clearCache(),this.store.clearFilters(),void this.store.fetch().finally(()=>{this.ui.refresh.button.classList.remove("fetching")});if(window.targetCheck(e,this.selectors.actions.refresh))return void this.handleRefresh(opId);if(window.targetCheck(e,this.selectors.actions.clear))return void this.opActions("completed","dismiss").then(()=>{});if(window.targetCheck(e,this.selectors.actions.retry))return void this.opActions("failed","retry").then(()=>{});const t=window.targetCheck(e,"[data-action]");if(t){const e=t.closest("[data-id]")?.dataset.id;return void(e&&this.opActions(e,t.dataset.action))}const s=window.targetCheck(e,this.selectors.filters.filter);s&&this.setFilter(s.dataset.filter)}setFilter(e){Object.values(this.ui.filters).forEach(t=>{t.input?.dataset.filter===e&&(t.input.checked=!0)}),"all"===e?this.store.clearFilters():this.store.setFilter("status",e)}trackActivity(){if(!this.activityListeners){const e=["mousedown","mousemove","keypress","scroll","touchstart"];this.activityListeners=e.map(e=>{const t=()=>this.resetActivityTimer();return document.addEventListener(e,t,{passive:!0}),{event:e,handler:t}})}this.resetActivityTimer()}resetActivityTimer(){this.activityTimer&&clearTimeout(this.activityTimer),this.activityTimer=setTimeout(()=>{this.processQueue()},1750)}stopActivityTracking(){this.activityTimer&&(clearTimeout(this.activityTimer),this.activityTimer=null),this.activityListeners&&(this.activityListeners.forEach(({event:e,handler:t})=>{document.removeEventListener(e,t)}),this.activityListeners=null)}initStore(){if(!this.user)return;const e=window.jvbStore.register("queue",{storeName:"queue",keyPath:"id",endpoint:this.endpoint,TTL:1/0,isAuth:!0,indexes:[{name:"status",keyPath:"status"},{name:"type",keyPath:"type"}],filters:{user:window.auth.getUser()},showLoading:!1});this.store=e.queue,this.store.subscribe((e,t)=>{switch(e){case"data-loaded":this.store.getAll().forEach(e=>{const t=this.queue.get(e.id),s=this.mapServerOperation(e);this.queue.set(s.id,s),t&&t.status!==s.status&&this.notify("operation-status",s)}),this.maybeStartPolling(),this.updateUI();break;case"items-save":this.maybeStartPolling(),this.updateUI();break;case"item-saved":t.item&&(this.queue.set(t.item.id,t.item),t.previousItem?.status!==t.item.status&&this.notify("operation-status",t.item)),this.maybeStartPolling()}})}handleRefresh(e){const t=this.getQueue(e);if(!t)return;let s=null;if(s={content_update:t.data?.posts?Object.values(t.data.posts)[0]?.content:null,batch_creation:t.data?.content,image_upload:"uploads",video_upload:"uploads",document_upload:"uploads"}[t.type],s&&window.jvbStore){if(window.jvbStore.stores.get(s)){window.jvbStore.clearCache(s),window.jvbStore.fetch(s);const t=this.items.get(e)?.ui?.actions?.refresh;if(t){const e=t.querySelector("span").textContent;t.querySelector("span").textContent="Refreshed!",t.disabled=!0,setTimeout(()=>{t.querySelector("span").textContent=e,t.disabled=!1},2e3)}}}else confirm("Refresh the page to see changes?")&&window.location.reload()}addToQueue(e){const t={id:`u${this.user}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,endpoint:null,method:"POST",headers:{},data:{},delay:!1,canMerge:!0,popup:"Saving changes...",title:"Operation",status:"queued",timestamp:Date.now(),created_at:(new Date).toISOString(),retries:0,user:this.user,...e};if(t.headers={...this.headers,...t.headers},!t.endpoint||!t.data)return null;if(t.popup&&this.ui.popup?.message&&(this.ui.popup.message.textContent=t.popup,this.ui.popup.popup.hidden=!1,setTimeout(()=>this.ui.popup.popup.hidden=!0,2e3)),!t.delay)return this.queue.set(t.id,t),this.processOperation(t).then(()=>{}),this.store.clearCache(),this.maybeStartPolling(),this.toggleQueue(),t.id;const s=Array.from(this.getAllQueue()).filter(e=>"queued"===e.status&&e.endpoint===t.endpoint&&e.canMerge);if(s.length>0){const e=s[0];return e.data=window.deepMerge(e.data,t.data),e.timestamp=Date.now(),this.setQueue(e),this.updateOperationStatus(e.id,e.status),this.updateUI(),this.trackActivity(),e.id}return this.store.clearCache(),this.setQueue(t),this.updateOperationStatus(t.id,t.status),this.updateUI(),this.trackActivity(),t.id}async opActions(e,t){if(this.statuses.includes(e)?e=this.getQueueByStatus(e).map(e=>e.id):"string"==typeof e&&(e=[e]),0===e.length)return;if(!["cancel","dismiss","retry"].includes(t))return;const s=["cancel","dismiss"].includes(t);s&&e.forEach(e=>{this.removeOperationUI(e)});try{const i=await window.auth.fetch(`${this.api}${this.endpoint}`,{method:"POST",headers:{"Content-Type":"application/json",...this.headers},body:JSON.stringify({action:t,ids:Array.isArray(e)?e:[e],user:this.user})});if(!i.ok)throw new Error(`${t} failed: ${i.status}`);const n=await i.json();if(!n.success)throw new Error(n.message||`${t} operation failed`);return e.forEach(e=>{let i=this.getQueue(e);if(i&&this.notify(`${t}-operation`,i),s)this.clearQueue(e);else{let t=this.getQueue(e);t.status="queued",this.setQueue(t),this.updateOperationStatus(t.id,t.status)}}),"retry"===t&&this.trackActivity(),this.updateUI(),n}catch(s){return await window.jvbError.log(s,{component:"Queue",operation:"performQueueAction",action:t,operationIds:e,itemCount:e.length},()=>this.opActions(e,t)),{success:!1,error:s.message}}}async processQueue(){if(this.isProcessing)return;const e=this.getQueueByStatus("queued");if(0===e.length)return void this.stopActivityTracking();this.setProcessing();for(const t of e)await this.processOperation(t);this.setProcessing(!1);0===this.getQueueByStatus("queued").length?this.stopActivityTracking():this.trackActivity(),this.toggleQueue(this.maybeStartPolling())}async processOperation(e){try{this.queue.has(e.id)||this.queue.set(e.id,e);let t,s,i=!1;if(e.data?._isFormData&&!e.data instanceof FormData&&(i=!0,e.data=await this.store.objectToFormData(e.data)),this.updateOperationStatus(e.id,"uploading"),e.data instanceof FormData?(e.data.append("id",e.id),e.data.append("user",window.auth.getUser()),t=e.data,s=e.data):(s={...e.data,id:e.id,user:window.auth.getUser()},t=JSON.stringify(s),e.headers["Content-Type"]="application/json"),"unknown"===e.endpoint||null==t)return;const n=await window.auth.fetch(`${this.api}${e.endpoint}`,{method:e.method,headers:e.headers,body:t});console.log("Sending request with data: ",s);const r=await n.json();if(i&&(e.data={}),console.log("Result: ",r),!n.ok||!r.success)throw new Error(r.message||`HTTP ${n.status}`);this.notify("sent-to-server",s),r.id&&e.id!==r.id?e=await this.handleServerMerge(e,r):(e.status=r.status??"failed",e.serverData=r,this.updateOperationStatus(e.id,e.status)),this.a11y.announce(`${e.title} sent to server for processing`),this.setQueue(e)}catch(t){console.error("Operation failed: ",t),e.retries++,e.lastError=t.message,e.retries>=3?e.status="failed_permanent":e.status="failed",this.updateOperationStatus(e.id,e.status),this.setQueue(e)}}async handleServerMerge(e,t){const s=this.getQueue(t.id);return s?(e.status=t.status||"pending",e.serverData=t,this.mergeOp(s,e)):(this.clearQueue(e.id),this.setQueue(t),t)}mergeOp(e,t){return e.data=window.deepMerge(e.data,t.data),e.status=t.status,Object.hasOwn(t,"serverData")&&(e.serverData=t.serverData),this.updateOperationStatus(e.id,e.status),this.removeOperationUI(t.id),this.clearQueue(t.id),e}sortByDate(e){return e.sort((e,t)=>(e.updated_at??e.timestamp??0)-(t.updated_at??t.timestamp??0))}sortOperations(e){const t={processing:0,uploading:1,pending:2,queued:3,localProcessing:4,failed:5,completed:6,failed_permanent:7};return e.sort((e,s)=>{const i=(t[e.status]??99)-(t[s.status]??99);if(0!==i)return i;const n=e.updated_at??e.timestamp??0,r=s.updated_at??s.timestamp??0;return new Date(r)-new Date(n)})}getAllQueue(){let e=new Set,t=[...Array.from(this.queue.values())];return this.loadFromStorage||(this.loadFromStorage=!0,t=[...t,...Array.from(this.store.data.values())],t=t.filter(t=>{const s=e.has(t.id);return e.add(t.id),!s})),this.sortOperations(t)}getQueueByStatus(e){return"string"==typeof e&&(e=[e]),this.getAllQueue().filter(t=>e.includes(t.status))}updateOperationStatus(e,t){let s=this.getQueue(e);s&&(this.statuses.includes(t)?(s.status=t,this.notify("operation-status",s),this.setQueue(s)):console.log("Invalid status: ",t))}setQueue(e){this.store.save(e),this.queue.set(e.id,e)}getQueue(e){return this.queue.has(e)?this.queue.get(e):this.store.get(e)}clearQueue(e){this.queue.delete(e),this.store.delete(e)}maybeStartPolling(){return this.getQueueByStatus([...this.pendingStatuses,...this.workingStatuses]).length>0?(this.startPolling(),!0):(this.updatePanel("synced"),!1)}startPolling(){this.isPolling||(this.isPolling=!0,this.updatePanel("pending"),this.runPollCycle())}async runPollCycle(){if(this.isPolling){try{if(this.ui.refresh.button.classList.add("fetching"),this.store.clearCache(),429===(await this.store.fetch()).status)return console.log("Too many requests. Waiting 30 seconds"),this.stopPolling(),void this.startCountdown(30,()=>this.runPollCycle());if(this.ui.refresh.button.classList.remove("fetching"),!this.maybeStartPolling())return this.stopPolling(),void this.updatePanel("synced")}catch(e){console.error("Polling error:",e)}this.startCountdown(5,()=>this.runPollCycle())}}startCountdown(e,t){this.ui.refresh.countdown?(this.ui.refresh.countdown.classList.add("counting"),this.ui.refresh.countdown.textContent=e,this.countdownTimer=setInterval(()=>{--e>0?this.ui.refresh.countdown.textContent=e:(this.stopCountdown(),t&&t())},1e3)):console.warn("Countdown element not found")}stopPolling(){this.isPolling&&(this.isPolling=!1,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.stopCountdown())}stopCountdown(){this.countdownTimer&&(clearInterval(this.countdownTimer),this.countdownTimer=null),this.ui.refresh.countdown.classList.remove("counting"),this.ui.refresh.countdown.textContent=""}updateUI(){this.canUpdateUI&&window.debouncer.schedule("queue-ui",this.handleUpdateUI.bind(this))}handleUpdateUI(){const e=this.getAllQueue();this.ui.actions.retry.disabled=0===e.filter(e=>"failed"===e.status).length,this.ui.actions.clear.disabled=0===e.filter(e=>"completed"===e.status).length;let t=e.filter(e=>[...this.pendingStatuses,...this.workingStatuses].includes(e.status));t=t.length,this.ui.toggle.count.hidden=0===t,this.ui.toggle.count.textContent=t;for(let t of this.statuses){if("failed_permanent"===t)continue;let s=e.filter(e=>e.status===t).length;this.ui.filters[t].label.hidden=0===s,this.ui.filters[t].input.dataset.count=`${s}`,this.ui.filters[t].count.textContent=s>0?s:""}this.renderOperations()}renderOperations(){if(!this.ui.items.container)return;const e=this.store.filters?.status??"all",t="all"===e?this.getAllQueue():this.getQueueByStatus(e),s=this.sortOperations(t);if(0===s.length){window.removeChildren(this.ui.items.container);const e=window.jvbTemplates.create("emptyQueue");return this.ui.items.container.append(e),void this.a11y.announce("No items in queue")}this.ui.items.container.querySelector(".empty-group")?.remove();const i=new Set(s.map(e=>e.id));this.items.forEach((e,t)=>{i.has(t)||(e.element?.remove(),this.items.delete(t))}),s.forEach((e,t)=>{let s=this.items.get(e.id);s||(s=this.createOperationElement(e)),s?.element&&(this.updateOperationUI(e.id),this.ui.items.container.append(s.element))})}createOperationElement(e){const t=window.jvbTemplates.create("queueItem",e),s={element:t,ui:window.uiFromSelectors(this.selectors.item,t)};return this.items.set(e.id,s),s}updateOperationUI(e){let t=this.items.has(e)?this.items.get(e):this.createOperationElement(e);if(!t)return;let s=this.getQueue(e),i=t.element;i.classList.remove(...this.statuses),i.classList.add(s.status);let n=this.getProgress(s);t.ui.type&&t.ui.type.textContent!==s.title&&(t.ui.type.textContent=s.title),t.ui.status&&(t.ui.status.title=this.statusLabel(s.status)),t.ui.icon&&(t.ui.icon.className=`icon icon-${this.icons[s.status]}`),t.ui.details&&(t.ui.details.textContent=this.itemMessage(s)),t.ui.startedAt&&(t.ui.startedAt.setAttribute("datetime",s.created_at),t.ui.startedAt.textContent=window.formatTimeAgo(s.created_at));s.status;const r="completed"===s.status&&(s.completed_at||s.updated_at);if(t.ui.completed.wrap.hidden=!r,r){const e=s.completed_at??s.updated_at;t.ui.completed.label.textContent="Completed: ",t.ui.completed.time.setAttribute("datetime",e),t.ui.completed.time.textContent=window.formatTimeAgo(e)}window.showProgress(t.ui.progress,n,100,this.statusLabel(s.status)),t.ui.actions.cancel&&(t.ui.actions.cancel.hidden=this.completedStatuses.includes(s.status)),t.ui.actions.retry&&(s.retries>=3&&(t.ui.actions.retry.disabled=!0),t.ui.actions.retry.hidden="failed"!==s.status),t.ui.actions.dismiss&&(t.ui.actions.dismiss.hidden=this.pendingStatuses.includes(s.status)),t.ui.actions.refresh&&(t.ui.actions.refresh.hidden="completed"!==s.status)}getProgress(e){if(void 0!==e.progress_percentage)return e.progress_percentage;if(void 0!==e.progress)return e.progress;if(!this.statuses.includes(e.status))return 0;return{queued:10,uploading:25,pending:40,processing:70,completed:100,failed:0,failed_permanent:0}[e.status]??0}removeOperationUI(e){let t=this.items.get(e);t&&window.fade(t.element,!1)}updatePanel(e="syncing"){this.ui.panel&&this.panelStatuses.includes(e)&&(this.ui.panel.classList.remove(...this.panelStatuses),this.ui.panel.classList.add(e))}statusLabel(e){if(!this.statuses.includes(e))return"";return{queued:"Queued",localProcessing:"Processing locally",uploading:"Uploading",pending:"Waiting on server",processing:"Processing",completed:"Completed",failed:"Failed",failed_permanent:"Failed permanently",merged:"Merged"}[e]}itemMessage(e){if(Object.hasOwn(e,"message")&&""!==e.message)return e.message;if(Object.hasOwn(e,"error_message")&&e.error_message)return e.error_message;switch(e.status){case"queued":return"Waiting to send...";case"uploading":return"Sending to server...";case"pending":return e.position?`Position ${e.position} in queue`:"In server queue";case"processing":if(e.count&&void 0!==e.progress_count){const t=e.progress_count,s=e.count;return`Processing ${t}/${s} items (${Math.round(t/s*100)}%)`}return void 0!==e.progress_percentage?`${e.progress_percentage}% complete`:"Processing...";case"completed":return"Successfully completed. Refresh to see changes.";case"merged":return e.merged_into?`Merged with another operation (${e.merged_into.substring(0,8)}...)`:"Merged with another operation";case"failed":return`Failed: ${e.lastError||"Unknown error"} (Retry ${e.retries}/2)`;case"failed_permanent":return`Failed: ${e.lastError||"Unknown error"}`;default:return""}}toggleQueue(e=!0){this.ui.panel&&(this.ui.panel.hidden=!e,this.ui.toggle.button.hidden=!e)}setProcessing(e=!0){this.isProcessing=e,this.ui.toggle.button.classList.toggle("saving",e)}mapServerOperation(e){const t=this.queue.get(e.id);if(t&&t.endpoint){const s={...t,...e,endpoint:t.endpoint,method:t.method,headers:t.headers,progress_percentage:e.progress_percentage,progress_count:e.progress_count,count:e.count};e.merged_into&&this.handleMergedOperation(s)}const s=e.type?e.type.replace("_update","").replace("_","/"):"unknown",i={...e,endpoint:s,method:"POST",headers:{...this.headers}};return e.merged_into&&this.handleMergedOperation(i),i}handleMergedOperation(e){e.merged_into&&(console.log(`[Queue] Operation ${e.id} merged into ${e.merged_into}`),setTimeout(()=>{this.clearQueue(e.id),this.removeOperationFromUI(e.id)},3e3))}subscribe(e){if(this.subscribers)return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>s(e,t))}destroy(){this.isPolling&&this.stopPolling(),this.stopActivityTracking(),document.removeEventListener("click",this.clickHandler),this.subscribers.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbQueue=new e)})})})();
(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.user=window.auth.getUser(),this.user&&(this.canUpdateUI=!0,this.isProcessing=!1,this.isPolling=!1,this.queue=new Map,this.items=new Map,this.subscribers=new Set,this.loadFromStorage=!1,this.api=jvbSettings.api,this.endpoint="queue",this.init())}init(){this.headers={"X-WP-Nonce":window.auth.getNonce()},this.initElements(),this.initListeners(),this.initStore(),this.canUpdateUI&&this.ui.panel&&(this.popup=window.jvbPopup.registerPopup({popup:this.ui.panel,toggle:this.ui.toggle.button,name:"Queue Panel"})),this.defineTemplates()}initElements(){this.panelStatuses=["syncing","synced","pending","offline"],this.statuses=["queued","localProcessing","uploading","pending","processing","completed","failed","failed_permanent"],this.pendingStatuses=["queued","localProcessing","uploading"],this.workingStatuses=["pending","processing"],this.completedStatuses=["completed","failed","failed_permanent"],this.icons={queued:"arrows-clockwise",localProcessing:"arrows-clockwise",uploading:"syncing",pending:"cloud",processing:"syncing",completed:"cloud-check",failed:"cloud-warning",failed_permanent:"cloud-warning"},this.selectors={panel:"aside#queue",toggle:{button:"button.qtoggle",indicator:".qtoggle .indicator",count:".qtoggle .count"},refresh:{button:"#queue .m-actions .refresh",countdown:"#queue .m-actions .refresh .countdown"},popup:{popup:"#queue .popup",message:"#queue .popup span"},items:{container:"#queue .qitems"},actions:{retry:"#queue .retry-all",clear:"#queue .dismiss-all"},filters:{filter:"#queue [data-filter]",all:{label:'#queue [for="qfilter-all"]',radio:'#queue [data-filter="all"]',count:'#queue [data-filter="all"] .count'},queued:{label:'#queue [for="qfilter-queued"]',input:'#queue [data-filter="queued"]',count:'#queue [for="qfilter-queued"] .count'},localProcessing:{label:'#queue [for="qfilter-localProcessing"]',input:'#queue [data-filter="localProcessing"]',count:'#queue [for="qfilter-localProcessing"] .count'},uploading:{label:'#queue [for="qfilter-uploading"]',input:'#queue [data-filter="uploading"]',count:'#queue [for="qfilter-uploading"] .count'},pending:{label:'#queue [for="qfilter-pending"]',input:'#queue [data-filter="pending"]',count:'#queue [for="qfilter-pending"] .count'},processing:{label:'#queue [for="qfilter-processing"]',input:'#queue [data-filter="processing"]',count:'#queue [for="qfilter-processing"] .count'},completed:{label:'#queue [for="qfilter-completed"]',input:'#queue [data-filter="completed"]',count:'#queue [for="qfilter-completed"] .count'},failed:{label:'#queue [for="qfilter-failed"]',input:'#queue [data-filter="failed"]',count:'#queue [for="qfilter-failed"] .count'}},item:{type:".type",status:".status",details:".info .details",icon:".status .icon",startedAt:".started time",completed:{wrap:".completed",label:".completed span",time:".completed time"},progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},actions:{cancel:"button.cancel",retry:"button.retry",refresh:"button.refresh",dismiss:"button.dismiss"}}},this.ui=window.uiFromSelectors(this.selectors),this.ui.panel||(this.canUpdateUI=!1)}defineTemplates(){const e=window.jvbTemplates;e.define("emptyState"),e.define("queueItem",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id}})}initListeners(){this.activityListeners=null,this.clickHandler=this.handleClick.bind(this),this.onlineHandler=this.handleOnline.bind(this),this.offlineHandler=this.handleOffline.bind(this),this.unloadHandler=this.handleBeforeUnload.bind(this),this.visibilityHandler=this.handleVisibilityChange.bind(this),document.addEventListener("click",this.clickHandler),window.addEventListener("online",this.onlineHandler),window.addEventListener("offline",this.offlineHandler),document.addEventListener("visibilitychange",this.visibilityHandler)}handleOnline(){this.updatePanel("synced"),this.getQueueByStatus(this.pendingStatuses).length>0&&this.processQueue()}handleOffline(){this.updatePanel("offline")}handleVisibilityChange(e){this.isPolling&&document.hidden?this.stopPolling():this.maybeStartPolling()}handleBeforeUnload(e){if(!this.ui.panel)return;return this.getQueueByStatus(this.pendingStatuses).length>0?(e.preventDefault(),e.returnValue="",""):void 0}handleClick(e){if(!window.targetCheck(e,this.selectors.panel+", "+this.selectors.toggle.button))return;if(window.targetCheck(e,this.selectors.refresh.button))return this.ui.refresh.button.classList.add("fetching"),this.store.clearCache(),this.store.clearFilters(),void this.store.fetch().finally(()=>{this.ui.refresh.button.classList.remove("fetching")});if(window.targetCheck(e,this.selectors.actions.refresh))return void this.handleRefresh(opId);if(window.targetCheck(e,this.selectors.actions.clear))return void this.opActions("completed","dismiss").then(()=>{});if(window.targetCheck(e,this.selectors.actions.retry))return void this.opActions("failed","retry").then(()=>{});const t=window.targetCheck(e,"[data-action]");if(t){const e=t.closest("[data-id]")?.dataset.id;return void(e&&this.opActions(e,t.dataset.action))}const s=window.targetCheck(e,this.selectors.filters.filter);s&&this.setFilter(s.dataset.filter)}setFilter(e){Object.values(this.ui.filters).forEach(t=>{t.input?.dataset.filter===e&&(t.input.checked=!0)}),"all"===e?this.store.clearFilters():this.store.setFilter("status",e)}trackActivity(){if(!this.activityListeners){const e=["mousedown","mousemove","keypress","scroll","touchstart"];this.activityListeners=e.map(e=>{const t=()=>this.resetActivityTimer();return document.addEventListener(e,t,{passive:!0}),{event:e,handler:t}})}this.resetActivityTimer()}resetActivityTimer(){this.activityTimer&&clearTimeout(this.activityTimer),this.activityTimer=setTimeout(()=>{this.processQueue()},1750)}stopActivityTracking(){this.activityTimer&&(clearTimeout(this.activityTimer),this.activityTimer=null),this.activityListeners&&(this.activityListeners.forEach(({event:e,handler:t})=>{document.removeEventListener(e,t)}),this.activityListeners=null)}initStore(){if(!this.user)return;const e=window.jvbStore.register("queue",{storeName:"queue",keyPath:"id",endpoint:this.endpoint,TTL:1/0,isAuth:!0,indexes:[{name:"status",keyPath:"status"},{name:"type",keyPath:"type"}],filters:{user:window.auth.getUser()},showLoading:!1});this.store=e.queue,this.store.subscribe((e,t)=>{switch(e){case"data-loaded":this.store.getAll().forEach(e=>{const t=this.queue.get(e.id),s=this.mapServerOperation(e);this.queue.set(s.id,s),t&&t.status!==s.status&&this.notify("operation-status",s)}),this.maybeStartPolling(),this.updateUI();break;case"items-save":this.maybeStartPolling(),this.updateUI();break;case"item-saved":t.item&&(this.queue.set(t.item.id,t.item),t.previousItem?.status!==t.item.status&&this.notify("operation-status",t.item)),this.maybeStartPolling()}})}handleRefresh(e){const t=this.getQueue(e);if(!t)return;let s=null;if(s={content_update:t.data?.posts?Object.values(t.data.posts)[0]?.content:null,batch_creation:t.data?.content,image_upload:"uploads",video_upload:"uploads",document_upload:"uploads"}[t.type],s&&window.jvbStore){if(window.jvbStore.stores.get(s)){window.jvbStore.clearCache(s),window.jvbStore.fetch(s);const t=this.items.get(e)?.ui?.actions?.refresh;if(t){const e=t.querySelector("span").textContent;t.querySelector("span").textContent="Refreshed!",t.disabled=!0,setTimeout(()=>{t.querySelector("span").textContent=e,t.disabled=!1},2e3)}}}else confirm("Refresh the page to see changes?")&&window.location.reload()}addToQueue(e){const t={id:`u${this.user}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,endpoint:null,method:"POST",headers:{},data:{},delay:!1,canMerge:!0,popup:"Saving changes...",title:"Operation",status:"queued",timestamp:Date.now(),created_at:(new Date).toISOString(),retries:0,user:this.user,...e};if(t.headers={...this.headers,...t.headers},!t.endpoint||!t.data)return null;if(t.popup&&this.ui.popup?.message&&(this.ui.popup.message.textContent=t.popup,this.ui.popup.popup.hidden=!1,setTimeout(()=>this.ui.popup.popup.hidden=!0,2e3)),!t.delay)return this.queue.set(t.id,t),this.processOperation(t).then(()=>{}),this.store.clearCache(),this.maybeStartPolling(),this.toggleQueue(),t.id;const s=Array.from(this.getAllQueue()).filter(e=>"queued"===e.status&&e.endpoint===t.endpoint&&e.canMerge);if(s.length>0){const e=s[0];return e.data=window.deepMerge(e.data,t.data),e.timestamp=Date.now(),this.setQueue(e),this.updateOperationStatus(e.id,e.status),this.updateUI(),this.trackActivity(),e.id}return this.store.clearCache(),this.setQueue(t),this.updateOperationStatus(t.id,t.status),this.updateUI(),this.trackActivity(),t.id}async opActions(e,t){if(this.statuses.includes(e)?e=this.getQueueByStatus(e).map(e=>e.id):"string"==typeof e&&(e=[e]),0===e.length)return;if(!["cancel","dismiss","retry"].includes(t))return;const s=["cancel","dismiss"].includes(t);s&&e.forEach(e=>{this.removeOperationUI(e)});try{const i=await window.auth.fetch(`${this.api}${this.endpoint}`,{method:"POST",headers:{"Content-Type":"application/json",...this.headers},body:JSON.stringify({action:t,ids:Array.isArray(e)?e:[e],user:this.user})});if(!i.ok)throw new Error(`${t} failed: ${i.status}`);const n=await i.json();if(!n.success)throw new Error(n.message||`${t} operation failed`);return e.forEach(e=>{let i=this.getQueue(e);if(i&&this.notify(`${t}-operation`,i),s)this.clearQueue(e);else{let t=this.getQueue(e);t.status="queued",this.setQueue(t),this.updateOperationStatus(t.id,t.status)}}),"retry"===t&&this.trackActivity(),this.updateUI(),n}catch(s){return await window.jvbError.log(s,{component:"Queue",operation:"performQueueAction",action:t,operationIds:e,itemCount:e.length},()=>this.opActions(e,t)),{success:!1,error:s.message}}}async processQueue(){if(this.isProcessing)return;const e=this.getQueueByStatus("queued");if(0===e.length)return void this.stopActivityTracking();this.setProcessing();for(const t of e)await this.processOperation(t);this.setProcessing(!1);0===this.getQueueByStatus("queued").length?this.stopActivityTracking():this.trackActivity(),this.toggleQueue(this.maybeStartPolling())}async processOperation(e){try{this.queue.has(e.id)||this.queue.set(e.id,e);let t,s,i=!1;if(e.data?._isFormData&&!e.data instanceof FormData&&(i=!0,e.data=await this.store.objectToFormData(e.data)),this.updateOperationStatus(e.id,"uploading"),e.data instanceof FormData?(e.data.append("id",e.id),e.data.append("user",window.auth.getUser()),t=e.data,s=e.data):(s={...e.data,id:e.id,user:window.auth.getUser()},t=JSON.stringify(s),e.headers["Content-Type"]="application/json"),"unknown"===e.endpoint||null==t)return;const n=await window.auth.fetch(`${this.api}${e.endpoint}`,{method:e.method,headers:e.headers,body:t});console.log("Sending request with data: ",s);const r=await n.json();if(i&&(e.data={}),console.log("Result: ",r),!n.ok||!r.success)throw new Error(r.message||`HTTP ${n.status}`);this.notify("sent-to-server",s),r.id&&e.id!==r.id?e=await this.handleServerMerge(e,r):(e.status=r.status??"pending",e.serverData=r,this.updateOperationStatus(e.id,e.status)),this.a11y.announce(`${e.title} sent to server for processing`),this.setQueue(e)}catch(t){console.error("Operation failed: ",t),e.retries++,e.lastError=t.message,e.retries>=3?e.status="failed_permanent":e.status="failed",this.updateOperationStatus(e.id,e.status),this.setQueue(e)}}async handleServerMerge(e,t){const s=this.getQueue(t.id);return s?(e.status=t.status||"pending",e.serverData=t,this.mergeOp(s,e)):(this.clearQueue(e.id),this.setQueue(t),t)}mergeOp(e,t){return e.data=window.deepMerge(e.data,t.data),e.status=t.status,Object.hasOwn(t,"serverData")&&(e.serverData=t.serverData),this.updateOperationStatus(e.id,e.status),this.removeOperationUI(t.id),this.clearQueue(t.id),e}sortByDate(e){return e.sort((e,t)=>(e.updated_at??e.timestamp??0)-(t.updated_at??t.timestamp??0))}sortOperations(e){const t={processing:0,uploading:1,pending:2,queued:3,localProcessing:4,failed:5,completed:6,failed_permanent:7};return e.sort((e,s)=>{const i=(t[e.status]??99)-(t[s.status]??99);if(0!==i)return i;const n=e.updated_at??e.timestamp??0,r=s.updated_at??s.timestamp??0;return new Date(r)-new Date(n)})}getAllQueue(){let e=new Set,t=[...Array.from(this.queue.values())];return this.loadFromStorage||(this.loadFromStorage=!0,t=[...t,...Array.from(this.store.data.values())],t=t.filter(t=>{const s=e.has(t.id);return e.add(t.id),!s})),this.sortOperations(t)}getQueueByStatus(e){return"string"==typeof e&&(e=[e]),this.getAllQueue().filter(t=>e.includes(t.status))}updateOperationStatus(e,t){let s=this.getQueue(e);s&&(this.statuses.includes(t)?(s.status=t,this.notify("operation-status",s),this.setQueue(s)):console.log("Invalid status: ",t))}setQueue(e){this.store.save(e),this.queue.set(e.id,e)}getQueue(e){return this.queue.has(e)?this.queue.get(e):this.store.get(e)}clearQueue(e){this.queue.delete(e),this.store.delete(e)}maybeStartPolling(){return this.getQueueByStatus([...this.pendingStatuses,...this.workingStatuses]).length>0?(this.startPolling(),!0):(this.updatePanel("synced"),!1)}startPolling(){this.isPolling||(this.isPolling=!0,this.updatePanel("pending"),this.runPollCycle())}async runPollCycle(){if(this.isPolling){try{if(this.ui.refresh.button.classList.add("fetching"),this.store.clearCache(),429===(await this.store.fetch()).status)return console.log("Too many requests. Waiting 30 seconds"),this.stopPolling(),void this.startCountdown(30,()=>this.runPollCycle());if(this.ui.refresh.button.classList.remove("fetching"),!this.maybeStartPolling())return this.stopPolling(),void this.updatePanel("synced")}catch(e){console.error("Polling error:",e)}this.startCountdown(5,()=>this.runPollCycle())}}startCountdown(e,t){this.ui.refresh.countdown?(this.ui.refresh.countdown.classList.add("counting"),this.ui.refresh.countdown.textContent=e,this.countdownTimer=setInterval(()=>{--e>0?this.ui.refresh.countdown.textContent=e:(this.stopCountdown(),t&&t())},1e3)):console.warn("Countdown element not found")}stopPolling(){this.isPolling&&(this.isPolling=!1,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.stopCountdown())}stopCountdown(){this.countdownTimer&&(clearInterval(this.countdownTimer),this.countdownTimer=null),this.ui.refresh.countdown.classList.remove("counting"),this.ui.refresh.countdown.textContent=""}updateUI(){this.canUpdateUI&&window.debouncer.schedule("queue-ui",this.handleUpdateUI.bind(this))}handleUpdateUI(){const e=this.getAllQueue();this.ui.actions.retry.disabled=0===e.filter(e=>"failed"===e.status).length,this.ui.actions.clear.disabled=0===e.filter(e=>"completed"===e.status).length;let t=e.filter(e=>[...this.pendingStatuses,...this.workingStatuses].includes(e.status));t=t.length,this.ui.toggle.count.hidden=0===t,this.ui.toggle.count.textContent=t;for(let t of this.statuses){if("failed_permanent"===t)continue;let s=e.filter(e=>e.status===t).length;this.ui.filters[t].label.hidden=0===s,this.ui.filters[t].input.dataset.count=`${s}`,this.ui.filters[t].count.textContent=s>0?s:""}this.renderOperations()}renderOperations(){if(!this.ui.items.container)return;const e=this.store.filters?.status??"all",t="all"===e?this.getAllQueue():this.getQueueByStatus(e),s=this.sortOperations(t);if(0===s.length){window.removeChildren(this.ui.items.container);const e=window.jvbTemplates.create("emptyQueue");return this.ui.items.container.append(e),void this.a11y.announce("No items in queue")}this.ui.items.container.querySelector(".empty-group")?.remove();const i=new Set(s.map(e=>e.id));this.items.forEach((e,t)=>{i.has(t)||(e.element?.remove(),this.items.delete(t))}),s.forEach((e,t)=>{let s=this.items.get(e.id);s||(s=this.createOperationElement(e)),s?.element&&(this.updateOperationUI(e.id),this.ui.items.container.append(s.element))})}createOperationElement(e){const t=window.jvbTemplates.create("queueItem",e),s={element:t,ui:window.uiFromSelectors(this.selectors.item,t)};return this.items.set(e.id,s),s}updateOperationUI(e){let t=this.items.has(e)?this.items.get(e):this.createOperationElement(e);if(!t)return;let s=this.getQueue(e),i=t.element;i.classList.remove(...this.statuses),i.classList.add(s.status);let n=this.getProgress(s);t.ui.type&&t.ui.type.textContent!==s.title&&(t.ui.type.textContent=s.title),t.ui.status&&(t.ui.status.title=this.statusLabel(s.status)),t.ui.icon&&(t.ui.icon.className=`icon icon-${this.icons[s.status]}`),t.ui.details&&(t.ui.details.textContent=this.itemMessage(s)),t.ui.startedAt&&(t.ui.startedAt.setAttribute("datetime",s.created_at),t.ui.startedAt.textContent=window.formatTimeAgo(s.created_at));s.status;const r="completed"===s.status&&(s.completed_at||s.updated_at);if(t.ui.completed.wrap.hidden=!r,r){const e=s.completed_at??s.updated_at;t.ui.completed.label.textContent="Completed: ",t.ui.completed.time.setAttribute("datetime",e),t.ui.completed.time.textContent=window.formatTimeAgo(e)}window.showProgress(t.ui.progress,n,100,this.statusLabel(s.status)),t.ui.actions.cancel&&(t.ui.actions.cancel.hidden=this.completedStatuses.includes(s.status)),t.ui.actions.retry&&(s.retries>=3&&(t.ui.actions.retry.disabled=!0),t.ui.actions.retry.hidden="failed"!==s.status),t.ui.actions.dismiss&&(t.ui.actions.dismiss.hidden=this.pendingStatuses.includes(s.status)),t.ui.actions.refresh&&(t.ui.actions.refresh.hidden="completed"!==s.status)}getProgress(e){if(void 0!==e.progress_percentage)return e.progress_percentage;if(void 0!==e.progress)return e.progress;if(!this.statuses.includes(e.status))return 0;return{queued:10,uploading:25,pending:40,processing:70,completed:100,failed:0,failed_permanent:0}[e.status]??0}removeOperationUI(e){let t=this.items.get(e);t&&window.fade(t.element,!1)}updatePanel(e="syncing"){this.ui.panel&&this.panelStatuses.includes(e)&&(this.ui.panel.classList.remove(...this.panelStatuses),this.ui.panel.classList.add(e))}statusLabel(e){if(!this.statuses.includes(e))return"";return{queued:"Queued",localProcessing:"Processing locally",uploading:"Uploading",pending:"Waiting on server",processing:"Processing",completed:"Completed",failed:"Failed",failed_permanent:"Failed permanently",merged:"Merged"}[e]}itemMessage(e){if(Object.hasOwn(e,"message")&&""!==e.message)return e.message;if(Object.hasOwn(e,"error_message")&&e.error_message)return e.error_message;switch(e.status){case"queued":return"Waiting to send...";case"uploading":return"Sending to server...";case"pending":return e.position?`Position ${e.position} in queue`:"In server queue";case"processing":if(e.count&&void 0!==e.progress_count){const t=e.progress_count,s=e.count;return`Processing ${t}/${s} items (${Math.round(t/s*100)}%)`}return void 0!==e.progress_percentage?`${e.progress_percentage}% complete`:"Processing...";case"completed":return"Successfully completed. Refresh to see changes.";case"merged":return e.merged_into?`Merged with another operation (${e.merged_into.substring(0,8)}...)`:"Merged with another operation";case"failed":return`Failed: ${e.lastError||"Unknown error"} (Retry ${e.retries}/2)`;case"failed_permanent":return`Failed: ${e.lastError||"Unknown error"}`;default:return""}}toggleQueue(e=!0){this.ui.panel&&(this.ui.panel.hidden=!e,this.ui.toggle.button.hidden=!e)}setProcessing(e=!0){this.isProcessing=e,this.ui.toggle.button.classList.toggle("saving",e)}mapServerOperation(e){const t=this.queue.get(e.id);if(t&&t.endpoint){const s={...t,...e,endpoint:t.endpoint,method:t.method,headers:t.headers,progress_percentage:e.progress_percentage,progress_count:e.progress_count,count:e.count};e.merged_into&&this.handleMergedOperation(s)}const s=e.type?e.type.replace("_update","").replace("_","/"):"unknown",i={...e,endpoint:s,method:"POST",headers:{...this.headers}};return e.merged_into&&this.handleMergedOperation(i),i}handleMergedOperation(e){e.merged_into&&(console.log(`[Queue] Operation ${e.id} merged into ${e.merged_into}`),setTimeout(()=>{this.clearQueue(e.id),this.removeOperationFromUI(e.id)},3e3))}subscribe(e){if(this.subscribers)return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>s(e,t))}destroy(){this.isPolling&&this.stopPolling(),this.stopActivityTracking(),document.removeEventListener("click",this.clickHandler),this.subscribers.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbQueue=new e)})})})();
assets/js/min/selector.min.js
@@ -1 +1 @@
(()=>{class e{constructor(){this.container=document.querySelector("dialog#jvb-selector"),this.container&&(this.a11y=window.jvbA11y,this.error=window.jvbError,this.subscribers=new Set,this.fields=new Map,this.selectedTerms=new Map,this.batchFetch=new Set,this.activeField=null,this.isInitializing=!0,this.lazyInit=!1,this.messageText={},this.init())}init(){this.initStore(),this.initElements(),this.defineTemplates(),this.initModal(),this.scanExistingFields(),this.initListeners(),this.needsCreator()&&window.jvbTaxCreator&&(this.creator=new window.jvbTaxCreator(this)),this.isInitializing=!1,this.batchFetchTaxonomies().then(()=>{})}initStore(){const e=window.jvbStore.register("taxonomies",{storeName:"terms",keyPath:"id",showLoading:!1,indexes:[{name:"taxonomy",keyPath:"taxonomy"},{name:"parent",keyPath:"parent"},{name:"slug",keyPath:"slug"},{name:"count",keyPath:"count"}],endpoint:"terms",TTL:12e4,filters:{taxonomy:"",page:1,search:"",parent:0},required:"taxonomy",delayFetch:!0});this.store=e.terms,this.store.subscribe(this.handleStoreEvent.bind(this))}defineTemplates(){const e=window.jvbTemplates,t=this;e.define("emptyState"),e.define("selectedTerm",{refs:{name:".item-name",btn:"button"},setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.dataset.taxonomy=i.taxonomy,t.name&&(t.name.textContent=i.path),t.button&&(t.button.title=`Remove ${i.name}`)}}),e.define("termListItem",{refs:{checkbox:"input",label:"label",name:"span, .term-name"},setup({el:e,refs:s,manyRefs:i,data:r}){e.dataset.id=r.id;let a=t.currentField(),n=t.selectedTerms.get(t.activeField).has(r.id),o=a.limit>0&&t.selectedTerms.get(t.activeField).size>=a.limit;if(s.checkbox&&(s.checkbox.dataset.id=r.id,s.checkbox.id=`${a.id}-${r.id}`,s.checkbox.name=`${a.id}-${a.taxonomy}-select`,s.checkbox.value=r.id,s.checkbox.disabled=!n&&o,s.checkbox.checked=n),s.label&&(s.label.htmlFor=`${a.id}-${r.id}`,s.label.title=r.path??r.name,s.label.dataset.path=r.path),s.name&&(s.name.textContent=r.show?r.path:r.name),r.hasChildren){let t={plural:a.plural,name:r.name};const s=window.jvbTemplates.create("termChildrenToggle",t);e.append(s)}}}),e.define("termChildrenToggle",{setup({el:e,refs:t,manyRefs:s,data:i}){e.ariaLabel=`View ${i.plural} nested under ${i.name}`}}),e.define("termBreadcrumb",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.textContent=i.name,e.title=i.name}}),e.define("autocompleteItem",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.textContent=i.path||i.name,e.title=`Select ${i.name}`}})}initElements(){this.selectors={search:{input:'[type="search"]',clear:".clear-search",container:".search-wrapper",results:".search-results"},create:{button:"button.submit-term",span:".submit-term span"},terms:{list:".items-container",wrap:".items-wrap",sentinel:".scroll-sentinel"},nav:{nav:"nav.term-navigation",back:".back-to-parent",child:".toggle-children",pathLevel:".path-level"},message:{message:"p.message",text:"p.message span"},selected:".selected-items",modal:{title:"#modal-title",content:".modal-content",count:".selection-count"},favourites:".favourite-terms",field:{toggle:'button.selector-toggle, [data-filter="taxonomy"]',value:'input[type="hidden"]',selected:".selected-items",dropdown:{list:".search-results",wrapper:".auto-wrapper"},create:{button:".auto-wrapper .submit-term",span:".auto-wrapper button span"},search:"input[data-autocomplete]",message:{message:"p.message",text:"p.message span"}}},this.ui=window.uiFromSelectors(this.selectors,this.container)}initListeners(){this.observer=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&this.nextPage()})},{root:this.ui.terms.sentinel,threshold:.5}),this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.inputHandler=this.handleInput.bind(this),this.focusHandler=this.handleFocus.bind(this),this.blurHandler=this.handleBlur.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("input",this.inputHandler),document.addEventListener("focus",this.focusHandler,!0),document.addEventListener("blur",this.blurHandler,!0)}handleClick(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target)||this.activeField,s=this.fields.get(t);if(!t||!s)return;if(this.creator){window.targetCheck(e,this.selectors.create.button)&&this.maybeCreateTerm(e).then(()=>{})}const i=window.targetCheck(e,".remove-term");if(i){const e=i.closest("[data-id]").dataset.id??!1;return void(t&&e&&this.removeSelected(parseInt(e),t))}const r=window.targetCheck(e,".item.autocomplete");if(r){let e=parseInt(r.dataset.id);return this.addSelected(e,t),this.scheduleHideDropdown(t,6e3),void(s.ui.search&&(s.ui.search.value=""))}if(window.targetCheck(e,this.selectors.field.toggle))return e.preventDefault(),void this.openModal(t);if(e.target.matches(".modal-close"))return this.updateFieldValue(t),void this.modal?.handleClose();if(window.targetCheck(e,this.selectors.nav.back))return void this.navigateToParent();if(window.targetCheck(e,this.selectors.nav.child)){const t=e.target.closest("li"),s=parseInt(t.dataset.id);return void(s&&this.navigateTo(s))}const a=window.targetCheck(e,this.selectors.nav.pathLevel);if(a){const e=parseInt(a.dataset.id)??0;return void this.navigateTo(e)}if(window.targetCheck(e,this.selectors.field.dropdown))return void this.scheduleHideDropdown(t);if(window.targetCheck(e,this.selectors.search.clear)){const e=this.currentField();e&&e.ui.search&&(e.ui.search.value="",this.store.setFilters({search:"",page:1,parent:this.store.filters.parent||0})),this.ui.search.input&&(this.ui.search.input.value="")}}handleChange(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;if(!["checkbox","button"].includes(e.target.type))return;e.preventDefault(),e.stopPropagation();const t=parseInt(e.target.dataset.id);let s=this.getFieldId(e.target);e.target.checked?this.addSelected(t,s):this.removeSelected(t,s)}handleInput(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;let t=this.getFieldId(e.target)??this.activeField;if(!t)return;const s=this.fields.get(t);if(!s)return;if(["checkbox","button"].includes(e.target.type))return;e.preventDefault(),e.stopPropagation(),this.container.open||this.setField(t);let i=e.target.value.trim();this.setMessage(s,!0,`Searching for "${i}" in ${s.plural??"items"}`),window.debouncer.schedule(`${t}-search`,async()=>{this.container.open&&window.removeChildren(this.ui.terms.list),await this.store.setFilters({taxonomy:s.taxonomy,search:i,page:1,parent:i?0:this.store.filters.parent||0})},100)}setField(e){const t=this.fields.get(e);t?(this.activeField=e,this.setMessage(t,!0,`Loading ${t.plural}...`),this.resetFilters({taxonomy:t.taxonomy})):console.error("No field found...")}resetFilters(e){Object.hasOwn(e,"taxonomy")&&(e={page:1,search:"",parent:0,...e},this.store.setFilters(e))}handleFocus(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target);if(!t)return;const s=this.fields.get(t);s&&(s.hasAutocomplete||s.hasSearch)&&(window.debouncer.cancel(`${t}-search-results`),this.container.open||this.setField(t))}handleBlur(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target);if(!t)return;const s=this.fields.get(t);s&&s.hasAutocomplete&&!this.container.open&&(e.target.closest(".remove-item")||e.relatedTarget&&s.ui.dropdown.wrapper?.contains(e.relatedTarget)||this.scheduleHideDropdown(t))}scheduleHideDropdown(e,t=1500){const s=this.fields.get(e);s&&window.debouncer.schedule(`${e}-search-results`,()=>{this.container.open||(this.activeField=null),s.ui.dropdown.wrapper&&(s.ui.dropdown.wrapper.hidden=!0)},t)}initModal(){this.modalID="dialog#jvb-selector",this.container=document.querySelector(this.modalID),this.modal=new window.jvbModal(this.container,{handleForm:!1,open:null}),this.modal.subscribe((e,t)=>{if("modal-close"===e)this.closeModal()})}toggleModal(e,t=!0){this.fields.get(e)&&(t?this.openModal(e):this.closeModal())}openModal(e){const t=this.fields.get(e);if(!t)return;this.setField(e),this.ui.modal.title.textContent=t.isFilter?`Filter by ${t.singular}`:`Select ${t.plural}`,this.ui.search.container&&(this.ui.search.container.hidden=!t.canSearch),this.creator&&this.creator.handleOpen(t);let s=`Opened ${t.singular} selection. Choose from checkboxes, or search to filter results.`;window.removeChildren(this.ui.selected),window.removeChildren(this.ui.terms.list),this.modal.handleOpen(),this.a11y.announce(s)}openEmpty(e,t,s,i){this.emptyCallback=i;const r=`empty-${e}-${Date.now()}`;this.fields.has(r)||(this.fields.set(r,{id:r,taxonomy:e,singular:t,plural:s,canSearch:!0,canCreate:!1,hasAutocomplete:!1,isFilter:!1,isEmpty:!0,limit:0,ui:{},element:null,value:null,toggle:null,checked:!0}),this.selectedTerms.set(r,new Set)),this.setField(r),this.ui.modal.title.textContent=`Add to ${s}`,this.ui.search?.container&&(this.ui.search.container.hidden=!1),window.removeChildren(this.ui.selected),window.removeChildren(this.ui.terms.list),this.modal.handleOpen()}closeModal(){const e=this.fields.get(this.activeField);if(!e)return;if(this.updateFieldValue(this.activeField),this.observer.unobserve(this.ui.terms.sentinel),window.removeChildren(this.ui.terms.list),e.isEmpty&&this.emptyCallback){const t=Array.from(this.selectedTerms.get(this.activeField)||[]),s=t.map(e=>this.store.get(e)).filter(Boolean);this.emptyCallback({taxonomy:e.taxonomy,termIds:t,terms:s}),this.fields.delete(this.activeField),this.selectedTerms.delete(this.activeField),this.emptyCallback=null,this.bulkAssignmentTaxonomy=null}else this.notify("selected-terms",{terms:this.selectedTerms.get(this.activeField),taxonomy:e.taxonomy});this.activeField=null;let t=`Closed ${e.singular} selector.`;this.a11y.announce(t)}navigateToParent(){const e=this.store.filters.parent;if(0===e)return;let t=this.store.get(parseInt(e));if(!t)return void this.navigateTo(0);let s=t.parent;this.navigateTo(parseInt(s))}navigateTo(e=0){e=parseInt(e)??0,this.store.setFilters({parent:e,page:1}),window.removeChildren(this.ui.terms.list),this.updateBreadcrumbs(e)}nextPage(){let e=this.store.filters.page,t=Math.min(e++,this.store.lastResponse.total);this.store.setFilters({page:t})}prevPage(){let e=this.store.filters.page,t=Math.max(e-1,1);this.store.setFilters({page:t})}addTermToModal(e){const t=this.store.get(e);if(!t)return;this.currentField()&&(this.ui.selected.querySelector(`[data-id="${e}"]`)||this.ui.selected.append(this.getSelectedTermUI(t)))}getSelectedTermUI(e,t=!0){return window.jvbTemplates.create("selectedTerm",e)}scanExistingFields(e=document.body){e.querySelectorAll('[data-type="selector"], [data-field-type="selector"]').forEach(e=>{try{e.dataset.lazy?this.lazyInit=!0:this.registerField(e)}catch(t){this.error.log(t,{component:"TaxonomySelector",action:"scanExistingFields",container:e.dataset.name})}}),this.lazyInit&&this.initObserver(e)}unregisterFields(e){e.querySelectorAll('[data-type="selector"],[data-field-type="selector"]').forEach(e=>{this.fields.delete(e.dataset.fieldId)})}initObserver(e){this.lazyObserver=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&e.target.dataset.lazy&&(delete e.target.dataset.lazy,this.registerField(e.target),this.lazyObserver.unobserve(e.target))})},{rootMargin:"50px"}),e.querySelectorAll('[data-type="selector"][data-lazy], [data-field-type="selector"][data-lazy]').forEach(e=>{this.lazyObserver.observe(e)})}registerField(e,t={}){if(e.dataset.fieldId&&this.fields.has(e.dataset.fieldId))return e.dataset.fieldId;let s=e.querySelector('input[type="hidden"]');if(!s&&!Object.hasOwn(e.dataset,"filter"))return;"fieldId"in e.dataset||(e.dataset.fieldId=window.generateID("selector"));const i=e.dataset.fieldId;let r=this.selectors.field;const a=Object.hasOwn(e.dataset,"filter")&&"taxonomy"===e.dataset.filter;let n=a?e:e.querySelector("button.selector-toggle");if(0===Object.keys(t).length){if(!n)return;t={taxonomy:n.dataset.taxonomy,single:n.dataset.single,plural:n.dataset.plural,search:Object.hasOwn(n.dataset,"search"),autocomplete:Object.hasOwn(n.dataset,"autocomplete"),creatable:Object.hasOwn(n.dataset,"creatable")}}else Object.hasOwn(t,"toggle")&&(n=document.querySelector(t.toggle),r.toggle=t.toggle);const o={id:i,value:s,element:e,taxonomy:t.taxonomy??!1,singular:t.single??"",plural:t.plural??"",name:e.dataset.field,canSearch:t.search??!1,limit:t.limit??0,hasAutocomplete:t.autocomplete??!1,canCreate:t.creatable??!1,isRequired:t.required??!1,isFilter:a,toggle:n,create:{button:null,span:null},selectors:r,ui:window.uiFromSelectors(r,e),checked:!1};if(a&&!o.ui.toggle&&(o.ui.toggle=e),o.taxonomy)return o.singular&&o.plural||(console.warn("TaxonomySelector: Field missing singular/plural labels",e),o.singular=o.taxonomy.replace("jvb_",""),o.plural=o.singular+"s"),this.fields.set(i,o),this.setSelectedFromValue(i,s),this.isInitializing&&this.batchFetch.add(o.taxonomy),null!==e.offsetParent?this.updateFieldUI(i):requestIdleCallback(()=>{null!==e.offsetParent&&this.updateFieldUI(i)},{timeout:2e3}),i;console.error("TaxonomySelector: Field missing taxonomy",e)}setSelectedFromValue(e,t){if(!e)return;let s=this.fields.get(e);if(!s)return;if(!t&&!s.isFilter)return;let i=new Set;t&&t.value.trim().split(",").map(e=>parseInt(e.trim())).filter(e=>!isNaN(e)).forEach(e=>i.add(e)),this.selectedTerms.set(e,i)}addSelected(e,t=null){t||(t=this.activeField);const s=this.fields.get(t),i=this.store.get(e);if(!s||!i)return;const r=this.selectedTerms.get(t);0!==s.limit&&r.size>=s.limit||(r.add(parseInt(e)),this.container.open||s.isFilter||this.updateFieldValue(t),this.addTermToDisplay(e,t),this.checkLimits(t))}removeSelected(e,t=null){t||(t=this.activeField);const s=this.fields.get(t),i=this.store.get(e);if(!s||!i)return;this.selectedTerms.get(t).delete(parseInt(e));const r=!!s.ui.selected&&s.ui.selected.querySelector(`[data-id="${e}"]`);if(r&&r.remove(),this.container.open){let t=!!this.ui.selected&&this.ui.selected.querySelector(`[data-id="${e}"]`);t&&t.remove();let s=this.ui.terms.list.querySelector(`[type=checkbox][data-id="${e}"]`);s&&(s.checked=!1)}this.container.open||s.isFilter||this.updateFieldValue(t),this.checkLimits(t)}updateFieldValue(e){const t=this.fields.get(e);if(!t)return;let s=Array.from(this.selectedTerms.get(e));t.ui.value&&(t.ui.value.value=s.join(",")??"",t.ui.value.dispatchEvent(new Event("change",{bubbles:!0})))}checkLimits(e){if(!this.container.open)return;const t=this.fields.get(e);if(!t||!t.isFilter||0===t.limit)return;const s=this.selectedTerms.get(e).size>=t.limit;this.setCheckboxes(s)}updateFieldFromInput(e){const t=this.getFieldId(e);if(!t)return;this.fields.get(t)&&(this.setSelectedFromValue(t,e),this.updateFieldUI(t))}updateFieldUI(e){const t=this.fields.get(e);let s=this.selectedTerms.get(e)??new Set;t&&!t.isFilter&&0!==s.size&&Array.from(s).forEach(t=>{this.addTermToDisplay(t,e)})}updateFieldsForTaxonomy(e){let t=Array.from(this.fields.values()).filter(t=>t.taxonomy===e);const s=Array.from(this.store.data.values()).some(t=>t&&t.taxonomy===e);t.forEach(e=>{e.toggle&&(e.toggle.disabled=!s&&!e.canCreate,e.toggle.title=s?`Select ${e.plural}`:`No ${e.singular} available`,e.checked=!0)})}showModalTerms(e=!1){const t=this.currentField(),s=this.store.getFiltered();if(0===s.length)return(this.store.filters.page??1)&&window.removeChildren(this.ui.terms.list),this.setMessage(t,!0,""===this.store.filters.search?`No matching ${t.plural}.`:`No ${t.plural} found.`,!1),void(this.ui.terms.sentinel&&this.observer.unobserve(this.ui.terms.sentinel));this.setCreateButton(t,!0),this.ui.terms.sentinel&&(this.store.lastResponse?.has_more?this.observer.observe(this.ui.terms.sentinel):this.observer.unobserve(this.ui.terms.sentinel));const i=this.store.filters.parent??0;this.ui.nav.back.hidden=0===i,window.chunkIt(s,t=>this.createTermElement({show:e,...t}),e=>this.ui.terms.list.append(e),10).then(()=>{}),s.length>0&&this.setMessage(t,!1)}createTermElement(e){return e&&e.name?window.jvbTemplates.create("termListItem",e):null}showAutocompleteTerms(){const e=this.currentField();if(!e||!e.hasAutocomplete||!e.ui.dropdown?.list)return;const t=e.ui.dropdown.list,s=this.currentTerms();window.removeChildren(t),0===s.length?this.setMessage(e,!0,`No ${e.plural} found.`,!1):(window.chunkIt(s,e=>this.createAutocompleteTerm(e),e=>t.append(e)).then(()=>{}),this.setMessage(e,!1)),this.setCreateButton(e,!0),e.ui.dropdown.wrapper&&(e.ui.dropdown.wrapper.hidden=!1)}createAutocompleteTerm(e){return window.jvbTemplates.create("autocompleteItem",e)}addTermToDisplay(e,t){const s=this.store.get(e),i=this.fields.get(t);if(!s||!i)return;if(i.ui.selected&&i.ui.selected.querySelector(`[data-id="${e}"]`))return;let r=this.getSelectedTermUI(s);if(i.ui.selected&&i.ui.selected.append(r),this.container.open){this.addTermToModal(e);const t=this.ui.terms.list.querySelector(`input[value="${e}"]`);t&&(t.checked=!0)}}updateBreadcrumbs(e){const t=this.ui.nav.nav;if(!t)return;const s=Array.from(t.children).find(t=>parseInt(t.dataset.id)===e);if(s){let e=s.nextElementSibling;for(;e;){const t=e;e=e.nextElementSibling,t.remove()}}else{const s=this.store.get(e);if(!s)return;const i=window.jvbTemplates.create("termBreadcrumb",s);t.append(i)}}updateSelectionCount(){if(!this.container.open)return;const e=this.fields.get(this.activeField);if(e&&this.ui.modal.count){const t=this.selectedTerms.get(this.activeField).size;this.ui.modal.count.textContent=e.limit>0?`${t} of ${e.limit} ${e.plural} selected`:`${t} ${e.plural} selected`}}checkRendered(e,t){if(e)return Object.hasOwn(e,t.taxonomy)||(e[t.taxonomy]=new Map),e[t.taxonomy].has(t.id)}currentField(){return this.fields.get(this.activeField)??!1}currentTerms(){return this.store.getFiltered()}needsCreator(){return Array.from(this.fields.values()).some(e=>e.canCreate||e.hasAutocomplete)}getFieldId(e){if(e.dataset.fieldId)return e.dataset.fieldId;const t=e.closest("[data-field-id]");return t?.dataset.fieldId||null}setCheckboxes(e){this.ui.terms.list.querySelectorAll("input[type=checkbox]").forEach(t=>{t.checked||(t.disabled=e)})}handleStoreEvent(e,t){const s={"data-loaded":()=>this.handleDataLoaded(),"filters-changed":()=>this.handleFiltersChanged(t),"fetch-error":()=>this.handleFetchError()};try{s[e]?.(t)}catch(t){console.error(`Error handling store event "${e}":`,t)}}handleDataLoaded(){const e=this.store.filters.taxonomy;if(e){e.split(",").map(e=>e.trim()).forEach(e=>this.updateFieldsForTaxonomy(e))}this.container.open?this.showResults():this.activeField&&this.showResults(!0)}showResults(e=!1){const t=this.store.getFiltered(),s=this.store.filters,i=s.search&&s.search.length>0;this.notify("terms-loaded",{terms:t,filters:s}),!this.activeField&&e||(this.setMessage(this.currentField(),!1),e?this.showAutocompleteTerms():this.showModalTerms(i),this.a11y.announce(t.length))}handleFiltersChanged(e){}handleFetchError(e){const t=this.currentField(),s=t?`Failed to load ${t.plural}`:"Failed to load data";this.setMessage(t,!0,s,!1),console.error("Store fetch error:",e)}async batchFetchTaxonomies(){if(0===this.batchFetch.size)return;const e=Array.from(this.batchFetch);this.batchFetch.clear();try{await this.store.setFilters({taxonomy:e.join(","),page:1,search:"",parent:0})}catch(e){console.error("Failed to batch fetch taxonomies:",e)}}preloadTaxonomy(e){this.store.setFilters({taxonomy:e,page:1,search:"",parent:0})}setCreateButton(e,t=!0){if(!e.canCreate||!this.creator)return;const s=this.container.open?this.ui:e.ui;if(!s.create?.button||!s.create?.span)return;const i=s.create.button;i.hidden=!t;const r=s.create.span,a=this.container.open?s.search.input:s.search;if(!a)return;let n=(this.currentTerms()??[]).map(e=>e.name),o=a.value;const l=t&&o.length>=2&&!n.includes(o);i.hidden=!l,l&&(r.textContent=a.value??"")}async maybeCreateTerm(e){const t=this.currentField();if(!t)return;window.debouncer.cancel(`${t.id}-search-results`);let s={taxonomy:t.taxonomy,parent:this.store.filters.parent??0};if(this.container.open&&""===this.ui.search.input.value?(s.parent=this.creator.ui.parent.value??s.parent,s.name=this.creator.ui.name.value??!1):s.name=this.container.open?this.ui.search.input.value:t.ui.search.value,void 0!==s.parent&&s.name){this.setMessage(t,!0,`Creating "${s.name}"...`),this.setCreateButton(t,!1),this.container.open?window.removeChildren(this.ui.terms.list):(t.ui.search.disabled=!0,t.ui.dropdown.wrapper&&(t.ui.dropdown.wrapper.hidden=!1));let e=await this.creator.handleTermCreation(s);if(e){if(this.setMessage(t,!0,`"${e.name}" created!`,!1),this.addSelected(e.id,t.id),this.updateFieldValue(t.id),!this.container.open&&t.ui.dropdown.list){window.removeChildren(t.ui.dropdown.list);const s=this.createAutocompleteTerm(e);s&&(s.classList.add("newly-created"),t.ui.dropdown.list.append(s))}this.scheduleHideDropdown(t.id,300),this.setMessage(t,!1)}else this.setMessage(t,!1),!this.container.open&&t.ui.dropdown.wrapper&&(t.ui.dropdown.wrapper.hidden=!0);this.container.open||(t.ui.search.disabled=!1,t.ui.search.value="")}}setMessage(e,t=!0,s="",i=!0){const r=this.container.open||e.isFilter?this.ui:e.isFilter?null:e.ui;if(!r?.message?.message)return;s=""===s?`No ${e.plural??"items"} found.`:s;const a=r.message.message,n=r.message.text;a.hidden=!t,t?s&&n&&(i&&window.typeLoop&&n?(this.messageText[e.id]&&(this.messageText[e.id](),delete this.messageText[e.id]),this.messageText[e.id]=window.typeLoop(n,s)):n.textContent=s):this.messageText[e.id]&&(this.messageText[e.id](),delete this.messageText[e.id])}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.fields.forEach((e,t)=>{window.debouncer.cancel(`${t}-search`),window.debouncer.cancel(`${t}-search-results`)}),Object.keys(this.messageText).forEach(e=>{this.messageText[e]&&this.messageText[e]()}),this.messageText={},this.ui.terms?.sentinel&&this.observer?.unobserve(this.ui.terms.sentinel),this.observer?.disconnect(),this.lazyObserver?.disconnect(),document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),document.removeEventListener("input",this.inputHandler),document.removeEventListener("focus",this.focusHandler,!0),document.removeEventListener("blur",this.blurHandler,!0),this.subscribers.clear(),this.fields.clear(),this.selectedTerms.clear(),this.batchFetch.clear(),this.creator&&(this.creator.destroy(),this.creator=null),this.store&&(this.store=null)}}document.addEventListener("DOMContentLoaded",function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbSelector=new e)})})})();
(()=>{class e{constructor(){this.container=document.querySelector("dialog#jvb-selector"),this.container&&(this.a11y=window.jvbA11y,this.error=window.jvbError,this.subscribers=new Set,this.fields=new Map,this.selectedTerms=new Map,this.batchFetch=new Set,this.activeField=null,this.isInitializing=!0,this.lazyInit=!1,this.messageText={},this.init())}init(){this.initStore(),this.initElements(),this.defineTemplates(),this.initModal(),this.scanExistingFields(),this.initListeners(),this.needsCreator()&&window.jvbTaxCreator&&(this.creator=new window.jvbTaxCreator(this)),this.isInitializing=!1,this.batchFetchTaxonomies().then(()=>{})}initStore(){const e=window.jvbStore.register("taxonomies",{storeName:"terms",keyPath:"id",showLoading:!1,indexes:[{name:"taxonomy",keyPath:"taxonomy"},{name:"parent",keyPath:"parent"},{name:"slug",keyPath:"slug"},{name:"count",keyPath:"count"}],endpoint:"terms",TTL:12e4,filters:{taxonomy:"",page:1,search:"",parent:0},required:"taxonomy",delayFetch:!0});this.store=e.terms,this.store.subscribe(this.handleStoreEvent.bind(this))}defineTemplates(){const e=window.jvbTemplates,t=this;e.define("emptyState"),e.define("selectedTerm",{refs:{name:".item-name",btn:"button"},setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.dataset.taxonomy=i.taxonomy,t.name&&(t.name.textContent=i.path),t.button&&(t.button.title=`Remove ${i.name}`)}}),e.define("termListItem",{refs:{checkbox:"input",label:"label",name:"span, .term-name"},setup({el:e,refs:s,manyRefs:i,data:r}){e.dataset.id=r.id;let a=t.currentField(),n=t.selectedTerms.get(t.activeField).has(r.id),o=a.limit>0&&t.selectedTerms.get(t.activeField).size>=a.limit;if(s.checkbox&&(s.checkbox.dataset.id=r.id,s.checkbox.id=`${a.id}-${r.id}`,s.checkbox.name=`${a.id}-${a.taxonomy}-select`,s.checkbox.value=r.id,s.checkbox.disabled=!n&&o,s.checkbox.checked=n),s.label&&(s.label.htmlFor=`${a.id}-${r.id}`,s.label.title=r.path??r.name,s.label.dataset.path=r.path),s.name&&(s.name.textContent=r.show?r.path:r.name),r.hasChildren){let t={plural:a.plural,name:r.name};const s=window.jvbTemplates.create("termChildrenToggle",t);e.append(s)}}}),e.define("termChildrenToggle",{setup({el:e,refs:t,manyRefs:s,data:i}){e.ariaLabel=`View ${i.plural} nested under ${i.name}`}}),e.define("termBreadcrumb",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.textContent=i.name,e.title=i.name}}),e.define("autocompleteItem",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.textContent=i.path||i.name,e.title=`Select ${i.name}`}})}initElements(){this.selectors={search:{input:'[type="search"]',clear:".clear-search",container:".search-wrapper",results:".search-results"},create:{button:"button.submit-term",span:".submit-term span"},terms:{list:".items-container",wrap:".items-wrap",sentinel:".scroll-sentinel"},nav:{nav:"nav.term-navigation",back:".back-to-parent",child:".toggle-children",pathLevel:".path-level"},message:{message:"p.message",text:"p.message span"},selected:".selected-items",modal:{title:"#modal-title",content:".modal-content",count:".selection-count"},favourites:".favourite-terms",field:{toggle:'button.selector-toggle, [data-filter="taxonomy"]',value:'input[type="hidden"]',selected:".selected-items",dropdown:{list:".search-results",wrapper:".auto-wrapper"},create:{button:".auto-wrapper .submit-term",span:".auto-wrapper button span"},search:"input[data-autocomplete]",message:{message:"p.message",text:"p.message span"}}},this.ui=window.uiFromSelectors(this.selectors,this.container)}initListeners(){this.observer=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&this.nextPage()})},{root:this.ui.terms.sentinel,threshold:.5}),this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.inputHandler=this.handleInput.bind(this),this.focusHandler=this.handleFocus.bind(this),this.blurHandler=this.handleBlur.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("input",this.inputHandler),document.addEventListener("focus",this.focusHandler,!0),document.addEventListener("blur",this.blurHandler,!0)}handleClick(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target)||this.activeField,s=this.fields.get(t);if(!t||!s)return;if(this.creator){window.targetCheck(e,this.selectors.create.button)&&this.maybeCreateTerm(e).then(()=>{})}const i=window.targetCheck(e,".remove-term");if(i){const e=i.closest("[data-id]").dataset.id??!1;return void(t&&e&&this.removeSelected(parseInt(e),t))}const r=window.targetCheck(e,".item.autocomplete");if(r){let e=parseInt(r.dataset.id);return this.addSelected(e,t),this.scheduleHideDropdown(t,6e3),void(s.ui.search&&(s.ui.search.value=""))}if(window.targetCheck(e,this.selectors.field.toggle))return e.preventDefault(),void this.openModal(t);if(e.target.matches(".modal-close"))return this.updateFieldValue(t),void this.modal?.handleClose();if(window.targetCheck(e,this.selectors.nav.back))return void this.navigateToParent();if(window.targetCheck(e,this.selectors.nav.child)){const t=e.target.closest("li"),s=parseInt(t.dataset.id);return void(s&&this.navigateTo(s))}const a=window.targetCheck(e,this.selectors.nav.pathLevel);if(a){const e=parseInt(a.dataset.id)??0;return void this.navigateTo(e)}if(window.targetCheck(e,this.selectors.field.dropdown))return void this.scheduleHideDropdown(t);if(window.targetCheck(e,this.selectors.search.clear)){const e=this.currentField();e&&e.ui.search&&(e.ui.search.value="",this.store.setFilters({search:"",page:1,parent:this.store.filters.parent||0})),this.ui.search.input&&(this.ui.search.input.value="")}}handleChange(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;if(!["checkbox","button"].includes(e.target.type))return;e.preventDefault(),e.stopPropagation();const t=parseInt(e.target.dataset.id);let s=this.getFieldId(e.target);e.target.checked?this.addSelected(t,s):this.removeSelected(t,s)}handleInput(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;let t=this.getFieldId(e.target)??this.activeField;if(!t)return;const s=this.fields.get(t);if(!s)return;if(["checkbox","button"].includes(e.target.type))return;e.preventDefault(),e.stopPropagation(),this.container.open||this.setField(t);let i=e.target.value.trim();this.setMessage(s,!0,`Searching for "${i}" in ${s.plural??"items"}`),window.debouncer.schedule(`${t}-search`,async()=>{this.container.open&&window.removeChildren(this.ui.terms.list),await this.store.setFilters({taxonomy:s.taxonomy,search:i,page:1,parent:i?0:this.store.filters.parent||0})},100)}setField(e){const t=this.fields.get(e);t?(this.activeField=e,this.setMessage(t,!0,`Loading ${t.plural}...`),this.resetFilters({taxonomy:t.taxonomy})):console.error("No field found...")}resetFilters(e){Object.hasOwn(e,"taxonomy")&&(e={page:1,search:"",parent:0,...e},this.store.setFilters(e))}handleFocus(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target);if(!t)return;const s=this.fields.get(t);s&&(s.hasAutocomplete||s.hasSearch)&&(window.debouncer.cancel(`${t}-search-results`),this.container.open||this.setField(t))}handleBlur(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target);if(!t)return;const s=this.fields.get(t);s&&s.hasAutocomplete&&!this.container.open&&(e.target.closest(".remove-item")||e.relatedTarget&&s.ui.dropdown.wrapper?.contains(e.relatedTarget)||this.scheduleHideDropdown(t))}scheduleHideDropdown(e,t=1500){const s=this.fields.get(e);s&&window.debouncer.schedule(`${e}-search-results`,()=>{this.container.open||(this.activeField=null),s.ui.dropdown.wrapper&&(s.ui.dropdown.wrapper.hidden=!0)},t)}initModal(){this.modalID="dialog#jvb-selector",this.container=document.querySelector(this.modalID),this.modal=new window.jvbModal(this.container,{handleForm:!1,open:null}),this.modal.subscribe((e,t)=>{if("modal-close"===e)this.closeModal()})}toggleModal(e,t=!0){this.fields.get(e)&&(t?this.openModal(e):this.closeModal())}openModal(e){const t=this.fields.get(e);if(!t)return;this.setField(e),this.ui.modal.title.textContent=t.isFilter?`Filter by ${t.singular}`:`Select ${t.plural}`,this.ui.search.container&&(this.ui.search.container.hidden=!t.canSearch),this.creator&&this.creator.handleOpen(t);let s=`Opened ${t.singular} selection. Choose from checkboxes, or search to filter results.`;window.removeChildren(this.ui.selected),window.removeChildren(this.ui.terms.list),this.modal.handleOpen(),this.a11y.announce(s)}openEmpty(e,t,s,i){this.emptyCallback=i;const r=`empty-${e}-${Date.now()}`;this.fields.has(r)||(this.fields.set(r,{id:r,taxonomy:e,singular:t,plural:s,canSearch:!0,canCreate:!1,hasAutocomplete:!1,isFilter:!1,isEmpty:!0,limit:0,ui:{},element:null,value:null,toggle:null,checked:!0}),this.selectedTerms.set(r,new Set)),this.setField(r),this.ui.modal.title.textContent=`Add to ${s}`,this.ui.search?.container&&(this.ui.search.container.hidden=!1),window.removeChildren(this.ui.selected),window.removeChildren(this.ui.terms.list),this.modal.handleOpen()}closeModal(){const e=this.fields.get(this.activeField);if(!e)return;if(this.updateFieldValue(this.activeField),this.observer.unobserve(this.ui.terms.sentinel),window.removeChildren(this.ui.terms.list),e.isEmpty&&this.emptyCallback){const t=Array.from(this.selectedTerms.get(this.activeField)||[]),s=t.map(e=>this.store.get(e)).filter(Boolean);this.emptyCallback({taxonomy:e.taxonomy,termIds:t,terms:s}),this.fields.delete(this.activeField),this.selectedTerms.delete(this.activeField),this.emptyCallback=null,this.bulkAssignmentTaxonomy=null}else this.notify("selected-terms",{terms:this.selectedTerms.get(this.activeField),taxonomy:e.taxonomy});this.activeField=null;let t=`Closed ${e.singular} selector.`;this.a11y.announce(t)}navigateToParent(){const e=this.store.filters.parent;if(0===e)return;let t=this.store.get(parseInt(e));if(!t)return void this.navigateTo(0);let s=t.parent;this.navigateTo(parseInt(s))}navigateTo(e=0){e=parseInt(e)??0,this.store.setFilters({parent:e,page:1}),window.removeChildren(this.ui.terms.list),this.updateBreadcrumbs(e)}nextPage(){let e=this.store.filters.page,t=Math.min(e++,this.store.lastResponse.total);this.store.setFilters({page:t})}prevPage(){let e=this.store.filters.page,t=Math.max(e-1,1);this.store.setFilters({page:t})}addTermToModal(e){const t=this.store.get(e);if(!t)return;this.currentField()&&(this.ui.selected.querySelector(`[data-id="${e}"]`)||this.ui.selected.append(this.getSelectedTermUI(t)))}getSelectedTermUI(e,t=!0){return window.jvbTemplates.create("selectedTerm",e)}scanExistingFields(e=document.body){e.querySelectorAll('[data-type="selector"], [data-field-type="selector"]').forEach(e=>{try{e.dataset.lazy?this.lazyInit=!0:this.registerField(e)}catch(t){this.error.log(t,{component:"TaxonomySelector",action:"scanExistingFields",container:e.dataset.name})}}),this.lazyInit&&this.initObserver(e)}unregisterFields(e){e.querySelectorAll('[data-type="selector"],[data-field-type="selector"]').forEach(e=>{this.fields.delete(e.dataset.fieldId)})}initObserver(e){this.lazyObserver=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&e.target.dataset.lazy&&(delete e.target.dataset.lazy,this.registerField(e.target),this.lazyObserver.unobserve(e.target))})},{rootMargin:"50px"}),e.querySelectorAll('[data-type="selector"][data-lazy], [data-field-type="selector"][data-lazy]').forEach(e=>{this.lazyObserver.observe(e)})}registerField(e,t={}){if(e.dataset.fieldId&&this.fields.has(e.dataset.fieldId))return e.dataset.fieldId;let s=e.querySelector('input[type="hidden"]');if(!s&&!Object.hasOwn(e.dataset,"filter"))return;"fieldId"in e.dataset||(e.dataset.fieldId=window.generateID("selector"));const i=e.dataset.fieldId;let r=this.selectors.field;const a=Object.hasOwn(e.dataset,"filter")&&"taxonomy"===e.dataset.filter;let n=a?e:e.querySelector("button.selector-toggle");if(0===Object.keys(t).length){if(!n)return;t={taxonomy:n.dataset.taxonomy,single:n.dataset.single,plural:n.dataset.plural,search:Object.hasOwn(n.dataset,"search"),autocomplete:Object.hasOwn(n.dataset,"autocomplete"),creatable:Object.hasOwn(n.dataset,"creatable")}}else Object.hasOwn(t,"toggle")&&(n=document.querySelector(t.toggle),r.toggle=t.toggle);const o={id:i,value:s,element:e,taxonomy:t.taxonomy??!1,singular:t.single??"",plural:t.plural??"",name:e.dataset.field,canSearch:t.search??!1,limit:t.limit??0,hasAutocomplete:t.autocomplete??!1,canCreate:t.creatable??!1,isRequired:t.required??!1,isFilter:a,toggle:n,create:{button:null,span:null},selectors:r,ui:window.uiFromSelectors(r,e),checked:!1};if(a&&!o.ui.toggle&&(o.ui.toggle=e),o.taxonomy)return o.singular&&o.plural||(console.warn("TaxonomySelector: Field missing singular/plural labels",e),o.singular=o.taxonomy.replace("jvb_",""),o.plural=o.singular+"s"),this.fields.set(i,o),this.setSelectedFromValue(i,s),this.isInitializing&&this.batchFetch.add(o.taxonomy),null!==e.offsetParent?this.updateFieldUI(i):requestIdleCallback(()=>{null!==e.offsetParent&&this.updateFieldUI(i)},{timeout:2e3}),i;console.error("TaxonomySelector: Field missing taxonomy",e)}setSelectedFromValue(e,t){if(!e)return;let s=this.fields.get(e);if(!s)return;if(!t&&!s.isFilter)return;let i=new Set;t&&t.value.trim().split(",").map(e=>parseInt(e.trim())).filter(e=>!isNaN(e)).forEach(e=>i.add(e)),this.selectedTerms.set(e,i)}addSelected(e,t=null){t||(t=this.activeField);const s=this.fields.get(t),i=this.store.get(e);if(!s||!i)return;const r=this.selectedTerms.get(t);0!==s.limit&&r.size>=s.limit||(r.add(parseInt(e)),this.container.open||s.isFilter||this.updateFieldValue(t),this.addTermToDisplay(e,t),this.checkLimits(t))}removeSelected(e,t=null){t||(t=this.activeField);const s=this.fields.get(t),i=this.store.get(e);if(!s||!i)return;this.selectedTerms.get(t).delete(parseInt(e));const r=!!s.ui.selected&&s.ui.selected.querySelector(`[data-id="${e}"]`);if(r&&r.remove(),this.container.open){let t=!!this.ui.selected&&this.ui.selected.querySelector(`[data-id="${e}"]`);t&&t.remove();let s=this.ui.terms.list.querySelector(`[type=checkbox][data-id="${e}"]`);s&&(s.checked=!1)}this.container.open||s.isFilter||this.updateFieldValue(t),this.checkLimits(t)}updateFieldValue(e){const t=this.fields.get(e);if(!t)return;let s=Array.from(this.selectedTerms.get(e));t.ui.value&&(t.ui.value.value=s.join(",")??"",t.ui.value.dispatchEvent(new Event("change",{bubbles:!0})))}checkLimits(e){if(!this.container.open)return;const t=this.fields.get(e);if(!t||!t.isFilter||0===t.limit)return;const s=this.selectedTerms.get(e).size>=t.limit;this.setCheckboxes(s)}updateFieldFromInput(e){const t=this.getFieldId(e);if(!t)return;this.fields.get(t)&&(this.setSelectedFromValue(t,e),this.updateFieldUI(t))}updateFieldUI(e){const t=this.fields.get(e);let s=this.selectedTerms.get(e)??new Set;t&&!t.isFilter&&0!==s.size&&Array.from(s).forEach(t=>{this.addTermToDisplay(t,e)})}updateFieldsForTaxonomy(e){let t=Array.from(this.fields.values()).filter(t=>t.taxonomy===e);const s=Array.from(this.store.data.values()).some(t=>t&&t.taxonomy===e);t.forEach(e=>{e.toggle&&(e.toggle.disabled=!s&&!e.canCreate,e.toggle.title=s?`Select ${e.plural}`:`No ${e.singular} available`,e.checked=!0)})}showModalTerms(e=!1){const t=this.currentField(),s=this.store.getFiltered();if(0===s.length)return(this.store.filters.page??1)&&window.removeChildren(this.ui.terms.list),this.setMessage(t,!0,""===this.store.filters.search?`No matching ${t.plural}.`:`No ${t.plural} found.`,!1),void(this.ui.terms.sentinel&&this.observer.unobserve(this.ui.terms.sentinel));this.setCreateButton(t,!0),this.ui.terms.sentinel&&(this.store.lastResponse?.has_more?this.observer.observe(this.ui.terms.sentinel):this.observer.unobserve(this.ui.terms.sentinel));const i=this.store.filters.parent??0;this.ui.nav.back.hidden=0===i,window.chunkIt(s,t=>this.createTermElement({show:e,...t}),e=>this.ui.terms.list.append(e),10).then(()=>{}),s.length>0&&this.setMessage(t,!1)}createTermElement(e){return e&&e.name?window.jvbTemplates.create("termListItem",e):null}showAutocompleteTerms(){const e=this.currentField();if(!e||!e.hasAutocomplete||!e.ui.dropdown?.list)return;const t=e.ui.dropdown.list,s=this.currentTerms();window.removeChildren(t),0===s.length?this.setMessage(e,!0,`No ${e.plural} found.`,!1):(window.chunkIt(s,e=>this.createAutocompleteTerm(e),e=>t.append(e)).then(()=>{}),this.setMessage(e,!1)),this.setCreateButton(e,!0),e.ui.dropdown.wrapper&&(e.ui.dropdown.wrapper.hidden=!1)}createAutocompleteTerm(e){return window.jvbTemplates.create("autocompleteItem",e)}addTermToDisplay(e,t){const s=this.store.get(e),i=this.fields.get(t);if(!s||!i)return;if(i.ui.selected&&i.ui.selected.querySelector(`[data-id="${e}"]`))return;let r=this.getSelectedTermUI(s);if(i.ui.selected&&i.ui.selected.append(r),this.container.open){this.addTermToModal(e);const t=this.ui.terms.list.querySelector(`input[value="${e}"]`);t&&(t.checked=!0)}}updateBreadcrumbs(e){const t=this.ui.nav.nav;if(!t)return;const s=Array.from(t.children).find(t=>parseInt(t.dataset.id)===e);if(s){let e=s.nextElementSibling;for(;e;){const t=e;e=e.nextElementSibling,t.remove()}}else{const s=this.store.get(e);if(!s)return;const i=window.jvbTemplates.create("termBreadcrumb",s);t.append(i)}}updateSelectionCount(){if(!this.container.open)return;const e=this.fields.get(this.activeField);if(e&&this.ui.modal.count){const t=this.selectedTerms.get(this.activeField).size;this.ui.modal.count.textContent=e.limit>0?`${t} of ${e.limit} ${e.plural} selected`:`${t} ${e.plural} selected`}}checkRendered(e,t){if(e)return Object.hasOwn(e,t.taxonomy)||(e[t.taxonomy]=new Map),e[t.taxonomy].has(t.id)}currentField(){return this.fields.get(this.activeField)??!1}currentTerms(){return this.store.getFiltered()}needsCreator(){return Array.from(this.fields.values()).some(e=>e.canCreate||e.hasAutocomplete)}getFieldId(e){if(e.dataset.fieldId)return e.dataset.fieldId;const t=e.closest("[data-field-id]");return t?.dataset.fieldId||null}setCheckboxes(e){this.ui.terms.list.querySelectorAll("input[type=checkbox]").forEach(t=>{t.checked||(t.disabled=e)})}handleStoreEvent(e,t){const s={"data-loaded":()=>this.handleDataLoaded(),"filters-changed":()=>this.handleFiltersChanged(t),"fetch-error":()=>this.handleFetchError()};try{s[e]?.(t)}catch(t){console.error(`Error handling store event "${e}":`,t)}}handleDataLoaded(){const e=this.store.filters.taxonomy;if(e){e.split(",").map(e=>e.trim()).forEach(e=>this.updateFieldsForTaxonomy(e))}this.container.open?this.showResults():this.activeField&&this.showResults(!0)}showResults(e=!1){const t=this.store.getFiltered(),s=this.store.filters,i=s.search&&s.search.length>0;this.notify("terms-loaded",{terms:t,filters:s}),!this.activeField&&e||(this.setMessage(this.currentField(),!1),e?this.showAutocompleteTerms():this.showModalTerms(i),this.a11y.announce(t.length))}handleFiltersChanged(e){}handleFetchError(e){const t=this.currentField();this.setMessage(t,!0,"Something went wrong.",!1);const s=this.container.open||t?.isFilter?this.ui:t?.ui,i=s?.message?.message;if(i&&!i.querySelector(".clear-cache-btn")){const e=document.createElement("button");e.className="clear-cache-btn",e.type="button",e.textContent="Clear cache and try again",e.addEventListener("click",async()=>{e.remove(),this.store.clearCache(),this.activeField&&t&&await this.store.setFilters({taxonomy:t.taxonomy,page:1,search:"",parent:0})}),i.appendChild(e)}console.error("Store fetch error:",e)}async batchFetchTaxonomies(){if(0===this.batchFetch.size)return;const e=Array.from(this.batchFetch);this.batchFetch.clear();try{await this.store.setFilters({taxonomy:e.join(","),page:1,search:"",parent:0})}catch(e){console.error("Failed to batch fetch taxonomies:",e)}}preloadTaxonomy(e){this.store.setFilters({taxonomy:e,page:1,search:"",parent:0})}setCreateButton(e,t=!0){if(!e.canCreate||!this.creator)return;const s=this.container.open?this.ui:e.ui;if(!s.create?.button||!s.create?.span)return;const i=s.create.button;i.hidden=!t;const r=s.create.span,a=this.container.open?s.search.input:s.search;if(!a)return;let n=(this.currentTerms()??[]).map(e=>e.name),o=a.value;const l=t&&o.length>=2&&!n.includes(o);i.hidden=!l,l&&(r.textContent=a.value??"")}async maybeCreateTerm(e){const t=this.currentField();if(!t)return;window.debouncer.cancel(`${t.id}-search-results`);let s={taxonomy:t.taxonomy,parent:this.store.filters.parent??0};if(this.container.open&&""===this.ui.search.input.value?(s.parent=this.creator.ui.parent.value??s.parent,s.name=this.creator.ui.name.value??!1):s.name=this.container.open?this.ui.search.input.value:t.ui.search.value,void 0!==s.parent&&s.name){this.setMessage(t,!0,`Creating "${s.name}"...`),this.setCreateButton(t,!1),this.container.open?window.removeChildren(this.ui.terms.list):(t.ui.search.disabled=!0,t.ui.dropdown.wrapper&&(t.ui.dropdown.wrapper.hidden=!1));let e=await this.creator.handleTermCreation(s);if(e){if(this.setMessage(t,!0,`"${e.name}" created!`,!1),this.addSelected(e.id,t.id),this.updateFieldValue(t.id),!this.container.open&&t.ui.dropdown.list){window.removeChildren(t.ui.dropdown.list);const s=this.createAutocompleteTerm(e);s&&(s.classList.add("newly-created"),t.ui.dropdown.list.append(s))}this.scheduleHideDropdown(t.id,300),this.setMessage(t,!1)}else this.setMessage(t,!1),!this.container.open&&t.ui.dropdown.wrapper&&(t.ui.dropdown.wrapper.hidden=!0);this.container.open||(t.ui.search.disabled=!1,t.ui.search.value="")}}setMessage(e,t=!0,s="",i=!0){const r=this.container.open||e.isFilter?this.ui:e.isFilter?null:e.ui;if(!r?.message?.message)return;s=""===s?`No ${e.plural??"items"} found.`:s;const a=r.message.message,n=r.message.text;a.hidden=!t,t?s&&n&&(i&&window.typeLoop&&n?(this.messageText[e.id]&&(this.messageText[e.id](),delete this.messageText[e.id]),this.messageText[e.id]=window.typeLoop(n,s)):n.textContent=s):this.messageText[e.id]&&(this.messageText[e.id](),delete this.messageText[e.id])}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.fields.forEach((e,t)=>{window.debouncer.cancel(`${t}-search`),window.debouncer.cancel(`${t}-search-results`)}),Object.keys(this.messageText).forEach(e=>{this.messageText[e]&&this.messageText[e]()}),this.messageText={},this.ui.terms?.sentinel&&this.observer?.unobserve(this.ui.terms.sentinel),this.observer?.disconnect(),this.lazyObserver?.disconnect(),document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),document.removeEventListener("input",this.inputHandler),document.removeEventListener("focus",this.focusHandler,!0),document.removeEventListener("blur",this.blurHandler,!0),this.subscribers.clear(),this.fields.clear(),this.selectedTerms.clear(),this.batchFetch.clear(),this.creator&&(this.creator.destroy(),this.creator=null),this.store&&(this.store=null)}}document.addEventListener("DOMContentLoaded",function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbSelector=new e)})})})();
base/SchemaHelper.php
@@ -275,6 +275,10 @@
        $className = $config['type'];
        unset($config['type']);
        $class = new $className();
        if (array_key_exists('id', $config)) {
            $class->setId($config['id']);
            unset($config['id']);
        }
        foreach ($config as $property => $value) {
build/drawer-menu/render.php
@@ -14,6 +14,9 @@
}
$cache = Cache::for('drawer');
if (JVB_TESTING) {
    $cache->flush();
}
if (!is_front_page()) {
    $menu_items[] = [
build/drawer-menu/style-index-rtl.css
@@ -1 +1 @@
nav.drawer{bottom:0;max-height:100vh;overflow:hidden auto;position:fixed;left:0;transition:var(--trans-size);width:var(--btn);z-index:var(--z-5);--dir:column-reverse;background-color:rgb(var(--base));border-right:1px solid rgb(var(--base-200));box-shadow:rgba(var(--base),var(--op-4)) var(--shdw-left);height:auto;--w:var(--chip_)}nav.drawer .section-title,nav.drawer .title{display:none}nav.drawer ul .icon{min-width:var(--chip_)}nav.drawer .a,nav.drawer a{gap:.5rem;height:var(--chipchip);justify-content:center;padding:0 .5rem;width:100%}nav.drawer .toggle{aspect-ratio:unset;width:100%}nav.drawer .toggle .icon{transform:rotate(0);transition:var(--trans-transform)}nav.drawer.open{width:240px}nav.drawer.open .section-title,nav.drawer.open .title{display:block}nav.drawer.open .toggle .icon{transform:rotate(-180deg)}nav.drawer.open .a,nav.drawer.open a{justify-content:flex-start}nav.drawer ul{--dir:column;--gap:0;--height:auto;margin:0;overflow:hidden;padding:0}nav.drawer li,nav.drawer ul{height:auto;width:100%}nav.drawer .row{width:100%}nav.drawer .menu-section{border-bottom:1px solid rgb(var(--contrast-200))}nav.drawer .section-title{font-size:var(--small);font-weight:700;opacity:.6;padding:.5rem var(--px);text-transform:uppercase}.main-actions .buttons.buttons{left:calc(var(--btn_) + .5rem)}
nav.drawer{bottom:0;max-height:100vh;overflow:hidden auto;position:fixed;left:0;transition:var(--trans-size);width:var(--btn);z-index:var(--z-5);--dir:column-reverse;background-color:rgb(var(--base));border-right:1px solid rgb(var(--base-200));box-shadow:rgba(var(--base),var(--op-4)) var(--shdw-left);height:auto;--w:var(--chip_)}nav.drawer .section-title,nav.drawer .title{display:none}nav.drawer ul .icon{min-width:var(--chip_)}nav.drawer .a,nav.drawer a{gap:.5rem;height:var(--chipchip);justify-content:center;padding:0 .5rem;width:100%}nav.drawer .toggle{aspect-ratio:unset;width:100%}nav.drawer .toggle .icon{transform:rotate(0);transition:var(--trans-transform)}nav.drawer.open{width:240px}nav.drawer.open .section-title,nav.drawer.open .title{display:block}nav.drawer.open .toggle .icon{transform:rotate(-180deg)}nav.drawer.open .a,nav.drawer.open a{justify-content:flex-start}nav.drawer ul{--dir:column;--gap:0;--height:auto;margin:0;overflow:hidden;padding:0}nav.drawer li,nav.drawer ul{height:auto;width:100%}nav.drawer .row{width:100%}nav.drawer .menu-section{border-bottom:1px solid rgb(var(--contrast-200))}nav.drawer .section-title{font-size:var(--small);font-weight:700;opacity:.6;padding:.5rem var(--px);text-transform:uppercase}.main-actions .buttons.buttons{left:calc(var(--btn_) + .5rem)}nav.on-this-page.on-this-page.on-this-page{max-width:calc(100vw - var(--btn_))!important}
build/drawer-menu/style-index.css
@@ -1 +1 @@
nav.drawer{bottom:0;max-height:100vh;overflow:hidden auto;position:fixed;right:0;transition:var(--trans-size);width:var(--btn);z-index:var(--z-5);--dir:column-reverse;background-color:rgb(var(--base));border-left:1px solid rgb(var(--base-200));box-shadow:rgba(var(--base),var(--op-4)) var(--shdw-left);height:auto;--w:var(--chip_)}nav.drawer .section-title,nav.drawer .title{display:none}nav.drawer ul .icon{min-width:var(--chip_)}nav.drawer .a,nav.drawer a{gap:.5rem;height:var(--chipchip);justify-content:center;padding:0 .5rem;width:100%}nav.drawer .toggle{aspect-ratio:unset;width:100%}nav.drawer .toggle .icon{transform:rotate(0);transition:var(--trans-transform)}nav.drawer.open{width:240px}nav.drawer.open .section-title,nav.drawer.open .title{display:block}nav.drawer.open .toggle .icon{transform:rotate(180deg)}nav.drawer.open .a,nav.drawer.open a{justify-content:flex-start}nav.drawer ul{--dir:column;--gap:0;--height:auto;margin:0;overflow:hidden;padding:0}nav.drawer li,nav.drawer ul{height:auto;width:100%}nav.drawer .row{width:100%}nav.drawer .menu-section{border-bottom:1px solid rgb(var(--contrast-200))}nav.drawer .section-title{font-size:var(--small);font-weight:700;opacity:.6;padding:.5rem var(--px);text-transform:uppercase}.main-actions .buttons.buttons{right:calc(var(--btn_) + .5rem)}
nav.drawer{bottom:0;max-height:100vh;overflow:hidden auto;position:fixed;right:0;transition:var(--trans-size);width:var(--btn);z-index:var(--z-5);--dir:column-reverse;background-color:rgb(var(--base));border-left:1px solid rgb(var(--base-200));box-shadow:rgba(var(--base),var(--op-4)) var(--shdw-left);height:auto;--w:var(--chip_)}nav.drawer .section-title,nav.drawer .title{display:none}nav.drawer ul .icon{min-width:var(--chip_)}nav.drawer .a,nav.drawer a{gap:.5rem;height:var(--chipchip);justify-content:center;padding:0 .5rem;width:100%}nav.drawer .toggle{aspect-ratio:unset;width:100%}nav.drawer .toggle .icon{transform:rotate(0);transition:var(--trans-transform)}nav.drawer.open{width:240px}nav.drawer.open .section-title,nav.drawer.open .title{display:block}nav.drawer.open .toggle .icon{transform:rotate(180deg)}nav.drawer.open .a,nav.drawer.open a{justify-content:flex-start}nav.drawer ul{--dir:column;--gap:0;--height:auto;margin:0;overflow:hidden;padding:0}nav.drawer li,nav.drawer ul{height:auto;width:100%}nav.drawer .row{width:100%}nav.drawer .menu-section{border-bottom:1px solid rgb(var(--contrast-200))}nav.drawer .section-title{font-size:var(--small);font-weight:700;opacity:.6;padding:.5rem var(--px);text-transform:uppercase}.main-actions .buttons.buttons{right:calc(var(--btn_) + .5rem)}nav.on-this-page.on-this-page.on-this-page{max-width:calc(100vw - var(--btn_))!important}
build/feed/style-index-rtl.css
@@ -1 +1 @@
.feed-block{grid-column:full}.feed-block .filters{margin:0 auto;max-width:var(--wide);padding:1rem 0}.feed-block .filters .remove-term.remove-term{height:-moz-max-content;height:max-content;width:-moz-max-content;width:max-content}.feed-block .filter-group{padding:2rem 0;position:relative}.feed-block .filter-group .label{right:0;position:absolute}.feed-block .filter-group>.label{top:0}.feed-block .filter-group [type=radio]{right:var(--offScreen);position:absolute}.feed-block .filter-group button,.feed-block .filter-group label{height:-moz-max-content;height:max-content;padding:.5rem;position:relative}.feed-block .filter-group button:hover,.feed-block .filter-group label:hover{color:var(--action-contrast)}.feed-block .filter-group :checked+label .label,.feed-block .filter-group button:hover .label,.feed-block .filter-group label:hover .label{opacity:1;visibility:visible}.feed-block .filter-group button .label,.feed-block .filter-group label .label,.feed-block .filter-group:has(label:hover) :checked+label .label{--height:max-content;bottom:-2rem;font-weight:var(--fw-b);opacity:0;visibility:hidden;white-space:nowrap;width:-moz-max-content;width:max-content}.feed-block h3{font-size:var(--medium);margin:0 0 .25rem}.placeholder{align-items:center;aspect-ratio:1;background:rgb(var(--base));border:1rem solid rgb(var(--base-50));border-radius:1rem;display:flex;justify-content:center}.placeholder i.icon{--w:50%;animation:dance 2.5s ease-in-out infinite;color:rgb(var(--base-200))}.item-grid{max-width:100%;padding:0 var(--chip)}.feed.item{background:rgb(var(--base-50));border-radius:.5rem;box-shadow:0 2px 4px rgba(0,0,0,.1);height:-moz-fit-content;height:fit-content;overflow:hidden;padding:0;position:relative}.feed.item details{padding:0;position:relative;width:100%;z-index:var(--z-2)}.feed.item details summary{backdrop-filter:blur(5px);background-color:rgba(var(--base),var(--op-2));right:0;position:absolute;top:-3rem;width:100%}.feed.item details summary:hover{background-color:rgba(var(--action-0),var(--op-45))}.feed.item details[open]{padding:.25rem .5rem}.feed.item details[open] summary .icon{opacity:0}.feed.item img{height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.feed.item img:hover{opacity:.8}.feed.item[data-timeline] .images{aspect-ratio:3/2;padding:0 0 1rem}.feed.item[data-timeline] .images span{background-color:rgb(var(--action-0));color:var(--action-contrast);padding:.25rem .5rem;position:absolute;width:50%}.feed.item[data-timeline] .images span:first-of-type{bottom:0;left:50%;text-align:left}.feed.item[data-timeline] .images span:last-of-type{right:50%;top:0}.feed.item[data-timeline] .images>a{display:flex;flex-wrap:nowrap;height:100%;position:relative;width:100%}.feed.item[data-timeline] img{height:100%;-o-object-fit:cover;object-fit:cover;width:50%}.feed.item[data-timeline] img:first-of-type{border-left:1px solid rgb(var(--action-0))}.feed.item a:after,.feed.item a:before{display:none}.feed.item label{font-weight:400;text-transform:none}.feed.item label .icon{--w:1.5em}.item-grid:has([data-timeline]){grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}.items-wrap [type=checkbox],.items-wrap [type=radio]{right:-200vw;opacity:0;position:absolute}.items-wrap [type=checkbox]+label,.items-wrap [type=radio]+label{cursor:pointer;position:relative}.items-wrap [type=checkbox]+label:hover,.items-wrap [type=radio]+label:hover{color:rgb(var(--action-0))}.items-wrap [type=checkbox]+label:after,.items-wrap [type=checkbox]+label:before,.items-wrap [type=radio]+label:after,.items-wrap [type=radio]+label:before{content:"";position:absolute;top:50%}.items-wrap [type=checkbox]+label:after,.items-wrap [type=radio]+label:after{border:solid var(--light-0);border-width:0 0 2px 2px;display:none;height:10px;right:5px;transform:translateY(-70%) rotate(-45deg);width:5px}.items-wrap [type=checkbox]+label:before,.items-wrap [type=radio]+label:before{background-color:rgb(var(--base));border:2px solid rgb(var(--contrast-200));border-radius:var(--radius);height:1rem;right:0;transform:translateY(-50%);width:1rem}.items-wrap [type=checkbox]:hover+label:before,.items-wrap [type=radio]:hover+label:before{border-color:rgb(var(--action-200))}.items-wrap [type=checkbox]:checked+label:before,.items-wrap [type=radio]:checked+label:before{background-color:rgb(var(--action-0));border-color:rgb(var(--action-100))}.items-wrap [type=radio]:checked+label:before{border-radius:50%}.items-wrap [type=checkbox]:checked+label:after{border:solid var(--light-0);border-width:0 0 2px 2px;display:block;height:.66rem;right:5px;top:50%;transform:translateY(-70%) rotate(-45deg);width:.35rem}.items-wrap :disabled+label{cursor:not-allowed}.items-wrap :disabled+label,.items-wrap :disabled+label:hover{background-color:rgb(var(--base-50));border-color:rgb(var(--base-200));color:rgb(var(--base-200))}.items-wrap :disabled+label:before{border-color:rgb(var(--base-200))}#jvb-selector .items-wrap [type=checkbox]+label,#jvb-selector .items-wrap [type=radio]+label{flex:1;padding-right:2rem!important;transform-origin:top center;will-change:transform}.feed-block+footer{background-color:rgb(var(--base-50));display:flex;grid-column:full;justify-content:flex-end;margin:0;padding:0!important;z-index:0}.feed-block+footer button{margin-right:auto;padding:.35rem .25rem;width:-moz-max-content;width:max-content;--w:1.3em!important;flex-wrap:nowrap;font-size:var(--txt-x-small);justify-content:flex-start;min-height:0;transition:var(--trans-size)}.feed-block+footer button span{display:none;white-space:nowrap}.feed-block+footer button:focus span,.feed-block+footer button:hover span{display:block}
.feed-block{grid-column:full}.feed-block .placeholder{align-items:center;aspect-ratio:1;background:rgb(var(--base));border:1rem solid rgb(var(--base-50));border-radius:1rem;display:flex;justify-content:center;--w:50%;color:rgb(var(--base-200))}.feed-block .placeholder i.icon{animation:dance 2.5s ease-in-out infinite}.feed-block .item-grid{max-width:var(--full)}.feed-block .item-grid:has([data-timeline]){grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}.feed-block .item{background:rgb(var(--base-50));box-shadow:rgba(var(--base),var(--op-2)) var(--shdw);height:-moz-fit-content;height:fit-content;overflow:hidden;padding:0}.feed-block .item h3{font-size:var(--txt-medium);margin:0}.feed-block .item details{padding:0;position:relative;width:100%;z-index:var(--z-2)}.feed-block .item details summary{backdrop-filter:blur(5px);background-color:rgba(var(--base),var(--op-2));right:0;position:absolute;top:calc(var(--chip_)*-1);width:100%}.feed-block .item details summary:hover{background-color:rgba(var(--action-0),var(--op-45))}.feed-block .item details[open]{padding:.25rem .5rem}.feed-block .item img:hover{opacity:.8}.feed-block .item[data-timeline] .images{aspect-ratio:3/2;padding:0 0 1rem}.feed-block .item[data-timeline] .images span{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast));padding:.25rem .5rem;position:absolute;width:50%}.feed-block .item[data-timeline] .images span:first-of-type{bottom:0;left:50%;text-align:left}.feed-block .item[data-timeline] .images span:last-of-type{right:50%;top:0}.feed-block .item[data-timeline] .images img{width:50%}.feed-block .item[data-timeline] .images img:first-of-type{border-left:2px solid rgb(var(--action-0))}.feed-block .item a:after,.feed-block .item a:before{display:none}.feed-block .item label{font-weight:400;text-transform:none;--w:1.5em}.all-filters{font-size:var(--txt-x-small)}.all-filters[open]{border:2px solid rgb(var(--action-0));border-radius:0 0 var(--radius-outer) var(--radius-outer);padding:0}.all-filters summary{width:100%}.all-filters summary:hover,.all-filters[open] summary{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.all-filters summary:hover:after,.all-filters[open] summary:after{background-color:rgb(var(--action-contrast))}.all-filters>.row.row{padding:0 .75rem 2rem;width:var(--content)}.all-filters>.row.row.search{padding-bottom:0}.all-filters>.row.row{position:relative}.all-filters>.row.row>.label,.all-filters>.row.row>.row>.label{font-family:var(--heading);font-weight:var(--fw-h-bold);text-transform:uppercase}.all-filters>.row.row>.label{width:20%}.all-filters>.row.row>.row>.label{white-space:nowrap}.all-filters .btn+label,.all-filters button{min-height:var(--chipchip);padding:0;width:var(--chipchip)}.all-filters .btn+label .label,.all-filters .row:has(>.btn:not(:checked)+label:hover) :checked+label .label,.all-filters button .label{bottom:-2rem;opacity:0;position:absolute;white-space:nowrap;width:-moz-max-content;width:max-content;z-index:var(--z-4)}.all-filters .btn+label:hover .label,.all-filters .btn:checked+label .label,.all-filters button:hover .label{opacity:1}.all-filters .search.row,.all-filters .view.row{display:none}.search-container:not(.open) .clear-search,.search-container:not(.open) input[type=search]{padding:0;transform:scaleX(0);transform-origin:right;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base);width:0}.search-container button{padding:.5rem}.search-container .icon{--w:1.5rem}.search-container.open .clear-search,.search-container.open input[type=search]{transform:scaleX(1);transform-origin:right;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.all-filters>.search,.search-container,input[type=search]{width:100%}.toggle-text input+label{border:1px dashed transparent;color:var(--contrast)!important;cursor:pointer;font-weight:400;padding:.25rem .5rem;position:relative;text-transform:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.toggle-text input+label:after,.toggle-text input+label:before{display:none!important}.toggle-text input+label .text{margin:0;position:relative;--gap:0;border:1px solid rgb(var(--action-50));border-radius:var(--radius);color:rgb(var(--action-50));font-weight:700;padding:2px 4px;width:-moz-fit-content;width:fit-content}.toggle-text input+label .off{--mid:-100%}.toggle-text input+label .on{--mid:100%}.toggle-text input+label .off,.toggle-text input+label .on{transition:var(--trans-transform),opacity var(--trans-base)}.toggle-text input+label .off,.toggle-text input:checked+label .on{max-width:100%;opacity:1;transform:translateZ(0)}.toggle-text input+label .on,.toggle-text input:checked+label .off{max-width:0;opacity:0;transform:translate3d(0,var(--mid),0)}.toggle-text:hover label{border-color:rgb(var(--action-200))}.toggle-text:hover .text{background-color:rgb(var(--action-50));border-color:rgb(var(--action-50));color:rgb(var(--action-contrast))}
build/feed/style-index.css
@@ -1 +1 @@
.feed-block{grid-column:full}.feed-block .filters{margin:0 auto;max-width:var(--wide);padding:1rem 0}.feed-block .filters .remove-term.remove-term{height:-moz-max-content;height:max-content;width:-moz-max-content;width:max-content}.feed-block .filter-group{padding:2rem 0;position:relative}.feed-block .filter-group .label{left:0;position:absolute}.feed-block .filter-group>.label{top:0}.feed-block .filter-group [type=radio]{left:var(--offScreen);position:absolute}.feed-block .filter-group button,.feed-block .filter-group label{height:-moz-max-content;height:max-content;padding:.5rem;position:relative}.feed-block .filter-group button:hover,.feed-block .filter-group label:hover{color:var(--action-contrast)}.feed-block .filter-group :checked+label .label,.feed-block .filter-group button:hover .label,.feed-block .filter-group label:hover .label{opacity:1;visibility:visible}.feed-block .filter-group button .label,.feed-block .filter-group label .label,.feed-block .filter-group:has(label:hover) :checked+label .label{--height:max-content;bottom:-2rem;font-weight:var(--fw-b);opacity:0;visibility:hidden;white-space:nowrap;width:-moz-max-content;width:max-content}.feed-block h3{font-size:var(--medium);margin:0 0 .25rem}.placeholder{align-items:center;aspect-ratio:1;background:rgb(var(--base));border:1rem solid rgb(var(--base-50));border-radius:1rem;display:flex;justify-content:center}.placeholder i.icon{--w:50%;animation:dance 2.5s ease-in-out infinite;color:rgb(var(--base-200))}.item-grid{max-width:100%;padding:0 var(--chip)}.feed.item{background:rgb(var(--base-50));border-radius:.5rem;box-shadow:0 2px 4px rgba(0,0,0,.1);height:-moz-fit-content;height:fit-content;overflow:hidden;padding:0;position:relative}.feed.item details{padding:0;position:relative;width:100%;z-index:var(--z-2)}.feed.item details summary{backdrop-filter:blur(5px);background-color:rgba(var(--base),var(--op-2));left:0;position:absolute;top:-3rem;width:100%}.feed.item details summary:hover{background-color:rgba(var(--action-0),var(--op-45))}.feed.item details[open]{padding:.25rem .5rem}.feed.item details[open] summary .icon{opacity:0}.feed.item img{height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.feed.item img:hover{opacity:.8}.feed.item[data-timeline] .images{aspect-ratio:3/2;padding:0 0 1rem}.feed.item[data-timeline] .images span{background-color:rgb(var(--action-0));color:var(--action-contrast);padding:.25rem .5rem;position:absolute;width:50%}.feed.item[data-timeline] .images span:first-of-type{bottom:0;right:50%;text-align:right}.feed.item[data-timeline] .images span:last-of-type{left:50%;top:0}.feed.item[data-timeline] .images>a{display:flex;flex-wrap:nowrap;height:100%;position:relative;width:100%}.feed.item[data-timeline] img{height:100%;-o-object-fit:cover;object-fit:cover;width:50%}.feed.item[data-timeline] img:first-of-type{border-right:1px solid rgb(var(--action-0))}.feed.item a:after,.feed.item a:before{display:none}.feed.item label{font-weight:400;text-transform:none}.feed.item label .icon{--w:1.5em}.item-grid:has([data-timeline]){grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}.items-wrap [type=checkbox],.items-wrap [type=radio]{left:-200vw;opacity:0;position:absolute}.items-wrap [type=checkbox]+label,.items-wrap [type=radio]+label{cursor:pointer;position:relative}.items-wrap [type=checkbox]+label:hover,.items-wrap [type=radio]+label:hover{color:rgb(var(--action-0))}.items-wrap [type=checkbox]+label:after,.items-wrap [type=checkbox]+label:before,.items-wrap [type=radio]+label:after,.items-wrap [type=radio]+label:before{content:"";position:absolute;top:50%}.items-wrap [type=checkbox]+label:after,.items-wrap [type=radio]+label:after{border:solid var(--light-0);border-width:0 2px 2px 0;display:none;height:10px;left:5px;transform:translateY(-70%) rotate(45deg);width:5px}.items-wrap [type=checkbox]+label:before,.items-wrap [type=radio]+label:before{background-color:rgb(var(--base));border:2px solid rgb(var(--contrast-200));border-radius:var(--radius);height:1rem;left:0;transform:translateY(-50%);width:1rem}.items-wrap [type=checkbox]:hover+label:before,.items-wrap [type=radio]:hover+label:before{border-color:rgb(var(--action-200))}.items-wrap [type=checkbox]:checked+label:before,.items-wrap [type=radio]:checked+label:before{background-color:rgb(var(--action-0));border-color:rgb(var(--action-100))}.items-wrap [type=radio]:checked+label:before{border-radius:50%}.items-wrap [type=checkbox]:checked+label:after{border:solid var(--light-0);border-width:0 2px 2px 0;display:block;height:.66rem;left:5px;top:50%;transform:translateY(-70%) rotate(45deg);width:.35rem}.items-wrap :disabled+label{cursor:not-allowed}.items-wrap :disabled+label,.items-wrap :disabled+label:hover{background-color:rgb(var(--base-50));border-color:rgb(var(--base-200));color:rgb(var(--base-200))}.items-wrap :disabled+label:before{border-color:rgb(var(--base-200))}#jvb-selector .items-wrap [type=checkbox]+label,#jvb-selector .items-wrap [type=radio]+label{flex:1;padding-left:2rem!important;transform-origin:top center;will-change:transform}.feed-block+footer{background-color:rgb(var(--base-50));display:flex;grid-column:full;justify-content:flex-end;margin:0;padding:0!important;z-index:0}.feed-block+footer button{margin-left:auto;padding:.35rem .25rem;width:-moz-max-content;width:max-content;--w:1.3em!important;flex-wrap:nowrap;font-size:var(--txt-x-small);justify-content:flex-start;min-height:0;transition:var(--trans-size)}.feed-block+footer button span{display:none;white-space:nowrap}.feed-block+footer button:focus span,.feed-block+footer button:hover span{display:block}
.feed-block{grid-column:full}.feed-block .placeholder{align-items:center;aspect-ratio:1;background:rgb(var(--base));border:1rem solid rgb(var(--base-50));border-radius:1rem;display:flex;justify-content:center;--w:50%;color:rgb(var(--base-200))}.feed-block .placeholder i.icon{animation:dance 2.5s ease-in-out infinite}.feed-block .item-grid{max-width:var(--full)}.feed-block .item-grid:has([data-timeline]){grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}.feed-block .item{background:rgb(var(--base-50));box-shadow:rgba(var(--base),var(--op-2)) var(--shdw);height:-moz-fit-content;height:fit-content;overflow:hidden;padding:0}.feed-block .item h3{font-size:var(--txt-medium);margin:0}.feed-block .item details{padding:0;position:relative;width:100%;z-index:var(--z-2)}.feed-block .item details summary{backdrop-filter:blur(5px);background-color:rgba(var(--base),var(--op-2));left:0;position:absolute;top:calc(var(--chip_)*-1);width:100%}.feed-block .item details summary:hover{background-color:rgba(var(--action-0),var(--op-45))}.feed-block .item details[open]{padding:.25rem .5rem}.feed-block .item img:hover{opacity:.8}.feed-block .item[data-timeline] .images{aspect-ratio:3/2;padding:0 0 1rem}.feed-block .item[data-timeline] .images span{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast));padding:.25rem .5rem;position:absolute;width:50%}.feed-block .item[data-timeline] .images span:first-of-type{bottom:0;right:50%;text-align:right}.feed-block .item[data-timeline] .images span:last-of-type{left:50%;top:0}.feed-block .item[data-timeline] .images img{width:50%}.feed-block .item[data-timeline] .images img:first-of-type{border-right:2px solid rgb(var(--action-0))}.feed-block .item a:after,.feed-block .item a:before{display:none}.feed-block .item label{font-weight:400;text-transform:none;--w:1.5em}.all-filters{font-size:var(--txt-x-small)}.all-filters[open]{border:2px solid rgb(var(--action-0));border-radius:0 0 var(--radius-outer) var(--radius-outer);padding:0}.all-filters summary{width:100%}.all-filters summary:hover,.all-filters[open] summary{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.all-filters summary:hover:after,.all-filters[open] summary:after{background-color:rgb(var(--action-contrast))}.all-filters>.row.row{padding:0 .75rem 2rem;width:var(--content)}.all-filters>.row.row.search{padding-bottom:0}.all-filters>.row.row{position:relative}.all-filters>.row.row>.label,.all-filters>.row.row>.row>.label{font-family:var(--heading);font-weight:var(--fw-h-bold);text-transform:uppercase}.all-filters>.row.row>.label{width:20%}.all-filters>.row.row>.row>.label{white-space:nowrap}.all-filters .btn+label,.all-filters button{min-height:var(--chipchip);padding:0;width:var(--chipchip)}.all-filters .btn+label .label,.all-filters .row:has(>.btn:not(:checked)+label:hover) :checked+label .label,.all-filters button .label{bottom:-2rem;opacity:0;position:absolute;white-space:nowrap;width:-moz-max-content;width:max-content;z-index:var(--z-4)}.all-filters .btn+label:hover .label,.all-filters .btn:checked+label .label,.all-filters button:hover .label{opacity:1}.all-filters .search.row,.all-filters .view.row{display:none}.search-container:not(.open) .clear-search,.search-container:not(.open) input[type=search]{padding:0;transform:scaleX(0);transform-origin:left;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base);width:0}.search-container button{padding:.5rem}.search-container .icon{--w:1.5rem}.search-container.open .clear-search,.search-container.open input[type=search]{transform:scaleX(1);transform-origin:left;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.all-filters>.search,.search-container,input[type=search]{width:100%}.toggle-text input+label{border:1px dashed transparent;color:var(--contrast)!important;cursor:pointer;font-weight:400;padding:.25rem .5rem;position:relative;text-transform:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.toggle-text input+label:after,.toggle-text input+label:before{display:none!important}.toggle-text input+label .text{margin:0;position:relative;--gap:0;border:1px solid rgb(var(--action-50));border-radius:var(--radius);color:rgb(var(--action-50));font-weight:700;padding:2px 4px;width:-moz-fit-content;width:fit-content}.toggle-text input+label .off{--mid:-100%}.toggle-text input+label .on{--mid:100%}.toggle-text input+label .off,.toggle-text input+label .on{transition:var(--trans-transform),opacity var(--trans-base)}.toggle-text input+label .off,.toggle-text input:checked+label .on{max-width:100%;opacity:1;transform:translateZ(0)}.toggle-text input+label .on,.toggle-text input:checked+label .off{max-width:0;opacity:0;transform:translate3d(0,var(--mid),0)}.toggle-text:hover label{border-color:rgb(var(--action-200))}.toggle-text:hover .text{background-color:rgb(var(--action-50));border-color:rgb(var(--action-50));color:rgb(var(--action-contrast))}
build/feed/view.asset.php
@@ -1 +1 @@
<?php return array('dependencies' => array(), 'version' => 'c3aa1c027f932096017e');
<?php return array('dependencies' => array(), 'version' => '9d6597e998a64f7951ae');
build/feed/view.js
@@ -1 +1 @@
(()=>{class e{constructor(){this.container=document.querySelector("section.feed-block"),this.container&&(this.a11y=window.jvbA11y,this.error=window.jvbError,this.cache=new window.jvbCache("feed"),this.templates=window.jvbTemplates,this.config={source:"",context:"",highlight:null,gallery:!1,view:this.cache.get("feedView")||"grid",...this.container.dataset},this.init())}init(){this.initElements(),this.defineTemplates(),this.initListeners(),this.initFilters(),"requestIdleCallback"in window?requestIdleCallback(()=>{this.initStore(),this.initTaxonomies(),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()},{timeout:2e3}):setTimeout(()=>{this.initStore(),this.initTaxonomies(),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()},100)}initElements(){this.selectors={filterTrigger:"[data-filter]",filters:{actions:".filter-actions .toggle-text",container:".filters",content:'[data-filter="content"]',orderby:'[data-filter="orderby"]',order:'[data-filter="order"]',match:'[data-filter="match"]',favourites:'[data-filter="favourites"]',taxonomy:'[data-filter^="taxonomy"]'},grid:".item-grid",selected:".selected-items",buttons:{loadMore:"button.load-more",remove:".remove-term",clearFilters:"button.clear-filters",refresh:'button[data-action="refresh"]'}},this.ui=window.uiFromSelectors(this.selectors,this.container),this.ui.buttons.refresh=document.querySelector(this.selectors.buttons.refresh),this.ui.content=this.ui.filters.container.querySelectorAll('[name="content"]'),0===this.ui.content.length&&(this.ui.content=!1),this.ui.taxonomies=this.ui.filters.container.querySelectorAll("[data-taxonomy]"),0===this.ui.taxonomies.length&&(this.ui.taxonomies=!1),this.ui.orderbyWrap=this.ui.filters.container.querySelector("[data-for-order]"),0===this.ui.orderbyWrap.length&&(this.ui.orderbyWrap=!1),this.ui.order=this.ui.filters.container.querySelectorAll('[data-filter="order"]'),0===this.ui.order.length&&(this.ui.order=!1),this.ui.orderby=this.ui.filters.container.querySelectorAll('[data-filter="orderby"]'),0===this.ui.orderby.length&&(this.ui.orderby=!1),this.orderbyFilters=this.ui.orderby?Array.from(this.ui.orderby).map(e=>e.value):[],this.contentTypes=this.ui.content?Array.from(this.ui.content).map(e=>e.value):[this.container.dataset.content],this.taxonomies=this.ui.taxonomies?.length>0?Array.from(this.ui.taxonomies).map(e=>e.dataset.taxonomy):[]}initListeners(){this.popStateHandler=this.handlePopState.bind(this),this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),window.addEventListener("popstate",this.popStateHandler),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler)}initFilters(){this.allowedFilters=["content","order","orderby","favourites","match"];let e={content:this.contentTypes[0],orderby:"date",order:"desc",page:1};this.config.context&&(e.context=this.config.context),this.config.source&&(e.source=this.config.source),this.filters=e,this.defaults={...e}}updateFilterUI(){if(this.ui.filters.container&&([this.ui.content,this.ui.orderby,this.ui.order].forEach(e=>{if(e)for(let t of e){let[e,i]=[t.dataset.filter,t.value];if(!Object.hasOwn(this.store.filters,e))break;let s=this.store.filters[e]===i;if(s){t.checked=s;break}}}),Object.hasOwn(this.store.filters,"taxonomy")))for(let[e,t]of Object.entries(this.store.filters.taxonomy))t.forEach(e=>{e=parseInt(e),this.selector.store.get(e)&&this.createTermElement(e)})}handlePopState(e){e.state?.filters&&this.processURLFilters()&&(this.store.setFilters(this.filters),this.a11y.announce("Feed filters updated from browser history"))}handleClick(e){window.targetCheck(e,this.selectors.buttons.loadMore)?this.nextPage():window.targetCheck(e,this.selectors.buttons.clearFilters)&&this.clearFilters();let t=window.targetCheck(e,this.selectors.buttons.remove);t&&this.removeSelectedTerm(t),window.targetCheck(e,this.selectors.buttons.refresh)&&(this.store.clearCache(),this.store.fetch());let i=window.targetCheck(e,'[data-filter="orderby"]');i&&"random"===i.value&&i.checked&&this.renderItems()}nextPage(){const e=(this.store.filters.page||1)+1,t=this.store.lastResponse?.pages||e;this.store.setFilters({page:Math.min(e,t)})}handleChange(e){const t=e.target;if(Object.hasOwn(t.dataset,"filter")){if(this.allowedFilters.includes(t.dataset.filter)){let e={};e[t.dataset.filter]=t.value,this.resetFilters(e)}switch(t.dataset.filter){case"content":this.updateContentFor(t.value);break;case"orderby":this.updateOrderOptions(t.value)}}}clearFilters(){this.taxFilters={},window.removeChildren(this.ui.selected),this.taxonomies.forEach(e=>{let t=this.getFieldId(e);this.selector.selectedTerms.get(t)?.clear()}),this.store.setFilters({...this.defaults,taxonomy:null}),this.updateURL(),this.saveToCacheFilters()}resetFilters(e){e={...this.store.filters,page:1,...e},this.store.setFilters(e),this.updateURL(),this.saveToCacheFilters()}getFieldId(e){var t;return this.selector.getFieldId(null!==(t=Array.from(this.ui.taxonomies).filter(t=>t.dataset.taxonomy===e)[0])&&void 0!==t?t:null)}removeSelectedTerm(e){const t=parseInt(e.dataset.id),i=e.dataset.taxonomy;Object.hasOwn(this.taxFilters,i)&&(this.taxFilters[i]=this.taxFilters[i].filter(e=>e!==t),0===this.taxFilters[i].length&&delete this.taxFilters[i]),e.remove();const s=this.getFieldId(i);s&&(this.selector.activeField=s,this.selector.removeSelected(t,s)),this.resetFilters({taxonomy:Object.keys(this.taxFilters).length>0?this.taxFilters:null})}updateContentFor(e){[this.ui.taxonomies,this.ui.orderby].forEach(t=>{t&&t.forEach(t=>{var i;const s=null!==(i=t.dataset.for?.split(","))&&void 0!==i?i:[];t.hidden=s.length>0&&!s.includes(e),t.hidden&&t.checked&&(t.checked=!1)})})}updateOrderOptions(e){if(this.ui.orderbyWrap){var t;let i=null!==(t=this.ui.orderbyWrap.dataset.forOrder.split(","))&&void 0!==t?t:[];this.ui.orderbyWrap.hidden=!i.includes(e)}}updateFilterControls(){const e=0===Object.keys(this.taxFilters).length;this.ui.buttons.clearFilters&&(this.ui.buttons.clearFilters.hidden=e),this.ui.filters.actions&&(this.ui.filters.actions.hidden=e)}async initTaxonomies(){this.taxFilters={},this.selector=window.jvbSelector,this.selector.subscribe((e,t)=>{"selected-terms"===e&&this.handleTaxonomyChange(t)})}handleTaxonomyChange(e){const{terms:t,taxonomy:i}=e;0!==t.size&&(this.taxFilters[i]=Array.from(t),this.resetFilters({taxonomy:this.taxFilters}),t.forEach(e=>{this.createTermElement(e)}),this.updateFilterControls())}getTaxonomyIcon(e){let t=Array.from(this.ui.taxonomies).find(t=>t.dataset.taxonomy===e);return t?.dataset.icon.trim()||"tag"}createTermElement(e){const t=this.selector.store.get(e);t&&(this.ui.selected.querySelector(`[data-id="${e}"]`)||(t.icon=this.getTaxonomyIcon(t.taxonomy),this.ui.selected.append(this.templates.create("feedTerm",t))))}processCachedFilters(){Object.keys(this.filters).forEach(e=>{let t=this.cache.get(`${this.config.source}_${this.config.context}_${e}`);t&&t!==this.filters[e]&&(this.filters[e]=t)})}processURLFilters(){if(!this.isFirstPage())return!1;const e=new URLSearchParams(window.location.search);if(!e.toString())return!1;let t=!1;this.allowedFilters.forEach(i=>{let s=e.get(`f_${i}`);s&&(t=!0,this.filters[i]=s)});let i=!1;return e.forEach((e,s)=>{if(s.startsWith("f_tax_")){i=!0,t=!0;const r=s.replace("f_tax_","");this.taxFilters[r]=e.split(",").map(Number)}}),t&&(i&&(this.filters.taxonomy=this.taxFilters),this.resetFilters(this.filters)),!0}updateURL(){const e=new URLSearchParams;this.allowedFilters.forEach(t=>{Object.hasOwn(this.store.filters,t)&&this.store.filters[t]!==this.defaults[t]&&e.set(`f_${t}`,this.store.filters[t])});for(let[t,i]of Object.entries(this.taxFilters))i.length>0&&e.set(`f_tax_${t}`,i.join(","));const t=`${window.location.pathname}${e.toString()?"?"+e.toString():""}`;t!==window.location.pathname+window.location.search&&window.history.pushState({filters:this.store.filters},"",t)}saveToCacheFilters(){Object.keys(this.store.filters).forEach(e=>{const t=`${this.config.source}_${this.config.context}_${e}`;this.store.filters[e]!==this.defaults[e]?this.cache.set(t,this.store.filters[e]):this.cache.remove(t)});const e=`${this.config.source}_${this.config.context}_taxonomy`;Object.keys(this.taxFilters).length>0?this.cache.set(e,this.taxFilters):this.cache.remove(e)}initGallery(){this.gallery=!!this.config.gallery&&window.jvbGallery,this.gallery&&this.gallery.subscribe((e,t)=>{"load-more"===e&&this.store.lastResponse?.has_more&&this.nextPage()})}initStore(){let e=this.orderbyFilters.filter(e=>!["date","modified","title","random"].includes(e)),t=[];e.forEach(e=>{t.push({name:e,keyPath:e})});const i=window.jvbStore.register("feed",{storeName:"feed",endpoint:"feed",keyPath:"id",indexes:[{name:"content",keyPath:"content"},{name:"taxonomy",keyPath:"taxonomy"},{name:"user",keyPath:"user"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"},...t],filters:this.filters,TTL:216e5,showLoading:!0,required:"content"});this.store=i.feed,this.store.subscribe((e,t)=>{var i;"data-loaded"===e&&(this.renderItems(t.items),this.ui.buttons.loadMore.hidden=!0,this.store.lastResponse&&this.store.lastResponse?.has_more&&(this.ui.buttons.loadMore.hidden=null===(i=!this.store.lastResponse?.has_more)||void 0===i||i))})}isFirstPage(){return 1===this.store.filters.page}renderItems(e=null){e=null!=e?e:this.store.getFiltered(),this.isFirstPage()&&window.removeChildren(this.ui.grid),0===e.length?(this.showEmptyState(),this.a11y.announceItems(0,this.isFirstPage())):window.chunkIt(e,e=>this.createItemElement(e),t=>{var i;this.removePlaceholders(),this.ui.grid.append(t),this.config.gallery&&this.gallery.buildGalleryItems(".item img"),this.a11y.makeNavigable(this.ui.grid.querySelectorAll(".item:not([data-keyboard-nav])")),this.a11y.announceItems(e.length,!this.isFirstPage(),null!==(i=this.store.lastResponse?.has_more)&&void 0!==i&&i)},5).then(()=>{}),this.updateFilterControls()}showEmptyState(){window.removeChildren(this.ui.grid),this.ui.grid.append(this.templates.create("emptyState"))}createItemElement(e){if("object"==typeof e||(e=this.store.get(e)))return this.templates.create(`feedItem${window.uppercaseFirst(e.content)}`,e)}splitIDs(e){return String(e).split(",").map(e=>parseInt(e.trim())).filter(e=>e)}isImageField(e,t){return!(!Object.hasOwn(e,"images")||0===Object.keys(e.images).length)&&this.splitIDs(t).some(t=>Object.keys(e.images).map(e=>parseInt(e)).includes(parseInt(t)))}formatImageFields(e,t,i){let s=this.splitIDs(t);if(0!==s.length)if(s.length>1){let t=e.querySelector("img");if(!t)return;s.forEach(s=>{let r=t.cloneNode(!0);this.formatImageField(r,s,i),e.append(r)}),t.remove()}else{if("IMG"!==e.tagName&&!(e=e.querySelector("img")))return;this.formatImageField(e,s[0],i)}}formatImageField(e,t,i){var s;let r=null!==(s=i.images[t])&&void 0!==s&&s;r&&([e.src,e.srcset,e.alt]=[r.tiny,`${r.tiny} 50w, ${r.small} 300w, ${r.medium} 1024w`,r["image-alt-text"]])}isTaxonomyField(e,t){return!(!Object.hasOwn(e,"taxonomies")||0===Object.keys(e.taxonomies).length)&&Object.keys(e.taxonomies).includes(t)}formatTaxonomyField(e,t,i,s){if("UL"!==e.tagName||!e.querySelector("li"))return;let r=this.splitIDs(s);0===r.length&&e.remove();let o=e.querySelector("li");for(let s of r){var a;let r=null!==(a=t.taxonomies[i][s])&&void 0!==a&&a;if(!r)continue;let n=o.cloneNode(!0),l=n.querySelector("a");if(!l)continue;let h=window.decodeHTMLEntities(r.title);[l.href,l.title,l.textContent]=[r.url,`See more ${h}`,h],e.append(n)}o.remove()}isTimeField(e){return"TIME"===e.tagName||null!==e.querySelector("time")}formatTimeField(e,t){("TIME"===e.tagName||(e=e.querySelector("time")))&&(e.setAttribute("datetime",t),e.textContent=window.formatTimeAgo(t,"F Y"))}formatField(e,t){e.textContent=window.decodeHTMLEntities(t)}addTimelineElements(e,t){let[i,s,r,o]=[t.querySelector("span.after-text"),t.querySelector('[data-field="number"] b'),t.querySelector('[data-field="started"] time'),t.querySelector('[data-field="updated"] time')];i&&(i.textContent=`After ${e.number-1} Tx`),s&&(s.textContent=e.number-1),r&&this.formatTimeField(r,e.fields.timeline[0].post_date),o&&this.formatTimeField(o,e.fields.timeline[e.fields.timeline.length-1].post_date)}removePlaceholders(){const e=this.ui.grid.querySelectorAll(".placeholder");e.length>0&&e.forEach(e=>e.remove())}defineTemplates(){const e=this.templates,t=this;e.define("feedTerm",{refs:{icon:".icon",span:"span"},setup({el:e,refs:t,manyRefs:i,data:s}){e.dataset.id=s.id,e.dataset.taxonomy=s.taxonomy,t.icon&&(t.icon.className=`icon icon=${s.icon}`),t.span&&(t.span.textContent=window.decodeHTMLEntities(s.name))}}),e.define("emptyState"),this.contentTypes.forEach(i=>{e.define(`feedItem${window.uppercaseFirst(i)}`,{refs:{link:"a"},manyRefs:{fields:"[data-field]"},setup({el:e,refs:i,manyRefs:s,data:r}){const o=Object.hasOwn(e.dataset,"timeline");if(s.fields){for(let e of s.fields){if(o&&["timeline","number"].includes(e.dataset.field))continue;const i=!!Object.hasOwn(r.fields,e.dataset.field)&&r.fields[e.dataset.field];i?t.isImageField(r,i)?t.formatImageField(e,i,r):t.isTaxonomyField(r,e.dataset.field)?t.formatTaxonomyField(e,r,e.dataset.field,i):t.isTimeField(e)?t.formatTimeField(e,i):t.formatField(e,i):e.remove()}var a;i.link&&""!==r.url&&(i.link.href=r.url,i.link.title=`View ${null!==(a=r.fields.post_title)&&void 0!==a?a:"Item"}`),o&&t.addTimelineElements(r,e)}}})})}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.feedBlock=new e)})})})();
(()=>{class e{constructor(){this.container=document.querySelector("section.feed-block"),this.container&&(this.a11y=window.jvbA11y,this.error=window.jvbError,this.cache=new window.jvbCache("feed"),this.templates=window.jvbTemplates,this.config={source:"",context:"",highlight:null,gallery:!1,view:this.cache.get("feedView")||"grid",...this.container.dataset},this.init())}init(){this.initElements(),this.defineTemplates(),this.initListeners(),this.initFilters(),"requestIdleCallback"in window?requestIdleCallback(()=>{this.initStore(),this.initTaxonomies(),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()},{timeout:2e3}):setTimeout(()=>{this.initStore(),this.initTaxonomies(),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()},100)}initElements(){this.selectors={filterTrigger:"[data-filter]",filters:{actions:".filter-actions .toggle-text",container:".all-filters",content:'[data-filter="content"]',orderby:'[data-filter="orderby"]',order:'[data-filter="order"]',match:'[data-filter="match"]',favourites:'[data-filter="favourites"]',taxonomy:'[data-filter^="taxonomy"]'},grid:".item-grid",selected:".selected-items",buttons:{loadMore:"button.load-more",remove:".remove-term",clearFilters:"button.clear-filters",refresh:'button[data-action="refresh"]'}},this.ui=window.uiFromSelectors(this.selectors,this.container),this.ui.buttons.refresh=document.querySelector(this.selectors.buttons.refresh),this.ui.content=this.ui.filters.container.querySelectorAll('[name="content"]'),0===this.ui.content.length&&(this.ui.content=!1),this.ui.taxonomies=this.ui.filters.container.querySelectorAll("[data-taxonomy]"),0===this.ui.taxonomies.length&&(this.ui.taxonomies=!1),this.ui.orderbyWrap=this.ui.filters.container.querySelector("[data-for-order]"),0===this.ui.orderbyWrap.length&&(this.ui.orderbyWrap=!1),this.ui.order=this.ui.filters.container.querySelectorAll('[data-filter="order"]'),0===this.ui.order.length&&(this.ui.order=!1),this.ui.orderby=this.ui.filters.container.querySelectorAll('[data-filter="orderby"]'),0===this.ui.orderby.length&&(this.ui.orderby=!1),this.orderbyFilters=this.ui.orderby?Array.from(this.ui.orderby).map(e=>e.value):[],this.contentTypes=this.ui.content?Array.from(this.ui.content).map(e=>e.value):[this.container.dataset.content],this.taxonomies=this.ui.taxonomies?.length>0?Array.from(this.ui.taxonomies).map(e=>e.dataset.taxonomy):[]}initListeners(){this.popStateHandler=this.handlePopState.bind(this),this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),window.addEventListener("popstate",this.popStateHandler),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler)}initFilters(){this.allowedFilters=["content","order","orderby","favourites","match"];let e={content:this.contentTypes[0],orderby:"date",order:"desc",page:1};this.config.context&&(e.context=this.config.context),this.config.source&&(e.source=this.config.source),this.filters=e,this.defaults={...e}}updateFilterUI(){if(this.ui.filters.container&&([this.ui.content,this.ui.orderby,this.ui.order].forEach(e=>{if(e)for(let t of e){let[e,i]=[t.dataset.filter,t.value];if(!Object.hasOwn(this.store.filters,e))break;let s=this.store.filters[e]===i;if(s){t.checked=s;break}}}),Object.hasOwn(this.store.filters,"taxonomy")))for(let[e,t]of Object.entries(this.store.filters.taxonomy))t.forEach(e=>{e=parseInt(e),this.selector.store.get(e)&&this.createTermElement(e)})}handlePopState(e){e.state?.filters&&this.processURLFilters()&&(this.store.setFilters(this.filters),this.a11y.announce("Feed filters updated from browser history"))}handleClick(e){window.targetCheck(e,this.selectors.buttons.loadMore)?this.nextPage():window.targetCheck(e,this.selectors.buttons.clearFilters)&&this.clearFilters();let t=window.targetCheck(e,this.selectors.buttons.remove);t&&this.removeSelectedTerm(t),window.targetCheck(e,this.selectors.buttons.refresh)&&(this.store.clearCache(),this.store.fetch());let i=window.targetCheck(e,'[data-filter="orderby"]');i&&"random"===i.value&&i.checked&&this.renderItems()}nextPage(){const e=(this.store.filters.page||1)+1,t=this.store.lastResponse?.pages||e;this.store.setFilters({page:Math.min(e,t)})}handleChange(e){const t=e.target;if(Object.hasOwn(t.dataset,"filter")){if(this.allowedFilters.includes(t.dataset.filter)){let e={};e[t.dataset.filter]=t.value,this.resetFilters(e)}switch(t.dataset.filter){case"content":this.updateContentFor(t.value);break;case"orderby":this.updateOrderOptions(t.value)}}}clearFilters(){this.taxFilters={},window.removeChildren(this.ui.selected),this.taxonomies.forEach(e=>{let t=this.getFieldId(e);this.selector.selectedTerms.get(t)?.clear()}),this.store.setFilters({...this.defaults,taxonomy:null}),this.updateURL(),this.saveToCacheFilters()}resetFilters(e){e={...this.store.filters,page:1,...e},this.store.setFilters(e),this.updateURL(),this.saveToCacheFilters()}getFieldId(e){return this.selector.getFieldId(Array.from(this.ui.taxonomies).filter(t=>t.dataset.taxonomy===e)[0]??null)}removeSelectedTerm(e){const t=parseInt(e.dataset.id),i=e.dataset.taxonomy;Object.hasOwn(this.taxFilters,i)&&(this.taxFilters[i]=this.taxFilters[i].filter(e=>e!==t),0===this.taxFilters[i].length&&delete this.taxFilters[i]),e.remove();const s=this.getFieldId(i);s&&(this.selector.activeField=s,this.selector.removeSelected(t,s)),this.resetFilters({taxonomy:Object.keys(this.taxFilters).length>0?this.taxFilters:null})}updateContentFor(e){[this.ui.taxonomies,this.ui.orderby].forEach(t=>{t&&t.forEach(t=>{const i=t.dataset.for?.split(",")??[];t.hidden=i.length>0&&!i.includes(e),t.hidden&&t.checked&&(t.checked=!1)})})}updateOrderOptions(e){if(this.ui.orderbyWrap){let t=this.ui.orderbyWrap.dataset.forOrder.split(",")??[];this.ui.orderbyWrap.hidden=!t.includes(e)}}updateFilterControls(){const e=Object.keys(this.taxFilters);this.ui.buttons.clearFilters&&(this.ui.buttons.clearFilters.hidden=0===e.length),this.ui.filters.actions&&(this.ui.filters.actions.hidden=e.length<=1)}async initTaxonomies(){this.taxFilters={},this.selector=window.jvbSelector,this.selector.subscribe((e,t)=>{"selected-terms"===e&&this.handleTaxonomyChange(t)})}handleTaxonomyChange(e){const{terms:t,taxonomy:i}=e;0!==t.size&&(this.taxFilters[i]=Array.from(t),this.resetFilters({taxonomy:this.taxFilters}),t.forEach(e=>{this.createTermElement(e)}),this.updateFilterControls())}getTaxonomyIcon(e){let t=Array.from(this.ui.taxonomies).find(t=>t.dataset.taxonomy===e);return t?.dataset.icon.trim()||"tag"}createTermElement(e){const t=this.selector.store.get(e);t&&(this.ui.selected.querySelector(`[data-id="${e}"]`)||(t.icon=this.getTaxonomyIcon(t.taxonomy),this.ui.selected.append(this.templates.create("feedTerm",t))))}processCachedFilters(){Object.keys(this.filters).forEach(e=>{let t=this.cache.get(`${this.config.source}_${this.config.context}_${e}`);t&&t!==this.filters[e]&&(this.filters[e]=t)})}processURLFilters(){if(!this.isFirstPage())return!1;const e=new URLSearchParams(window.location.search);if(!e.toString())return!1;let t=!1;this.allowedFilters.forEach(i=>{let s=e.get(`f_${i}`);s&&(t=!0,this.filters[i]=s)});let i=!1;return e.forEach((e,s)=>{if(s.startsWith("f_tax_")){i=!0,t=!0;const r=s.replace("f_tax_","");this.taxFilters[r]=e.split(",").map(Number)}}),t&&(i&&(this.filters.taxonomy=this.taxFilters),this.resetFilters(this.filters)),!0}updateURL(){const e=new URLSearchParams;this.allowedFilters.forEach(t=>{Object.hasOwn(this.store.filters,t)&&this.store.filters[t]!==this.defaults[t]&&e.set(`f_${t}`,this.store.filters[t])});for(let[t,i]of Object.entries(this.taxFilters))i.length>0&&e.set(`f_tax_${t}`,i.join(","));const t=`${window.location.pathname}${e.toString()?"?"+e.toString():""}`;t!==window.location.pathname+window.location.search&&window.history.pushState({filters:this.store.filters},"",t)}saveToCacheFilters(){Object.keys(this.store.filters).forEach(e=>{const t=`${this.config.source}_${this.config.context}_${e}`;this.store.filters[e]!==this.defaults[e]?this.cache.set(t,this.store.filters[e]):this.cache.remove(t)});const e=`${this.config.source}_${this.config.context}_taxonomy`;Object.keys(this.taxFilters).length>0?this.cache.set(e,this.taxFilters):this.cache.remove(e)}initGallery(){this.gallery=!!this.config.gallery&&window.jvbGallery,this.gallery&&this.gallery.subscribe((e,t)=>{"load-more"===e&&this.store.lastResponse?.has_more&&this.nextPage()})}initStore(){let e=this.orderbyFilters.filter(e=>!["date","modified","title","random"].includes(e)),t=[];e.forEach(e=>{t.push({name:e,keyPath:e})});const i=window.jvbStore.register("feed",{storeName:"feed",endpoint:"feed",keyPath:"id",indexes:[{name:"content",keyPath:"content"},{name:"taxonomy",keyPath:"taxonomy"},{name:"user",keyPath:"user"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"},...t],filters:this.filters,TTL:216e5,showLoading:!0,required:"content"});this.store=i.feed,this.store.subscribe((e,t)=>{"data-loaded"===e&&(this.renderItems(t.items),this.ui.buttons.loadMore.hidden=!0,this.store.lastResponse&&this.store.lastResponse?.has_more&&(this.ui.buttons.loadMore.hidden=!this.store.lastResponse?.has_more??!0))})}isFirstPage(){return 1===this.store.filters.page}renderItems(e=null){e=e??this.store.getFiltered(),this.isFirstPage()&&window.removeChildren(this.ui.grid),0===e.length?(this.showEmptyState(),this.a11y.announceItems(0,this.isFirstPage())):window.chunkIt(e,e=>this.createItemElement(e),t=>{this.removePlaceholders(),this.ui.grid.append(t),this.config.gallery&&this.gallery.buildGalleryItems(".item img"),this.a11y.makeNavigable(this.ui.grid.querySelectorAll(".item:not([data-keyboard-nav])")),this.a11y.announceItems(e.length,!this.isFirstPage(),this.store.lastResponse?.has_more??!1)},5).then(()=>{}),this.updateFilterControls()}showEmptyState(){window.removeChildren(this.ui.grid),this.ui.grid.append(this.templates.create("emptyState"))}createItemElement(e){if("object"==typeof e||(e=this.store.get(e)))return this.templates.create(`feedItem${window.uppercaseFirst(e.content)}`,e)}splitIDs(e){return String(e).split(",").map(e=>parseInt(e.trim())).filter(e=>e)}isImageField(e,t){return!(!Object.hasOwn(e,"images")||0===Object.keys(e.images).length)&&this.splitIDs(t).some(t=>Object.keys(e.images).map(e=>parseInt(e)).includes(parseInt(t)))}formatImageFields(e,t,i){let s=this.splitIDs(t);if(0!==s.length)if(s.length>1){let t=e.querySelector("img");if(!t)return;s.forEach(s=>{let r=t.cloneNode(!0);this.formatImageField(r,s,i),e.append(r)}),t.remove()}else{if("IMG"!==e.tagName&&!(e=e.querySelector("img")))return;this.formatImageField(e,s[0],i)}}formatImageField(e,t,i){let s=i.images[t]??!1;s&&([e.src,e.srcset,e.alt]=[s.tiny,`${s.tiny} 50w, ${s.small} 300w, ${s.medium} 1024w`,s["image-alt-text"]])}isTaxonomyField(e,t){return!(!Object.hasOwn(e,"taxonomies")||0===Object.keys(e.taxonomies).length)&&Object.keys(e.taxonomies).includes(t)}formatTaxonomyField(e,t,i,s){if("UL"!==e.tagName||!e.querySelector("li"))return;let r=this.splitIDs(s);0===r.length&&e.remove();let o=e.querySelector("li");for(let s of r){let r=t.taxonomies[i][s]??!1;if(!r)continue;let a=o.cloneNode(!0),n=a.querySelector("a");if(!n)continue;let l=window.decodeHTMLEntities(r.title);[n.href,n.title,n.textContent]=[r.url,`See more ${l}`,l],e.append(a)}o.remove()}isTimeField(e){return"TIME"===e.tagName||null!==e.querySelector("time")}formatTimeField(e,t){("TIME"===e.tagName||(e=e.querySelector("time")))&&(e.setAttribute("datetime",t),e.textContent=window.formatTimeAgo(t,"F Y"))}formatField(e,t){e.textContent=window.decodeHTMLEntities(t)}addTimelineElements(e,t){let[i,s,r,o]=[t.querySelector("span.after-text"),t.querySelector('[data-field="number"] b'),t.querySelector('[data-field="started"] time'),t.querySelector('[data-field="updated"] time')];i&&(i.textContent=`After ${e.number-1} Tx`),s&&(s.textContent=e.number-1),r&&this.formatTimeField(r,e.fields.timeline[0].post_date),o&&this.formatTimeField(o,e.fields.timeline[e.fields.timeline.length-1].post_date)}removePlaceholders(){const e=this.ui.grid.querySelectorAll(".placeholder");e.length>0&&e.forEach(e=>e.remove())}defineTemplates(){const e=this.templates,t=this;e.define("feedTerm",{refs:{icon:".icon",span:"span"},setup({el:e,refs:t,manyRefs:i,data:s}){e.dataset.id=s.id,e.dataset.taxonomy=s.taxonomy,t.icon&&(t.icon.className=`icon icon-${s.icon}`),t.span&&(t.span.textContent=window.decodeHTMLEntities(s.name))}}),e.define("emptyState"),this.contentTypes.forEach(i=>{e.define(`feedItem${window.uppercaseFirst(i)}`,{refs:{link:"a"},manyRefs:{fields:"[data-field]"},setup({el:e,refs:i,manyRefs:s,data:r}){const o=Object.hasOwn(e.dataset,"timeline");if(s.fields){for(let e of s.fields){if(o&&["timeline","number"].includes(e.dataset.field))continue;const i=!!Object.hasOwn(r.fields,e.dataset.field)&&r.fields[e.dataset.field];i?t.isImageField(r,i)?t.formatImageField(e,i,r):t.isTaxonomyField(r,e.dataset.field)?t.formatTaxonomyField(e,r,e.dataset.field,i):t.isTimeField(e)?t.formatTimeField(e,i):t.formatField(e,i):e.remove()}i.link&&""!==r.url&&(i.link.href=r.url,i.link.title=`View ${r.fields.post_title??"Item"}`),o&&t.addTimelineElements(r,e)}}})})}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.feedBlock=new e)})})})();
build/summary/style-index-rtl.css
@@ -1 +1 @@
details>div{margin:1rem 0}main>header:not(:has(img)){margin-top:3rem!important}header a:before{display:none!important}header+details{margin:1.5rem auto 3rem!important;max-width:var(--wide)}main{padding-top:0!important}
details>div{margin:1rem 0}main>header:not(:has(img)){margin-top:3rem!important}header a:before{display:none!important}header+details{margin:1.5rem auto 3rem!important;max-width:var(--wide)}main{padding-top:0!important}ul.summary .term-list{display:flex;flex-wrap:nowrap;padding-right:0}
build/summary/style-index.css
@@ -1 +1 @@
details>div{margin:1rem 0}main>header:not(:has(img)){margin-top:3rem!important}header a:before{display:none!important}header+details{margin:1.5rem auto 3rem!important;max-width:var(--wide)}main{padding-top:0!important}
details>div{margin:1rem 0}main>header:not(:has(img)){margin-top:3rem!important}header a:before{display:none!important}header+details{margin:1.5rem auto 3rem!important;max-width:var(--wide)}main{padding-top:0!important}ul.summary .term-list{display:flex;flex-wrap:nowrap;padding-left:0}
inc/blocks/CustomBlocks.php
@@ -24,7 +24,7 @@
    protected static ?int $currentQueryId = null;
    protected static array $counters = [];
    protected static ?WP_Query $originalQuery = null;
    protected array $ignore = ['align','alt','area','aspectRatio','backgroundColor','borderColor','buttonText','buttonPosition','buttonUseIcon','categories','className','columns','contentPosition','customOverlayColor','dimRatio','displayAsDropdown','displayAuthor','displayFeaturedImage','displayPostContent','displayPostContentRadio','displayPostDate','excerptLength','featuredImageAlign','fontSize','gradient','hasFixedLayout','hasParallax','height','iconColor','iconColorValue','iconColorValue','iconBackgroundColor','iconBackgroundColorValue','id','imageFill','isDark','isLink','isObjectPosition','isRepeated','isSearchFieldHidden','isStackedOnMobile','isUserOverlayColor','kind','label','largestFontSize','layout','lightbox','linkDestination','linkTo','level','mediaId','mediaLink','mediaPosition','mediaSizeSlug','mediaType','mediaWidth','metadata','minHeight','minHeightUnit','opacity','opensInNewTab','order','orderBy','ordered','overlayColor','overlayMenu','placeholder','postLayout','postsToShow','query', 'queryId','ref','rel','scale','shouldSyncIcon','showContent','showEmpty','showHierarchy','showLabel','showLabels','showOnlyTopLevel','showPostCounts','showTagCounts','size','sizeSlug','slug','smallestFontSize','tagName','taxonomy','term','textAlign','textColor','theme','title','type','url','useFeaturedImage','verticalAlignment','width','widthUnit',];
    protected array $ignore = ['align','alt','area','aspectRatio','backgroundColor','borderColor','buttonText','buttonPosition','buttonUseIcon','categories','className','columns','contentPosition','customOverlayColor','datetime','dimRatio','displayAsDropdown','displayAuthor','displayFeaturedImage','displayPostContent','displayPostContentRadio','displayPostDate','excerptLength','featuredImageAlign','fontSize','gradient','hasFixedLayout','hasParallax','height','iconColor','iconColorValue','iconColorValue','iconBackgroundColor','iconBackgroundColorValue','id','imageFill','isDark','isLink','isObjectPosition','isRepeated','isSearchFieldHidden','isStackedOnMobile','isUserOverlayColor','kind','label','largestFontSize','layout','lightbox','linkDestination','linkTo','level','mediaId','mediaLink','mediaPosition','mediaSizeSlug','mediaType','mediaWidth','metadata','minHeight','minHeightUnit','opacity','opensInNewTab','order','orderBy','ordered','overlayColor','overlayMenu','placeholder','postLayout','postsToShow','query', 'queryId','ref','rel','scale','shouldSyncIcon','showContent','showEmpty','showHierarchy','showLabel','showLabels','showOnlyTopLevel','showPostCounts','showTagCounts','size','sizeSlug','slug','smallestFontSize','tagName','taxonomy','term','textAlign','textColor','theme','title','type','url','useFeaturedImage','verticalAlignment','width','widthUnit',];
    //For custom style output for nested links, etc
    protected static array $pendingStyles = [];
@@ -88,6 +88,27 @@
                'label' => __('With Logo', 'jvb')
            ]
        );
        register_block_style(
            'core/gallery',
            [
                'name'  =>'hexagon',
                'label' => __('Hexagonal', 'jvb')
            ]
        );
        register_block_style(
            'core/gallery',
            [
                'name'  =>'diamond',
                'label' => __('Diamond', 'jvb')
            ]
        );
        register_block_style(
            'core/gallery',
            [
                'name'  =>'cut',
                'label' => __('Cut Corners', 'jvb')
            ]
        );
    }
    protected function checkMethods(?string $content, array $block, ?WP_Block $parent = null, bool $isPrerender = false):?string
    {
@@ -115,7 +136,11 @@
            $base = [
                'core/null',
                'core/list-item',
                'jvb/drawer-menu'
                'core/post-content',
                'jvb/drawer-menu',
                'jvb/summary',
                'jvb/feed',
                'jvb/forms',
            ];
            if ($isPrerender) {
                $base = array_merge($base, [
@@ -3422,6 +3447,7 @@
                            break;
                        case 'textAlign':
                            $styles['text-align'] = $value;
                            break;
                        default:
                            if (JVB_TESTING) {
                                jvbDump($value,'[getTypographyStyle] No property set for '.$property.': ');
inc/blocks/FeedBlock.php
@@ -5,6 +5,7 @@
use JVBase\registrar\Registrar;
use JVBase\base\Site;
use JVBase\forms\TaxonomySelector;
use JVBase\ui\CRUDSkeleton;
use WP_Block;
if (!defined('ABSPATH')) {
@@ -17,13 +18,17 @@
    protected array $config;
    protected string $path = JVB_DIR.'/build/feed';
    protected array $content = [];
    protected array $taxonomies = [];
    protected bool $isGallery = false;
    public function __construct()
    {
        // Initialize cache with connections
        $this->cache = Cache::for('feed_block', WEEK_IN_SECONDS);
//      if (JVB_TESTING) {
//          $this->cache->flush();
//      }
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        add_action('init', [$this, 'registerBlock']);
    }
@@ -35,385 +40,483 @@
        ]);
    }
    protected function buildParams(array $attributes): array
    {
        if (!jvbCheck('inheritQuery', $attributes)) {
            return [
                'title' => $attributes['title'],
                'content' => $attributes['contentTypes'],
                'taxonomies' => $this->getTaxonomies($attributes['contentTypes'])
            ];
        }
        $config = [
            'is_gallery'    => false,
            'content'       => '',
            'taxonomies'    => []
        ];
        $type = get_queried_object();
        if (is_post_type_archive() || is_singular()) {
            $content = is_singular() ? jvbNoBase($type->post_type) : jvbNoBase($type->name);
            $registrar = Registrar::getInstance($content)??false;
            if ($registrar) {
                $config = array_merge($config, $registrar->getConfig('feed'));
            } else {
                $config['content'] = $content;
                $config['icon'] = jvbDefaultIcon();
            }
            if (is_singular()) {
                $config['source'] = $type->ID;
            }
            $config['taxonomies'] = $this->getTaxonomies([$content]);
        } elseif (is_tax()) {
            $content = jvbNoBase($type->taxonomy);
            $registrar = Registrar::getInstance($content)??false;
            if ($registrar) {
                $config['content'] = $registrar->registrar->for;
                $config['context'] = $content;
                $config['taxonomies'] = $this->getTaxonomies($registrar->registrar->for);
                if (!empty($registrar->getConfig('feed'))){
                    $config = array_merge($config, $registrar->getConfig('feed'));
                }
            }
            $config['source'] = $type->term_id;
        }
        if (!is_array($config['content'])) {
            $config['content'] = [$config['content']];
        }
        return $config;
    }
    /**
     * Get taxonomies for given content types
     */
    protected function getTaxonomies(array $content): array
    {
        $taxonomies = [];
        foreach ($content as $contentType) {
            $registrar = Registrar::getInstance($contentType);
            if (!$registrar) {
                continue;
            }
            $contentTaxonomies = $registrar->registrar->taxonomies;
            $contentTaxonomies = array_filter($contentTaxonomies, function($taxonomy) {
                return Registrar::getInstance($taxonomy)?->hasFeature('show_feed');
            });
            $taxonomies = array_merge($taxonomies, $contentTaxonomies);
        }
        return array_unique($taxonomies);
    }
    public function render(array $attributes, string $content, WP_Block $block)
    {
        $this->config = $this->buildParams($attributes);
        return $this->cache->remember(
            $this->cache->generateKey($this->config),
            function() {
                return $this->renderBlock();
            }
        );
    }
    protected function getContext():string|bool
    {
        return array_key_exists('context', $this->config)?$this->config['context']:false;
    }
    protected function getIDS():array|bool
    {
        return (array_key_exists('ids', $this->config) && !empty($this->config['ids'])) ? $this->config['ids'] : false;
    }
    protected function getClasses():array|bool
    {
        return array_key_exists('classes', $this->config) && !empty($this->config['classes']) ? $this->config['classes'] : false;
    }
    protected function getSource():string|bool
    {
        return array_key_exists('source', $this->config) ? $this->config['source'] : false;
    }
    protected function getIcon():string|bool
    {
        return array_key_exists('icon', $this->config) ? $this->config['icon'] : false;
    }
    protected function isGallery():bool
    {
        return (array_key_exists('is_gallery', $this->config) && $this->config['is_gallery']);
    }
    protected function getContent():array|bool
    {
        return (array_key_exists('content', $this->config) && !empty ($this->config['content'])) ? $this->config['content'] : false;
    }
    protected function renderBlock(): string
    public function render(array $attributes): string
    {
        if (is_post_type_archive(BASE.'directory')) {
            return '';
        }
        $ids = ($this->getIDS()) ? ' id="'.implode(' ',$this->getIDS()).'"' : '';
        $classes = ($this->getClasses()) ? ' class="'.implode(' ',$this->getClasses()).'"' : '';
        $source = ($this->getSource()) ? ' data-source="'.$this->getSource().'"' : '';
        $context = ($this->getContext()) ? ' data-context="'.$this->getContext().'"' : '';
        $icons = ($this->getIcon()) ? ' data-icon="'.$this->getIcon().'"' : ' data-icon="'.jvbLogoIcon().'"';
        $gallery = $this->isGallery() ? ' data-gallery' : '';
        $content = ($this->getContent()) ? ' data-content="'.implode(',',$this->getContent()).'"' : '';
        ob_start();
        ?>
        <section<?= $ids.$classes ?> class="feed-block"<?= $content.$source.$context.$gallery.$icons ?>>
            <?php
            $this->renderFilters();
            $this->renderGrid();
            $this->renderLoader();
            $this->renderTemplates();
            echo TaxonomySelector::outputSelectorModal();
            ?>
        </section>
        <footer><button data-action="refresh" data-ignore><?=jvbIcon('arrows-clockwise')?><span>Hard Refresh</span></span></button></footer>
        <?php
        return ob_get_clean();
        $this->determineContent($attributes);
        if (empty($this->content)) {
            return '';
        }
        $this->determineTaxonomies();
        $classes = '';
        return sprintf(
            '<section class="feed-block%s" data-content="%s"%s>
            %s%s%s%s%s
            <footer>%s</footer>
            </section>',
            $classes,
            $this->getContent(),
            $this->isGallery ? ' data-gallery' : '',
            $this->renderFiltersAndControls(),
            $this->renderGrid(),
            $this->renderTemplates(),
            $this->renderLoader(),
            TaxonomySelector::outputSelectorModal(),
            $this->renderActions()
        );
    }
    protected function renderFilters(): void
    protected function determineContent(array $attrs):void
    {
        if (empty($this->config)) {
            return;
        if (array_key_exists('inheritQuery', $attrs) && $attrs['inheritQuery'] === true) {
            if (is_post_type_archive()) {
                $obj = get_queried_object();
                $this->content = [jvbNoBase($obj->name)];
                return;
            } elseif (!empty(Registrar::getProfileTypes()) && is_singular(Registrar::getProfileTypes())) {
                global $post;
                $author = $post->post_author;
                $role = jvbUserRole($author);
                $registrar = Registrar::getInstance($role);
                if (!$registrar) {
                    return;
                }
                $this->content = $registrar->getCreatable();
                return;
            } elseif (is_tax()) {
                $obj = get_queried_object();
                $registrar = Registrar::getInstance($obj->taxonomy);
                if (!$registrar) {
                    return;
                }
                if ($registrar->hasFeature('is_content')) {
                    //example: tattoo shop, etc TODO
                    return;
                }
                $this->content = array_map(function ($item) { return jvbNoBase($item); }, $registrar->registrar->for);
                return;
            }
        }
        // not inheriting, getting from config
        $this->content = $attrs['contentTypes']??[];
    }
        protected function getContent():string
        {
            return implode(',', $this->content);
        }
        protected function determineTaxonomies():void
        {
            $taxonomies = [];
            $ignore = [];
            foreach ($this->content as $content) {
        $feedContent = $this->getFeedContent();
        $hasMany = count($this->getContent()) > 1;
        ?>
        <form class="filters" data-save="feed-<?=$this->getContext()?>">
            <?php if ($hasMany) {
                //If we have multiple content, only show the content first
                ?>
                <details class="col left">
                <summary class="row x-btw">
                    <span class="label">SHOWING: </span>
                    <?php
                    $labels = [];
                    foreach ($this->getContent() as $i => $type) :
                        $checked = $i === 0 ? ' checked' : '';
                        $label = $feedContent[$type]['plural'] ?? ucfirst($type);
                        ?>
                        <input type="radio"
                               id="filter-<?= esc_attr($type) ?>"
                               class="btn"
                               name="content"
                               data-filter="content"
                               value="<?= esc_attr($type) ?>"
                            <?= $checked ?>>
                        <label for="filter-<?= esc_attr($type) ?>" title="Show <?= $label ?>" class="row">
                            <?= jvbIcon($feedContent[$type]['icon']) ?>
                            <span class="screen-reader-text"><?= $label ?></span>
                        </label>
                        <?php
                        $labels['filter-'.$type] = $label;
                    endforeach;
                    ?>
                    <ul class="filter-label">
                        <?php
                        $i = 0;
                        foreach ($labels as $id => $label) {
                            $active = $i === 0 ? ' class="active"' : '';
                            ?>
                            <li id="<?= $id ?>"<?= $active ?>>
                                <?= $label ?>
                            </li>
                            <?php
                            $i++;
                $registrar = Registrar::getInstance($content);
                if (!$registrar) continue;
                $theTax = $registrar->registrar->taxonomies;
                foreach ($theTax as $tax) {
                    if (!in_array($tax, $ignore) && !in_array($tax, $taxonomies)) {
                        $taxReg = Registrar::getInstance($tax);
                        if ($taxReg->hasFeature('show_feed')) {
                            $taxonomies[] = $tax;
                        } else {
                            $ignore[] = $tax;
                        }
                        ?>
                    </ul>
            <?php } ?>
                    <?php if (Site::has('favourites') && is_user_logged_in()) : ?>
                        <input type="checkbox" id="favourites" class="btn" name="favourites" value="on"
                               data-filter="favourites">
                        <label for="favourites" title="Show Favourites" class="row">
                            <?= jvbIcon('heart').jvbIcon('heart', ['style' => 'fill']) ?>
                            <span class="screen-reader-text">Show Favourites Only</span>
                        </label>
                    <?php endif; ?>
                <?php if ($hasMany) { ?>
                </summary>
                <?php }
                if (!empty ($this->config['taxonomies'])) {
                ?>
                    <div class="filters">
                        <div class="filter-group row left">
                            <span class="label">FILTER BY:</span>
                            <?php
                            foreach ($this->config['taxonomies'] as $tax) :
                                $registrar = Registrar::getInstance($tax)??false;
                                if (!$registrar) continue;
                                $contentForTax = $registrar->registrar->for;
                                $hidden = empty($contentForTax) ? ' hidden' : '';
                                $taxSelector = new TaxonomySelector(
                                    'feed-'.$tax,
                                    $tax,
                                    [
                                        'icon'          => $registrar->getIcon()??jvbLogoIcon(),
                                        'update'        => '.selected-items-section .selected-items',
                                        'types'         => $contentForTax,
                                        'autocomplete'  => false,
                                        'hidden'        => $hidden,
                                        'output'        => 'minimal'
                                    ]
                                );
                                echo $taxSelector->render();
                            endforeach;
                            ?>
                        </div>
                        <div class="selected-items-section">
                            <div class="selected-items row"></div>
                            <div class="filter-actions row">
                                <?= str_replace('class="toggle-text"', 'class="toggle-text" hidden', jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match'])) ?>
                                <button type="button" class="clear-filters row" hidden>
                                    <?= jvbIcon('x') ?>
                                    Clear All Filters
                                </button>
                            </div>
                        </div>
                    </div>
                <?php } ?>
                    <div class="row x-btw nowrap">
                        <div class="order-by filter-group row left w-full">
                            <span class="label">ORDER BY:</span>
                            <?php
                            //TODO: Get content types that can be sorted alphabetically
                            ?>
                            <input type="radio" id="order-title" class="btn" name="orderby" value="title" data-for="artist,shop" data-filter="orderby" hidden>
                            <label for="order-title" title="Order by Name" class="row">
                                <?= jvbIcon('alphabetical') ?>
                                <span class="label">Name</span>
                            </label>
                            <input type="radio" id="order-date" class="btn" name="orderby" value="date" data-filter="orderby" checked>
                            <label for="order-date" title="Order by Date Created" class="row">
                                <?= jvbIcon('calendar', ['title' => 'Date']) ?>
                                <span class="label">Date Created</span>
                            </label>
                            <input type="radio" id="order-modified" class="btn" name="orderby" value="modified" data-filter="orderby">
                            <label for="order-modified" title="Order by Date Modified" class="row">
                                <?= jvbIcon('clock-clockwise') ?>
                                <span class="label">Date Modified</span>
                            </label>
                            <?php
                                $custom = [];
                                foreach ($this->getContent() as $content) {
                                    $registrar = Registrar::getInstance($content)??false;
                                    if ($registrar && !empty($registrar->config('feed')->getCustomOrder())) {
                                        $custom = array_merge_recursive($custom, $registrar->config('feed')->getCustomOrder());
                                    }
                                }
                                foreach ($custom as $slug => $conf) {
                                    ?>
                                    <input type="radio" id="order-<?=$slug?>" class="btn" name="orderby" value="<?=$slug?>" data-for="<?=$conf['for']?>" data-filter="orderby">
                                    <label for="order-<?=$slug?>" title="<?= $conf['label']?>" class="row">
                                        <?= jvbIcon($conf['icon']) ?>
                                        <span class="label"><?=$conf['label']?></span>
                                    </label>
                                    <?php
                                }
                                $custom = implode(',', array_keys($custom));
                            ?>
                            <input type="radio" id="order-random" class="btn" name="orderby" value="random" data-filter="orderby">
                            <label for="order-random" title="Random Order" class="row">
                                <?= jvbIcon('shuffle') ?>
                                <span class="label">Random</span>
                            </label>
                        </div>
                        <div class="order-direction filter-group row left w-full" data-for-order="date,modified,title<?= $custom === '' ? '' : ','.$custom?>">
                            <span class="label">ORDER:</span>
                            <input type="radio" id="order-desc" class="btn" name="order" value="desc" data-filter="order" checked>
                            <label for="order-desc" title="Sort Descending (A-Z, 1-10)" class="row">
                                <?= jvbIcon('sort-descending') ?>
                                <span class="label" >DESC (A-Z)</span>
                            </label>
                            <input type="radio" id="order-asc" class="btn" name="order" value="asc" data-filter="order">
                            <label for="order-asc" title="Sort Ascending (Z-A, 10-1)" class="row">
                                <?= jvbIcon('sort-ascending') ?>
                                <span class="label" >ASC (Z-A)</span>
                            </label>
                        </div>
                    </div>
            <?php if ($hasMany) { ?>
                </details>
            <?php } ?>
        </form>
        <?php
    }
    protected function renderGrid(): void
    {
        ?>
        <div class="item-grid">
            <?php
            $total = count($this->getContent()) - 1;
            for ($i = 1; $i <= 36; $i++) {
                $rand = rand(0, $total);
                $config = Registrar::getInstance($this->getContent()[$rand]);
                $icon = jvbIcon($config->getIcon??jvbLogoIcon());
                ?>
                <div class="placeholder"><?=apply_filters('jvbFeedPlaceholder', $icon) ?></div>
                <?php
                    }
                }
            }
            ?>
        </div>
        <?php
    }
    protected function renderLoader(): void
    {
        ?>
        <button type="button" class="load-more">
            <?= jvbIcon('arrow-elbow-left-down') ?>
            Show Me More
            <?= jvbIcon('arrow-elbow-right-down') ?>
        </button>
        <?= jvbLoadingScreen() ?>
        <?php
        if (array_key_exists('is_gallery', $this->config)) {
            jvbRenderGallery();
        }
    }
    protected function renderTemplates(): void
    {
        if ($this->getContent()) {
            foreach ($this->getContent() as $content) {
                echo $this->getDefaultTemplate($content);
            }
            $this->taxonomies = array_unique($taxonomies);
        }
        echo '<template class="feedTerm"><button class="remove-term">'.jvbIcon(jvbDefaultIcon()).'<span></span>'.jvbIcon('x').'</button></template>';
        echo '<template class="emptyState">'.apply_filters('jvbFeedEmptyState', '<div class="empty-state">
                <h3>'.jvbIcon($this->getIcon()).'NOTHING HERE'.jvbIcon($this->getIcon()).'</h3>
    protected function renderFiltersAndControls():string
    {
        return sprintf(
            '<details class="all-filters col top left" data-ignore open>
            <summary>Filters %s</summary>
            %s%s%s%s%s
            </details>
            <button data-action="clear-filters" data-ignore hidden>%s<span>Clear Filters</span></span></button>',
            $this->renderContentLabels(),
            $this->renderSearch(),
            $this->renderContent(),
            $this->renderFilters(),
            $this->renderOrderControls(),
            $this->renderViewControls(),
            jvbIcon('x')
        );
    }
        protected function renderSearch():string
        {
            return sprintf(
                '<div class="search row left nowrap">
                <span class="label">Search:</span>
                %s
                </div>',
                jvbSearch()
            );
        }
        protected function renderContentLabels():string
        {
            $inside = '';
            if (count($this->content) === 1) {
                return '';
            }
            foreach ($this->content as $i => $type) {
                $active = $i === 0 ? ' class="active"' : '';
                $inside .= sprintf(
                    '<li id="filter-%s"%s>%s</li>',
                    $type,
                    $active,
                    Registrar::getInstance($type)->getPlural()??''
                );
            }
            return sprintf(
                '<ul class="filter-label">%s</ul>',
                $inside
            );
        }
        protected function renderContent():string
        {
            $favourites = '';
            if (Site::has('favourites')) {
                $favourites = sprintf(
                    '<input type="checkbox" id="favourites" class="btn" name="favourites" value="on" data-filter="favourites">
                    <label for="favourites" title="Show Favourites">%s%s<span class="screen-reader-text">Show Favourites Only</span></label>',
                    jvbIcon('heart'),
                    jvbIcon('heart', ['style' => 'fill'])
                );
            }
            if (count($this->content) === 1) {
                return empty($favourites)
                    ? sprintf(
                        '<input type="hidden" name="content" value="%s">',
                        implode(',', $this->content)
                    )
                    : sprintf(
                    '<div class="content row right">
                        <input type="hidden" name="content" value="%s">
                        %s
                        </div>',
                    implode(',', $this->content),
                    $favourites
                );
            }
            $i = 0;
            $content = implode('', array_map(function($type) use (&$i) {
                $i++;
                $registrar = Registrar::getInstance($type);
                return sprintf(
                    '<input type="radio"
                    id="filter-%s"
                    class="btn"
                    name="content"
                    data-filter="content"
                    value="%s"%s>
                    <label for="filter-%s" title="Show %s">%s<span class="screen-reader-text">%s</span></label>',
                    $type,
                    $type,
                    $i === 0 ? ' checked' : '',
                    $type,
                    $registrar->getPlural(),
                    jvbIcon($registrar->getIcon()),
                    $registrar->getPlural()
                );
            }, $this->content));
            return sprintf(
                '<div class="content row left nowrap"><span class="label">Showing:</span>
                    %s%s
                </div>',
                $content,
                $favourites
            );
        }
        protected function renderFilters():string
        {
            if (empty ($this->taxonomies)) {
                return '';
            }
            $inside = implode('', array_filter(array_map(function($tax) {
                $registrar = Registrar::getInstance($tax);
                if (!$registrar) return '';
                $current = BASE.$this->content[0];
                $contentFor = $registrar->registrar->for;
                $hidden = in_array($current, $contentFor) ? '' : ' hidden';
                $selector = new TaxonomySelector(
                    'feed-'.$tax,
                    $tax,
                    [
                        'icon'  => $registrar->getIcon(),
                        'update'=> '.selected-items-section .selected-items',
                        'types' => $contentFor,
                        'autocomplete'  => false,
                        'hidden'    => $hidden,
                        'output'    => 'minimal',
                        'search'    => true
                    ]
                );
                return $selector->render();
            }, $this->taxonomies)));
            return sprintf(
                '<div class="taxonomies row left">
                <div class="row top nowrap">
                <span class="label">Filter By:</span>
                <div class="row left">%s</div>
                </div>
                <div class="selected-items-section">
                    <div class="selected-items row left"></div>
                    <div class="filter-actions row">
                        %s
                        <button type="button" class="clear-filters" hidden>
                            %s
                            <span>Clear All Filters</span>
                        </button>
                    </div>
                </div>
            </div>',
                $inside,
                str_replace('class="toggle-text"', 'class="toggle-text" hidden', jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match'])),
                jvbIcon('x')
            );
        }
        protected function renderOrderControls():string
        {
            $orderby = [
                [
                    'slug'  => 'title',
                    'icon'  => 'alphabetical',
                    'label' => 'Name'
                ],
                [
                    'slug'  => 'date',
                    'icon'  => 'calendar',
                    'label' => 'Date Created',
                ],
                [
                    'slug'  => 'date_modified',
                    'icon'  => 'clock-clockwise',
                    'label' => 'Date Modified'
                ]
            ];
            $custom = $this->getCustomOrdering();
            $orderby = $orderby + $custom;
            $orderby[] = [
                'slug'  => 'random',
                'icon'  => 'shuffle',
                'label' => 'Randomly'
            ];
            $custom = implode(',', array_map(function($ord) {
                return $ord['slug'];
            }, $custom));
            $i = 0;
            $orderby = sprintf(
                '<div class="orderby row left">
                <span class="label">Order by:</span>%s
                </div>',
                implode('', array_map(function ($by) use (&$i){
                    $checked = $i === 0 ? ' checked' : '';
                    $i++;
                    return sprintf(
                        '<input type="radio" id="order-%s" class="btn" name="orderby" value="%s" data-filter="orderby"%s%s>
                            <label for="order-%s" title="Order %s">%s<span class="label">%s</span></label>',
                        $by['slug'],
                        $by['slug'],
                        $checked,
                        empty($by['for']??[]) ? '' : ' data-for="'.implode($by['for']).'"',
                        $by['slug'],
                        $by['slug'] === 'random' ? $by['label'] : 'by '.$by['label'],
                        jvbIcon($by['icon']),
                        $by['label']
                    );
                }, $orderby))
            );
            $order = [
                [
                    'slug'  => 'desc',
                    'icon'  => 'sort-descending',
                    'label' => 'Descending (A-Z, 1-10)'
                ],
                [
                    'slug'  => 'asc',
                    'icon'  => 'sort-ascending',
                    'label' => 'Ascending (Z-A, 10-1)'
                ]
            ];
            $i = 0;
            $order = sprintf(
                '<div class="order-direction row left" data-for-order="date,date_modified,title%s">
                <span class="label">Order:</span>
                %s
                </div>',
                $custom === '' ? '' : ','.$custom,
                implode('', array_map(function ($ord) use (&$i) {
                    $checked = $i=== 0 ? ' checked' : '';
                    $i++;
                    return sprintf(
                        '<input type="radio" id="order-%s" class="btn" name="order" value="%s" data-filter="order"%s>
                        <label for="order-%s" title="Sort %s">
                            %s
                            <span class="label">%s</span>
                        </label>',
                        $ord['slug'],
                        $ord['slug'],
                        $checked,
                        $ord['slug'],
                        $ord['label'],
                        jvbIcon($ord['icon']),
                        $ord['label']
                    );
                }, $order))
            );
            return sprintf(
                '<div class="ordering row left nowrap">%s%s</div>',
                $orderby,
                $order
            );
        }
            protected function getCustomOrdering():array
            {
                $custom = [];
                foreach ($this->content as $content) {
                    $registrar = Registrar::getInstance($content);
                    if (!$registrar || empty($registrar->config('feed')->getCustomOrder())) {
                        continue;
                    }
                    $custom = array_merge_recursive($custom, $registrar->config('feed')->getCustomOrder());
                }
                return $custom;
            }
        protected function renderViewControls():string
        {
            $views = [
                'grid'  => ['slug' => 'grid', 'icon' => 'squares-four', 'label' => 'Grid View'],
                'list'  => ['slug' => 'list', 'icon' => 'rows', 'label' => 'List View']
            ];
            $i = 0;
            return sprintf(
                '<div class="view row left nowrap"><span class="label">Switch View:</span>%s</div>',
                implode('', array_map(function ($view) use (&$i) {
                    $checked = $i === 0 ? ' checked' : '';
                    $i++;
                    return sprintf(
                        '<input type="radio"
                        data-view="%s" value="%s" class="btn" name="view" id="view-%s"%s>
                        <label for="view-%s" title="%s">
                        %s<span class="label">%s</span>
</label>',
                        $view['slug'],
                        $view['slug'],
                        $view['slug'],
                        $checked,
                        $view['slug'],
                        $view['label'],
                        jvbIcon($view['icon']),
                        $view['label'],
                    );
                }, $views))
            );
        }
    protected function renderGrid():string
    {
        $placeholders = '';
        $total = count($this->content) - 1;
        $icons = [];
        $icon = apply_filters('jvbFeedPlaceholder', '');
        for ($i=1; $i<=36; $i++) {
            if (empty($icon)) {
                $rand = $total === 0 ? $total : rand(0, $total);
                $content = $this->content[$rand];
                if (!in_array($content, $icons)) {
                    $icons[$content] = Registrar::getInstance($content)->getIcon();
                }
                $icon = jvbIcon($icons[$content]);
            }
            $placeholders .= sprintf(
                '<div class="placeholder">%s</div>',
                $icon
            );
        }
        return sprintf(
            '<div class="item-grid">%s</div>',
            $placeholders
        );
    }
    protected function renderLoader():string
    {
        return sprintf(
            '<button type="button" class="load-more">%s<span>Show Me More</span>%s</button>
            %s%s',
            jvbIcon('arrow-elbow-left-down'),
            jvbIcon('arrow-elbow-right-down'),
            jvbLoadingScreen(),
            $this->isGallery ? jvbRenderGallery(false) : '',
        );
    }
    protected function renderTemplates():string
    {
        $templates = [];
        foreach ($this->content as $content) {
            $templates[] =  $this->getDefaultTemplate($content);
        }
        $templates[] = sprintf(
            '<template class="feedTerm"><button class="remove-term">%s<span></span>%s</button></template>',
            jvbIcon(jvbDefaultIcon()),
            jvbIcon('x')
        );
        $defaultEmptyState = sprintf(
            '<div class="empty-state">
                <h3>%sNOTHING HERE%s</h3>
                <p>Try tweaking those filters a bit.</p>
            </div>', $this->config). '</template>';
        echo '<template class="placeholderTemplate"><div class="placeholder">'.apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon())).'</div></template>';
            </div>',
            jvbIcon(jvbDefaultIcon()),
            jvbIcon(jvbDefaultIcon()),
        );
        $emptyState = apply_filters('jvbFeedEmptyState', $defaultEmptyState, $this->content);
        $templates[] = sprintf(
            '<template class="emptyState">%s</template>',
            $emptyState
        );
        $placeholder = apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon()));
        $templates[] = sprintf(
            '<template class="placeholderTemplate"><div class="placeholder">%s</div></template>',
            $placeholder
        );
        return implode('', $templates);
    }
    protected function getFavouritesButton(string $content):string
    protected function renderActions():string
    {
        return sprintf(
            '<button data-action="refresh" data-ignore>%s<span>Hard Refresh</span></span></button>',
            jvbIcon('arrows-clockwise')
        );
    }
    public static function getFavouritesButton(string $content):string
    {
        $registrar = Registrar::getInstance($content);
        if (!$registrar || !Site::has('favourites') || !$registrar->hasFeature('favouritable')) {
@@ -424,7 +527,7 @@
            .jvbIcon('heart', ['style'=>'fill']).'
        </button>';
    }
    protected function getUpvotesButton(string $content):string
    public static function getUpvotesButton(string $content):string
    {
        $registrar = Registrar::getInstance($content);
        if (!Site::has('karma') || !$registrar || !$registrar->hasFeature('karma')){
@@ -444,39 +547,66 @@
    }
    protected function getDefaultTemplate(string $content): string
    {
        $config = Registrar::getInstance($content)->getConfig('feed');
        $allFields = Registrar::getFieldsFor($content);
        $images = $config['images']??['post_thumbnail'];
        $fields = $config['fields']??['post_title','post_date','post_excerpt'];
        $fields = array_filter($fields, function($field) use($images) {
            return !in_array($field, $images);
        });
        $fields = array_filter($allFields, function($field) use($fields) {
            return in_array($field, $fields);
        }, ARRAY_FILTER_USE_KEY);
        $template = '<div class="feed item col '.$content.'">'.$this->getFavouritesButton($content).$this->getUpvotesButton($content);
        $template = apply_filters('jvbFeedItem', '', $content);
        if (empty($template)) {
            $config = Registrar::getInstance($content)->getConfig('feed');
            $allFields = Registrar::getFieldsFor($content);
            $images = $config['images']??['post_thumbnail'];
            $fields = $config['fields']??['post_title','post_date','post_excerpt'];
            $fields = array_filter($fields, function($field) use($images) {
                return !in_array($field, $images);
            });
            $fields = array_filter($allFields, function($field) use($fields) {
                return in_array($field, $fields);
            }, ARRAY_FILTER_USE_KEY);
        //Add all defined images, but allow for filtering
        $imageTemplate = '<a>';
        foreach ($images as $image) {
            $imageTemplate .= '<img data-field="'.$image.'" width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">';
            $template = sprintf(
                '<div class="feed item col %s">%s%s',
                $content,
                self::getFavouritesButton($content),
                self::getUpvotesButton($content)
            );
            //Add all defined images, but allow for filtering
            $imageTemplate = '<a>';
            foreach ($images as $image) {
                $imageTemplate .= sprintf(
                    '<img data-field="%s" width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">',
                    $image
                );
            }
            $imageTemplate .= '</a>';
            $template .= sprintf(
                '<div class="images">%s</div>',
                apply_filters('jvbFeedImages', $imageTemplate, $content, $images)
            );
            //Output default fields, but allow for filtering
            $template .= sprintf(
                '<details>
        <summary>%s</summary>',
                apply_filters('jvbFeedItemSummary', jvbIcon('dots-three'), $content)
            );
            $fieldsTemplate = '';
            foreach ($fields as $fieldName => $config) {
                $fieldsTemplate .= apply_filters('jvbFeedItemField', $this->defaultFieldTemplate($config['type'], $fieldName), $content, $fieldName, $config['type']);
            }
            $template .= sprintf(
                '<div class="item-info">%s</div>',
                apply_filters('jvbFeedItemFields', $fieldsTemplate, $content, $fields)
            );
            $template .= '</details></div>';
        }
        $imageTemplate .= '</a>';
        $template .= '<div class="images">'.apply_filters('jvbFeedImages', $imageTemplate, $content, $images).'</div>';
        //Output default fields, but allow for filtering
        $template .= '<details>
        <summary>'.apply_filters('jvbFeedItemSummary', jvbIcon('dots-three'), $content).'</summary>';
        $fieldsTemplate = '';
        foreach ($fields as $fieldName => $config) {
            $fieldsTemplate .= apply_filters('jvbFeedItemField', $this->defaultFieldTemplate($config['type'], $fieldName), $content, $fieldName, $config['type']);
        }
        $template .= '<div class="item-info">'.apply_filters('jvbFeedItemFields', $fieldsTemplate, $content, $fields).'</div>';
        $template .= '</details></div>';
        return '<template class="feedItem'.ucfirst($content).'">'.apply_filters('jvbFeedItem', $template, $content).'</template>';
        return sprintf(
            '<template class="feedItem%s">%s</template>',
            ucfirst($content),
            $template
        );
    }
    protected function defaultFieldTemplate(string $fieldType, string $fieldName):string
@@ -490,17 +620,11 @@
                return '<time'.$data.'></time>';
        }
        return match($fieldType) {
            'date','datetime','time' => '<time'.$data.'></time>',
            'upload'    => '<img'.$data.' width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">',
            'taxonomy'  => '<ul'.$data.'><li><a><i></i></a></li></ul>',
            default =>  '<p'.$data.'></p>',
        };
    }
    /**
     * Get feed content using Features instead of get_option
     * Returns array of slug => config for types that show in feed
     */
    public function getFeedContent(): array
    {
        return JVB()->routes('feed')->getFeedTypesConfig();
    }
}
inc/blocks/FeedBlockOld.php
New file
@@ -0,0 +1,507 @@
<?php
namespace JVBase\blocks;
use JVBase\managers\Cache;
use JVBase\registrar\Registrar;
use JVBase\base\Site;
use JVBase\forms\TaxonomySelector;
use JVBase\ui\CRUDSkeleton;
use WP_Block;
if (!defined('ABSPATH')) {
    exit;
}
class FeedBlockOld
{
    protected Cache $cache;
    protected array $config;
    protected string $path = JVB_DIR.'/build/feed';
    public function __construct()
    {
        // Initialize cache with connections
        $this->cache = Cache::for('feed_block', WEEK_IN_SECONDS);
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        add_action('init', [$this, 'registerBlock']);
    }
    public function registerBlock()
    {
        register_block_type($this->path, [
            'render_callback' => [$this, 'render']
        ]);
    }
    protected function buildParams(array $attributes): array
    {
        if (!jvbCheck('inheritQuery', $attributes)) {
            return [
                'title' => $attributes['title'],
                'content' => $attributes['contentTypes'],
                'taxonomies' => $this->getTaxonomies($attributes['contentTypes'])
            ];
        }
        $config = [
            'is_gallery'    => false,
            'content'       => '',
            'taxonomies'    => []
        ];
        $type = get_queried_object();
        if (is_post_type_archive() || is_singular()) {
            $content = is_singular() ? jvbNoBase($type->post_type) : jvbNoBase($type->name);
            $registrar = Registrar::getInstance($content)??false;
            if ($registrar) {
                $config = array_merge($config, $registrar->getConfig('feed'));
            } else {
                $config['content'] = $content;
                $config['icon'] = jvbDefaultIcon();
            }
            if (is_singular()) {
                $config['source'] = $type->ID;
            }
            $config['taxonomies'] = $this->getTaxonomies([$content]);
        } elseif (is_tax()) {
            $content = jvbNoBase($type->taxonomy);
            $registrar = Registrar::getInstance($content)??false;
            if ($registrar) {
                $config['content'] = $registrar->registrar->for;
                $config['context'] = $content;
                $config['taxonomies'] = $this->getTaxonomies($registrar->registrar->for);
                if (!empty($registrar->getConfig('feed'))){
                    $config = array_merge($config, $registrar->getConfig('feed'));
                }
            }
            $config['source'] = $type->term_id;
        }
        if (!is_array($config['content'])) {
            $config['content'] = [$config['content']];
        }
        return $config;
    }
    /**
     * Get taxonomies for given content types
     */
    protected function getTaxonomies(array $content): array
    {
        $taxonomies = [];
        foreach ($content as $contentType) {
            $registrar = Registrar::getInstance($contentType);
            if (!$registrar) {
                continue;
            }
            $contentTaxonomies = $registrar->registrar->taxonomies;
            $contentTaxonomies = array_filter($contentTaxonomies, function($taxonomy) {
                return Registrar::getInstance($taxonomy)?->hasFeature('show_feed');
            });
            $taxonomies = array_merge($taxonomies, $contentTaxonomies);
        }
        return array_unique($taxonomies);
    }
    public function render(array $attributes, string $content, WP_Block $block)
    {
        $this->config = $this->buildParams($attributes);
        return $this->cache->remember(
            $this->cache->generateKey($this->config),
            function() {
                return $this->renderBlock();
            }
        );
    }
    protected function getContext():string|bool
    {
        return array_key_exists('context', $this->config)?$this->config['context']:false;
    }
    protected function getIDS():array|bool
    {
        return (array_key_exists('ids', $this->config) && !empty($this->config['ids'])) ? $this->config['ids'] : false;
    }
    protected function getClasses():array|bool
    {
        return array_key_exists('classes', $this->config) && !empty($this->config['classes']) ? $this->config['classes'] : false;
    }
    protected function getSource():string|bool
    {
        return array_key_exists('source', $this->config) ? $this->config['source'] : false;
    }
    protected function getIcon():string|bool
    {
        return array_key_exists('icon', $this->config) ? $this->config['icon'] : false;
    }
    protected function isGallery():bool
    {
        return (array_key_exists('is_gallery', $this->config) && $this->config['is_gallery']);
    }
    protected function getContent():array|bool
    {
        return (array_key_exists('content', $this->config) && !empty ($this->config['content'])) ? $this->config['content'] : false;
    }
    protected function renderBlock(): string
    {
        if (is_post_type_archive(BASE.'directory')) {
            return '';
        }
        $ids = ($this->getIDS()) ? ' id="'.implode(' ',$this->getIDS()).'"' : '';
        $classes = ($this->getClasses()) ? ' class="'.implode(' ',$this->getClasses()).'"' : '';
        $source = ($this->getSource()) ? ' data-source="'.$this->getSource().'"' : '';
        $context = ($this->getContext()) ? ' data-context="'.$this->getContext().'"' : '';
        $icons = ($this->getIcon()) ? ' data-icon="'.$this->getIcon().'"' : ' data-icon="'.jvbLogoIcon().'"';
        $gallery = $this->isGallery() ? ' data-gallery' : '';
        $content = ($this->getContent()) ? ' data-content="'.implode(',',$this->getContent()).'"' : '';
        ob_start();
        ?>
        <section<?= $ids.$classes ?> class="feed-block"<?= $content.$source.$context.$gallery.$icons ?>>
            <?php
            $this->renderFilters();
            $this->renderGrid();
            $this->renderLoader();
            $this->renderTemplates();
            echo TaxonomySelector::outputSelectorModal();
            ?>
        </section>
        <footer><button data-action="refresh" data-ignore><?=jvbIcon('arrows-clockwise')?><span>Hard Refresh</span></span></button></footer>
        <?php
        return ob_get_clean();
    }
    protected function renderFilters(): void
    {
        if (empty($this->config)) {
            return;
        }
        $feedContent = $this->getFeedContent();
        $hasMany = count($this->getContent()) > 1;
        ?>
        <details class="all-filters col top" data-ignore>
            <summary>Filters</summary>
<!--            <form class="filters" data-save="feed---><?php //=$this->getContext()?><!--">-->
                <?= CRUDSkeleton::searchFilter(); ?>
                <?= CRUDSkeleton::viewFilter(['grid', 'list']); ?>
                <?php if ($hasMany) {
                //If we have multiple content, only show the content first
                        $labels = [];
                        foreach ($this->getContent() as $i => $type) :
                            $checked = $i === 0 ? ' checked' : '';
                            $label = $feedContent[$type]['plural'] ?? ucfirst($type);
                            ?>
                            <input type="radio"
                                   id="filter-<?= esc_attr($type) ?>"
                                   class="btn"
                                   name="content"
                                   data-filter="content"
                                   value="<?= esc_attr($type) ?>"
                                <?= $checked ?>>
                            <label for="filter-<?= esc_attr($type) ?>" title="Show <?= $label ?>" class="row">
                                <?= jvbIcon($feedContent[$type]['icon']) ?>
                                <span class="screen-reader-text"><?= $label ?></span>
                            </label>
                            <?php
                            $labels['filter-'.$type] = $label;
                        endforeach;
                        ?>
                        <ul class="filter-label">
                            <?php
                            $i = 0;
                            foreach ($labels as $id => $label) {
                                $active = $i === 0 ? ' class="active"' : '';
                                ?>
                                <li id="<?= $id ?>"<?= $active ?>>
                                    <?= $label ?>
                                </li>
                                <?php
                                $i++;
                            }
                            ?>
                        </ul>
                        <?php } ?>
                        <?php if (Site::has('favourites') && is_user_logged_in()) : ?>
                            <input type="checkbox" id="favourites" class="btn" name="favourites" value="on"
                                   data-filter="favourites">
                            <label for="favourites" title="Show Favourites" class="row">
                                <?= jvbIcon('heart').jvbIcon('heart', ['style' => 'fill']) ?>
                                <span class="screen-reader-text">Show Favourites Only</span>
                            </label>
                        <?php endif; ?>
                        <?php if ($hasMany) { ?>
                    <?php }
                    if (!empty ($this->config['taxonomies'])) {
                        ?>
                        <div class="filters">
                            <div class="filter-group row left">
                                <span class="label">FILTER BY:</span>
                                <?php
                                foreach ($this->config['taxonomies'] as $tax) :
                                    $registrar = Registrar::getInstance($tax)??false;
                                    if (!$registrar) continue;
                                    $contentForTax = $registrar->registrar->for;
                                    $hidden = empty($contentForTax) ? ' hidden' : '';
                                    $taxSelector = new TaxonomySelector(
                                        'feed-'.$tax,
                                        $tax,
                                        [
                                            'icon'          => $registrar->getIcon()??jvbLogoIcon(),
                                            'update'        => '.selected-items-section .selected-items',
                                            'types'         => $contentForTax,
                                            'autocomplete'  => false,
                                            'hidden'        => $hidden,
                                            'output'        => 'minimal'
                                        ]
                                    );
                                    echo $taxSelector->render();
                                endforeach;
                                ?>
                            </div>
                            <div class="selected-items-section">
                                <div class="selected-items row"></div>
                                <div class="filter-actions row">
                                    <?= str_replace('class="toggle-text"', 'class="toggle-text" hidden', jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match'])) ?>
                                    <button type="button" class="clear-filters row" hidden>
                                        <?= jvbIcon('x') ?>
                                        Clear All Filters
                                    </button>
                                </div>
                            </div>
                        </div>
                    <?php } ?>
                    <div class="row x-btw nowrap w-full">
                        <div class="orderby filter-group row left w-full">
                            <span class="label">ORDER BY:</span>
                            <?php
                            //TODO: Get content types that can be sorted alphabetically
                            ?>
                            <input type="radio" id="order-title" class="btn" name="orderby" value="title" data-filter="orderby" hidden>
                            <label for="order-title" title="Order by Name" class="row">
                                <?= jvbIcon('alphabetical') ?>
                                <span class="label">Name</span>
                            </label>
                            <input type="radio" id="order-date" class="btn" name="orderby" value="date" data-filter="orderby" checked>
                            <label for="order-date" title="Order by Date Created" class="row">
                                <?= jvbIcon('calendar', ['title' => 'Date']) ?>
                                <span class="label">Date Created</span>
                            </label>
                            <input type="radio" id="order-modified" class="btn" name="orderby" value="modified" data-filter="orderby">
                            <label for="order-modified" title="Order by Date Modified" class="row">
                                <?= jvbIcon('clock-clockwise') ?>
                                <span class="label">Date Modified</span>
                            </label>
                            <?php
                            $custom = [];
                            foreach ($this->getContent() as $content) {
                                $registrar = Registrar::getInstance($content)??false;
                                if ($registrar && !empty($registrar->config('feed')->getCustomOrder())) {
                                    $custom = array_merge_recursive($custom, $registrar->config('feed')->getCustomOrder());
                                }
                            }
                            foreach ($custom as $slug => $conf) {
                                ?>
                                <input type="radio" id="order-<?=$slug?>" class="btn" name="orderby" value="<?=$slug?>" data-for="<?=$conf['for']?>" data-filter="orderby">
                                <label for="order-<?=$slug?>" title="<?= $conf['label']?>" class="row">
                                    <?= jvbIcon($conf['icon']) ?>
                                    <span class="label"><?=$conf['label']?></span>
                                </label>
                                <?php
                            }
                            $custom = implode(',', array_keys($custom));
                            ?>
                            <input type="radio" id="order-random" class="btn" name="orderby" value="random" data-filter="orderby">
                            <label for="order-random" title="Random Order" class="row">
                                <?= jvbIcon('shuffle') ?>
                                <span class="label">Random</span>
                            </label>
                        </div>
                        <div class="order-direction filter-group row left w-full" data-for-order="date,modified,title<?= $custom === '' ? '' : ','.$custom?>">
                            <span class="label">ORDER:</span>
                            <input type="radio" id="order-desc" class="btn" name="order" value="desc" data-filter="order" checked>
                            <label for="order-desc" title="Sort Descending (A-Z, 1-10)" class="row">
                                <?= jvbIcon('sort-descending') ?>
                                <span class="label" >DESC (A-Z)</span>
                            </label>
                            <input type="radio" id="order-asc" class="btn" name="order" value="asc" data-filter="order">
                            <label for="order-asc" title="Sort Ascending (Z-A, 10-1)" class="row">
                                <?= jvbIcon('sort-ascending') ?>
                                <span class="label" >ASC (Z-A)</span>
                            </label>
                        </div>
                    </div>
<!--            </form>-->
        </details>
        <?php
    }
    protected function renderGrid(): void
    {
        ?>
        <div class="item-grid">
            <?php
            $total = count($this->getContent()) - 1;
            for ($i = 1; $i <= 36; $i++) {
                $rand = rand(0, $total);
                $config = Registrar::getInstance($this->getContent()[$rand]);
                $icon = jvbIcon($config->getIcon??jvbLogoIcon());
                ?>
                <div class="placeholder"><?=apply_filters('jvbFeedPlaceholder', $icon) ?></div>
                <?php
            }
            ?>
        </div>
        <?php
    }
    protected function renderLoader(): void
    {
        ?>
        <button type="button" class="load-more">
            <?= jvbIcon('arrow-elbow-left-down') ?>
            Show Me More
            <?= jvbIcon('arrow-elbow-right-down') ?>
        </button>
        <?= jvbLoadingScreen() ?>
        <?php
        if (array_key_exists('is_gallery', $this->config)) {
            jvbRenderGallery();
        }
    }
    protected function renderTemplates(): void
    {
        if ($this->getContent()) {
            foreach ($this->getContent() as $content) {
                echo $this->getDefaultTemplate($content);
            }
        }
        echo '<template class="feedTerm"><button class="remove-term">'.jvbIcon(jvbDefaultIcon()).'<span></span>'.jvbIcon('x').'</button></template>';
        echo '<template class="emptyState">'.apply_filters('jvbFeedEmptyState', '<div class="empty-state">
                <h3>'.jvbIcon($this->getIcon()).'NOTHING HERE'.jvbIcon($this->getIcon()).'</h3>
                <p>Try tweaking those filters a bit.</p>
            </div>', $this->config). '</template>';
        echo '<template class="placeholderTemplate"><div class="placeholder">'.apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon())).'</div></template>';
    }
    protected function getFavouritesButton(string $content):string
    {
        $registrar = Registrar::getInstance($content);
        if (!$registrar || !Site::has('favourites') || !$registrar->hasFeature('favouritable')) {
            return '';
        }
        return '<button class="favourite" type="button" title="Add to favourites" data-action="favourite">
            '.jvbIcon('heart')
            .jvbIcon('heart', ['style'=>'fill']).'
        </button>';
    }
    protected function getUpvotesButton(string $content):string
    {
        $registrar = Registrar::getInstance($content);
        if (!Site::has('karma') || !$registrar || !$registrar->hasFeature('karma')){
            return '';
        }
        return '<div class="karma row">
            <button type="button" class="vote" data-action="upvote">
                '.jvbIcon('arrow-fat-up')
                .jvbIcon('arrow-fat-up', ['style'=>'fill']).
            '</button>
            <button type="button" class="vote" data-action="downvote">
                '.jvbIcon('arrow-fat-down')
                .jvbIcon('arrow-fat-down', ['style'=>'fill']).
            '</button>
            <span class="score"></span>
        </div>';
    }
    protected function getDefaultTemplate(string $content): string
    {
        $config = Registrar::getInstance($content)->getConfig('feed');
        $allFields = Registrar::getFieldsFor($content);
        $images = $config['images']??['post_thumbnail'];
        $fields = $config['fields']??['post_title','post_date','post_excerpt'];
        $fields = array_filter($fields, function($field) use($images) {
            return !in_array($field, $images);
        });
        $fields = array_filter($allFields, function($field) use($fields) {
            return in_array($field, $fields);
        }, ARRAY_FILTER_USE_KEY);
        $template = '<div class="feed item col '.$content.'">'.$this->getFavouritesButton($content).$this->getUpvotesButton($content);
        //Add all defined images, but allow for filtering
        $imageTemplate = '<a>';
        foreach ($images as $image) {
            $imageTemplate .= '<img data-field="'.$image.'" width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">';
        }
        $imageTemplate .= '</a>';
        $template .= '<div class="images">'.apply_filters('jvbFeedImages', $imageTemplate, $content, $images).'</div>';
        //Output default fields, but allow for filtering
        $template .= '<details>
        <summary>'.apply_filters('jvbFeedItemSummary', jvbIcon('dots-three'), $content).'</summary>';
        $fieldsTemplate = '';
        foreach ($fields as $fieldName => $config) {
            $fieldsTemplate .= apply_filters('jvbFeedItemField', $this->defaultFieldTemplate($config['type'], $fieldName), $content, $fieldName, $config['type']);
        }
        $template .= '<div class="item-info">'.apply_filters('jvbFeedItemFields', $fieldsTemplate, $content, $fields).'</div>';
        $template .= '</details></div>';
        return '<template class="feedItem'.ucfirst($content).'">'.apply_filters('jvbFeedItem', $template, $content).'</template>';
    }
    protected function defaultFieldTemplate(string $fieldType, string $fieldName):string
    {
        $data = ' data-field="'.$fieldName.'"';
        switch ($fieldName) {
            case 'post_title':
                return '<h3'.$data.'></h3>';
            case 'post_date':
            case 'post_modified':
                return '<time'.$data.'></time>';
        }
        return match($fieldType) {
            'upload'    => '<img'.$data.' width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">',
            'taxonomy'  => '<ul'.$data.'><li><a><i></i></a></li></ul>',
            default =>  '<p'.$data.'></p>',
        };
    }
    /**
     * Get feed content using Features instead of get_option
     * Returns array of slug => config for types that show in feed
     */
    public function getFeedContent(): array
    {
        return JVB()->routes('feed')->getFeedTypesConfig();
    }
}
inc/blocks/SummaryBlock.php
@@ -173,7 +173,7 @@
        }
        $open = $this->isOpen ? ' open' : '';
        ?>
        <details class="info"<?=$open?>>
        <section><details class="info"<?=$open?>>
            <summary class="row x-btw"><?= $this->detailsTitle ?></summary>
            <?php
            foreach ($this->details as $key => $details) {
@@ -187,7 +187,7 @@
            <?php
            }
            ?>
        </details>
        </details></section>
        <?php
    }
inc/forms/TaxonomySelector.php
@@ -57,6 +57,7 @@
            'update'    => true,   // Whether to update on close
        ]);
        $this->plural = $registrar->getPlural();
        $this->singular = $registrar->getSingular();
    }
@@ -101,7 +102,7 @@
        ?>
        <dialog id="jvb-selector" aria-labelledby="modal-title" aria-modal="true">
            <div class="wrap col">
                <header class="modal-header">
                <header class="row">
                    <h3 id="modal-title">Select Taxonomy</h3>
                </header>
@@ -143,7 +144,7 @@
                <!-- Search section -->
                <div class="search-wrapper">
                    <div class="search-bar">
                        <?= jvbSearch('Search terms', 'search-terms') ?>
                        <?= str_replace('class="search-container', 'class="open search-container', jvbSearch('Search terms', 'search-terms')) ?>
                    </div>
                </div>
@@ -195,7 +196,7 @@
        <template class="selectedTerm">
            <div class="selected-item row">
                <span class="item-name"></span>
                <button type="button" class="remove-term row"><?=jvbIcon('x')?></button>
                <button type="button" class="remove-term"><?=jvbIcon('x')?></button>
            </div>
        </template>
        <template class="termBreadcrumb">
@@ -268,7 +269,7 @@
                $selectedItems = ob_get_clean();
            endif;
            ?>
            <div class="selected-items row" role="region" aria-label="Selected <?= esc_attr($this->plural) ?>"><?=$selectedItems?></div>
            <div class="selected-items row left" role="region" aria-label="Selected <?= esc_attr($this->plural) ?>"><?=$selectedItems?></div>
            <?= $extra ?>
        </div>
        <?php
@@ -277,12 +278,14 @@
    protected function renderTaxonomyToggle(array $selected = [], string $extra = ''): string
    {
        return sprintf(
            '<button type="button" data-icon="%s" data-filter="taxonomy" data-taxonomy="%s" data-type="selector" data-single="%s" data-plural="%s" title="Filter by %s">%s<span class="label">%s</span></button>',
            '<button type="button" data-icon="%s" data-filter="taxonomy" data-taxonomy="%s" data-type="selector" data-single="%s" data-plural="%s" %stitle="Filter by %s">%s<span class="label">%s</span></button>',
            $this->registrar->getIcon(),
            $this->name,
            $this->singular,
            $this->plural,
            $this->buildDataAttributes([]),
            $this->singular,
            jvbIcon($this->registrar->getIcon()),
            $this->singular
inc/helpers/media.php
@@ -4,21 +4,21 @@
    exit;
}
function jvbRenderGallery()
function jvbRenderGallery(bool $output = true):string
{
    ?>
    <dialog class="gallery" aria-modal="true" aria-label="Image Gallery">
    $gallery = sprintf(
        '<dialog class="gallery" aria-modal="true" aria-label="Image Gallery">
        <div class="wrap col">
            <p class="hint">Pinch to Zoom</p>
            <div class="controls row">
                <button type="button" class="cancel" title="Close Gallery" aria-label="Close Gallery"> <?= jvbIcon('x') ?></button>
                <button type="button" class="cancel" title="Close Gallery" aria-label="Close Gallery">%s</button>
                <button class="nav prev" title="Previous image" aria-label="Previous image">
                    <?= jvbIcon('caret-left') ?>
                    %s
                </button>
                <button class="nav next" title="Next Image" aria-label="Next image">
                    <?= jvbIcon('caret-right') ?>
                    %s
                </button>
            </div>
            <div class="content row">
@@ -34,7 +34,15 @@
            <div class="favourite"></div>
            <div class="counter"><span id="gallery-index">1</span> / <span class="total"></span></div>
        </div>
    </dialog>
    <?php
    </dialog>',
        jvbIcon('x'),
        jvbIcon('caret-left'),
        jvbIcon('caret-right')
    );
    if ($output) {
        echo $gallery;
    }
    return $gallery;
}
inc/helpers/terms.php
@@ -76,7 +76,7 @@
    $icon = ($icon && $registrar) ? $registrar->getIcon() : '';
    $icon = ($icon === '') ? '' : jvbIcon($icon);
    $title = $registrar ? '<li class="title">'.$icon.$registrar->getSingular().'</li>' : '';
    return (!empty($out)) ? '<ul class="term-list '.jvbNoBase($tax).'">'.$title.implode('',$out).'</ul>' : '';
    return (!empty($out)) ? '<ul class="term-list row left '.jvbNoBase($tax).'">'.$title.implode('',$out).'</ul>' : '';
}
/**
inc/helpers/ui.php
@@ -55,7 +55,7 @@
                ?>
            </nav>
        </div>
        <div class="qitems item-grid col left nowrap">
        <div class="qitems item-grid col top left nowrap">
        </div>
        <div class="queue-actions row x-btw nowrap">
            <button class="dismiss-all">Clear Completed</button>
@@ -302,11 +302,7 @@
            <span>Jump To:</span><?= jvbIcon('plus-square') ?>
        </button>
        <ul>
            <li id="back-to-top">
                <a href="#top" title="Back to Top">
                    <?= jvbIcon('caret-circle-up') ?><span>Back to Top</span>
                </a>
            </li>
            <?php
            foreach ($ids as $slug) {
                ?>
inc/managers/CRUDManager.php
@@ -72,11 +72,11 @@
        $this->initTaxonomies();
        // Statuses
        if ($this->registrar && $this->registrar->hasFeature('is_calendar')) {
        if ($this->registrar->hasFeature('is_calendar')) {
            $this->skeleton->setCalendar();
        }
        if ($this->registrar && $this->registrar->getType() === 'post') {
        if ($this->registrar->getType() === 'post') {
            $this->skeleton->setDefaultStatus();
        } else {
            $this->skeleton->setStatuses([]);
inc/managers/DashboardManager.php
@@ -26,6 +26,9 @@
    public function __construct()
    {
        $this->cache = Cache::for('dashboard', WEEK_IN_SECONDS)->connect('user');
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        add_action('init', [$this, 'registerDashboard']);
        $this->user = wp_get_current_user();
@@ -379,7 +382,7 @@
    }
    protected function renderDashboard(string $page):string
    {
        ob_start();
//      ob_start();
        jvbInlineStyles('nav');
        jvbInlineStyles('dash');
        jvbInlineStyles('forms');
@@ -395,7 +398,8 @@
        );
        $this->renderFooter();
        return ob_get_clean();
//      return ob_get_clean();
return '';
//      $integrationSlugs = array_map(function($name) {
//          return sanitize_title(str_replace('_', '-', $name));
//      }, array_keys(JVB()->getAvailableServices(false)));
@@ -704,9 +708,9 @@
        <?php
        $menu = new Navigation('sidebar');
        $menuClasses = ['col', 'left', 'nowrap'];
        $menuClasses = ['left'];
        $itemClasses = ['col'];
        $menu->addClass('col left')->hasToggle()->defaultMenuClasses($menuClasses);
        $menu->addClass('sidebar left')->hasToggle()->defaultMenuClasses($menuClasses);
        $menu->defaultItemClasses($itemClasses);
        $pages = $this->getUserAllowedPages()?:[];
        //Dashboard
inc/managers/DirectoryManager.php
@@ -38,7 +38,7 @@
        jvb_register_do_once('buildDirectories', [$this, 'activate']);
        add_action('init', [$this, 'registerDirectories']);
        add_filter('render_block', [$this, 'renderBlock'], 998, 3);
        add_filter('pre_render_block', [$this, 'renderBlock'], 20, 3);
    }
    public function registerDirectories():void
@@ -698,7 +698,7 @@
        return $out;
    }
    public function renderBlock(string $content, array $block, WP_Block $instance)
    public function renderBlock(?string $content, array $block, ?WP_Block $instance)
    {
        if (!is_post_type_archive(BASE.'directory') && !is_singular(BASE.'directory')) {
            return $content;
@@ -707,7 +707,6 @@
            return $content;
        }
        error_log('Still working on directory manager...');
        // For archive page
        if (is_post_type_archive(BASE.'directory') && $block['blockName'] === 'core/group') {
            return ($block['attrs']['tagName']??'' === 'main') ? '<main>'.$this->renderArchive().'</main>' : $content;
inc/managers/SEO/BreadcrumbManager.php
@@ -145,12 +145,12 @@
        // Add directory if exists
        if ($registrar && $registrar->hasFeature('directory')) {
        if ($registrar && $registrar->hasFeature('show_directory')) {
            $directory = JVB()->directories();
            if ($directory && !empty($directory->directories($tax))) {
            if ($directory && !empty($directory->directories($tax)??[])) {
                $crumbs[] = [
                    'name' => $directory['title'],
                    'url'  => $directory['url']
                    'name'  => $directory->directories($tax)['title'],
                    'url'   =>$directory->directories($tax)['url']
                ];
            }
        }
inc/managers/SEO/render/SchemaOutput.php
@@ -23,7 +23,7 @@
    {
        $schema = [];
        $registrar = false;
        if (is_front_page()) {
            $schema[] = $this->buildWebsiteSchema(true);
            $test = $this->buildOrganizationSchema();
@@ -74,6 +74,10 @@
        }
        if ($registrar && !empty($registrar->getSEO()->schema()->extra())) {
            $schema = array_merge($schema, $registrar->getSEO()->schema()->extra());
        }
        $breadcrumbs = $this->buildBreadcrumbs();
@@ -103,6 +107,7 @@
                '@graph'        => $schema
            ];
        }
        return $schema;
    }
inc/managers/SEO/render/Traits/ThingSchema.php
@@ -40,7 +40,7 @@
    public function outputSchema():array
    {
        global $wp;
        $current = home_url( add_query_arg( $_GET, $wp->request ) );
        $current = get_home_url(null, add_query_arg( $_GET, $wp->request ) );
        $id = (isset($this->id)) ? $this->id : $current.'/#'.strtolower($this->getTypeName());
        $elements = array_map(
            function ($value) {
inc/managers/ScriptLoader.php
@@ -2,7 +2,7 @@
add_action('init', 'jvbRegisterScripts', 5);
function jvbRegisterScripts() {
    $version = '1.1.55';
    $version = '1.1.6';
    $strategy = [
        'strategy'  => 'defer',
        'in_footer' => true
inc/meta/Form.php
@@ -1719,7 +1719,7 @@
    {
        return sprintf('<dialog id="jvb-selector" aria-labelledby="modal-title" aria-modal="true">
                <div class="wrap col">
                    <header class="modal-header">
                    <header class="row">
                        <h3 id="modal-title">Select Taxonomy</h3>
                    </header>
@@ -1813,13 +1813,13 @@
            <template class="selectedTerm">
                <div class="selected-item row">
                    <span class="item-name"></span>
                    <button type="button" class="remove-term row">%s</button>
                    <button type="button" class="remove-term">%s</button>
                </div>
            </template>
            <template class="termBreadcrumb">
                <button type="button" class="path-level"></button>
            </template>',
            static::search('Search terms', 'search-terms'),
            str_replace('class="search-container', 'class="open search-container', static::search('Search terms', 'search-terms')),
             jvbModalActions(),
            jvbIcon('plus-square'),
            jvbIcon('x')
inc/meta/Meta.php
@@ -124,6 +124,9 @@
            'user'  => get_user_meta($id),
            default => []
        };
        if (!$meta) {
            $meta = [];
        }
        $meta = array_map(fn($value) => maybe_unserialize($value[0]), $meta);
        foreach ($fields as $fieldName => $config) {
@@ -192,8 +195,10 @@
        if (str_contains($name, ':')) {
            return $this->getByPath($name);
        }
        return $this->fields[$name]->get();
        if (!array_key_exists($name, $this->fields)) {
            error_log('[Meta]::get Attempted to get unregistered field: '.$name);
        }
        return $this->fields[$name]->get()??'';
    }
    /**
@@ -312,12 +317,12 @@
                    }
                    break;
                case 'term':
                    $result = wp_update_term($this->ID, $this->slug, $defaults);
                    $termDefaults = array_map(fn($field) => $field->value, $defaults);
                    $result = wp_update_term($this->ID, $this->slug, $termDefaults);
                    break;
                case 'user':
                    $data = array_merge([
                        'ID'    => $this->ID
                    ], $defaults);
                    $userDefaults = array_map(fn($field) => $field->value, $defaults);
                    $data = array_merge(['ID' => $this->ID], $userDefaults);
                    $result = wp_update_user($data);
                    break;
            }
@@ -343,9 +348,10 @@
                    error_log('Problem saving field: '.$field->name.' with value: '.print_r($field->value, true));
                }
            }
            if ($this->type === 'term' && Registrar::getInstance($this->slug)->hasFeature('is_content')) {
                update_term_meta($this->ID, BASE.'date_modified', date('Y-m-d H:i:s'));
            }
            //Now handled directly from Registrar
//          if ($this->type === 'term' && Registrar::getInstance($this->slug)->hasFeature('is_content')) {
//              update_term_meta($this->ID, BASE.'date_modified', date('Y-m-d H:i:s'));
//          }
        }
inc/registrar/Registrar.php
@@ -185,7 +185,7 @@
    /**
     * @var array|string
     */
    protected array|string $can_create = [];
    protected array $can_create = [];
    /**
     * @var array slugs of other user roles this role can manage
     */
@@ -750,19 +750,22 @@
        }, static::$instances);
    }
    public function getCreatable():array
    public function getCreatable(bool $based = false):array
    {
        if ($this->type !== 'user') {
            return [];
        }
        return $this->can_create;
        return $based ? array_map(function ($item) { return jvbCheckBase($item); },$this->can_create) : $this->can_create;
    }
    public function setCreatable(string|array $creatable):self
    {
        $this->can_create = $creatable;
        $this->can_create = is_string($creatable) ? [jvbNoBase($creatable)] : array_map(function ($type) {
            return jvbNoBase($type);
        }, $creatable);
        return $this;
    }
    public function getManageOthers():array
    {
        if ($this->type !== 'user'){
@@ -838,8 +841,8 @@
    }
        public function addTermCreatedMeta(int $termId):void
        {
            $meta = Meta::forTerm($termId);
            $meta->set('date_published', date('Y-m-d H:i:s'));
            update_term_meta($termId, BASE . 'date_published', date('Y-m-d H:i:s'));
            update_term_meta($termId, BASE . 'date_modified', date('Y-m-d H:i:s'));
        }
        public function handleContentTermMetaChange(int $meta_id, int $term_id, string $meta_key, $meta_value):void
        {
@@ -853,8 +856,14 @@
        }
        public function addTermUpdatedMeta(int $termId):void
        {
            $meta = Meta::forTerm($termId);
            $meta->set('date_modified', date('Y-m-d H:i:s'));
            static $processing = [];
            if (isset($processing[$termId])) return;
            $processing[$termId] = true;
            update_term_meta($termId, BASE . 'date_modified', date('Y-m-d H:i:s'));
            unset($processing[$termId]);
        }
    public function renderContent(string $content, array $block):string
    {
@@ -868,24 +877,75 @@
            Cache::for($this->slug)->flush();
        }
        $out = Cache::for($this->slug)->remember(
            get_the_ID(),
            function() {
        $per_page = 10;
        $page = $_GET['tp']??1;
                $items = get_terms([
                    'taxonomy'  => jvbCheckBase($this->slug),
        $args = apply_filters('jvb_content_tax_args_'.$this->slug, [
            'taxonomy'  => $this->based,
//                      'hide_empty' => true,
                    'fields'    => 'ids',
                ]);
            'fields'    => 'ids',
            'number'     => $per_page,
            'offset'    => ($page - 1) * $per_page,
            'meta_key'  => BASE.'date_modified',
            'meta_type' => 'DATETIME',
            'orderby'   => 'meta_value'
        ]);
        $cache = Cache::for($this->slug);
        $max = get_terms([
            'taxonomy'  => $this->based,
            'fields'    => 'ids',
            'number'     => 0,
            'hide_empty'    => true
        ]);
        $max = count($max??[]);
        $totalPages = floor($max/$per_page);
        global $wp;
        $current = get_home_url(null, '/'.$wp->request);
        $pages = '';
        for ($i = 1; $i<=$totalPages; $i++) {
            $pages .= (int)$page === $i ?
                sprintf(
                    '<li class="current">%s</li>',
                    $i
                ): sprintf(
                '<li><a href="%s">%s</a></li>',
                add_query_arg('tp', $i, $current),
                $i
            );
        }
        $nav = sprintf(
            '<nav class="pagination">%s<ul>%s</ul>%s</nav>',
            $page > 1 ? '<a href="'.add_query_arg('tp', $page-1, $current).'" title="Next Page" class="btn">'.jvbIcon('arrow-circle-left').'<span class="screen-reader-text">Previous Page</span></a>' : '',
            $pages,
            $page < $totalPages ? '<a href="'.add_query_arg('tp', $page+1, $current).'" title="Next Page" class="btn">'.jvbIcon('arrow-circle-right').'<span class="screen-reader-text">Next Page</span></a>' : '',
        );
        $out = $nav. Cache::for($this->slug)->remember(
            $cache->generateKey(['type' =>'contentArchive', ... $args]),
            function() use ($args) {
                $items = get_terms($args);
                $out = [];
                $method = BASE.'render_'.$this->slug.'_content';
                if ($items && !is_wp_error($items)) {
                    foreach ($items as $item) {
                        $meta = Meta::forTerm($item);
                    foreach ($items as $termID) {
                        if (function_exists($method)) {
                            $out[] = $method($termID);
                            continue;
                        }
                        $meta = Meta::forTerm($termID);
                        $slug = sanitize_title($meta->get('name'));
                        $item = sprintf(
                            '<li id="%s"><h2><a href="%s">%s</a></h2><p>%s</p><ul class="item-grid">',
                            '<li id="%s"><h2><a href="%s">%s</a></h2><p>%s</p><ul class="loop scroll">',
                            $slug,
                            get_term_link($item, jvbCheckBase($this->slug))??'',
                            get_term_link($termID, $this->based)??'',
                            $meta->get('name'),
                            $meta->get('description')
                        );
@@ -895,6 +955,12 @@
                                'post_status'   => 'publish',
                                'posts_per_page'    => 3,
                                'fields'    => 'ids',
                                'tax_query' => [
                                    [
                                        'taxonomy'  => $this->based,
                                        'terms'     => $termID
                                    ]
                                ]
                            ]);
                            if ($posts->have_posts()) {
                                while($posts->have_posts()) {
@@ -917,9 +983,13 @@
                        $out[] = $item;
                    }
                }
                return empty($out) ? '' : '<ul class="content-term-list">'.implode('',$out).'</ul>';
                $before = apply_filters(BASE.'before_'.$this->slug.'_content','');
                $out = empty($out) ? '' : '<ul class="content-term-list">'.implode('',$out).'</ul>';
                $after = apply_filters(BASE.'after_'.$this->slug.'_content', '');
                return $before.$out.$after;
            }
        );
        ).$nav;
        error_log('Built the '.$this->slug.' page content.');
        return $content . $out;
    }
@@ -961,6 +1031,18 @@
        return self::getInstance($this->profile);
    }
    public static function getProfileTypes():array
    {
        $hasProfiles = self::getFeatured('profile_link');
        if (empty($hasProfiles)) {
            return [];
        }
        return array_filter(array_map(function($profile) {
            $instance = self::getInstance($profile);
            return $instance->getProfile()->based??false;
        }, $hasProfiles));
    }
    public function setUserSubtype(string $type):self
    {
        $this->user_subtype = sanitize_text_field($type);
inc/registrar/config/seo/Resolver.php
@@ -4,6 +4,7 @@
use JVBase\managers\Cache;
use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject;
use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Distance;
use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
use JVBase\meta\Meta;
use JVBase\registrar\Registrar;
@@ -37,7 +38,7 @@
            case 'CREATOR':
                return JVB()->seo()->getCreator();
            case 'name':
                if (is_post_type_archive()) {
                if (!$meta && is_post_type_archive()) {
                    $obj = get_queried_object();
                    $registrar = Registrar::getInstance($obj->name);
@@ -45,7 +46,8 @@
                }
                break;
        }
        if (str_contains($variable, '.')) {
        if (str_contains($variable, '.') && $meta) {
            return self::resolveRelation($variable, $meta);
        }
        if ($meta && $variable === 'post_permalink') {
@@ -62,6 +64,7 @@
        if ($variable === 'post_content') {
            return self::resolvePostContent($variable, $meta);
        }
        return match($config['type']) {
            'upload', 'image', 'gallery' => self::resolveImage($variable, $meta),
            'selector', 'taxonomy', 'user', 'post' => self::resolveObject($variable, $meta),
@@ -75,6 +78,7 @@
        $relation = array_shift($parts);
        $field = implode('.', $parts);
        //We need to:
            // 1) Get the id of the item we're fetching (meta value of the $relation)
        if ($relation === 'registrar') {
@@ -91,9 +95,10 @@
            return'';
        }
        $ID = $meta->get($relation);
        if (!$ID || $ID === '') {
        if (empty($ID)) {
            return '';
        }
        $IDs = explode(',',$ID);
            // 2) Create new Meta for that relation
        $config = $meta->config($relation);
        if (!$config) {
@@ -113,9 +118,15 @@
            error_log('[SEO]Meta Resolver. Could not find type for relation: '.$relation.': '.$field);
            return '';
        }
        $newMeta = new Meta($ID, $type);
        $results = [];
        foreach ($IDs as $ID) {
            $newMeta = new Meta((int)$ID, $type);
            // 3) Pass to resolver
        return self::resolve($field, $newMeta);
            $results[] =  self::resolveVariable($field, $newMeta);
        }
        return jvbCommaList($results);
    }
    protected static function resolvePostContent(string $variable, ?Meta $meta):string{
@@ -160,7 +171,7 @@
        }
        $ignore = ['description', 'name'];
        if (!in_array($property, $ignore)) {
        if (JVB_TESTING && !in_array($property, $ignore)) {
            error_log('[SEO]Resolver - No method found for '.$property.' with value: '.print_r($value, true).'. Defaulting to base Resolver');
        }
@@ -201,11 +212,13 @@
                function () use ($imgID, $img) {
                    $imageObject = new ImageObject();
                    $imageObject->setContentUrl($img[0]);
                    $width = new Distance();
                    $width->setName($img[1].' px');
                    $width = new QuantitativeValue();
                    $width->setValue($img[1]);
                    $width->setUnitText('px');
                    $imageObject->setWidth($width);
                    $height = new Distance();
                    $height->setName($img[2].' px');
                    $height = new QuantitativeValue();
                    $height->setValue($img[2]);
                    $width->setUnitText('px');
                    $imageObject->setHeight($height);
                    $image_path = get_attached_file($imgID);
inc/registrar/config/seo/Schema.php
@@ -23,6 +23,7 @@
    protected array $properties = [];
    protected array $referenceProperties = [];
    protected array $extras = [];
    protected array $defaultReference = [
        'name'  => '{{post_title}}',
        'url'   => '{{post_permalink}}',
@@ -67,6 +68,8 @@
            }
            $this->defaultArchive['description'] = '{{registrar.'.$slug.'.description}}';
        }
        $this->extras = apply_filters(BASE.ucfirst($slug).'Extras', []);
        $this->initFilters();
        $this->registerHooks();
    }
@@ -295,6 +298,7 @@
                    $class->setMainEntity($itemList);
                    $schema = $class->outputSchema();
                    if (JVB_TESTING) {
//                      error_log('Generated archive schema: '.print_r($schema, true));
                    }
@@ -659,4 +663,20 @@
        $page->setMainEntity($termset);
        return $page->outputSchema();
    }
    public function extra():array
    {
        if (empty($this->extras)) {
            return [];
        }
        $out = [];
        foreach ($this->extras as $config) {
            $schema = SchemaHelper::classFromConfig($config);
            $output = $schema->outputSchema();
            if (!empty($output)) {
                $out[] = $output;
            }
        }
        return $out;
    }
}
inc/registrar/fields/Field.php
@@ -19,6 +19,8 @@
    protected bool $quickEdit = true;   // whether to show in quick edit table
    protected bool $quill;              // whether to use quill
    protected int $maxLength;           // of characters
    protected int $min;
    protected int $max;
    protected string $subtype;
    protected array $condition;
    protected array $allowedSubtype = ['text', 'url','number','tel','email','number'];
@@ -126,7 +128,22 @@
    {
        return $this->section??null;
    }
    public function setMin(int $min):void
    {
        $this->min = $min;
    }
    public function getMin():?int
    {
        return $this->min??null;
    }
    public function setMax(int $max):void
    {
        $this->max = $max;
    }
    public function getMax():?int
    {
        return $this->max??null;
    }
    public function setMaxLength(int $maxLength):void
    {
        $this->maxLength = $maxLength;
inc/rest/routes/NotificationsRoutes.php
@@ -1185,7 +1185,7 @@
    /**
     * Get notification actions
     */
    protected function getNotificationActions(string $type, array $data, object $notification = null): array
    protected function getNotificationActions(string $type, array $data, ?object $notification = null): array
    {
        $actions = [];
inc/ui/CRUDSkeleton.php
@@ -662,7 +662,7 @@
            return;
        }
        ?>
        <div class="radio-options view row">
        <div class="radio-options view row left">
            <span class="label">View:</span>
            <?php
            $views = [
@@ -683,7 +683,7 @@
                <label for="view-<?=$view?>"
                   title="<?=$views[$view]['label']?>">
                    <?= jvbDashIcon($views[$view]['icon']) ?>
                    <span class="screen-reader-text"><?=$views[$view]['label']?></span>
                    <span class="label"><?=$views[$view]['label']?></span>
                </label>
                <?php
            }
@@ -698,7 +698,7 @@
            return;
        }
        ?>
        <div class="radio-options status row">
        <div class="radio-options status row left">
            <span class="label">Status:</span>
            <?php
            $i = 1;
@@ -713,6 +713,7 @@
                <input type="radio" class="btn" data-filter="status" value="<?=$status?>" name="status" id="<?=$status?>"<?=$checked?>>
                <label for="<?=$status?>" title="<?=$config['label']?>">
                    <?= jvbDashIcon($config['icon']) ?>
                    <span class="label"><?= $config['label'] ?></span>
                </label>
                <?php
                $i++;
@@ -729,12 +730,33 @@
            <?php
            $order = [
                'orderby' => [
                    'date' => 'Order by date created',
                    'alphabetical' => 'Order alphabetically'
                    [
                        'slug'  => 'date',
                        'label' => 'Date Created',
                        'icon'  => 'calendar'
                    ],
                    [
                        'slug'  => 'alphabetical',
                        'label' => 'Alphabetically',
                        'icon'  => 'alphabetical',
                    ],
                    [
                        'slug'  => 'date_modified',
                        'icon'  => 'clock-clockwise',
                        'label' => 'Date Modified',
                    ],
                ],
                'order' => [
                    'sort-ascending' => 'In ascending order (Z-A, oldest to newest)',
                    'sort-descending' => 'In descending order (A-Z, newest to oldest)'
                    [
                        'slug'  => 'desc',
                        'icon'  => 'sort-descending',
                        'label' => 'Descending (A-Z, 1-10)'
                    ],
                    [
                        'slug'  => 'asc',
                        'icon'  => 'sort-ascending',
                        'label' => 'Ascending (Z-A, 10-1)'
                    ]
                ]
            ];
@@ -743,16 +765,13 @@
                <div class="row left">
                    <span class="label"><?= ucfirst($o)?>:</span>
                    <?php
                    $title = $o === 'orderby' ? 'Order by ' : 'Sort ';
                    $i = 0;
                    foreach ($option as $opt => $label) {
                        $icon = $opt === 'date' ? 'calendar' : $opt;
                        $value = $opt;
                        $value = ($value === 'sort-ascending') ? 'asc' : $value;
                        $value = ($value === 'sort-descending') ? 'desc' : $value;
                    foreach ($option as $conf) {
                        ?>
                        <input id="<?=$opt?>" class="btn" type="radio" name="<?=$o?>" data-filter="<?=$o?>" value="<?=$value?>"<?=$i===0 ? ' checked':''?>>
                        <input id="<?=$o.'-'.$conf['slug'] ?>" class="btn" type="radio" name="<?=$o?>" data-filter="<?=$o?>" value="<?=$conf['slug']?>"<?=$i===0 ? ' checked':''?>>
                        <label for="<?=$opt?>" title="<?=$label?>"><?=jvbDashIcon($icon)?></label>
                        <label for="<?=$o.'-'.$conf['slug']?>" title="<?=$title.' '.$conf['label']?>"><?=jvbDashIcon($conf['icon'])?><span class="label"><?= $conf['label'] ?></span></label>
                        <?php
                        $i++;
                    }
@@ -1585,8 +1604,13 @@
                }
                $fields = $this->fields;
                if (!$this->isTimeline) {
                    $first = ['post_thumbnail', 'post_title', 'price'];
                    $first = $this->registrar->getType() === 'post' ?
                        ['post_thumbnail', 'post_title', 'price'] :
                        ( $this->registrar->getType() === 'term' ?
                            ['name', 'thumbnail', 'description'] :
                            ['username', 'display_name', 'description']);
                    foreach ($first as $f) {
                        if (array_key_exists($f, $fields)) {
@@ -1790,4 +1814,31 @@
        $out = ob_get_clean();
        echo Form::fieldWrap('post_status', $out, ['type'=>'group']);
    }
    public static function searchFilter():string
    {
        $self = new self();
        $self->hasSearch = true;
        ob_start();
        $self->renderSearch();
        return ob_get_clean();
    }
    public static function viewFilter(array $views):string
    {
        $self = new self();
        $self->views = $views;
        ob_start();
        $self->renderViewControls();
        return ob_get_clean();
    }
    public static function orderFilter(bool $random = false):string
    {
        $self = new self();
        ob_start();
        $self->renderOrderControls();
        return ob_get_clean();
    }
}
inc/utility/Image.php
@@ -160,4 +160,30 @@
        return (new Image)->getImageData($imgID);
    }
    public static function gallery(string $ids, ?string $type = null):string
    {
        $ids = explode(',', $ids);
        $inner = implode('', array_map(function ($ID) {
            $caption = wp_get_attachment_caption($ID);
            if (!empty($caption)) {
                $caption = sprintf('<figcaption>%s</figcaption', $caption);
            }
            return sprintf(
                '<li><figure>%s%s</figure></li>',
                self::image($ID),
                $caption
            );
        }, $ids));
        return sprintf(
            '<ul class="gallery%s">%s</ul>',
            $type? ' '.$type : '',
            $inner
        );
    }
    public static function image(int $id, string $start = 'tiny', string $replace = 'large', bool $addLink = true, ?string $postSlug = null):string
    {
        return (new Image)->formatImage($id, $start, $replace, $addLink, $postSlug);
    }
}
jvb.php
@@ -84,6 +84,7 @@
define('JVB_TESTING', str_contains(get_home_url(),'.test'));
//const JVB_TESTING = false;
//if (!JVB_TESTING) {
    add_filter('show_admin_bar', '__return_false');
src/drawer-menu/render.php
@@ -14,6 +14,9 @@
}
$cache = Cache::for('drawer');
if (JVB_TESTING) {
    $cache->flush();
}
if (!is_front_page()) {
    $menu_items[] = [
src/drawer-menu/style.scss
@@ -92,3 +92,7 @@
.main-actions .buttons.buttons {
    right: calc(var(--btn_) + .5rem);
}
nav.on-this-page.on-this-page.on-this-page {
    max-width: calc(100vw - var(--btn_))!important;
}
src/feed/style.scss
@@ -640,317 +640,679 @@
//  }
//}
//
//.feed-block {
//  grid-column: full;
//  .filters {
//      padding: 1rem 0;
//      max-width:var(--wide);
//      margin: 0 auto;
//
//      .remove-term.remove-term {
//          width: max-content;
//          height: max-content
//      }
//  }
//  .filter-group {
//      position: relative;
//      padding: 2rem 0;
//      .label {
//          position: absolute;
//          left: 0;
//      }
//      > .label {
//          top: 0;
//      }
//      .btn + label {
//          width: auto;
//      }
//      [type=radio] {
//          position:absolute;
//          left: var(--offScreen);
//      }
//      button, label {
//          position: relative;
//          padding: .5rem;
//          height: max-content;
//          &:hover {
//              color: var(--action-contrast);
//          }
//      }
//      button:hover .label,
//      :checked + label .label,
//      label:hover .label {
//          opacity: 1;
//          visibility: visible;
//      }
//      &:has(label:hover) :checked + label .label,
//      button .label,
//      label .label {
//          --height: max-content;
//          opacity: 0;
//          visibility: hidden;
//          bottom: -2rem;
//          width: max-content;
//          white-space: nowrap;
//          font-weight: var(--fw-b);
//      }
//
//
//  }
//  h3 {
//      margin: 0 0 .25rem;
//      font-size: var(--medium);
//  }
//}
///** PLACEHOLDERS **/
//.placeholder {
//  aspect-ratio: 1;
//  background: rgb(var(--base));
//  border: 1rem solid rgb(var(--base-50));
//  border-radius: 1rem;
//  display: flex;
//  justify-content: center;
//  align-items: center;
//
//  i.icon {
//      --w: 50%;
//      color: rgb(var(--base-200));
//      animation: dance 2.5s ease-in-out infinite;
//  }
//}
//
//.item-grid {
//  padding: 0 var(--chip);
//  max-width: 100%;
//}
///** FEED ITEM **/
//.feed.item {
//  position: relative;
//  border-radius: 0.5rem;
//  overflow: hidden;
//  background: rgb(var(--base-50));
//  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
//  height: fit-content;
//  padding: 0;
//  details {
//      z-index: var(--z-2);
//      width: 100%;
//      position: relative;
//      padding: 0;
//      summary {
//          position:absolute;
//          top: -3rem;
//          left:0;
//          width: 100%;
//          background-color: rgba(var(--base),var(--op-2));
//          backdrop-filter: blur(5px);
//          &:hover {
//              background-color: rgba(var(--action-0),var(--op-45));
//          }
//      }
//      &[open] {
//          padding: .25rem .5rem;
//          summary .icon {
//              opacity: 0;
//          }
//      }
//
//  }
//
//  img {
//      object-fit: cover;
//      width: 100%;
//      height: 100%;
//      //opacity: .7;
//      //filter: grayscale(.5) sepia(.3);
//      &:hover {
//          opacity: .8;
//      }
//  }
//
//  &[data-timeline] {
//      .images {
//          aspect-ratio: 3/2;
//          padding: 0 0 1rem;
//          span {
//              width: 50%;
//              position: absolute;
//              background-color: rgb(var(--action-0));
//              color: var(--action-contrast);
//              padding: .25rem .5rem;
//              &:first-of-type {
//                  bottom: 0;
//                  right: 50%;
//                  text-align: right;
//              }
//              &:last-of-type {
//                  top: 0;
//                  left: 50%;
//              }
//          }
//          > a {
//              position: relative;
//              display: flex;
//              flex-wrap: nowrap;
//              width: 100%;
//              height: 100%;
//          }
//      }
//      img {
//          width: 50%;
//          object-fit: cover;
//          height: 100%;
//          &:first-of-type {
//              border-right: 1px solid rgb(var(--action-0));
//          }
//      }
//  }
//
//
//  a {
//      &::before,
//      &::after {
//          display: none;
//      }
//  }
//
//  label {
//      font-weight: normal;
//      text-transform: none;
//      .icon {
//          --w: 1.5em;
//      }
//  }
//}
//
//
//.item-grid:has([data-timeline]) {
//  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
//}
//
//.items-wrap [type=radio],
//.items-wrap [type=checkbox] {
//  position: absolute;
//  opacity: 0;
//  left: -200vw;
//}
//
//.items-wrap [type=radio] + label,
//.items-wrap [type=checkbox] + label {
//  position: relative;
//  cursor: pointer;
//}
//
//.items-wrap [type=radio] + label:hover,
//.items-wrap [type=checkbox] + label:hover {
//  color: rgb(var(--action-0));
//}
//
//.items-wrap [type=radio] + label::before,
//.items-wrap [type=checkbox] + label::before,
//.items-wrap [type=radio] + label::after,
//.items-wrap [type=checkbox] + label::after {
//  content: '';
//  position: absolute;
//  top: 50%;
//}
//
//.items-wrap [type=radio] + label::after,
//.items-wrap [type=checkbox] + label::after {
//  left: 5px;
//  transform: translateY(-70%) rotate(45deg);
//  width: 5px;
//  height: 10px;
//  border: solid var(--light-0);
//  border-width: 0 2px 2px 0;
//  display: none;
//}
//
//.items-wrap [type=radio] + label::before,
//.items-wrap [type=checkbox] + label::before {
//  left: 0;
//  transform: translateY(-50%);
//  width: 1rem;
//  height: 1rem;
//  border: 2px solid rgb(var(--contrast-200));
//  background-color: rgb(var(--base));
//  border-radius: var(--radius);
//}
//
//.items-wrap [type=radio]:hover + label::before,
//.items-wrap [type=checkbox]:hover + label::before {
//  border-color: rgb(var(--action-200));
//}
//
//.items-wrap [type=radio]:checked + label::before,
//.items-wrap [type=checkbox]:checked + label::before{
//  background-color: rgb(var(--action-0));
//  border-color: rgb(var(--action-100));
//}
//
//.items-wrap [type=radio]:checked + label::before {
//  border-radius: 50%;
//}
//
//.items-wrap [type=checkbox]:checked + label::after {
//  display: block;
//  left: 5px;
//  top: 50%;
//  transform: translateY(-70%) rotate(45deg);
//  width: .35rem;
//  height: .66rem;
//  border: solid var(--light-0);
//  border-width: 0 2px 2px 0;
//}
//
//.items-wrap :disabled + label {
//  cursor: not-allowed;
//  background-color: rgb(var(--base-50));
//  color: rgb(var(--base-200));
//  border-color: rgb(var(--base-200));
//}
//
//.items-wrap :disabled + label:hover {
//  background-color: rgb(var(--base-50));
//  color: rgb(var(--base-200));
//  border-color: rgb(var(--base-200));
//}
//
//.items-wrap :disabled + label::before {
//  border-color: rgb(var(--base-200));
//}
//
//#jvb-selector .items-wrap [type=radio] + label,
//#jvb-selector .items-wrap [type=checkbox] + label{
//  flex: 1;
//  padding-left: 2rem!important;
//  transform-origin: top center;
//  will-change: transform;
//}
//
//.feed-block + footer {
//  grid-column: full;
//  padding: 0!important;
//  margin: 0;
//  background-color: rgb(var(--base-50));
//  z-index: 0;
//  display: flex;
//  justify-content: flex-end;
//  button {
//      width: max-content;
//      margin-left: auto;
//      padding: .35rem .25rem;
//      --w: 1.3em!important;
//      flex-wrap: nowrap;
//      justify-content:flex-start;
//      transition: var(--trans-size);
//      font-size: var(--txt-x-small);
//      min-height: 0;
//      span {
//          display: none;
//          white-space: nowrap;
//      }
//      &:focus span,
//      &:hover span {
//          display: block;
//      }
//  }
//}
//
//
//
//.search-container:not(.open) input[type=search],
//.search-container:not(.open) .clear-search {
//  transform: scaleX(0);
//  transform-origin: left;
//  width: 0;
//  padding: 0;
//  transition: transform var(--trans-base), width var(--trans-base), padding var(--trans-base);
//}
//.search-container button {
//  padding: .5rem;
//}
//.search-container .icon {
//  --w: 1.5rem;
//}
//.search-container.open input[type=search],
//.search-container.open .clear-search {
//  transform: scaleX(1);
//  transform-origin: left;
//  transition: transform var(--trans-base), width var(--trans-base), padding var(--trans-base);
//}
//.all-filters > .search,
//input[type=search],
//.search-container {
//  width: 100%;
//}
//
//
//.all-filters {
//  button .label,
//  .row:has(label:hover),
//  .btn:checked + label .label {
//      --height: max-content;
//      bottom: -2rem;
//      font-weight: var(--fw-b);
//      opacity: 0;
//      visibility: hidden;
//      white-space: nowrap;
//      width: -moz-max-content;
//      width: max-content;
//  }
//  summary {
//      width: 100%;
//      &:hover {
//          background-color: rgb(var(--action-0));
//          color: rgb(var(--action-contrast));
//          &::after {
//              background-color: rgb(var(--action-contrast));
//          }
//      }
//  }
//  > .row {
//      width: 100%;
//      margin: 0;
//      .label {
//          width: 20%;
//      }
//  }
//}
/***** START FRESH *****/
.feed-block {
    grid-column: full;
    .filters {
        padding: 1rem 0;
        max-width:var(--wide);
        margin: 0 auto;
        .remove-term.remove-term {
            width: max-content;
            height: max-content
        }
    }
    .filter-group {
        position: relative;
        padding: 2rem 0;
        .label {
            position: absolute;
            left: 0;
        }
        > .label {
            top: 0;
        }
        [type=radio] {
            position:absolute;
            left: var(--offScreen);
        }
        button, label {
            position: relative;
            padding: .5rem;
            height: max-content;
            &:hover {
                color: var(--action-contrast);
            }
        }
        button:hover .label,
        :checked + label .label,
        label:hover .label {
            opacity: 1;
            visibility: visible;
        }
        &:has(label:hover) :checked + label .label,
        button .label,
        label .label {
            --height: max-content;
            opacity: 0;
            visibility: hidden;
            bottom: -2rem;
            width: max-content;
            white-space: nowrap;
            font-weight: var(--fw-b);
        }
    }
    h3 {
        margin: 0 0 .25rem;
        font-size: var(--medium);
    }
}
/** PLACEHOLDERS **/
.placeholder {
    aspect-ratio: 1;
    background: rgb(var(--base));
    border: 1rem solid rgb(var(--base-50));
    border-radius: 1rem;
    display: flex;
    justify-content: center;
    align-items: center;
    i.icon {
    .placeholder {
        aspect-ratio: 1;
        background: rgb(var(--base));
        border: 1rem solid rgb(var(--base-50));
        border-radius: 1rem;
        display: flex;
        justify-content: center;
        align-items: center;
        --w: 50%;
        color: rgb(var(--base-200));
        animation: dance 2.5s ease-in-out infinite;
        i.icon {
            animation: dance 2.5s ease-in-out infinite;
        }
    }
}
.item-grid {
    padding: 0 var(--chip);
    max-width: 100%;
}
/** FEED ITEM **/
.feed.item {
    position: relative;
    border-radius: 0.5rem;
    overflow: hidden;
    background: rgb(var(--base-50));
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    height: fit-content;
    padding: 0;
    details {
        z-index: var(--z-2);
        width: 100%;
        position: relative;
    .item-grid {
        max-width: var(--full);
        &:has([data-timeline]) {
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
        }
    }
    .item {
        overflow: hidden;
        background: rgb(var(--base-50));
        box-shadow: rgba(var(--base), var(--op-2)) var(--shdw);
        height: fit-content;
        padding: 0;
        summary {
            position:absolute;
            top: -3rem;
            left:0;
        h3 {
            margin: 0;
            font-size: var(--txt-medium);
        }
        details {
            z-index: var(--z-2);
            width: 100%;
            background-color: rgba(var(--base),var(--op-2));
            backdrop-filter: blur(5px);
            &:hover {
                background-color: rgba(var(--action-0),var(--op-45));
            }
        }
        &[open] {
            padding: .25rem .5rem;
            summary .icon {
                opacity: 0;
            }
        }
            position: relative;
            padding: 0;
    }
    img {
        object-fit: cover;
        width: 100%;
        height: 100%;
        //opacity: .7;
        //filter: grayscale(.5) sepia(.3);
        &:hover {
            opacity: .8;
        }
    }
    &[data-timeline] {
        .images {
            aspect-ratio: 3/2;
            padding: 0 0 1rem;
            span {
                width: 50%;
            summary {
                position: absolute;
                background-color: rgb(var(--action-0));
                color: var(--action-contrast);
                padding: .25rem .5rem;
                &:first-of-type {
                    bottom: 0;
                    right: 50%;
                    text-align: right;
                }
                &:last-of-type {
                    top: 0;
                    left: 50%;
                }
            }
            > a {
                position: relative;
                display: flex;
                flex-wrap: nowrap;
                top: calc(var(--chip_) * -1);
                left: 0;
                width: 100%;
                height: 100%;
                background-color: rgba(var(--base), var(--op-2));
                backdrop-filter: blur(5px);
                &:hover {
                    background-color: rgba(var(--action-0), var(--op-45));
                }
            }
            &[open] {
                padding: .25rem .5rem;
            }
        }
        img {
            width: 50%;
            object-fit: cover;
            height: 100%;
            &:first-of-type {
                border-right: 1px solid rgb(var(--action-0));
            &:hover {
                opacity: .8;
            }
        }
    }
        &[data-timeline] {
            .images {
                aspect-ratio: 3/2;
                padding: 0 0 1rem;
    a {
        &::before,
        &::after {
            display: none;
                span {
                    width: 50%;
                    position: absolute;
                    background-color: rgb(var(--action-0));
                    color: rgb(var(--action-contrast));
                    padding: .25rem .5rem;
                    &:first-of-type {
                        bottom: 0;
                        right: 50%;
                        text-align: right;
                    }
                    &:last-of-type {
                        top: 0;
                        left: 50%;
                    }
                }
                //> a {
                //  position: relative;
                //  display: flex;
                //  flex-wrap: nowrap;
                //  width: 100%;
                //  height: 100%;
                //}
                img {
                    width: 50%;
                    &:first-of-type {
                        border-right: 2px solid rgb(var(--action-0));
                    }
                }
            }
        }
    }
    label {
        font-weight: normal;
        text-transform: none;
        .icon {
        a {
            &::before,
            &::after {
                display: none;
            }
        }
        label {
            font-weight: normal;
            text-transform: none;
            --w: 1.5em;
        }
    }
}
.all-filters {
    font-size: var(--txt-x-small);
    &[open] {
        border: 2px solid rgb(var(--action-0));
        padding: 0;
        border-radius: 0 0 var(--radius-outer) var(--radius-outer);
    }
.item-grid:has([data-timeline]) {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
    summary {
        width: 100%;
    }
.items-wrap [type=radio],
.items-wrap [type=checkbox] {
    position: absolute;
    opacity: 0;
    left: -200vw;
}
    &[open] summary,
    summary:hover {
        background-color: rgb(var(--action-0));
        color: rgb(var(--action-contrast));
        &::after {
            background-color: rgb(var(--action-contrast));
        }
    }
.items-wrap [type=radio] + label,
.items-wrap [type=checkbox] + label {
    position: relative;
    cursor: pointer;
}
    >.row.row {
        padding: 0 .75rem 2rem;
        width: var(--content);
.items-wrap [type=radio] + label:hover,
.items-wrap [type=checkbox] + label:hover {
    color: rgb(var(--action-0));
}
        &.search {
            padding-bottom: 0;
        }
        position: relative;
.items-wrap [type=radio] + label::before,
.items-wrap [type=checkbox] + label::before,
.items-wrap [type=radio] + label::after,
.items-wrap [type=checkbox] + label::after {
    content: '';
    position: absolute;
    top: 50%;
}
.items-wrap [type=radio] + label::after,
.items-wrap [type=checkbox] + label::after {
    left: 5px;
    transform: translateY(-70%) rotate(45deg);
    width: 5px;
    height: 10px;
    border: solid var(--light-0);
    border-width: 0 2px 2px 0;
    display: none;
}
.items-wrap [type=radio] + label::before,
.items-wrap [type=checkbox] + label::before {
    left: 0;
    transform: translateY(-50%);
    width: 1rem;
    height: 1rem;
    border: 2px solid rgb(var(--contrast-200));
    background-color: rgb(var(--base));
    border-radius: var(--radius);
}
.items-wrap [type=radio]:hover + label::before,
.items-wrap [type=checkbox]:hover + label::before {
    border-color: rgb(var(--action-200));
}
.items-wrap [type=radio]:checked + label::before,
.items-wrap [type=checkbox]:checked + label::before{
    background-color: rgb(var(--action-0));
    border-color: rgb(var(--action-100));
}
.items-wrap [type=radio]:checked + label::before {
    border-radius: 50%;
}
.items-wrap [type=checkbox]:checked + label::after {
    display: block;
    left: 5px;
    top: 50%;
    transform: translateY(-70%) rotate(45deg);
    width: .35rem;
    height: .66rem;
    border: solid var(--light-0);
    border-width: 0 2px 2px 0;
}
.items-wrap :disabled + label {
    cursor: not-allowed;
    background-color: rgb(var(--base-50));
    color: rgb(var(--base-200));
    border-color: rgb(var(--base-200));
}
.items-wrap :disabled + label:hover {
    background-color: rgb(var(--base-50));
    color: rgb(var(--base-200));
    border-color: rgb(var(--base-200));
}
.items-wrap :disabled + label::before {
    border-color: rgb(var(--base-200));
}
#jvb-selector .items-wrap [type=radio] + label,
#jvb-selector .items-wrap [type=checkbox] + label{
    flex: 1;
    padding-left: 2rem!important;
    transform-origin: top center;
    will-change: transform;
}
.feed-block + footer {
    grid-column: full;
    padding: 0!important;
    margin: 0;
    background-color: rgb(var(--base-50));
    z-index: 0;
    display: flex;
    justify-content: flex-end;
    button {
        width: max-content;
        margin-left: auto;
        padding: .35rem .25rem;
        --w: 1.3em!important;
        flex-wrap: nowrap;
        justify-content:flex-start;
        transition: var(--trans-size);
        font-size: var(--txt-x-small);
        min-height: 0;
        span {
            display: none;
        > .label,
        > .row > .label {
            font-family: var(--heading);
            font-weight: var(--fw-h-bold);
            text-transform: uppercase;
        }
        > .label {
            width: 20%;
        }
        >.row > .label {
            white-space: nowrap;
        }
        &:focus span,
        &:hover span {
            display: block;
    }
    button,
    .btn + label {
        width: var(--chipchip);
        min-height: var(--chipchip);
        padding: 0;
    }
    button .label,
    .row:has(> .btn:not(:checked) + label:hover) :checked + label .label,
    .btn + label .label {
        position: absolute;
        bottom: -2rem;
        width: max-content;
        white-space: nowrap;
        opacity: 0;
        z-index: var(--z-4);
    }
    button:hover .label,
    .btn:checked + label .label,
    .btn + label:hover .label {
        opacity: 1;
    }
    .search.row,
    .view.row {
        display: none;
    }
}
.search-container:not(.open) input[type=search],
.search-container:not(.open) .clear-search {
    transform: scaleX(0);
    transform-origin: left;
    width: 0;
    padding: 0;
    transition: transform var(--trans-base), width var(--trans-base), padding var(--trans-base);
}
.search-container button {
    padding: .5rem;
}
.search-container .icon {
    --w: 1.5rem;
}
.search-container.open input[type=search],
.search-container.open .clear-search {
    transform: scaleX(1);
    transform-origin: left;
    transition: transform var(--trans-base), width var(--trans-base), padding var(--trans-base);
}
.all-filters > .search,
input[type=search],
.search-container {
    width: 100%;
}
.toggle-text {
    input {
        + label {
            font-weight: normal;
            color: var(--contrast)!important;
            text-transform: none;
            cursor: pointer;
            position: relative;
            padding: .25rem .5rem;
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
            border: 1px dashed transparent;
            &::before,
            &::after {
                display: none!important;
            }
            .text {
                position: relative;
                margin: 0;
                --gap: 0;
                font-weight: bold;
                width: fit-content;
                padding: 2px 4px;
                border: 1px solid rgb(var(--action-50));
                border-radius: var(--radius);
                color: rgb(var(--action-50));
            }
            .off {
                --mid: -100%;
            }
            .on {
                --mid: 100%;
            }
            .off,
            .on {
                transition: var(--trans-transform), opacity var(--trans-base);
            }
        }
         + label .off,
        &:checked + label .on {
            opacity: 1;
            max-width: 100%;
            transform: translate3d(0,0,0);
        }
         + label .on,
        &:checked + label .off {
            opacity: 0;
            max-width: 0;
            transform: translate3d(0,var(--mid),0);
        }
    }
    &:hover {
        label {
            border-color: rgb(var(--action-200));
        }
        .text {
            background-color: rgb(var(--action-50));
            color: rgb(var(--action-contrast));
            border-color: rgb(var(--action-50));
        }
    }
}
src/feed/view.js
@@ -53,7 +53,7 @@
            filterTrigger: '[data-filter]',
            filters: {
                actions:    '.filter-actions .toggle-text',
                container:  '.filters',
                container:  '.all-filters',
                content:    '[data-filter="content"]',
                orderby:    '[data-filter="orderby"]',
                order:      '[data-filter="order"]',
@@ -301,12 +301,12 @@
    }
    updateFilterControls() {
        const isHidden = Object.keys(this.taxFilters).length === 0;
        const keys = Object.keys(this.taxFilters);
        if (this.ui.buttons.clearFilters) {
            this.ui.buttons.clearFilters.hidden = isHidden;
            this.ui.buttons.clearFilters.hidden = keys.length === 0;
        }
        if (this.ui.filters.actions) {
            this.ui.filters.actions.hidden = isHidden;
            this.ui.filters.actions.hidden = keys.length <= 1;
        }
    }
@@ -672,7 +672,7 @@
            setup({el, refs, manyRefs, data}) {
                el.dataset.id = data.id;
                el.dataset.taxonomy = data.taxonomy;
                if (refs.icon) refs.icon.className = `icon icon=${data.icon}`;
                if (refs.icon) refs.icon.className = `icon icon-${data.icon}`;
                if (refs.span) refs.span.textContent = window.decodeHTMLEntities(data.name);
            }
        });
src/summary/style.scss
@@ -18,3 +18,9 @@
main {
    padding-top: 0!important;
}
ul.summary .term-list {
    padding-left: 0;
    display: flex;
    flex-wrap: nowrap;
}