=DashboardManager overhaul. A bit easier to modify the output of pages. Still have to get the account pages to work as expected, as well as verify the integrations and others are working - but registrar/content pages work as expected. Also fixed up the table generation in CRUD.js and CRUDSkeleton.php
21 files added
72 files modified
| | |
| | | use JVBase\managers\LoginManager; |
| | | use JVBase\managers\MagicLinkManager; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\DashboardManager; |
| | | use JVBase\managers\Dashboard\DashboardManager; |
| | | use JVBase\managers\DirectoryManager; |
| | | use JVBase\managers\ReferralManager; |
| | | use JVBase\managers\RoleManager; |
| | |
| | | 'forms' => new FormRoutes() |
| | | ]; |
| | | |
| | | if (Site::has('magicLink')) { |
| | | if (Site::has('magic_link')) { |
| | | // $this->routes['magicLink'] = new MagicLinkRoutes(); |
| | | $this->managers['magicLink'] = new MagicLinkManager(); |
| | | } |
| | |
| | | ?> |
| | | <section class="main-actions"> |
| | | <div class="buttons col"> |
| | | <?= implode($buttons); ?> |
| | | <?= implode('', $buttons); ?> |
| | | </div> |
| | | <div class="actions"> |
| | | <?= implode($contents); ?> |
| | | <?= implode('', $contents); ?> |
| | | </div> |
| | | </section> |
| | | <?php |
| | |
| | | error_log('Action done!'); |
| | | error_log('Checking custom tables...'); |
| | | CustomTable::ensureTables(); |
| | | error_log('Dashboard is setup: '.print_r(JVB()->dashboard(), true)); |
| | | |
| | | |
| | | // (new JVBase\registry\CheckCustomTables())->maybeCreateTables(); |
| | | error_log('Tables created!'); |
| | |
| | | .replace{margin-left:var(--btn_)!important;padding:0}.dashboard aside.main.left{bottom:0}.dashboard .qtoggle{left:0;bottom:0;margin:0!important}.dashboard>header{padding-left:var(--btn_)}.dashboard nav.tabs{max-width:none;flex-wrap:nowrap}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}dialog form nav.tabs{--wrap:nowrap!important;--gap:0!important;overflow-x:auto;touch-action:pan-x;position:absolute;top:0;left:0;right:0;max-width:100%;height:var(--chipchip);padding:0;background-color:rgba(var(--base),var(--op-5))}dialog form nav.tabs button{min-height:var(--chipchip);padding:0 1rem}dialog nav.tabs button:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}dialog:has(nav.tabs) .wrap{padding-top:3rem}dialog{height:100%!important} |
| | | .replace{margin-left:var(--btn_)!important;padding:0}.dashboard aside.main.left{bottom:0}.dashboard .qtoggle{left:0;bottom:0;margin:0!important}.dashboard>header{padding-left:var(--btn_)}.dashboard nav.tabs{max-width:none;flex-wrap:nowrap}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;width:100%;--gap:0}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);gap:0}nav.sidebar.open li.has-submenu>div{padding-right:0}nav.sidebar.open li.has-submenu>ul{padding-left:var(--chip)}nav.sidebar .has-submenu.open>ul{background-color:rgba(var(--base-200),var(--op-2));border:none}nav.sidebar button{box-shadow:var(--shdw-none);border:0}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}dialog form nav.tabs{--wrap:nowrap!important;--gap:0!important;overflow-x:auto;touch-action:pan-x;position:absolute;top:0;left:0;right:0;max-width:100%;height:var(--chipchip);padding:0;background-color:rgba(var(--base),var(--op-5))}dialog form nav.tabs button{min-height:var(--chipchip);padding:0 1rem}dialog nav.tabs button:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}dialog:has(nav.tabs) .wrap{padding-top:3rem}dialog{height:100%!important}.dashboard-page{max-width:calc(100% - var(--btn));margin-left:0}.toggle-text input{display:none}.toggle-text input+label{font-weight:400;color:var(--contrast)!important;text-transform:none;cursor:pointer;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.toggle-text label::after,.toggle-text label::before{display:none!important}.toggle-text label{padding-left:0!important}.toggle-text input+label .text{position:relative;margin:0;--gap:0;font-weight:700;width:fit-content;padding:2px 4px;border:1px solid var(--action-50);border-radius:4px;color:var(--action-50)!important}table .toggle-text input+label .text{color:var(--contrast)!important;border-color:var(--contrast)}.toggle-text:hover .text,table .toggle-text:hover .text{background-color:var(--action-50);color:var(--light-0)!important;border-color:var(--action-50)}.toggle-text input+label .off,.toggle-text input+label .on{transition:var(--trans-transform),opacity var(--trans-base)}.toggle-text input+label .off{opacity:1;max-width:100%;transform:none}.toggle-text input+label .on{opacity:0;max-width:0;transform:translate3d(0,100%,0)}.toggle-text input:checked+label .off{opacity:0;max-width:0;transform:translate3d(0,-100%,0)}.toggle-text input:checked+label .on{max-width:100%;opacity:1;transform:none} |
| | |
| | | .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}.field.quantity.quantity{margin:0;padding:0;display:inline-flex;width:max-content;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;position:relative;--gap:0!important}.quantity:focus-within{border-color:rgb(var(--action-0))}.field.quantity .wrapper .row{--gap:0!important}.field.quantity>label{margin:0;background-color:transparent;font-size:var(--txt-medium);white-space:nowrap;top:0;left:0}.quantity button{background:rgb(var(--base));padding:0;width:var(--chip_)!important;height:var(--chip_)!important;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}.quantity .wrapper{width:max-content}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{--gap:0!important;--align:flex-start!important}.repeater-row details{width:100%}.repeater-row .drag-handle,.repeater-row button{width:var(--chipchip);height:var(--chipchip)}.drag-handle{cursor:grab;background-color:rgb(var(--base-100));user-select:none}.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))}.repeater-row-ghost{opacity:.3;background:var(--gap-bg,#f0f0f0);border:2px dashed var(--gap-border,#ccc)}.repeater-row-chosen{opacity:.6}.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%;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)}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-200));color:rgb(var(--action-contrast))}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;bottom:0;--w:1em;box-shadow:rgba(var(--base),var(--op-6)) var(--shdw);--wrap:nowrap;--gap:1rem}body:has(nav.fixed.bottom) .fstatus{bottom:var(--btn);z-index:var(--z-8)}.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:min(500px,90vw);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;left:0;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%}[data-field=openingHours] fieldset fieldset{display:flex;align-items:flex-end}[data-field=openingHours] .field+.field{border:none}[data-field=openingHours] .true-false{width:max-content}[data-field=openingHours] label.switch{width:max-content;padding:0 1rem 0 0}[data-field=openingHours] fieldset>.group>.row{max-width:calc(100% - var(--btn_))}[data-field=openingHours] fieldset .field{padding:0;margin:0}[data-field=openingHours] fieldset .group{display:flex}.feedback{position:fixed;top:2rem;right:2rem;background-color:var(--action-50);color:var(--action-contrast);padding:1rem 1.5rem;border-radius:var(--innerRadius);box-shadow:var(--shadow);z-index:10000;opacity:0;transform:translateX(100px);transition:all var(--transition-base);display:flex;align-items:center;gap:.5rem;--w:1.25rem}.feedback.show{opacity:1;transform:translateX(0)} |
| | | .jvb-form-block{grid-column:wide}.jvb-form-block nav.tabs button{min-width:max-content}.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}.field.quantity.quantity{margin:0;padding:0;display:inline-flex;width:max-content;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;position:relative;--gap:0!important}.quantity:focus-within{border-color:rgb(var(--action-0))}.field.quantity .wrapper .row{--gap:0!important}.field.quantity>label{margin:0;background-color:transparent;font-size:var(--txt-medium);white-space:nowrap;top:0;left:0}.quantity button{background:rgb(var(--base));padding:0;width:var(--chip_)!important;height:var(--chip_)!important;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}.quantity .wrapper{width:max-content}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{--gap:0!important;--align:flex-start!important}.repeater-row details{width:100%}.repeater-row .drag-handle,.repeater-row button{width:var(--chipchip);height:var(--chipchip)}.drag-handle{cursor:grab;background-color:rgb(var(--base-100));user-select:none}.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))}.repeater-row-ghost{opacity:.3;background:var(--gap-bg,#f0f0f0);border:2px dashed var(--gap-border,#ccc)}.repeater-row-chosen{opacity:.6}.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%;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)}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-200));color:rgb(var(--action-contrast))}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;bottom:0;--w:1em;box-shadow:rgba(var(--base),var(--op-6)) var(--shdw);--wrap:nowrap;--gap:1rem}body:has(nav.fixed.bottom) .fstatus{bottom:var(--btn);z-index:var(--z-8)}.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:min(500px,90vw);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;left:0;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%}[data-field=openingHours] fieldset fieldset{display:flex;align-items:flex-end}[data-field=openingHours] .field+.field{border:none}[data-field=openingHours] .true-false{width:max-content}[data-field=openingHours] label.switch{width:max-content;padding:0 1rem 0 0}[data-field=openingHours] fieldset>.group>.row{max-width:calc(100% - var(--btn_))}[data-field=openingHours] fieldset .field{padding:0;margin:0}[data-field=openingHours] fieldset .group{display:flex}.feedback{position:fixed;top:2rem;right:2rem;background-color:var(--action-50);color:var(--action-contrast);padding:1rem 1.5rem;border-radius:var(--innerRadius);box-shadow:var(--shadow);z-index:10000;opacity:0;transform:translateX(100px);transition:all var(--transition-base);display:flex;align-items:center;gap:.5rem;--w:1.25rem}.feedback.show{opacity:1;transform:translateX(0)} |
| | |
| | | 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.fixed{z-index:var(--z-10)}nav.always>ul{--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;max-height:100vh;position:fixed;right:-300vw;bottom:0;width:100vw;padding:var(--btn) 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{width:100%;height:max-content}nav.always.fixed.open{width:100vw;height:100vh;inset:0;z-index:var(--z-10)}nav.always a{height:max-content;min-height:var(--chipchip)}nav.always li:not(.has-submenu)>a{padding-right:calc(var(--btn) + 1rem)}nav.always .has-submenu{--dir:row;--wrap:wrap;height:max-content}nav.always .has-submenu a{max-width:calc(100% - var(--btn))}nav.always .has-submenu .toggle{max-width:var(--btn)}.has-submenu.open>a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.has-submenu.open>ul{border:1px solid rgb(var(--action-0));background-color:rgb(var(--action-0));--align:flex-start}nav.always ul.submenu{position:relative;top:0}nav.always .submenu a{padding-left:var(--btn)}nav.always.fixed{width:var(--btn);height:var(--btn);bottom:0;right:0;overflow:hidden}nav.always.fixed .toggle.main{background-color:rgb(var(--base))}nav.always.fixed .toggle.main:focus,nav.always.fixed .toggle.main:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.mobile .toggle.main{width:var(--btn);transition:width var(--trans-base)}nav.mobile .icon-list,nav.mobile .icon-x{--w:32px}nav.mobile .icon-x,nav.mobile.open .icon-list{display:none}nav.mobile .icon-list,nav.mobile.open .icon-x{display:block}nav.mobile.open>ul{--dir:column;z-index:var(--z-9);background-color:rgba(var(--base),var(--op-6));width:100vw;height:100vh;overflow:hidden auto;right:0;bottom:0;position:fixed;padding:var(--btn) 0}nav.always>ul::before,nav.mobile.open>ul::before{content:'';z-index:-1;position:absolute;inset:0;filter:blur(5px)}nav.always.open .main.toggle,nav.mobile.open .main.toggle{position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-10);aspect-ratio:unset}nav.always>ul,nav.always>ul:before,nav.mobile.open>ul,nav.mobile.open>ul::before{background-color:rgba(var(--base),var(--op-6))}@media (max-width:767px){nav.col{height:var(--btn)}nav.mobile>ul{--dir:column;--align:center;--justify:flex-end;--gap:0;height:100%;max-height:none;position:relative;right:-300vw;width:100vw;padding:var(--btn) 0 0;overflow:hidden auto}nav.mobile.open>ul{right:0}}@media (min-width:768px){nav.mobile:not(.always) .toggle.main{display:none}}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap;--justify:flex-start}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color:rgb(var(--contrast-200));padding:0 .25rem}nav#breadcrumbs li:last-of-type::after{display:none}nav#breadcrumbs a{height:var(--chip)}nav#breadcrumbs a,nav#breadcrumbs span{padding:0 .125rem;color:rgb(var(--contrast));text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:rgb(var(--action-0))}nav.fixed{position:fixed;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-6)}nav.fixed.bottom{left:0;bottom:0;width:calc(100vw - var(--btn))}nav.fixed:not(.always) ul{--justify:space-between;width:100%;background-color:rgb(var(--base));padding:0 .25rem}nav.fixed:not(.always) li{flex:1}nav.fixed a{--align:center;--gap:1rem;--w:var(--chip_);color:rgb(var(--contrast));font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base),var(--op-45));max-width:none;margin:0!important}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}nav.on-this-page button{order:3;padding:0 1rem;width:max-content;aspect-ratio:unset}nav.on-this-page.open button{order:0}nav.on-this-page ul{width:100%;--gap:0}nav.on-this-page a{padding:0}nav.on-this-page .active a{background-color:rgba(var(--base),var(--op-6));color:rgb(var(--action-contrast))}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.letters,nav.letters a,nav.letters li,nav.letters ul{min-height:var(--chip);height:max-content}nav.letters li{width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{width:inherit}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base),var(--op-6))}nav.index,nav.index a,nav.index button,nav.index li,nav.index ul{min-height:var(--chipchip);height:var(--chipchip)}nav.index ul{width:100%}nav.index li{flex-shrink:0;transform:scaleX(0);max-width:0;overflow:hidden}nav.index li.active,nav.index li.adj{transform:scaleX(1);width:100%;flex-shrink:1;max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open ul{min-height:max-content;--dir:column}nav.index.open li{width:100%;max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:center;padding:0 2rem 0 0}nav.condensed,nav.condensed a,nav.condensed li,nav.condensed ul{height:max-content;width:max-content;--wrap:wrap;min-height:var(--chip)}.condensed ul{--justify:center;--dir:row}nav.condensed{--gap:0 .25rem;width:100%;--justify:center}nav.condensed li+li::before{content:'·';padding:0 .25em}nav.condensed a{font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:transparent;color:rgb(var(--contrast));border-color:rgb(var(--action-0))}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:flex-end;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x;width:100%}ul.socials li{list-style:none}.always ul.socials{width:100vw;--justify:stretch;--gap:0}.always ul.socials li{flex:1;--justify:center;--align:center}.always ul.socials a{display:inline-flex}ul.socials li{flex:1}ul.socials a{display:inline-flex;font-size:var(--txt-x-small);padding:.25rem .5rem;max-width:none;justify-content:center;width:100%}ul.socials .icon{margin:0}ul.socials .icon+span:not(.screen-reader-text){margin-left:.5rem}nav.tabs.tabs.row{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden;--dir:row}nav.tabs button.active{cursor:default}nav.tabs button{font-family:var(--heading);font-size:var(--txt-x-small);border-bottom:4px solid transparent}nav.tabs button.active,nav.tabs button.active:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast));border-color:rgb(var(--base))}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:rgb(var(--base));--gap:0}.tab-content .tab-content nav.tabs{background-color:rgb(var(--base-100))}.tab-content .tab-content .tab-content nav.tabs{background-color:rgb(var(--base-200))}.tab-content nav.tabs button.active h2{color:rgb(var(--action-0))}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}.wp-site-blocks>header,body>header{--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:var(--align,center);justify-content:var(--justify,space-between);padding:0 .5rem;background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}.dashboard-nav{width:100%}nav.filters{--dir:row;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}nav.term-navigation:has([hidden]){display:none}nav.pagination{width:100%}nav.pagination>a{min-width:var(--chipchip)}nav.pagination>ul{margin:0 auto}nav.pagination:not(:has(a+ul)){margin-left:var(--chipchip)}nav.pagination:not(:has(ul+a)){margin-right:var(--chipchip)}.pagination.condensed li+li::before{display:none}.pagination li.current{width:var(--chip_);height:var(--chip_);background-color:rgb(var(--action-0));border-radius:var(--radius);line-height:1}.pagination.condensed a{font-size:var(--txt-medium);width:var(--chip_);height:var(--chip_)} |
| | | nav,nav ol,nav ul{--padding:0 1rem;--wrap:nowrap;font-family:var(--heading)}nav,nav a,nav li,nav ol,nav ul,ul.socials{display:flex;flex-direction:var(--dir,row);justify-content:var(--justify,flex-start);align-items:var(--align,center);gap:var(--gap,0);flex-wrap:var(--wrap,nowrap);height:var(--btn,3rem);max-width:100%;padding:0;margin:0}nav.col,nav.col ul{height:max-content}nav>ul{width:100%;overflow:auto hidden}nav li{width:max-content;--justify:center;max-inline-size:none;padding:0;list-style:none}nav.fill li{width:100%}nav a,nav button{--justify:center;width:100%;white-space:nowrap;text-transform:uppercase;border-radius:0;background-color:transparent;text-decoration:none}nav a{padding:var(--padding)}nav .toggle{aspect-ratio:1;border:1px solid rgb(var(--base));color:rgb(var(--contrast))}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav a:focus:hover,nav a:focus:visited:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.toggle .icon-caret-down{transform:rotate(0);transition:transform var(--trans-base)}.open>.row>.toggle .icon-caret-down,.open>.toggle .icon-caret-down{transform:rotate(900deg)}.has-submenu{position:relative}ul.submenu{--dir:column;height:max-content;position:absolute;top:100%;right:0;max-height:0;transform:scaleY(0);transform-origin:top;width:100%;min-width:max-content;background-color:rgba(var(--base),var(--op-3));border:2px solid rgba(var(--base),var(--op-3));transition:max-height var(--trans-base),transform var(--trans-base);box-shadow:var(--shdw-none);overflow:hidden}.submenu li{background-color:rgba(var(--base),var(--op-6));border:1px solid rgb(var(--base-50))}.submenu a{height:var(--chipchip)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}nav a:focus:not(:focus-visible){outline:0}nav a:focus-visible{outline:1px solid rgb(var(--action-0));outline-offset:1px}nav.always{overflow:visible;transition:width var(--trans-base);width:max-content}nav.always.fixed{z-index:var(--z-10)}nav.always>ul{--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;max-height:100vh;position:fixed;right:-300vw;bottom:0;width:100vw;padding:var(--btn) 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{width:100%;height:max-content}nav.always.fixed.open{width:100vw;height:100vh;inset:0;z-index:var(--z-10)}nav.always a{height:max-content;min-height:var(--chipchip)}nav.always li:not(.has-submenu)>a{padding-right:calc(var(--btn) + 1rem)}nav.always .has-submenu{--dir:row;--wrap:wrap;height:max-content}nav.always .has-submenu a{max-width:calc(100% - var(--btn))}nav.always .has-submenu .toggle{max-width:var(--btn)}.has-submenu.open>a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.has-submenu.open>ul{border:1px solid rgb(var(--action-0));background-color:rgb(var(--action-0));--align:flex-start}nav.always ul.submenu{position:relative;top:0}nav.always .submenu a{padding-left:var(--btn)}nav.always.fixed{width:var(--btn);height:var(--btn);bottom:0;right:0;overflow:hidden}nav.always.fixed .toggle.main{background-color:rgb(var(--base))}nav.always.fixed .toggle.main:focus,nav.always.fixed .toggle.main:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.mobile .toggle.main{width:var(--btn);transition:width var(--trans-base)}nav.mobile .icon-list,nav.mobile .icon-x{--w:32px}nav.mobile .icon-x,nav.mobile.open .icon-list{display:none}nav.mobile .icon-list,nav.mobile.open .icon-x{display:block}nav.mobile.open>ul{--dir:column;z-index:var(--z-9);background-color:rgba(var(--base),var(--op-6));width:100vw;height:100vh;overflow:hidden auto;right:0;bottom:0;position:fixed;padding:var(--btn) 0}nav.always>ul::before,nav.mobile.open>ul::before{content:'';z-index:-1;position:absolute;inset:0;filter:blur(5px)}nav.always.open .main.toggle,nav.mobile.open .main.toggle{position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-10);aspect-ratio:unset}nav.always>ul,nav.always>ul:before,nav.mobile.open>ul,nav.mobile.open>ul::before{background-color:rgba(var(--base),var(--op-6))}@media (max-width:767px){nav.col{height:var(--btn)}nav.mobile>ul{--dir:column;--align:center;--justify:flex-end;--gap:0;height:100%;max-height:none;position:relative;right:-300vw;width:100vw;padding:var(--btn) 0 0;overflow:hidden auto}nav.mobile.open>ul{right:0}}@media (min-width:768px){nav.mobile:not(.always) .toggle.main{display:none}}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap;--justify:flex-start}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color:rgb(var(--contrast-200));padding:0 .25rem}nav#breadcrumbs li:last-of-type::after{display:none}nav#breadcrumbs a{height:var(--chip)}nav#breadcrumbs a,nav#breadcrumbs span{padding:0 .125rem;color:rgb(var(--contrast));text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:rgb(var(--action-0))}nav.fixed{position:fixed;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-6)}nav.fixed.bottom{left:0;bottom:0;width:calc(100vw - var(--btn))}nav.fixed:not(.always) ul{--justify:space-between;width:100%;background-color:rgb(var(--base));padding:0 .25rem}nav.fixed:not(.always) li{flex:1}nav.fixed a{--align:center;--gap:1rem;--w:var(--chip_);color:rgb(var(--contrast));font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base),var(--op-45));max-width:none;margin:0!important}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}nav.on-this-page button{order:3;padding:0 1rem;width:max-content;aspect-ratio:unset}nav.on-this-page.open button{order:0}nav.on-this-page ul{width:100%;--gap:0}nav.on-this-page a{padding:0}nav.on-this-page .active a{background-color:rgba(var(--base),var(--op-6));color:rgb(var(--action-contrast))}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.letters,nav.letters a,nav.letters li,nav.letters ul{min-height:var(--chip);height:max-content}nav.letters li{width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{width:inherit}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base),var(--op-6))}nav.index,nav.index a,nav.index button,nav.index li,nav.index ul{min-height:var(--chipchip);height:var(--chipchip)}nav.index ul{width:100%}nav.index li{flex-shrink:0;transform:scaleX(0);max-width:0;overflow:hidden}nav.index li.active,nav.index li.adj{transform:scaleX(1);width:100%;flex-shrink:1;max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open ul{min-height:max-content;--dir:column}nav.index.open li{width:100%;max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:center;padding:0 2rem 0 0}nav.condensed,nav.condensed a,nav.condensed li,nav.condensed ul{height:max-content;width:max-content;--wrap:wrap;min-height:var(--chip)}.condensed ul{--justify:center;--dir:row}nav.condensed{--gap:0 .25rem;width:100%;--justify:center}nav.condensed li+li::before{content:'·';padding:0 .25em}nav.condensed a{font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:transparent;color:rgb(var(--contrast));border-color:rgb(var(--action-0))}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:flex-end;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x;width:100%}ul.socials li{list-style:none}.always ul.socials{width:100vw;--justify:stretch;--gap:0}.always ul.socials li{flex:1;--justify:center;--align:center}.always ul.socials a{display:inline-flex}ul.socials li{flex:1}ul.socials a{display:inline-flex;font-size:var(--txt-x-small);padding:.25rem .5rem;max-width:none;justify-content:center;width:100%}ul.socials .icon{margin:0}ul.socials .icon+span:not(.screen-reader-text){margin-left:.5rem}nav.tabs.tabs.row{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden;--dir:row}nav.tabs button.active{cursor:default}nav.tabs button{font-family:var(--heading);font-size:var(--txt-x-small);border-bottom:4px solid transparent}nav.tabs button.active,nav.tabs button.active:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast));border-color:rgb(var(--base))}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:rgb(var(--base));--gap:0}.tab-content .tab-content nav.tabs{background-color:rgb(var(--base-100))}.tab-content .tab-content .tab-content nav.tabs{background-color:rgb(var(--base-200))}.tab-content nav.tabs button.active h2{color:rgb(var(--action-0))}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}.wp-site-blocks>header,body>header{--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:var(--align,center);justify-content:var(--justify,space-between);padding:0 .5rem;background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}.dashboard-nav{width:100%}nav.filters{--dir:row;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}nav.term-navigation:has([hidden]){display:none}nav.pagination{width:100%}nav.pagination>a{min-width:var(--chipchip)}nav.pagination>ul{margin:0 auto}nav.pagination:not(:has(a+ul)){margin-left:var(--chipchip)}nav.pagination:not(:has(ul+a)){margin-right:var(--chipchip)}.pagination.condensed li+li::before{display:none}.pagination li.current{width:var(--chip_);height:var(--chip_);background-color:rgb(var(--action-0));border-radius:var(--radius);line-height:1}.pagination.condensed a{font-size:var(--txt-medium);width:var(--chip_);height:var(--chip_)} |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Refresh nonce if authentication fails |
| | | */ |
| | |
| | | 'X-WP-Nonce': this.getNonce() |
| | | }; |
| | | |
| | | const response = await fetch(url, { |
| | | if (!isFormData && typeof options.body === 'object' && !Object.hasOwn(options.body, 'user')) { |
| | | options.body.user = this.getUser(); |
| | | } |
| | | |
| | | let obj = { |
| | | ...options, |
| | | credentials: 'same-origin', |
| | | headers |
| | | }); |
| | | }; |
| | | |
| | | if (!isFormData && typeof obj.body === 'object') { |
| | | |
| | | obj.body = JSON.stringify(obj.body); |
| | | } |
| | | |
| | | const response = await fetch(url, obj); |
| | | |
| | | if ((response.status === 403 || response.status === 401) && retryCount === 0) { |
| | | const result = await response.clone().json(); |
| | |
| | | setup({ el, refs, manyRefs, data }) { |
| | | baseSetup(el, refs, data); |
| | | |
| | | manyRefs?.inputs?.forEach(el => { |
| | | let wrapper = el.closest('[data-field]'); |
| | | window.prefixInput(el, `${data.id}-`, wrapper); |
| | | }); |
| | | data.prefix = `${data.id}-`; |
| | | |
| | | manyRefs?.status?.forEach(el => { |
| | | if (el.value === data.status) { |
| | | el.checked = true; |
| | | } |
| | | }); |
| | | window.jvbPopulate.populate(el, data); |
| | | |
| | | // |
| | | // manyRefs?.status?.forEach(el => { |
| | | // if (el.value === data.status) { |
| | | // el.checked = true; |
| | | // } |
| | | // }); |
| | | // |
| | | if (crud.isTimeline) { |
| | | if (refs.sharedRow) { |
| | | refs.sharedRow.querySelectorAll('input,select,textarea').forEach(input => { |
| | |
| | | month: 'dialog.date-range .month-select', |
| | | } |
| | | }, |
| | | wrap: '#item-wrap', |
| | | grid: `.${this.content}.item-grid`, |
| | | table: { |
| | | nav: '#vertical', |
| | | form: 'form.table', |
| | | table: 'form.table table', |
| | | body: 'form.table body', |
| | | body: 'form.table tbody', |
| | | head: 'form.table thead', |
| | | foot: 'form.table tfoot', |
| | | selectedColumns: '.all-filters .multi-select', |
| | | columns: 'thead th', |
| | | }, |
| | | bulk: { |
| | | hasEdit: '.bulk-action-select [value="edit"]', |
| | | action: '.bulk-action-select', |
| | | count: '.bulk-controls .selected-count', |
| | | control: '.bulk-controls .bulk-actions', |
| | |
| | | span: '.bulk-select label span' |
| | | }, |
| | | wrapper: { |
| | | wrapper: '.wrap' |
| | | wrapper: this.selectors.wrap, |
| | | }, |
| | | item: { |
| | | idAttribute: 'itemId' |
| | | } |
| | | }); |
| | | }, this.ui.views.table !== null); |
| | | this.selectionHandler.subscribe((event, data) => { |
| | | this.selected = new Set([...data.selectedItems].map(id => parseInt(id))); |
| | | this.ui.bulk.control.hidden = this.selected.size === 0; |
| | |
| | | //current view (defaults to grid) |
| | | let defaultView = this.container.dataset.view??'grid' |
| | | this.view = this.cache.get('view')??defaultView; |
| | | |
| | | if (!this.view) { |
| | | this.view = defaultView; |
| | | } |
| | | if (this.view !== defaultView) { |
| | | this.ui.views[this.view].checked = true; |
| | | } |
| | |
| | | if (this.ui.table.nav && tabDirection === 'vertical') { |
| | | this.ui.table.nav.checked = true; |
| | | } |
| | | this.tabNav = tabDirection !== 'horizontal'; |
| | | |
| | | |
| | | |
| | |
| | | const isBulkAction = e.target.matches('.bulk-action-select'); |
| | | const isView = e.target.matches('[data-view]'); |
| | | |
| | | if (this.view === 'table') { |
| | | this.handleTableChange(e); |
| | | } |
| | | if (!inItem && !isFilter && !isBulkAction && !isView) return; |
| | | |
| | | if (!this.isPopulating && inItem && !e.target.closest('[data-ignore], .select-item')) { |
| | |
| | | return; |
| | | } |
| | | |
| | | // Table-specific handlers |
| | | if (this.view === 'table') { |
| | | if (e.target.matches('details.multi-select')) { |
| | | this.toggleColumn(e.target.id, e.target.checked); |
| | | return; |
| | | } |
| | | } |
| | | handleTableChange(e) { |
| | | // Table-specific handlers |
| | | if (this.view === 'table') { |
| | | if (e.target.matches('details.multi-select')) { |
| | | this.toggleColumn(e.target.id, e.target.checked); |
| | | return; |
| | | } |
| | | |
| | | if (e.target.matches(this.selectors.table.nav)) { |
| | | this.tabNav = e.target.checked; |
| | | this.cache.set('tabNav', e.target.checked ? 'vertical' : 'horizontal'); |
| | | if (e.target.matches(this.selectors.table.nav)) { |
| | | this.tabNav = e.target.checked; |
| | | this.cache.set('tabNav', e.target.checked ? 'vertical' : 'horizontal'); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | handleBulkAction(bulkAction) { |
| | | if (bulkAction.value.startsWith('tax-')) { |
| | | const selectedOption = bulkAction.options[bulkAction.selectedIndex]; |
| | |
| | | // For regular tables, use all rows in tbody |
| | | if (!this.ui.table.body) return null; |
| | | |
| | | const rows = Array.from(this.ui.table.body.querySelectorAll('tr')) |
| | | const rows = Array.from(this.ui.table.body.children) |
| | | .filter(row => this.isEditableRow(row)); |
| | | |
| | | return goBackward ? rows[rows.length - 1] : rows[0]; |
| | |
| | | |
| | | if (on && !this.ui.table.form) { |
| | | let table = window.jvbTemplates.create('contentTable'); |
| | | this.container.append(table); |
| | | this.ui.wrap.append(table); |
| | | this.ui.table = window.uiFromSelectors(this.selectors.table); |
| | | this.ui.table.columns = this.container.querySelectorAll(this.selectors.table.columns); |
| | | } |
| | |
| | | this.toggleTable(); |
| | | window.removeChildren(this.ui.grid); |
| | | |
| | | |
| | | await window.chunkIt( |
| | | items, |
| | | (item) => this.renderTableItem(item), |
| | | (fragment) => { |
| | | if (this.ui.table.body) { |
| | | this.ui.table.body.append(fragment); |
| | | } else { |
| | | this.ui.table.table.insertBefore(fragment, this.ui.table.foot); |
| | | } |
| | | this.ui.table.body.append(fragment); |
| | | }, |
| | | 5 |
| | | ); |
| | |
| | | CLEANUP |
| | | ***************************************************************/ |
| | | resetForm(form) { |
| | | // Clear text inputs, textareas |
| | | form.querySelectorAll('input[type="hidden"], input[type="text"], input[type="number"], input[type="email"], input[type="url"], textarea').forEach(input => { |
| | | input.value = ''; |
| | | }); |
| | | |
| | | // Uncheck checkboxes and radios |
| | | form.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(input => { |
| | | input.checked = false; |
| | | }); |
| | | |
| | | // Reset selects to first option |
| | | form.querySelectorAll('select').forEach(select => { |
| | | select.selectedIndex = 0; |
| | | }); |
| | | form.reset(); |
| | | |
| | | // Clear any selected items displays |
| | | form.querySelectorAll('.selected-items').forEach(container => { |
| | |
| | | class Checkout { |
| | | constructor(config = {}) { |
| | | this.config = config; |
| | | this.isActive = false; |
| | | |
| | | this.isInitialized = false; |
| | | this.cartItems = new Map(); |
| | |
| | | this.provider = this.checkout?.querySelector('form')?.dataset.provider || ''; |
| | | |
| | | this.isOpen = this.config.isOpen === '1' || false; |
| | | //TODO: Remove this. |
| | | this.isOpen = true; |
| | | |
| | | this.api = `${jvbSettings.api}${this.provider}/`; |
| | | |
| | | this.isLoggedIn = this.config.is_logged_in || false; |
| | | this.userEmail = this.config.user_email || ''; |
| | | this.savedCards = []; |
| | |
| | | this.initListeners(); |
| | | this.defineTemplates(); |
| | | |
| | | if (this.isLoggedIn) { |
| | | if (this.isLoggedIn && this.isActive) { |
| | | console.log('Loading saved cards'); |
| | | this.loadSavedCards(); |
| | | } |
| | | } |
| | |
| | | initElements() { |
| | | this.restoreElement = false; |
| | | this.selectors = { |
| | | pass: '#magic-link-form a[data-tab]', |
| | | toggle: { |
| | | btn: '.toggle-cart', |
| | | count: '.toggle-cart .count', |
| | |
| | | panels: { |
| | | checkout: { |
| | | toggle: 'button[data-tab="checkout"]', |
| | | form: '#cart form' |
| | | form: 'form#checkout' |
| | | }, |
| | | order: { |
| | | toggle: 'button[data-tab="order"]', |
| | | }, |
| | | login: { |
| | | pass: 'button[data-tab="login"]', |
| | | } |
| | | }, |
| | | cart: { |
| | |
| | | total: '#cart tfoot .total', |
| | | tax: '#cart tfoot .tax', |
| | | }, |
| | | grandTotal: '#cart .total span', |
| | | tax: '#cart .cart-total .tax span', |
| | | // grandTotal: '#cart .total span', |
| | | // tax: '#cart .cart-total .tax span', |
| | | } |
| | | }; |
| | | this.ui = window.uiFromSelectors(this.selectors); |
| | |
| | | this.ui.toggle.btn.title = 'Currently closed for online ordering'; |
| | | } |
| | | |
| | | this.checkoutForm = this.checkout.querySelector('form'); |
| | | |
| | | this.tabs = window.jvbTabs.registerTab(this.checkoutForm, { |
| | | this.tabs = window.jvbTabs.registerTab(this.checkout, { |
| | | updateURL: false |
| | | }); |
| | | } |
| | |
| | | this.keyHandler = this.handleEscape.bind(this); |
| | | this.changeHandler = this.handleChange.bind(this); |
| | | |
| | | this.checkoutForm.addEventListener('submit', (e) => this.handleFormSubmit(e)); |
| | | this.ui.panels.checkout.form?.addEventListener('submit', (e) => this.handleFormSubmit(e)); |
| | | document.addEventListener('click', this.clickHandler); |
| | | document.addEventListener('change', this.changeHandler); |
| | | // document.addEventListener('keydown', this.keyHandler); |
| | |
| | | *****************************************************************/ |
| | | |
| | | handleClick(e) { |
| | | if (window.targetCheck(e, 'button') && window.targetCheck(e, 'div.quantity')) { |
| | | if (e.target === this.ui.pass) { |
| | | e.preventDefault(); |
| | | window.jvbTabs.switchTab('login', window.jvbTabs.getConfig(this.ui.panels.login.pass)); |
| | | } else if (window.targetCheck(e, 'button') && window.targetCheck(e, 'div.quantity')) { |
| | | let quantity = window.targetCheck(e, 'div.quantity'); |
| | | this.handleNumberClick(e, quantity); |
| | | } else if (window.targetCheck(e, '[data-add-to-cart]') && window.targetCheck(e, 'button')) { |
| | |
| | | this.ui.panels.checkout.toggle.title = 'Add some things to your cart first!'; |
| | | this.ui.cart.empty.hidden = false; |
| | | this.ui.cart.table.hidden = true; |
| | | this.ui.cart.total.hidden = true; |
| | | // this.ui.cart.total.hidden = true; |
| | | this.a11y.announce('Nothing in Cart'); |
| | | } else { |
| | | this.ui.cart.empty.hidden = true; |
| | | this.ui.panels.checkout.toggle.disabled = false; |
| | | this.ui.panels.checkout.toggle.disabled = !this.isActive; |
| | | if (!this.isActive) { |
| | | this.ui.panels.checkout.toggle.title = 'We\'re experiencing technical difficulties. Try calling or coming in person.'; |
| | | } |
| | | this.ui.cart.table.hidden = false; |
| | | this.ui.cart.total.hidden = false; |
| | | // this.ui.cart.total.hidden = false; |
| | | this.ui.panels.checkout.toggle.title = 'Checkout'; |
| | | } |
| | | } |
| | |
| | | let total = 0; |
| | | this.cartItems.forEach(item => total += item.total); |
| | | let tax = total * 0.05; |
| | | window.eraseText(this.ui.cart.tax); |
| | | // window.eraseText(this.ui.cart.tax); |
| | | window.eraseText(this.ui.cart.foot.tax); |
| | | window.eraseText(this.ui.cart.foot.total); |
| | | window.typeText(this.ui.cart.foot.tax, window.formatPrice(tax)); |
| | | window.typeText(this.ui.cart.grandTotal, window.formatPrice(total + tax)); |
| | | // window.typeText(this.ui.cart.grandTotal, window.formatPrice(total + tax)); |
| | | window.typeText(this.ui.cart.foot.total, window.formatPrice(total + tax)); |
| | | this.ui.cart.tax.classList.remove('typeText'); |
| | | this.ui.cart.foot.tax.classList.remove('typeText'); |
| | | this.ui.cart.foot.total.classList.remove('typeText'); |
| | | } |
| | | |
| | | /***************************************************************** |
| | |
| | | const orderData = this.extractOrderData(form); |
| | | |
| | | try { |
| | | window.jvbLoading?.showLoading?.('Processing payment...'); |
| | | const result = await this.processPayment(orderData); |
| | | this.handleSuccess(result, form); |
| | | } catch (error) { |
| | | this.handleError(error); |
| | | } finally { |
| | | window.jvbLoading?.hideLoading?.(); |
| | | } |
| | | } |
| | | |
| | |
| | | trackOrder(orderNum) { |
| | | this.orderId = orderNum; |
| | | this.scheduleOrderCheck(); |
| | | this.checkout.querySelector('button[data-tab=order]').hidden = false; |
| | | this.ui.panels.order.toggle.hidden = false; |
| | | } |
| | | |
| | | scheduleOrderCheck() { |
| | |
| | | } |
| | | |
| | | async checkOrderStatus() { |
| | | const response = await fetch(`${this.config.api_url}order-status/${this.orderId}`, { |
| | | headers: { 'X-WP-Nonce': this.config.nonce } |
| | | }); |
| | | const response = await window.auth.fetch(`${this.api}order-status/${this.orderId}`, ); |
| | | const data = await response.json(); |
| | | if (data.status !== 'ready') { |
| | | this.scheduleOrderCheck(); |
| | |
| | | const container = document.getElementById('saved-cards'); |
| | | if (!container || this.savedCards.length === 0) return; |
| | | |
| | | console.log('Yup'); |
| | | const html = ` |
| | | <div class="saved-cards-section"> |
| | | <h4>Saved Payment Methods</h4> |
| | |
| | | } |
| | | |
| | | try { |
| | | this.isActive = true; |
| | | this.payments = window.Square.payments( |
| | | this.config.application_id, |
| | | this.config.location_id |
| | |
| | | })); |
| | | |
| | | } catch (error) { |
| | | console.error('Failed to initialize Square payments:', error); |
| | | this.handleError(error); |
| | | this.isActive = false; |
| | | } |
| | | } |
| | | |
| | |
| | | throw new Error('Store is currently closed'); |
| | | } |
| | | |
| | | const response = await fetch(this.config.api_url + 'process-payment', { |
| | | const response = await window.auth.fetch(`${this.api}process-payment`, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | 'X-WP-Nonce': this.config.nonce, |
| | | }, |
| | | body: JSON.stringify({ |
| | | source_id: sourceToken, |
| | |
| | | |
| | | async loadSavedCards() { |
| | | try { |
| | | const response = await fetch(this.config.api_url + 'saved-cards', { |
| | | method: 'GET', |
| | | headers: { 'X-WP-Nonce': this.config.nonce }, |
| | | const response = await window.auth.fetch(`${this.api}saved-cards`, { |
| | | method: 'POST', |
| | | }); |
| | | |
| | | const result = await response.json(); |
| | |
| | | // Get source day hours |
| | | const sourceData = this.getSourceDayData(dayGroup); |
| | | |
| | | console.log(sourceData); |
| | | |
| | | // Update source day info |
| | | const sourceDayEl = modal.querySelector('.source-day'); |
| | | const sourceHoursEl = modal.querySelector('.source-hours'); |
| | | |
| | | console.log('Elements: ', {sourceDayEl, sourceHoursEl}); |
| | | |
| | | if (sourceDayEl) { |
| | | sourceDayEl.textContent = window.uppercaseFirst ? |
| | |
| | | const openCheckbox = targetGroup.querySelector(`input[type="checkbox"][name="openingHours|${dayName}|isOpen"]`); |
| | | const opensInput = targetGroup.querySelector(`input[name="openingHours|${dayName}|opens"]`); |
| | | const closesInput = targetGroup.querySelector(`input[name="openingHours|${dayName}|closes"]`); |
| | | console.log('applying hours to days: ', { |
| | | openCheckbox, |
| | | opensInput, |
| | | closesInput |
| | | }); |
| | | |
| | | if (!openCheckbox) return; |
| | | |
| | | // Set checkbox state |
| | |
| | | } else { |
| | | this.notify('form-submit', { |
| | | config: form, |
| | | data: this.changes.get(form.id)?.changes??{}, |
| | | data: this.changes.get(form.id)?.changes ?? {}, |
| | | }); |
| | | } |
| | | if (form.options.endpoint && !form.options.handled) { |
| | | console.log('Submitting form...'); |
| | | await this.handleServerSave(form, this.collectFormData(form.form)); |
| | | } |
| | | |
| | | } |
| | | if (form.options.endpoint && !form.options.handled) { |
| | | e.preventDefault(); |
| | | console.log('Submitting form...', this.changes.get(form.id)?.changes ?? {}); |
| | | await this.handleServerSave(form, this.changes.get(form.id)?.changes ?? {}); |
| | | } |
| | | |
| | | if (form.options.showSummary) { |
| | |
| | | } |
| | | |
| | | if (form.tabs) { |
| | | console.log('Updating step progress on change'); |
| | | this.updateStepProgress(form); |
| | | } |
| | | |
| | |
| | | async handleServerSave(form, changes) { |
| | | this.cancelServerSave(form.id); |
| | | |
| | | if (window.jvbQueue) { |
| | | if (form.useQueue && window.jvbQueue) { |
| | | changes.user = window.auth.getUser(); |
| | | window.jvbQueue.addToQueue({ |
| | | endpoint: form.options.endpoint, |
| | |
| | | headers: form.options.headers??{}, |
| | | }); |
| | | } else { |
| | | await window.auth.fetch(jvbBase.api+form.options.endpoint, { |
| | | await window.auth.fetch(jvbSettings.api+form.options.endpoint, { |
| | | body: changes, |
| | | headers: form.options.headers??{}, |
| | | method: 'POST', |
| | | }); |
| | | } |
| | | } |
| | |
| | | autoUpload: false, |
| | | imageMeta: true, |
| | | delay: 1500, |
| | | useQueue: true, |
| | | endpoint: Object.hasOwn(form.dataset, 'save') ? form.dataset.save: '', |
| | | showStatus: true, |
| | | showSummary: false, |
| | |
| | | |
| | | field.classList.remove('has-success'); |
| | | field.classList.add('has-error'); |
| | | field.scrollIntoView({ |
| | | behavior: "smooth", |
| | | block: "center", |
| | | inline: "nearest" |
| | | }); |
| | | field.querySelector(this.inputSelectors)?.focus(); |
| | | |
| | | if (item.ui.message) { |
| | | item.ui.message.hidden = false; |
| | |
| | | class HandleSelection { |
| | | constructor(container, options = {}) { |
| | | constructor(container, options = {}, hasTable = false) { |
| | | this.container = container; |
| | | const defaults = { |
| | | selectAll: { |
| | |
| | | count: '.selected-count, .selected .info', |
| | | bulkControls: '.bulk-actions', |
| | | }, |
| | | items: '.item-grid', |
| | | items: '.item-grid, table', |
| | | wrapper: { |
| | | wrapper: ':has(.item-grid, [data-select-all])', |
| | | wrapper: ':has(.item-grid, [data-select-all]):not([hidden]), table', |
| | | id: 'selection', |
| | | }, |
| | | item: { |
| | |
| | | wrappers: {}, |
| | | }; |
| | | this.selectors = window.deepMerge(defaults, options); |
| | | |
| | | this.hasTable = hasTable; |
| | | this.a11y = window.jvbA11y; |
| | | |
| | | this.selectedItems = new Set(); |
| | |
| | | this.index = 0; |
| | | let selectors = this.removeDataReferences(); |
| | | this.ui = window.uiFromSelectors(selectors, this.container); |
| | | if (this.hasTable) { |
| | | this.checkForTable(); |
| | | } |
| | | |
| | | this.container.querySelectorAll(this.selectors.wrapper.wrapper).forEach(wrapper => { |
| | | this.addWrapper(wrapper); |
| | | }); |
| | | } |
| | | checkForTable() { |
| | | if (!Object.hasOwn(this.ui.wrapper, 'table') || !this.ui.wrapper.table) { |
| | | this.ui.wrapper.table = this.container.querySelector('table'); |
| | | } |
| | | } |
| | | addWrapper(el) { |
| | | let id = this.selectors.wrapper.id; |
| | | if (!Object.hasOwn(el.dataset, id)) { |
| | |
| | | this.index++; |
| | | } |
| | | let selectors = this.removeDataReferences().selectAll; |
| | | |
| | | console.log('Adding wrapper from element: ', el); |
| | | //store the DOM of the grid and selectAll |
| | | this.ui.wrappers[el.dataset[id]] = { |
| | | element: el, |
| | |
| | | |
| | | // Do range selection |
| | | const currentId = this.getItemId(item); |
| | | |
| | | const items = this.getWrapperChildren(wrapper); |
| | | const lastIndex = items.findIndex(itemId => itemId === this.lastSelected); |
| | | const currentIndex = items.findIndex(itemId => itemId === currentId); |
| | |
| | | } |
| | | |
| | | getWrapperChildren(wrapper) { |
| | | return Array.from(wrapper.items.children) |
| | | |
| | | return Array.from(wrapper.tagName === 'TABLE' ? Array.from(wrapper.children)[1].children : wrapper.items.children) |
| | | .map(item => this.getItemId(item)); |
| | | } |
| | | |
| | | getItemWrapper(item) { |
| | | if (!item) return null; |
| | | console.log('Wrapper: ',this.selectors.wrapper.wrapper); |
| | | let wrapper = item.closest(this.selectors.wrapper.wrapper); |
| | | console.log('Found wrapper: ', wrapper); |
| | | if (!wrapper)return null; |
| | | return this.getWrapper(wrapper); |
| | | } |
| | | |
| | | getWrapper(wrapper) { |
| | | if (this.hasTable) { |
| | | //Find the table, if available |
| | | this.checkForTable(); |
| | | if (!this.ui.wrapper.table.hidden) { |
| | | return this.ui.wrapper.table; |
| | | } |
| | | } |
| | | return this.ui.wrappers[wrapper.dataset[this.selectors.wrapper.id]]??null; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // Update label text |
| | | if (wrapper.selectAll.span) { |
| | | if (Object.hasOwn(wrapper, 'selectAll') && Object.hasOwn(Object.selectAll,'span') && wrapper.selectAll.span) { |
| | | wrapper.selectAll.span.textContent = (trigger.checked && ids.length > 0) ? 'Clear Selection' : 'Select All'; |
| | | } |
| | | |
| | |
| | | * PUBLIC API |
| | | *******************************************************************/ |
| | | select(id, updateCheckbox = true, updateUI = true) { |
| | | console.log('Selecting items...'); |
| | | if (this.selectedItems.has(id)) return; |
| | | console.log('Not already selected...'); |
| | | |
| | | this.selectedItems.add(id); |
| | | let item = this.getItem(id); |
| | | console.log('Got item: ', item); |
| | | if (item) item.element.classList.add('selected'); |
| | | if (updateCheckbox) this.setCheckboxState(id, true); |
| | | if (updateUI) { |
| New file |
| | |
| | | class Login { |
| | | constructor() { |
| | | this.initElements(); |
| | | } |
| | | |
| | | initElements() { |
| | | this.selectors = { |
| | | magic: 'form#magic-link-form', |
| | | login: 'form#loginform', |
| | | } |
| | | this.ui = window.uiFromSelectors(this.selectors); |
| | | this.forms = {}; |
| | | |
| | | for (let [id, form] of Object.entries(this.ui)) { |
| | | if (form) { |
| | | this.forms[id] = window.jvbForm.registerForm(form, { |
| | | cache: false, |
| | | useQueue: false, |
| | | }); |
| | | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | document.addEventListener('DOMContentLoaded', async function() { |
| | | window.auth.subscribe((event) => { |
| | | if (event === 'auth-loaded') { |
| | | window.login = new Login(); |
| | | } |
| | | }); |
| | | }) |
| | |
| | | for (let [name, value] of Object.entries(this.data.fields)) { |
| | | let field = form.querySelector(`[data-field="${name}"]`); |
| | | if (field) { |
| | | this.populateField(field, name, value); |
| | | this.populateField(field, name, value, Object.hasOwn(this.data, 'prefix') ? this.data.prefix : null); |
| | | } |
| | | } |
| | | } |
| | |
| | | * @param {HTMLElement} field |
| | | * @param {string} name |
| | | * @param {mixed} value |
| | | * @param {string|null} prefix, to prefix the input name/id |
| | | */ |
| | | populateField(field, name, value) { |
| | | populateField(field, name, value, prefix = null) { |
| | | |
| | | let type = this.formHelper.getFieldType(field); |
| | | if (!type || this.isEmptyValue(name)) return; |
| | |
| | | }; |
| | | |
| | | if (Object.hasOwn(handlers, type)) { |
| | | handlers[type](field, name, value); |
| | | handlers[type](field, name, value, prefix); |
| | | } else { |
| | | this.populateText(field, name, value); |
| | | this.populateText(field, name, value, prefix); |
| | | } |
| | | } |
| | | |
| | | populateRepeater(field, name, value) { |
| | | populateRepeater(field, name, value, prefix = null) { |
| | | const container = field.querySelector('.repeater-items'); |
| | | let template = field.querySelector('template')?.className ?? false; |
| | | if (!container || !template) return; |
| | |
| | | |
| | | value.forEach((data, index) => { |
| | | const templateData = { ...data, index, repeater: field }; |
| | | |
| | | if (prefix) { |
| | | templateData.prefix = prefix; |
| | | } |
| | | const row = this.templates.create(template, templateData); |
| | | if (!row) return; |
| | | |
| | |
| | | for (let [fieldName, fieldValue] of Object.entries(data)) { |
| | | let subField = row.querySelector(`[data-field="${fieldName}"]`); |
| | | if (subField) { |
| | | this.populateField(subField, fieldName, fieldValue); |
| | | this.populateField(subField, fieldName, fieldValue, prefix); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | populateTagList(field, name, value) { |
| | | populateTagList(field, name, value, prefix = null) { |
| | | |
| | | const container = field.querySelector('.tag-items'); |
| | | let template = field.querySelector('template')?.className ?? false; |
| | |
| | | row.querySelectorAll('input[type="hidden"]').forEach(input => { |
| | | const key = input.dataset.field; |
| | | if (key && data[key] !== undefined) { |
| | | if (prefix) { |
| | | window.prefixInput(input, prefix, field,false, true); |
| | | } |
| | | |
| | | input.value = data[key]; |
| | | } |
| | | }); |
| | |
| | | return data[format] ?? values[0] ?? 'New Item'; |
| | | } |
| | | } |
| | | populateGroup(field, name, value) { |
| | | populateGroup(field, name, value, prefix = null) { |
| | | if (!value || typeof value !== 'object') return; |
| | | console.log(value, 'Populating Group Value: ') |
| | | for (let [subName, subValue] of Object.entries(value)) { |
| | | let subField = field.querySelector(`[data-field="${subName}"]`); |
| | | if (subField) { |
| | | this.populateField(subField, subName, subValue); |
| | | this.populateField(subField, subName, subValue, prefix); |
| | | } |
| | | } |
| | | } |
| | | populateLocation(field, name, value) { |
| | | populateLocation(field, name, value, prefix = null) { |
| | | const subFields = ['address', 'lat', 'lng', 'street', 'city', 'province', 'postal_code', 'country']; |
| | | subFields.forEach(subField => { |
| | | if (Object.hasOwn(value, subField)) { |
| | | let input = field.querySelector(`[data-location-field="${subField}"]`); |
| | | |
| | | if (prefix && input) { |
| | | window.prefixInput(input, prefix, field,false, true); |
| | | } |
| | | if (input) input.value = String(value[subField]||''); |
| | | |
| | | } |
| | | }); |
| | | } |
| | | populateTaxonomy(field, name, value) { |
| | | populateTaxonomy(field, name, value, prefix = null) { |
| | | let termIds = this.splitIDs(value); |
| | | |
| | | const hiddenInput = field.querySelector(`input[type="hidden"][name="${name}"]`); |
| | | if (hiddenInput) { |
| | | if (prefix) { |
| | | window.prefixInput(hiddenInput, prefix, field,false, true); |
| | | } |
| | | hiddenInput.value = termIds.join(','); |
| | | if (window.jvbSelector) { |
| | | requestAnimationFrame(() => { |
| | |
| | | } |
| | | } |
| | | } |
| | | populateUser(field, name, value) { |
| | | this.populateTaxonomy(field, name, value); |
| | | populateUser(field, name, value, prefix = null) { |
| | | this.populateTaxonomy(field, name, value, prefix); |
| | | } |
| | | populateUpload(field, name, value) { |
| | | populateUpload(field, name, value, prefix = null) { |
| | | if (field.dataset.subtype && field.dataset.subtype === 'timeline') { |
| | | this.populateTimelineGallery(field,name,value); |
| | | this.populateTimelineGallery(field,name,value, prefix); |
| | | return; |
| | | } |
| | | |
| | |
| | | if (ids.length === 0) return; |
| | | const hiddenInput = field.querySelector(`input[type="hidden"]`); |
| | | if (hiddenInput) { |
| | | if (prefix) { |
| | | window.prefixInput(hiddenInput, prefix, field,false, true); |
| | | } |
| | | hiddenInput.value = ids.join(','); |
| | | } |
| | | |
| | |
| | | } |
| | | }; |
| | | data.id = id; |
| | | |
| | | grid.append(this.templates.create('uploadItem', data)); |
| | | let items = grid.children; |
| | | |
| | |
| | | }); |
| | | } |
| | | } |
| | | populateUploadMeta(element, name, id) { |
| | | populateUploadMeta(element, name, id, prefix = null) { |
| | | // Find the image_data field group |
| | | const imageDataField = element.querySelector('[data-field="image_data"]'); |
| | | if (!imageDataField) return; |
| | |
| | | |
| | | for (const m of meta) { |
| | | const input = imageDataField.querySelector(`[data-field="image_data|${m}"] input, [data-field="image_data|${m}"] textarea`); |
| | | if (input && prefix) { |
| | | window.prefixInput(input, prefix, element,false, true); |
| | | } |
| | | if (input && data[m]!=='') { |
| | | input.value = window.decodeHTMLEntities(data[m]); |
| | | } |
| | | } |
| | | } |
| | | populateTimelineGallery(field,name, value) { |
| | | populateTimelineGallery(field,name, value, prefix = null) { |
| | | if (!value || !Array.isArray(value) || value.length === 0) return; |
| | | |
| | | let grid = field.querySelector('.item-grid'); |
| | |
| | | |
| | | field.querySelector('.progress')?.remove(); |
| | | for (let data of value) { |
| | | if (prefix) { |
| | | data.prefix = prefix; |
| | | } |
| | | let point = this.templates.create('timelineItem', data); |
| | | if (point) { |
| | | grid.append(point); |
| | |
| | | } |
| | | } |
| | | } |
| | | populateMultiValue(field, name, value) { |
| | | populateMultiValue(field, name, value, prefix = null) { |
| | | if (typeof value === 'string') { |
| | | try { |
| | | value = JSON.parse(value); |
| | |
| | | for (let option of select.options) { |
| | | option.selected = value.includes(option.value); |
| | | } |
| | | |
| | | if (prefix) { |
| | | window.prefixInput(select, prefix, field,false, true); |
| | | } |
| | | return; |
| | | } |
| | | field.querySelectorAll(`input[type="checkbox"][name="${name}[]"], input[type="checkbox"][name="${name}"]`).forEach(checkbox => { |
| | | |
| | | if (prefix) { |
| | | window.prefixInput(checkbox, prefix, field,false, true); |
| | | } |
| | | |
| | | checkbox.checked = value.includes(checkbox.value); |
| | | }); |
| | | |
| | | } |
| | | populateSingleValue(field, name, value) { |
| | | populateSingleValue(field, name, value, prefix = null) { |
| | | value = String(value || ''); |
| | | |
| | | let select = field.querySelector(`select[name="${name}"]`); |
| | | if (select) { |
| | | if (prefix) { |
| | | window.prefixInput(select, prefix, field,false, true); |
| | | } |
| | | select.value = value; |
| | | |
| | | return; |
| | | } |
| | | |
| | | let input = field.querySelector(`input[type="radio"][value="${value}"], input[type="checkbox"][value="${value}"]`) |
| | | || field.querySelector(`[name="${name}"][value="${value}"]`); |
| | | if (input) { |
| | | if (prefix) { |
| | | window.prefixInput(input, prefix, field,false, true); |
| | | } |
| | | input.checked = true; |
| | | } |
| | | } |
| | | populateBoolean(field, name, value) { |
| | | const input = field.querySelector(`[name="${name}"], input[type="checkbox"]`); |
| | | if (input) { |
| | | input.checked = Boolean(value); |
| | | |
| | | if (prefix) { |
| | | field.querySelectorAll(`input[type="radio"],input[type="checkbox"], input[name="${name}"]`).forEach(i => { |
| | | if (i !== input) { |
| | | window.prefixInput(input, prefix, field, false, true); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | populateDate(field, name, value) { |
| | | populateBoolean(field, name, value, prefix = null) { |
| | | const input = field.querySelector(`[name="${name}"], input[type="checkbox"]`); |
| | | if (input) { |
| | | if (prefix) { |
| | | window.prefixInput(input, prefix, field,false, true); |
| | | } |
| | | input.checked = Boolean(value); |
| | | |
| | | } |
| | | } |
| | | populateDate(field, name, value, prefix = null) { |
| | | const input = field.querySelector(`[name="${name}"], input`); |
| | | if (input) { |
| | | if (prefix) { |
| | | window.prefixInput(input, prefix, field,false, true); |
| | | } |
| | | |
| | | if (typeof value === 'object' && Object.hasOwn(value, 'date')) { |
| | | value = value.date; |
| | | } |
| | |
| | | } catch (e) { |
| | | input.value = value; |
| | | } |
| | | |
| | | } |
| | | } |
| | | populateNumber(field, name, value) { |
| | | populateNumber(field, name, value, prefix = null) { |
| | | const input = field.querySelector(`[name="${name}"], input[type="number"]`); |
| | | if (input) { |
| | | if (prefix) { |
| | | window.prefixInput(input, prefix, field,false, true); |
| | | } |
| | | input.value = Number(value) || 0; |
| | | |
| | | } |
| | | } |
| | | populateTextarea(field, name, value) { |
| | | populateTextarea(field, name, value, prefix = null) { |
| | | let textarea = field.querySelector('textarea[data-editor], textarea'); |
| | | this.populateText(field, name, value); |
| | | this.populateText(field, name, value, prefix); |
| | | |
| | | if (textarea?.dataset.editor) { |
| | | const editor = field.querySelector('.ql-editor'); |
| | |
| | | } |
| | | } |
| | | } |
| | | populateText(field, name, value) { |
| | | populateText(field, name, value, prefix = null) { |
| | | let input = field.querySelector(`[name="${name}"]`) |
| | | || field.querySelector('textarea[data-editor]') |
| | | || field.querySelector('input:not([type="hidden"]):not([type="file"]), textarea, select'); |
| | | if (input) { |
| | | if (prefix) { |
| | | window.prefixInput(input, prefix, field,false, true); |
| | | } |
| | | input.value = window.decodeHTMLEntities(value??''); |
| | | } |
| | | } |
| | | /******************************************************************** |
| | | UTILITY |
| | | ********************************************************************/ |
| | | getFormHelper() { |
| | | window.requestAnimationFrame(()=> { |
| | | this.formHelper = window.jvbForm; |
| | | }); |
| | | } |
| | | |
| | | splitIDs(value) { |
| | | return String(value).split(',') |
| | |
| | | this.tabs = new Map(); |
| | | |
| | | this.hasHash = false; |
| | | this.shouldNotify = true; |
| | | this.init(); |
| | | } |
| | | init() { |
| | |
| | | |
| | | const tab = e.target.closest(this.selectors.tab); |
| | | if (tab) { |
| | | if (tab.dataset.skip) { |
| | | delete config.preCheck; |
| | | } |
| | | this.switchTab(tab.dataset.tab, config); |
| | | } |
| | | } |
| | |
| | | ui.sections.forEach(section => { |
| | | if (section.querySelector('.tabs')) { |
| | | options.hasChildren = true; |
| | | this.registerTab(section, {parent: tabsId}); |
| | | this.registerTab(section, {parent: tabsId, ... options}); |
| | | } |
| | | }); |
| | | |
| | |
| | | let config = { |
| | | id: tabsId, |
| | | ui: ui, |
| | | updateURL: options.updateURL ?? true |
| | | updateURL: options.updateURL ?? true, |
| | | ... options |
| | | }; |
| | | if (Object.hasOwn(options, 'preCheck')) { |
| | | config.preCheck = options.preCheck; |
| | |
| | | ui.nav.addEventListener('click', this.clickHandler); |
| | | ui.select?.addEventListener('change', this.changeHandler); |
| | | this.tabs.set(tabsId, config); |
| | | this.shouldNotify = false; |
| | | this.determineActiveTab(config); |
| | | |
| | | this.shouldNotify = true; |
| | | return config; |
| | | } |
| | | |
| | |
| | | config = (typeof config === 'string') ? this.tabs.get(config) : config; |
| | | if (!config) return; |
| | | let activeTab = config.ui.sections.filter(section => section.classList.contains('active')); |
| | | |
| | | if (Object.hasOwn(config, 'preCheck') && !config.preCheck(activeTab[0], tab, config)) return; |
| | | |
| | | if (document.activeElement && this.isInTabs(document.activeElement, config)) document.activeElement.blur(); |
| | |
| | | if (config?.updateURL) this.updateURL(config); |
| | | if (config.ui.select) this.maybeUpdateSelect(tab, config); |
| | | |
| | | this.a11y.announce(`Switched to ${tab} tab`); |
| | | if (this.shouldNotify) { |
| | | this.a11y.announce(`Switched to ${tab} tab`); |
| | | } |
| | | } |
| | | updateChildTabs(id) { |
| | | Array.from(this.tabs.values()).filter(conf => conf.parent === id).forEach(inst => { |
| | |
| | | } |
| | | |
| | | handleFocus(e) { |
| | | if (!Object.hasOwn(e, 'target')) return; |
| | | if (!this.container.contains(e.target) && !e.target.closest('[data-type="selector"], [data-field-type="selector"]')) { |
| | | return; |
| | | } |
| | |
| | | |
| | | //Hide autocomplete dropdown on blur |
| | | handleBlur(e) { |
| | | if (!Object.hasOwn(e, 'target')) return; |
| | | if (!this.container.contains(e.target) && !e.target.closest('[data-type="selector"], [data-field-type="selector"]')) { |
| | | return; |
| | | } |
| | |
| | | *********************************************************************/ |
| | | getWorker() { |
| | | if (!this.workerState.worker && typeof OffscreenCanvas !== 'undefined') { |
| | | this.workerState.worker = new Worker('worker.js'); |
| | | const workerCode = ` |
| | | self.onmessage = function(e) { |
| | | const { id, imageBitmap, width, height, type, quality } = e.data; |
| | | const canvas = new OffscreenCanvas(width, height); |
| | | const ctx = canvas.getContext('2d'); |
| | | ctx.drawImage(imageBitmap, 0, 0, width, height); |
| | | canvas.convertToBlob({ type, quality }).then(blob => { |
| | | self.postMessage({ id, blob }); |
| | | }); |
| | | }; |
| | | `; |
| | | const blob = new Blob([workerCode], { type: 'application/javascript' }); |
| | | const url = URL.createObjectURL(blob); |
| | | this.workerState.worker = new Worker(url); |
| | | URL.revokeObjectURL(url); // Safe to revoke immediately after Worker is created |
| | | this.workerState.worker.onmessage = (e) => this.handleWorkerMessage(e); |
| | | this.workerState.worker.onerror = (e) => this.handleWorkerError(e); |
| | | } |
| | |
| | | window.auth=new class{constructor(){this.initialized=!1,this.isAuthenticating=!1,this.authenticated=!1,this.user=!1,this.nonces={},this.subscribers=new Set,this.cacheExpiry=3e5,this.init()}async init(){if(!this.isAuthenticating){this.isAuthenticating=!0;try{if("undefined"!=typeof jvbAuth)return this.setAuthData(jvbAuth),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1});await this.fetchAuth()}catch(t){this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}}async refreshNonce(t="wp_rest"){try{return await this.fetchAuth(),this.getNonce(t)}catch(t){return console.error("Failed to refresh nonce:",t),null}}async fetch(t,i={}){const e=async(s=0)=>{const n={...!(i.body instanceof FormData)&&{"Content-Type":"application/json"},...i.headers,"X-WP-Nonce":this.getNonce()},h=await fetch(t,{...i,credentials:"same-origin",headers:n});if((403===h.status||401===h.status)&&0===s){const t=await h.clone().json();if("rest_cookie_invalid_nonce"===t.code||t.message?.includes("Cookie check"))return console.log("Nonce invalid, refreshing auth..."),await this.refresh(),e(s+1)}return h};return e()}async fetchAuth(){const t=await fetch(`${jvbSettings.api}auth/status`,{method:"GET",credentials:"same-origin",headers:{"Content-Type":"application/json"}});if(!t.ok)throw new Error("Auth check failed");const i=await t.json();this.setAuthData(i),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-loaded",{fromCache:!1})}setAuthData(t){const i=this.initialized&&this.authenticated;this.authenticated=t.authenticated||!1,this.user=t.user||!1,this.nonces=t.nonces||{},console.log(this.nonces),i&&!this.authenticated&&(window.location.href=`/login?redirect_to=${encodeURIComponent(window.location.href)}`)}clearAuthData(){this.authenticated=!1,this.user=null,this.nonces={}}async refresh(){this.isAuthenticating=!0,this.initialized=!1;try{await this.fetchAuth(),this.notify("auth-refreshed",{})}catch(t){console.error("Failed to refresh auth:",t),this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}getNonce(t="wp_rest"){return this.nonces[t]||""}getUser(){return this.user}isAuthenticated(){return this.authenticated}async handleLogin(t=null){if(t)return this.setAuthData(t),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1,fromLogin:!0});await this.refresh()}handleLogout(){this.clearAuthData(),this.notify("logged-out",{})}subscribe(t){return this.subscribers.add(t),this.initialized&&t("auth-loaded",{fromCache:!1,immediate:!0}),()=>this.subscribers.delete(t)}notify(t,i){this.subscribers.forEach(e=>{try{e(t,i)}catch(t){console.error("Subscriber error:",t)}})}}; |
| | | window.auth=new class{constructor(){this.initialized=!1,this.isAuthenticating=!1,this.authenticated=!1,this.user=!1,this.nonces={},this.subscribers=new Set,this.cacheExpiry=3e5,this.init()}async init(){if(!this.isAuthenticating){this.isAuthenticating=!0;try{if("undefined"!=typeof jvbAuth)return this.setAuthData(jvbAuth),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1});await this.fetchAuth()}catch(t){this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}}async refreshNonce(t="wp_rest"){try{return await this.fetchAuth(),this.getNonce(t)}catch(t){return console.error("Failed to refresh nonce:",t),null}}async fetch(t,i={}){const e=async(s=0)=>{const n=i.body instanceof FormData,h={...!n&&{"Content-Type":"application/json"},...i.headers,"X-WP-Nonce":this.getNonce()};n||"object"!=typeof i.body||Object.hasOwn(i.body,"user")||(i.body.user=this.getUser());let a={...i,credentials:"same-origin",headers:h};n||"object"!=typeof a.body||(a.body=JSON.stringify(a.body));const r=await fetch(t,a);if((403===r.status||401===r.status)&&0===s){const t=await r.clone().json();if("rest_cookie_invalid_nonce"===t.code||t.message?.includes("Cookie check"))return console.log("Nonce invalid, refreshing auth..."),await this.refresh(),e(s+1)}return r};return e()}async fetchAuth(){const t=await fetch(`${jvbSettings.api}auth/status`,{method:"GET",credentials:"same-origin",headers:{"Content-Type":"application/json"}});if(!t.ok)throw new Error("Auth check failed");const i=await t.json();this.setAuthData(i),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-loaded",{fromCache:!1})}setAuthData(t){const i=this.initialized&&this.authenticated;this.authenticated=t.authenticated||!1,this.user=t.user||!1,this.nonces=t.nonces||{},console.log(this.nonces),i&&!this.authenticated&&(window.location.href=`/login?redirect_to=${encodeURIComponent(window.location.href)}`)}clearAuthData(){this.authenticated=!1,this.user=null,this.nonces={}}async refresh(){this.isAuthenticating=!0,this.initialized=!1;try{await this.fetchAuth(),this.notify("auth-refreshed",{})}catch(t){console.error("Failed to refresh auth:",t),this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}getNonce(t="wp_rest"){return this.nonces[t]||""}getUser(){return this.user}isAuthenticated(){return this.authenticated}async handleLogin(t=null){if(t)return this.setAuthData(t),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1,fromLogin:!0});await this.refresh()}handleLogout(){this.clearAuthData(),this.notify("logged-out",{})}subscribe(t){return this.subscribers.add(t),this.initialized&&t("auth-loaded",{fromCache:!1,immediate:!0}),()=>this.subscribers.delete(t)}notify(t,i){this.subscribers.forEach(e=>{try{e(t,i)}catch(t){console.error("Subscriber error:",t)}})}}; |
| | |
| | | window.jvbCheckout=class{constructor(t={}){this.config=t,this.isInitialized=!1,this.cartItems=new Map,this.checkout=document.querySelector("aside#cart"),this.provider=this.checkout?.querySelector("form")?.dataset.provider||"",this.isOpen="1"===this.config.isOpen||!1,this.isOpen=!0,this.isLoggedIn=this.config.is_logged_in||!1,this.userEmail=this.config.user_email||"",this.savedCards=[],this.selectedCardId=null,this.cartId=null,this.stepMultiplier=1,this.cache=new window.jvbCache("cart",{TTL:864e5}),this.a11y=window.jvbA11y,this.T=window.jvbTemplates,this.initCart(),this.checkout&&(this.initElements(),this.init(),this.initListeners(),this.defineTemplates(),this.isLoggedIn&&this.loadSavedCards()),this.isOpen||this.closeShop(),this.popup=window.jvbPopup.registerPopup({popup:this.checkout,toggle:this.ui.toggle.btn,name:"Cart",onOpen:this.maybeAddEmptyState.bind(this),onClose:this.toggleButton.bind(this)})}closeShop(){document.querySelectorAll("[data-add-to-cart]").forEach(t=>{t.querySelectorAll("button, input").forEach(t=>{t.disabled=!0,t.title="Sorry, we are currently closed."})})}async init(){throw new Error("init() must be implemented by subclass")}async processPayment(t){throw new Error("processPayment() must be implemented by subclass")}async submitToServer(t,e){throw new Error("submitToServer() must be implemented by subclass")}async loadSavedCards(){}async initCart(){let t=await this.cache.get("cart")??new Map;t.size>0&&(this.cartItems=t,this.updateToggle(),this.notifyRestoredCart())}saveCart(){this.updateTotal(),this.updateToggle(),this.cache.set("cart",this.cartItems)}clearCart(){for(let[t,e]of this.cartItems.entries())this.removeFromCart(t);this.cartItems.clear(),Array.from(this.ui.cart.table.children).forEach(t=>{"TBODY"===t.tagName&&t.remove()}),this.saveCart()}getCartId(){return this.cartId||(this.cartId=crypto.randomUUID(),this.cache.set("cart_id",this.cartId)),this.cartId}defineTemplates(){this.T.define("mainCartItem",{refs:{header:".header th"},setup({el:t,refs:e,manyRefs:a,data:i}){e.header.textContent=i.label,t.dataset.label=i.label,t.dataset.id=i.parentId}}),this.T.define("childCartItem",{refs:{quantity:".quantity",label:"label",price:"span.price",button:"[data-remove-from-cart]"},manyRefs:{},setup({el:t,refs:e,manyRefs:a,data:i}){[e.quantity.dataset.id,e.quantity.dataset.price,e.quantity.dataset.catalogId,e.label.textContent,e.price.textContent,e.button.dataset.id]=[i.id,i.price,i.catalogId||"",i.name,window.formatPrice(i.price),i.id]}}),this.T.define("emptyCart")}initElements(){this.restoreElement=!1,this.selectors={toggle:{btn:".toggle-cart",count:".toggle-cart .count",icon:".toggle-cart .icon"},panels:{checkout:{toggle:'button[data-tab="checkout"]',form:"#cart form"}},cart:{wrap:"#cart .cart-items",empty:"#cart .cart-items .empty",table:"#cart .cart-items table",body:"#cart .cart-items tbody",total:"#cart .cart-total",foot:{foot:"#cart tfoot",total:"#cart tfoot .total",tax:"#cart tfoot .tax"},grandTotal:"#cart .total span",tax:"#cart .cart-total .tax span"}},this.ui=window.uiFromSelectors(this.selectors),this.ui.cart.items=new Map,this.isOpen||(this.ui.toggle.btn.disabled=!0,this.ui.toggle.btn.title="Currently closed for online ordering"),this.checkoutForm=this.checkout.querySelector("form"),this.tabs=window.jvbTabs.registerTab(this.checkoutForm,{updateURL:!1})}initListeners(){this.clickHandler=this.handleClick.bind(this),this.keyHandler=this.handleEscape.bind(this),this.changeHandler=this.handleChange.bind(this),this.checkoutForm.addEventListener("submit",t=>this.handleFormSubmit(t)),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler)}handleClick(t){if(window.targetCheck(t,"button")&&window.targetCheck(t,"div.quantity")){let e=window.targetCheck(t,"div.quantity");this.handleNumberClick(t,e)}else if(window.targetCheck(t,"[data-add-to-cart]")&&window.targetCheck(t,"button")){let e=window.targetCheck(t,"[data-add-to-cart]");this.handleAddToCart(e)}else if(window.targetCheck(t,"[data-remove-from-cart]")){let e=window.targetCheck(t,"[data-remove-from-cart]");this.handleRemoveFromCart(e)}else window.targetCheck(t,"[data-clear-cart]")?(this.clearCart(),document.querySelector(".restored")?.remove()):window.targetCheck(t,"[data-dismiss]")&&window.targetCheck(t,"[data-dismiss]").closest(".restored")?.remove()}handleChange(t){if(window.targetCheck(t,".quantity-input")){let e=t.target.closest(".quantity");t.target.value>0?this.handleAddToCart(e):this.handleRemoveFromCart(e)}}handleEscape(t){"Escape"===t.key?(this.restoreElement&&(this.restoreElement.classList.remove("show"),this.restoreElement=!1),this.stepMultiplier=1):t.ctrlKey&&t.shiftKey?this.stepMultiplier=Math.max(100*parseInt(this.stepMultiplier),1e3):t.shiftKey&&(this.stepMultiplier=Math.max(10*parseInt(this.stepMultiplier),1e3))}handleAddToCart(t){let e=t.dataset.id,a=parseFloat(t.dataset.price),i=parseInt(t.querySelector(".quantity input")?.value)??1,r=parseFloat(a*i);this.createItemElement(t);let s=this.determineParent(t),n={};this.cartItems.has(e)&&(n=this.cartItems.get(e));let o={post_id:e,name:n.name??t.dataset.name,label:n.label??s.dataset.label??"",parentId:n.parentId??s.dataset.id??"",price:a,quantity:i,total:r,catalog_id:n.catalogId??t.dataset.catalogId??""};this.cartItems.set(e,o),this.saveCart()}handleRemoveFromCart(t){if(console.log(t),confirm("This will remove this item from the cart. Continue?")){let e=t.dataset.id??t.id??!1;e||console.error("No ID found.."),this.removeFromCart(e),this.maybeAddEmptyState(),this.saveCart()}}removeFromCart(t){let e=this.findParentItem(t);if(e){let a=e.items.get(t);a.el.remove(),e.items.delete(t),a.reference&&(a.reference.value=0),0===e.items.size&&(console.log("Removing parent item"),e.el.remove(),this.ui.cart.items.delete(e.id))}this.cartItems.delete(t)}handleNumberClick(t,e){t.preventDefault();let a=0;if(t.target.closest(".increase")?a+=1:t.target.closest(".decrease")&&(a-=1),0!==a){let t=e.querySelector("input"),i=parseInt(t.step),r=""===t.value?0:parseInt(t.value);if(0===r&&a<0)return;let s=r+i*a*this.stepMultiplier;t.value=Math.max(s,0),t.dispatchEvent(new Event("change",{bubbles:!0})),this.handleNumberLimits(e)}}handleNumberLimits(t){let e=t.dataset.min,a=t.dataset.max,i=t.querySelector("input"),r=t.querySelector(".increase"),s=t.querySelector(".decrease"),n=parseInt(i.value);void 0!==e&&n<=e?(console.log("Resetting to minimum value"),i.value=e,s.disabled=!0):void 0!==a&&n>=a?(console.log("Resetting to maximum value"),i.value=a,r.disabled=!0):(r.disabled=!1,s.disabled=!1)}createItemElement(t){let e=this.maybeCreateCartItem(t),a=void 0===t.tagName?t.quantity:t.querySelector("input").value??1,i=void 0===t.tagName?t.price:t.dataset.price;e.quantity.value=a,e.total.textContent=window.formatPrice(a*i),e.reference&&(e.reference.value=a)}maybeCreateCartItem(t){let e={};if(void 0===t.tagName)e={id:String(t.post_id),name:t.name,price:t.price,catalogId:t.catalog_id||"",quantity:t.quantity,label:t.label,parentId:String(t.parentId)};else if(t.closest("#cart")){let e=t.dataset.id,a=this.findParentItem(e);if(a)return a.items.get(e);console.warn("Cart quantity changed but no existing row found for id:",e)}else{e={id:t.dataset.id,name:t.dataset.name,price:t.dataset.price,catalogId:t.dataset.catalogId||""};let a=this.determineParent(t);e.parentId=a.dataset.id??a.id,e.label=a.dataset.label??a.label}if(!this.ui.cart.items.has(e.parentId)){let t=this.T.create("mainCartItem",e);this.ui.cart.items.set(e.parentId,{el:t,id:e.parentId,label:e.label,items:new Map}),this.ui.cart.table.insertBefore(t,this.ui.cart.foot.foot)}let a=this.ui.cart.items.get(e.parentId);if(!a.items.has(e.id)){let t=this.T.create("childCartItem",e);a.items.set(e.id,{el:t,quantity:t.querySelector('[name="quantity"]'),total:t.querySelector(".total"),reference:document.querySelector(`.menu-items [data-id="${e.id}"] input`)}),a.el.append(t)}return a.items.get(e.id)}determineParent(t){if(void 0!==t.tagName)return this.parentFromHTML(t)}parentFromHTML(t){let e=t.closest(".menu-item");if(e)return e;let a=t.dataset.id;return this.findParentItem(a).el??!1}findParentItem(t){let e=!1;for(let[a,i]of this.ui.cart.items.entries())i.items.has(t)&&(e=this.ui.cart.items.get(a));return e}toggleButton(t=!1){this.ui.toggle.icon.hidden=t}maybeAddEmptyState(){this.toggleButton(!0),this.findEmpty(),0===this.cartItems.size?(this.ui.panels.checkout.toggle.disabled=!0,this.ui.panels.checkout.toggle.title="Add some things to your cart first!",this.ui.cart.empty.hidden=!1,this.ui.cart.table.hidden=!0,this.ui.cart.total.hidden=!0,this.a11y.announce("Nothing in Cart")):(this.ui.cart.empty.hidden=!0,this.ui.panels.checkout.toggle.disabled=!1,this.ui.cart.table.hidden=!1,this.ui.cart.total.hidden=!1,this.ui.panels.checkout.toggle.title="Checkout")}findEmpty(){this.ui.cart.empty||(this.ui.cart.empty=this.T.create("emptyCart"),this.ui.cart.wrap.append(this.ui.cart.empty))}notifyRestoredCart(){let t=window.getTemplate("restoredCart");document.body.append(t),t.classList.toggle("show"),this.restoreElement=t,this.cartItems.forEach(t=>{this.createItemElement(t)}),this.updateTotal()}updateToggle(){this.cartItems.size>0&&this.ui.toggle.btn.classList.contains("empty")&&this.ui.toggle.btn.classList.remove("empty"),0!==this.cartItems.size||this.ui.toggle.btn.classList.contains("empty")||this.ui.toggle.btn.classList.add("empty");let t=0;for(let[e,a]of this.cartItems.entries())t+=a.quantity;this.ui.toggle.count.textContent=0===this.cartItems.size?"":t}updateTotal(){let t=0;this.cartItems.forEach(e=>t+=e.total);let e=.05*t;window.eraseText(this.ui.cart.tax),window.eraseText(this.ui.cart.foot.tax),window.eraseText(this.ui.cart.foot.total),window.typeText(this.ui.cart.foot.tax,window.formatPrice(e)),window.typeText(this.ui.cart.grandTotal,window.formatPrice(t+e)),window.typeText(this.ui.cart.foot.total,window.formatPrice(t+e)),this.ui.cart.tax.classList.remove("typeText")}extractOrderData(t){const e=Array.from(this.cartItems.values()).map(t=>({catalog_id:t.catalog_id,quantity:String(t.quantity),price:t.price,note:t.note||""})),a=e.reduce((t,e)=>t+e.price*e.quantity,0);return{total:Math.round(100*a),items:e,customer:{email:this.isLoggedIn?this.userEmail:t.querySelector('[name="cart_email"]')?.value||"",name:t.querySelector('[name="cart_name"]')?.value||"",phone:t.querySelector('[name="cart_phone"]')?.value||""},note:t.querySelector('[name="special_instructions"]')?.value||"",pickup_time:t.querySelector('[name="pickup_time"]')?.value||""}}async handleFormSubmit(t){if(!this.isOpen)return;if(t.preventDefault(),!this.isInitialized)return void this.handleError("Checkout not initialized");const e=t.target,a=this.extractOrderData(e);try{window.jvbLoading?.showLoading?.("Processing payment...");const t=await this.processPayment(a);this.handleSuccess(t,e)}catch(t){this.handleError(t)}finally{window.jvbLoading?.hideLoading?.()}}trackOrder(t){this.orderId=t,this.scheduleOrderCheck(),this.checkout.querySelector("button[data-tab=order]").hidden=!1}scheduleOrderCheck(){window.debouncer.schedule("order",()=>this.checkOrderStatus(),3e4)}async checkOrderStatus(){const t=await fetch(`${this.config.api_url}order-status/${this.orderId}`,{headers:{"X-WP-Nonce":this.config.nonce}}),e=await t.json();"ready"!==e.status&&this.scheduleOrderCheck(),this.updateOrderStatus(e)}updateOrderStatus(t){this.checkout.querySelectorAll(".status-item").forEach(e=>{e.dataset.status===t.status&&e.classList.add("active")}),this.checkout.querySelector("#eta").textContent=t.eta||"In progress"}renderSavedCards(){const t=document.getElementById("saved-cards");if(!t||0===this.savedCards.length)return;const e=`\n\t\t\t<div class="saved-cards-section">\n\t\t\t\t<h4>Saved Payment Methods</h4>\n\t\t\t\t${this.savedCards.map(t=>`\n\t\t\t\t\t<label class="saved-card">\n\t\t\t\t\t\t<input type="radio" name="payment-method" value="saved" data-card-id="${t.id}">\n\t\t\t\t\t\t<span class="card-info">\n\t\t\t\t\t\t\t<strong>${t.card_brand}</strong> ending in ${t.last_4}\n\t\t\t\t\t\t\t<small>Exp: ${t.exp_month}/${t.exp_year}</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t`).join("")}\n\t\t\t\t<label class="saved-card">\n\t\t\t\t\t<input type="radio" name="payment-method" value="new" checked>\n\t\t\t\t\t<span>Use a new card</span>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t`;t.innerHTML=e,t.querySelectorAll('input[name="payment-method"]').forEach(t=>{t.addEventListener("change",t=>{const e="new"===t.target.value,a=document.getElementById("payment-container");a&&(a.style.display=e?"block":"none"),this.selectedCardId=e?null:t.target.dataset.cardId})})}handleSuccess(t,e){document.dispatchEvent(new CustomEvent("checkoutSuccess",{detail:{result:t,form:e,provider:this.provider}}));const a=e.dataset.successUrl||`/order-confirmation/?order=${t.order_id||t.wp_order_id}`;window.location.href=a}handleError(t){console.error(`${this.provider} checkout error:`,t),document.dispatchEvent(new CustomEvent("checkoutError",{detail:{error:t,provider:this.provider}})),window.jvbNotifications?.show?.(t.message||t||"Payment failed","error")}}; |
| | | window.jvbCheckout=class{constructor(t={}){this.config=t,this.isActive=!1,this.isInitialized=!1,this.cartItems=new Map,this.checkout=document.querySelector("aside#cart"),this.provider=this.checkout?.querySelector("form")?.dataset.provider||"",this.isOpen="1"===this.config.isOpen||!1,this.isOpen=!0,this.api=`${jvbSettings.api}${this.provider}/`,this.isLoggedIn=this.config.is_logged_in||!1,this.userEmail=this.config.user_email||"",this.savedCards=[],this.selectedCardId=null,this.cartId=null,this.stepMultiplier=1,this.cache=new window.jvbCache("cart",{TTL:864e5}),this.a11y=window.jvbA11y,this.T=window.jvbTemplates,this.initCart(),this.checkout&&(this.initElements(),this.init(),this.initListeners(),this.defineTemplates(),this.isLoggedIn&&this.isActive&&(console.log("Loading saved cards"),this.loadSavedCards())),this.isOpen||this.closeShop(),this.popup=window.jvbPopup.registerPopup({popup:this.checkout,toggle:this.ui.toggle.btn,name:"Cart",onOpen:this.maybeAddEmptyState.bind(this),onClose:this.toggleButton.bind(this)})}closeShop(){document.querySelectorAll("[data-add-to-cart]").forEach(t=>{t.querySelectorAll("button, input").forEach(t=>{t.disabled=!0,t.title="Sorry, we are currently closed."})})}async init(){throw new Error("init() must be implemented by subclass")}async processPayment(t){throw new Error("processPayment() must be implemented by subclass")}async submitToServer(t,e){throw new Error("submitToServer() must be implemented by subclass")}async loadSavedCards(){}async initCart(){let t=await this.cache.get("cart")??new Map;t.size>0&&(this.cartItems=t,this.updateToggle(),this.notifyRestoredCart())}saveCart(){this.updateTotal(),this.updateToggle(),this.cache.set("cart",this.cartItems)}clearCart(){for(let[t,e]of this.cartItems.entries())this.removeFromCart(t);this.cartItems.clear(),Array.from(this.ui.cart.table.children).forEach(t=>{"TBODY"===t.tagName&&t.remove()}),this.saveCart()}getCartId(){return this.cartId||(this.cartId=crypto.randomUUID(),this.cache.set("cart_id",this.cartId)),this.cartId}defineTemplates(){this.T.define("mainCartItem",{refs:{header:".header th"},setup({el:t,refs:e,manyRefs:a,data:i}){e.header.textContent=i.label,t.dataset.label=i.label,t.dataset.id=i.parentId}}),this.T.define("childCartItem",{refs:{quantity:".quantity",label:"label",price:"span.price",button:"[data-remove-from-cart]"},manyRefs:{},setup({el:t,refs:e,manyRefs:a,data:i}){[e.quantity.dataset.id,e.quantity.dataset.price,e.quantity.dataset.catalogId,e.label.textContent,e.price.textContent,e.button.dataset.id]=[i.id,i.price,i.catalogId||"",i.name,window.formatPrice(i.price),i.id]}}),this.T.define("emptyCart")}initElements(){this.restoreElement=!1,this.selectors={pass:"#magic-link-form a[data-tab]",toggle:{btn:".toggle-cart",count:".toggle-cart .count",icon:".toggle-cart .icon"},panels:{checkout:{toggle:'button[data-tab="checkout"]',form:"form#checkout"},order:{toggle:'button[data-tab="order"]'},login:{pass:'button[data-tab="login"]'}},cart:{wrap:"#cart .cart-items",empty:"#cart .cart-items .empty",table:"#cart .cart-items table",body:"#cart .cart-items tbody",total:"#cart .cart-total",foot:{foot:"#cart tfoot",total:"#cart tfoot .total",tax:"#cart tfoot .tax"}}},this.ui=window.uiFromSelectors(this.selectors),this.ui.cart.items=new Map,this.isOpen||(this.ui.toggle.btn.disabled=!0,this.ui.toggle.btn.title="Currently closed for online ordering"),this.tabs=window.jvbTabs.registerTab(this.checkout,{updateURL:!1})}initListeners(){this.clickHandler=this.handleClick.bind(this),this.keyHandler=this.handleEscape.bind(this),this.changeHandler=this.handleChange.bind(this),this.ui.panels.checkout.form?.addEventListener("submit",t=>this.handleFormSubmit(t)),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler)}handleClick(t){if(t.target===this.ui.pass)t.preventDefault(),window.jvbTabs.switchTab("login",window.jvbTabs.getConfig(this.ui.panels.login.pass));else if(window.targetCheck(t,"button")&&window.targetCheck(t,"div.quantity")){let e=window.targetCheck(t,"div.quantity");this.handleNumberClick(t,e)}else if(window.targetCheck(t,"[data-add-to-cart]")&&window.targetCheck(t,"button")){let e=window.targetCheck(t,"[data-add-to-cart]");this.handleAddToCart(e)}else if(window.targetCheck(t,"[data-remove-from-cart]")){let e=window.targetCheck(t,"[data-remove-from-cart]");this.handleRemoveFromCart(e)}else window.targetCheck(t,"[data-clear-cart]")?(this.clearCart(),document.querySelector(".restored")?.remove()):window.targetCheck(t,"[data-dismiss]")&&window.targetCheck(t,"[data-dismiss]").closest(".restored")?.remove()}handleChange(t){if(window.targetCheck(t,".quantity-input")){let e=t.target.closest(".quantity");t.target.value>0?this.handleAddToCart(e):this.handleRemoveFromCart(e)}}handleEscape(t){"Escape"===t.key?(this.restoreElement&&(this.restoreElement.classList.remove("show"),this.restoreElement=!1),this.stepMultiplier=1):t.ctrlKey&&t.shiftKey?this.stepMultiplier=Math.max(100*parseInt(this.stepMultiplier),1e3):t.shiftKey&&(this.stepMultiplier=Math.max(10*parseInt(this.stepMultiplier),1e3))}handleAddToCart(t){let e=t.dataset.id,a=parseFloat(t.dataset.price),i=parseInt(t.querySelector(".quantity input")?.value)??1,s=parseFloat(a*i);this.createItemElement(t);let r=this.determineParent(t),n={};this.cartItems.has(e)&&(n=this.cartItems.get(e));let o={post_id:e,name:n.name??t.dataset.name,label:n.label??r.dataset.label??"",parentId:n.parentId??r.dataset.id??"",price:a,quantity:i,total:s,catalog_id:n.catalogId??t.dataset.catalogId??""};this.cartItems.set(e,o),this.saveCart()}handleRemoveFromCart(t){if(console.log(t),confirm("This will remove this item from the cart. Continue?")){let e=t.dataset.id??t.id??!1;e||console.error("No ID found.."),this.removeFromCart(e),this.maybeAddEmptyState(),this.saveCart()}}removeFromCart(t){let e=this.findParentItem(t);if(e){let a=e.items.get(t);a.el.remove(),e.items.delete(t),a.reference&&(a.reference.value=0),0===e.items.size&&(console.log("Removing parent item"),e.el.remove(),this.ui.cart.items.delete(e.id))}this.cartItems.delete(t)}handleNumberClick(t,e){t.preventDefault();let a=0;if(t.target.closest(".increase")?a+=1:t.target.closest(".decrease")&&(a-=1),0!==a){let t=e.querySelector("input"),i=parseInt(t.step),s=""===t.value?0:parseInt(t.value);if(0===s&&a<0)return;let r=s+i*a*this.stepMultiplier;t.value=Math.max(r,0),t.dispatchEvent(new Event("change",{bubbles:!0})),this.handleNumberLimits(e)}}handleNumberLimits(t){let e=t.dataset.min,a=t.dataset.max,i=t.querySelector("input"),s=t.querySelector(".increase"),r=t.querySelector(".decrease"),n=parseInt(i.value);void 0!==e&&n<=e?(console.log("Resetting to minimum value"),i.value=e,r.disabled=!0):void 0!==a&&n>=a?(console.log("Resetting to maximum value"),i.value=a,s.disabled=!0):(s.disabled=!1,r.disabled=!1)}createItemElement(t){let e=this.maybeCreateCartItem(t),a=void 0===t.tagName?t.quantity:t.querySelector("input").value??1,i=void 0===t.tagName?t.price:t.dataset.price;e.quantity.value=a,e.total.textContent=window.formatPrice(a*i),e.reference&&(e.reference.value=a)}maybeCreateCartItem(t){let e={};if(void 0===t.tagName)e={id:String(t.post_id),name:t.name,price:t.price,catalogId:t.catalog_id||"",quantity:t.quantity,label:t.label,parentId:String(t.parentId)};else if(t.closest("#cart")){let e=t.dataset.id,a=this.findParentItem(e);if(a)return a.items.get(e);console.warn("Cart quantity changed but no existing row found for id:",e)}else{e={id:t.dataset.id,name:t.dataset.name,price:t.dataset.price,catalogId:t.dataset.catalogId||""};let a=this.determineParent(t);e.parentId=a.dataset.id??a.id,e.label=a.dataset.label??a.label}if(!this.ui.cart.items.has(e.parentId)){let t=this.T.create("mainCartItem",e);this.ui.cart.items.set(e.parentId,{el:t,id:e.parentId,label:e.label,items:new Map}),this.ui.cart.table.insertBefore(t,this.ui.cart.foot.foot)}let a=this.ui.cart.items.get(e.parentId);if(!a.items.has(e.id)){let t=this.T.create("childCartItem",e);a.items.set(e.id,{el:t,quantity:t.querySelector('[name="quantity"]'),total:t.querySelector(".total"),reference:document.querySelector(`.menu-items [data-id="${e.id}"] input`)}),a.el.append(t)}return a.items.get(e.id)}determineParent(t){if(void 0!==t.tagName)return this.parentFromHTML(t)}parentFromHTML(t){let e=t.closest(".menu-item");if(e)return e;let a=t.dataset.id;return this.findParentItem(a).el??!1}findParentItem(t){let e=!1;for(let[a,i]of this.ui.cart.items.entries())i.items.has(t)&&(e=this.ui.cart.items.get(a));return e}toggleButton(t=!1){this.ui.toggle.icon.hidden=t}maybeAddEmptyState(){this.toggleButton(!0),this.findEmpty(),0===this.cartItems.size?(this.ui.panels.checkout.toggle.disabled=!0,this.ui.panels.checkout.toggle.title="Add some things to your cart first!",this.ui.cart.empty.hidden=!1,this.ui.cart.table.hidden=!0,this.a11y.announce("Nothing in Cart")):(this.ui.cart.empty.hidden=!0,this.ui.panels.checkout.toggle.disabled=!this.isActive,this.isActive||(this.ui.panels.checkout.toggle.title="We're experiencing technical difficulties. Try calling or coming in person."),this.ui.cart.table.hidden=!1,this.ui.panels.checkout.toggle.title="Checkout")}findEmpty(){this.ui.cart.empty||(this.ui.cart.empty=this.T.create("emptyCart"),this.ui.cart.wrap.append(this.ui.cart.empty))}notifyRestoredCart(){let t=window.getTemplate("restoredCart");document.body.append(t),t.classList.toggle("show"),this.restoreElement=t,this.cartItems.forEach(t=>{this.createItemElement(t)}),this.updateTotal()}updateToggle(){this.cartItems.size>0&&this.ui.toggle.btn.classList.contains("empty")&&this.ui.toggle.btn.classList.remove("empty"),0!==this.cartItems.size||this.ui.toggle.btn.classList.contains("empty")||this.ui.toggle.btn.classList.add("empty");let t=0;for(let[e,a]of this.cartItems.entries())t+=a.quantity;this.ui.toggle.count.textContent=0===this.cartItems.size?"":t}updateTotal(){let t=0;this.cartItems.forEach(e=>t+=e.total);let e=.05*t;window.eraseText(this.ui.cart.foot.tax),window.eraseText(this.ui.cart.foot.total),window.typeText(this.ui.cart.foot.tax,window.formatPrice(e)),window.typeText(this.ui.cart.foot.total,window.formatPrice(t+e)),this.ui.cart.foot.tax.classList.remove("typeText"),this.ui.cart.foot.total.classList.remove("typeText")}extractOrderData(t){const e=Array.from(this.cartItems.values()).map(t=>({catalog_id:t.catalog_id,quantity:String(t.quantity),price:t.price,note:t.note||""})),a=e.reduce((t,e)=>t+e.price*e.quantity,0);return{total:Math.round(100*a),items:e,customer:{email:this.isLoggedIn?this.userEmail:t.querySelector('[name="cart_email"]')?.value||"",name:t.querySelector('[name="cart_name"]')?.value||"",phone:t.querySelector('[name="cart_phone"]')?.value||""},note:t.querySelector('[name="special_instructions"]')?.value||"",pickup_time:t.querySelector('[name="pickup_time"]')?.value||""}}async handleFormSubmit(t){if(!this.isOpen)return;if(t.preventDefault(),!this.isInitialized)return void this.handleError("Checkout not initialized");const e=t.target,a=this.extractOrderData(e);try{const t=await this.processPayment(a);this.handleSuccess(t,e)}catch(t){this.handleError(t)}}trackOrder(t){this.orderId=t,this.scheduleOrderCheck(),this.ui.panels.order.toggle.hidden=!1}scheduleOrderCheck(){window.debouncer.schedule("order",()=>this.checkOrderStatus(),3e4)}async checkOrderStatus(){const t=await window.auth.fetch(`${this.api}order-status/${this.orderId}`),e=await t.json();"ready"!==e.status&&this.scheduleOrderCheck(),this.updateOrderStatus(e)}updateOrderStatus(t){this.checkout.querySelectorAll(".status-item").forEach(e=>{e.dataset.status===t.status&&e.classList.add("active")}),this.checkout.querySelector("#eta").textContent=t.eta||"In progress"}renderSavedCards(){const t=document.getElementById("saved-cards");if(!t||0===this.savedCards.length)return;console.log("Yup");const e=`\n\t\t\t<div class="saved-cards-section">\n\t\t\t\t<h4>Saved Payment Methods</h4>\n\t\t\t\t${this.savedCards.map(t=>`\n\t\t\t\t\t<label class="saved-card">\n\t\t\t\t\t\t<input type="radio" name="payment-method" value="saved" data-card-id="${t.id}">\n\t\t\t\t\t\t<span class="card-info">\n\t\t\t\t\t\t\t<strong>${t.card_brand}</strong> ending in ${t.last_4}\n\t\t\t\t\t\t\t<small>Exp: ${t.exp_month}/${t.exp_year}</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t`).join("")}\n\t\t\t\t<label class="saved-card">\n\t\t\t\t\t<input type="radio" name="payment-method" value="new" checked>\n\t\t\t\t\t<span>Use a new card</span>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t`;t.innerHTML=e,t.querySelectorAll('input[name="payment-method"]').forEach(t=>{t.addEventListener("change",t=>{const e="new"===t.target.value,a=document.getElementById("payment-container");a&&(a.style.display=e?"block":"none"),this.selectedCardId=e?null:t.target.dataset.cardId})})}handleSuccess(t,e){document.dispatchEvent(new CustomEvent("checkoutSuccess",{detail:{result:t,form:e,provider:this.provider}}));const a=e.dataset.successUrl||`/order-confirmation/?order=${t.order_id||t.wp_order_id}`;window.location.href=a}handleError(t){console.error(`${this.provider} checkout error:`,t),document.dispatchEvent(new CustomEvent("checkoutError",{detail:{error:t,provider:this.provider}})),window.jvbNotifications?.show?.(t.message||t||"Payment failed","error")}}; |
| | |
| | | (()=>{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.isPopulating=!0,this.modals.edit.handleOpen(),this.forms.registerForm(this.ui.modals.edit.form,{cache:!1,autoUpload:!0}),this.populate.populate(this.ui.modals.edit.form,s),requestAnimationFrame(()=>{requestAnimationFrame(()=>{setTimeout(()=>{this.isPopulating=!1},50)})}))}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-WP-Nonce":window.auth.getNonce(),"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),l.prefix=`${l.id}-`,window.jvbPopulate.populate(e,l),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"}},wrap:"#item-wrap",grid:`.${this.content}.item-grid`,table:{nav:"#vertical",form:"form.table",table:"form.table table",body:"form.table tbody",head:"form.table thead",foot:"form.table tfoot",selectedColumns:".all-filters .multi-select",columns:"thead th"},bulk:{hasEdit:'.bulk-action-select [value="edit"]',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:this.selectors.wrap},item:{idAttribute:"itemId"}},null!==this.ui.views.table),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||(this.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),this.tabNav="horizontal"!==s;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("table"===this.view&&this.handleTableChange(e),t||s||i||a){if(this.isPopulating||!t||e.target.closest("[data-ignore], .select-item"))return a?(this.items.clear(),void this.handleViewChange(e.target)):void(i?this.handleBulkAction(e.target):s&&this.handleFilterChange(e.target));this.handleItemUpdate(e)}}handleTableChange(e){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"))}}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.children).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.isPopulating=!0,this.modals.edit.handleOpen(),this.forms.registerForm(this.ui.modals.edit.form,{cache:!1,autoUpload:!0}),this.populate.populate(this.ui.modals.edit.form,s),requestAnimationFrame(()=>{requestAnimationFrame(()=>{setTimeout(()=>{this.isPopulating=!1},50)})}))}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-WP-Nonce":window.auth.getNonce(),"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.ui.wrap.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.append(e)},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.reset(),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.a11y=window.jvbA11y,this.error=window.jvbError,this.queue=window.jvbQueue,this.populate=window.jvbPopulate,this.changes=new Map,this.forms=new Map,this.inputs=new Map,this.repeaters=new Map,this.tagLists=new Map,this.charLimits=new Map,this.quantityFields=new Map,this.quillInstances=new Map,this.dependencies=new Map,this.subscribers=new Set,this.isRestoring=!1,this.hasListeners=!1,this.hasUploads=!1,this.summaryTemplate=!1,this.init()}init(){this.templates=window.jvbTemplates,this.defineSummaryTemplate(),this.initElements(),this.initListeners(),this.initStore(),this.initValidators(),this.initUploadSubscription()}initUploadSubscription(){window.jvbUploads.subscribe((e,t)=>{if(this.hasUploads&&"upload-received"===e){let e=this.getForm(t.field);e&&this.updateItem(`${t.field.dataset.field}_tempUpload`,t.id,e)}})}initElements(){this.inputSelectors="input, textarea, select",this.selectors={tabs:{nav:"nav.tabs",sections:".tab-content",progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},buttons:"nav.tabs button"},dependsOn:"[data-depends-on]",forms:{status:{status:".fstatus",message:".fstatus .message",icon:".fstatus .icon",actions:".fstatus .actions"},restore:{container:".restore-form",restore:'[data-action="restore"]',clear:'[data-action="clear"]'}},inputs:this.inputSelectors,fields:{field:".field",label:"label",success:".success",error:".error",message:".validation-message"},repeater:{repeater:".repeater",row:".repeater-row",handle:".drag-handle",header:".repeater-row-header",remove:".remove-row",add:".add-repeater-row",template:"template",items:".repeater-items",inputs:this.inputSelectors},tagList:{tagList:".field.tag-list",input:".row",add:".add-tag",remove:".remove-tag",label:".tag-label",items:".tag-items",item:".tag-item",inputs:this.inputSelectors,value:'input[type="hidden"]'},tag:{label:".tag-label"},number:{number:".field div.quantity",increase:"button.increase",decrease:"button.decrease",input:'input[type="number"]'},limits:{hasLimit:"[data-maxlength]",limit:".limit",current:".current"}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.blurHandler=this.handleBlur.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleSubmit.bind(this),this.quantityClick=this.handleQuantityClick.bind(this),this.repeaterClick=this.handleRepeaterClick.bind(this),this.tagListClick=this.handleTagListClick.bind(this),this.tagListInput=this.handleTagListInput.bind(this)}addFormListeners(e){e.addEventListener("click",this.clickHandler),e.addEventListener("change",this.changeHandler),e.addEventListener("input",this.inputHandler),e.addEventListener("blur",this.blurHandler),e.addEventListener("submit",this.submitHandler)}removeFormListeners(e){e.removeEventListener("click",this.clickHandler),e.removeEventListener("change",this.changeHandler),e.removeEventListener("input",this.inputHandler),e.removeEventListener("blur",this.blurHandler),e.removeEventListener("submit",this.submitHandler)}initStore(){const e=window.jvbStore.register("forms",{storeName:"forms",keyPath:"id",indexes:[{name:"src",keyPath:"src"},{name:"timestamp",keyPath:"timestamp"},{name:"formType",keyPath:"type"}],TTL:1008e4});this.store=e.forms,this.store.subscribe((e,t)=>{if("data-ready"===e){let e=this.store.getFiltered().filter(e=>e.src===window.location.pathname);for(let t of e)this.showPendingNotification(t.id,t.changes)}else"operation-status"===e&&"completed"===t.status&&t.config&&this.store.delete(t.config.id)})}showPendingNotification(e,t){let s=this.forms.get(e);if(!s)return;let i=s.element;if(!i)return void console.warn(`Form element not found for: ${e}`);s.ui.restore.container.hidden=!1;const a=async(e,t)=>{this.isRestoring=!0;let i={fields:e};await this.checkStoredUploads(e,t),this.populate.populate(t,i),this.a11y.announce("Previous changes restored"),this.isRestoring=!1,s.ui.restore.container.remove()},r=async e=>{await this.checkStoredUploads(t,i,!1),await this.store.delete(e),this.a11y.announce("Previous changes discarded"),s.ui.restore.container.remove()};s.ui.restore.restore.addEventListener("click",()=>a(t,i)),s.ui.restore.clear.addEventListener("click",async()=>r(e))}async checkStoredUploads(e,t,s=!0){let i=this.forms.get(t.dataset.formId);if(!i)return;let a=[];for(let[t,s]of Object.entries(e))if(t.includes("_tempUpload")){let e=t.replace("_tempUpload","");Object.hasOwn(i.ui.uploads,e)&&(a=[...a,...s])}a.length>0&&(s?await window.jvbUploads.restoreUploads(a):await window.jvbUploads.clearUploads(a))}initValidators(){this.validators={email:{pattern:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,message:"Please enter a valid email address"},url:{pattern:/^https?:\/\/.+\..+/,message:"Please enter a valid URL starting with https://"},phone:{pattern:/^[\d\s\-+().]+$/,message:"Please enter a valid phone number"},number:{test:(e,t)=>{const s=parseFloat(e);if(isNaN(s))return"Please enter a valid number";const i=t.dataset.min,a=t.dataset.max;return void 0!==i&&s<parseFloat(i)?`Value must be at least ${i}`:!(void 0!==a&&s>parseFloat(a))||`Value must be at most ${a}`}},text:{test:(e,t)=>{const s=t.dataset.minlength,i=t.dataset.maxlength;return s&&e.length<parseInt(s)?`Must be at least ${s} characters`:!(i&&e.length>parseInt(i))||`Must be no more than ${i} characters`}}}}validateField(e){const t=this.performValidation(e);return this.updateValidationUI(e,t),t.isValid}performValidation(e){const t=e.closest(".field"),s=this.getFieldCheckedValue(e);if(!s&&!e.required)return{isValid:!0,message:""};if(e.required)if("checkbox"===e.type){if(!e.checked)return{isValid:!1,message:"This field is required"}}else if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`);if(!Array.from(t).some(e=>e.checked))return{isValid:!1,message:"Please select an option"}}else if(!s)return{isValid:!1,message:"This field is required"};if(e.checkValidity&&!e.checkValidity())return{isValid:!1,message:e.validationMessage};if(s&&Object.hasOwn(t.dataset,"pattern")){if(!new RegExp(t.dataset.pattern).test(s))return{isValid:!1,message:t.dataset.validationMessage||"Invalid format"}}if(Object.hasOwn(t.dataset,"validate")||e.type){const i=this.validators[t.dataset.validate||e.type];if(i&&i.pattern&&!i.pattern.test(s))return{isValid:!1,message:i.message};if(i&&i.test){const e=i.test(s,t);if(!0!==e)return{isValid:!1,message:e}}}return{isValid:!0,message:""}}updateValidationUI(e,t){t.isValid?this.showSuccess(e,t.message):this.showError(e,t.message)}handleClick(e){let t=this.getForm(e.target);if(!t)return;const s=window.targetCheck(e,"[data-action]");if(s){switch(s.dataset.action){case"clear-form":this.store.delete(t.id),t.element.reset(),t.ui.status.status.hidden=!0,this.a11y.announce("Form cleared, starting fresh");break;case"dismiss-restore":t.ui.status.status.hidden=!0;break;case"next-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s+1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}});break;case"prev-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s-1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}})}}}handleChange(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getField(e.target);const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');if(s){if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}const e=s.dataset.field;return this.maybeUpdateCollectionDisplay(t),void window.debouncer.schedule(`collection:${e}`,()=>this.updateCollectionField(s),150)}if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}let i=this.getForm(e.target);this.updateItem(t.dataset.field,this.getFieldValue(e.target),i)}handleBlur(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target).dataset.field;window.debouncer.cancel(`form:${t.id}:validate:${s}`),this.validateField(e.target);const i=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');i?this.updateCollectionField(i):this.updateItem(s,this.getFieldValue(e.target),t)}handleInput(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target);if(!s)return;const i=e.target,a=s.dataset.field;this.showFormStatus(t.id,"pending"),window.debouncer.schedule(`form:${t.id}:validate:${a}`,()=>this.validateField(i),500)}async handleSubmit(e){let t=this.getForm(e.target);if(t){if(this.subscribers.size>0){if(e.preventDefault(),t.options.cache){this.cancelBackup(),await this.backup();const e=await this.store.get(t.id);this.notify("form-submit",{config:t,data:e.changes})}else this.notify("form-submit",{config:t,data:this.changes.get(t.id)?.changes??{}});t.options.endpoint&&!t.options.handled&&(console.log("Submitting form..."),await this.handleServerSave(t,this.collectFormData(t.form)))}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,s.tabs&&(console.log("Updating step progress on change"),this.updateStepProgress(s)),this.changes.set(s.id,i),s.options.cache&&this.scheduleBackup(),s.options.endpoint&&!s.options.handled&&this.scheduleServerSave(s,i)}updateStepProgress(e){if(console.log("First check in updateStepProgress"),!e.tabs)return;let t=this.getCurrentStep(e);if(console.log("Current step: ",t),!t)return;t-=1;let s=this.getCurrentSection(e);if(console.log("Current section: ",s),!s)return;let i=0,a=0;if(s.childNodes.forEach(e=>{if(console.log("Child node: ",e),e.classList.contains("field")){i++;let t=this.getFieldValue(e.querySelector(this.inputSelectors));console.log("Value",t),t.length>0&&a++}}),0===i)return;let r=t+a/i,n=e.ui.tabs.sections.length;window.showProgress(e.ui.tabs.progress,r,n,`Step ${t+1} of ${n}`)}getCurrentStep(e){let t=null;return e.tabs.ui.buttons.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s.dataset.step)}),t}getCurrentSection(e){console.log("Getting current section: ",e.tabs.ui.sections);let t=null;return e.tabs.ui.sections.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s)}),t}scheduleBackup(){window.debouncer.schedule("form_changes",async()=>{this.changes.size>0&&await this.backup()},2e3)}cancelBackup(){window.debouncer.cancel("form_changes")}async backup(){const e=new Map;for(let[t,s]of this.changes.entries()){const i=await this.store.get(t);i?e.set(t,{...i,...s,changes:{...i.changes,...s.changes},timestamp:Date.now()}):e.set(t,s)}await this.store.saveMany(e);for(let e of this.changes.keys())this.showFormStatus(e,"autosaved");this.changes.clear()}saveCache(e){if(!this.changes.has(e))return;let t=this.changes.get(e);0!==t.size&&(this.store.save(t).then(()=>{}),this.changes.delete(e))}scheduleServerSave(e,t){window.debouncer.schedule(`form_${e.id}_server_save`,async()=>{await this.handleServerSave(e,t)},1500)}cancelServerSave(e){window.debouncer.cancel(`form_${e}_server_save`)}async handleServerSave(e,t){this.cancelServerSave(e.id),window.jvbQueue?(t.user=window.auth.getUser(),window.jvbQueue.addToQueue({endpoint:e.options.endpoint,data:t,headers:e.options.headers??{}})):await window.auth.fetch(jvbBase.api+e.options.endpoint,{body:t,headers:e.options.headers??{}})}registerForm(e,t){if(t={autoUpload:!1,imageMeta:!0,delay:1500,endpoint:Object.hasOwn(e.dataset,"save")?e.dataset.save:"",showStatus:!0,showSummary:!1,handled:Object.hasOwn(e.dataset,"handled"),cache:!0,ignore:[],...t},Object.hasOwn(e.dataset,"formId")&&this.forms.has(e.dataset.formId))return;Object.hasOwn(e.dataset,"formId")||(e.dataset.formId=window.generateID("form_"));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(s.ui.items,{animation:150,draggable:this.selectors.repeater.row,handle:this.selectors.repeater.handle,ghostClass:"repeater-row-ghost",chosenClass:"repeater-row-chosen",dragClass:"dragging",onEnd:()=>{this.reindexList(s.ui.items)}})),t.dataset.repeaterId=s.id,this.addRepeaterListeners(t),this.repeaters.set(s.id,s)})}addRepeaterListeners(e){e.addEventListener("click",this.repeaterClick)}removeRepeaterListeners(e){e.removeEventListener("click",this.repeaterClick)}handleRepeaterClick(e){e.target.matches(this.selectors.repeater.add)?this.addRepeaterRow(e.target.closest("[data-repeater-id]")):e.target.matches(this.selectors.repeater.remove)&&this.removeRepeaterRow(e.target.closest("[data-index]"))}addRepeaterRow(e){let t={};t.repeater=e;let s=this.repeaters.get(e.dataset.repeaterId),i=this.templates.create(e.dataset.repeaterId,t);s.rows.push({element:i,fields:Array.from(i.querySelectorAll("[data-field]"))}),this.repeaters.set(s.id,s),s.ui.items.append(i);let a=this.getForm(e);this.initializeFields(e,a),this.reindexList(s.ui.items),this.a11y.announce("Row added")}removeRepeaterRow(e){let t=e.closest("[data-repeater-id]");e.remove();let s=this.repeaters.get(t);s&&this.reindexList(s.ui.items),this.a11y.announce("Row removed")}checkForTagLists(e){e.querySelectorAll(this.selectors.tagList.tagList)?.forEach(t=>{let s={id:t.querySelector("template").className??window.generateID("tagList"),ui:window.uiFromSelectors(this.selectors.tagList,t),element:t,form:e.dataset.formId,format:t.dataset.tagFormat??"first_field"};if(!s.ui.input||!s.ui.add||!s.ui.items)return;t.dataset.tagListId=s.id,s.fieldName=t.dataset.field;let i=t.querySelector("template");this.templates.define(i.className,{refs:{label:this.selectors.tagList.label},manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach(e=>{let t=e.closest(".tag-item");window.prefixInput(e,`${a.fieldName}:${r}:`,t,!1,!0)}),t.label&&(t.label.textContent=a.label)}}),s.ui.inputs=Array.from(t.querySelectorAll(this.selectors.tagList.inputs)),s.ui.value=Array.from(t.querySelectorAll(this.selectors.tagList.value)),this.tagLists.set(s.id,s),this.addTagListListeners(t)})}addTagListListeners(e){e.addEventListener("click",this.tagListClick),e.addEventListener("keypress",this.tagListInput)}removeTagListListeners(e){e.removeEventListener("click",this.tagListClick),e.removeEventListener("keypress",this.tagListInput)}handleTagListClick(e){window.targetCheck(e,this.selectors.tagList.add)?this.addTagListItem(e.target.closest("[data-tag-list-id]")):window.targetCheck(e,this.selectors.tagList.remove)&&this.removeTagListItem(e.target.closest(this.selectors.tagList.item))}addTagListItem(e){let t=this.tagLists.get(e.dataset.tagListId);if(!t)return;let s,i={},a=!1,r=!0;for(let e of t.ui.inputs){const t=e.required||"true"===e.dataset.required,s=this.getFieldValue(e);s&&(a=!0);const n=this.validateField(e);t&&!s?(this.showError(e,"This field is required"),r=!1):n||(r=!1);const 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,t,s)=>this.validateStep(e,t,s)}),t.ui.tabs=window.uiFromSelectors(this.selectors.tabs,e),t.ui.tabs.sections=Array.from(e.querySelectorAll(this.selectors.tabs.sections)),t.ui.tabs.inputs={},t.ui.tabs.sections.forEach(e=>{t.ui.tabs.inputs[e.dataset.tab]=Array.from(e.querySelectorAll(this.inputSelectors))}),t.ui.tabs.buttons=Array.from(e.querySelectorAll(this.selectors.tabs.buttons)),t.unsubscribeTabs=window.jvbTabs.subscribe((e,s)=>{if("tab-switched"===e&&t.ui.tabs.progress){const e=t.ui.tabs.sections.filter(e=>e.dataset.tab===s.current)[0]??!1;if(!e)return;const i=e.dataset.step-1,a=t.ui.tabs.sections.length;window.showProgress(t.ui.tabs.progress,i,a,`Step ${i+1} of ${a}`)}}),this.forms.set(t.id,t))}validateStep(e,t,s){let i=!1;if(s.ui.sections&&s.ui.sections.forEach((a,r)=>{if(a===e){let e=r-1;if(e>=0){let a=s.ui.sections[e];a&&a.dataset.tab===t&&(i=!0)}}}),i)return!0;const a=e.closest("[data-form-id]")?.dataset.formId;if(!a)return!0;if(!this.forms.get(a))return!0;return Array.from(this.inputs.values()).filter(t=>t&&t.form===a&&t.section===e.dataset.tab&&!t.element.closest("[hidden]")).every(e=>!0===this.validateField(e.element))}checkForSelectors(e){window.jvbSelector&&window.jvbSelector.scanExistingFields(e)}reindexList(e){let t=e.dataset.field||e.dataset.repeaterId||e.dataset.tagListId;if(!t){let s=e.closest("[data-field]");t=s.dataset.field||s.dataset.repeaterId||s.dataset.tagListId}Array.from(e.children).forEach((e,s)=>{e.dataset.index=`${s}`;let i=e.querySelector(".row-number");i&&(i.textContent=`#${s+1}`);let a=e.querySelector(".row-title"),r=a?a.dataset.label:null;e.querySelectorAll("input, select, textarea").forEach(i=>{if("file"===i.type)return;const n=Object.hasOwn(i.dataset,"field")?i.dataset.field:i.name.split(":").pop();n===r&&(a.textContent=window.escapeHtml(i.value)),window.prefixInput(i,`${t}:${s}:${n}`,e,!0,!0)})}),this.updateCollectionField(e)}updateCollectionField(e){const t=e.closest("[data-field]");if(!t)return;const s=t.dataset.fieldType;if(!["repeater","tag-list"].includes(s))return;const i=this.getForm(e);if(!i)return;const a=this.getFieldValue(t);this.updateItem(t.dataset.field,a,i)}maybeUpdateCollectionDisplay(e){let t=e.closest("[data-repeater-id],[data-tag-list-id]");if(t&&"repeater"===t.dataset.fieldType){let t=e.closest(".repeater-row");if(!t)return;let s=t.querySelector(".row-title");if(!s||!s.dataset.label)return;console.log(e.dataset.field.split(":").pop()),console.log(s.dataset.label),e.dataset.field.split(":").pop()===s.dataset.label&&(s.textContent=this.getFieldValue(e.querySelector(this.inputSelectors)))}}clearValidation(e){let t=this.getField(e);if(!t)return;let s=this.getItem(e);s&&(t.classList.remove("has-error","has-success"),s.ui.success&&(s.ui.success.hidden=!0),s.ui.error&&(s.ui.error.hidden=!0),s.ui.message&&(s.ui.message.hidden=!0,s.ui.message.textContent=""))}showError(e,t="Invalid field"){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-success"),s.classList.add("has-error"),i.ui.message&&(i.ui.message.hidden=!1,i.ui.message.textContent=t))}showSuccess(e,t=""){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-error"),s.classList.add("has-success"),i.ui.message&&(i.ui.message.hidden=""===t,i.ui.message.textContent=t))}handleFormSuccess(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error").forEach(e=>e.classList.remove("field-error")),e.classList.add("form-success"),t.message){const s=document.createElement("div");s.className="form-success-message success-message",s.textContent=t.message,e.insertBefore(s,e.firstChild);const i=window.getIcon?.("check-circle");i&&(i.classList.add("success-icon"),s.prepend(i))}if(t.title||t.description){const s=document.createElement("div");if(s.className="success-box",t.title){const e=document.createElement("h3");e.textContent=t.title,s.appendChild(e)}if(t.description){(Array.isArray(t.description)?t.description:[t.description]).forEach(e=>{const t=document.createElement("p");t.textContent=e,s.appendChild(t)})}e.insertBefore(s,e.firstChild)}if(e.dataset.formId){this.store.delete(e.dataset.formId).catch(e=>{console.warn("Failed to clear form cache:",e)});const t=this.forms.get(e.dataset.formId);t&&(t.isDirty=!1,t.lastSaved=Date.now(),t.data={})}window.jvbA11y&&window.jvbA11y.announce(t.message||"Form submitted successfully")}handleFormError(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error, .has-error").forEach(e=>{e.classList.remove("field-error","has-error")}),e.querySelectorAll(".field").forEach(e=>{this.clearValidation(e)}),t.field){const s=e.querySelector(`[data-field="${t.field}"]`);if(s){this.showError(s,t.message),s.scrollIntoView({behavior:"smooth",block:"center"});const e=s.querySelector("input, textarea, select");e&&e.focus()}}else{const s=document.createElement("div");s.className="form-error error-message",s.textContent=t.message;const i=window.getIcon?.("close-circle");i&&(i.classList.add("error-icon"),s.prepend(i)),e.insertBefore(s,e.firstChild),e.scrollIntoView({behavior:"smooth",block:"start"})}if(window.jvbA11y){const e=t.field?`Error in ${t.field}: ${t.message}`:`Form error: ${t.message}`;window.jvbA11y.announce(e)}e.dispatchEvent(new CustomEvent("jvb-form-error",{detail:t}))}showFormStatus(e,t,s=""){let i=this.forms.get(e);i&&i.options.showStatus&&i.ui?.status?.status&&i.status!==t&&(i.status=t,i.ui.status.status.hidden=!1,i.ui.status.status.classList.toggle("loading",["uploading","saving"].includes(t)),i.ui.status.message.textContent=""===s?this.getDefaultMessage(t):s,i.ui.status.icon.className="icon icon-"+this.getDefaultIcon(t),setTimeout(()=>i.ui.status.status.hidden=!0,"submitted"===t?3e3:1e4))}getDefaultMessage(e){return{saving:"Saving changes...",autosaved:"Changes saved locally. Submit form to send to server.",uploading:"Uploading your form to server",submitted:"Successfully sent to server",pending:"Unsaved changes",restored:"Welcome back! We've restored your previous entry.",error:"Failed to save changes. Refresh and try again?",offline:"Changes will be saved when online"}[e]??e}getDefaultIcon(e){return{autosaved:"check-circle",submitted:"check-circle",restored:"history",error:"close-circle",offline:"cloud-slash",pending:"exclamation-mark"}[e]??""}showSummary(e){let t=this.templates.create("formSummary",e);e.config.element.after(t),window.fade(e.config.element,!1)}getForm(e){let t=e.closest("[data-form-id]");if(!t)return!1;let s=t.dataset.formId;if(!s)return!1;let i=this.forms.get(s);return i||!1}getField(e){return e.closest("[data-field]")}getFieldType(e){let t=this.getField(e);if(t)return t.dataset.fieldType}getFieldValue(e){let t=this.getFieldType(e),s=this.getItem(e),i=s.field?.dataset.field??!1;if(!i)return!1;switch(t){case"repeater":return this.getRepeaterValue(e,s);case"tag-list":return this.getTagListValue(e,s);case"group":return null;case"location":return this.getLocationValue(e,s);case"selector":case"upload":case"gallery":case"image":return this.getHiddenInputValue(e,s,i);case"true-false":case"toggle-text":return e.checked;case"checkbox":return e.name.endsWith("[]")?this.getCheckboxGroupValue(e,s):e.checked?e.value:"";default:return e.value}}getCheckboxGroupValue(e,t){return t.checkboxGroup||(t.checkboxGroup=t.field?.querySelectorAll(`input[type="checkbox"][name="${e.name}"]`),this.saveItem(t)),Array.from(t.checkboxGroup).filter(e=>e.checked).map(e=>e.value)}getFieldCheckedValue(e){if("checkbox"===e.type){return"true-false"===this.getFieldType(e)?e.checked:e.checked?e.value:""}if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`),s=Array.from(t).find(e=>e.checked);return s?s.value:""}return this.getFieldValue(e)}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}getRepeaterValue(e,t){const s=e.querySelector(".repeater-items");if(!s)return[];let i=["image_data","image-title","image-caption","image-description","image-alt-text"],a=[];return Array.from(s.children).forEach(e=>{let t={};e.querySelectorAll('input[type="hidden"]').forEach(e=>{t[e.name]=e.value}),e.querySelectorAll("[data-field]").forEach(e=>{if(!i.includes(e.dataset.field)){const s=this.getFieldInput(e);s&&(t[e.dataset.field]=this.getFieldValue(s))}}),a.push(t)}),a}getFieldInput(e){const t=e.querySelector("textarea[data-editor]");return t||e.querySelector(this.inputSelectors)}getTagListValue(e,t){t.container||(t.container=t.field?.querySelector(".tag-items"),this.saveItem(t));let s=[];return Array.from(t.container.children).forEach(e=>{let t=e.querySelectorAll('input[type="hidden"]'),i={};t.forEach(e=>{i[e.dataset.field]=e.value}),s.push(i)}),s}getLocationValue(e,t){t.values||(t.values=Array.from(t.field?.querySelectorAll("[data-location-field]")),this.saveItem(t));let s={};return t.values.forEach(e=>{s[e.dataset.locationField]=e.value}),s}getHiddenInputValue(e,t,s){return"INPUT"===e.tagName&&"hidden"===e.type||(e=e.querySelector('input[type="hidden"][name="'+s+'"]'))?(void 0!==t.value&&t.value===e.value||(t.value=e.value,this.saveItem(t)),t.value):null}formatValueForSummary(e,t){const s=this.getFieldType(t.element);if(this.isEmptyValue(e))return"";switch(s){case"repeater":return this.formatRepeaterForSummary(e,t);case"tag-list":return this.formatTagListForSummary(e,t);case"location":return this.formatLocationForSummary(e);case"true-false":return e?"Yes":"No";case"checkbox":return Array.isArray(e)?this.formatCheckboxGroupForSummary(e,t):this.getDisplayLabel(t,e);case"selector":case"upload":case"image":case"gallery":return this.formatHiddenFieldForSummary(e,t,s);default:return"string"==typeof e?this.getDisplayLabel(t,e):"string"==typeof e&&e.includes("\n")?this.convertLineBreaks(e):e}}formatCheckboxGroupForSummary(e,t){return e.map(e=>this.getDisplayLabel(t,e)).join(", ")}convertLineBreaks(e){const t=document.createElement("span");return t.innerHTML=e.split("\n").join("<br>"),t}formatRepeaterForSummary(e,t){const s=document.createElement("div");return s.className="summary-repeater",e.forEach((e,i)=>{const a=document.createElement("div");a.className="summary-repeater-row";const r=document.createElement("strong");r.textContent=`Entry ${i+1}:`,a.appendChild(r);const n=document.createElement("ul");n.className="summary-repeater-fields";for(const[s,i]of Object.entries(e)){if(this.isEmptyValue(i))continue;const e=document.createElement("li"),a=t.field?.querySelector(`[data-field="${s}"]`),r=a?.closest(".field")?.querySelector("label")?.textContent.replace("*","").trim()||s;e.innerHTML=`<span class="field-label">${r}:</span> <span class="field-value">${i}</span>`,n.appendChild(e)}a.appendChild(n),s.appendChild(a)}),s}formatTagListForSummary(e,t){const s=document.createElement("div");s.className="summary-taglist";const i=document.createElement("ul");return i.className="summary-tags",e.forEach(e=>{const t=document.createElement("li");t.className="summary-tag";const s=Object.values(e).find(e=>!this.isEmptyValue(e))||"",a=Object.entries(e).filter(([e,t])=>!this.isEmptyValue(t));a.length>1?t.textContent=a.map(([e,t])=>t).join(", "):t.textContent=s,i.appendChild(t)}),s.appendChild(i),s}formatLocationForSummary(e){const t=[];return e.street&&t.push(e.street),e.city&&t.push(e.city),e.province&&t.push(e.province),e.postal_code&&t.push(e.postal_code),e.country&&t.push(e.country),t.length>0?t.join(", "):e.address||""}formatHiddenFieldForSummary(e,t,s){if(["upload","gallery","image"].includes(s)){const s=t.field?.querySelector("[data-upload-field]");if(s){const e=s.querySelectorAll(".item-grid.preview img");if(e.length>0){const t=document.createElement("div");return t.className="summary-uploads",e.forEach(e=>{const s=e.cloneNode(!0);s.style.maxWidth="100px",s.style.maxHeight="100px",t.appendChild(s)}),t}}return`${e.split(",").length} file(s) uploaded`}return e}getDisplayLabel(e,t){if(!e.element)return t;const s=e.element.type;if("radio"===s){const s=e.field.querySelectorAll(`input[type="radio"][name="${e.element.name}"]`),i=Array.from(s).find(e=>e.value===t);if(i){const t=i.closest("label")||e.field.querySelector(`label[for="${i.id}"]`);if(t)return t.textContent.replace("*","").trim()}}if("checkbox"===s&&"true-false"!==this.getFieldType(e.element)){const s=e.field.querySelector(`input[type="checkbox"][value="${t}"]`);if(s){const t=s.closest("label")||e.field.querySelector(`label[for="${s.id}"]`);if(t){const e=t.querySelector("span");return e?e.textContent.trim():t.textContent.replace("*","").trim()}}}return t}getItem(e,t=null){const s=Object.hasOwn(e.dataset,"ref");let i=s?e.dataset.ref:window.generateID("input");if(s||(e.dataset.ref=i),!this.inputs.has(i)){t||(t=e.closest("[data-form-id]")?.dataset.formId??!1);let s=this.getField(e);this.inputs.set(i,{id:i,element:e,form:t,field:s,section:e.closest("[data-tab]")?.dataset.tab??!1,ui:window.uiFromSelectors(this.selectors.fields,s)})}return this.inputs.get(i)}saveItem(e){this.inputs.set(e.id,e)}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("HandleSelection subscriber error:",e)}})}destroy(){this.forms.size>0&&(Array.from(this.forms.values()).forEach(e=>{this.removeFormListeners(e)}),this.forms.clear()),this.repeaters.size>0&&(Array.from(this.repeaters.values()).forEach(e=>{this.removeRepeaterListeners(e.element),e.sortable?.destroy()}),this.repeaters.clear()),this.quantityFields.size>0&&(Array.from(this.quantityFields.values()).forEach(e=>{this.removeQuantityListeners(e.element)}),this.quantityFields.clear()),this.tagLists.size>0&&(Array.from(this.tagLists.values()).forEach(e=>{this.removeTagListListeners(e.element)}),this.tagLists.clear()),this.charLimits.size>0&&Array.from(this.charLimits.values()).forEach(e=>{e.element.removeEventListener("input",this.countUpdaters)}),this.inputs.clear(),this.forms.clear(),this.charLimits.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbForm=new e)})})})(); |
| | | (()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.queue=window.jvbQueue,this.populate=window.jvbPopulate,this.changes=new Map,this.forms=new Map,this.inputs=new Map,this.repeaters=new Map,this.tagLists=new Map,this.charLimits=new Map,this.quantityFields=new Map,this.quillInstances=new Map,this.dependencies=new Map,this.subscribers=new Set,this.isRestoring=!1,this.hasListeners=!1,this.hasUploads=!1,this.summaryTemplate=!1,this.init()}init(){this.templates=window.jvbTemplates,this.defineSummaryTemplate(),this.initElements(),this.initListeners(),this.initStore(),this.initValidators(),this.initUploadSubscription()}initUploadSubscription(){window.jvbUploads.subscribe((e,t)=>{if(this.hasUploads&&"upload-received"===e){let e=this.getForm(t.field);e&&this.updateItem(`${t.field.dataset.field}_tempUpload`,t.id,e)}})}initElements(){this.inputSelectors="input, textarea, select",this.selectors={tabs:{nav:"nav.tabs",sections:".tab-content",progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},buttons:"nav.tabs button"},dependsOn:"[data-depends-on]",forms:{status:{status:".fstatus",message:".fstatus .message",icon:".fstatus .icon",actions:".fstatus .actions"},restore:{container:".restore-form",restore:'[data-action="restore"]',clear:'[data-action="clear"]'}},inputs:this.inputSelectors,fields:{field:".field",label:"label",success:".success",error:".error",message:".validation-message"},repeater:{repeater:".repeater",row:".repeater-row",handle:".drag-handle",header:".repeater-row-header",remove:".remove-row",add:".add-repeater-row",template:"template",items:".repeater-items",inputs:this.inputSelectors},tagList:{tagList:".field.tag-list",input:".row",add:".add-tag",remove:".remove-tag",label:".tag-label",items:".tag-items",item:".tag-item",inputs:this.inputSelectors,value:'input[type="hidden"]'},tag:{label:".tag-label"},number:{number:".field div.quantity",increase:"button.increase",decrease:"button.decrease",input:'input[type="number"]'},limits:{hasLimit:"[data-maxlength]",limit:".limit",current:".current"}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.blurHandler=this.handleBlur.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleSubmit.bind(this),this.quantityClick=this.handleQuantityClick.bind(this),this.repeaterClick=this.handleRepeaterClick.bind(this),this.tagListClick=this.handleTagListClick.bind(this),this.tagListInput=this.handleTagListInput.bind(this)}addFormListeners(e){e.addEventListener("click",this.clickHandler),e.addEventListener("change",this.changeHandler),e.addEventListener("input",this.inputHandler),e.addEventListener("blur",this.blurHandler),e.addEventListener("submit",this.submitHandler)}removeFormListeners(e){e.removeEventListener("click",this.clickHandler),e.removeEventListener("change",this.changeHandler),e.removeEventListener("input",this.inputHandler),e.removeEventListener("blur",this.blurHandler),e.removeEventListener("submit",this.submitHandler)}initStore(){const e=window.jvbStore.register("forms",{storeName:"forms",keyPath:"id",indexes:[{name:"src",keyPath:"src"},{name:"timestamp",keyPath:"timestamp"},{name:"formType",keyPath:"type"}],TTL:1008e4});this.store=e.forms,this.store.subscribe((e,t)=>{if("data-ready"===e){let e=this.store.getFiltered().filter(e=>e.src===window.location.pathname);for(let t of e)this.showPendingNotification(t.id,t.changes)}else"operation-status"===e&&"completed"===t.status&&t.config&&this.store.delete(t.config.id)})}showPendingNotification(e,t){let s=this.forms.get(e);if(!s)return;let i=s.element;if(!i)return void console.warn(`Form element not found for: ${e}`);s.ui.restore.container.hidden=!1;const a=async(e,t)=>{this.isRestoring=!0;let i={fields:e};await this.checkStoredUploads(e,t),this.populate.populate(t,i),this.a11y.announce("Previous changes restored"),this.isRestoring=!1,s.ui.restore.container.remove()},r=async e=>{await this.checkStoredUploads(t,i,!1),await this.store.delete(e),this.a11y.announce("Previous changes discarded"),s.ui.restore.container.remove()};s.ui.restore.restore.addEventListener("click",()=>a(t,i)),s.ui.restore.clear.addEventListener("click",async()=>r(e))}async checkStoredUploads(e,t,s=!0){let i=this.forms.get(t.dataset.formId);if(!i)return;let a=[];for(let[t,s]of Object.entries(e))if(t.includes("_tempUpload")){let e=t.replace("_tempUpload","");Object.hasOwn(i.ui.uploads,e)&&(a=[...a,...s])}a.length>0&&(s?await window.jvbUploads.restoreUploads(a):await window.jvbUploads.clearUploads(a))}initValidators(){this.validators={email:{pattern:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,message:"Please enter a valid email address"},url:{pattern:/^https?:\/\/.+\..+/,message:"Please enter a valid URL starting with https://"},phone:{pattern:/^[\d\s\-+().]+$/,message:"Please enter a valid phone number"},number:{test:(e,t)=>{const s=parseFloat(e);if(isNaN(s))return"Please enter a valid number";const i=t.dataset.min,a=t.dataset.max;return void 0!==i&&s<parseFloat(i)?`Value must be at least ${i}`:!(void 0!==a&&s>parseFloat(a))||`Value must be at most ${a}`}},text:{test:(e,t)=>{const s=t.dataset.minlength,i=t.dataset.maxlength;return s&&e.length<parseInt(s)?`Must be at least ${s} characters`:!(i&&e.length>parseInt(i))||`Must be no more than ${i} characters`}}}}validateField(e){const t=this.performValidation(e);return this.updateValidationUI(e,t),t.isValid}performValidation(e){const t=e.closest(".field"),s=this.getFieldCheckedValue(e);if(!s&&!e.required)return{isValid:!0,message:""};if(e.required)if("checkbox"===e.type){if(!e.checked)return{isValid:!1,message:"This field is required"}}else if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`);if(!Array.from(t).some(e=>e.checked))return{isValid:!1,message:"Please select an option"}}else if(!s)return{isValid:!1,message:"This field is required"};if(e.checkValidity&&!e.checkValidity())return{isValid:!1,message:e.validationMessage};if(s&&Object.hasOwn(t.dataset,"pattern")){if(!new RegExp(t.dataset.pattern).test(s))return{isValid:!1,message:t.dataset.validationMessage||"Invalid format"}}if(Object.hasOwn(t.dataset,"validate")||e.type){const i=this.validators[t.dataset.validate||e.type];if(i&&i.pattern&&!i.pattern.test(s))return{isValid:!1,message:i.message};if(i&&i.test){const e=i.test(s,t);if(!0!==e)return{isValid:!1,message:e}}}return{isValid:!0,message:""}}updateValidationUI(e,t){t.isValid?this.showSuccess(e,t.message):this.showError(e,t.message)}handleClick(e){let t=this.getForm(e.target);if(!t)return;const s=window.targetCheck(e,"[data-action]");if(s){switch(s.dataset.action){case"clear-form":this.store.delete(t.id),t.element.reset(),t.ui.status.status.hidden=!0,this.a11y.announce("Form cleared, starting fresh");break;case"dismiss-restore":t.ui.status.status.hidden=!0;break;case"next-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s+1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}});break;case"prev-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s-1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}})}}}handleChange(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getField(e.target);const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');if(s){if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}const e=s.dataset.field;return this.maybeUpdateCollectionDisplay(t),void window.debouncer.schedule(`collection:${e}`,()=>this.updateCollectionField(s),150)}if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}let i=this.getForm(e.target);this.updateItem(t.dataset.field,this.getFieldValue(e.target),i)}handleBlur(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target).dataset.field;window.debouncer.cancel(`form:${t.id}:validate:${s}`),this.validateField(e.target);const i=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');i?this.updateCollectionField(i):this.updateItem(s,this.getFieldValue(e.target),t)}handleInput(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target);if(!s)return;const i=e.target,a=s.dataset.field;this.showFormStatus(t.id,"pending"),window.debouncer.schedule(`form:${t.id}:validate:${a}`,()=>this.validateField(i),500)}async handleSubmit(e){let t=this.getForm(e.target);if(t){if(this.subscribers.size>0)if(e.preventDefault(),t.options.cache){this.cancelBackup(),await this.backup();const e=await this.store.get(t.id);this.notify("form-submit",{config:t,data:e.changes})}else this.notify("form-submit",{config:t,data:this.changes.get(t.id)?.changes??{}});if(t.options.endpoint&&!t.options.handled&&(e.preventDefault(),console.log("Submitting form...",this.changes.get(t.id)?.changes??{}),await this.handleServerSave(t,this.changes.get(t.id)?.changes??{})),t.options.showSummary){const e=await this.store.get(t.id);this.showSummary({config:t,changes:e?.changes})}}}updateItem(e,t,s){if(void 0===t)return;this.changes.has(s.id)||this.changes.set(s.id,{id:s.id,timestamp:Date.now(),src:window.location.pathname,changes:{}});let i=this.changes.get(s.id);e.includes("_tempUpload")?(Object.hasOwn(i.changes,e)||(i.changes[e]=[]),i.changes[e].push(t)):i.changes[e]=t,s.tabs&&this.updateStepProgress(s),this.changes.set(s.id,i),s.options.cache&&this.scheduleBackup(),s.options.endpoint&&!s.options.handled&&this.scheduleServerSave(s,i)}updateStepProgress(e){if(console.log("First check in updateStepProgress"),!e.tabs)return;let t=this.getCurrentStep(e);if(console.log("Current step: ",t),!t)return;t-=1;let s=this.getCurrentSection(e);if(console.log("Current section: ",s),!s)return;let i=0,a=0;if(s.childNodes.forEach(e=>{if(console.log("Child node: ",e),e.classList.contains("field")){i++;let t=this.getFieldValue(e.querySelector(this.inputSelectors));console.log("Value",t),t.length>0&&a++}}),0===i)return;let r=t+a/i,n=e.ui.tabs.sections.length;window.showProgress(e.ui.tabs.progress,r,n,`Step ${t+1} of ${n}`)}getCurrentStep(e){let t=null;return e.tabs.ui.buttons.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s.dataset.step)}),t}getCurrentSection(e){console.log("Getting current section: ",e.tabs.ui.sections);let t=null;return e.tabs.ui.sections.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s)}),t}scheduleBackup(){window.debouncer.schedule("form_changes",async()=>{this.changes.size>0&&await this.backup()},2e3)}cancelBackup(){window.debouncer.cancel("form_changes")}async backup(){const e=new Map;for(let[t,s]of this.changes.entries()){const i=await this.store.get(t);i?e.set(t,{...i,...s,changes:{...i.changes,...s.changes},timestamp:Date.now()}):e.set(t,s)}await this.store.saveMany(e);for(let e of this.changes.keys())this.showFormStatus(e,"autosaved");this.changes.clear()}saveCache(e){if(!this.changes.has(e))return;let t=this.changes.get(e);0!==t.size&&(this.store.save(t).then(()=>{}),this.changes.delete(e))}scheduleServerSave(e,t){window.debouncer.schedule(`form_${e.id}_server_save`,async()=>{await this.handleServerSave(e,t)},1500)}cancelServerSave(e){window.debouncer.cancel(`form_${e}_server_save`)}async handleServerSave(e,t){this.cancelServerSave(e.id),e.useQueue&&window.jvbQueue?(t.user=window.auth.getUser(),window.jvbQueue.addToQueue({endpoint:e.options.endpoint,data:t,headers:e.options.headers??{}})):await window.auth.fetch(jvbSettings.api+e.options.endpoint,{body:t,headers:e.options.headers??{},method:"POST"})}registerForm(e,t){if(t={autoUpload:!1,imageMeta:!0,delay:1500,useQueue:!0,endpoint:Object.hasOwn(e.dataset,"save")?e.dataset.save:"",showStatus:!0,showSummary:!1,handled:Object.hasOwn(e.dataset,"handled"),cache:!0,ignore:[],...t},Object.hasOwn(e.dataset,"formId")&&this.forms.has(e.dataset.formId))return;Object.hasOwn(e.dataset,"formId")||(e.dataset.formId=window.generateID("form_"));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(s.ui.items,{animation:150,draggable:this.selectors.repeater.row,handle:this.selectors.repeater.handle,ghostClass:"repeater-row-ghost",chosenClass:"repeater-row-chosen",dragClass:"dragging",onEnd:()=>{this.reindexList(s.ui.items)}})),t.dataset.repeaterId=s.id,this.addRepeaterListeners(t),this.repeaters.set(s.id,s)})}addRepeaterListeners(e){e.addEventListener("click",this.repeaterClick)}removeRepeaterListeners(e){e.removeEventListener("click",this.repeaterClick)}handleRepeaterClick(e){e.target.matches(this.selectors.repeater.add)?this.addRepeaterRow(e.target.closest("[data-repeater-id]")):e.target.matches(this.selectors.repeater.remove)&&this.removeRepeaterRow(e.target.closest("[data-index]"))}addRepeaterRow(e){let t={};t.repeater=e;let s=this.repeaters.get(e.dataset.repeaterId),i=this.templates.create(e.dataset.repeaterId,t);s.rows.push({element:i,fields:Array.from(i.querySelectorAll("[data-field]"))}),this.repeaters.set(s.id,s),s.ui.items.append(i);let a=this.getForm(e);this.initializeFields(e,a),this.reindexList(s.ui.items),this.a11y.announce("Row added")}removeRepeaterRow(e){let t=e.closest("[data-repeater-id]");e.remove();let s=this.repeaters.get(t);s&&this.reindexList(s.ui.items),this.a11y.announce("Row removed")}checkForTagLists(e){e.querySelectorAll(this.selectors.tagList.tagList)?.forEach(t=>{let s={id:t.querySelector("template").className??window.generateID("tagList"),ui:window.uiFromSelectors(this.selectors.tagList,t),element:t,form:e.dataset.formId,format:t.dataset.tagFormat??"first_field"};if(!s.ui.input||!s.ui.add||!s.ui.items)return;t.dataset.tagListId=s.id,s.fieldName=t.dataset.field;let i=t.querySelector("template");this.templates.define(i.className,{refs:{label:this.selectors.tagList.label},manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach(e=>{let t=e.closest(".tag-item");window.prefixInput(e,`${a.fieldName}:${r}:`,t,!1,!0)}),t.label&&(t.label.textContent=a.label)}}),s.ui.inputs=Array.from(t.querySelectorAll(this.selectors.tagList.inputs)),s.ui.value=Array.from(t.querySelectorAll(this.selectors.tagList.value)),this.tagLists.set(s.id,s),this.addTagListListeners(t)})}addTagListListeners(e){e.addEventListener("click",this.tagListClick),e.addEventListener("keypress",this.tagListInput)}removeTagListListeners(e){e.removeEventListener("click",this.tagListClick),e.removeEventListener("keypress",this.tagListInput)}handleTagListClick(e){window.targetCheck(e,this.selectors.tagList.add)?this.addTagListItem(e.target.closest("[data-tag-list-id]")):window.targetCheck(e,this.selectors.tagList.remove)&&this.removeTagListItem(e.target.closest(this.selectors.tagList.item))}addTagListItem(e){let t=this.tagLists.get(e.dataset.tagListId);if(!t)return;let s,i={},a=!1,r=!0;for(let e of t.ui.inputs){const t=e.required||"true"===e.dataset.required,s=this.getFieldValue(e);s&&(a=!0);const n=this.validateField(e);t&&!s?(this.showError(e,"This field is required"),r=!1):n||(r=!1);const 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,t,s)=>this.validateStep(e,t,s)}),t.ui.tabs=window.uiFromSelectors(this.selectors.tabs,e),t.ui.tabs.sections=Array.from(e.querySelectorAll(this.selectors.tabs.sections)),t.ui.tabs.inputs={},t.ui.tabs.sections.forEach(e=>{t.ui.tabs.inputs[e.dataset.tab]=Array.from(e.querySelectorAll(this.inputSelectors))}),t.ui.tabs.buttons=Array.from(e.querySelectorAll(this.selectors.tabs.buttons)),t.unsubscribeTabs=window.jvbTabs.subscribe((e,s)=>{if("tab-switched"===e&&t.ui.tabs.progress){const e=t.ui.tabs.sections.filter(e=>e.dataset.tab===s.current)[0]??!1;if(!e)return;const i=e.dataset.step-1,a=t.ui.tabs.sections.length;window.showProgress(t.ui.tabs.progress,i,a,`Step ${i+1} of ${a}`)}}),this.forms.set(t.id,t))}validateStep(e,t,s){let i=!1;if(s.ui.sections&&s.ui.sections.forEach((a,r)=>{if(a===e){let e=r-1;if(e>=0){let a=s.ui.sections[e];a&&a.dataset.tab===t&&(i=!0)}}}),i)return!0;const a=e.closest("[data-form-id]")?.dataset.formId;if(!a)return!0;if(!this.forms.get(a))return!0;return Array.from(this.inputs.values()).filter(t=>t&&t.form===a&&t.section===e.dataset.tab&&!t.element.closest("[hidden]")).every(e=>!0===this.validateField(e.element))}checkForSelectors(e){window.jvbSelector&&window.jvbSelector.scanExistingFields(e)}reindexList(e){let t=e.dataset.field||e.dataset.repeaterId||e.dataset.tagListId;if(!t){let s=e.closest("[data-field]");t=s.dataset.field||s.dataset.repeaterId||s.dataset.tagListId}Array.from(e.children).forEach((e,s)=>{e.dataset.index=`${s}`;let i=e.querySelector(".row-number");i&&(i.textContent=`#${s+1}`);let a=e.querySelector(".row-title"),r=a?a.dataset.label:null;e.querySelectorAll("input, select, textarea").forEach(i=>{if("file"===i.type)return;const n=Object.hasOwn(i.dataset,"field")?i.dataset.field:i.name.split(":").pop();n===r&&(a.textContent=window.escapeHtml(i.value)),window.prefixInput(i,`${t}:${s}:${n}`,e,!0,!0)})}),this.updateCollectionField(e)}updateCollectionField(e){const t=e.closest("[data-field]");if(!t)return;const s=t.dataset.fieldType;if(!["repeater","tag-list"].includes(s))return;const i=this.getForm(e);if(!i)return;const a=this.getFieldValue(t);this.updateItem(t.dataset.field,a,i)}maybeUpdateCollectionDisplay(e){let t=e.closest("[data-repeater-id],[data-tag-list-id]");if(t&&"repeater"===t.dataset.fieldType){let t=e.closest(".repeater-row");if(!t)return;let s=t.querySelector(".row-title");if(!s||!s.dataset.label)return;console.log(e.dataset.field.split(":").pop()),console.log(s.dataset.label),e.dataset.field.split(":").pop()===s.dataset.label&&(s.textContent=this.getFieldValue(e.querySelector(this.inputSelectors)))}}clearValidation(e){let t=this.getField(e);if(!t)return;let s=this.getItem(e);s&&(t.classList.remove("has-error","has-success"),s.ui.success&&(s.ui.success.hidden=!0),s.ui.error&&(s.ui.error.hidden=!0),s.ui.message&&(s.ui.message.hidden=!0,s.ui.message.textContent=""))}showError(e,t="Invalid field"){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-success"),s.classList.add("has-error"),s.scrollIntoView({behavior:"smooth",block:"center",inline:"nearest"}),s.querySelector(this.inputSelectors)?.focus(),i.ui.message&&(i.ui.message.hidden=!1,i.ui.message.textContent=t))}showSuccess(e,t=""){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-error"),s.classList.add("has-success"),i.ui.message&&(i.ui.message.hidden=""===t,i.ui.message.textContent=t))}handleFormSuccess(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error").forEach(e=>e.classList.remove("field-error")),e.classList.add("form-success"),t.message){const s=document.createElement("div");s.className="form-success-message success-message",s.textContent=t.message,e.insertBefore(s,e.firstChild);const i=window.getIcon?.("check-circle");i&&(i.classList.add("success-icon"),s.prepend(i))}if(t.title||t.description){const s=document.createElement("div");if(s.className="success-box",t.title){const e=document.createElement("h3");e.textContent=t.title,s.appendChild(e)}if(t.description){(Array.isArray(t.description)?t.description:[t.description]).forEach(e=>{const t=document.createElement("p");t.textContent=e,s.appendChild(t)})}e.insertBefore(s,e.firstChild)}if(e.dataset.formId){this.store.delete(e.dataset.formId).catch(e=>{console.warn("Failed to clear form cache:",e)});const t=this.forms.get(e.dataset.formId);t&&(t.isDirty=!1,t.lastSaved=Date.now(),t.data={})}window.jvbA11y&&window.jvbA11y.announce(t.message||"Form submitted successfully")}handleFormError(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error, .has-error").forEach(e=>{e.classList.remove("field-error","has-error")}),e.querySelectorAll(".field").forEach(e=>{this.clearValidation(e)}),t.field){const s=e.querySelector(`[data-field="${t.field}"]`);if(s){this.showError(s,t.message),s.scrollIntoView({behavior:"smooth",block:"center"});const e=s.querySelector("input, textarea, select");e&&e.focus()}}else{const s=document.createElement("div");s.className="form-error error-message",s.textContent=t.message;const i=window.getIcon?.("close-circle");i&&(i.classList.add("error-icon"),s.prepend(i)),e.insertBefore(s,e.firstChild),e.scrollIntoView({behavior:"smooth",block:"start"})}if(window.jvbA11y){const e=t.field?`Error in ${t.field}: ${t.message}`:`Form error: ${t.message}`;window.jvbA11y.announce(e)}e.dispatchEvent(new CustomEvent("jvb-form-error",{detail:t}))}showFormStatus(e,t,s=""){let i=this.forms.get(e);i&&i.options.showStatus&&i.ui?.status?.status&&i.status!==t&&(i.status=t,i.ui.status.status.hidden=!1,i.ui.status.status.classList.toggle("loading",["uploading","saving"].includes(t)),i.ui.status.message.textContent=""===s?this.getDefaultMessage(t):s,i.ui.status.icon.className="icon icon-"+this.getDefaultIcon(t),setTimeout(()=>i.ui.status.status.hidden=!0,"submitted"===t?3e3:1e4))}getDefaultMessage(e){return{saving:"Saving changes...",autosaved:"Changes saved locally. Submit form to send to server.",uploading:"Uploading your form to server",submitted:"Successfully sent to server",pending:"Unsaved changes",restored:"Welcome back! We've restored your previous entry.",error:"Failed to save changes. Refresh and try again?",offline:"Changes will be saved when online"}[e]??e}getDefaultIcon(e){return{autosaved:"check-circle",submitted:"check-circle",restored:"history",error:"close-circle",offline:"cloud-slash",pending:"exclamation-mark"}[e]??""}showSummary(e){let t=this.templates.create("formSummary",e);e.config.element.after(t),window.fade(e.config.element,!1)}getForm(e){let t=e.closest("[data-form-id]");if(!t)return!1;let s=t.dataset.formId;if(!s)return!1;let i=this.forms.get(s);return i||!1}getField(e){return e.closest("[data-field]")}getFieldType(e){let t=this.getField(e);if(t)return t.dataset.fieldType}getFieldValue(e){let t=this.getFieldType(e),s=this.getItem(e),i=s.field?.dataset.field??!1;if(!i)return!1;switch(t){case"repeater":return this.getRepeaterValue(e,s);case"tag-list":return this.getTagListValue(e,s);case"group":return null;case"location":return this.getLocationValue(e,s);case"selector":case"upload":case"gallery":case"image":return this.getHiddenInputValue(e,s,i);case"true-false":case"toggle-text":return e.checked;case"checkbox":return e.name.endsWith("[]")?this.getCheckboxGroupValue(e,s):e.checked?e.value:"";default:return e.value}}getCheckboxGroupValue(e,t){return t.checkboxGroup||(t.checkboxGroup=t.field?.querySelectorAll(`input[type="checkbox"][name="${e.name}"]`),this.saveItem(t)),Array.from(t.checkboxGroup).filter(e=>e.checked).map(e=>e.value)}getFieldCheckedValue(e){if("checkbox"===e.type){return"true-false"===this.getFieldType(e)?e.checked:e.checked?e.value:""}if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`),s=Array.from(t).find(e=>e.checked);return s?s.value:""}return this.getFieldValue(e)}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}getRepeaterValue(e,t){const s=e.querySelector(".repeater-items");if(!s)return[];let i=["image_data","image-title","image-caption","image-description","image-alt-text"],a=[];return Array.from(s.children).forEach(e=>{let t={};e.querySelectorAll('input[type="hidden"]').forEach(e=>{t[e.name]=e.value}),e.querySelectorAll("[data-field]").forEach(e=>{if(!i.includes(e.dataset.field)){const s=this.getFieldInput(e);s&&(t[e.dataset.field]=this.getFieldValue(s))}}),a.push(t)}),a}getFieldInput(e){const t=e.querySelector("textarea[data-editor]");return t||e.querySelector(this.inputSelectors)}getTagListValue(e,t){t.container||(t.container=t.field?.querySelector(".tag-items"),this.saveItem(t));let s=[];return Array.from(t.container.children).forEach(e=>{let t=e.querySelectorAll('input[type="hidden"]'),i={};t.forEach(e=>{i[e.dataset.field]=e.value}),s.push(i)}),s}getLocationValue(e,t){t.values||(t.values=Array.from(t.field?.querySelectorAll("[data-location-field]")),this.saveItem(t));let s={};return t.values.forEach(e=>{s[e.dataset.locationField]=e.value}),s}getHiddenInputValue(e,t,s){return"INPUT"===e.tagName&&"hidden"===e.type||(e=e.querySelector('input[type="hidden"][name="'+s+'"]'))?(void 0!==t.value&&t.value===e.value||(t.value=e.value,this.saveItem(t)),t.value):null}formatValueForSummary(e,t){const s=this.getFieldType(t.element);if(this.isEmptyValue(e))return"";switch(s){case"repeater":return this.formatRepeaterForSummary(e,t);case"tag-list":return this.formatTagListForSummary(e,t);case"location":return this.formatLocationForSummary(e);case"true-false":return e?"Yes":"No";case"checkbox":return Array.isArray(e)?this.formatCheckboxGroupForSummary(e,t):this.getDisplayLabel(t,e);case"selector":case"upload":case"image":case"gallery":return this.formatHiddenFieldForSummary(e,t,s);default:return"string"==typeof e?this.getDisplayLabel(t,e):"string"==typeof e&&e.includes("\n")?this.convertLineBreaks(e):e}}formatCheckboxGroupForSummary(e,t){return e.map(e=>this.getDisplayLabel(t,e)).join(", ")}convertLineBreaks(e){const t=document.createElement("span");return t.innerHTML=e.split("\n").join("<br>"),t}formatRepeaterForSummary(e,t){const s=document.createElement("div");return s.className="summary-repeater",e.forEach((e,i)=>{const a=document.createElement("div");a.className="summary-repeater-row";const r=document.createElement("strong");r.textContent=`Entry ${i+1}:`,a.appendChild(r);const n=document.createElement("ul");n.className="summary-repeater-fields";for(const[s,i]of Object.entries(e)){if(this.isEmptyValue(i))continue;const e=document.createElement("li"),a=t.field?.querySelector(`[data-field="${s}"]`),r=a?.closest(".field")?.querySelector("label")?.textContent.replace("*","").trim()||s;e.innerHTML=`<span class="field-label">${r}:</span> <span class="field-value">${i}</span>`,n.appendChild(e)}a.appendChild(n),s.appendChild(a)}),s}formatTagListForSummary(e,t){const s=document.createElement("div");s.className="summary-taglist";const i=document.createElement("ul");return i.className="summary-tags",e.forEach(e=>{const t=document.createElement("li");t.className="summary-tag";const s=Object.values(e).find(e=>!this.isEmptyValue(e))||"",a=Object.entries(e).filter(([e,t])=>!this.isEmptyValue(t));a.length>1?t.textContent=a.map(([e,t])=>t).join(", "):t.textContent=s,i.appendChild(t)}),s.appendChild(i),s}formatLocationForSummary(e){const t=[];return e.street&&t.push(e.street),e.city&&t.push(e.city),e.province&&t.push(e.province),e.postal_code&&t.push(e.postal_code),e.country&&t.push(e.country),t.length>0?t.join(", "):e.address||""}formatHiddenFieldForSummary(e,t,s){if(["upload","gallery","image"].includes(s)){const s=t.field?.querySelector("[data-upload-field]");if(s){const e=s.querySelectorAll(".item-grid.preview img");if(e.length>0){const t=document.createElement("div");return t.className="summary-uploads",e.forEach(e=>{const s=e.cloneNode(!0);s.style.maxWidth="100px",s.style.maxHeight="100px",t.appendChild(s)}),t}}return`${e.split(",").length} file(s) uploaded`}return e}getDisplayLabel(e,t){if(!e.element)return t;const s=e.element.type;if("radio"===s){const s=e.field.querySelectorAll(`input[type="radio"][name="${e.element.name}"]`),i=Array.from(s).find(e=>e.value===t);if(i){const t=i.closest("label")||e.field.querySelector(`label[for="${i.id}"]`);if(t)return t.textContent.replace("*","").trim()}}if("checkbox"===s&&"true-false"!==this.getFieldType(e.element)){const s=e.field.querySelector(`input[type="checkbox"][value="${t}"]`);if(s){const t=s.closest("label")||e.field.querySelector(`label[for="${s.id}"]`);if(t){const e=t.querySelector("span");return e?e.textContent.trim():t.textContent.replace("*","").trim()}}}return t}getItem(e,t=null){const s=Object.hasOwn(e.dataset,"ref");let i=s?e.dataset.ref:window.generateID("input");if(s||(e.dataset.ref=i),!this.inputs.has(i)){t||(t=e.closest("[data-form-id]")?.dataset.formId??!1);let s=this.getField(e);this.inputs.set(i,{id:i,element:e,form:t,field:s,section:e.closest("[data-tab]")?.dataset.tab??!1,ui:window.uiFromSelectors(this.selectors.fields,s)})}return this.inputs.get(i)}saveItem(e){this.inputs.set(e.id,e)}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("HandleSelection subscriber error:",e)}})}destroy(){this.forms.size>0&&(Array.from(this.forms.values()).forEach(e=>{this.removeFormListeners(e)}),this.forms.clear()),this.repeaters.size>0&&(Array.from(this.repeaters.values()).forEach(e=>{this.removeRepeaterListeners(e.element),e.sortable?.destroy()}),this.repeaters.clear()),this.quantityFields.size>0&&(Array.from(this.quantityFields.values()).forEach(e=>{this.removeQuantityListeners(e.element)}),this.quantityFields.clear()),this.tagLists.size>0&&(Array.from(this.tagLists.values()).forEach(e=>{this.removeTagListListeners(e.element)}),this.tagLists.clear()),this.charLimits.size>0&&Array.from(this.charLimits.values()).forEach(e=>{e.element.removeEventListener("input",this.countUpdaters)}),this.inputs.clear(),this.forms.clear(),this.charLimits.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbForm=new e)})})})(); |
| | |
| | | window.jvbHandleSelection=class{constructor(e,t={}){this.container=e,this.selectors=window.deepMerge({selectAll:{checkbox:"[data-select-all]",label:".selected label",span:".selected label span",target:"data-selects",count:".selected-count, .selected .info",bulkControls:".bulk-actions"},items:".item-grid",wrapper:{wrapper:":has(.item-grid, [data-select-all])",id:"selection"},item:{item:".item",idAttribute:"id",checkbox:'[name="select-item"]'},wrappers:{}},t),this.a11y=window.jvbA11y,this.selectedItems=new Set,this.lastSelected=null,this.lastSelectedWrapper=null,this.lastClicked=null,this.subscribers=new Set,this.items=new Map,this.initElements(),this.initListeners(),this.collectItems()}removeDataReferences(){let e=JSON.parse(JSON.stringify(this.selectors));return delete e.item.idAttribute,delete e.wrapper.id,delete e.selectAll.target,e}initElements(){this.index=0;let e=this.removeDataReferences();this.ui=window.uiFromSelectors(e,this.container),this.container.querySelectorAll(this.selectors.wrapper.wrapper).forEach(e=>{this.addWrapper(e)})}addWrapper(e){let t=this.selectors.wrapper.id;Object.hasOwn(e.dataset,t)||(e.setAttribute(`data-${t}`,this.index),this.index++);let s=this.removeDataReferences().selectAll;this.ui.wrappers[e.dataset[t]]={element:e,items:e.querySelector(this.selectors.items),selectAll:window.uiFromSelectors(s,e)}}removeWrapper(e){delete this.ui.wrappers[e.dataset[this.selectors.wrapper.id]]}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.keyHandler=this.handleKeys.bind(this),this.container.addEventListener("change",this.changeHandler),this.container.addEventListener("click",this.clickHandler),document.addEventListener("keydown",this.keyHandler)}handleChange(e){if(this.container.contains(e.target))if(e.target.matches(this.selectors.selectAll.checkbox))this.handleSelectAll(e.target);else if(e.target.matches(this.selectors.item.checkbox)){const t=e.target.closest(this.selectors.item.item);if(!t)return;const s=this.getItemWrapper(e.target);if(!s)return;this.lastClicked=s.element;const i=this.getItemId(t);if(!i)return;e.target.checked?this.select(i,!1):this.deselect(i,!1),this.lastSelected=i,this.lastSelectedWrapper=s.element,this.updateSelectionUI()}}handleClick(e){if(!e.shiftKey)return;const t=e.target.closest(this.selectors.item.item);if(!t)return;const s=e.target.matches(this.selectors.item.checkbox),i=e.target.closest("label[for]");if(!s&&!i)return;const l=this.getItemWrapper(t);if(!l)return;if(!this.lastSelected||!this.lastSelectedWrapper||l.element!==this.lastSelectedWrapper)return;e.preventDefault();const c=this.getItemId(t),r=this.getWrapperChildren(l),n=r.findIndex(e=>e===this.lastSelected),a=r.findIndex(e=>e===c);if(-1===n||-1===a)return;const[h,d]=[Math.min(n,a),Math.max(n,a)];r.slice(h,d+1).forEach(e=>{this.select(e,!0,!1)}),this.lastSelected=c,this.updateSelectionUI(),this.notify("range-selected",{selectedItems:new Set(this.selectedItems),wrapper:l})}getWrapperChildren(e){return Array.from(e.items.children).map(e=>this.getItemId(e))}getItemWrapper(e){if(!e)return null;let t=e.closest(this.selectors.wrapper.wrapper);return t?this.getWrapper(t):null}getWrapper(e){return this.ui.wrappers[e.dataset[this.selectors.wrapper.id]]??null}handleKeys(e){"Escape"===e.key&&this.selectedItems.size>0&&(e.preventDefault(),Object.keys(this.ui.wrappers).length>1&&this.lastClicked?this.clearWrapperSelection(this.lastClicked):this.clearSelection())}handleSelectAll(e){const t=this.getItemWrapper(e);if(!t)return;const s=this.getWrapperChildren(t);e.checked?(s.forEach(e=>this.select(e,!0,!1)),this.lastSelectedWrapper=t.element):(s.forEach(e=>this.deselect(e,!0,!1)),this.lastSelectedWrapper=null),t.selectAll.span&&(t.selectAll.span.textContent=e.checked&&s.length>0?"Clear Selection":"Select All"),this.updateSelectionUI(),this.notify("select-all",{wrapper:t,checked:e.checked,ids:s,selectedItems:new Set(this.selectedItems)})}getItemId(e){if(!(!e instanceof Element)||(e=e.element??!1))return e.dataset[`${this.selectors.item.idAttribute}`]}select(e,t=!0,s=!0){if(this.selectedItems.has(e))return;this.selectedItems.add(e);let i=this.getItem(e);i&&i.element.classList.add("selected"),t&&this.setCheckboxState(e,!0),s&&this.updateSelectionUI(),this.notify("item-selected",{id:e,selectedItems:new Set(this.selectedItems)})}deselect(e,t=!0,s=!0){if(!this.selectedItems.has(e))return;this.selectedItems.delete(e);let i=this.getItem(e);i&&i.element.classList.remove("selected"),t&&this.setCheckboxState(e,!1),s&&this.updateSelectionUI(),this.notify("item-deselected",{id:e,selectedItems:new Set(this.selectedItems)})}toggle(e){this.selectedItems.has(e)?this.deselect(e):this.select(e),this.updateSelectionUI()}clearSelection(){this.selectedItems.forEach(e=>this.deselect(e,!0,!1)),this.selectedItems.clear(),this.lastSelected=null,this.lastSelectedWrapper=null;for(let e of Object.values(this.ui.wrappers))e.selectAll.checkbox&&(e.selectAll.checkbox.checked=!1),e.selectAll.span&&(e.selectAll.span.textContent="Select All");this.a11y.announce("Selection cleared"),this.updateSelectionUI(),this.notify("selection-cleared",{selectedItems:new Set})}clearWrapperSelection(e){(e=this.getWrapper(e))&&(this.getWrapperChildren(e).forEach(e=>this.deselect(e,!0,!1)),e.selectAll.checkbox&&(e.selectAll.checkbox.checked=!1),e.selectAll.span&&(e.selectAll.span.textContent="Select All"),this.a11y.announce("Selection cleared in group"),this.updateSelectionUI(),this.notify("wrapper-selection-cleared",{selectedItems:this.selectedItems}))}isSelected(e){return this.selectedItems.has(e)}getSelection(){return new Set(this.selectedItems)}setCheckboxState(e,t){const s=this.getItem(e);s&&s.checkbox&&s.checkbox.checked!==t&&(s.checkbox.checked=t)}updateSelectionUI(){if(!this.lastClicked)return;const e=this.getWrapper(this.lastClicked);if(!e||!e.selectAll)return;const t=this.selectedItems.size;let s=e.selectAll.bulkControls;s&&(s.hidden=0===t);let i=e.selectAll.count;if(i){const e=1===t?"item":"items";i.textContent=0===t?"":`${t} ${e} selected`,i.hidden=0===t}}collectItems(){this.container.querySelectorAll(this.selectors.item.item).forEach(e=>{this.setItem(e,!0)})}getItem(e){return this.items.has(e)?this.items.get(e):this.setItem(e)}setItem(e,t=!1){let s=t?e:this.container.querySelector(`[data-${this.camelToKebab(this.selectors.item.idAttribute)}="${e}"]`);return s?(e=this.getItemId(s),this.items.has(e)||this.items.set(e,{element:s,checkbox:s.querySelector(this.selectors.item.checkbox)}),this.items.get(e)):null}camelToKebab(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}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.container.removeEventListener("change",this.changeHandler),this.container.removeEventListener("click",this.clickHandler),this.container.removeEventListener("keydown",this.keyHandler),this.subscribers.clear(),this.selectedItems.clear()}}; |
| | | window.jvbHandleSelection=class{constructor(e,t={},s=!1){this.container=e,this.selectors=window.deepMerge({selectAll:{checkbox:"[data-select-all]",label:".selected label",span:".selected label span",target:"data-selects",count:".selected-count, .selected .info",bulkControls:".bulk-actions"},items:".item-grid, table",wrapper:{wrapper:":has(.item-grid, [data-select-all]):not([hidden]), table",id:"selection"},item:{item:".item",idAttribute:"id",checkbox:'[name="select-item"]'},wrappers:{}},t),this.hasTable=s,this.a11y=window.jvbA11y,this.selectedItems=new Set,this.lastSelected=null,this.lastSelectedWrapper=null,this.lastClicked=null,this.subscribers=new Set,this.items=new Map,this.initElements(),this.initListeners(),this.collectItems()}removeDataReferences(){let e=JSON.parse(JSON.stringify(this.selectors));return delete e.item.idAttribute,delete e.wrapper.id,delete e.selectAll.target,e}initElements(){this.index=0;let e=this.removeDataReferences();this.ui=window.uiFromSelectors(e,this.container),this.hasTable&&this.checkForTable(),this.container.querySelectorAll(this.selectors.wrapper.wrapper).forEach(e=>{this.addWrapper(e)})}checkForTable(){Object.hasOwn(this.ui.wrapper,"table")&&this.ui.wrapper.table||(this.ui.wrapper.table=this.container.querySelector("table"))}addWrapper(e){let t=this.selectors.wrapper.id;Object.hasOwn(e.dataset,t)||(e.setAttribute(`data-${t}`,this.index),this.index++);let s=this.removeDataReferences().selectAll;console.log("Adding wrapper from element: ",e),this.ui.wrappers[e.dataset[t]]={element:e,items:e.querySelector(this.selectors.items),selectAll:window.uiFromSelectors(s,e)}}removeWrapper(e){delete this.ui.wrappers[e.dataset[this.selectors.wrapper.id]]}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.keyHandler=this.handleKeys.bind(this),this.container.addEventListener("change",this.changeHandler),this.container.addEventListener("click",this.clickHandler),document.addEventListener("keydown",this.keyHandler)}handleChange(e){if(this.container.contains(e.target))if(e.target.matches(this.selectors.selectAll.checkbox))this.handleSelectAll(e.target);else if(e.target.matches(this.selectors.item.checkbox)){const t=e.target.closest(this.selectors.item.item);if(!t)return;const s=this.getItemWrapper(e.target);if(!s)return;this.lastClicked=s.element;const l=this.getItemId(t);if(!l)return;e.target.checked?this.select(l,!1):this.deselect(l,!1),this.lastSelected=l,this.lastSelectedWrapper=s.element,this.updateSelectionUI()}}handleClick(e){if(!e.shiftKey)return;const t=e.target.closest(this.selectors.item.item);if(!t)return;const s=e.target.matches(this.selectors.item.checkbox),l=e.target.closest("label[for]");if(!s&&!l)return;const i=this.getItemWrapper(t);if(!i)return;if(!this.lastSelected||!this.lastSelectedWrapper||i.element!==this.lastSelectedWrapper)return;e.preventDefault();const r=this.getItemId(t),c=this.getWrapperChildren(i),a=c.findIndex(e=>e===this.lastSelected),n=c.findIndex(e=>e===r);if(-1===a||-1===n)return;const[h,d]=[Math.min(a,n),Math.max(a,n)];c.slice(h,d+1).forEach(e=>{this.select(e,!0,!1)}),this.lastSelected=r,this.updateSelectionUI(),this.notify("range-selected",{selectedItems:new Set(this.selectedItems),wrapper:i})}getWrapperChildren(e){return Array.from("TABLE"===e.tagName?Array.from(e.children)[1].children:e.items.children).map(e=>this.getItemId(e))}getItemWrapper(e){if(!e)return null;console.log("Wrapper: ",this.selectors.wrapper.wrapper);let t=e.closest(this.selectors.wrapper.wrapper);return console.log("Found wrapper: ",t),t?this.getWrapper(t):null}getWrapper(e){return this.hasTable&&(this.checkForTable(),!this.ui.wrapper.table.hidden)?this.ui.wrapper.table:this.ui.wrappers[e.dataset[this.selectors.wrapper.id]]??null}handleKeys(e){"Escape"===e.key&&this.selectedItems.size>0&&(e.preventDefault(),Object.keys(this.ui.wrappers).length>1&&this.lastClicked?this.clearWrapperSelection(this.lastClicked):this.clearSelection())}handleSelectAll(e){const t=this.getItemWrapper(e);if(!t)return;const s=this.getWrapperChildren(t);e.checked?(s.forEach(e=>this.select(e,!0,!1)),this.lastSelectedWrapper=t.element):(s.forEach(e=>this.deselect(e,!0,!1)),this.lastSelectedWrapper=null),Object.hasOwn(t,"selectAll")&&Object.hasOwn(Object.selectAll,"span")&&t.selectAll.span&&(t.selectAll.span.textContent=e.checked&&s.length>0?"Clear Selection":"Select All"),this.updateSelectionUI(),this.notify("select-all",{wrapper:t,checked:e.checked,ids:s,selectedItems:new Set(this.selectedItems)})}getItemId(e){if(!(!e instanceof Element)||(e=e.element??!1))return e.dataset[`${this.selectors.item.idAttribute}`]}select(e,t=!0,s=!0){if(console.log("Selecting items..."),this.selectedItems.has(e))return;console.log("Not already selected..."),this.selectedItems.add(e);let l=this.getItem(e);console.log("Got item: ",l),l&&l.element.classList.add("selected"),t&&this.setCheckboxState(e,!0),s&&this.updateSelectionUI(),this.notify("item-selected",{id:e,selectedItems:new Set(this.selectedItems)})}deselect(e,t=!0,s=!0){if(!this.selectedItems.has(e))return;this.selectedItems.delete(e);let l=this.getItem(e);l&&l.element.classList.remove("selected"),t&&this.setCheckboxState(e,!1),s&&this.updateSelectionUI(),this.notify("item-deselected",{id:e,selectedItems:new Set(this.selectedItems)})}toggle(e){this.selectedItems.has(e)?this.deselect(e):this.select(e),this.updateSelectionUI()}clearSelection(){this.selectedItems.forEach(e=>this.deselect(e,!0,!1)),this.selectedItems.clear(),this.lastSelected=null,this.lastSelectedWrapper=null;for(let e of Object.values(this.ui.wrappers))e.selectAll.checkbox&&(e.selectAll.checkbox.checked=!1),e.selectAll.span&&(e.selectAll.span.textContent="Select All");this.a11y.announce("Selection cleared"),this.updateSelectionUI(),this.notify("selection-cleared",{selectedItems:new Set})}clearWrapperSelection(e){(e=this.getWrapper(e))&&(this.getWrapperChildren(e).forEach(e=>this.deselect(e,!0,!1)),e.selectAll.checkbox&&(e.selectAll.checkbox.checked=!1),e.selectAll.span&&(e.selectAll.span.textContent="Select All"),this.a11y.announce("Selection cleared in group"),this.updateSelectionUI(),this.notify("wrapper-selection-cleared",{selectedItems:this.selectedItems}))}isSelected(e){return this.selectedItems.has(e)}getSelection(){return new Set(this.selectedItems)}setCheckboxState(e,t){const s=this.getItem(e);s&&s.checkbox&&s.checkbox.checked!==t&&(s.checkbox.checked=t)}updateSelectionUI(){if(!this.lastClicked)return;const e=this.getWrapper(this.lastClicked);if(!e||!e.selectAll)return;const t=this.selectedItems.size;let s=e.selectAll.bulkControls;s&&(s.hidden=0===t);let l=e.selectAll.count;if(l){const e=1===t?"item":"items";l.textContent=0===t?"":`${t} ${e} selected`,l.hidden=0===t}}collectItems(){this.container.querySelectorAll(this.selectors.item.item).forEach(e=>{this.setItem(e,!0)})}getItem(e){return this.items.has(e)?this.items.get(e):this.setItem(e)}setItem(e,t=!1){let s=t?e:this.container.querySelector(`[data-${this.camelToKebab(this.selectors.item.idAttribute)}="${e}"]`);return s?(e=this.getItemId(s),this.items.has(e)||this.items.set(e,{element:s,checkbox:s.querySelector(this.selectors.item.checkbox)}),this.items.get(e)):null}camelToKebab(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}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.container.removeEventListener("change",this.changeHandler),this.container.removeEventListener("click",this.clickHandler),this.container.removeEventListener("keydown",this.keyHandler),this.subscribers.clear(),this.selectedItems.clear()}}; |
| | |
| | | (()=>{class e{constructor(){this.createModal(),this.setupModal(),this.currentSourceDay=null,this.days=["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],this.init()}handleChange(e){window.targetCheck(".hours-copy-btn")&&e.preventDefault({bubbles:!0})}init(){this.addCopyButtons(),this.bindEvents()}createModal(){this.modalElement=document.querySelector("dialog#copy-hours")}setupModal(){this.modal=new window.jvbModal(this.modalElement,{open:".hours-copy-btn",openMessage:"Copy hours modal opened",closeMessage:"Copy hours modal closed"}),this.modal.subscribe((e,o)=>{switch(e){case"modal-open":let e=o.event,t=o.modal;const s=e.target.closest('.group:not([data-field="openingHours"])'),n=this.getDayNameFromGroup(s);if(!n)return;this.currentSourceDay=n;const a=this.getSourceDayData(s);console.log(a);const c=t.querySelector(".source-day"),r=t.querySelector(".source-hours");console.log("Elements: ",{sourceDayEl:c,sourceHoursEl:r}),c&&(c.textContent=window.uppercaseFirst?window.uppercaseFirst(n):n.charAt(0).toUpperCase()+n.slice(1)),r&&(a.isOpen&&a.opens&&a.closes?(r.textContent=`${a.opens} - ${a.closes}`,r.className="source-hours"):(r.textContent="Closed",r.className="source-hours closed")),this.modal.modal.querySelector(`label[for=copy-to-${n}]`).hidden=!0,this.modal.modal.querySelectorAll(`label:not([for=copy-to-${n}])[hidden]`).hidden=!1;break;case"modal-close":this.applyCopyHours(),this.currentSourceDay=null}})}addCopyButtons(){document.querySelectorAll('[data-field="openingHours"] .group.field').forEach(e=>{if(!e.querySelector(".hours-copy-btn")){const o=this.createCopyButton();e.append(o)}})}createCopyButton(){const e=window.getTemplate("blankButton");return e.type="button",e.className="hours-copy-btn",e.title="Copy this time to other days",e.prepend(window.getIcon("copy")),e.setAttribute("data-action","copy-hours"),e}bindEvents(){this.changeHandler=this.handleChange.bind(this),this.modal.modal.addEventListener("change",this.changeHandler)}getDayNameFromGroup(e){return e.dataset.field.replace("openingHours|","")}getSourceDayData(e){const o=e.querySelector(`input[type="checkbox"][name="openingHours|${this.currentSourceDay}|isOpen"]`),t=e.querySelector(`input[name="openingHours|${this.currentSourceDay}|opens"]`),s=e.querySelector(`input[name="openingHours|${this.currentSourceDay}|closes"]`);return{isOpen:!!o&&o.checked,opens:t?t.value:"",closes:s?s.value:""}}applyCopyHours(){if(!this.currentSourceDay)return;const e=document.querySelector(`.group[data-field="openingHours|${this.currentSourceDay}"]`);if(!e)return;const o=this.getSourceDayData(e),t=Array.from(this.modal.modal.querySelectorAll("input:checked")).map(e=>e.value);0!==t.length?(t.forEach(e=>{this.applyHoursToDay(e,o)}),this.showSuccessFeedback(t.length)):alert("Please select at least one day to copy to.")}applyHoursToDay(e,o){const t=document.querySelector(`.group[data-field="openingHours|${e}`);if(!t)return;const s=t.querySelector(`input[type="checkbox"][name="openingHours|${e}|isOpen"]`),n=t.querySelector(`input[name="openingHours|${e}|opens"]`),a=t.querySelector(`input[name="openingHours|${e}|closes"]`);console.log("applying hours to days: ",{openCheckbox:s,opensInput:n,closesInput:a}),s&&(s.checked=o.isOpen,s.dispatchEvent(new Event("change",{bubbles:!0})),o.isOpen&&n&&a?(n.value=o.opens,n.dispatchEvent(new Event("change",{bubbles:!0})),a.value=o.closes,a.dispatchEvent(new Event("change",{bubbles:!0}))):n&&a&&(n.value="",n.dispatchEvent(new Event("change",{bubbles:!0})),a.value="",a.dispatchEvent(new Event("change",{bubbles:!0}))))}showSuccessFeedback(e){let o=document.getElementById("copy-success-feedback");const t=1===e?"Hours copied to 1 day successfully!":`Hours copied to ${e} days successfully!`;o.querySelector("span").textContent=t,o.classList.add("show"),window.debouncer.schedule("copy-success",()=>{o.classList.remove("show")},3e3),window.jvbA11y&&window.jvbA11y.announce&&window.jvbA11y.announce(t)}}document.addEventListener("DOMContentLoaded",()=>{window.jvbCopy=new e})})(); |
| | | (()=>{class e{constructor(){this.createModal(),this.setupModal(),this.currentSourceDay=null,this.days=["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],this.init()}handleChange(e){window.targetCheck(".hours-copy-btn")&&e.preventDefault({bubbles:!0})}init(){this.addCopyButtons(),this.bindEvents()}createModal(){this.modalElement=document.querySelector("dialog#copy-hours")}setupModal(){this.modal=new window.jvbModal(this.modalElement,{open:".hours-copy-btn",openMessage:"Copy hours modal opened",closeMessage:"Copy hours modal closed"}),this.modal.subscribe((e,t)=>{switch(e){case"modal-open":let e=t.event,o=t.modal;const s=e.target.closest('.group:not([data-field="openingHours"])'),n=this.getDayNameFromGroup(s);if(!n)return;this.currentSourceDay=n;const a=this.getSourceDayData(s),r=o.querySelector(".source-day"),c=o.querySelector(".source-hours");r&&(r.textContent=window.uppercaseFirst?window.uppercaseFirst(n):n.charAt(0).toUpperCase()+n.slice(1)),c&&(a.isOpen&&a.opens&&a.closes?(c.textContent=`${a.opens} - ${a.closes}`,c.className="source-hours"):(c.textContent="Closed",c.className="source-hours closed")),this.modal.modal.querySelector(`label[for=copy-to-${n}]`).hidden=!0,this.modal.modal.querySelectorAll(`label:not([for=copy-to-${n}])[hidden]`).hidden=!1;break;case"modal-close":this.applyCopyHours(),this.currentSourceDay=null}})}addCopyButtons(){document.querySelectorAll('[data-field="openingHours"] .group.field').forEach(e=>{if(!e.querySelector(".hours-copy-btn")){const t=this.createCopyButton();e.append(t)}})}createCopyButton(){const e=window.getTemplate("blankButton");return e.type="button",e.className="hours-copy-btn",e.title="Copy this time to other days",e.prepend(window.getIcon("copy")),e.setAttribute("data-action","copy-hours"),e}bindEvents(){this.changeHandler=this.handleChange.bind(this),this.modal.modal.addEventListener("change",this.changeHandler)}getDayNameFromGroup(e){return e.dataset.field.replace("openingHours|","")}getSourceDayData(e){const t=e.querySelector(`input[type="checkbox"][name="openingHours|${this.currentSourceDay}|isOpen"]`),o=e.querySelector(`input[name="openingHours|${this.currentSourceDay}|opens"]`),s=e.querySelector(`input[name="openingHours|${this.currentSourceDay}|closes"]`);return{isOpen:!!t&&t.checked,opens:o?o.value:"",closes:s?s.value:""}}applyCopyHours(){if(!this.currentSourceDay)return;const e=document.querySelector(`.group[data-field="openingHours|${this.currentSourceDay}"]`);if(!e)return;const t=this.getSourceDayData(e),o=Array.from(this.modal.modal.querySelectorAll("input:checked")).map(e=>e.value);0!==o.length?(o.forEach(e=>{this.applyHoursToDay(e,t)}),this.showSuccessFeedback(o.length)):alert("Please select at least one day to copy to.")}applyHoursToDay(e,t){const o=document.querySelector(`.group[data-field="openingHours|${e}`);if(!o)return;const s=o.querySelector(`input[type="checkbox"][name="openingHours|${e}|isOpen"]`),n=o.querySelector(`input[name="openingHours|${e}|opens"]`),a=o.querySelector(`input[name="openingHours|${e}|closes"]`);s&&(s.checked=t.isOpen,s.dispatchEvent(new Event("change",{bubbles:!0})),t.isOpen&&n&&a?(n.value=t.opens,n.dispatchEvent(new Event("change",{bubbles:!0})),a.value=t.closes,a.dispatchEvent(new Event("change",{bubbles:!0}))):n&&a&&(n.value="",n.dispatchEvent(new Event("change",{bubbles:!0})),a.value="",a.dispatchEvent(new Event("change",{bubbles:!0}))))}showSuccessFeedback(e){let t=document.getElementById("copy-success-feedback");const o=1===e?"Hours copied to 1 day successfully!":`Hours copied to ${e} days successfully!`;t.querySelector("span").textContent=o,t.classList.add("show"),window.debouncer.schedule("copy-success",()=>{t.classList.remove("show")},3e3),window.jvbA11y&&window.jvbA11y.announce&&window.jvbA11y.announce(o)}}document.addEventListener("DOMContentLoaded",()=>{window.jvbCopy=new e})})(); |
| New file |
| | |
| | | (()=>{class e{constructor(){this.initElements()}initElements(){this.selectors={magic:"form#magic-link-form",login:"form#loginform"},this.ui=window.uiFromSelectors(this.selectors),this.forms={};for(let[e,i]of Object.entries(this.ui))i&&(this.forms[e]=window.jvbForm.registerForm(i,{cache:!1,useQueue:!1}))}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(i=>{"auth-loaded"===i&&(window.login=new e)})})})(); |
| | |
| | | (()=>{class e{constructor(){this.templates=window.jvbTemplates,this.formHelper=window.jvbForm,this.defineTemplates(),this.data=null,this.form=null}populate(e,t={}){if(this.data=t,this.mergeRootData(),this.form=e,this.formHelper||(this.formHelper=window.jvbForm),this.formHelper){if(Object.hasOwn(this.data,"fields")&&0!==Object.keys(this.data.fields).length)for(let[t,i]of Object.entries(this.data.fields)){let a=e.querySelector(`[data-field="${t}"]`);a&&this.populateField(a,t,i)}}else requestAnimationFrame(()=>{this.populate(e,t)})}mergeRootData(){["status","date","modified"].forEach(e=>{this.data.fields[`post_${e}`]=this.data[e]})}populateField(e,t,i){let a=this.formHelper.getFieldType(e);if(!a||this.isEmptyValue(t))return;const l={repeater:this.populateRepeater.bind(this),"tag-list":this.populateTagList.bind(this),group:this.populateGroup.bind(this),location:this.populateLocation.bind(this),selector:this.populateTaxonomy.bind(this),user:this.populateUser.bind(this),upload:this.populateUpload.bind(this),gallery:this.populateUpload.bind(this),image:this.populateUpload.bind(this),set:this.populateMultiValue.bind(this),checkbox:this.populateMultiValue.bind(this),select:this.populateSingleValue.bind(this),radio:this.populateSingleValue.bind(this),"true-false":this.populateBoolean.bind(this),"toggle-text":this.populateBoolean.bind(this),date:this.populateDate.bind(this),time:this.populateDate.bind(this),datetime:this.populateDate.bind(this),number:this.populateNumber.bind(this),textarea:this.populateTextarea.bind(this),quantity:this.populateNumber.bind(this)};Object.hasOwn(l,a)?l[a](e,t,i):this.populateText(e,t,i)}populateRepeater(e,t,i){const a=e.querySelector(".repeater-items");let l=e.querySelector("template")?.className??!1;if(a&&l){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=[]}i&&!Array.isArray(i)&&"object"==typeof i&&(i=Object.values(i)),i&&Array.isArray(i)&&0!==i.length?(window.removeChildren(a),i.forEach((t,i)=>{const o={...t,index:i,repeater:e},r=this.templates.create(l,o);if(!r)return;a.append(r);const s=this.formHelper.getForm(r);s&&this.formHelper.initializeFields(r,s);for(let[e,i]of Object.entries(t)){let t=r.querySelector(`[data-field="${e}"]`);t&&this.populateField(t,e,i)}})):window.removeChildren(a)}}populateTagList(e,t,i){const a=e.querySelector(".tag-items");let l=e.querySelector("template")?.className??!1;if(a&&l){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=[]}i&&!Array.isArray(i)&&"object"==typeof i&&(i=Object.values(i)),i&&Array.isArray(i)&&0!==i.length?(window.removeChildren(a),i.forEach((i,o)=>{const r=this.templates.create(l,{label:this.getTagLabel(i,e.dataset.tagFormat??"first_field"),fieldName:t,...i});r&&(r.querySelectorAll('input[type="hidden"]').forEach(e=>{const t=e.dataset.field;t&&void 0!==i[t]&&(e.value=i[t])}),a.append(r))})):window.removeChildren(a)}}getTagLabel(e,t){const i=Object.values(e).filter(e=>!this.isEmptyValue(e));switch(t){case"first_field":return i[0]??"New Item";case"all_fields":return i.join(", ")||"New Item";default:if(t.includes("{")){let i=t;for(const[t,a]of Object.entries(e))i=i.replace(`{${t}}`,a);return i}return e[t]??i[0]??"New Item"}}populateGroup(e,t,i){if(i&&"object"==typeof i){console.log(i,"Populating Group Value: ");for(let[t,a]of Object.entries(i)){let i=e.querySelector(`[data-field="${t}"]`);i&&this.populateField(i,t,a)}}}populateLocation(e,t,i){["address","lat","lng","street","city","province","postal_code","country"].forEach(t=>{if(Object.hasOwn(i,t)){let a=e.querySelector(`[data-location-field="${t}"]`);a&&(a.value=String(i[t]||""))}})}populateTaxonomy(e,t,i){let a=this.splitIDs(i);const l=e.querySelector(`input[type="hidden"][name="${t}"]`);l&&(l.value=a.join(","),window.jvbSelector&&requestAnimationFrame(()=>{window.jvbSelector.updateFieldFromInput(l)}))}populateUser(e,t,i){this.populateTaxonomy(e,t,i)}populateUpload(e,t,i){if(e.dataset.subtype&&"timeline"===e.dataset.subtype)return void this.populateTimelineGallery(e,t,i);const a=this.splitIDs(i);if(0===a.length)return;const l=e.querySelector('input[type="hidden"]');l&&(l.value=a.join(","));const o=e.querySelector(".item-grid");e.querySelector(".progress")?.remove(),o&&(window.removeChildren(o),a.forEach(e=>{let t=this.data.images[e]??{};t.field={config:{showMeta:!0}},t.id=e,o.append(this.templates.create("uploadItem",t));let i=o.children,a=Array.from(i).filter(t=>t.dataset.id===`${e}`);a=a[0]??!1,a&&this.populateUploadMeta(a,null,e)}))}populateUploadMeta(e,t,i){const a=e.querySelector('[data-field="image_data"]');if(!a)return;let l=this.data.images[i]??!1;if(!l)return;a.dataset.attachmentId=l.id,a.setAttribute("data-ignore","");const o=["image-title","image-alt-text","image-caption"];for(const e of o){const t=a.querySelector(`[data-field="image_data|${e}"] input, [data-field="image_data|${e}"] textarea`);t&&""!==l[e]&&(t.value=window.decodeHTMLEntities(l[e]))}}populateTimelineGallery(e,t,i){if(!i||!Array.isArray(i)||0===i.length)return;let a=e.querySelector(".item-grid");if(a){window.removeChildren(a),e.querySelector(".progress")?.remove();for(let e of i){let t=this.templates.create("timelineItem",e);t&&a.append(t)}}}populateMultiValue(e,t,i){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=i.split(",").map(e=>e.trim())}Array.isArray(i)||(i=[String(i)]);let a=e.querySelector(`select[name="${t}"]`);if(a&&a.multiple)for(let e of a.options)e.selected=i.includes(e.value);else e.querySelectorAll(`input[type="checkbox"][name="${t}[]"], input[type="checkbox"][name="${t}"]`).forEach(e=>{e.checked=i.includes(e.value)})}populateSingleValue(e,t,i){i=String(i||"");let a=e.querySelector(`select[name="${t}"]`);if(a)return void(a.value=i);let l=e.querySelector(`input[type="radio"][value="${i}"], input[type="checkbox"][value="${i}"]`)||e.querySelector(`[name="${t}"][value="${i}"]`);l&&(l.checked=!0)}populateBoolean(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="checkbox"]`);a&&(a.checked=Boolean(i))}populateDate(e,t,i){const a=e.querySelector(`[name="${t}"], input`);if(a){"object"==typeof i&&Object.hasOwn(i,"date")&&(i=i.date);try{const e=new Date(i);if(!isNaN(e.getTime()))switch(a.type){case"date":a.value=e.toISOString().split("T")[0];break;case"time":a.value=e.toTimeString().slice(0,5);break;case"datetime-local":a.value=e.toISOString().slice(0,16);break;default:a.value=i}}catch(e){a.value=i}}}populateNumber(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="number"]`);a&&(a.value=Number(i)||0)}populateTextarea(e,t,i){let a=e.querySelector("textarea[data-editor], textarea");if(this.populateText(e,t,i),a?.dataset.editor){const t=e.querySelector(".ql-editor");t?t.innerHTML=i:a.dispatchEvent(new Event("change",{bubbles:!0}))}}populateText(e,t,i){let a=e.querySelector(`[name="${t}"]`)||e.querySelector("textarea[data-editor]")||e.querySelector('input:not([type="hidden"]):not([type="file"]), textarea, select');a&&(a.value=window.decodeHTMLEntities(i??""))}getFormHelper(){window.requestAnimationFrame(()=>{this.formHelper=window.jvbForm})}splitIDs(e){return String(e).split(",").map(e=>parseInt(e.trim())).filter(e=>!isNaN(e)&&e>0)}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}defineTemplates(){const e=this.templates,t=this;e.define("timelineItem",{refs:{select:'[name="select-item"]',video:"video",file:".select-item span",img:"img",details:'[data-field="image_data"] details',imgAlt:'[data-field="image-alt-text"]',imgTitle:'[data-field="image-title"]',imgDesc:'[data-field="image-caption"]'},manyRefs:{fields:".field"},setup({el:e,refs:i,manyRefs:a,data:l}){if(e.dataset.itemId=l.id,i.select){let e=i.select.closest(".preview");window.prefixInput(i.select,`${l.id}-`,e)}i.video&&i.video.remove(),i.file&&i.file.remove();let o=t.data.images[l.post_thumbnail]??!1;if(i.img&&o&&(i.img.src=o.medium||o.small||o.large||"",i.img.title=o.large.split("/").pop()??"",i.img.alt=o["image-alt-text"]??""),i.details){let e=t.data.images[l.post_thumbnail];i.details.setAttribute("data-ignore",""),i.details.dataset.attachmentId=l.post_thumbnail;let a=i.imgAlt.querySelector("input"),o=i.imgTitle.querySelector("input"),r=i.imgDesc.querySelector("textarea");window.prefixInput(a,`[${l.post_thumbnail}]`,i.imgAlt,!1,!0),window.prefixInput(o,`[${l.post_thumbnail}]`,i.imgTitle,!1,!0),window.prefixInput(r,`[${l.post_thumbnail}]`,i.imgDesc,!1,!0),Object.hasOwn(e,"image-alt-text")&&i.imgAlt&&(a.value=window.decodeHTMLEntities(e["image-alt-text"])),(Object.hasOwn(e,"image-title")||Object.hasOwn(l,"file"))&&i.imgTitle&&(o.value=window.decodeHTMLEntities(e["image-title"]||l.file.name)),Object.hasOwn(e,"image-caption")&&i.imgDesc&&(r.value=window.decodeHTMLEntities(e["image-caption"]))}if(a.fields)for(let e of a.fields){if(e.closest("[data-ignore]"))continue;if("group"===e.dataset.fieldType)continue;if("post_thumbnail"===e.dataset.field){e.remove();continue}let i=e.dataset.field;const a=e.querySelector('input:not([type="file"]), textarea, select');a&&window.prefixInput(a,`[${l.id}]`,e,!1,!0);let o=l[i]??"";t.isEmptyValue(o)||t.populateField(e,i,o)}}})}}document.addEventListener("DOMContentLoaded",function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbPopulate=new e)})})})(); |
| | | (()=>{class e{constructor(){this.templates=window.jvbTemplates,this.formHelper=window.jvbForm,this.defineTemplates(),this.data=null,this.form=null}populate(e,t={}){if(this.data=t,this.mergeRootData(),this.form=e,this.formHelper||(this.formHelper=window.jvbForm),this.formHelper){if(Object.hasOwn(this.data,"fields")&&0!==Object.keys(this.data.fields).length)for(let[t,i]of Object.entries(this.data.fields)){let a=e.querySelector(`[data-field="${t}"]`);a&&this.populateField(a,t,i,Object.hasOwn(this.data,"prefix")?this.data.prefix:null)}}else requestAnimationFrame(()=>{this.populate(e,t)})}mergeRootData(){["status","date","modified"].forEach(e=>{this.data.fields[`post_${e}`]=this.data[e]})}populateField(e,t,i,a=null){let l=this.formHelper.getFieldType(e);if(!l||this.isEmptyValue(t))return;const o={repeater:this.populateRepeater.bind(this),"tag-list":this.populateTagList.bind(this),group:this.populateGroup.bind(this),location:this.populateLocation.bind(this),selector:this.populateTaxonomy.bind(this),user:this.populateUser.bind(this),upload:this.populateUpload.bind(this),gallery:this.populateUpload.bind(this),image:this.populateUpload.bind(this),set:this.populateMultiValue.bind(this),checkbox:this.populateMultiValue.bind(this),select:this.populateSingleValue.bind(this),radio:this.populateSingleValue.bind(this),"true-false":this.populateBoolean.bind(this),"toggle-text":this.populateBoolean.bind(this),date:this.populateDate.bind(this),time:this.populateDate.bind(this),datetime:this.populateDate.bind(this),number:this.populateNumber.bind(this),textarea:this.populateTextarea.bind(this),quantity:this.populateNumber.bind(this)};Object.hasOwn(o,l)?o[l](e,t,i,a):this.populateText(e,t,i,a)}populateRepeater(e,t,i,a=null){const l=e.querySelector(".repeater-items");let o=e.querySelector("template")?.className??!1;if(l&&o){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=[]}i&&!Array.isArray(i)&&"object"==typeof i&&(i=Object.values(i)),i&&Array.isArray(i)&&0!==i.length?(window.removeChildren(l),i.forEach((t,i)=>{const r={...t,index:i,repeater:e};a&&(r.prefix=a);const n=this.templates.create(o,r);if(!n)return;l.append(n);const s=this.formHelper.getForm(n);s&&this.formHelper.initializeFields(n,s);for(let[e,i]of Object.entries(t)){let t=n.querySelector(`[data-field="${e}"]`);t&&this.populateField(t,e,i,a)}})):window.removeChildren(l)}}populateTagList(e,t,i,a=null){const l=e.querySelector(".tag-items");let o=e.querySelector("template")?.className??!1;if(l&&o){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=[]}i&&!Array.isArray(i)&&"object"==typeof i&&(i=Object.values(i)),i&&Array.isArray(i)&&0!==i.length?(window.removeChildren(l),i.forEach((i,r)=>{const n=this.templates.create(o,{label:this.getTagLabel(i,e.dataset.tagFormat??"first_field"),fieldName:t,...i});n&&(n.querySelectorAll('input[type="hidden"]').forEach(t=>{const l=t.dataset.field;l&&void 0!==i[l]&&(a&&window.prefixInput(t,a,e,!1,!0),t.value=i[l])}),l.append(n))})):window.removeChildren(l)}}getTagLabel(e,t){const i=Object.values(e).filter(e=>!this.isEmptyValue(e));switch(t){case"first_field":return i[0]??"New Item";case"all_fields":return i.join(", ")||"New Item";default:if(t.includes("{")){let i=t;for(const[t,a]of Object.entries(e))i=i.replace(`{${t}}`,a);return i}return e[t]??i[0]??"New Item"}}populateGroup(e,t,i,a=null){if(i&&"object"==typeof i){console.log(i,"Populating Group Value: ");for(let[t,l]of Object.entries(i)){let i=e.querySelector(`[data-field="${t}"]`);i&&this.populateField(i,t,l,a)}}}populateLocation(e,t,i,a=null){["address","lat","lng","street","city","province","postal_code","country"].forEach(t=>{if(Object.hasOwn(i,t)){let l=e.querySelector(`[data-location-field="${t}"]`);a&&l&&window.prefixInput(l,a,e,!1,!0),l&&(l.value=String(i[t]||""))}})}populateTaxonomy(e,t,i,a=null){let l=this.splitIDs(i);const o=e.querySelector(`input[type="hidden"][name="${t}"]`);o&&(a&&window.prefixInput(o,a,e,!1,!0),o.value=l.join(","),window.jvbSelector&&requestAnimationFrame(()=>{window.jvbSelector.updateFieldFromInput(o)}))}populateUser(e,t,i,a=null){this.populateTaxonomy(e,t,i,a)}populateUpload(e,t,i,a=null){if(e.dataset.subtype&&"timeline"===e.dataset.subtype)return void this.populateTimelineGallery(e,t,i,a);const l=this.splitIDs(i);if(0===l.length)return;const o=e.querySelector('input[type="hidden"]');o&&(a&&window.prefixInput(o,a,e,!1,!0),o.value=l.join(","));const r=e.querySelector(".item-grid");e.querySelector(".progress")?.remove(),r&&(window.removeChildren(r),l.forEach(e=>{let t=this.data.images[e]??{};t.field={config:{showMeta:!0}},t.id=e,r.append(this.templates.create("uploadItem",t));let i=r.children,a=Array.from(i).filter(t=>t.dataset.id===`${e}`);a=a[0]??!1,a&&this.populateUploadMeta(a,null,e)}))}populateUploadMeta(e,t,i,a=null){const l=e.querySelector('[data-field="image_data"]');if(!l)return;let o=this.data.images[i]??!1;if(!o)return;l.dataset.attachmentId=o.id,l.setAttribute("data-ignore","");const r=["image-title","image-alt-text","image-caption"];for(const t of r){const i=l.querySelector(`[data-field="image_data|${t}"] input, [data-field="image_data|${t}"] textarea`);i&&a&&window.prefixInput(i,a,e,!1,!0),i&&""!==o[t]&&(i.value=window.decodeHTMLEntities(o[t]))}}populateTimelineGallery(e,t,i,a=null){if(!i||!Array.isArray(i)||0===i.length)return;let l=e.querySelector(".item-grid");if(l){window.removeChildren(l),e.querySelector(".progress")?.remove();for(let e of i){a&&(e.prefix=a);let t=this.templates.create("timelineItem",e);t&&l.append(t)}}}populateMultiValue(e,t,i,a=null){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=i.split(",").map(e=>e.trim())}Array.isArray(i)||(i=[String(i)]);let l=e.querySelector(`select[name="${t}"]`);if(l&&l.multiple){for(let e of l.options)e.selected=i.includes(e.value);a&&window.prefixInput(l,a,e,!1,!0)}else e.querySelectorAll(`input[type="checkbox"][name="${t}[]"], input[type="checkbox"][name="${t}"]`).forEach(t=>{a&&window.prefixInput(t,a,e,!1,!0),t.checked=i.includes(t.value)})}populateSingleValue(e,t,i,a=null){i=String(i||"");let l=e.querySelector(`select[name="${t}"]`);if(l)return a&&window.prefixInput(l,a,e,!1,!0),void(l.value=i);let o=e.querySelector(`input[type="radio"][value="${i}"], input[type="checkbox"][value="${i}"]`)||e.querySelector(`[name="${t}"][value="${i}"]`);o&&(a&&window.prefixInput(o,a,e,!1,!0),o.checked=!0),a&&e.querySelectorAll(`input[type="radio"],input[type="checkbox"], input[name="${t}"]`).forEach(t=>{t!==o&&window.prefixInput(o,a,e,!1,!0)})}populateBoolean(e,t,i,a=null){const l=e.querySelector(`[name="${t}"], input[type="checkbox"]`);l&&(a&&window.prefixInput(l,a,e,!1,!0),l.checked=Boolean(i))}populateDate(e,t,i,a=null){const l=e.querySelector(`[name="${t}"], input`);if(l){a&&window.prefixInput(l,a,e,!1,!0),"object"==typeof i&&Object.hasOwn(i,"date")&&(i=i.date);try{const e=new Date(i);if(!isNaN(e.getTime()))switch(l.type){case"date":l.value=e.toISOString().split("T")[0];break;case"time":l.value=e.toTimeString().slice(0,5);break;case"datetime-local":l.value=e.toISOString().slice(0,16);break;default:l.value=i}}catch(e){l.value=i}}}populateNumber(e,t,i,a=null){const l=e.querySelector(`[name="${t}"], input[type="number"]`);l&&(a&&window.prefixInput(l,a,e,!1,!0),l.value=Number(i)||0)}populateTextarea(e,t,i,a=null){let l=e.querySelector("textarea[data-editor], textarea");if(this.populateText(e,t,i,a),l?.dataset.editor){const t=e.querySelector(".ql-editor");t?t.innerHTML=i:l.dispatchEvent(new Event("change",{bubbles:!0}))}}populateText(e,t,i,a=null){let l=e.querySelector(`[name="${t}"]`)||e.querySelector("textarea[data-editor]")||e.querySelector('input:not([type="hidden"]):not([type="file"]), textarea, select');l&&(a&&window.prefixInput(l,a,e,!1,!0),l.value=window.decodeHTMLEntities(i??""))}splitIDs(e){return String(e).split(",").map(e=>parseInt(e.trim())).filter(e=>!isNaN(e)&&e>0)}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}defineTemplates(){const e=this.templates,t=this;e.define("timelineItem",{refs:{select:'[name="select-item"]',video:"video",file:".select-item span",img:"img",details:'[data-field="image_data"] details',imgAlt:'[data-field="image-alt-text"]',imgTitle:'[data-field="image-title"]',imgDesc:'[data-field="image-caption"]'},manyRefs:{fields:".field"},setup({el:e,refs:i,manyRefs:a,data:l}){if(e.dataset.itemId=l.id,i.select){let e=i.select.closest(".preview");window.prefixInput(i.select,`${l.id}-`,e)}i.video&&i.video.remove(),i.file&&i.file.remove();let o=t.data.images[l.post_thumbnail]??!1;if(i.img&&o&&(i.img.src=o.medium||o.small||o.large||"",i.img.title=o.large.split("/").pop()??"",i.img.alt=o["image-alt-text"]??""),i.details){let e=t.data.images[l.post_thumbnail];i.details.setAttribute("data-ignore",""),i.details.dataset.attachmentId=l.post_thumbnail;let a=i.imgAlt.querySelector("input"),o=i.imgTitle.querySelector("input"),r=i.imgDesc.querySelector("textarea");window.prefixInput(a,`[${l.post_thumbnail}]`,i.imgAlt,!1,!0),window.prefixInput(o,`[${l.post_thumbnail}]`,i.imgTitle,!1,!0),window.prefixInput(r,`[${l.post_thumbnail}]`,i.imgDesc,!1,!0),Object.hasOwn(e,"image-alt-text")&&i.imgAlt&&(a.value=window.decodeHTMLEntities(e["image-alt-text"])),(Object.hasOwn(e,"image-title")||Object.hasOwn(l,"file"))&&i.imgTitle&&(o.value=window.decodeHTMLEntities(e["image-title"]||l.file.name)),Object.hasOwn(e,"image-caption")&&i.imgDesc&&(r.value=window.decodeHTMLEntities(e["image-caption"]))}if(a.fields)for(let e of a.fields){if(e.closest("[data-ignore]"))continue;if("group"===e.dataset.fieldType)continue;if("post_thumbnail"===e.dataset.field){e.remove();continue}let i=e.dataset.field;const a=e.querySelector('input:not([type="file"]), textarea, select');a&&window.prefixInput(a,`[${l.id}]`,e,!1,!0);let o=l[i]??"";t.isEmptyValue(o)||t.populateField(e,i,o)}}})}}document.addEventListener("DOMContentLoaded",function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbPopulate=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)})})})(); |
| | | (()=>{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(!Object.hasOwn(e,"target"))return;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(!Object.hasOwn(e,"target"))return;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)})})})(); |
| | |
| | | (()=>{class e extends window.jvbCheckout{constructor(e={}){super({...window.squareConfig,...e}),this.payments=null,this.card=null}async init(){if(window.Square)try{this.payments=window.Square.payments(this.config.application_id,this.config.location_id),await this.initializePaymentMethods(),this.isInitialized=!0,document.dispatchEvent(new CustomEvent("checkoutReady",{detail:{checkout:this,provider:"square"}}))}catch(e){console.error("Failed to initialize Square payments:",e),this.handleError(e)}else console.error("Square Web Payments SDK not loaded")}async initializePaymentMethods(){if(document.getElementById("payment-container"))try{this.card=await this.payments.card({style:this.getCardStyle()}),await this.card.attach("#payment-container"),this.card.addEventListener("cardBrandChanged",e=>{console.log("Card brand:",e.detail.cardBrand)})}catch(e){throw console.error("Failed to initialize card:",e),e}}getCardStyle(){return{input:{fontSize:"16px",fontFamily:"inherit",color:"#333",backgroundColor:"#fff"},".input-container":{borderColor:"#ccc",borderRadius:"4px"},".input-container.is-focus":{borderColor:"#006AFF",borderWidth:"2px",outline:"2px solid #006AFF",outlineOffset:"2px"},".input-container.is-error":{borderColor:"#d63638"}}}async processPayment(e){try{let t=null;if(this.selectedCardId)t=this.selectedCardId;else{const i=await this.card.tokenize({verificationDetails:{amount:String(e.total),currencyCode:this.config.currency||"CAD",intent:"CHARGE",customerInitiated:!0,billingContact:{givenName:e.customer.name.split(" ")[0],familyName:e.customer.name.split(" ").slice(1).join(" "),email:e.customer.email,phone:e.customer.phone}}});if("OK"!==i.status){const e=i.errors?.map(e=>e.message).join(", ")||"Unknown error";throw new Error(`Card tokenization failed: ${e}`)}t=i.token,i.details?.userChallenged&&console.log("3D Secure verification completed")}return await this.submitToServer(t,e,!!this.selectedCardId)}catch(e){throw console.error("Payment processing failed:",e),e}}async submitToServer(e,t,i=!1){if(!this.isOpen)throw new Error("Store is currently closed");const a=await fetch(this.config.api_url+"process-payment",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":this.config.nonce},body:JSON.stringify({source_id:e,is_saved_card:i,cart_id:this.getCartId(),amount:t.total,items:t.items,customer:{email:this.isLoggedIn?this.userEmail:t.customer.email,name:t.customer.name,phone:t.customer.phone},note:t.note,pickup_time:t.pickup_time})}),r=await a.json();if(!a.ok)throw new Error(r.message||"Payment processing failed");return this.clearCart(),r}extractOrderData(e){const t=super.extractOrderData(e);return t.items=t.items.map(e=>({catalog_object_id:e.catalog_id,quantity:e.quantity,price:e.price,note:e.note})),t}async loadSavedCards(){try{const e=await fetch(this.config.api_url+"saved-cards",{method:"GET",headers:{"X-WP-Nonce":this.config.nonce}}),t=await e.json();t.success&&t.cards&&(this.savedCards=t.cards,this.renderSavedCards())}catch(e){console.error("Failed to load saved cards:",e)}}}document.addEventListener("DOMContentLoaded",()=>{document.querySelector('#checkout[data-provider="square"]')&&(window.squareCheckout=new e)})})(); |
| | | (()=>{class e extends window.jvbCheckout{constructor(e={}){super({...window.squareConfig,...e}),this.payments=null,this.card=null}async init(){if(window.Square)try{this.isActive=!0,this.payments=window.Square.payments(this.config.application_id,this.config.location_id),await this.initializePaymentMethods(),this.isInitialized=!0,document.dispatchEvent(new CustomEvent("checkoutReady",{detail:{checkout:this,provider:"square"}}))}catch(e){this.isActive=!1}else console.error("Square Web Payments SDK not loaded")}async initializePaymentMethods(){if(document.getElementById("payment-container"))try{this.card=await this.payments.card({style:this.getCardStyle()}),await this.card.attach("#payment-container"),this.card.addEventListener("cardBrandChanged",e=>{console.log("Card brand:",e.detail.cardBrand)})}catch(e){throw console.error("Failed to initialize card:",e),e}}getCardStyle(){return{input:{fontSize:"16px",fontFamily:"inherit",color:"#333",backgroundColor:"#fff"},".input-container":{borderColor:"#ccc",borderRadius:"4px"},".input-container.is-focus":{borderColor:"#006AFF",borderWidth:"2px",outline:"2px solid #006AFF",outlineOffset:"2px"},".input-container.is-error":{borderColor:"#d63638"}}}async processPayment(e){try{let t=null;if(this.selectedCardId)t=this.selectedCardId;else{const i=await this.card.tokenize({verificationDetails:{amount:String(e.total),currencyCode:this.config.currency||"CAD",intent:"CHARGE",customerInitiated:!0,billingContact:{givenName:e.customer.name.split(" ")[0],familyName:e.customer.name.split(" ").slice(1).join(" "),email:e.customer.email,phone:e.customer.phone}}});if("OK"!==i.status){const e=i.errors?.map(e=>e.message).join(", ")||"Unknown error";throw new Error(`Card tokenization failed: ${e}`)}t=i.token,i.details?.userChallenged&&console.log("3D Secure verification completed")}return await this.submitToServer(t,e,!!this.selectedCardId)}catch(e){throw console.error("Payment processing failed:",e),e}}async submitToServer(e,t,i=!1){if(!this.isOpen)throw new Error("Store is currently closed");const a=await window.auth.fetch(`${this.api}process-payment`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({source_id:e,is_saved_card:i,cart_id:this.getCartId(),amount:t.total,items:t.items,customer:{email:this.isLoggedIn?this.userEmail:t.customer.email,name:t.customer.name,phone:t.customer.phone},note:t.note,pickup_time:t.pickup_time})}),r=await a.json();if(!a.ok)throw new Error(r.message||"Payment processing failed");return this.clearCart(),r}extractOrderData(e){const t=super.extractOrderData(e);return t.items=t.items.map(e=>({catalog_object_id:e.catalog_id,quantity:e.quantity,price:e.price,note:e.note})),t}async loadSavedCards(){try{const e=await window.auth.fetch(`${this.api}saved-cards`,{method:"POST"}),t=await e.json();t.success&&t.cards&&(this.savedCards=t.cards,this.renderSavedCards())}catch(e){console.error("Failed to load saved cards:",e)}}}document.addEventListener("DOMContentLoaded",()=>{document.querySelector('#checkout[data-provider="square"]')&&(window.squareCheckout=new e)})})(); |
| | |
| | | (()=>{class t{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.subscribers=new Set,this.tabs=new Map,this.hasHash=!1,this.init()}init(){this.initElements(),this.initListeners()}initElements(){this.selectors={nav:".tabs",tab:"[data-tab]",active:"button.tab.active",section:".tab-content",button:"button.tab",select:"select.tab-list"}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this)}handleClick(t){let e=this.getConfig(t.target);if(!e)return;const s=t.target.closest(this.selectors.tab);s&&this.switchTab(s.dataset.tab,e)}handleChange(t){let e=this.getConfig(t.target);e&&e&&this.switchTab(t.target.value,e)}registerTab(t,e={}){if(!t)return!1;let s=window.uiFromSelectors(this.selectors,t);if(!s.nav||!s.section)return console.error("No tab navigation or section found"),!1;s.nav.previousElementSibling&&s.nav.previousElementSibling.classList.contains("form-progress");let i=window.generateID("tab");t.dataset.tabsId=i,s.buttons=Array.from(t.querySelectorAll(this.selectors.button)),s.sections=Array.from(t.querySelectorAll(this.selectors.section)),s.sections.forEach(t=>{t.querySelector(".tabs")&&(e.hasChildren=!0,this.registerTab(t,{parent:i}))}),s.select&&(s.options=Array.from(s.select.querySelectorAll("option")));let a={id:i,ui:s,updateURL:e.updateURL??!0};return Object.hasOwn(e,"preCheck")&&(a.preCheck=e.preCheck),s.nav.addEventListener("click",this.clickHandler),s.select?.addEventListener("change",this.changeHandler),this.tabs.set(i,a),this.determineActiveTab(a),a}determineActiveTab(t){if(this.getInitialTabFromHash()){let e=this.tabs.get(t.id);if(e.activeTab&&t.activeTab&&e.activeTab===t.activeTab)return}let e=t.ui.buttons[0].dataset.tab??!1;e&&this.switchTab(e,t)}getInitialTabFromHash(){if(this.hasHash||!window.location.search)return!1;const t=new URLSearchParams(window.location.search).get("tab");if(!t)return!1;return t.split("|").forEach(t=>{const e=Array.from(this.tabs.values()).find(e=>e.ui.buttons.some(e=>e.dataset.tab===t));e&&this.switchTab(t,e)}),this.hasHash=!0,!0}removeTab(t){if(!t||!t.dataset.tabsId)return;let e=this.tabs.get(t.dataset.tabsId);e&&(e.ui.nav.removeEventListener("click",this.clickHandler),e.ui.select?.removeEventListener("change",this.changeHandler),this.tabs.delete(t.dataset.tabsId))}switchTab(t,e){if(!(e="string"==typeof e?this.tabs.get(e):e))return;let s=e.ui.sections.filter(t=>t.classList.contains("active"));Object.hasOwn(e,"preCheck")&&!e.preCheck(s[0],t,e)||(document.activeElement&&this.isInTabs(document.activeElement,e)&&document.activeElement.blur(),e.ui.buttons.forEach(t=>{t.classList.remove("active","previous","next")}),e.ui.buttons.forEach((s,i)=>{let a=s.dataset.tab===t;if(s.setAttribute("aria-selected",a),a){s.classList.add("active");let t=Math.max(i-1,0),a=Math.min(i+1,e.ui.buttons.length-1);t!==i&&e.ui.buttons[t]?.classList.add("previous"),a!==i&&e.ui.buttons[a]?.classList.add("next")}}),e.ui.sections.forEach((e,s)=>{let i=e.dataset.tab===t;e.classList.toggle("active",i),e.setAttribute("aria-hidden",!i),e.hidden=!i}),this.notify("tab-switched",{previous:e.activeTab,current:t,config:e}),e.activeTab=t,this.tabs.set(e.id,e),e?.hasChildren&&this.updateChildTabs(e.id),e?.updateURL&&this.updateURL(e),e.ui.select&&this.maybeUpdateSelect(t,e),this.a11y.announce(`Switched to ${t} tab`))}updateChildTabs(t){Array.from(this.tabs.values()).filter(e=>e.parent===t).forEach(t=>{let e=t.ui.buttons[0].dataset.tab??!1;e&&this.switchTab(e,t)})}updateURL(t){if(!t.updateURL)return;let e=this.checkAncestorsHash(t);e&&window.history.pushState({tab:t.activeTab},"",`?tab=${e}`)}checkAncestorsHash(t){const e=[];let s=t;for(;s;)e.unshift(s.activeTab),s=s.parent?this.tabs.get(s.parent):null;return e.join("|")}maybeUpdateSelect(t,e){e.ui.select&&Object.hasOwn(e,"options")&&e.options.forEach(s=>{s.value!==t||(e.ui.select.value=t)})}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}notify(t,e){this.subscribers.forEach(s=>s(t,e))}getConfig(t){const e=t.closest("[data-tabs-id]");if(!e)return!1;const s=this.tabs.get(e.dataset.tabsId);return s||!1}isInTabs(t,e){return e.ui.sections.some(e=>e.contains(t))}destroy(){this.subscribers.clear(),Array.from(this.tabs.values()).forEach(t=>{t.ui.nav.removeEventListener("click",this.clickHandler),t.ui.select?.removeEventListener("change",this.changeHandler)})}}document.addEventListener("DOMContentLoaded",function(){window.jvbTabs=new t})})(); |
| | | (()=>{class t{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.subscribers=new Set,this.tabs=new Map,this.hasHash=!1,this.shouldNotify=!0,this.init()}init(){this.initElements(),this.initListeners()}initElements(){this.selectors={nav:".tabs",tab:"[data-tab]",active:"button.tab.active",section:".tab-content",button:"button.tab",select:"select.tab-list"}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this)}handleClick(t){let e=this.getConfig(t.target);if(!e)return;const s=t.target.closest(this.selectors.tab);s&&(s.dataset.skip&&delete e.preCheck,this.switchTab(s.dataset.tab,e))}handleChange(t){let e=this.getConfig(t.target);e&&e&&this.switchTab(t.target.value,e)}registerTab(t,e={}){if(!t)return!1;let s=window.uiFromSelectors(this.selectors,t);if(!s.nav||!s.section)return console.error("No tab navigation or section found"),!1;s.nav.previousElementSibling&&s.nav.previousElementSibling.classList.contains("form-progress");let i=window.generateID("tab");t.dataset.tabsId=i,s.buttons=Array.from(t.querySelectorAll(this.selectors.button)),s.sections=Array.from(t.querySelectorAll(this.selectors.section)),s.sections.forEach(t=>{t.querySelector(".tabs")&&(e.hasChildren=!0,this.registerTab(t,{parent:i,...e}))}),s.select&&(s.options=Array.from(s.select.querySelectorAll("option")));let a={id:i,ui:s,updateURL:e.updateURL??!0,...e};return Object.hasOwn(e,"preCheck")&&(a.preCheck=e.preCheck),s.nav.addEventListener("click",this.clickHandler),s.select?.addEventListener("change",this.changeHandler),this.tabs.set(i,a),this.shouldNotify=!1,this.determineActiveTab(a),this.shouldNotify=!0,a}determineActiveTab(t){if(this.getInitialTabFromHash()){let e=this.tabs.get(t.id);if(e.activeTab&&t.activeTab&&e.activeTab===t.activeTab)return}let e=t.ui.buttons[0].dataset.tab??!1;e&&this.switchTab(e,t)}getInitialTabFromHash(){if(this.hasHash||!window.location.search)return!1;const t=new URLSearchParams(window.location.search).get("tab");if(!t)return!1;return t.split("|").forEach(t=>{const e=Array.from(this.tabs.values()).find(e=>e.ui.buttons.some(e=>e.dataset.tab===t));e&&this.switchTab(t,e)}),this.hasHash=!0,!0}removeTab(t){if(!t||!t.dataset.tabsId)return;let e=this.tabs.get(t.dataset.tabsId);e&&(e.ui.nav.removeEventListener("click",this.clickHandler),e.ui.select?.removeEventListener("change",this.changeHandler),this.tabs.delete(t.dataset.tabsId))}switchTab(t,e){if(!(e="string"==typeof e?this.tabs.get(e):e))return;let s=e.ui.sections.filter(t=>t.classList.contains("active"));Object.hasOwn(e,"preCheck")&&!e.preCheck(s[0],t,e)||(document.activeElement&&this.isInTabs(document.activeElement,e)&&document.activeElement.blur(),e.ui.buttons.forEach(t=>{t.classList.remove("active","previous","next")}),e.ui.buttons.forEach((s,i)=>{let a=s.dataset.tab===t;if(s.setAttribute("aria-selected",a),a){s.classList.add("active");let t=Math.max(i-1,0),a=Math.min(i+1,e.ui.buttons.length-1);t!==i&&e.ui.buttons[t]?.classList.add("previous"),a!==i&&e.ui.buttons[a]?.classList.add("next")}}),e.ui.sections.forEach((e,s)=>{let i=e.dataset.tab===t;e.classList.toggle("active",i),e.setAttribute("aria-hidden",!i),e.hidden=!i}),this.notify("tab-switched",{previous:e.activeTab,current:t,config:e}),e.activeTab=t,this.tabs.set(e.id,e),e?.hasChildren&&this.updateChildTabs(e.id),e?.updateURL&&this.updateURL(e),e.ui.select&&this.maybeUpdateSelect(t,e),this.shouldNotify&&this.a11y.announce(`Switched to ${t} tab`))}updateChildTabs(t){Array.from(this.tabs.values()).filter(e=>e.parent===t).forEach(t=>{let e=t.ui.buttons[0].dataset.tab??!1;e&&this.switchTab(e,t)})}updateURL(t){if(!t.updateURL)return;let e=this.checkAncestorsHash(t);e&&window.history.pushState({tab:t.activeTab},"",`?tab=${e}`)}checkAncestorsHash(t){const e=[];let s=t;for(;s;)e.unshift(s.activeTab),s=s.parent?this.tabs.get(s.parent):null;return e.join("|")}maybeUpdateSelect(t,e){e.ui.select&&Object.hasOwn(e,"options")&&e.options.forEach(s=>{s.value!==t||(e.ui.select.value=t)})}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}notify(t,e){this.subscribers.forEach(s=>s(t,e))}getConfig(t){const e=t.closest("[data-tabs-id]");if(!e)return!1;const s=this.tabs.get(e.dataset.tabsId);return s||!1}isInTabs(t,e){return e.ui.sections.some(e=>e.contains(t))}destroy(){this.subscribers.clear(),Array.from(this.tabs.values()).forEach(t=>{t.ui.nav.removeEventListener("click",this.clickHandler),t.ui.select?.removeEventListener("change",this.changeHandler)})}}document.addEventListener("DOMContentLoaded",function(){window.jvbTabs=new t})})(); |
| | |
| | | (()=>{class e{constructor(){this.a11y=window.jvbA11y,this.queue=window.jvbQueue,this.error=window.jvbError,this.templates=window.jvbTemplates,this.subscribers=new Set,this.initStores(),this.initWorker(),this.fields=new Map,this.uploads=new Map,this.groups=new Map,this.selected=new Map,this.selectionHandlers=new Map,this.sortables=new Map,this.changes=new Map,this.previewUrls=new Set,this.initElements(),this.initListeners(),this.defineTemplates()}defineTemplates(){const e=this.templates,t=this;e.define("uploadItem",{refs:{select:'[name="select-item"]',featured:'[name="featured"]',img:"img",video:"video",file:"label > span",details:"details",alt:'[name="image-alt-text"]',title:'[name="image-title"]',description:'[name="image-caption"]'},manyRefs:{inputs:"input, select, textarea"},setup({el:e,refs:s,manyRefs:i,data:a}){let r,o,l,d=!1;switch(Object.hasOwn(a,"file")?(e.dataset.uploadId=a.uploadId,r=t.getSubtypeFromMime(a.file.type)||"image",o="document"!==r&&t.createPreviewUrl(a.file),d=o,l=a.file.name||""):(e.dataset.id=a.id,r=t.getSubtypeFromURL(a.medium??a.src),o=a.medium??a.src,l=a["image-alt-text"]??""),e.dataset.subtype=r,s.featured&&(s.featured.value=a.uploadId),r){case"image":s.img&&(s.img.src=o,s.img.alt=l,d&&(s.img.dataset.previewUrl=d)),s.video&&s.video.remove(),s.file&&s.file.remove();break;case"video":s.video&&(s.video.src=o,s.video.alt=l,d&&(s.video.dataset.previewUrl=d)),s.img&&s.img.remove(),s.file&&s.file.remove();break;case"document":if(s.preview){let e=a.file.name.split(".").pop()?.toLowerCase()??"",t={pdf:"file-pdf",csv:"file-csv",doc:"file-doc",docx:"file-doc",txt:"file-txt",xls:"file-xls",xlsx:"file-xls"},i=window.getIcon(t[e]??"file");s.preview.innerText=a.file.name??a.title,s.preview.prepend(i)}s.img&&s.img.remove(),s.video&&s.video.remove()}if(s.details&&(Object.hasOwn(a,"field")&&Object.hasOwn(a.field,"config")&&Object.hasOwn(a.field.config,"showMeta")&&!a.field.config.showMeta?s.details.remove():(Object.hasOwn(a,"id")?s.details.dataset.attachmentId=a.id:Object.hasOwn(a,"uploadId")&&(s.details.dataset.uploadId=a.uploadId),s.details.setAttribute("data-ignore",""),"image"!==r&&s.alt?s.alt.closest(".field")?.remove():Object.hasOwn(a,"image-alt-text")&&s.alt&&(s.alt.value=a["image-alt-text"]),(Object.hasOwn(a,"title")||Object.hasOwn(a,"file"))&&s.title&&(s.title.value=a.title||a.file.name),Object.hasOwn(a,"image-caption")&&s.description&&(s.description.value=a["image-caption"]))),e.draggable="single"!==e.dataset.mode,i.inputs)for(let t of i.inputs){let s=t.closest("[data-field]")??t.closest(".radio-button")??e;window.prefixInput(t,`${a.id??a.uploadId}-`,s)}}}),e.define("imageGroup",{refs:{selectAll:"[data-select-all]",fields:".fields",details:"details",grid:".item-grid"},setup({el:t,refs:s,manyRefs:i,data:a}){if(t.dataset.groupId=a.groupId,s.selectAll){let e=s.selectAll.closest(".field");window.prefixInput(s.selectAll,`select-all-${a.groupId}`,e,!0)}let r=e.create("groupMetadata",{groupId:a.groupId});r?s.fields.append(r):s.details.remove(),s.grid&&(s.grid.dataset.groupId=a.groupId)}}),e.define("groupMetadata",{manyRefs:{inputs:"input,textarea,select"},setup({el:e,refs:t,manyRefs:s,data:i}){s.inputs&&s.inputs.forEach(e=>{let t=e.closest("[data-field]");e.dataset.groupId=i.groupId,window.prefixInput(e,`${i.groupId}-`,t)})}}),e.define("restoreNotification",{refs:{details:".details",wrap:".wrap"},setup({el:t,refs:s,manyRefs:i,data:a}){if(s.details){let e=a.bySource.size>1?` across ${a.bySource.size} pages`:"",t=a.pendingUploads.length>1?"uploads":"upload";s.details.textContent=`${a.pendingUploads.length} ${t} can be recovered${e}`}if(!s.wrap)return void console.warn("No wrap element in template");let r=1;for(const[t,i]of a.bySource){let a={index:r,isCurrent:t===window.location.href,src:t,uploads:i};s.wrap.append(e.create("restoreField",a)),r++}}}),e.define("restoreField",{refs:{h3:"h3",a:"h3 a",grid:".item-grid"},async setup({el:e,refs:s,manyRefs:i,data:a}){let r=t.registerField(e,!1,!1,`recovery_${a.index}`);a.isCurrent?(e.open=!0,s.a?.remove(),s.h3&&(s.h3.textContent="From this page:")):s.a&&(s.a.href=a.src,s.a.title="Navigate to page and restore",s.a.textContent=a.src);let o=[...new Set(a.uploads.map(e=>e.group??"preview"))];for(let e of o){let i="preview"===e||t.stores.groups.get(e);if(!i)continue;let o=await t.createGroupElement(e,r),l=o.querySelector(".item-grid"),d=a.uploads.filter(t=>t.group===("preview"===e)?null:e);for(const[e,t]of Object.entries(i.fields??{})){let s=o.querySelector(`input[name*="${e}"]`);s&&(s.value=t)}for(let e of d){let s=await t.createUpload(e.id,t.formatFile(e),r);l.append(s)}s.grid.append(o)}}})}initStores(){const{uploads:e,groups:t}=window.jvbStore.register("uploads",[{storeName:"uploads",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"status",keyPath:"status"},{name:"group",keyPath:"group"},{name:"src",keyPath:"src"}]},{storeName:"groups",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"src",keyPath:"src"}]}]);this.stores={uploads:e,groups:t,ready:[]},this.stores.uploads.subscribe(this.handleStores.bind(this,"uploads")),this.stores.groups.subscribe(this.handleStores.bind(this,"groups")),this.queue.subscribe((e,t)=>{if(("operation-status"===e||"cancel-operation"===e)&&["image_upload","video_upload","document_upload"].includes(t.type)){let s=[];if(t.data)if(t.data instanceof FormData){const e=this.stores.uploads.formDataToObject(t.data);s=e.upload_ids||[]}else s=t.data.upload_ids||[];if(0===s.length&&t.result&&t.result.upload_ids&&(s=t.result.upload_ids),!s||0===s.length)return void console.warn("[UploadManager] No upload_ids found for operation:",{id:t.id,type:t.type,status:t.status,hasData:!!t.data,hasResult:!!t.result});if("cancel-operation"===e)return this.handleOperationCancelled(s);this.setBulkUpload(s,"status",t.status).then(()=>{console.log(`[UploadManager] Updated ${s.length} uploads to status: ${t.status}`)}),"completed"===t.status&&("process_upload_groups"===t.type?(s.forEach(e=>{this.setBulkUpload([e],"serverProcessed",!0).then(()=>{})}),t.result&&t.result.created_posts&&console.log("[UploadManager] Created posts:",t.result.created_posts),setTimeout(()=>{s.forEach(e=>{this.removeUpload(e).then(()=>{})})},2e3)):s.forEach(e=>{this.removeUpload(e).then(()=>{})})),"failed"!==t.status&&"failed_permanent"!==t.status||console.error("[UploadManager] Operation failed:",{id:t.id,type:t.type,uploadIds:s,error:t.error_message})}})}storesReady(){return 2===this.stores.ready.length}handleStores(e,t){"data-ready"===t&&(this.stores.ready.push(e),this.storesReady()&&this.checkRecovery().then(()=>{}))}initWorker(){this.worker=null,this.workerState={worker:null,tasks:new Map,restart:{count:0,max:3},settings:{timeout:3e3,maxConcurrent:3,restartAfterTimeout:!0}}}initElements(){this.selectors={fields:{field:"[data-upload-field]",input:'input[type="file"]',dropZone:".file-upload-wrapper",preview:".preview-wrap",grid:".item-grid.preview",progress:{progress:".file-upload-container .progress",fill:".file-upload-container .progress .fill",details:".file-upload-container .progress .details",icon:".file-upload-container .progress .icon"},selectAll:"[data-select-all]",actions:".selection-actions",count:".selected .info",hidden:'input[type="hidden"]'},groups:{container:".group-display",grid:".item-grid.groups",empty:".empty-group",header:".sidebar .header"},group:{item:".upload-group",actions:".selection-actions",selectAll:'[name="select-all-group"]',count:".group-header .info",fields:"details .fields",grid:".item-grid.group",total:".group-content .group-count"},items:{item:".item.upload",checkbox:'[name="select-item"]',featured:'[name="featured"]',image:"img",details:"details",progress:{progress:".progress",fill:".fill",details:".details",icon:".icon"}}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.dragEnterHandler=this.handleDragEnter.bind(this),this.dragLeaveHandler=this.handleDragLeave.bind(this),this.dragOverHandler=this.handleDragOver.bind(this),this.dropHandler=this.handleDrop.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("dragenter",this.dragEnterHandler),document.addEventListener("dragleave",this.dragLeaveHandler),document.addEventListener("dragover",this.dragOverHandler),document.addEventListener("drop",this.dropHandler),window.addEventListener("beforeunload",()=>{this.cleanupAllPreviewUrls()})}async setUpload(e,t){const s={...{id:e,attachment:null,group:null,field:null,src:window.location.href,blob:null,status:"local_processing",operationId:null,fields:{}},...t};if(Object.preventExtensions(s),await this.stores.uploads.save(s),this.fields.has(s.field)){let e=this.fields.get(s.field);if("local_processing"===s.status)this.notify("upload-received",{field:e.element,id:s.id})}return s}createPreviewUrl(e){const t=URL.createObjectURL(e);return this.previewUrls.add(t),t}revokePreviewUrl(e){e?.startsWith("blob:")&&(URL.revokeObjectURL(e),this.previewUrls.delete(e))}formatFile(e){return e.blob?new File([e.blob],e.fields.originalName||"file",{type:e.fields.type||e.blob.type,lastModified:e.fields.lastModified||Date.now()}):null}handleClick(e){if(!window.targetCheck(e,this.selectors.fields.field))return;let t=window.targetCheck(e,this.selectors.fields.dropZone);t&&!e.target.matches("input, button, a")&&t.querySelector(this.selectors.fields.input)?.click();const s=window.targetCheck(e,"[data-action]");s&&this.handleAction(s)}handleAction(e){const t=e.dataset.action,s=this.getFieldIdFromElement(e);switch(t){case"add-to-group":this.handleAddToGroup(s).then(()=>{});break;case"delete-group":this.handleDeleteGroup(e);break;case"delete-upload":case"remove-from-group":this.handleRemoveItem(e).then(()=>{});break;case"upload":this.queueUploads("uploads/groups",s).then(()=>{});break;case"restore":this.handleRestoreSelected().then(()=>{});break;case"restore-all":this.handleRestoreAll().then(()=>{});break;case"clear-cache":this.handleClearCache().then(()=>{})}}handleChange(e){let t=this.getFieldIdFromElement(e.target);if(!t){return void(e.target.closest("[data-upload-id], [data-attachment-id]")&&this.queueUploadMeta(e))}if(e.target.matches(this.selectors.fields.input)){const s=Array.from(e.target.files);return void(s.length>0&&this.processFiles(t,s).then(()=>{}))}e.target.matches(this.selectors.items.checkbox)||e.target.matches(this.selectors.items.featured)||e.target.matches('[name*="select-"]')||("post_group"===this.fields.get(t).config.destination?this.handleGroupMetaChange(e.target):this.queueUploadMeta(e))}handleGroupMetaChange(e){const t=e.dataset.groupId;if(!t)return;const s=e.name;if(!s)return;const i=e.value,a=s.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");window.debouncer.schedule(`group-meta-${t}-${a}`,async()=>{const e=this.stores.groups.get(t);e&&(e.fields||(e.fields={}),e.fields[a]=i,await this.setGroup(t,e))},300)}handleDragEnter(e){if(!e.dataTransfer.types.includes("Files"))return;const t=e.target.closest(this.selectors.fields.dropZone);t&&(e.preventDefault(),t.classList.add("dragover"))}handleDragLeave(e){const t=e.target.closest(this.selectors.fields.dropZone);t&&!t.contains(e.relatedTarget)&&t.classList.remove("dragover")}handleDragOver(e){if(!e.dataTransfer.types.includes("Files"))return;e.target.closest(this.selectors.fields.dropZone)&&(e.preventDefault(),e.dataTransfer.dropEffect="copy")}handleDrop(e){const t=e.target.closest(this.selectors.fields.dropZone);if(!t)return;e.preventDefault(),t.classList.remove("dragover"),t.classList.add("uploading");const s=Array.from(e.dataTransfer.files);if(0===s.length)return;const i=this.getFieldIdFromElement(t);i&&(this.processFiles(i,s).then(()=>{this.updateHandlerItems(i)}),this.a11y.announce(`${s.length} file(s) dropped for upload`))}async queueUploads(e,t,s=null){let i=new FormData;const a=this.fields.get(t);if(!a)return;let r=this.stores.uploads.filterByIndex({field:t});if(0===r.length)return;const[o,l]=["uploads"===e,"uploads/groups"===e];let d,n,u,p,c;i.append("fieldId",a.id),i.append("content",a.config.content),o&&(i.append("mode",a.config.mode),i.append("field_name",a.config.repeaterPath||a.config.name),i.append("fieldId",a.id),i.append("field_type",a.config.type),i.append("subtype",a.config.subtype),i.append("item_id",a.config.itemID),i.append("destination",a.config.destination),s&&i.append("depends_on",s)),l?({posts:d,uploadMap:n,files:u}=this.collectGroups(t)):o&&({uploadMap:n,files:u}=this.collectUploads(t)),l&&i.append("posts",JSON.stringify(d)),u.forEach(e=>{i.append("files[]",e)}),i.append("upload_ids",JSON.stringify(n)),o?(p=`Uploading ${r.length} file${r.length>1?"s":""} to server...`,c=`Uploading ${r.length} file${r.length>1?"s":""}...`):l&&(p=`Creating ${d.length} ${a.config.content}${d.length>1?"s":""} from uploads...`,c=`Creating ${d.length} post${d.length>1?"s":""}...`),await this.setBulkUpload(r,"status","queued");let h=this.sendToQueue(e,i,p,c);if("uploads/groups"===e){let e=a.element.closest("details");e&&(e.open=!1),this.notify("groups_uploaded",{fieldId:t,posts:d,content:a.config.content})}return h?(a.operationId=h,await this.setBulkUpload(r,"operationId",h),await this.setBulkUpload(r,"status","uploading"),await this.setBulkGroup(t,"operationId",h),this.fields.set(a.id,a),this.notify("sent-to-queue",{field:a,operation:h})):await this.setBulkUpload(r,"status","failed"),h}async sendToQueue(e,t,s="",i="",a=!1){""===i&&(i=s);const r={endpoint:e,method:"POST",data:t,title:s,popup:i,canMerge:a,sendNow:"uploads/groups"===e,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},append:"_upload"};try{return await this.queue.addToQueue(r)}catch(e){return this.error.log(e,{component:"UploadManager",action:"sentToQueue"}),!1}}collectGroups(e){let t=this.stores.uploads.filterByIndex({field:e}),s=[],i=[],a=[];const r=this.stores.groups.filterByIndex({field:e}).filter(e=>{const t=this.getGroupUploadsInOrder(e);return t.length>0&&t.some(e=>this.formatFile(e))});for(const e of r){const t=this.groups.get(e.id)?.element,r=this.collectGroupFieldsFromDOM(t,e.id),o={groupId:e.id,images:[],fields:r},l=this.getGroupUploadsInOrder(e);for(const t of l){const s=this.formatFile(t);if(s){a.push(s);const r={upload_id:t.id,index:i.length},l=this.uploads.get(t.id),d=l?.element?.querySelector(`input[name="${e.id}_featured"]`);d?.checked&&(o.fields.featured=t.id),o.images.push(r),i.push(t.id)}}o.images.length>0&&s.push(o)}const o=t.filter(e=>!e.group);for(const e of o){const t={groupId:window.generateID("group"),images:[],fields:{}},r=this.formatFile(e);if(r){a.push(r);const s={upload_id:e.id,index:i.length};t.images.push(s),i.push(e.id)}t.images.length>0&&s.push(t)}return{posts:s,uploadMap:i,files:a}}getGroupUploadsInOrder(e){return e.uploads&&0!==e.uploads.length?e.uploads.map(e=>this.stores.uploads.get(e)).filter(Boolean):[]}collectGroupFieldsFromDOM(e,t){if(!e)return{};const s={};return e.querySelectorAll("input, textarea, select").forEach(e=>{const i=e.name.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");["featured","select-all"].some(e=>i.includes(e))||e.value&&(s[i]=e.value)}),s}collectUploads(e){let t=this.stores.uploads.filterByIndex({field:e});if(0===t.length)return;let s=[],i=[];for(const e of t){const t=this.formatFile(e);t&&(i.push(t),s.push(e.id))}return{uploadMap:s,files:i}}queueUploadMeta(e){let t=e.target.closest("[data-attachment-id]")?.dataset.attachmentId,s=!1;if(!t&&(t=e.target.closest("[data-upload-id]")?.dataset.uploadId,s=!0,!t))return;if(!this.changes.has(t)){let e={};s?e.uploadId=t:e.attachmentId=t,this.changes.set(t,e)}let i=e.target.closest("[data-field]").dataset.field;this.changes.get(t)[i]=e.target.value,this.scheduleSave()}scheduleSave(){window.debouncer.schedule("upload-meta",async()=>{if(this.changes.size>0){let e={};for(let[t,s]of this.changes.entries())console.log(t,s),e[t]=s;let t={user:window.auth.getUser(),items:e};await this.sendToQueue("uploads/meta",t,"Uploading Meta","Uploading Meta",!0),this.changes.clear()}},2e3)}scanFields(e,t=!0,s=!0){e.querySelectorAll(this.selectors.fields.field).forEach(e=>this.registerField(e,t,s))}registerField(e,t=!0,s=!0,i=null){const a={element:e,id:i||this.determineFieldId(e),config:this.extractFieldConfig(e,t,s),uploads:new Set,operationId:null,groups:[],ui:window.uiFromSelectors(this.selectors.fields,e),groupUI:window.uiFromSelectors(this.selectors.groups,e)};return this.fields.set(a.id,a),e.dataset.uploader=a.id,this.getSelectionHandler(a.id),"single"!==a.config.type&&this.initSortable(a.id),this.maybeLockUploads(a.id),a.id}extractFieldConfig(e,t,s){const i={autoUpload:t,showMeta:s,destination:e.dataset.destination||"meta",content:this.extractFieldContent(e),mode:e.dataset.mode||"direct",type:e.dataset.type||"single",name:e.dataset.field,itemID:this.extractFieldItemId(e)??0,maxFiles:"max-files"in e.dataset?parseInt(e.dataset.maxFiles):0,subType:e.dataset.subtype??"image",repeaterPath:null},a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a&&(i.repeaterPath=`${r.dataset.field}:${a.dataset.index}:${i.name}`),i}extractFieldContent(e){return e.dataset.content||e.closest("dialog")?.dataset.content||e.closest("form")?.dataset.save||null}extractFieldItemId(e){return e.dataset.itemId||e.closest("dialog")?.dataset.itemId||null}determineFieldId(e){let t=this.extractFieldContent(e);t=null===t?"":t+"_";let s=this.extractFieldItemId(e);s=null===s?"":s+"_";const i=e.dataset.field||"",a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a?`${t}${s}${r.dataset.field}_${a.dataset.index}_${i}`:`${t}${s}${i}`}getFieldIdFromElement(e){const t=e.closest(this.selectors.fields.field);return t?.dataset.uploader||null}updateFieldProgress(e,t,s,i){const a=this.fields.get(e);a&&window.showProgress(a.ui.progress,t,s,i)}getWorker(){return this.workerState.worker||"undefined"==typeof OffscreenCanvas||(this.workerState.worker=new Worker("worker.js"),this.workerState.worker.onmessage=e=>this.handleWorkerMessage(e),this.workerState.worker.onerror=e=>this.handleWorkerError(e)),this.workerState.worker}handleWorkerMessage(e){const{id:t,blob:s}=e.data,i=this.workerState.tasks.get(t);i&&(clearTimeout(i.timeoutId),i.resolve(s),this.workerState.tasks.delete(t))}handleWorkerError(e){this.workerState.tasks.forEach(t=>{clearTimeout(t.timeoutId),t.reject(e)}),this.workerState.tasks.clear(),this.restartWorker()}restartWorker(){this.workerState.worker&&(this.workerState.worker.terminate(),this.workerState.worker=null),this.workerState.restart.count++}async processImages(e,t=2200,s=2200){const i=[],a=[...e],r=this.workerState.settings.maxConcurrent,o=async()=>{for(;a.length>0;){const e=a.shift(),r=await this.processImage(e.file,t,s);i.push({uploadId:e.uploadId,blob:r})}};return await Promise.all(Array.from({length:Math.min(r,e.length)},()=>o())),i}async processImage(e,t=2200,s=2200,i=3e3){if("undefined"==typeof OffscreenCanvas)return this.resizeImage(e,t,s);try{return await this.withTimeout(this.workerImage(e,t,s),i)}catch(i){return this.resizeImage(e,t,s)}}withTimeout(e,t){return Promise.race([e,new Promise((e,s)=>setTimeout(()=>s(new Error("Timeout")),t))])}async workerImage(e,t=2200,s=2200){const{settings:i,restart:a}=this.workerState;if(a.count>=a.max)throw new Error("Worker max restarts exceeded");const r=await createImageBitmap(e);let{width:o,height:l}=r;if(o>t||l>s){const e=Math.min(t/o,s/l);o=Math.round(o*e),l=Math.round(l*e)}const d=this.getWorker(),n=crypto.randomUUID();return new Promise((t,s)=>{const a=setTimeout(()=>{this.workerState.tasks.delete(n),i.restartAfterTimeout&&this.restartWorker(),s(new Error("Timeout"))},i.timeout);this.workerState.tasks.set(n,{resolve:t,reject:s,timeoutId:a}),d.postMessage({id:n,imageBitmap:r,width:o,height:l,type:e.type,quality:.9},[r])})}resizeImage(e,t,s){return new Promise(i=>{const a=new Image;a.onload=()=>{URL.revokeObjectURL(a.src);let{width:r,height:o}=a;if(r>t||o>s){const e=Math.min(t/r,s/o);r=Math.round(r*e),o=Math.round(o*e)}const l=document.createElement("canvas");l.width=r,l.height=o,l.getContext("2d").drawImage(a,0,0,r,o),l.toBlob(i,e.type,.9)},a.src=URL.createObjectURL(e)})}async processFiles(e,t){let s=this.fields.get(e);if(!s)return;s.groupUI.container&&(s.groupUI.container.hidden=!1);const i=t.length;let a=0;this.updateFieldProgress(e,0,i,"Processing files...");const r=await Promise.all(t.map(async t=>{const s=window.generateID("upload"),i=await this.setUpload(s,{id:s,field:e,status:"local_processing",fields:{originalName:t.name,originalSize:t.size,type:t.type,lastModified:t.lastModified}}),a=await this.createUpload(s,t,e);return this.uploads.set(s,{element:a,ui:window.uiFromSelectors(this.selectors.items,a)}),await this.addToGroup(s,null),{uploadId:s,upload:i,file:t}})),o=r.filter(e=>e.file.type.startsWith("image/")),l=r.filter(e=>!e.file.type.startsWith("image/")),d=await this.processImages(o.map(e=>({file:e.file,uploadId:e.uploadId})));for(const{uploadId:t,blob:s}of d){const r=o.find(e=>e.uploadId===t);r&&(r.upload.blob=s,r.upload.fields.size=s.size,r.upload.status="queued",await this.setUpload(t,r.upload),a++,this.updateFieldProgress(e,a,i,"Processing files..."))}for(const{uploadId:t,upload:s,file:r}of l)s.blob=r,s.status="queued",await this.setUpload(t,s),a++,this.updateFieldProgress(e,a,i,"Processing files...");this.maybeLockUploads(e),s.config.autoUpload&&"post_group"!==s.config.destination&&await this.queueUploads("uploads",e)}async checkRecovery(){const e=Array.from(this.stores.groups.data.values());for(const t of e){this.stores.uploads.filterByIndex({group:t.id}).length>0||await this.stores.groups.delete(t.id)}}async restoreSelectedUploads(e){let t=window.location.href,s=Array.from(this.stores.uploads.data.values()).filter(s=>e.includes(s.id)&&s.src===t),i=[...new Set(s.map(e=>e.group))].filter(Boolean),a=s[0].field,r=document.querySelector(`[data-uploader="${a}"]`);if(!r){if(!("crudManager"in window)||!a.startsWith(window.crudManager.content))return void console.log("No field found for "+a);{let[e,t,s]=a.split("_");if(!(parseInt(t)>0))return void console.log("No field found for "+a);window.crudManager.openEditModal(t),r=document.querySelector(`[data-uploader="${a}"]`)}}let o=this.fields.get(a);o.groupUI.container&&(o.groupUI.container.hidden=!1);let l=[];for(let e of i){let t=this.stores.groups.get(e);await this.createGroup(a,e);let i=this.groups.get(e),r=s.filter(t=>t.group===e);if(t&&this.groups.has(e)){let e=t.fields;for(const[t,s]of Object.entries(e)){let e=i.element.querySelector(`input[name*="${t}"]`);e&&(e.value=s)}}else e=null;for(let t of r){let s=await this.createUpload(t.id,this.formatFile(t),a);this.uploads.set(t.id,{element:s,ui:window.uiFromSelectors(this.selectors.items,s)}),await this.addToGroup(t.id,e),l.push(t.id)}}let d=s.filter(e=>!l.includes(e.id));for(let e of d){let t=await this.createUpload(e.id,this.formatFile(e),a);this.uploads.set(e.id,{element:t,ui:window.uiFromSelectors(this.selectors.items,t)}),await this.addToGroup(e.id,null)}}async restoreUploads(e){const t=e.map(e=>this.stores.uploads.get(e)).filter(Boolean);0!==t.length&&await this.restoreSelectedUploads(t.map(e=>e.id))}async clearUploads(e){await Promise.all(e.map(e=>this.clearUpload(e)))}getStatusText(e){return{received:"Image Received",local_processing:"Processing Image...",queued:"Waiting to upload...",uploading:"Uploading to Server",pending:"Successfully sent to server. In line for further processing.",processing:"Processing on server...",completed:"Upload complete!",failed:"Upload failed (will retry)",failed_permanent:"Upload failed permanently"}[e]||e}getStatusProgress(e){return{local_processing:28,queued:50,uploading:66,pending:75,processing:89,completed:100}[e]??0}async createUpload(e,t,s){let i=this.fields.get(s);if(!i)return null;let a={uploadId:e,file:t,field:i};return this.templates.create("uploadItem",a)}getSubtypeFromURL(e){if(!e||""===e)return"";const t=e.split("?")[0].toLowerCase();return[".webp",".jpg",".jpeg",".png",".gif",".svg"].some(e=>t.endsWith(e))?"image":[".mp4",".ogg",".mov",".webm",".avi"].some(e=>t.endsWith(e))?"video":"document"}getSubtypeFromMime(e){return e.startsWith("image/")?"image":e.startsWith("video/")?"video":"document"}async handleRemoveItem(e){console.log("Handling remove upload");const t=e.closest(this.selectors.items.item);if(!t)return;const s=t.dataset.uploadId,i=t.dataset.id;if((s||i)&&confirm("Remove this item?")){if(s)await this.removeUpload(s);else{const s=this.getFieldIdFromElement(e);t.remove(),s&&(this.updateHiddenInput(s),this.maybeLockUploads(s))}this.a11y.announce("Item removed")}}updateHiddenInput(e){const t=this.fields.get(e);if(!t?.ui.hidden)return;const s=Array.from(t.ui.grid?.querySelectorAll(this.selectors.items.item)||[]).map(e=>Object.hasOwn(e.dataset,"id")&&e.dataset.id>0?e.dataset.id:Object.hasOwn(e.dataset,"upload-id")&&e.dataset.uploadId>0?e.dataset.uploadId:e.dataset.itemId).filter(Boolean).join(",");t.ui.hidden.value!==s&&(t.ui.hidden.value=s,t.ui.hidden.dispatchEvent(new Event("change",{bubbles:!0})))}async setBulkUpload(e,t,s){const i=Array.from(e).map(async e=>{if("string"==typeof e&&(e=await this.stores.uploads.get(e)),e)return"status"===t&&await this.setUploadStatus(e,s),e[t]=s,this.stores.uploads.save(e)});await Promise.all(i)}async setUploadStatus(e,t){"string"==typeof e&&(e=await this.stores.uploads.get(e)),e&&e.progress&&window.showProgress(e.progress,this.getStatusProgress(t),100,this.getStatusText(t),this.queue.icons[t]??"")}async removeUpload(e){let t=this.stores.uploads.get(e);if(!t)return;const s=t.field;if(t.group){let s=this.stores.groups.get(t.group);s.uploads=s.uploads.filter(t=>t!==e),0===s.uploads.length?await this.removeGroup(s.id,!1):await this.stores.groups.save(s)}await this.clearUpload(e),this.updateHiddenInput(s),this.maybeLockUploads(s);let i=this.selectionHandlers.get(s);i&&i.deselect(e),this.a11y.announce("Upload removed")}async clearUpload(e){const t=this.uploads.get(e);if(t&&(this.revokePreviewUrl(t.preview),t.element)){const e=t.element.dataset.previewUrl;this.revokePreviewUrl(e),t.element.remove()}this.uploads.delete(e),await this.stores.uploads.delete(e)}async handleAddToGroup(e){const t=this.selected.get(e);if(!t||0===t.size)return;let s=await this.createGroup(e);s&&(await Promise.all(Array.from(t).map(e=>this.addToGroup(e,s))),this.selectionHandlers.get(e)?.clearSelection(),this.a11y.announce(`Created group with ${t.size} items`))}async createGroup(e,t=null){let s=this.fields.get(e);if(!s)return;t||(t=window.generateID("group"));const i=this.createGroupElement(t,e);if(!i)return null;const a=s.groupUI.empty;a?.nextSibling?s.groupUI.grid.insertBefore(i,a.nextSibling):s.groupUI.grid.append(i);const r=i.querySelector(".item-grid");r&&(r.dataset.groupId=t,this.createSortable(e,r,t));let o=this.stores.groups.data.has(t)?this.stores.groups.data.get(t):{};return await this.setGroup(t,{...o,id:t,field:e}),t}createGroupElement(e,t=null){let s={groupId:e,fieldId:t},i=this.templates.create("imageGroup",s);return this.groups.set(e,{element:i,ui:window.uiFromSelectors(this.selectors.group,i)}),this.getSelectionHandler(t)?.addWrapper(i),i}async setGroup(e,t){const s={...{id:e,src:window.location.href,uploads:[],operationId:null,field:null,fields:{}},...t};Object.preventExtensions(s),await this.stores.groups.save(s)}async setBulkGroup(e,t,s){let i=this.stores.groups.filterByIndex({field:e});if(0===i.length)return;let a=i.map(e=>{e[t]=s,this.stores.groups.save(e)});await Promise.all(a)}async addToGroup(e,t=null){const s=this.stores.uploads.get(e),i=this.uploads.get(e);if(!s||!i)return;const a=this.fields.get(s.field);if(!a)return;if(null!==i.element?.parentElement&&(!t&&null===s.group||t===s.group))return void this.handleReorder(s.field,t);if(s.group){const t=this.stores.groups.get(s.group);t&&(t.uploads=t.uploads.filter(t=>t!==e),0===t.uploads.length?await this.removeGroup(t.id,!1):await this.stores.groups.save(t))}i.ui.checkbox&&(i.ui.checkbox.checked=!1);const r=this.selectionHandlers.get(s.field);if(r&&r.isSelected(e)&&r.deselect(e),this.selected.get(s.field)?.has(e)&&this.selected.get(s.field).delete(e),i.ui.featured&&(i.ui.featured.hidden=!t),t){i.ui.featured&&(i.ui.featured.name=`${t}_featured`);let a=this.stores.groups.get(t);a&&(a.uploads.push(e),s.group=t,await this.stores.groups.save(a))}else s.group=null;let o=t?this.groups.get(t)?.ui.grid:a.ui.grid;o&&(o.append(i.element),t&&await this.handleReorder(s.field,t)),await this.stores.uploads.save(s)}handleDeleteGroup(e){const t=e.closest(this.selectors.group.item);if(!t)return;let s=t.dataset.groupId;if(!confirm("Delete this group? Items will be moved back to the upload area."))return;let i=this.stores.uploads.filterByIndex({group:s});Promise.all(i.map(e=>this.addToGroup(e.id,null))).then(()=>{this.removeGroup(s,!1).then(()=>{}),this.a11y.announce("Group deleted. Items returned to upload area")})}async removeGroup(e,t=!0){let s=this.groups.get(e),i=this.stores.groups.get(e);if(!i)return;let a=!0;t&&i.uploads.length>0&&(a=window.confirm("Keep uploads in this group?")),await Promise.all(i.uploads.map(e=>a?this.addToGroup(e,null):this.removeUpload(e)));if(this.fields.get(i.field)){const t=this.getGroupKey(i.field,e),a=this.selectionHandlers.get(t);if(a?.destroy&&a.destroy(),this.selectionHandlers.get(i.field)&&s&&s.element&&this.selectionHandlers.get(i.field).removeWrapper(s.element),this.sortables.has(t)){const e=this.sortables.get(t);e?.destroy&&e.destroy(),this.sortables.delete(t)}}s?.element&&s.element.remove(),this.groups.delete(e),await this.stores.groups.delete(e),this.a11y.announce("Group removed")}maybeLockUploads(e){let t=this.fields.get(e);if(!t||!t.ui.dropZone)return;let s=this.stores.uploads.filterByIndex({field:e}).length,i=t.config.maxFiles??0;t.ui.dropZone.hidden=i>0&&s>=i}async handleOperationCancelled(e){0!==e.length&&e.forEach(e=>{this.removeUpload(e)})}getGroupKey(e,t=null){return t?`${e}_${t}`:`${e}`}getSelectionHandler(e){let t=this.getGroupKey(e);if(!this.selectionHandlers.has(t)){let s=this.fields.get(e);if(!s)return;if("post_group"!==s.config.destination)return;let i=new window.jvbHandleSelection(s.element,{selectAll:{checkbox:this.selectors.fields.selectAll,count:this.selectors.fields.count,bulkControls:this.selectors.fields.actions},item:{item:this.selectors.items.item,checkbox:this.selectors.items.checkbox,idAttribute:"uploadId"},wrapper:{wrapper:".preview-wrap, .upload-group",id:"groupId"}});i.subscribe((t,s)=>{this.selected.set(e,s.selectedItems)}),this.selectionHandlers.set(t,i)}return this.selectionHandlers.get(t)}updateHandlerItems(e){let t=this.getSelectionHandler(e);t&&t.collectItems()}initSortable(e){if(!window.Sortable)return;const t=this.fields.get(e);t&&(!Sortable._multiDragMounted&&Sortable.MultiDrag&&(Sortable.mount(new Sortable.MultiDrag),Sortable._multiDragMounted=!0),this.createSortable(e,t.ui.grid,null),this.initEmptyGroupDropZone(e))}createSortable(e,t,s){if(!t)return null;const i=this.getGroupKey(e,s);if(this.sortables.has(i))return this.sortables.get(i);const a=new Sortable(t,{animation:150,draggable:".item",multiDrag:!0,selectedClass:"selected",avoidImplicitDeselect:!0,group:{name:e,pull:!0,put:!0},dragClass:"dragging",ignore:".empty-group",onStart:t=>{const s=t.item,i=s?.dataset.uploadId,a=this.selected.get(e);if(i&&(!a||!a.has(i))){const t=this.selectionHandlers.get(e);t&&t.select(i)}},onEnd:t=>this.sortableDrop(t,e)});return this.sortables.set(i,a),a}initEmptyGroupDropZone(e){const t=this.fields.get(e),s=t?.groupUI?.empty;s&&(s.addEventListener("dragover",e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="move",s.classList.add("drag-over")}),s.addEventListener("dragleave",e=>{s.contains(e.relatedTarget)||s.classList.remove("drag-over")}),s.addEventListener("drop",async t=>{t.preventDefault(),t.stopPropagation(),s.classList.remove("drag-over");const i=this.selected.get(e);if(!i||0===i.size)return;const a=await this.createGroup(e);a&&(await Promise.all(Array.from(i).map(e=>this.addToGroup(e,a))),this.selectionHandlers.get(e)?.clearSelection())}))}async sortableDrop(e,t){const s=e.to,i=(e.items?.length>0?Array.from(e.items):[e.item]).map(e=>e.dataset.uploadId).filter(Boolean);if(0===i.length)return;const a=s.dataset.groupId||null;for(const e of i)await this.addToGroup(e,a);await this.handleReorder(t,a),this.selectionHandlers.get(t)?.clearSelection()}handleReorder(e,t=null){let s=t?this.groups.get(t)?.ui.grid:this.fields.get(e)?.ui.grid;if(s){if(t){let e=Array.from(s.children).filter(e=>e.matches(this.selectors.items.item)&&!e.classList.contains("ghost")).map(e=>e.dataset.uploadId).filter(e=>e),i=this.stores.groups.get(t);i&&(i.uploads=e,this.stores.groups.save(i).then(()=>{}))}else this.updateHiddenInput(e);this.a11y.announce("Items reordered")}else console.log("Couldn't Reorder items...")}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t={}){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("Subscriber error:",e)}})}destroy(){this.subscribers.clear(),this.previewUrls.forEach(e=>{this.revokePreviewUrl(e)}),this.previewUrls.clear()}cleanupAllPreviewUrls(){this.previewUrls.forEach(e=>this.revokePreviewUrl(e)),this.previewUrls.clear()}async handleClearCache(){const e=window.location.href,t=this.stores.uploads.filterByIndex({src:e}),s=this.stores.groups.filterByIndex({src:e});await Promise.all([...t.map(e=>this.clearUpload(e.id)),...s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id)))]),this.restoreModal&&this.cleanupRestore(),this.a11y.announce("Cache cleared for this page")}async getFilesForForm(e){const t=e.querySelectorAll(this.selectors.fields.field),s=[];for(const e of t){const t=this.determineFieldId(e),i=e.dataset.field,a=this.stores.uploads.filterByIndex({field:t});for(const e of a){const t=this.formatFile(e);t&&s.push({file:t,fieldName:i,uploadId:e.id,meta:e.fields||{}})}}return s}async clearFieldFromStores(e){const t=this.stores.uploads.filterByIndex({field:e}),s=this.stores.groups.filterByIndex({field:e});await Promise.all(t.map(e=>this.clearUpload(e.id))),await Promise.all(s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id))))}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbUploads=new e)})})})(); |
| | | (()=>{class e{constructor(){this.a11y=window.jvbA11y,this.queue=window.jvbQueue,this.error=window.jvbError,this.templates=window.jvbTemplates,this.subscribers=new Set,this.initStores(),this.initWorker(),this.fields=new Map,this.uploads=new Map,this.groups=new Map,this.selected=new Map,this.selectionHandlers=new Map,this.sortables=new Map,this.changes=new Map,this.previewUrls=new Set,this.initElements(),this.initListeners(),this.defineTemplates()}defineTemplates(){const e=this.templates,t=this;e.define("uploadItem",{refs:{select:'[name="select-item"]',featured:'[name="featured"]',img:"img",video:"video",file:"label > span",details:"details",alt:'[name="image-alt-text"]',title:'[name="image-title"]',description:'[name="image-caption"]'},manyRefs:{inputs:"input, select, textarea"},setup({el:e,refs:s,manyRefs:i,data:a}){let r,o,l,d=!1;switch(Object.hasOwn(a,"file")?(e.dataset.uploadId=a.uploadId,r=t.getSubtypeFromMime(a.file.type)||"image",o="document"!==r&&t.createPreviewUrl(a.file),d=o,l=a.file.name||""):(e.dataset.id=a.id,r=t.getSubtypeFromURL(a.medium??a.src),o=a.medium??a.src,l=a["image-alt-text"]??""),e.dataset.subtype=r,s.featured&&(s.featured.value=a.uploadId),r){case"image":s.img&&(s.img.src=o,s.img.alt=l,d&&(s.img.dataset.previewUrl=d)),s.video&&s.video.remove(),s.file&&s.file.remove();break;case"video":s.video&&(s.video.src=o,s.video.alt=l,d&&(s.video.dataset.previewUrl=d)),s.img&&s.img.remove(),s.file&&s.file.remove();break;case"document":if(s.preview){let e=a.file.name.split(".").pop()?.toLowerCase()??"",t={pdf:"file-pdf",csv:"file-csv",doc:"file-doc",docx:"file-doc",txt:"file-txt",xls:"file-xls",xlsx:"file-xls"},i=window.getIcon(t[e]??"file");s.preview.innerText=a.file.name??a.title,s.preview.prepend(i)}s.img&&s.img.remove(),s.video&&s.video.remove()}if(s.details&&(Object.hasOwn(a,"field")&&Object.hasOwn(a.field,"config")&&Object.hasOwn(a.field.config,"showMeta")&&!a.field.config.showMeta?s.details.remove():(Object.hasOwn(a,"id")?s.details.dataset.attachmentId=a.id:Object.hasOwn(a,"uploadId")&&(s.details.dataset.uploadId=a.uploadId),s.details.setAttribute("data-ignore",""),"image"!==r&&s.alt?s.alt.closest(".field")?.remove():Object.hasOwn(a,"image-alt-text")&&s.alt&&(s.alt.value=a["image-alt-text"]),(Object.hasOwn(a,"title")||Object.hasOwn(a,"file"))&&s.title&&(s.title.value=a.title||a.file.name),Object.hasOwn(a,"image-caption")&&s.description&&(s.description.value=a["image-caption"]))),e.draggable="single"!==e.dataset.mode,i.inputs)for(let t of i.inputs){let s=t.closest("[data-field]")??t.closest(".radio-button")??e;window.prefixInput(t,`${a.id??a.uploadId}-`,s)}}}),e.define("imageGroup",{refs:{selectAll:"[data-select-all]",fields:".fields",details:"details",grid:".item-grid"},setup({el:t,refs:s,manyRefs:i,data:a}){if(t.dataset.groupId=a.groupId,s.selectAll){let e=s.selectAll.closest(".field");window.prefixInput(s.selectAll,`select-all-${a.groupId}`,e,!0)}let r=e.create("groupMetadata",{groupId:a.groupId});r?s.fields.append(r):s.details.remove(),s.grid&&(s.grid.dataset.groupId=a.groupId)}}),e.define("groupMetadata",{manyRefs:{inputs:"input,textarea,select"},setup({el:e,refs:t,manyRefs:s,data:i}){s.inputs&&s.inputs.forEach(e=>{let t=e.closest("[data-field]");e.dataset.groupId=i.groupId,window.prefixInput(e,`${i.groupId}-`,t)})}}),e.define("restoreNotification",{refs:{details:".details",wrap:".wrap"},setup({el:t,refs:s,manyRefs:i,data:a}){if(s.details){let e=a.bySource.size>1?` across ${a.bySource.size} pages`:"",t=a.pendingUploads.length>1?"uploads":"upload";s.details.textContent=`${a.pendingUploads.length} ${t} can be recovered${e}`}if(!s.wrap)return void console.warn("No wrap element in template");let r=1;for(const[t,i]of a.bySource){let a={index:r,isCurrent:t===window.location.href,src:t,uploads:i};s.wrap.append(e.create("restoreField",a)),r++}}}),e.define("restoreField",{refs:{h3:"h3",a:"h3 a",grid:".item-grid"},async setup({el:e,refs:s,manyRefs:i,data:a}){let r=t.registerField(e,!1,!1,`recovery_${a.index}`);a.isCurrent?(e.open=!0,s.a?.remove(),s.h3&&(s.h3.textContent="From this page:")):s.a&&(s.a.href=a.src,s.a.title="Navigate to page and restore",s.a.textContent=a.src);let o=[...new Set(a.uploads.map(e=>e.group??"preview"))];for(let e of o){let i="preview"===e||t.stores.groups.get(e);if(!i)continue;let o=await t.createGroupElement(e,r),l=o.querySelector(".item-grid"),d=a.uploads.filter(t=>t.group===("preview"===e)?null:e);for(const[e,t]of Object.entries(i.fields??{})){let s=o.querySelector(`input[name*="${e}"]`);s&&(s.value=t)}for(let e of d){let s=await t.createUpload(e.id,t.formatFile(e),r);l.append(s)}s.grid.append(o)}}})}initStores(){const{uploads:e,groups:t}=window.jvbStore.register("uploads",[{storeName:"uploads",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"status",keyPath:"status"},{name:"group",keyPath:"group"},{name:"src",keyPath:"src"}]},{storeName:"groups",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"src",keyPath:"src"}]}]);this.stores={uploads:e,groups:t,ready:[]},this.stores.uploads.subscribe(this.handleStores.bind(this,"uploads")),this.stores.groups.subscribe(this.handleStores.bind(this,"groups")),this.queue.subscribe((e,t)=>{if(("operation-status"===e||"cancel-operation"===e)&&["image_upload","video_upload","document_upload"].includes(t.type)){let s=[];if(t.data)if(t.data instanceof FormData){const e=this.stores.uploads.formDataToObject(t.data);s=e.upload_ids||[]}else s=t.data.upload_ids||[];if(0===s.length&&t.result&&t.result.upload_ids&&(s=t.result.upload_ids),!s||0===s.length)return void console.warn("[UploadManager] No upload_ids found for operation:",{id:t.id,type:t.type,status:t.status,hasData:!!t.data,hasResult:!!t.result});if("cancel-operation"===e)return this.handleOperationCancelled(s);this.setBulkUpload(s,"status",t.status).then(()=>{console.log(`[UploadManager] Updated ${s.length} uploads to status: ${t.status}`)}),"completed"===t.status&&("process_upload_groups"===t.type?(s.forEach(e=>{this.setBulkUpload([e],"serverProcessed",!0).then(()=>{})}),t.result&&t.result.created_posts&&console.log("[UploadManager] Created posts:",t.result.created_posts),setTimeout(()=>{s.forEach(e=>{this.removeUpload(e).then(()=>{})})},2e3)):s.forEach(e=>{this.removeUpload(e).then(()=>{})})),"failed"!==t.status&&"failed_permanent"!==t.status||console.error("[UploadManager] Operation failed:",{id:t.id,type:t.type,uploadIds:s,error:t.error_message})}})}storesReady(){return 2===this.stores.ready.length}handleStores(e,t){"data-ready"===t&&(this.stores.ready.push(e),this.storesReady()&&this.checkRecovery().then(()=>{}))}initWorker(){this.worker=null,this.workerState={worker:null,tasks:new Map,restart:{count:0,max:3},settings:{timeout:3e3,maxConcurrent:3,restartAfterTimeout:!0}}}initElements(){this.selectors={fields:{field:"[data-upload-field]",input:'input[type="file"]',dropZone:".file-upload-wrapper",preview:".preview-wrap",grid:".item-grid.preview",progress:{progress:".file-upload-container .progress",fill:".file-upload-container .progress .fill",details:".file-upload-container .progress .details",icon:".file-upload-container .progress .icon"},selectAll:"[data-select-all]",actions:".selection-actions",count:".selected .info",hidden:'input[type="hidden"]'},groups:{container:".group-display",grid:".item-grid.groups",empty:".empty-group",header:".sidebar .header"},group:{item:".upload-group",actions:".selection-actions",selectAll:'[name="select-all-group"]',count:".group-header .info",fields:"details .fields",grid:".item-grid.group",total:".group-content .group-count"},items:{item:".item.upload",checkbox:'[name="select-item"]',featured:'[name="featured"]',image:"img",details:"details",progress:{progress:".progress",fill:".fill",details:".details",icon:".icon"}}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.dragEnterHandler=this.handleDragEnter.bind(this),this.dragLeaveHandler=this.handleDragLeave.bind(this),this.dragOverHandler=this.handleDragOver.bind(this),this.dropHandler=this.handleDrop.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("dragenter",this.dragEnterHandler),document.addEventListener("dragleave",this.dragLeaveHandler),document.addEventListener("dragover",this.dragOverHandler),document.addEventListener("drop",this.dropHandler),window.addEventListener("beforeunload",()=>{this.cleanupAllPreviewUrls()})}async setUpload(e,t){const s={...{id:e,attachment:null,group:null,field:null,src:window.location.href,blob:null,status:"local_processing",operationId:null,fields:{}},...t};if(Object.preventExtensions(s),await this.stores.uploads.save(s),this.fields.has(s.field)){let e=this.fields.get(s.field);if("local_processing"===s.status)this.notify("upload-received",{field:e.element,id:s.id})}return s}createPreviewUrl(e){const t=URL.createObjectURL(e);return this.previewUrls.add(t),t}revokePreviewUrl(e){e?.startsWith("blob:")&&(URL.revokeObjectURL(e),this.previewUrls.delete(e))}formatFile(e){return e.blob?new File([e.blob],e.fields.originalName||"file",{type:e.fields.type||e.blob.type,lastModified:e.fields.lastModified||Date.now()}):null}handleClick(e){if(!window.targetCheck(e,this.selectors.fields.field))return;let t=window.targetCheck(e,this.selectors.fields.dropZone);t&&!e.target.matches("input, button, a")&&t.querySelector(this.selectors.fields.input)?.click();const s=window.targetCheck(e,"[data-action]");s&&this.handleAction(s)}handleAction(e){const t=e.dataset.action,s=this.getFieldIdFromElement(e);switch(t){case"add-to-group":this.handleAddToGroup(s).then(()=>{});break;case"delete-group":this.handleDeleteGroup(e);break;case"delete-upload":case"remove-from-group":this.handleRemoveItem(e).then(()=>{});break;case"upload":this.queueUploads("uploads/groups",s).then(()=>{});break;case"restore":this.handleRestoreSelected().then(()=>{});break;case"restore-all":this.handleRestoreAll().then(()=>{});break;case"clear-cache":this.handleClearCache().then(()=>{})}}handleChange(e){let t=this.getFieldIdFromElement(e.target);if(!t){return void(e.target.closest("[data-upload-id], [data-attachment-id]")&&this.queueUploadMeta(e))}if(e.target.matches(this.selectors.fields.input)){const s=Array.from(e.target.files);return void(s.length>0&&this.processFiles(t,s).then(()=>{}))}e.target.matches(this.selectors.items.checkbox)||e.target.matches(this.selectors.items.featured)||e.target.matches('[name*="select-"]')||("post_group"===this.fields.get(t).config.destination?this.handleGroupMetaChange(e.target):this.queueUploadMeta(e))}handleGroupMetaChange(e){const t=e.dataset.groupId;if(!t)return;const s=e.name;if(!s)return;const i=e.value,a=s.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");window.debouncer.schedule(`group-meta-${t}-${a}`,async()=>{const e=this.stores.groups.get(t);e&&(e.fields||(e.fields={}),e.fields[a]=i,await this.setGroup(t,e))},300)}handleDragEnter(e){if(!e.dataTransfer.types.includes("Files"))return;const t=e.target.closest(this.selectors.fields.dropZone);t&&(e.preventDefault(),t.classList.add("dragover"))}handleDragLeave(e){const t=e.target.closest(this.selectors.fields.dropZone);t&&!t.contains(e.relatedTarget)&&t.classList.remove("dragover")}handleDragOver(e){if(!e.dataTransfer.types.includes("Files"))return;e.target.closest(this.selectors.fields.dropZone)&&(e.preventDefault(),e.dataTransfer.dropEffect="copy")}handleDrop(e){const t=e.target.closest(this.selectors.fields.dropZone);if(!t)return;e.preventDefault(),t.classList.remove("dragover"),t.classList.add("uploading");const s=Array.from(e.dataTransfer.files);if(0===s.length)return;const i=this.getFieldIdFromElement(t);i&&(this.processFiles(i,s).then(()=>{this.updateHandlerItems(i)}),this.a11y.announce(`${s.length} file(s) dropped for upload`))}async queueUploads(e,t,s=null){let i=new FormData;const a=this.fields.get(t);if(!a)return;let r=this.stores.uploads.filterByIndex({field:t});if(0===r.length)return;const[o,l]=["uploads"===e,"uploads/groups"===e];let d,n,u,p,c;i.append("fieldId",a.id),i.append("content",a.config.content),o&&(i.append("mode",a.config.mode),i.append("field_name",a.config.repeaterPath||a.config.name),i.append("fieldId",a.id),i.append("field_type",a.config.type),i.append("subtype",a.config.subtype),i.append("item_id",a.config.itemID),i.append("destination",a.config.destination),s&&i.append("depends_on",s)),l?({posts:d,uploadMap:n,files:u}=this.collectGroups(t)):o&&({uploadMap:n,files:u}=this.collectUploads(t)),l&&i.append("posts",JSON.stringify(d)),u.forEach(e=>{i.append("files[]",e)}),i.append("upload_ids",JSON.stringify(n)),o?(p=`Uploading ${r.length} file${r.length>1?"s":""} to server...`,c=`Uploading ${r.length} file${r.length>1?"s":""}...`):l&&(p=`Creating ${d.length} ${a.config.content}${d.length>1?"s":""} from uploads...`,c=`Creating ${d.length} post${d.length>1?"s":""}...`),await this.setBulkUpload(r,"status","queued");let h=this.sendToQueue(e,i,p,c);if("uploads/groups"===e){let e=a.element.closest("details");e&&(e.open=!1),this.notify("groups_uploaded",{fieldId:t,posts:d,content:a.config.content})}return h?(a.operationId=h,await this.setBulkUpload(r,"operationId",h),await this.setBulkUpload(r,"status","uploading"),await this.setBulkGroup(t,"operationId",h),this.fields.set(a.id,a),this.notify("sent-to-queue",{field:a,operation:h})):await this.setBulkUpload(r,"status","failed"),h}async sendToQueue(e,t,s="",i="",a=!1){""===i&&(i=s);const r={endpoint:e,method:"POST",data:t,title:s,popup:i,canMerge:a,sendNow:"uploads/groups"===e,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},append:"_upload"};try{return await this.queue.addToQueue(r)}catch(e){return this.error.log(e,{component:"UploadManager",action:"sentToQueue"}),!1}}collectGroups(e){let t=this.stores.uploads.filterByIndex({field:e}),s=[],i=[],a=[];const r=this.stores.groups.filterByIndex({field:e}).filter(e=>{const t=this.getGroupUploadsInOrder(e);return t.length>0&&t.some(e=>this.formatFile(e))});for(const e of r){const t=this.groups.get(e.id)?.element,r=this.collectGroupFieldsFromDOM(t,e.id),o={groupId:e.id,images:[],fields:r},l=this.getGroupUploadsInOrder(e);for(const t of l){const s=this.formatFile(t);if(s){a.push(s);const r={upload_id:t.id,index:i.length},l=this.uploads.get(t.id),d=l?.element?.querySelector(`input[name="${e.id}_featured"]`);d?.checked&&(o.fields.featured=t.id),o.images.push(r),i.push(t.id)}}o.images.length>0&&s.push(o)}const o=t.filter(e=>!e.group);for(const e of o){const t={groupId:window.generateID("group"),images:[],fields:{}},r=this.formatFile(e);if(r){a.push(r);const s={upload_id:e.id,index:i.length};t.images.push(s),i.push(e.id)}t.images.length>0&&s.push(t)}return{posts:s,uploadMap:i,files:a}}getGroupUploadsInOrder(e){return e.uploads&&0!==e.uploads.length?e.uploads.map(e=>this.stores.uploads.get(e)).filter(Boolean):[]}collectGroupFieldsFromDOM(e,t){if(!e)return{};const s={};return e.querySelectorAll("input, textarea, select").forEach(e=>{const i=e.name.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");["featured","select-all"].some(e=>i.includes(e))||e.value&&(s[i]=e.value)}),s}collectUploads(e){let t=this.stores.uploads.filterByIndex({field:e});if(0===t.length)return;let s=[],i=[];for(const e of t){const t=this.formatFile(e);t&&(i.push(t),s.push(e.id))}return{uploadMap:s,files:i}}queueUploadMeta(e){let t=e.target.closest("[data-attachment-id]")?.dataset.attachmentId,s=!1;if(!t&&(t=e.target.closest("[data-upload-id]")?.dataset.uploadId,s=!0,!t))return;if(!this.changes.has(t)){let e={};s?e.uploadId=t:e.attachmentId=t,this.changes.set(t,e)}let i=e.target.closest("[data-field]").dataset.field;this.changes.get(t)[i]=e.target.value,this.scheduleSave()}scheduleSave(){window.debouncer.schedule("upload-meta",async()=>{if(this.changes.size>0){let e={};for(let[t,s]of this.changes.entries())console.log(t,s),e[t]=s;let t={user:window.auth.getUser(),items:e};await this.sendToQueue("uploads/meta",t,"Uploading Meta","Uploading Meta",!0),this.changes.clear()}},2e3)}scanFields(e,t=!0,s=!0){e.querySelectorAll(this.selectors.fields.field).forEach(e=>this.registerField(e,t,s))}registerField(e,t=!0,s=!0,i=null){const a={element:e,id:i||this.determineFieldId(e),config:this.extractFieldConfig(e,t,s),uploads:new Set,operationId:null,groups:[],ui:window.uiFromSelectors(this.selectors.fields,e),groupUI:window.uiFromSelectors(this.selectors.groups,e)};return this.fields.set(a.id,a),e.dataset.uploader=a.id,this.getSelectionHandler(a.id),"single"!==a.config.type&&this.initSortable(a.id),this.maybeLockUploads(a.id),a.id}extractFieldConfig(e,t,s){const i={autoUpload:t,showMeta:s,destination:e.dataset.destination||"meta",content:this.extractFieldContent(e),mode:e.dataset.mode||"direct",type:e.dataset.type||"single",name:e.dataset.field,itemID:this.extractFieldItemId(e)??0,maxFiles:"max-files"in e.dataset?parseInt(e.dataset.maxFiles):0,subType:e.dataset.subtype??"image",repeaterPath:null},a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a&&(i.repeaterPath=`${r.dataset.field}:${a.dataset.index}:${i.name}`),i}extractFieldContent(e){return e.dataset.content||e.closest("dialog")?.dataset.content||e.closest("form")?.dataset.save||null}extractFieldItemId(e){return e.dataset.itemId||e.closest("dialog")?.dataset.itemId||null}determineFieldId(e){let t=this.extractFieldContent(e);t=null===t?"":t+"_";let s=this.extractFieldItemId(e);s=null===s?"":s+"_";const i=e.dataset.field||"",a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a?`${t}${s}${r.dataset.field}_${a.dataset.index}_${i}`:`${t}${s}${i}`}getFieldIdFromElement(e){const t=e.closest(this.selectors.fields.field);return t?.dataset.uploader||null}updateFieldProgress(e,t,s,i){const a=this.fields.get(e);a&&window.showProgress(a.ui.progress,t,s,i)}getWorker(){if(!this.workerState.worker&&"undefined"!=typeof OffscreenCanvas){const e=new Blob(["\n self.onmessage = function(e) {\n const { id, imageBitmap, width, height, type, quality } = e.data;\n const canvas = new OffscreenCanvas(width, height);\n const ctx = canvas.getContext('2d');\n ctx.drawImage(imageBitmap, 0, 0, width, height);\n canvas.convertToBlob({ type, quality }).then(blob => {\n self.postMessage({ id, blob });\n });\n };\n "],{type:"application/javascript"}),t=URL.createObjectURL(e);this.workerState.worker=new Worker(t),URL.revokeObjectURL(t),this.workerState.worker.onmessage=e=>this.handleWorkerMessage(e),this.workerState.worker.onerror=e=>this.handleWorkerError(e)}return this.workerState.worker}handleWorkerMessage(e){const{id:t,blob:s}=e.data,i=this.workerState.tasks.get(t);i&&(clearTimeout(i.timeoutId),i.resolve(s),this.workerState.tasks.delete(t))}handleWorkerError(e){this.workerState.tasks.forEach(t=>{clearTimeout(t.timeoutId),t.reject(e)}),this.workerState.tasks.clear(),this.restartWorker()}restartWorker(){this.workerState.worker&&(this.workerState.worker.terminate(),this.workerState.worker=null),this.workerState.restart.count++}async processImages(e,t=2200,s=2200){const i=[],a=[...e],r=this.workerState.settings.maxConcurrent,o=async()=>{for(;a.length>0;){const e=a.shift(),r=await this.processImage(e.file,t,s);i.push({uploadId:e.uploadId,blob:r})}};return await Promise.all(Array.from({length:Math.min(r,e.length)},()=>o())),i}async processImage(e,t=2200,s=2200,i=3e3){if("undefined"==typeof OffscreenCanvas)return this.resizeImage(e,t,s);try{return await this.withTimeout(this.workerImage(e,t,s),i)}catch(i){return this.resizeImage(e,t,s)}}withTimeout(e,t){return Promise.race([e,new Promise((e,s)=>setTimeout(()=>s(new Error("Timeout")),t))])}async workerImage(e,t=2200,s=2200){const{settings:i,restart:a}=this.workerState;if(a.count>=a.max)throw new Error("Worker max restarts exceeded");const r=await createImageBitmap(e);let{width:o,height:l}=r;if(o>t||l>s){const e=Math.min(t/o,s/l);o=Math.round(o*e),l=Math.round(l*e)}const d=this.getWorker(),n=crypto.randomUUID();return new Promise((t,s)=>{const a=setTimeout(()=>{this.workerState.tasks.delete(n),i.restartAfterTimeout&&this.restartWorker(),s(new Error("Timeout"))},i.timeout);this.workerState.tasks.set(n,{resolve:t,reject:s,timeoutId:a}),d.postMessage({id:n,imageBitmap:r,width:o,height:l,type:e.type,quality:.9},[r])})}resizeImage(e,t,s){return new Promise(i=>{const a=new Image;a.onload=()=>{URL.revokeObjectURL(a.src);let{width:r,height:o}=a;if(r>t||o>s){const e=Math.min(t/r,s/o);r=Math.round(r*e),o=Math.round(o*e)}const l=document.createElement("canvas");l.width=r,l.height=o,l.getContext("2d").drawImage(a,0,0,r,o),l.toBlob(i,e.type,.9)},a.src=URL.createObjectURL(e)})}async processFiles(e,t){let s=this.fields.get(e);if(!s)return;s.groupUI.container&&(s.groupUI.container.hidden=!1);const i=t.length;let a=0;this.updateFieldProgress(e,0,i,"Processing files...");const r=await Promise.all(t.map(async t=>{const s=window.generateID("upload"),i=await this.setUpload(s,{id:s,field:e,status:"local_processing",fields:{originalName:t.name,originalSize:t.size,type:t.type,lastModified:t.lastModified}}),a=await this.createUpload(s,t,e);return this.uploads.set(s,{element:a,ui:window.uiFromSelectors(this.selectors.items,a)}),await this.addToGroup(s,null),{uploadId:s,upload:i,file:t}})),o=r.filter(e=>e.file.type.startsWith("image/")),l=r.filter(e=>!e.file.type.startsWith("image/")),d=await this.processImages(o.map(e=>({file:e.file,uploadId:e.uploadId})));for(const{uploadId:t,blob:s}of d){const r=o.find(e=>e.uploadId===t);r&&(r.upload.blob=s,r.upload.fields.size=s.size,r.upload.status="queued",await this.setUpload(t,r.upload),a++,this.updateFieldProgress(e,a,i,"Processing files..."))}for(const{uploadId:t,upload:s,file:r}of l)s.blob=r,s.status="queued",await this.setUpload(t,s),a++,this.updateFieldProgress(e,a,i,"Processing files...");this.maybeLockUploads(e),s.config.autoUpload&&"post_group"!==s.config.destination&&await this.queueUploads("uploads",e)}async checkRecovery(){const e=Array.from(this.stores.groups.data.values());for(const t of e){this.stores.uploads.filterByIndex({group:t.id}).length>0||await this.stores.groups.delete(t.id)}}async restoreSelectedUploads(e){let t=window.location.href,s=Array.from(this.stores.uploads.data.values()).filter(s=>e.includes(s.id)&&s.src===t),i=[...new Set(s.map(e=>e.group))].filter(Boolean),a=s[0].field,r=document.querySelector(`[data-uploader="${a}"]`);if(!r){if(!("crudManager"in window)||!a.startsWith(window.crudManager.content))return void console.log("No field found for "+a);{let[e,t,s]=a.split("_");if(!(parseInt(t)>0))return void console.log("No field found for "+a);window.crudManager.openEditModal(t),r=document.querySelector(`[data-uploader="${a}"]`)}}let o=this.fields.get(a);o.groupUI.container&&(o.groupUI.container.hidden=!1);let l=[];for(let e of i){let t=this.stores.groups.get(e);await this.createGroup(a,e);let i=this.groups.get(e),r=s.filter(t=>t.group===e);if(t&&this.groups.has(e)){let e=t.fields;for(const[t,s]of Object.entries(e)){let e=i.element.querySelector(`input[name*="${t}"]`);e&&(e.value=s)}}else e=null;for(let t of r){let s=await this.createUpload(t.id,this.formatFile(t),a);this.uploads.set(t.id,{element:s,ui:window.uiFromSelectors(this.selectors.items,s)}),await this.addToGroup(t.id,e),l.push(t.id)}}let d=s.filter(e=>!l.includes(e.id));for(let e of d){let t=await this.createUpload(e.id,this.formatFile(e),a);this.uploads.set(e.id,{element:t,ui:window.uiFromSelectors(this.selectors.items,t)}),await this.addToGroup(e.id,null)}}async restoreUploads(e){const t=e.map(e=>this.stores.uploads.get(e)).filter(Boolean);0!==t.length&&await this.restoreSelectedUploads(t.map(e=>e.id))}async clearUploads(e){await Promise.all(e.map(e=>this.clearUpload(e)))}getStatusText(e){return{received:"Image Received",local_processing:"Processing Image...",queued:"Waiting to upload...",uploading:"Uploading to Server",pending:"Successfully sent to server. In line for further processing.",processing:"Processing on server...",completed:"Upload complete!",failed:"Upload failed (will retry)",failed_permanent:"Upload failed permanently"}[e]||e}getStatusProgress(e){return{local_processing:28,queued:50,uploading:66,pending:75,processing:89,completed:100}[e]??0}async createUpload(e,t,s){let i=this.fields.get(s);if(!i)return null;let a={uploadId:e,file:t,field:i};return this.templates.create("uploadItem",a)}getSubtypeFromURL(e){if(!e||""===e)return"";const t=e.split("?")[0].toLowerCase();return[".webp",".jpg",".jpeg",".png",".gif",".svg"].some(e=>t.endsWith(e))?"image":[".mp4",".ogg",".mov",".webm",".avi"].some(e=>t.endsWith(e))?"video":"document"}getSubtypeFromMime(e){return e.startsWith("image/")?"image":e.startsWith("video/")?"video":"document"}async handleRemoveItem(e){console.log("Handling remove upload");const t=e.closest(this.selectors.items.item);if(!t)return;const s=t.dataset.uploadId,i=t.dataset.id;if((s||i)&&confirm("Remove this item?")){if(s)await this.removeUpload(s);else{const s=this.getFieldIdFromElement(e);t.remove(),s&&(this.updateHiddenInput(s),this.maybeLockUploads(s))}this.a11y.announce("Item removed")}}updateHiddenInput(e){const t=this.fields.get(e);if(!t?.ui.hidden)return;const s=Array.from(t.ui.grid?.querySelectorAll(this.selectors.items.item)||[]).map(e=>Object.hasOwn(e.dataset,"id")&&e.dataset.id>0?e.dataset.id:Object.hasOwn(e.dataset,"upload-id")&&e.dataset.uploadId>0?e.dataset.uploadId:e.dataset.itemId).filter(Boolean).join(",");t.ui.hidden.value!==s&&(t.ui.hidden.value=s,t.ui.hidden.dispatchEvent(new Event("change",{bubbles:!0})))}async setBulkUpload(e,t,s){const i=Array.from(e).map(async e=>{if("string"==typeof e&&(e=await this.stores.uploads.get(e)),e)return"status"===t&&await this.setUploadStatus(e,s),e[t]=s,this.stores.uploads.save(e)});await Promise.all(i)}async setUploadStatus(e,t){"string"==typeof e&&(e=await this.stores.uploads.get(e)),e&&e.progress&&window.showProgress(e.progress,this.getStatusProgress(t),100,this.getStatusText(t),this.queue.icons[t]??"")}async removeUpload(e){let t=this.stores.uploads.get(e);if(!t)return;const s=t.field;if(t.group){let s=this.stores.groups.get(t.group);s.uploads=s.uploads.filter(t=>t!==e),0===s.uploads.length?await this.removeGroup(s.id,!1):await this.stores.groups.save(s)}await this.clearUpload(e),this.updateHiddenInput(s),this.maybeLockUploads(s);let i=this.selectionHandlers.get(s);i&&i.deselect(e),this.a11y.announce("Upload removed")}async clearUpload(e){const t=this.uploads.get(e);if(t&&(this.revokePreviewUrl(t.preview),t.element)){const e=t.element.dataset.previewUrl;this.revokePreviewUrl(e),t.element.remove()}this.uploads.delete(e),await this.stores.uploads.delete(e)}async handleAddToGroup(e){const t=this.selected.get(e);if(!t||0===t.size)return;let s=await this.createGroup(e);s&&(await Promise.all(Array.from(t).map(e=>this.addToGroup(e,s))),this.selectionHandlers.get(e)?.clearSelection(),this.a11y.announce(`Created group with ${t.size} items`))}async createGroup(e,t=null){let s=this.fields.get(e);if(!s)return;t||(t=window.generateID("group"));const i=this.createGroupElement(t,e);if(!i)return null;const a=s.groupUI.empty;a?.nextSibling?s.groupUI.grid.insertBefore(i,a.nextSibling):s.groupUI.grid.append(i);const r=i.querySelector(".item-grid");r&&(r.dataset.groupId=t,this.createSortable(e,r,t));let o=this.stores.groups.data.has(t)?this.stores.groups.data.get(t):{};return await this.setGroup(t,{...o,id:t,field:e}),t}createGroupElement(e,t=null){let s={groupId:e,fieldId:t},i=this.templates.create("imageGroup",s);return this.groups.set(e,{element:i,ui:window.uiFromSelectors(this.selectors.group,i)}),this.getSelectionHandler(t)?.addWrapper(i),i}async setGroup(e,t){const s={...{id:e,src:window.location.href,uploads:[],operationId:null,field:null,fields:{}},...t};Object.preventExtensions(s),await this.stores.groups.save(s)}async setBulkGroup(e,t,s){let i=this.stores.groups.filterByIndex({field:e});if(0===i.length)return;let a=i.map(e=>{e[t]=s,this.stores.groups.save(e)});await Promise.all(a)}async addToGroup(e,t=null){const s=this.stores.uploads.get(e),i=this.uploads.get(e);if(!s||!i)return;const a=this.fields.get(s.field);if(!a)return;if(null!==i.element?.parentElement&&(!t&&null===s.group||t===s.group))return void this.handleReorder(s.field,t);if(s.group){const t=this.stores.groups.get(s.group);t&&(t.uploads=t.uploads.filter(t=>t!==e),0===t.uploads.length?await this.removeGroup(t.id,!1):await this.stores.groups.save(t))}i.ui.checkbox&&(i.ui.checkbox.checked=!1);const r=this.selectionHandlers.get(s.field);if(r&&r.isSelected(e)&&r.deselect(e),this.selected.get(s.field)?.has(e)&&this.selected.get(s.field).delete(e),i.ui.featured&&(i.ui.featured.hidden=!t),t){i.ui.featured&&(i.ui.featured.name=`${t}_featured`);let a=this.stores.groups.get(t);a&&(a.uploads.push(e),s.group=t,await this.stores.groups.save(a))}else s.group=null;let o=t?this.groups.get(t)?.ui.grid:a.ui.grid;o&&(o.append(i.element),t&&await this.handleReorder(s.field,t)),await this.stores.uploads.save(s)}handleDeleteGroup(e){const t=e.closest(this.selectors.group.item);if(!t)return;let s=t.dataset.groupId;if(!confirm("Delete this group? Items will be moved back to the upload area."))return;let i=this.stores.uploads.filterByIndex({group:s});Promise.all(i.map(e=>this.addToGroup(e.id,null))).then(()=>{this.removeGroup(s,!1).then(()=>{}),this.a11y.announce("Group deleted. Items returned to upload area")})}async removeGroup(e,t=!0){let s=this.groups.get(e),i=this.stores.groups.get(e);if(!i)return;let a=!0;t&&i.uploads.length>0&&(a=window.confirm("Keep uploads in this group?")),await Promise.all(i.uploads.map(e=>a?this.addToGroup(e,null):this.removeUpload(e)));if(this.fields.get(i.field)){const t=this.getGroupKey(i.field,e),a=this.selectionHandlers.get(t);if(a?.destroy&&a.destroy(),this.selectionHandlers.get(i.field)&&s&&s.element&&this.selectionHandlers.get(i.field).removeWrapper(s.element),this.sortables.has(t)){const e=this.sortables.get(t);e?.destroy&&e.destroy(),this.sortables.delete(t)}}s?.element&&s.element.remove(),this.groups.delete(e),await this.stores.groups.delete(e),this.a11y.announce("Group removed")}maybeLockUploads(e){let t=this.fields.get(e);if(!t||!t.ui.dropZone)return;let s=this.stores.uploads.filterByIndex({field:e}).length,i=t.config.maxFiles??0;t.ui.dropZone.hidden=i>0&&s>=i}async handleOperationCancelled(e){0!==e.length&&e.forEach(e=>{this.removeUpload(e)})}getGroupKey(e,t=null){return t?`${e}_${t}`:`${e}`}getSelectionHandler(e){let t=this.getGroupKey(e);if(!this.selectionHandlers.has(t)){let s=this.fields.get(e);if(!s)return;if("post_group"!==s.config.destination)return;let i=new window.jvbHandleSelection(s.element,{selectAll:{checkbox:this.selectors.fields.selectAll,count:this.selectors.fields.count,bulkControls:this.selectors.fields.actions},item:{item:this.selectors.items.item,checkbox:this.selectors.items.checkbox,idAttribute:"uploadId"},wrapper:{wrapper:".preview-wrap, .upload-group",id:"groupId"}});i.subscribe((t,s)=>{this.selected.set(e,s.selectedItems)}),this.selectionHandlers.set(t,i)}return this.selectionHandlers.get(t)}updateHandlerItems(e){let t=this.getSelectionHandler(e);t&&t.collectItems()}initSortable(e){if(!window.Sortable)return;const t=this.fields.get(e);t&&(!Sortable._multiDragMounted&&Sortable.MultiDrag&&(Sortable.mount(new Sortable.MultiDrag),Sortable._multiDragMounted=!0),this.createSortable(e,t.ui.grid,null),this.initEmptyGroupDropZone(e))}createSortable(e,t,s){if(!t)return null;const i=this.getGroupKey(e,s);if(this.sortables.has(i))return this.sortables.get(i);const a=new Sortable(t,{animation:150,draggable:".item",multiDrag:!0,selectedClass:"selected",avoidImplicitDeselect:!0,group:{name:e,pull:!0,put:!0},dragClass:"dragging",ignore:".empty-group",onStart:t=>{const s=t.item,i=s?.dataset.uploadId,a=this.selected.get(e);if(i&&(!a||!a.has(i))){const t=this.selectionHandlers.get(e);t&&t.select(i)}},onEnd:t=>this.sortableDrop(t,e)});return this.sortables.set(i,a),a}initEmptyGroupDropZone(e){const t=this.fields.get(e),s=t?.groupUI?.empty;s&&(s.addEventListener("dragover",e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="move",s.classList.add("drag-over")}),s.addEventListener("dragleave",e=>{s.contains(e.relatedTarget)||s.classList.remove("drag-over")}),s.addEventListener("drop",async t=>{t.preventDefault(),t.stopPropagation(),s.classList.remove("drag-over");const i=this.selected.get(e);if(!i||0===i.size)return;const a=await this.createGroup(e);a&&(await Promise.all(Array.from(i).map(e=>this.addToGroup(e,a))),this.selectionHandlers.get(e)?.clearSelection())}))}async sortableDrop(e,t){const s=e.to,i=(e.items?.length>0?Array.from(e.items):[e.item]).map(e=>e.dataset.uploadId).filter(Boolean);if(0===i.length)return;const a=s.dataset.groupId||null;for(const e of i)await this.addToGroup(e,a);await this.handleReorder(t,a),this.selectionHandlers.get(t)?.clearSelection()}handleReorder(e,t=null){let s=t?this.groups.get(t)?.ui.grid:this.fields.get(e)?.ui.grid;if(s){if(t){let e=Array.from(s.children).filter(e=>e.matches(this.selectors.items.item)&&!e.classList.contains("ghost")).map(e=>e.dataset.uploadId).filter(e=>e),i=this.stores.groups.get(t);i&&(i.uploads=e,this.stores.groups.save(i).then(()=>{}))}else this.updateHiddenInput(e);this.a11y.announce("Items reordered")}else console.log("Couldn't Reorder items...")}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t={}){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("Subscriber error:",e)}})}destroy(){this.subscribers.clear(),this.previewUrls.forEach(e=>{this.revokePreviewUrl(e)}),this.previewUrls.clear()}cleanupAllPreviewUrls(){this.previewUrls.forEach(e=>this.revokePreviewUrl(e)),this.previewUrls.clear()}async handleClearCache(){const e=window.location.href,t=this.stores.uploads.filterByIndex({src:e}),s=this.stores.groups.filterByIndex({src:e});await Promise.all([...t.map(e=>this.clearUpload(e.id)),...s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id)))]),this.restoreModal&&this.cleanupRestore(),this.a11y.announce("Cache cleared for this page")}async getFilesForForm(e){const t=e.querySelectorAll(this.selectors.fields.field),s=[];for(const e of t){const t=this.determineFieldId(e),i=e.dataset.field,a=this.stores.uploads.filterByIndex({field:t});for(const e of a){const t=this.formatFile(e);t&&s.push({file:t,fieldName:i,uploadId:e.id,meta:e.fields||{}})}}return s}async clearFieldFromStores(e){const t=this.stores.uploads.filterByIndex({field:e}),s=this.stores.groups.filterByIndex({field:e});await Promise.all(t.map(e=>this.clearUpload(e.id))),await Promise.all(s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id))))}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbUploads=new e)})})})(); |
| | |
| | | * @var bool $magic_link Whether users can login without a password |
| | | */ |
| | | protected static bool $magic_link = true; |
| | | protected static string $defaultRole = 'subscriber'; |
| | | /** |
| | | * @var bool $support Whether to implement the support ticket system |
| | | */ |
| | |
| | | return self::$directory_plural ?? false; |
| | | } |
| | | |
| | | public static function setDefaultReferralRole(string $role):void |
| | | public static function setReferralRole(string $role):void |
| | | { |
| | | self::$defaultReferralRole = $role; |
| | | self::$defaultReferralRole = jvbCheckBase($role); |
| | | } |
| | | public static function getDefaultReferralRole():string |
| | | public static function getReferralRole():string |
| | | { |
| | | return self::$defaultReferralRole; |
| | | } |
| | | public static function setDefaultRole(string $role):void |
| | | { |
| | | self::$defaultRole = jvbCheckBase($role); |
| | | } |
| | | public static function getDefaultRole():string |
| | | { |
| | | return self::$defaultRole; |
| | | } |
| | | } |
| | |
| | | .info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity>label{right:var(--offScreen);position:absolute}.menu-item .header{flex-wrap:nowrap}.menu-item .header h3,.menu-item .header p{margin:0;width:-moz-max-content;width:max-content} |
| | | .info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity>label{right:var(--offScreen);position:absolute}nav.menu{max-width:100%}nav.menu ul{--justify:flex-start}nav.menu ul li{padding:0 .5rem}main>header{grid-column:full}main>header h1,main>section *{max-width:var(--wide)}.menu-item.menu-item{margin:var(--sp2);max-width:none}.menu-item.menu-item+.menu-item{border-top:1px solid rgb(var(--base-200));padding-top:var(--sp4)}.menu-item.menu-item .header{flex-wrap:nowrap}.menu-item.menu-item .header h3,.menu-item.menu-item .header p{margin:0;width:-moz-max-content;width:max-content} |
| | |
| | | .info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity>label{left:var(--offScreen);position:absolute}.menu-item .header{flex-wrap:nowrap}.menu-item .header h3,.menu-item .header p{margin:0;width:-moz-max-content;width:max-content} |
| | | .info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity>label{left:var(--offScreen);position:absolute}nav.menu{max-width:100%}nav.menu ul{--justify:flex-start}nav.menu ul li{padding:0 .5rem}main>header{grid-column:full}main>header h1,main>section *{max-width:var(--wide)}.menu-item.menu-item{margin:var(--sp2);max-width:none}.menu-item.menu-item+.menu-item{border-top:1px solid rgb(var(--base-200));padding-top:var(--sp4)}.menu-item.menu-item .header{flex-wrap:nowrap}.menu-item.menu-item .header h3,.menu-item.menu-item .header p{margin:0;width:-moz-max-content;width:max-content} |
| New file |
| | |
| | | { |
| | | "$schema": "https://schemas.wp.org/trunk/block.json", |
| | | "apiVersion": 3, |
| | | "name": "jvb/simple-calendar", |
| | | "version": "0.1.0", |
| | | "title": "Simple Calendar", |
| | | "category": "jvb", |
| | | "icon": "calendar-alt", |
| | | "description": "Simple Calendar", |
| | | "example": {}, |
| | | "supports": { |
| | | "html": false, |
| | | "align": [ |
| | | "wide", |
| | | "full" |
| | | ] |
| | | }, |
| | | "textdomain": "jvb", |
| | | "selectors": { |
| | | "root": ".simple-calendar" |
| | | }, |
| | | "editorScript": "file:./index.js", |
| | | "editorStyle": "file:./index.css", |
| | | "style": "file:./style-index.css", |
| | | "render": "file:./render.php", |
| | | "viewScript": "file:./view.js" |
| | | } |
| New file |
| | |
| | | <?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-i18n'), 'version' => '88c5d2b06f87f7081e11'); |
| New file |
| | |
| | | (()=>{"use strict";var r,e={332(){const r=window.wp.blocks,e=window.wp.i18n,o=window.wp.blockEditor,i=window.ReactJSXRuntime,n=JSON.parse('{"UU":"jvb/simple-calendar"}');(0,r.registerBlockType)(n.UU,{edit:function(){return(0,i.jsx)("p",{...(0,o.useBlockProps)(),children:(0,e.__)("Will output the simple calendar","jvb")})}})}},o={};function i(r){var n=o[r];if(void 0!==n)return n.exports;var t=o[r]={exports:{}};return e[r](t,t.exports,i),t.exports}i.m=e,r=[],i.O=(e,o,n,t)=>{if(!o){var l=1/0;for(c=0;c<r.length;c++){for(var[o,n,t]=r[c],a=!0,s=0;s<o.length;s++)(!1&t||l>=t)&&Object.keys(i.O).every(r=>i.O[r](o[s]))?o.splice(s--,1):(a=!1,t<l&&(l=t));if(a){r.splice(c--,1);var p=n();void 0!==p&&(e=p)}}return e}t=t||0;for(var c=r.length;c>0&&r[c-1][2]>t;c--)r[c]=r[c-1];r[c]=[o,n,t]},i.o=(r,e)=>Object.prototype.hasOwnProperty.call(r,e),(()=>{var r={241:0,237:0};i.O.j=e=>0===r[e];var e=(e,o)=>{var n,t,[l,a,s]=o,p=0;if(l.some(e=>0!==r[e])){for(n in a)i.o(a,n)&&(i.m[n]=a[n]);if(s)var c=s(i)}for(e&&e(o);p<l.length;p++)t=l[p],i.o(r,t)&&r[t]&&r[t][0](),r[t]=0;return i.O(c)},o=globalThis.webpackChunkjvb=globalThis.webpackChunkjvb||[];o.forEach(e.bind(null,0)),o.push=e.bind(null,o.push.bind(o))})();var n=i.O(void 0,[237],()=>i(332));n=i.O(n)})(); |
| New file |
| | |
| | | p.empty{background-color:rgb(var(--base));border:2px dashed rgba(var(--base-200));border-radius:var(--radius);font-style:italic;text-align:center}details{margin:2rem 0}details h2{margin:0} |
| New file |
| | |
| | | p.empty{background-color:rgb(var(--base));border:2px dashed rgba(var(--base-200));border-radius:var(--radius);font-style:italic;text-align:center}details{margin:2rem 0}details h2{margin:0} |
| New file |
| | |
| | | <?php return array('dependencies' => array(), 'version' => 'fa2ae0d9cf37735dbf5a'); |
| New file |
| | |
| | | window.details=document.querySelectorAll("details"),window.toggles=document.querySelectorAll(".toggle-details"),document.addEventListener("click",e=>{if(e.target.classList.contains("toggle-details")){e.target.classList.toggle("open");let t=e.target.classList.contains("open"),o=e.target.dataset.toggle;"all"===o&&function(e){window.toggles.forEach(t=>{"all"!==t.dataset.toggle&&(t.querySelector("span").textContent=e?"Close":"Open")})}(t),e.target.querySelector("span").textContent=t?"Close":"Open",function(e,t){if("all"===e)console.log("Toggling all!"),window.details.forEach(e=>{console.log(e),e.open=t});else for(let o of window.details)o.dataset.section===e&&(o.open=t)}(o,t)}}),console.log(window.details); |
| | |
| | | // Hook into WordPress admin |
| | | add_action('admin_menu', [$this, 'registerAdminPages']); |
| | | add_action('admin_enqueue_scripts', [$this, 'enqueueAdminAssets']); |
| | | add_action(BASE.'dashboard_page_integrations', [$this, 'renderDashPage'], 10); |
| | | } |
| | | |
| | | /** |
| | |
| | | // ); |
| | | } |
| | | |
| | | public function renderDashPage():void |
| | | { |
| | | $this->renderPage(); |
| | | } |
| | | /** |
| | | * Render the main integrations page |
| | | */ |
| | |
| | | <div class="jvb-admin-content"> |
| | | <?php |
| | | // Display any admin notices from form submissions |
| | | settings_errors('jvb_integrations'); |
| | | if (is_admin()) { |
| | | settings_errors('jvb_integrations'); |
| | | } |
| | | |
| | | $this->displayNotices(); |
| | | ?> |
| | | |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <?php $this->renderStyles(); ?> |
| | | <?php if (is_admin()) { |
| | | $this->renderStyles(); |
| | | } ?> |
| | | <?php |
| | | } |
| | | |
| | |
| | | <?php |
| | | namespace JVBase\admin; |
| | | |
| | | use JVBase\base\SchemaHelper; |
| | | use JVBase\meta\Form; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\ui\Tabs; |
| | |
| | | public function __construct() |
| | | { |
| | | $this->setChecks(); |
| | | add_filter('jvbDashboardPage', [$this, 'addDashboardSection'], 20, 2); |
| | | add_action(BASE.'dashboard_page_seo', [$this, 'addDashboardSection'], 10); |
| | | |
| | | add_action('admin_menu', [$this, 'addAdminMenu']); |
| | | $this->addFormListeners(); |
| | | } |
| | |
| | | ); |
| | | } |
| | | |
| | | public function addDashboardSection(string $content, string $page):string |
| | | public function addDashboardSection():void |
| | | { |
| | | if ($page !== 'SEO') { |
| | | return $content; |
| | | } |
| | | ob_start(); |
| | | $this->renderAdminPage(); |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | | public function renderAdminPage(bool $outputScripts = true):void |
| | |
| | | jvbInlineStyles('forms'); |
| | | } |
| | | |
| | | public function renderProperty(string $property, ?string $value, mixed $class):void |
| | | public function renderProperty(string $property, mixed $value, mixed $class):void |
| | | { |
| | | $method = 'get'.ucFirst($property).'FieldConfig'; |
| | | $config = method_exists($class, $method) ? $class->$method() : $this->buildConfig($property); |
| | |
| | | $this->formStart('website'); |
| | | $default = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebSite'; |
| | | // $this->renderSchemaTypeSelection($value); |
| | | $stored = get_option(BASE.'WebsiteSchema',apply_filters(BASE.'websiteSchema', [])); |
| | | $stored = SchemaHelper::schema('website'); |
| | | |
| | | $this->renderFieldsFor($default, $stored); |
| | | $this->formEnd(); |
| | | return ob_get_clean(); |
| | |
| | | $default = apply_filters(BASE.'OrganizationSchemaType', 'JVBase\managers\SEO\render\Thing\Organization\Organization'); |
| | | $value = get_option(BASE.'OrganizationSchemaType', $default); |
| | | $this->renderSchemaTypeSelection($value); |
| | | $stored = get_option(BASE.'OrganizationSchema',apply_filters(BASE.'OrganizationSchema', [])); |
| | | |
| | | $stored = SchemaHelper::schema('organization'); |
| | | $this->renderFieldsFor($value, $stored); |
| | | $this->formEnd(); |
| | | return ob_get_clean(); |
| | |
| | | $prices[] = (float) $row[$this->base.'price']; |
| | | } |
| | | } |
| | | if (!empty($prices)) { |
| | | if (!empty($prices) && (min($prices) !== max($prices))) { |
| | | $priceRange = jvbCurrency(min($prices)).' - '.jvbCurrency(max($prices)); |
| | | } elseif (!empty($prices)) { |
| | | $priceRange = jvbCurrency(max($prices)); |
| | | } |
| | | } |
| | | |
| | |
| | | if (empty($variations)) { |
| | | echo Form::render( |
| | | $ID.'|cart_quantity', |
| | | '', |
| | | [ |
| | | 'type' => 'quantity', |
| | | 'label' => 'Add to Order', |
| New file |
| | |
| | | <?php |
| | | namespace JVBase\blocks; |
| | | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Block; |
| | | use WP_Query; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | | } |
| | | |
| | | class SimpleCalendarBlock |
| | | { |
| | | protected Cache $cache; |
| | | protected string $config; |
| | | protected string $type; |
| | | protected string $path = JVB_DIR . '/build/simple-calendar'; |
| | | protected string $image; |
| | | protected string $header; |
| | | protected array $params = ['all']; |
| | | protected string $headerExtra; |
| | | protected string $detailsTitle; |
| | | protected array $details; |
| | | protected array|false $sections = false; |
| | | protected string $base; |
| | | protected array $content; |
| | | protected array $taxonomies; |
| | | |
| | | public function __construct() |
| | | { |
| | | $this->cache = Cache::for('menu', DAY_IN_SECONDS)->connect('post'); |
| | | add_action('init', [ $this, 'registerBlock' ]); |
| | | } |
| | | |
| | | public function registerBlock():void |
| | | { |
| | | register_block_type($this->path, [ |
| | | 'render_callback' => [ $this, 'render' ] |
| | | ]); |
| | | } |
| | | |
| | | protected function buildParams():void |
| | | { |
| | | $registered = Registrar::withFeature('is_calendar', 'post'); |
| | | $registered = array_map('jvbCheckBase', $registered); |
| | | $types = []; |
| | | foreach ($registered as $type) { |
| | | $registrar = Registrar::getInstance($type); |
| | | $types[$type] = $registrar->getPlural(); |
| | | } |
| | | $this->content = $types; |
| | | |
| | | $taxonomies = []; |
| | | foreach ($registered as $type) { |
| | | $registrar = Registrar::getInstance($type); |
| | | $taxonomies = array_merge($taxonomies, $registrar->registrar->taxonomies); |
| | | } |
| | | $taxonomies = array_map('jvbCheckBase', $taxonomies); |
| | | $types = []; |
| | | foreach ($taxonomies as $type) { |
| | | $registrar = Registrar::getInstance($type); |
| | | $types[$type] = $registrar->getPlural(); |
| | | } |
| | | $this->taxonomies = $types; |
| | | |
| | | |
| | | if (is_post_type_archive($registered)) { |
| | | $this->params = ['all']; |
| | | } elseif (is_tax($taxonomies)) { |
| | | $this->params = []; |
| | | } else { |
| | | $this->params = []; |
| | | } |
| | | } |
| | | |
| | | public function render(array $attributes, string $content, WP_Block $block) |
| | | { |
| | | $this->buildParams(); |
| | | if (empty($this->params)) { |
| | | return ''; |
| | | } |
| | | |
| | | $key = $this->cache->generateKey($this->params); |
| | | $this->cache->flush(); |
| | | return $this->cache->remember( |
| | | $key, |
| | | function() { |
| | | ob_start(); |
| | | $this->renderBlock(); |
| | | return ob_get_clean(); |
| | | } |
| | | ); |
| | | } |
| | | |
| | | protected function renderBlock():void |
| | | { |
| | | $this->renderHeader(); |
| | | $this->renderCalendar(); |
| | | $this->renderOnThisPage(); |
| | | } |
| | | |
| | | protected function renderHeader():void |
| | | { |
| | | $title = 'Events'; |
| | | if(!in_array('all', $this->params)) { |
| | | $title = '<small>'.$title.'</small>'; |
| | | } |
| | | |
| | | ?> |
| | | <header id="top"> |
| | | <h1><?=$title?></h1> |
| | | <?php $this->buildNav(); ?> |
| | | </header> |
| | | <section id="intro"> |
| | | <p>We bring southern-style barbeque eats with Northern flair to any event throughout Alberta.</p> |
| | | <p><b>Want to bring us to your event?</b> <a href="<?=get_home_url(null,'/book-your-event')?>">Let us know</a>, we'd love to come!</p> |
| | | </section> |
| | | <?php |
| | | } |
| | | protected function buildNav():void |
| | | { |
| | | $content = $taxonomies = ''; |
| | | if (count($this->content) > 1) { |
| | | $out = []; |
| | | foreach ($this->content as $slug => $title) { |
| | | $out[] = sprintf( |
| | | '<li><a href="%s" title="See %s">%s</a></li>', |
| | | get_post_type_archive_link($slug), |
| | | $title, |
| | | $title |
| | | ); |
| | | } |
| | | if (!empty($out)) { |
| | | $content = sprintf( |
| | | '<ul class="term-list content"><li class="label">Content:</li>%s</ul>', |
| | | implode('', $out) |
| | | ); |
| | | } |
| | | } |
| | | |
| | | if (!empty($this->taxonomies)) { |
| | | foreach ($this->taxonomies as $slug => $title) { |
| | | $check = get_terms([ |
| | | 'taxonomy' => $slug, |
| | | 'hide_empty' => true, |
| | | 'orderby' => 'name', |
| | | ]); |
| | | $out = []; |
| | | if ($check && !is_wp_error($check)) { |
| | | foreach ($check as $term) { |
| | | $out[] = sprintf( |
| | | '<li><a href="%s" title="See %s">%s</a></li>', |
| | | get_term_link($term->term_id, $slug), |
| | | $term->name, |
| | | $term->name |
| | | ); |
| | | } |
| | | } |
| | | if (!empty($out)) { |
| | | $taxonomies .= sprintf( |
| | | '<ul class="term-list %s"><li class="label">%s</li>%s</ul>', |
| | | $slug, |
| | | $title, |
| | | implode('', $out) |
| | | ); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (!empty($content) || !empty($taxonomies)) { |
| | | echo sprintf( |
| | | '<nav class="types">%s%s</nav>', |
| | | $content, |
| | | $taxonomies |
| | | ); |
| | | } |
| | | } |
| | | |
| | | protected function renderCalendar():void |
| | | { |
| | | ?> |
| | | <details id="calendar" open> |
| | | <summary><h2>Upcoming Events <?= jvbIcon('calendar-blank')?></h2></summary> |
| | | <?= $this->getEvents() ?> |
| | | </details> |
| | | <details class="past"> |
| | | <summary><h2>Past Events <?= jvbIcon('calendar-check')?></h2></summary> |
| | | <?= $this->getEvents(true) ?> |
| | | </details> |
| | | <?php |
| | | } |
| | | |
| | | protected function getCurrentPostTypes():array |
| | | { |
| | | if (is_post_type_archive()) { |
| | | return [get_queried_object()->name]; |
| | | } |
| | | if (is_singular()) { |
| | | return [get_queried_object()->post_type]; |
| | | } |
| | | if (is_tax()) { |
| | | $tax = get_queried_object(); |
| | | jvbDump($tax); |
| | | } |
| | | return []; |
| | | } |
| | | protected function getEvents(bool $past = false):string |
| | | { |
| | | |
| | | $events = ''; |
| | | if (is_singular()) { |
| | | return $this->formatSingular(); |
| | | } |
| | | |
| | | $page = $past |
| | | ? $_GET['jpast']??1 |
| | | : $_GET['jpage']??1; |
| | | |
| | | $items = new WP_Query([ |
| | | 'post_type' =>$this->getCurrentPostTypes(), |
| | | 'post_status' => 'publish', |
| | | 'orderby' => 'meta_value', |
| | | 'meta_key' => BASE.'date_start', |
| | | 'posts_per_page'=> 30, |
| | | 'fields' => 'ids', |
| | | 'paged' => $page, |
| | | 'meta_type' => 'DATETIME', |
| | | 'meta_query' => [ |
| | | [ |
| | | 'key' => BASE.'past', |
| | | 'value' => '1', |
| | | 'compare' => $past ? '=' : '!=' |
| | | ] |
| | | ] |
| | | ]); |
| | | |
| | | $events = implode('', array_map([$this, 'formatItem'], $items->posts)); |
| | | |
| | | $events = empty($events) ? '<p class="empty">Nothing here yet</p>' : sprintf( |
| | | '<ul class="item-list">%s</ul>', |
| | | $events |
| | | ); |
| | | $total = $items->max_num_pages; |
| | | if ($total > 1) { |
| | | $big = 999999999; // need an unlikely integer |
| | | $format = $past ? 'jpast' : 'jpage'; |
| | | $events .= paginate_links([ |
| | | 'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ), |
| | | 'format' => '?'.$format.'=%#%', |
| | | 'current' => $page, |
| | | 'total' => $total |
| | | ]); |
| | | } |
| | | |
| | | return $events; |
| | | } |
| | | |
| | | |
| | | protected function formatItem(int $ID):string |
| | | { |
| | | $meta = Meta::forPost($ID); |
| | | $type = jvbNoBase(get_post_type($ID)); |
| | | return sprintf( |
| | | '<li class="item %s"> |
| | | %s |
| | | <h3> |
| | | <a href="%s" title="Learn More">%s</a> |
| | | <small><time datetime="%s">%s</time></small> |
| | | </h3> |
| | | %s |
| | | </li>', |
| | | $type, |
| | | empty($meta->get('post_thumbnail')) ? '' : jvbFormatImage($meta->get('post_thumbnail'), 'tiny', 'medium'), |
| | | get_the_permalink($ID), |
| | | $meta->get('post_title'), |
| | | date('c', $meta->get('date_start')), |
| | | date('F j, Y', $meta->get('date_start')), |
| | | empty($meta->get('post_excerpt')) ? '' : wpautop($meta->get('post_excerpt')) |
| | | ); |
| | | } |
| | | |
| | | protected function formatSingular():string |
| | | { |
| | | return ''; |
| | | } |
| | | |
| | | |
| | | protected function renderOnThisPage():void |
| | | { |
| | | if (empty($this->details)) { |
| | | return; |
| | | } |
| | | echo jvbOnThisPage(array_keys($this->details)); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | |
| | | require('SimpleCalendarBlock.php'); |
| | | new JVBase\blocks\SimpleCalendarBlock(); |
| | | |
| | | |
| | | require('SummaryBlock.php'); |
| | | new JVBase\blocks\SummaryBlock(); |
| | | |
| | |
| | | return false; |
| | | } |
| | | $user = wp_get_current_user(); |
| | | |
| | | $registered = Registrar::withFeature('has_dashboard', 'user'); |
| | | $mapped = array_map(fn($role) => BASE.$role,$registered); |
| | | return count( |
| | | array_intersect( |
| | | array_values($user->roles), |
| | | array_map(function ($role) { |
| | | return BASE.$role; |
| | | }, |
| | | Registrar::withFeature('has_dashboard', 'user')) |
| | | $mapped |
| | | ) |
| | | )>0; |
| | | } |
| | |
| | | return 'admin'; |
| | | } |
| | | $user = ($ID === 0) ? wp_get_current_user() : get_userdata($ID); |
| | | return array_values(array_intersect( |
| | | array_keys(array_merge(Registrar::getRegistered('user'), ['administrator'])), |
| | | array_map(function ($role) { |
| | | return jvbNoBase($role); |
| | | }, |
| | | $user->roles) |
| | | ))[0]; |
| | | |
| | | return array_values(array_values(array_intersect( |
| | | array_merge(Registrar::getRegistered('user'), ['administrator']), |
| | | array_map(function ($role) { |
| | | return jvbNoBase($role); |
| | | }, |
| | | $user->roles) |
| | | )))[0]; |
| | | } |
| | | |
| | | function jvbUserProfileLink(int $userID):string|false |
| | |
| | | |
| | | public function getAdditionalFields(?string $content_type = null):array |
| | | { |
| | | if ($content_type === 'customer') { |
| | | $array = $this->getCustomerFields(); |
| | | return array_combine( |
| | | array_map(fn($k) => '_square_' . $k, array_keys($array)), |
| | | $array |
| | | ); |
| | | } |
| | | return array_combine( |
| | | array_map(fn($k) => 'hc_' . $k, array_keys($this->getHelcimMeta($content_type))), |
| | | $this->getHelcimMeta($content_type) |
| | | ); |
| | | } |
| | | |
| | | protected function getCustomerFields():array |
| | | { |
| | | return []; |
| | | } |
| | | |
| | | /** |
| | | * Get Helcim product meta fields by type. |
| | | * |
| | |
| | | { |
| | | $this->service_name = 'instagram'; |
| | | $this->title = 'Instagram'; |
| | | $this->icon = 'instagram'; |
| | | $this->icon = 'instagram-logo'; |
| | | $this->apiBase = [ |
| | | 'graph' => self::GRAPH_API_BASE . self::API_VERSION, |
| | | 'instagram' => self::INSTAGRAM_BASE . self::API_VERSION |
| | |
| | | */ |
| | | abstract class Integrations |
| | | { |
| | | //Flag to allow for custom settings (defaults, etc) for an integration in the dashboard |
| | | public static bool $hasExtraOptions = false; |
| | | /** |
| | | * Queue types |
| | | * These types match with IntegrationExecutor |
| | |
| | | return (new static())->getIcon(); |
| | | } |
| | | |
| | | public static function hasExtraOptions():bool |
| | | { |
| | | return (new static())::$hasExtraOptions; |
| | | } |
| | | |
| | | /********************************************************************* |
| | | RENDERING |
| | | *********************************************************************/ |
| | |
| | | */ |
| | | public function enqueueScripts(): void |
| | | { |
| | | jvbInlineStyles('forms'); |
| | | $this->loadCredentials(); |
| | | $sdk_url = $this->environment === 'production' |
| | | ? 'https://web.squarecdn.com/v1/square.js' |
| | |
| | | wp_register_script( |
| | | 'jvb-checkout', |
| | | JVB_URL . 'assets/js/min/checkout.min.js', |
| | | ['jvb-utility', 'jvb-queue', 'jvb-a11y', 'jvb-cache', 'jvb-tabs', 'jvb-popup'], |
| | | '1.1.31', |
| | | ['jvb-utility', 'jvb-queue', 'jvb-a11y', 'jvb-cache', 'jvb-tabs', 'jvb-popup', 'jvb-login'], |
| | | '1.1.32', |
| | | ['strategy' => 'defer', 'in_footer' => true] |
| | | ); |
| | | |
| | |
| | | 'jvb-square-checkout', |
| | | JVB_URL . 'assets/js/min/square.min.js', |
| | | ['jvb-checkout', 'square-payments-sdk'], |
| | | '1.1.31', |
| | | '1.1.32', |
| | | ['strategy' => 'defer', 'in_footer' => true] |
| | | ); |
| | | |
| | |
| | | 'application_id' => $this->credentials['client_id'] ?? '', |
| | | 'location_id' => $this->locationId, |
| | | 'environment' => $this->environment, |
| | | 'api_url' => rest_url('jvb/v1/square/'), |
| | | 'nonce' => wp_create_nonce('wp_rest'), |
| | | 'currency' => get_option(BASE . 'currency', 'CAD'), |
| | | 'is_logged_in' => is_user_logged_in(), |
| | | 'user_email' => is_user_logged_in() ? wp_get_current_user()->user_email : '', |
| | |
| | | ]; |
| | | } |
| | | public function getAdditionalFields(?string $content_type = null):array { |
| | | if ($content_type === 'customer') { |
| | | return $this->getCustomerFields(); |
| | | } |
| | | if ($content_type && array_key_exists($content_type, $this->contentTypes)){ |
| | | $array = $this->contentTypes[$content_type]; |
| | | return array_combine( |
| | |
| | | $array |
| | | ); |
| | | } |
| | | |
| | | protected function getCustomerFields():array |
| | | { |
| | | return [ |
| | | 'customer_id' => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Square Customer ID', |
| | | 'hidden'=> true, |
| | | ], |
| | | 'address_line_1' => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Address Line 1', |
| | | 'hint' => 'ex: 6551 111 St NW', |
| | | 'required' => true, |
| | | 'section' => 'about' |
| | | ], |
| | | 'address_line_2' => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Address Line 2', |
| | | 'hint' => 'ex: Unit 2', |
| | | 'section' => 'about' |
| | | ], |
| | | 'city' => [ |
| | | 'type' => 'text', |
| | | 'label'=> 'City', |
| | | 'section' => 'about', |
| | | 'required' => true, |
| | | ], |
| | | 'state' => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Province', |
| | | 'hint' => 'The two-character code, example: AB', |
| | | 'default'=> 'AB', |
| | | 'section' => 'about', |
| | | 'required' => true, |
| | | ], |
| | | 'countryCode' => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Country Code', |
| | | 'hint' => 'The tw-character country code, example: CA', |
| | | 'default' => 'CA', |
| | | 'section' => 'about', |
| | | 'required' => true, |
| | | ] |
| | | ]; |
| | | } |
| | | protected function setBaseFields():array |
| | | { |
| | | return [ |
| | |
| | | |
| | | public function remember(int|string|array $key, callable $callback, ?int $ttl = null): mixed |
| | | { |
| | | //TODO: Redis is fucking this all up for some reason. While we figure it out, let's just return the callback |
| | | return $callback(); |
| | | |
| | | if (is_array($key)) { |
| | | $key = $this->generateKey($key); |
| | | } |
| | |
| | | use JVBase\forms\TaxonomySelector; |
| | | use JVBase\base\Site; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\RoleManager;use JVBase\registrar\Registrar; |
| | | use JVBase\managers\IconsManager;use JVBase\managers\RoleManager;use JVBase\meta\Form;use JVBase\meta\Meta;use JVBase\registrar\Fields;use JVBase\registrar\Registrar; |
| | | use JVBase\ui\Navigation; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | | class DashboardManager { |
| | | public static string $based; |
| | | protected string $slug = 'dash'; |
| | | protected string $baseURl; |
| | | protected array $currentUserPages = []; |
| | | protected array $pages = []; |
| | | protected array $sections = []; |
| | | protected Cache $cache; |
| | | public function __construct() { |
| | | self::$based = BASE.$this->slug; |
| | | $this->cache = Cache::for('dashboard')->connect('post'); |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | $this->registerDashboard(); |
| | | $this->buildPages(); |
| | | $this->buildSections(); |
| | | $this->setCurrentUserPages(); |
| | | //Since this is loaded via JVBase, it's already running on init |
| | | $this->registerDashboard(); |
| | | $this->registerHooks(); |
| | | |
| | | $doit = apply_filters('jvb_base_dashboard_dash', true); |
| | | if ($doit) { |
| | | add_action(BASE.'dashboard_page_dash', [$this, 'renderMain'], 10); |
| | | } |
| | | add_action(BASE.'dashboard_page_account', [$this, 'renderAccount'], 10); |
| | | } |
| | | |
| | | protected function registerDashboard():void |
| | | { |
| | | $dash = Registrar::forPost('dash', 'Dashboard', 'Dashboards'); |
| | | $dash = Registrar::forPost($this->slug, 'Dashboard', 'Dashboards'); |
| | | $dash->setIcon('gauge') |
| | | ->make([ |
| | | 'show_in_admin_bar' => false, |
| | | 'rewrite' => [ |
| | | 'slug' => 'dash', |
| | | 'slug' => $this->slug, |
| | | 'with_front' => false, |
| | | ], |
| | | 'supports' => [ 'title', 'editor', 'custom-fields'], |
| | | 'hierarchical' => true |
| | | 'hierarchical' => true, |
| | | 'has_archive' => true, |
| | | ])->setAll([ |
| | | 'system' |
| | | ]); |
| | | |
| | | $dash->register(); |
| | | } |
| | | |
| | | protected function registerHooks():void |
| | |
| | | } |
| | | public function handleRedirects():void |
| | | { |
| | | if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash') && !is_404()) { |
| | | if (!is_singular(self::$based) && !is_post_type_archive(self::$based) && !is_404()) { |
| | | return; |
| | | } |
| | | |
| | | |
| | | if (!is_404()) { |
| | | if (!is_user_logged_in()) { |
| | | error_log('Redirecting to login - user not logged in'); |
| | | $this->redirectToLogin(); |
| | | } elseif (!isOurPeople() && !current_user_can('manage_options')) { |
| | | $this->redirectToHome(); |
| | |
| | | if (array_key_exists($page, ['', 'dash'])) { |
| | | return; |
| | | } |
| | | if (!array_key_exists($page, $this->pages)) { |
| | | $page = $this->getCurrentPage(); |
| | | if (!in_array($page, $this->pages)) { |
| | | error_log('Looking for page: '.$page. ' in: '.print_r($this->pages, true)); |
| | | error_log('[DashboardManager]::handleRedirect could not find page for '.$page); |
| | | return; |
| | | } |
| | | $permission = $this->pages[$page]->getPermission(); |
| | | $permission = $page->getPermission(); |
| | | if (!empty($permission) && !current_user_can($permission)) { |
| | | error_log('[DashboardManager]::handleRedirect User cannot manage '.$page); |
| | | $this->redirectToDashboard(); |
| | | } |
| | | } |
| | | return; |
| | | } |
| | | global $wp; |
| | | if (str_starts_with($wp->request, 'dash/') || $wp->request === 'dash') { |
| | | if (is_404() && str_starts_with($wp->request, $this->slug.'/') || $wp->request === $this->slug) { |
| | | error_log('404 on dashboard URL, redirecting to dashboard home'); |
| | | $this->redirectToDashboard(); |
| | | } |
| | | } |
| | | public function dashboardTemplates(string $template):string |
| | | { |
| | | if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash')) { |
| | | if (!is_singular(self::$based) && !is_post_type_archive(self::$based)) { |
| | | return $template; |
| | | } |
| | | $page = $this->getCurrentPage(); |
| | | if (!empty($page->getIcon())) { |
| | | add_filter('jvbLoadingIcon', $page->getIcon()); |
| | | |
| | | if ($page && !empty($page->getIcon())) { |
| | | add_filter('jvbLoadingIcon', [$page, 'getIcon']); |
| | | } |
| | | ob_start(); |
| | | jvbInlineStyles('nav'); |
| | | jvbInlineStyles('dash'); |
| | | jvbInlineStyles('forms'); |
| | | $this->renderHeader(); |
| | | $page->render(); |
| | | echo $page->render(); |
| | | $this->renderFooter(); |
| | | return ob_get_clean(); |
| | | $got = ob_get_clean(); |
| | | echo $got; |
| | | return $got; |
| | | } |
| | | protected function renderHeader():void |
| | | { |
| | |
| | | <footer class="col"> |
| | | <?= jvbLoadingScreen() ?> |
| | | <?= TaxonomySelector::outputSelectorModal() ?> |
| | | <!-- <nav class="dashboard-nav">--> |
| | | <?php |
| | | // $current_page = $this->getCurrentPageSlug(); |
| | | // $pages = $this->getUserAllowedPages()?:[]; |
| | | // echo '<ul>'; |
| | | // foreach ($pages as $slug => $page) { |
| | | // $slug = $this->getSlug($slug, $page); |
| | | // $icon = $this->getIcon($slug, $page); |
| | | // // Add data-page attribute for the navigator |
| | | // $active = ($current_page == $slug) ? ' class="current"' : ''; |
| | | // $current = ($current_page == $slug) ? ' aria-current="page"' : ''; |
| | | // |
| | | // |
| | | // $link = ($page === 'dash') ? '/'.$page : "/dash/$slug"; |
| | | // printf( |
| | | // '<li%s><a href="%s"%s data-page="%s" data-dash title="%s">%s<span>%s</span></a></li>', |
| | | // $active, |
| | | // get_home_url(null, $link), |
| | | // $current, |
| | | // $slug, |
| | | // $page, |
| | | // jvbDashIcon($icon, ['title'=> $page]), |
| | | // $page |
| | | // ); |
| | | // } |
| | | // |
| | | // echo '</ul>'; |
| | | ?> |
| | | <!-- </nav>--> |
| | | </footer> |
| | | |
| | | |
| | |
| | | protected function outputSidebarNavigation():string |
| | | { |
| | | $menu = new Navigation('sidebar'); |
| | | |
| | | $menuClasses = ['left']; |
| | | $itemClasses = ['col']; |
| | | $menu->addClass('sidebar left')->hasToggle()->defaultMenuClasses($menuClasses); |
| | |
| | | $dashboard = $menu->addItem('Dashboard', jvbDashIcon($this->pages['dash']->getIcon())) |
| | | ->url($this->pages['dash']->getURL()); |
| | | |
| | | foreach ($this->sections as $section) { |
| | | $pages = array_filter($this->pages, function($page) use ($section) { |
| | | $canDo = empty($page->getPermission()) || current_user_can($page->getPermission()); |
| | | return $page->getSection() === $section && $canDo; |
| | | }); |
| | | $all = $this->getSectionPagesAndSections(); |
| | | |
| | | if (empty($pages)) { |
| | | continue; |
| | | } |
| | | $icon = !empty($section['icon']??'') ? jvbDashIcon($section['icon']) : null; |
| | | $section = $menu->addItem($section['title'], $icon) |
| | | ->submenu($section['slug']) |
| | | ->defaultMenuClasses($menuClasses) |
| | | ->defaultItemClasses($itemClasses); |
| | | foreach ($pages as $page) { |
| | | $icon = empty($page->getIcon()) ? null : jvbDashIcon($page->getIcon()); |
| | | $item = $section->addItem($page->getTitle(), $icon) |
| | | ->url($page->getURL()); |
| | | $registrar = Registrar::getInstance($page->getSlug()); |
| | | if ($registrar && !empty($registrar->registrar->taxonomies)) { |
| | | $itemMenu = $item->submenu($page->getSlug()); |
| | | foreach ($registrar->registrar->taxonomies as $taxonomy) { |
| | | $taxonomy = jvbNoBase($taxonomy); |
| | | if (!array_key_exists($taxonomy, $this->pages)) { |
| | | error_log('Could not add Taxonomy subpage for '.$taxonomy); |
| | | continue; |
| | | } |
| | | $icon = empty($this->pages[$taxonomy]->getIcon()) ? null : jvbDashIcon($this->pages[$taxonomy]->getIcon()); |
| | | $itemMenu->addItem($this->pages[$taxonomy]->getTitle(),$icon) |
| | | ->url($this->pages[$taxonomy]->getURL()); |
| | | } |
| | | } |
| | | } |
| | | unset($all['dash']); |
| | | // uasort($pages, fn($a, $b) => $a->getOrder() <=> $b->getOrder()); |
| | | uasort($all, fn($a, $b) => $a->getOrder() <=> $b->getOrder()); |
| | | |
| | | } |
| | | $pages = $this->getUserAllowedPages()?:[]; |
| | | //Dashboard |
| | | //Referrals |
| | | $dashboard = $menu->addItem('Dashboard',jvbDashIcon('door')) |
| | | ->url($this->baseURL); |
| | | // ->submenu('dashboard') |
| | | // ->defaultMenuClasses($menuClasses) |
| | | // ->defaultItemClasses($itemClasses); |
| | | //notifications |
| | | if (in_array('Notifications', $pages)) { |
| | | $menu->addItem('Notifications',jvbDashIcon('bell')) |
| | | ->url($this->baseURL.'/notifications'); |
| | | } |
| | | if (in_array('Referrals', $pages)) { |
| | | $menu->addItem('Referrals', jvbDashIcon('hand-heart')) |
| | | ->url($this->baseURL.'/referrals'); |
| | | } |
| | | if (in_array('Favourites', $pages)) { |
| | | $menu->addItem('Favourites', jvbDashIcon('heart')) |
| | | ->url($this->baseURL.'/favourites'); |
| | | } |
| | | |
| | | //Content |
| | | //content types |
| | | $all = array_merge( |
| | | Registrar::getRegistered('post'), |
| | | Registrar::withFeature('is_content', 'term') |
| | | ); |
| | | $availableContent = []; |
| | | // $availableContent = array_filter($pages, function($page, $key) use($all) { |
| | | // return !is_numeric($key) && in_array($key, $all) && JVB()->roles()->checkRole($this->user, $key); |
| | | // }, ARRAY_FILTER_USE_BOTH); |
| | | if (!empty ($availableContent)){ |
| | | $content = $menu->addItem('Your Content', jvbDashIcon('book-bookmark')) |
| | | ->submenu('content') |
| | | ->defaultMenuClasses($menuClasses) |
| | | ->defaultItemClasses($itemClasses); |
| | | foreach ($availableContent as $slug => $page) { |
| | | $registrar = Registrar::getInstance($slug); |
| | | |
| | | $item = $content->addItem($page, $registrar->getIcon()) |
| | | ->url($this->baseURL.'/'.$slug); |
| | | |
| | | if ($registrar->getType() === 'post') { |
| | | $taxonomies = $registrar->registrar->taxonomies; |
| | | if (!empty ($taxonomies)) { |
| | | //TODO: If we add a dedicated 'create item' page, remove this from the empty check |
| | | $itemMenu = $item->submenu($slug); |
| | | foreach ($taxonomies as $s) { |
| | | $taxRegistrar = Registrar::getInstance($s); |
| | | if ($taxRegistrar) { |
| | | $itemMenu->addItem($taxRegistrar->getPlural(), $taxRegistrar->getIcon()) |
| | | ->url($this->baseURL.'/'.$s); |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | foreach ($all as $slug => $item) { |
| | | if (is_a($item, Section::class)) { |
| | | $this->buildMenuSection($item, $menu); |
| | | } else { |
| | | $menuItem = $menu->addItem($item->getTitle(), jvbDashIcon($item->getIcon())); |
| | | $menuItem->url($item->getURL()); |
| | | } |
| | | } |
| | | |
| | | //Taxonomies |
| | | |
| | | //Settings |
| | | $settings = $menu->addItem('Settings', jvbDashIcon('faders')) |
| | | ->submenu('settings') |
| | | ->defaultItemClasses($itemClasses) |
| | | ->defaultMenuClasses($menuClasses); |
| | | |
| | | //SEO |
| | | if (in_array('SEO', $pages)) { |
| | | $settings->addItem('SEO', jvbDashIcon('robot')) |
| | | ->url($this->baseURL.'/seo'); |
| | | } |
| | | //Integrations |
| | | if (in_array('Integrations', $pages)) { |
| | | $settings->addItem('Integrations', jvbDashIcon('plugs-connected')) |
| | | ->url($this->baseURL.'/integrations'); |
| | | } |
| | | //Account |
| | | $account = $menu->addItem('Account', jvbDashIcon('user-circle')) |
| | | ->url($this->baseURL.'/account') |
| | | ->submenu('account') |
| | | ->defaultMenuClasses($menuClasses) |
| | | ->defaultItemClasses($itemClasses); |
| | | $account->addItem('Reset Password', jvbDashIcon('password')) |
| | | ->url($this->baseURL.'/reset-password'); |
| | | //name + contact |
| | | //reset password |
| | | |
| | | if (in_array('notifications', $pages)) { |
| | | $account->addItem('Permissions', jvbDashIcon('keyhole')) |
| | | ->url($this->baseURL.'/permissions'); |
| | | } |
| | | |
| | | echo $menu->render(); |
| | | return $menu->render(); |
| | | } |
| | | protected function buildMenuSection(Section $section, Navigation $menu, array $pages = []):void |
| | | { |
| | | $all = $this->getSectionPagesAndSections($section->getSlug()); |
| | | if (empty($all)) { |
| | | return; |
| | | } |
| | | uasort($all, fn($a, $b) => $a->getOrder() <=> $b->getOrder()); |
| | | |
| | | $page = $menu->addItem($section->getTitle(), jvbDashIcon($section->getIcon())); |
| | | |
| | | $main = $this->getMainPage($section->getSlug()); |
| | | if ($main && $section->getIsLink()) { |
| | | $page->url($main->getURL()); |
| | | } |
| | | $submenu = $page->submenu(); |
| | | |
| | | foreach ($all as $slug=>$item) { |
| | | if (is_a($item, Section::class)) { |
| | | $this->buildMenuSection($item, $submenu); |
| | | } elseif ($slug !== $section->getSlug()) { |
| | | $menuItem = $submenu->addItem($item->getTitle(), jvbDashIcon($item->getIcon())); |
| | | $menuItem->url($item->getURL()); |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | protected function getSectionPagesAndSections(?string $section = null):array |
| | | { |
| | | $pages = $this->getSectionPages($section); |
| | | $pages = array_filter($pages, function ($p) { |
| | | return empty($p->getPermission()) || current_user_can('manage_options') || current_user_can($p->getPermission()); |
| | | }); |
| | | $sections = $this->getSectionSections($section); |
| | | $sections = array_filter($sections, function ($s) { |
| | | $check = empty($s->getPermission()) || current_user_can('manage_options') || current_user_can($s->getPermission()); |
| | | $pages = $this->getSectionPagesAndSections($s->getSlug()); |
| | | return $check && !empty($pages); |
| | | }); |
| | | return array_merge($pages, $sections); |
| | | } |
| | | public function redirectFromAdmin():void |
| | | { |
| | | //Skip if already processing a redirect |
| | |
| | | } |
| | | public function dashboardScripts():void |
| | | { |
| | | if (!is_singular(self::$based) && !is_post_type_archive(self::$based)) { |
| | | return; |
| | | } |
| | | IconsManager::for('forms')->enqueueIconStyles(); |
| | | IconsManager::for('dash')->enqueueIconStyles(); |
| | | |
| | | wp_enqueue_script('jvb-form'); |
| | | wp_enqueue_script('jvb-selector'); |
| | | wp_enqueue_script('jvb-uploader'); |
| | | wp_enqueue_script('jvb-content'); |
| | | |
| | | $page = $this->getCurrentPage(); |
| | | foreach ($page->getScripts() as $script) { |
| | | wp_enqueue_script($script); |
| | | } |
| | | } |
| | | |
| | | public function excludeDashboard(array $IDs):array { |
| | |
| | | 'dashboardIDs', |
| | | function() { |
| | | return get_posts([ |
| | | 'post_type' => BASE.'dash', |
| | | 'post_type' => self::$based, |
| | | 'posts_per_page' => -1, |
| | | 'fields' => 'ids', |
| | | ]); |
| | |
| | | |
| | | private function buildPages():void |
| | | { |
| | | $this->addPage('dash', 'Dashboard','door'); |
| | | $this->buildContentPages(); |
| | | $this->buildReferrals(); |
| | | $this->addPage('Dashboard', 'dash','door'); |
| | | $this->buildContentPages(10); |
| | | $this->buildReferrals(30); |
| | | $this->buildFavourites(5); |
| | | $this->buildKarma(15); |
| | | $this->buildNotifications(20); |
| | | $this->buildMembership(); |
| | | $this->buildFavourites(); |
| | | $this->buildKarma(); |
| | | $this->buildNotifications(); |
| | | $this->buildIntegrations(); |
| | | $this->buildSettingsPages(); |
| | | $this->buildAccountPages(); |
| | | $this->buildIntegrations(35); |
| | | $this->buildSettingsPages(40); |
| | | $this->buildAccountPages(50); |
| | | } |
| | | |
| | | private function buildContentPages():void |
| | | private function buildContentPages(int $order = 10):void |
| | | { |
| | | $content = Registrar::getRegistered('post'); |
| | | $this->sections['content'] = new Section('Your Content', 'content', 'book-bookmark'); |
| | | $this->sections['content']->setOrder($order); |
| | | foreach ($content as $c) { |
| | | $registrar = Registrar::getInstance($c); |
| | | $page = $this->addPage($registrar->getPlural(), $c, $registrar->getIcon()); |
| | | $page->setPermission(RoleManager::getPermissionName('edit', $c)); |
| | | $page->setSection('content'); |
| | | $page->setScripts(['jvb-crud', 'jvb-creator']); |
| | | $this->sections[$c] = new Section($registrar->getPlural(), $c, $registrar->getIcon(), 'content'); |
| | | $this->sections[$c]->setPermission(RoleManager::getPermissionName('edit', $c)); |
| | | $this->sections[$c]->setIsLink(true); |
| | | |
| | | $taxonomies = $registrar->registrar->taxonomies; |
| | | foreach ($taxonomies as $tax) { |
| | | $taxReg = Registrar::getInstance($tax); |
| | | if ($taxReg) { |
| | | $taxPage = $this->addPage($taxReg->getPlural(), $tax, $registrar->getIcon(), $page->getID()); |
| | | $taxPage->setPermission(RoleManager::getPermissionName('edit', $c)); |
| | | $taxPage->setSection($c); |
| | | |
| | | $taxPage->setScripts(['jvb-crud', 'jvb-creator']); |
| | | } |
| | | } |
| | | $this->pages[$c] = $page; |
| | | } |
| | | |
| | | $contentTax = Registrar::withFeature('is_content', 'term'); |
| | | foreach ($contentTax as $c) { |
| | | $registrar = Registrar::getInstance($c); |
| | | $page = $this->addPage($registrar->getPlural(), $c, $registrar->getIcon()); |
| | | $page->setPermission(RoleManager::getPermissionName('edit', $c)); |
| | | $page->setSection($c); |
| | | //TODO: add artist management and invitations, if applicable |
| | | } |
| | | } |
| | | |
| | | private function buildSettingsPages():void |
| | | private function buildSettingsPages(int $order = 40):void |
| | | { |
| | | $this->sections['settings'] = new Section('Settings', 'settings', 'gear-six'); |
| | | $this->sections['settings']->setOrder($order); |
| | | |
| | | $seo = $this->addPage('SEO', 'seo', 'robot'); |
| | | $seo->setSection('settings'); |
| | | $seo->setPermission('manage_options'); |
| | | $seo->setScripts(['jvb-schema']); |
| | | //TODO: Create permission that user has editable content |
| | | $this->pages['seo'] = $seo; |
| | | |
| | | } |
| | | private function buildAccountPages():void |
| | | private function buildAccountPages(int $order = 50):void |
| | | { |
| | | if (Site::has('support')) { |
| | | $this->sections['support'] = new Section('Support', 'support', 'question'); |
| | | $this->sections['support']->setOrder($order + 5); |
| | | $page = $this->addPage('Support', 'support', 'question'); |
| | | $page->setSection('support'); |
| | | } |
| | | |
| | | $account = $this->addPage('Account', 'account', 'user-circle'); |
| | | $account->setSection('account'); |
| | | $accountID = $account->getID(); |
| | | |
| | | $password = $this->addPage('Reset Password', 'reset-password', 'password', $accountID); |
| | | $this->sections['account'] = new Section('Account', 'account', 'user-circle'); |
| | | $this->sections['account']->setOrder($order); |
| | | $this->sections['account']->setIsLink(true); |
| | | |
| | | |
| | | $password = $this->addPage('Reset Password', 'reset-password', 'password', $account->getID()); |
| | | $password->setOrder(2); |
| | | $password->setSection('account'); |
| | | |
| | | } |
| | | private function buildReferrals():void |
| | | private function buildReferrals(int $order = 30):void |
| | | { |
| | | if (Site::has('referrals')) { |
| | | $page = $this->addPage('Referrals', 'referrals', 'hand-heart'); |
| | | $page->setOrder($order); |
| | | } |
| | | } |
| | | private function buildMembership():void |
| | | { |
| | | $membership = Site::membership(); |
| | | if ($membership) { |
| | | $buildNotification = false; |
| | | if ($membership->has('can_invite')) { |
| | | $buildNotification = true; |
| | | $page = $this->addPage('Invite', 'invite', ''); |
| | | $page->setSection('notifications'); |
| | | } |
| | | if ($membership->has('term_approval')) { |
| | | $buildNotification = true; |
| | | $page = $this->addPage('Approvals', 'approvals', 'check-circle'); |
| | | $page->setSection('notifications'); |
| | | } |
| | | if ($membership->has('forum')) { |
| | | $page = $this->addPage('Forum', 'forum', 'chats-teardrop'); |
| | | $page->setScripts(['jvb-news']); |
| | | } |
| | | if ($membership->has('member_content')) { |
| | | $page = $this->addPage('Metrics', 'metrics', 'chart-line'); |
| | | } |
| | | |
| | | if ($buildNotification){ |
| | | $this->buildNotificationSection(); |
| | | } |
| | | } |
| | | } |
| | | private function buildFavourites():void |
| | | private function buildFavourites(int $order = 5):void |
| | | { |
| | | if (Site::has('favourites')) { |
| | | $this->sections['favourites'] = new Section('Favourites', 'favourites', 'heart'); |
| | | $this->sections['favourites']->setOrder($order); |
| | | $page = $this->addPage('Favourites', 'favourites', 'heart'); |
| | | //TODO: Lists, Share permissions |
| | | $page->setSection('favourites'); |
| | | $page->setScripts(['jvb-favourites']); |
| | | |
| | | $lists = $this->addPage('Lists', 'lists', 'list-heart', $page->getID()); |
| | | $lists->setSection('favourites'); |
| | | $lists->setScripts(['jvb-favourites']); |
| | | |
| | | $permissions = $this->addPage('Permissions', 'permissions', 'hand-heart', $page->getID()); |
| | | $permissions->setSection('favourites'); |
| | | $permissions->setScripts(['jvb-favourites']); |
| | | } |
| | | } |
| | | private function buildKarma():void |
| | | private function buildKarma(int $order = 15):void |
| | | { |
| | | if (!empty(Registrar::withFeature('karma'))) { |
| | | $page = $this->addPage('Karmic', 'karmic', 'arrow-fat-up'); |
| | | $page->setOrder($order); |
| | | } |
| | | } |
| | | private function buildNotifications():void |
| | | private function buildNotifications(int $order = 20):void |
| | | { |
| | | if (Site::has('notifications')) { |
| | | $this->buildNotificationSection($order); |
| | | $page = $this->addPage('Notifications', 'notifications', 'bell'); |
| | | $page->setSection('notifications'); |
| | | $page->setScripts(['jvb-notifications-manager']); |
| | | |
| | | $page = $this->addPage('Permissions', 'permissions', 'gear-six'); |
| | | $page->setSection('notifications'); |
| | | $page->setOrder(999); |
| | | } |
| | | } |
| | | private function buildIntegrations():void |
| | | private function buildNotificationSection(int $order = 20):void |
| | | { |
| | | $this->sections['notifications'] = new Section('Notifications', 'notifications', 'bell'); |
| | | $this->sections['notifications']->setOrder($order); |
| | | } |
| | | private function buildIntegrations(int $order = 35):void |
| | | { |
| | | if (Site::hasAnyIntegration()) { |
| | | $page = $this->addPage('Integrations', 'integrations', 'plugs-connected'); |
| | | $page->setSection('settings'); |
| | | $page->setOrder($order); |
| | | $page->setScripts(['jvb-integrations']); |
| | | $parent = $page->getID(); |
| | | |
| | | $this->sections['integrations'] = new Section('Integrations', 'integrations', 'plugs-connected', 'settings'); |
| | | $this->sections['integrations']->setOrder($order); |
| | | $this->sections['integrations']->setIsLink(true); |
| | | |
| | | foreach (array_keys(Site::getIntegrations()) as $integration) { |
| | | $integration = match($integration) { |
| | | 'maps' => 'JVBase\integrations\GoogleMaps', |
| | |
| | | }; |
| | | $title = $integration::title(); |
| | | $icon = $integration::icon(); |
| | | $page = $this->addPage($title, $title, $icon, $parent); |
| | | if ($integration::hasExtraOptions()) { |
| | | $page = $this->addPage($title, $title, $icon, $parent); |
| | | |
| | | $page->setScripts(['jvb-integrations']); |
| | | $page->setSection('integrations'); |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | |
| | | public function addPage(string $title, string $slug = '', string $icon = '', int $parent = 0):DashboardPage |
| | | { |
| | | $page = new DashboardPage($title, $slug, $icon, $parent); |
| | | $this->pages[$page->getSlug()] = $page; |
| | | $this->pages[$slug] = $page; |
| | | return $page; |
| | | } |
| | | |
| | |
| | | $sections = array_values(array_filter(array_unique(array_map(function ($page) { |
| | | return $page->getSection(); |
| | | }, $this->pages)))); |
| | | foreach ($sections as $section) { |
| | | $isLink = false; |
| | | switch ($section) { |
| | | case 'content': |
| | | $title = 'Your Content'; |
| | | $icon = 'book-bookmark'; |
| | | break; |
| | | case 'settings': |
| | | $title = 'Settings'; |
| | | $icon = 'faders'; |
| | | break; |
| | | case 'account': |
| | | $title = 'Account'; |
| | | $icon = 'user-circle'; |
| | | break; |
| | | case 'notifications': |
| | | $title = 'Notifications'; |
| | | $icon = 'bell'; |
| | | break; |
| | | case 'support': |
| | | $title = 'Support'; |
| | | $icon = 'question'; |
| | | break; |
| | | default: |
| | | $mainPage = $this->getMainPage($section); |
| | | if ($mainPage) { |
| | | $title = $mainPage->getTitle(); |
| | | $icon = $mainPage->getIcon(); |
| | | $isLink = true; |
| | | } else { |
| | | error_log('[DashboardManager]::buildSections Could not create section for '.$section); |
| | | return; |
| | | } |
| | | break; |
| | | } |
| | | |
| | | if (!$isLink && $this->hasMainPage($section)) { |
| | | $isLink = true; |
| | | } |
| | | |
| | | $this->sections[$section] = new Section($title, $section, $icon); |
| | | if ($isLink) { |
| | | $this->sections[$section]->setIsLink(true); |
| | | } |
| | | $missing = array_diff($sections, array_map(function($s) { return $s->getSlug(); }, $this->sections)); |
| | | if (!empty($missing)) { |
| | | error_log('[DashboardManager]::buildSections Section mentioned, but not defined: '.print_r($missing, true)); |
| | | } |
| | | |
| | | // foreach ($sections as $section) { |
| | | // $isLink = $this->hasMainPage($section); |
| | | // if ($isLink) { |
| | | // $this->sections[$section]->setIsLink(true); |
| | | // } |
| | | // } |
| | | } |
| | | protected function getSectionPages(string $section):array |
| | | protected function getSectionPages(?string $section = null):array |
| | | { |
| | | return array_filter($this->pages, function($page) use ($section) { |
| | | return $page->getSection() === $section; |
| | | }); |
| | | } |
| | | protected function getSectionSections(?string $section = null):array |
| | | { |
| | | return array_filter($this->sections, function($s) use ($section) { |
| | | return $s->getParent() === $section; |
| | | }); |
| | | } |
| | | |
| | | protected function getMainPage(string $section):DashboardPage|false |
| | | { |
| | | $sectionPages = $this->getSectionPages($section); |
| | | $mainPage = array_filter($sectionPages, function ($page) use ($section) { |
| | | |
| | | $section = DashboardPage::sanitizeString($section); |
| | | |
| | | $mainPage = array_values(array_filter($this->pages, function ($page) use ($section) { |
| | | return $page->getSlug() === $section; |
| | | }); |
| | | })); |
| | | |
| | | return empty($mainPage) ? false : $mainPage[0]; |
| | | } |
| | | |
| | | protected function hasMainPage(string $section):bool |
| | | { |
| | | $section = DashboardPage::sanitizeString($section); |
| | | return $this->getMainPage($section) !== false; |
| | | } |
| | | |
| | |
| | | return true; |
| | | } |
| | | |
| | | protected function setCurrentUserPages():void |
| | | { |
| | | $this->currentUserPages = array_filter($this->pages, function($page) { |
| | | return current_user_can('manage_options') || empty($page->getPermission()) || current_user_can($page->getPermission()); |
| | | }); |
| | | } |
| | | |
| | | #[NoReturn]protected function redirectToLogin():void |
| | | { |
| | | wp_redirect(wp_login_url(get_home_url(null, '/dash'))); |
| | | wp_redirect(wp_login_url(get_home_url(null, '/'.$this->slug))); |
| | | exit; |
| | | } |
| | | |
| | | #[NoReturn]protected function redirectToDashboard():void |
| | | { |
| | | wp_redirect(get_home_url(null, '/dash')); |
| | | wp_redirect(get_home_url(null, '/'.$this->slug)); |
| | | exit; |
| | | } |
| | | #[NoReturn]protected function redirectToHome():void |
| | |
| | | protected function getCurrentPage():DashboardPage|false |
| | | { |
| | | $slug = $this->getCurrentPageSlug(); |
| | | if (!array_key_exists($slug, $this->pages)) { |
| | | $page = array_filter($this->pages, function($p) use ($slug) { |
| | | return $p->getSlug() === $slug; |
| | | }); |
| | | if (empty($page)) { |
| | | error_log('[DashboardManager]::getCurrentPage Could not get configuration for '.$slug); |
| | | return false; |
| | | } |
| | | return $this->pages[$slug]; |
| | | return $page[array_key_first($page)]; |
| | | } |
| | | |
| | | protected function getCurrentPageSlug():string |
| | | { |
| | | if (is_post_type_archive(BASE.'dash')) { |
| | | if (is_post_type_archive(self::$based)) { |
| | | return 'dash'; |
| | | } |
| | | |
| | |
| | | |
| | | return $post->post_name; |
| | | } |
| | | |
| | | |
| | | public function renderMain(string $slug):void |
| | | { |
| | | if ($slug !== 'dash') { |
| | | return; |
| | | } |
| | | $user = get_userdata(get_current_user_id()); |
| | | $name = ($user->first_name !== '') ? $user->first_name : $user->display_name; |
| | | |
| | | echo sprintf( |
| | | '<h1>Hey %s</h1><p>Welcome back!</p>', |
| | | $name |
| | | ); |
| | | |
| | | $reg = Registrar::getInstance(jvbUserRole($user->ID)); |
| | | if ($reg && !empty($reg->getCreatable())) { |
| | | echo '<p>Everything saves auto-magically - so rest easy.</p>'; |
| | | } |
| | | } |
| | | |
| | | public function renderAccount():void |
| | | { |
| | | |
| | | $user = get_userdata(get_current_user_id()); |
| | | $role = jvbUserRole($user->ID); |
| | | $registrar = Registrar::getInstance($role); |
| | | |
| | | |
| | | if ($registrar) { |
| | | $meta = Meta::forUser($user->ID); |
| | | $fields = $registrar->getFields(); |
| | | } else { |
| | | $fields = Fields::getUserFields(); |
| | | $meta = new Meta($user->ID, 'user', $fields); |
| | | } |
| | | |
| | | echo Form::renderFormFrom($meta, 'user', [ |
| | | 'heading' => 'Your Account', |
| | | 'description' => [ |
| | | 'You can set your information here.', |
| | | 'To reset your password, check the side menu under "Account"' |
| | | ] |
| | | ]); |
| | | } |
| | | } |
| | |
| | | namespace JVBase\managers\Dashboard; |
| | | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Query; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | class DashboardPage { |
| | | protected string $title; |
| | | protected string $slug; |
| | | protected string $ogSlug; |
| | | protected string $icon; |
| | | protected string $URL; |
| | | protected int $parent = 0; |
| | | protected int $ID; |
| | | protected ?string $permission = null; |
| | | protected ?string $section = null; |
| | |
| | | |
| | | public function __construct(string $title, string $slug = '', string $icon ='', int $parent = 0) { |
| | | $this->cache = Cache::for('dashboard'); |
| | | $this->title = $title; |
| | | if (empty($slug)) { |
| | | $this->setSlug($title); |
| | | }else { |
| | | $this->setSlug($slug); |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | $this->title = $title; |
| | | $this->setSlug(empty($slug) ? $title : $slug); |
| | | $this->icon = $icon; |
| | | $this->parent = $parent; |
| | | $this->setID($parent); |
| | | $this->setURL(); |
| | | } |
| | | |
| | | public function setID(int $parentID):void |
| | | { |
| | | if ($this->slug === 'dash') { |
| | | //We do not need to create the page for the main dash |
| | | $this->ID = 0; |
| | | return; |
| | | } |
| | | $this->ID = $this->cache->remember( |
| | | $this->slug.'_ID', |
| | | function() use ($parentID) { |
| | | $existing = new WP_Query([ |
| | | 'post_type' => BASE.'dash', |
| | | 'post_type' => DashboardManager::$based, |
| | | 'name' => $this->slug, |
| | | 'fields' => 'ids', |
| | | 'posts_per_page'=> 1, |
| | |
| | | $args = [ |
| | | 'post_title' => $this->title, |
| | | 'post_name' => $this->slug, |
| | | 'post_type' => BASE.'dash', |
| | | 'post_type' => DashboardManager::$based, |
| | | 'post_status' => 'publish' |
| | | ]; |
| | | if ($parentID > 0) { |
| | |
| | | return $this->ID; |
| | | } |
| | | |
| | | public function getParent():int |
| | | { |
| | | return $this->parent; |
| | | } |
| | | |
| | | public function setURL():void |
| | | { |
| | | $this->URL = $this->cache->remember( |
| | | $this->slug.'_url', |
| | | function () { |
| | | return get_permalink($this->ID); |
| | | } |
| | | ); |
| | | |
| | | if ($this->slug === 'dash') { |
| | | $this->URL = get_post_type_archive_link(DashboardManager::$based); |
| | | return; |
| | | } |
| | | $this->URL = get_the_permalink($this->ID); |
| | | } |
| | | |
| | | public function getURL():string |
| | |
| | | |
| | | public function setSlug(string $slug):void |
| | | { |
| | | $this->ogSlug = $slug; |
| | | $this->slug = self::sanitizeString($slug); |
| | | } |
| | | public function getSlug():string |
| | |
| | | } |
| | | public function render():string |
| | | { |
| | | return $this->cache->remember( |
| | | $this->ID, |
| | | function () { |
| | | return apply_filters(BASE.'render_'.$this->slug, '<h2>'.$this->title.' is not configured yet.</h2><p>Add a filter to '.BASE.'render_'.$this->slug.'</p>'); |
| | | } |
| | | ); |
| | | $check = str_replace('-','_',$this->slug); |
| | | ob_start(); |
| | | do_action(BASE.'dashboard_page_'.$check, $this->slug, $this->ogSlug); |
| | | $out = ob_get_clean(); |
| | | |
| | | if (empty($out)) { |
| | | $out = sprintf( |
| | | '<h1>%s</h1><p>It doesn\'t look like this page is configured yet.</p>', |
| | | $this->title, |
| | | ); |
| | | } |
| | | return $out; |
| | | } |
| | | /********************************************************* |
| | | * UTILITY |
| | |
| | | { |
| | | return str_replace('_', '-', strtolower(sanitize_title($string))); |
| | | } |
| | | |
| | | public function setScripts(array $scripts):void |
| | | { |
| | | $this->scripts = $scripts; |
| | | } |
| | | public function getScripts():array |
| | | { |
| | | return $this->scripts; |
| | | } |
| | | } |
| | |
| | | protected string $icon = ''; |
| | | protected int $order = 0; |
| | | protected bool $isLink = false; |
| | | protected ?string $permission = null; |
| | | |
| | | public function __construct(string $title, ?string $slug = null, string $icon = '', ?string $parent = null) { |
| | | $this->title = $title; |
| | | $this->slug = is_null($slug) ? DashboardPage::sanitizeString($title) : DashboardPage::sanitizeString($slug); |
| | | $this->icon = $icon; |
| | | $this->parent = $parent; |
| | | $this->parent = is_null($parent) ? $parent : DashboardPage::sanitizeString($parent); |
| | | } |
| | | |
| | | public function getTitle():string |
| | |
| | | { |
| | | return $this->isLink; |
| | | } |
| | | |
| | | public function setPermission(string $permission):void |
| | | { |
| | | $this->permission = $permission; |
| | | } |
| | | public function getPermission():?string |
| | | { |
| | | return $this->permission; |
| | | } |
| | | } |
| | |
| | | $page = $this->getCurrentPageTitle(); |
| | | // Check if page exists in allowed pages |
| | | $allowedPages = $this->getUserAllowedPages(); |
| | | |
| | | error_log('Allowed pages: '.print_r($allowedPages, true)); |
| | | if (!in_array($page, $allowedPages)) { |
| | | error_log("User not allowed to access page: {$page}"); |
| | | $this->redirectToDashboard(); |
| | |
| | | |
| | | public function renderIndex(string $content, string $page):string |
| | | { |
| | | jvbDump($page); |
| | | if ($page !== '' && $page !== 'dash') { |
| | | return $content; |
| | | } |
| | |
| | | } |
| | | |
| | | if (!$user || !$this->userHasDashboardAccess($user)) { |
| | | error_log('No Dashboard Access'); |
| | | return []; |
| | | } |
| | | |
| | | |
| | | $pages = $this->cache->get($userID); |
| | | |
| | | $pages = false; |
| | | if ($pages === false || JVB_TESTING) { |
| | | if (user_can($userID, 'manage_options')) { |
| | | // Admin gets all pages as flat array |
| | |
| | | $pages = $this->getAllDashboardPages(); |
| | | |
| | | $canSkip = user_can($userID, 'skip_moderation'); |
| | | jvbDump($pages, 'All Pages'); |
| | | foreach($pages as $key => $slug) { |
| | | //Default to Remove pages |
| | | $remove = true; |
| | |
| | | } |
| | | } |
| | | break; |
| | | case 'Account': |
| | | case 'dash': |
| | | case 'Referrals': |
| | | case 'favourites': |
| | |
| | | if (Site::has('dashboard')) { |
| | | $map['dash'] = 'door'; |
| | | } |
| | | if (Site::has('magicLink')) { |
| | | if (Site::has('magic_link')) { |
| | | $map['magicLink'] = 'magic-wand'; |
| | | } |
| | | if (Site::hasAnyIntegration()) { |
| | |
| | | $this->cache = Cache::for('login'); |
| | | $this->cache->flush(); |
| | | // Initialize magic link support if enabled |
| | | if (Site::has('magicLink')) { |
| | | if (Site::has('magic_link')) { |
| | | $this->initMagicLinkSupport(); |
| | | } |
| | | |
| | |
| | | add_filter( 'logout_url', [$this, 'logoutUrl'], 10, 2 ); |
| | | // Allow other features to register handlers |
| | | do_action('jvbLoginManagerInit', $this); |
| | | add_action('user_register', array($this, 'saveRegistrationFields'), 999, 2); |
| | | add_action('user_register', [$this, 'saveRegistrationFields'], 50, 2); |
| | | add_filter('the_seo_framework_sitemap_exclude_ids', [$this, 'excludeLoginSitemap'], 8, 1); |
| | | } |
| | | public static function getInstance():self |
| | |
| | | |
| | | protected function initMagicLinkSupport(): void |
| | | { |
| | | if (!Site::has('magicLink')) { |
| | | if (!Site::has('magic_link')) { |
| | | return; |
| | | } |
| | | } |
| | |
| | | $redirect = !empty($redirect) ? $redirect : esc_attr($_GET['redirect_to'] ?? ''); |
| | | |
| | | return sprintf( |
| | | '<form name="%sform" method="post" data-action="jvb_%s" data-handled> |
| | | '<form name="%sform" method="post" data-action="jvb_%s" data-save="auth/login"> |
| | | %s%s%s |
| | | <input type="hidden" name="action" value="jvb_%s"> |
| | | <input type="hidden" name="redirect_to" value="%s"> |
| | |
| | | $additionalInputs, |
| | | $fields, |
| | | $turnstile, |
| | | $this->labels['submit'], |
| | | $this->labels['submit']??'Login', |
| | | $magicLink |
| | | ); |
| | | } |
| | |
| | | |
| | | $this->maybeTurnstileScripts(); |
| | | wp_enqueue_script('jvb-form'); |
| | | wp_enqueue_script('jvb-login'); |
| | | $action = $this->getAction(); |
| | | |
| | | $redirect_to = isset($_GET['redirect_to']) ? esc_url_raw($_GET['redirect_to']) : ''; |
| | |
| | | <?php |
| | | namespace JVBase\managers; |
| | | |
| | | use JetBrains\PhpStorm\NoReturn; |
| | | use JVBase\base\Site; |
| | | use JVBase\meta\Form; |
| | | use JVBase\ui\Tabs; |
| | | use WP_User; |
| | | use WP_Error; |
| | | |
| | |
| | | // Validate email |
| | | $email = sanitize_email($email); |
| | | if (!is_email($email)) { |
| | | error_log('Invalid email for Magic Link: '.$email); |
| | | return new WP_Error('invalid_email', 'Invalid email address'); |
| | | } |
| | | |
| | | // Check rate limiting |
| | | $rate_check = $this->checkRateLimit($email); |
| | | if (is_wp_error($rate_check)) { |
| | | error_log('Rate limit reached for Magic Link: '.$email); |
| | | return $rate_check; |
| | | } |
| | | |
| | | // Handle different link types |
| | | switch ($type) { |
| | | case self::TYPE_LOGIN: |
| | | return $this->sendLoginLink($email, $context); |
| | | |
| | | case self::TYPE_SIGNUP: |
| | | return $this->sendSignupLink($email, $context); |
| | | |
| | | case self::TYPE_REFERRAL: |
| | | return $this->sendReferralLink($email, $context); |
| | | |
| | | case self::TYPE_RESET: |
| | | return $this->sendResetLink($email, $context); |
| | | |
| | | default: |
| | | return new WP_Error('invalid_type', 'Invalid magic link type'); |
| | | } |
| | | return match ($type) { |
| | | self::TYPE_LOGIN => $this->sendLoginLink($email, $context), |
| | | self::TYPE_SIGNUP => $this->sendSignupLink($email, $context), |
| | | self::TYPE_REFERRAL => $this->sendReferralLink($email, $context), |
| | | self::TYPE_RESET => $this->sendResetLink($email, $context), |
| | | default => new WP_Error('invalid_type', 'Invalid magic link type'), |
| | | }; |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | $user = get_user_by('email', $email); |
| | | if (!$user) { |
| | | return new WP_Error('user_not_found', 'No account found with this email'); |
| | | error_log('Attempted signin with no account, redirecting to signup link '.$email); |
| | | return $this->sendSignupLink($email, $context); |
| | | } |
| | | |
| | | $token = $this->generateToken($email, self::TYPE_LOGIN, [ |
| | |
| | | |
| | | $token_data = [ |
| | | 'name' => $context['name'] ?? '', |
| | | 'role' => $context['role'] ?? 'subscriber', |
| | | 'role' => $context['role'] ?? Site::getDefaultRole(), |
| | | 'meta' => $context['meta'] ?? [] |
| | | ]; |
| | | |
| | |
| | | */ |
| | | public function handleMagicLinkClick(): void |
| | | { |
| | | if (!isset($_GET['action']) || !isset($_GET['magic_token']) || !isset($_GET['email'])) { |
| | | if (!isset($_GET['magic_token']) || !isset($_GET['action']) || !isset($_GET['email'])) { |
| | | return; |
| | | } |
| | | |
| | |
| | | $token_data = $this->verifyToken($token, $email); |
| | | |
| | | if (is_wp_error($token_data)) { |
| | | $this->handleInvalidToken($token_data); |
| | | $this->cleanURL(); |
| | | return; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | #[NoReturn] protected function cleanURL():void |
| | | { |
| | | global $wp; |
| | | $redirect = isset($_GET['redirect_to']) ? esc_url_raw($_GET['redirect_to']) : get_home_url(null, '/'.$wp->request); |
| | | if (isset($_GET['magic_token'])) { |
| | | error_log('============= Redirecting and removing magic token stuffs ============='); |
| | | wp_safe_redirect(remove_query_arg(['magic_token', 'action', 'email'], esc_url($redirect))); |
| | | exit; |
| | | } |
| | | wp_safe_redirect(esc_url($redirect)); |
| | | exit; |
| | | } |
| | | |
| | | /** |
| | | * Process login via magic link |
| | | */ |
| | |
| | | wp_set_auth_cookie($user->ID, true); |
| | | |
| | | do_action('wp_login', $user->user_login, $user); |
| | | |
| | | $redirect = isset($_GET['redirect_to']) ? esc_url_raw($_GET['redirect_to']) : home_url('/dash'); |
| | | |
| | | wp_safe_redirect($redirect); |
| | | exit; |
| | | $this->cleanURL(); |
| | | } |
| | | |
| | | /** |
| | | * Process signup via magic link |
| | | */ |
| | | protected function processSignup(array $token_data): void |
| | | #[NoReturn] protected function processSignup(array $token_data): void |
| | | { |
| | | if (!array_key_exists('email', $token_data) || !array_key_exists('name', $token_data)) { |
| | | JVB()->error()->log('[MagicLinkManager]Could not process Signup'); |
| | | return; |
| | | JVB()->error()->log('MagicLinkManager', 'Could not process Signup'); |
| | | $this->cleanURL(); |
| | | } |
| | | $user_id = wp_create_user( |
| | | $token_data['email'], |
| | |
| | | ); |
| | | |
| | | if (is_wp_error($user_id)) { |
| | | wp_die('Failed to create account: ' . $user_id->get_error_message()); |
| | | $this->cleanURL(); |
| | | } |
| | | |
| | | $user = get_user_by('ID', $user_id); |
| | |
| | | wp_set_current_user($user_id); |
| | | wp_set_auth_cookie($user_id, true); |
| | | |
| | | do_action('user_register', $user_id); |
| | | do_action('wp_login', $user->user_login, $user); |
| | | |
| | | wp_safe_redirect(home_url('/dash?welcome=1')); |
| | | exit; |
| | | $this->cleanURL(); |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | $email = sanitize_email($token_data['email']); |
| | | if (email_exists($email)) { |
| | | wp_die('Looks like you already have an account!'); |
| | | $this->processSignup($token_data); |
| | | } |
| | | |
| | | $role = JVB()->referrals()->getRole(); |
| | | $pass = wp_generate_password(20, true, true); |
| | | $name = sanitize_text_field($token_data['name']); |
| | |
| | | $user_id->get_error_message(), |
| | | $token_data |
| | | ); |
| | | $this->cleanURL(); |
| | | } |
| | | } |
| | | |
| | |
| | | $user = get_user_by('ID', $token_data['user_id']); |
| | | |
| | | if (!$user) { |
| | | wp_die('Invalid user'); |
| | | JVB()->error()->log( |
| | | 'MagicLinkManager', |
| | | 'Invalid user, could not reset password', |
| | | ); |
| | | $this->cleanURL(); |
| | | } |
| | | |
| | | // Log user in and redirect to password change page |
| | | wp_set_current_user($user->ID); |
| | | wp_set_auth_cookie($user->ID, true); |
| | | |
| | | //TODO: hmmm |
| | | wp_safe_redirect(admin_url('profile.php?password_reset=1')); |
| | | exit; |
| | | } |
| | | |
| | | /** |
| | | * Handle invalid token |
| | | */ |
| | | protected function handleInvalidToken(WP_Error $error): void |
| | | { |
| | | wp_die($error->get_error_message()); |
| | | } |
| | | |
| | | /** |
| | | * Handle failed login - offer magic link option |
| | | */ |
| | | public function handleFailedLogin(string $username): void |
| | |
| | | |
| | | return $content; |
| | | } |
| | | |
| | | public function renderForm():string |
| | | { |
| | | ob_start(); |
| | | JVB()->connect('cloudflare')->renderTurnstile(); |
| | | $turnstile = ob_get_clean(); |
| | | global $wp; |
| | | |
| | | $magicLink = sprintf( |
| | | '<form id="magic-link-form" data-save="auth/magic"> |
| | | %s%s |
| | | <input type="hidden" name="redirect_to" value="%s"> |
| | | %s |
| | | <button type="submit">%sLogin with Magic Link</button> |
| | | <p class="hint"><a href="" data-tab="login">Prefer to use a password?</a></p> |
| | | </form> |
| | | <div class="success-content" hidden> |
| | | <h3>Check Your Email!</h3> |
| | | <p>We\'ve sent you a magic link to log in - no password required! Click the link in your email to log in.</p> |
| | | <p class="hint">Can\'t find it? Check your spam folder.</p> |
| | | </div>', |
| | | jvbFormStatus(), |
| | | Form::render('user_email', null, [ |
| | | 'required' => true, |
| | | 'type' => 'email', |
| | | 'label' => 'Your Email', |
| | | 'autocomplete'=>'email' |
| | | ]), |
| | | get_home_url(null,'/'.$wp->request), |
| | | $turnstile, |
| | | jvbIcon('magic-wand'), |
| | | // wp_login_url() |
| | | ); |
| | | |
| | | $login = LoginManager::getInstance()->renderLoginForm('login', get_home_url(null,'/'.$wp->request)); |
| | | |
| | | $tabs = new Tabs(); |
| | | $tabs->addTab('magic') |
| | | ->title('With Magic Link') |
| | | ->icon('magic-wand') |
| | | ->content($magicLink); |
| | | |
| | | $tabs->addTab('login') |
| | | ->title('With Password') |
| | | ->icon('password') |
| | | ->content($login); |
| | | |
| | | return $tabs->render(); |
| | | } |
| | | } |
| | |
| | | add_action('admin_notices', [$this, 'showReferralPageNotice']); |
| | | |
| | | |
| | | add_filter('jvbDashboardPage', [$this, 'renderDashPage'], 10, 2); |
| | | add_action(BASE.'dashboard_page_referrals', [$this, 'renderDashPage'], 10); |
| | | // add_filter('jvbDashboardPage', [$this, 'renderDashPage'], 10, 2); |
| | | |
| | | // Handle settings save |
| | | add_action('admin_init', [$this, 'registerSettings']); |
| | |
| | | <p class="hint">Can\'t find it? Check your spam folder.</p> |
| | | </div>', |
| | | jvbFormStatus(), |
| | | esc_attr(get_option(BASE.'referral_role','client')), |
| | | esc_attr(Site::getReferralRole()), |
| | | Form::render('referral_name', '', [ |
| | | 'required' => true, |
| | | 'type' => 'text', |
| | |
| | | '<form id="login-form">%s%s%s |
| | | <button type="submit">%sLogin With Magic Link</button> |
| | | </form> |
| | | |
| | | <div class="success-content" hidden> |
| | | <h3>Check Your Email!</h3> |
| | | <p>We\'ve sent you a magic link to log in - no password required! Click the link in your email to log in.</p> |
| | |
| | | } |
| | | } |
| | | |
| | | public function renderDashPage(string $content, string $page): string |
| | | public function renderDashPage():void |
| | | { |
| | | if ($page !== 'Referrals') { |
| | | return $content; |
| | | } |
| | | |
| | | // Regular users get their referral dashboard |
| | | $user_id = get_current_user_id(); |
| | | |
| | |
| | | |
| | | $referrals = $this->getUserReferrals($user_id, ['per_page' => 20]); |
| | | |
| | | ob_start(); |
| | | |
| | | $tabs = new Tabs(); |
| | | $tabs->addTab('share') |
| | |
| | | |
| | | ?> |
| | | <div class="referral-dashboard"> |
| | | <?= $tabs->render(true);?> |
| | | <?= $tabs->render();?> |
| | | </div> |
| | | |
| | | <?php |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | | protected function shareDashboard(int $user_id, string $referral_code):string |
| | |
| | | } |
| | | |
| | | |
| | | switch (true) { |
| | | |
| | | case is_singular(): |
| | | $key = get_queried_object_id(); |
| | | break; |
| | | case is_tax(): |
| | | $obj = get_queried_object(); |
| | | $key = $obj->taxonomy; |
| | | break; |
| | | case is_post_type_archive(): |
| | | $obj = get_queried_object(); |
| | | $key = $obj->name; |
| | | break; |
| | | case is_home(): |
| | | $obj = get_queried_object(); |
| | | $key = $obj->post_type; |
| | | break; |
| | | default: |
| | | $key = 'home'; |
| | | break; |
| | | } |
| | | $key = match(true) { |
| | | is_singular() => get_queried_object_id(), |
| | | is_tax() => get_queried_object()->taxonomy, |
| | | is_post_type_archive() => get_queried_object()->name, |
| | | default => 'home' |
| | | }; |
| | | |
| | | return $this->cache->remember( |
| | | $key, |
| | |
| | | ]; |
| | | |
| | | $obj = get_queried_object(); |
| | | if (is_null($obj)) { |
| | | return $crumbs; |
| | | } |
| | | if (is_tax()) { |
| | | $crumbs = $this->addTaxonomyCrumbs($crumbs, $obj); |
| | | } elseif (is_singular() || is_home()) { |
| | |
| | | |
| | | |
| | | // Add to JVB dashboard |
| | | add_action(BASE.'dashboardPage', [$this, 'addDashboardSection'], 10, 1); |
| | | add_filter('jvbDashboardPage', [$this, 'addDashboardSection'], 20, 2); |
| | | add_action('admin_enqueue_scripts', [$this, 'enqueueScripts']); |
| | | } |
| | |
| | | /** |
| | | * Add section to JVB dashboard |
| | | */ |
| | | public function addDashboardSection(string $content, string $page): string |
| | | public function addDashboardSection(string $page):void |
| | | { |
| | | if ($page !== 'SEO') { |
| | | return $content; |
| | | jvbDump($page); |
| | | if ($page !== 'seo') { |
| | | return; |
| | | } |
| | | ob_start(); |
| | | $this->renderAdminPage(); |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | | /** |
| | |
| | | return $item; |
| | | },$array), function ($item) use ($property, $className) { |
| | | $test = class_exists($className) && is_a($item, $className); |
| | | |
| | | if (!$test) { |
| | | error_log('[SEO]Item property '.$property.' is not an instance of '.$className); |
| | | } |
| | |
| | | add_action('init', 'jvbRegisterScripts', 5); |
| | | |
| | | function jvbRegisterScripts() { |
| | | $version = '1.1.67'; |
| | | $version = '1.1.68'; |
| | | $strategy = [ |
| | | 'strategy' => 'defer', |
| | | 'in_footer' => true |
| | | ]; |
| | | wp_register_script( |
| | | 'jvb-login', |
| | | JVB_URL.'assets/js/min/login.min.js', |
| | | [ |
| | | 'jvb-auth', |
| | | 'jvb-form', |
| | | ], |
| | | $version |
| | | ); |
| | | |
| | | wp_register_style( |
| | | 'jvb-form', |
| | |
| | | IconsManager::for('dash'); |
| | | } |
| | | |
| | | if (Site::has('magicLink')) { |
| | | if (Site::has('magic_link')) { |
| | | require(JVB_DIR . '/inc/managers/MagicLinkManager.php'); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | if (Site::has('dashboard')) { |
| | | require(JVB_DIR . '/inc/managers/DashboardManager.php'); |
| | | // require(JVB_DIR . '/inc/managers/DashboardManager.php'); |
| | | require(JVB_DIR . '/inc/managers/Dashboard\_setup.php'); |
| | | require(JVB_DIR . '/inc/managers/CRUDManager.php'); |
| | | require(JVB_DIR . '/inc/managers/UploadManager.php'); |
| | | } |
| | |
| | | // Use provided operation_id or generate one |
| | | $op->id = !empty($options['operation_id']) |
| | | ? $options['operation_id'] |
| | | : 'u' . $userId . '_' . uniqid('op_'); |
| | | : self::generateID($userId); |
| | | |
| | | $op->type = $type; |
| | | $op->userId = $userId; |
| | |
| | | return $response; |
| | | } |
| | | } |
| | | |
| | | public static function generateId(int $userID):string |
| | | { |
| | | return 'u' . $userID . '_' . uniqid('op_'); |
| | | } |
| | | } |
| | |
| | | |
| | | public function insert(Operation $op): bool |
| | | { |
| | | $result = $this->table->insert([ |
| | | 'id' => $op->id, |
| | | 'type' => $op->type, |
| | | 'user_id' => $op->userId, |
| | | $existing = $this->find($op->id); |
| | | if ($existing && in_array($existing->state, ['completed', 'processing'] )) { |
| | | $op->id = Queue::generateId($op->userId); |
| | | } |
| | | $result = $this->table->findOrCreate([ |
| | | 'id' => $op->id, |
| | | 'type' => $op->type, |
| | | 'user_id' => $op->userId, |
| | | ],[ |
| | | 'request_data' => json_encode($op->requestData), |
| | | 'total_items' => $op->totalItems, |
| | | 'processed_items' => $op->processedItems, |
| | |
| | | * Constructor |
| | | ***************************************************************/ |
| | | |
| | | public function __construct(int|string|null $id, string $type) |
| | | public function __construct(int|string|null $id, string $type, array $fields = []) |
| | | { |
| | | $this->validator = new Validator(); |
| | | $this->sanitizer = new Sanitizer(); |
| | | $this->typeManager = new MetaTypeManager(); |
| | | $this->type = $type; |
| | | $this->ID = $id; |
| | | $this->buildData($id, $type); |
| | | $this->buildData($id, $type, $fields); |
| | | } |
| | | |
| | | protected function buildData(int|string|null $id, string $type):void |
| | | protected function buildData(int|string|null $id, string $type, array $fields):void |
| | | { |
| | | $this->wpObject = match($type) { |
| | | 'post' => get_post($id), |
| | |
| | | default => null |
| | | }; |
| | | |
| | | |
| | | |
| | | $registrar = !is_null($this->slug) ? Registrar::getInstance($this->slug) : false; |
| | | $fields = $registrar ? $registrar->getFields() : []; |
| | | $fields = $registrar ? $registrar->getFields() : $fields; |
| | | |
| | | if ($this->type == 'option') { |
| | | $options = Options::getInstance(); |
| | | $fields = $options->getFields(); |
| | |
| | | $meta = []; |
| | | } |
| | | $meta = array_map(fn($value) => maybe_unserialize($value[0]), $meta); |
| | | |
| | | $this->fields = []; |
| | | foreach ($fields as $fieldName => $config) { |
| | | $fieldName = jvbNoBase($fieldName); |
| | | if ($this->type === 'option') { |
| | | $value = get_option(BASE . $fieldName, $config['default'] ?? ''); |
| | | } else if ($this->wpObject && property_exists($this->wpObject, $fieldName)) { |
| | | } else if ( |
| | | $this->wpObject && property_exists($this->wpObject, $fieldName) |
| | | || $type === 'user' && $this->wpObject && property_exists($this->wpObject->data, $fieldName)) { |
| | | $config['wp'] = true; |
| | | $value = $this->wpObject->$fieldName; |
| | | } else if (in_array($fieldName, $this->defaults)) { |
| | | |
| | | } else if (in_array($fieldName, $this->defaults)) { |
| | | $config['wp'] = true; |
| | | switch ($fieldName) { |
| | | case 'post_thumbnail': |
| | |
| | | $this->save(); |
| | | } |
| | | |
| | | |
| | | if ($this->type === 'option') { |
| | | error_log('Value saved for: '.$name.': '.print_r($value, true)); |
| | | } |
| | | |
| | | return $this; |
| | | } |
| | | |
| | |
| | | foreach ($custom as $field) { |
| | | if ($this->type === 'option') { |
| | | $result = update_option(BASE.$field->name, $field->value); |
| | | if ($result) { |
| | | error_log('Option '.$field->name.' updated'); |
| | | } else { |
| | | error_log('Option '.$field->name.' not updated'); |
| | | } |
| | | error_log('Update option '.$field->name.' result: '.print_r($result, true)); |
| | | } else { |
| | | $result = $function($this->ID, BASE.$field->name, $field->value); |
| | | } |
| | |
| | | |
| | | protected function setByPath(string $path, mixed $value): self |
| | | { |
| | | error_log('Setting by path: '.$path.', with value: '.print_r($value, true)); |
| | | $parts = explode(':', $path, 3); |
| | | if (count($parts) !== 3) { |
| | | return $this; |
| | |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\meta\Form; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\fields\Field; |
| | | use JVBase\registrar\fields\GroupedField; |
| | | use JVBase\registrar\fields\OptionsField; |
| | |
| | | class Fields { |
| | | protected array $fields; |
| | | private ?Registrar $registrar = null; |
| | | protected ?string $type; |
| | | |
| | | public function __construct(?string $type = null, ?Registrar $registrar = null) { |
| | | $this->registrar = $registrar; |
| | | $this->type = $type; |
| | | switch ($type) { |
| | | case 'post': |
| | | $this->addPostFields(); |
| | | //TODO: Add fields to post edit screen |
| | | // add_action('add_meta_boxes', [$this, 'registerPostFields']); |
| | | break; |
| | | case 'term': |
| | | $this->addTermFields(); |
| | |
| | | return $this; |
| | | } |
| | | |
| | | public function addPostFields():void |
| | | protected function defaultPostFields():array |
| | | { |
| | | $statuses = [ |
| | | // 'all' => [ |
| | |
| | | ], |
| | | ]; |
| | | } |
| | | $fields = [ |
| | | 'post_status' => [ |
| | | 'type' => 'radio', |
| | | 'label' => 'Status', |
| | | 'default' => 'draft', |
| | | 'options' => $statuses |
| | | ], |
| | | return [ |
| | | 'post_status' => [ |
| | | 'type' => 'radio', |
| | | 'label' => 'Status', |
| | | 'default' => 'draft', |
| | | 'options' => $statuses |
| | | ], |
| | | 'post_thumbnail' => [ |
| | | 'type' => 'upload', |
| | | 'subtype' => 'image', |
| | |
| | | 'maxLength' => 158, |
| | | ], |
| | | ]; |
| | | } |
| | | |
| | | public function addPostFields():void |
| | | { |
| | | $fields = $this->defaultPostFields(); |
| | | |
| | | foreach ($fields as $name => $config) { |
| | | $this->addField($name, $config); |
| | | } |
| | | } |
| | | |
| | | public function addTermFields():void |
| | | protected function defaultTermFields():array |
| | | { |
| | | $fields = [ |
| | | 'name' => [ |
| | |
| | | 'label' => 'Term Parent' |
| | | ]; |
| | | } |
| | | foreach ($fields as $name => $config) { |
| | | |
| | | return $fields; |
| | | } |
| | | |
| | | public function addTermFields():void |
| | | { |
| | | foreach ($this->defaultTermFields() as $name => $config) { |
| | | $this->addField($name, $config); |
| | | } |
| | | } |
| | | |
| | | public function addUserFields():void |
| | | protected function defaultUserFields():array |
| | | { |
| | | $fields = [ |
| | | return [ |
| | | 'first_name' => [ |
| | | 'type' => 'text', |
| | | 'label' => 'First Name', |
| | |
| | | 'type' => 'text', |
| | | 'label' => 'Last Name', |
| | | ], |
| | | 'user_email' => [ |
| | | 'type' => 'email', |
| | | 'label' => 'Your Email', |
| | | ], |
| | | 'display_name' => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Display Name', |
| | | ], |
| | | 'website' => [ |
| | | 'type' => 'url', |
| | | 'label' => 'Website', |
| | | ], |
| | | 'description' => [ |
| | | 'type' => 'textarea', |
| | | 'quill' => true, |
| | | 'label' => 'Description', |
| | | ] |
| | | // 'website' => [ |
| | | // 'type' => 'url', |
| | | // 'label' => 'Website', |
| | | // ], |
| | | // 'description' => [ |
| | | // 'type' => 'textarea', |
| | | // 'quill' => true, |
| | | // 'label' => 'Description', |
| | | // ], |
| | | ]; |
| | | foreach ($fields as $name => $config) { |
| | | } |
| | | public function addUserFields():void |
| | | { |
| | | foreach ($this->defaultUserFields() as $name => $config) { |
| | | $this->addField($name, $config); |
| | | } |
| | | } |
| | |
| | | $business = new LocalBusiness(); |
| | | $business->setHasOfferCatalogField($this); |
| | | } |
| | | |
| | | public function registerPostFields():void |
| | | { |
| | | $postType = $this->registrar->getBased(); |
| | | add_meta_box( |
| | | $postType.'_fields', |
| | | 'Extra Fields', |
| | | [$this, 'renderMyMetaBox'], |
| | | $postType, |
| | | 'side', |
| | | 'high' |
| | | ); |
| | | } |
| | | public function renderMyMetaBox():void |
| | | { |
| | | $ID = get_the_ID(); |
| | | $meta = match ($this->type) { |
| | | 'post' => Meta::forPost($ID), |
| | | 'term' => Meta::forTerm($ID), |
| | | 'user' => Meta::forUser($ID), |
| | | }; |
| | | $defaults = array_keys(match($this->type) { |
| | | 'post' => $this->defaultPostFields(), |
| | | 'term' => $this->defaultTermFields(), |
| | | 'user' => $this->defaultUserFields() |
| | | }); |
| | | foreach ($this->fields as $fName => $field){ |
| | | if (!in_array($fName, $defaults)){ |
| | | echo Form::render($fName, $meta->get($fName),$field->getConfig()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public static function getUserFields():array |
| | | { |
| | | return (new self())->defaultUserFields(); |
| | | } |
| | | } |
| | |
| | | * @var bool Whether to make this a calendar type (example: events) |
| | | */ |
| | | protected bool $is_calendar = false; |
| | | protected ?MakeCalendarType $isCalendarHandler = null; |
| | | |
| | | /** |
| | | * @var bool Whether this is a before/after post type |
| | | */ |
| | |
| | | |
| | | /** Helpers **/ |
| | | protected MakeCalendarType|false $calendar = false; |
| | | protected bool $hasIntegrations = true; |
| | | protected array $integrationConfigs = []; |
| | | protected array $integrationFields = []; |
| | | protected MakeTrackChanges $trackChanges; |
| | |
| | | $this->setFields(); |
| | | |
| | | add_action('init', [$this, 'register'], 2); |
| | | add_filter('jvbDashboardPage', [$this, 'renderDashPage'], 10, 3); |
| | | if ($this->slug !== 'dash') { |
| | | add_action(BASE.'dashboard_page_'.$this->slug, [$this, 'renderDashPage'], 10, 2); |
| | | } |
| | | |
| | | } |
| | | |
| | | public static function maybeExcludeSingles(array $IDs):array |
| | |
| | | } |
| | | public function hasIntegration(string $integration):bool |
| | | { |
| | | if (!$this->hasIntegrations) { |
| | | return false; |
| | | } |
| | | return array_key_exists($integration, $this->integrationConfigs); |
| | | } |
| | | public function hasAnyIntegrations(array $integrations = []):bool |
| | | { |
| | | if (!$this->hasIntegrations) { |
| | | return false; |
| | | } |
| | | |
| | | if (empty($integrations)) { |
| | | $integrations = array_keys($this->integrationConfigs); |
| | | return !empty($integrations); |
| | |
| | | foreach ($flags as $flag) { |
| | | $this->$flag = true; |
| | | switch ($flag) { |
| | | case 'is_calendar': |
| | | $this->calendar = new MakeCalendarType($this->slug, $this); |
| | | break; |
| | | case 'is_content': |
| | | add_action('init', [$this, 'setupContent'], 20); |
| | | break; |
| | |
| | | $this->isTimelineHandler = new MakeTimelineType($this->slug); |
| | | $this->registrar->hierarchical = true; |
| | | } |
| | | if ($this->is_calendar) { |
| | | $this->isCalendarHandler = new MakeCalendarType($this->slug, $this); |
| | | } |
| | | |
| | | if (!is_null($this->rewrite_taxonomy)) { |
| | | $this->registrar->addTaxonomyRewrite($this->rewrite_taxonomy); |
| | |
| | | return $this; |
| | | } |
| | | |
| | | public function renderDashPage(string $content, string $page, string $slug):string |
| | | public function renderDashPage(string $page, string $slug):void |
| | | { |
| | | if ($slug === $this->slug) { |
| | | ob_start(); |
| | | $crud = new CRUD($slug); |
| | | $crud->render(); |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | | return $content; |
| | | $crud = new CRUD($slug); |
| | | $crud->render(); |
| | | } |
| | | |
| | | public function setupContent():void |
| | |
| | | |
| | | return [$images,$fields]; |
| | | } |
| | | |
| | | public function setHasIntegrations(bool $has = true):void |
| | | { |
| | | $this->hasIntegrations = $has; |
| | | } |
| | | } |
| | |
| | | public function setCustomer(bool $set):self |
| | | { |
| | | $this->isCustomer = $set; |
| | | // JVB()->connect($this->service_name)->addCustomerFields(); |
| | | |
| | | return $this; |
| | | } |
| | | public function isCustomer():bool |
| | |
| | | $config = $this->getConfig(); |
| | | |
| | | $class = JVB()->schemaHelper()::classFromConfig($config, $meta); |
| | | $class->setAuthor(JVB()->seo()->getCreator(true)); |
| | | if (property_exists($class, 'author')) { |
| | | $class->setAuthor(JVB()->seo()->getCreator(true)); |
| | | } |
| | | |
| | | |
| | | $class = apply_filters('jvb_single_'.$this->slug.'_schema_output', $class, $ID); |
| | | return $class->outputSchema(); |
| | |
| | | if (JVB_TESTING){ |
| | | $this->archiveCache->flush(); |
| | | } |
| | | |
| | | $this->archiveCache->flush(); |
| | | return $this->archiveCache->remember( |
| | | $this->slug, |
| | | function() { |
| | | |
| | | $action = BASE.ucfirst($this->slug).'Archive'; |
| | | $override = apply_filters($action.'Override', []); |
| | | if (!empty($override)) { |
| | | return $override; |
| | | } |
| | | $config = JVB()->schemaHelper()->archive($this->slug); |
| | | if (!array_key_exists('type', $config)) { |
| | | $config['type'] = 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage'; |
| | |
| | | ]; |
| | | $question = SchemaHelper::classFromConfig($question); |
| | | $page->setMainEntity($question); |
| | | |
| | | $page->setAuthor(JVB()->seo()->getCreator(true)); |
| | | return $page->outputSchema(); |
| | | |
| | |
| | | |
| | | public function getIntegrationFields():array |
| | | { |
| | | if ($this->registrar && $this->registrar->getType() === 'user' && $this->config->isCustomer()) { |
| | | return JVB()->connect($this->service_name)->getAdditionalFields('customer'); |
| | | } |
| | | $fields = [ |
| | | 'share_to_'.$this->service_name => [ |
| | | 'type' => 'true_false', |
| | |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Post; |
| | | use WP_Query; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | protected string $slug; |
| | | protected string $postType; |
| | | private Registrar $registrar; |
| | | protected string $hook; |
| | | public function __construct(string $slug, Registrar $registrar) { |
| | | $this->slug = $slug; |
| | | $this->postType = jvbCheckBase($slug); |
| | |
| | | add_filter('post_type_archive_link', [$this, 'handlePostTypeArchiveLinks'], 15, 2); |
| | | add_filter('query_vars', [$this, 'addQueryVars']); |
| | | add_action('init', [$this, 'addCalendarRewrites']); |
| | | |
| | | $this->hook = BASE.'mark_'.$this->slug.'_passed'; |
| | | add_action($this->hook, [$this, 'markPassed']); |
| | | } |
| | | |
| | | |
| | |
| | | ], |
| | | 'date_start' => [ |
| | | 'type' => 'date', |
| | | 'label' => __('Date', 'jvb'), |
| | | 'label' => 'Date', |
| | | ], |
| | | 'time_start' => [ |
| | | 'type' => 'time', |
| | | 'label' => __('Time Start', 'jvb'), |
| | | 'label' => 'Time Start', |
| | | ], |
| | | 'time_end' => [ |
| | | 'type' => 'time', |
| | | 'label' => __('Time End', 'jvb'), |
| | | 'label' => 'Time End', |
| | | ], |
| | | 'make_multiple' => [ |
| | | 'type' => 'true_false', |
| | |
| | | ], |
| | | 'date_end' => [ |
| | | 'type' => 'date', |
| | | 'label' => __('Date End', 'jvb'), |
| | | 'label' => 'Date End', |
| | | 'condition' => [ |
| | | 'field' => 'make_multiple', |
| | | 'operator' => '==', |
| | |
| | | ], |
| | | 'past' => [ |
| | | 'type' => 'true_false', |
| | | 'label' => __('Past Event', 'jvb'), |
| | | 'label' => 'Past Event', |
| | | 'hidden' => true, |
| | | ], |
| | | 'year' => [ |
| | | 'type' => 'number', |
| | | 'label' => __('Year', 'jvb'), |
| | | 'label' => 'Year', |
| | | 'hidden' => true, |
| | | ], |
| | | 'month' => [ |
| | | 'type' => 'number', |
| | | 'label' => __('Month', 'jvb'), |
| | | 'label' => 'Month', |
| | | 'hidden' => true, |
| | | ], |
| | | 'day' => [ |
| | | 'type' => 'number', |
| | | 'label' => __('Day', 'jvb'), |
| | | 'label' => 'Day', |
| | | 'hidden' => true, |
| | | ], |
| | | 'schedule' => [ |
| | |
| | | ], |
| | | 'max_participants' => [ |
| | | 'type' => 'number', |
| | | 'label' => __('Maximum Participants', 'jvb'), |
| | | 'label' => 'Maximum Participants', |
| | | ], |
| | | 'is_free' => [ |
| | | 'type' => 'true_false', |
| | |
| | | foreach ($values as $key => $value) { |
| | | if (!empty($value)) { |
| | | $url = str_replace("%e$key%", $value, $url); |
| | | } |
| | | } |
| | | $doubleCheck = ['eyear', 'emonth', 'eday']; |
| | | foreach ($doubleCheck as $check) { |
| | | if (str_contains($url, $check)) { |
| | | $url = str_replace("/%e$check%", '', $url); |
| | | } else { |
| | | $url = str_replace("/%e$key%", '', $url); |
| | | } |
| | | } |
| | | } |
| | |
| | | return $vars; |
| | | } |
| | | |
| | | protected function scheduleMarkPassed():void |
| | | { |
| | | if (!wp_next_scheduled($this->hook)) { |
| | | $time = strtotime('tomorrow at 12:01am'); |
| | | wp_schedule_event($time, 'daily', $this->hook); |
| | | } |
| | | } |
| | | public function markPassed():void |
| | | { |
| | | $now = date('Y-m-d'); |
| | | $items = new WP_Query([ |
| | | 'post_type' => $this->postType, |
| | | 'posts_per_page' => -1, |
| | | 'post_status' => 'publish', |
| | | 'meta_query' => [ |
| | | 'relation' => 'AND', |
| | | [ |
| | | 'key' => BASE.'past', |
| | | 'value' => '1', |
| | | 'compare'=> '!=' |
| | | ], |
| | | [ |
| | | 'key' => BASE.'date_start', |
| | | 'value' => $now, |
| | | 'compare' => '<', |
| | | 'type' => 'DATETIME' |
| | | ] |
| | | ], |
| | | 'fields' => 'ids', |
| | | ]); |
| | | |
| | | if ($items->have_posts()) { |
| | | foreach ($items->posts as $ID) { |
| | | $meta = Meta::forPost($ID); |
| | | $meta->set('past', '1'); |
| | | } |
| | | } |
| | | } |
| | | public function addCalendarRewrites():void |
| | | { |
| | | $this->scheduleMarkPassed(); |
| | | add_rewrite_tag('%eyear%', '([^&]+)'); |
| | | add_rewrite_tag('%emonth%', '([^&]+)'); |
| | | add_rewrite_tag('%eday%', '([^&]+)'); |
| | | $registrar = Registrar::getInstance($this->slug); |
| | | $base = $registrar->registrar->rewrite['slug']??$this->slug; |
| | | |
| | | // Adds the rewrite rule to capture URLs with year, month, and day. |
| | | add_rewrite_rule( |
| | | '^'.$base.'/([0-9]{4})/([0-9]{2})/([0-9]{2})/?$', |
| | | 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&emonth=$matches[2]&eday=$matches[3]', |
| | | 'top' |
| | | ); |
| | | add_rewrite_rule( |
| | | '^'.$base.'/([0-9]{4})/([0-9]{2})/([0-9]{2})/page/([0-9-]+)/?$', |
| | | 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&emonth=$matches[2]&eday=$matches[3]&paged=$matches[4]', |
| | | 'top' |
| | | ); |
| | | |
| | | // Adds the rewrite rule to capture URLs with year, month only. |
| | | add_rewrite_rule( |
| | | '^'.$base.'/([0-9]{4})/([0-9]{2})/?$', |
| | | 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&emonth=$matches[2]', |
| | | 'top' |
| | | ); |
| | | add_rewrite_rule( |
| | | '^'.$base.'/([0-9]{4})/([0-9]{2})/page/([0-9-]+)/?$', |
| | | 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&emonth=$matches[2]&paged=$matches[3]', |
| | | 'top' |
| | | ); |
| | | |
| | | // Adds the rewrite rule to capture URLs with year only. |
| | | add_rewrite_rule( |
| | | '^'.$base.'/([0-9]{4})/?$', |
| | | 'index.php?post_type='.$this->postType.'&eyear=$matches[1]', |
| | | 'top' |
| | | ); |
| | | add_rewrite_rule( |
| | | '^'.$base.'/([0-9]{4})/page/([0-9-]+)/?$', |
| | | 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&paged=$matches[2]', |
| | | 'top' |
| | | ); |
| | | |
| | | // Adds the rewrite rule for all events |
| | | add_rewrite_rule( |
| | | '^'.$base.'/?$', |
| | | 'index.php?post_type='.$this->postType, |
| | | 'top' |
| | | ); |
| | | add_rewrite_rule( |
| | | '^'.$base.'/page/([0-9-]+)/?$', |
| | | 'index.php?post_type='.$this->postType.'&paged=$matches[1]', |
| | | 'top' |
| | | ); |
| | | |
| | | add_rewrite_rule( |
| | | "^{$base}/([^/]+)/?$", |
| | | "index.php?post_type={$this->postType}&name=\$matches[1]", |
| | | 'top' |
| | | ); |
| | | |
| | | } |
| | | } |
| | |
| | | $this->cache = Cache::for($this->cacheName, $this->cacheTtl); |
| | | } |
| | | |
| | | add_action('rest_api_init', [$this, 'registerRoutes']); |
| | | add_action('rest_api_init', [$this, 'registerRoutes'], 10); |
| | | add_action('rest_api_init', [$this, 'ensureDefinitions'], 9); |
| | | } |
| | | |
| | | public function ensureDefinitions():void |
| | | { |
| | | Site::getInstance(); |
| | | Registrar::ensureInstanced(); |
| | | } |
| | | /** |
| | | * Register routes - implement using Route builder |
| | | */ |
| | |
| | | //if (Site::has('feed_block')) { |
| | | require(JVB_DIR . '/inc/rest/routes/FeedRoutes.php'); |
| | | //} |
| | | //if (Site::has('magicLink')) { |
| | | //if (Site::has('magic_link')) { |
| | | // require(JVB_DIR . '/inc/rest/routes/MagicLinkRoutes.php'); |
| | | //} |
| | | //if (Site::has('favourites')) { |
| | |
| | | { |
| | | $data = $request->get_json_params(); |
| | | |
| | | |
| | | // Generate idempotency key from cart_id + timestamp |
| | | // This ensures retries use SAME key |
| | | $cart_id = $data['cart_id'] ?? ''; |
| | |
| | | public function getSavedCards(WP_REST_Request $request):WP_REST_Response |
| | | { |
| | | $data = $request->get_params(); |
| | | error_log('Getting Saved Cards: '.print_r($data, true)); |
| | | $user_id = absint($data['user']??0); |
| | | if ($user_id === 0) { |
| | | return $this->validationError(['message' => 'Not logged in']); |
| | | return $this->success(['cards' => []]); |
| | | } |
| | | |
| | | $square = JVB()->connect('square'); |
| | |
| | | $this->cacheTtl = WEEK_IN_SECONDS; |
| | | |
| | | parent::__construct(); |
| | | $this->hasMagicLink = Site::has('magic_link'); |
| | | |
| | | $this->hasMagicLink = Site::has('magicLink'); |
| | | } |
| | | |
| | | public function registerRoutes(): void |
| | |
| | | ->register(); |
| | | |
| | | // Magic link endpoint |
| | | if ($this->hasMagicLink) { |
| | | |
| | | if (Site::has('magic_link')) { |
| | | Route::for('auth/magic') |
| | | ->post([$this, 'handleMagicLink']) |
| | | ->args([ |
| | |
| | | */ |
| | | public function handleMagicLink(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | if (!$this->hasMagicLink) { |
| | | |
| | | if (!Site::has('magic_link')) { |
| | | return $this->error( |
| | | 'Magic link authentication is not enabled.', |
| | | 'feature_disabled', |
| | |
| | | $dataIgnore = $this->useCRUDjs ? '' : ' data-ignore'; |
| | | ?> |
| | | <section class="items-list <?= esc_attr($this->dataType) ?> crud" data-content="<?= esc_attr($this->dataType) ?>" data-singular="<?=$this->singular?>" data-plural="<?=$this->plural?>" data-view="<?= $this->defaultView?>"<?=$dataIgnore?>> |
| | | <div class="wrap"> |
| | | <div id="item-wrap"> |
| | | <?php |
| | | $this->renderControlsAndFilters(); |
| | | |
| | |
| | | ?> |
| | | <template class="contentTable"> |
| | | <form class="table" |
| | | <!-- data-save="content"--> |
| | | data-content="<?= esc_attr($this->dataType) ?>" |
| | | data-form-id="content-table-<?= esc_attr($this->dataType) ?>" |
| | | <?= $permissions?>> |
| | |
| | | <?= $this->renderItemSelect() ?> |
| | | </td> |
| | | <?php |
| | | if (array_key_exists('status', $this->fields)){ |
| | | if (array_key_exists('post_status', $this->fields)){ |
| | | ?> |
| | | <td class="status" data-field="post_status"> |
| | | <td class="post_status" data-field="post_status" data-field-type="radio"> |
| | | <?= $this->renderStatusRadios() ?> |
| | | </td> |
| | | <?php |
| | |
| | | 'radio' |
| | | ]; |
| | | foreach ($this->fields as $name => $config): |
| | | if (array_key_exists('hidden', $config) || $name === 'status'){ |
| | | if ((array_key_exists('hidden', $config) && $config['hidden'] === true) || $name === 'post_status'){ |
| | | continue; |
| | | } |
| | | $makeThisDetailed = (in_array($config['type'], $makeDetails)); |
| | |
| | | ?> |
| | | <template class="contentTable"> |
| | | <form class="table" |
| | | <!-- data-save="content"--> |
| | | data-content="<?= esc_attr($this->dataType) ?>" |
| | | data-form-id="content-table-<?= esc_attr($this->dataType) ?>"> |
| | | <?= jvbFormStatus() ?> |
| | |
| | | </td> |
| | | <?php |
| | | foreach ($this->fields as $name => $config) { |
| | | if(array_key_exists('hidden', $config) || $name === 'post_status') { |
| | | if((array_key_exists('hidden', $config) && $config['hidden'] === true) || $name === 'post_status') { |
| | | continue; |
| | | } |
| | | if (!in_array($name, $this->timelineUniqueFields)) { |
| | |
| | | * Render table header |
| | | */ |
| | | protected function renderTableHeader(): string { |
| | | ob_start(); |
| | | |
| | | ?> |
| | | <tr> |
| | | $out = '<tr> |
| | | <th scope="col" class="select-header"> |
| | | <input type="checkbox" id="select-all" name="select-all"> |
| | | <label for="select-all">All</label> |
| | | </th> |
| | | <?php if (array_key_exists('status', $this->fields)) { ?> |
| | | <th scope="col" class="status-header">Status</th> |
| | | <?php } ?> |
| | | <?php foreach ($this->fields as $name => $config): |
| | | if (array_key_exists('hidden', $config) || $name === 'status'){ |
| | | continue; |
| | | } |
| | | ?> |
| | | <th scope="col" class="show-<?= esc_attr($name) ?>"<?= (in_array($name, $this->stuck)) ? ' data-stuck':''?>> |
| | | <?= esc_html($config['label']) ?> |
| | | </th> |
| | | <?php endforeach; |
| | | if (!empty($this->itemActions)) { |
| | | ?> |
| | | <th scope="col" class="show-actions"> |
| | | Actions |
| | | </th> |
| | | <?php |
| | | } |
| | | ?> |
| | | </th>'; |
| | | |
| | | </tr> |
| | | <?php |
| | | return ob_get_clean(); |
| | | if (array_key_exists('post_status', $this->fields)){ |
| | | $out .= '<th scope="col" class="status-header">Status</th>'; |
| | | } |
| | | |
| | | foreach ($this->fields as $name => $config) { |
| | | if ($name === 'post_status' || (array_key_exists('hidden', $config) && $config['hidden'] === true)) { |
| | | continue; |
| | | } |
| | | $out .= sprintf( |
| | | '<th scope="col" class="show-%s"%s>%s</th>', |
| | | esc_attr($name), |
| | | in_array($name, $this->stuck) ? ' data-stuck' : '', |
| | | esc_html($config['label']) |
| | | ); |
| | | } |
| | | |
| | | if (!empty($this->itemActions)) { |
| | | $out .= '<th scope="col" class="show-actions">Actions</th>'; |
| | | } |
| | | $out .= '</tr>'; |
| | | |
| | | return $out; |
| | | } |
| | | |
| | | protected function renderTimelineTableHeader(): string { |
| | | ob_start(); |
| | | |
| | | ?> |
| | | <tr> |
| | | $out = '<tr> |
| | | <th scope="col" class="select-header"> |
| | | <input type="checkbox" id="select-all" name="select-all"> |
| | | <label for="select-all">All</label> |
| | | </th> |
| | | <th scope="col" class="show-post_status"> |
| | | Status |
| | | </th> |
| | | <?php foreach ($this->fields as $name => $config): |
| | | if (array_key_exists('hidden', $config) || $name === 'post_status'){ |
| | | continue; |
| | | } |
| | | ?> |
| | | <th scope="col" class="show-<?= esc_attr($name) ?>"<?= (in_array($name, $this->stuck)) ? ' data-stuck':''?>> |
| | | <?= esc_html($config['label']) ?> |
| | | </th> |
| | | <?php endforeach; ?> |
| | | </tr> |
| | | <?php |
| | | return ob_get_clean(); |
| | | </th>'; |
| | | |
| | | foreach ($this->fields as $name => $config) { |
| | | if ($name === 'post_status' || (array_key_exists('hidden', $config) && $config['hidden'] === true)) { |
| | | continue; |
| | | } |
| | | $out .= sprintf( |
| | | '<th scope="col" class="show-%s"%s>%s</th>th>', |
| | | esc_attr($name), |
| | | in_array($name, $this->stuck) ? ' data-stuck':'', |
| | | esc_html($config['label']) |
| | | ); |
| | | } |
| | | $out .= '</tr>'; |
| | | return $out; |
| | | } |
| | | |
| | | /** |
| | |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | | protected function renderStatusRadios(): string { |
| | | ob_start(); |
| | | ?> |
| | | <div class="radio-options status-options row" data-field="post_status" data-field-type="radio"> |
| | | <?php foreach ($this->statuses as $status): |
| | | if ($status === 'all') continue; |
| | | if (!array_key_exists($status, $this->allowedStatuses)) continue; |
| | | $config = $this->allowedStatuses[$status]; |
| | | ?> |
| | | protected function renderStatusRadios(): string |
| | | { |
| | | $out = '<div class="radio-options status-options row">'; |
| | | foreach ($this->statuses as $status) { |
| | | if ($status === 'all' || !array_key_exists($status, $this->allowedStatuses)) continue; |
| | | $config = $this->allowedStatuses[$status]; |
| | | |
| | | <input type="radio" |
| | | $out .= sprintf( |
| | | '<input type="radio" |
| | | name="post_status" |
| | | id="status-<?= esc_attr($status) ?>" |
| | | value="<?= esc_attr($status) ?>"> |
| | | <label for="status-<?= esc_attr($status) ?>"> |
| | | <?= jvbDashIcon($config['icon']) ?> |
| | | <span class="screen-reader-text"><?= esc_html($config['label']) ?></span> |
| | | </label> |
| | | <?php endforeach; ?> |
| | | <span class="validation-message" hidden role="alert"></span> |
| | | </div> |
| | | <?php |
| | | return ob_get_clean(); |
| | | id="post_status-%s" |
| | | class="btn" |
| | | value="%s"> |
| | | <label for="post_status-%s" title="%s"> |
| | | %s |
| | | <span class="screen-reader-text">%s</span> |
| | | </label>', |
| | | esc_attr($status), |
| | | esc_attr($status), |
| | | esc_attr($status), |
| | | esc_html($config['label']), |
| | | jvbDashIcon($config['icon']), |
| | | esc_html($config['label']) |
| | | ); |
| | | } |
| | | $out .= '<span class="validation-message" hidden role="alert"></span> |
| | | </div>'; |
| | | |
| | | return $out; |
| | | } |
| | | |
| | | |
| | |
| | | protected function editForm():string |
| | | { |
| | | ob_start(); |
| | | /* <form class="edit-form" data-save="content" data-form-id="edit-<?=$this->dataType?>" data-autosave<?= ($this->isTimeline) ? ' data-timeline' : ''*/ |
| | | ?> |
| | | <!-- <form class="edit-form" data-save="content" data-form-id="edit---><?php //=$this->dataType?><!--" data-autosave--><?php //= ($this->isTimeline) ? ' data-timeline' : ''?><!-->--> |
| | | |
| | | <form class="edit-form" data-form-id="edit-<?=$this->dataType?>" data-autosave<?= ($this->isTimeline) ? ' data-timeline' : ''?>> |
| | | <?= jvbFormStatus() ?> |
| | | <input type="hidden" name="form-id" value="<?=uniqid('new-')?>" /> |
| | |
| | | { |
| | | if (empty($this->bulkActions)) return; |
| | | ob_start(); |
| | | /* <form class="bulk-edit-form" data-save="content" data-form-id="bulk-edit-<?=$this->dataType?>">*/ |
| | | ?> |
| | | <!-- <form class="bulk-edit-form" data-save="content" data-form-id="bulk-edit---><?php //=$this->dataType?><!--">--> |
| | | |
| | | <form class="bulk-edit-form" data-form-id="bulk-edit-<?=$this->dataType?>"> |
| | | <?= jvbFormStatus() ?> |
| | | <div class="selected"></div> |
| | |
| | | use JVBase\base\Site; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\meta\Form; |
| | | use JVBase\meta\Meta; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | |
| | | $providerName = strtolower($provider->getServiceName()); |
| | | |
| | | $form = sprintf( |
| | | '<aside id="cart" class="right main"><div class="wrap"> |
| | | <form id="checkout" data-form-id="checkout" data-save="checkout" data-provider="%s">', |
| | | esc_attr($providerName) |
| | | ); |
| | | $aside = '<aside id="cart" class="right main"><div class="wrap">'; |
| | | $tabs = new Tabs(); |
| | | $tabs->addTab('cartItems') |
| | | ->title('Your Cart') |
| | | ->icon('shopping-cart-simple') |
| | | ->description('Here\'s your order. You can change quantities, remove items, or clear your cart. To checkout, click the checkout tab.') |
| | | ->content(self::cartContent($providerName)); |
| | | |
| | | $tabs = [ |
| | | 'cartItems' => [ |
| | | 'title' => 'Your Order', |
| | | 'icon' => 'cart', |
| | | 'description' => 'Here\'s your order. You can change quantities, remove items, or clear your cart.', |
| | | 'content' => self::cartContent(), |
| | | ], |
| | | 'checkout' => [ |
| | | 'title' => 'Checkout', |
| | | 'icon' => 'checkout', |
| | | 'description' => apply_filters('jvb_checkout_description', |
| | | 'Securely checkout with your name, email, and payment.', |
| | | $providerName |
| | | ), |
| | | 'content' => self::checkoutContent($providerName), |
| | | ], |
| | | 'order' => [ |
| | | 'title' => 'Your Order', |
| | | 'icon' => 'truck', |
| | | 'hidden' => true, |
| | | 'description' => '', |
| | | 'content' => self::orderStatus(), |
| | | ], |
| | | ]; |
| | | if (!is_user_logged_in()) { |
| | | $tabs->addTab('thelogin') |
| | | ->title('Login') |
| | | ->description('Login to see past orders and access your saved cards') |
| | | ->content(JVB()->magicLink()->renderForm()); |
| | | } else { |
| | | $tabs->addTab('account') |
| | | ->title('Account') |
| | | ->description('Manage your contact information, saved cards, and see past orders') |
| | | ->content(self::renderAccount()); |
| | | } |
| | | |
| | | $form .= jvbRenderTabs($tabs, true); |
| | | $tabs->addTab('checkout') |
| | | ->title('Checkout') |
| | | ->icon('hand-coins') |
| | | ->description(apply_filters('jvb_checkout_description', |
| | | 'Securely checkout with your name, email, and payment.', $providerName)) |
| | | ->content(self::checkoutContent($providerName)); |
| | | |
| | | $form .= '<div class="cart-total row right"> |
| | | <p class="tax">Tax: <span></span></p> |
| | | <p class="total">GRAND TOTAL: <span></span></p> |
| | | </div> |
| | | </form> |
| | | </div> |
| | | $tabs->addTab('order') |
| | | ->title('Your Order') |
| | | ->icon('truck') |
| | | ->hidden() |
| | | ->content(self::orderStatus()); |
| | | |
| | | $aside .= $tabs->render(); |
| | | |
| | | |
| | | |
| | | $aside .= '</div> |
| | | </aside>'; |
| | | |
| | | $form .= self::templates($providerName); |
| | | $aside .= self::templates($providerName); |
| | | |
| | | $actions[] = [ |
| | | 'button' => self::toggleButton(), |
| | | 'content' => $form, |
| | | 'content' => $aside, |
| | | ]; |
| | | |
| | | return $actions; |
| | |
| | | */ |
| | | private static function checkoutContent(string $provider): string |
| | | { |
| | | $fields = '<div class="checkout-section"> |
| | | <h3>Customer Information</h3>' |
| | | . Form::render('cart_name', null, [ |
| | | $name = $email = $phone = ''; |
| | | if (is_user_logged_in()) { |
| | | $meta = Meta::forUser(get_current_user_id()); |
| | | if ($meta) { |
| | | $name = $meta->get('display_name'); |
| | | $email = $meta->get('user_email'); |
| | | $phone = $meta->get('phone'); |
| | | } |
| | | |
| | | } |
| | | $form = sprintf( |
| | | '<form id="checkout" data-handled> |
| | | <h3>Your Information</h3> |
| | | %s%s%s', |
| | | Form::render('cart_name', $name, [ |
| | | 'type' => 'text', |
| | | 'label' => 'Your Name', |
| | | 'required' => true, |
| | | 'autocomplete' => 'name', |
| | | ]) |
| | | . Form::render('cart_email', null, [ |
| | | ]), |
| | | Form::render('cart_email', $email, [ |
| | | 'type' => 'email', |
| | | 'label' => 'Your Email', |
| | | 'required' => true, |
| | | 'autocomplete' => 'email', |
| | | ]) |
| | | . Form::render('cart_phone', null, [ |
| | | ]), |
| | | Form::render('cart_phone', $phone, [ |
| | | 'type' => 'tel', |
| | | 'label' => 'Your Phone', |
| | | 'required' => true, |
| | | 'autocomplete' => 'phone', |
| | | ]); |
| | | ]) |
| | | ); |
| | | |
| | | |
| | | // Optional sections — integrations can add pickup, scheduling, etc. |
| | | $fields .= apply_filters('jvb_checkout_fields', '', $provider); |
| | | $form .= apply_filters('jvb_checkout_fields', '', $provider); |
| | | |
| | | $fields .= '</div>'; |
| | | |
| | | |
| | | // Payment section — provider JS mounts its own UI inside #payment-container |
| | | $fields .= '<div class="checkout-section"> |
| | | $form .= '<div class="checkout-section"> |
| | | <h3>Payment Information</h3> |
| | | <div id="saved-cards"></div> |
| | | <div id="payment-container" data-provider="' . esc_attr($provider) . '"></div> |
| | | </div>'; |
| | | </div> |
| | | </form>'; |
| | | |
| | | return $fields; |
| | | return $form; |
| | | } |
| | | |
| | | /** |
| | | * Cart items tab: table + account details |
| | | */ |
| | | private static function cartContent(): string |
| | | private static function cartContent(string $provider): string |
| | | { |
| | | ob_start(); |
| | | ?> |
| | | <div class="cart-items"> |
| | | $content = sprintf('<form id="checkout" data-form-id="checkout" data-save="checkout" data-provider="%s">', |
| | | esc_attr($provider) |
| | | ); |
| | | $content.= '<div class="cart-items"> |
| | | <table> |
| | | <thead> |
| | | <tr> |
| | |
| | | </tfoot> |
| | | </table> |
| | | </div> |
| | | |
| | | <details class="account"> |
| | | <summary> |
| | | <?php |
| | | if (is_user_logged_in()) { |
| | | echo 'Your Favourites and Order History'; |
| | | } else { |
| | | echo '<a href="' . wp_login_url(get_the_permalink()) . '">Log in</a> to save your favourites and view order history.'; |
| | | } |
| | | ?> |
| | | </summary> |
| | | <?php |
| | | if (is_user_logged_in()) { |
| | | $tabs = [ |
| | | 'history' => [ |
| | | 'title' => 'Order History', |
| | | 'icon' => 'checkout', |
| | | 'description' => 'View your past orders and quickly reorder', |
| | | 'content' => apply_filters('jvb_checkout_order_history', ''), |
| | | ], |
| | | 'favourites' => [ |
| | | 'title' => 'Favourites', |
| | | 'icon' => 'heart', |
| | | 'description' => 'View your favourites', |
| | | 'content' => apply_filters('jvb_checkout_favourites', ''), |
| | | ], |
| | | ]; |
| | | jvbRenderTabs($tabs); |
| | | } |
| | | ?> |
| | | </details> |
| | | <?php |
| | | return ob_get_clean(); |
| | | </form>'; |
| | | return $content; |
| | | } |
| | | |
| | | /** |
| | |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | | private static function renderAccount():string |
| | | { |
| | | return ''; |
| | | } |
| | | |
| | | /***************************************************************** |
| | | * TEMPLATES — cloned by JS at runtime |
| | | *****************************************************************/ |
| | |
| | | //add_action('wp_head', 'jvbDumpIt'); |
| | | function jvbDumpIt() |
| | | { |
| | | |
| | | // jvbDump(Registrar::getInstance('foodie')->getFields()); |
| | | } |
| | | |
| | | add_action('after_setup_theme', 'jvbImageSize'); |
| | |
| | | position: absolute; |
| | | left: var(--offScreen); |
| | | } |
| | | |
| | | .menu-item { |
| | | .header { |
| | | flex-wrap:nowrap; |
| | | |
| | | h3, p { |
| | | margin: 0; |
| | | width: max-content; |
| | | nav.menu { |
| | | max-width: 100%; |
| | | ul { |
| | | --justify: flex-start; |
| | | li { |
| | | padding: 0 .5rem; |
| | | } |
| | | } |
| | | } |
| | | main { |
| | | > header { |
| | | grid-column: full; |
| | | |
| | | h1 { |
| | | max-width: var(--wide); |
| | | } |
| | | } |
| | | |
| | | > section * { |
| | | max-width: var(--wide); |
| | | } |
| | | } |
| | | |
| | | .menu-item.menu-item { |
| | | margin: var(--sp2); |
| | | max-width: none; |
| | | +.menu-item { |
| | | padding-top: var(--sp4); |
| | | border-top: 1px solid rgb(var(--base-200)); |
| | | } |
| | | |
| | | .header { |
| | | flex-wrap:nowrap; |
| | | |
| | | h3, p { |
| | | margin: 0; |
| | | width: max-content; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | { |
| | | "$schema": "https://schemas.wp.org/trunk/block.json", |
| | | "apiVersion": 3, |
| | | "name": "jvb/simple-calendar", |
| | | "version": "0.1.0", |
| | | "title": "Simple Calendar", |
| | | "category": "jvb", |
| | | "icon": "calendar-alt", |
| | | "description": "Simple Calendar", |
| | | "example": {}, |
| | | "supports": { |
| | | "html": false, |
| | | "align": ["wide", "full"] |
| | | }, |
| | | "textdomain": "jvb", |
| | | "selectors": { |
| | | "root": ".simple-calendar" |
| | | }, |
| | | "editorScript": "file:./index.js", |
| | | "editorStyle": "file:./index.css", |
| | | "style": "file:./style-index.css", |
| | | "render": "file:./render.php", |
| | | "viewScript": "file:./view.js" |
| | | } |
| New file |
| | |
| | | /** |
| | | * Retrieves the translation of text. |
| | | * |
| | | * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/ |
| | | */ |
| | | import { __ } from '@wordpress/i18n'; |
| | | |
| | | /** |
| | | * React hook that is used to mark the block wrapper element. |
| | | * It provides all the necessary props like the class name. |
| | | * |
| | | * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops |
| | | */ |
| | | import { useBlockProps } from '@wordpress/block-editor'; |
| | | |
| | | /** |
| | | * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files. |
| | | * Those files can contain any CSS code that gets applied to the editor. |
| | | * |
| | | * @see https://www.npmjs.com/package/@wordpress/scripts#using-css |
| | | */ |
| | | import './editor.scss'; |
| | | |
| | | /** |
| | | * The edit function describes the structure of your block in the context of the |
| | | * editor. This represents what the editor will render when the block is used. |
| | | * |
| | | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit |
| | | * |
| | | * @return {Element} Element to render. |
| | | */ |
| | | export default function Edit() { |
| | | return ( |
| | | <p { ...useBlockProps() }> |
| | | { __( 'Will output the simple calendar', 'jvb' ) } |
| | | </p> |
| | | ); |
| | | } |
| New file |
| | |
| | | /** |
| | | * Registers a new block provided a unique name and an object defining its behavior. |
| | | * |
| | | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ |
| | | */ |
| | | import { registerBlockType } from '@wordpress/blocks'; |
| | | |
| | | /** |
| | | * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files. |
| | | * All files containing `style` keyword are bundled together. The code used |
| | | * gets applied both to the front of your site and to the editor. |
| | | * |
| | | * @see https://www.npmjs.com/package/@wordpress/scripts#using-css |
| | | */ |
| | | import './style.scss'; |
| | | |
| | | /** |
| | | * Internal dependencies |
| | | */ |
| | | import Edit from './edit'; |
| | | import metadata from './block.json'; |
| | | |
| | | /** |
| | | * Every block starts by registering a new block type definition. |
| | | * |
| | | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ |
| | | */ |
| | | registerBlockType( metadata.name, { |
| | | /** |
| | | * @see ./edit.js |
| | | */ |
| | | edit: Edit, |
| | | } ); |
| New file |
| | |
| | | p.empty { |
| | | text-align: center; |
| | | font-style: italic; |
| | | background-color: rgb(var(--base)); |
| | | border: 2px dashed rgba(var(--base-200)); |
| | | border-radius: var(--radius); |
| | | } |
| | | |
| | | details { |
| | | margin: 2rem 0; |
| | | h2 { |
| | | margin: 0; |
| | | } |
| | | } |
| New file |
| | |
| | | window.details = document.querySelectorAll('details'); |
| | | window.toggles = document.querySelectorAll('.toggle-details'); |
| | | |
| | | document.addEventListener('click', (e) => { |
| | | if (e.target.classList.contains('toggle-details')) { |
| | | e.target.classList.toggle('open'); |
| | | let on = e.target.classList.contains('open'); |
| | | let section = e.target.dataset.toggle; |
| | | if (section === 'all') { |
| | | toggleToggles(on); |
| | | } |
| | | |
| | | let span = e.target.querySelector('span'); |
| | | span.textContent = (on) ? 'Close': 'Open'; |
| | | toggleDetails(section, on); |
| | | } |
| | | }); |
| | | |
| | | |
| | | console.log(window.details); |
| | | function toggleDetails(name, toggle) { |
| | | if (name === 'all') { |
| | | console.log('Toggling all!'); |
| | | window.details.forEach(detail => { |
| | | console.log(detail); |
| | | detail.open = toggle; |
| | | }); |
| | | } else { |
| | | for (let detail of window.details) { |
| | | if (detail.dataset.section === name) { |
| | | detail.open = toggle; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | function toggleToggles(on) { |
| | | window.toggles.forEach(toggle => { |
| | | if (toggle.dataset.toggle !== 'all') { |
| | | toggle.querySelector('span').textContent = (on) ? 'Close' : 'Open'; |
| | | } |
| | | }); |
| | | } |
| | |
| | | 'maps': './assets/js/concise/GoogleMaps.js', |
| | | 'handleSelection': './assets/js/concise/HandleSelection.js', |
| | | 'integrations': './assets/js/concise/Integrations.js', |
| | | 'login': './assets/js/concise/Login.js', |
| | | 'modal': './assets/js/concise/Modal.js', |
| | | 'navigation': './assets/js/concise/navigation.js', |
| | | 'news': './assets/js/concise/NewsManager.js', |