admin.js
2 weeks ago
admin.min.js
2 weeks ago
beacon-clicked.js
2 weeks ago
beacon-clicked.min.js
2 weeks ago
contextual-info.js
2 weeks ago
contextual-info.min.js
2 weeks ago
duplicate-methods.js
2 weeks ago
duplicate-methods.min.js
2 weeks ago
new-rules-table-popup.js
2 weeks ago
onboarding.js
2 weeks ago
rules-settings.js
2 weeks ago
shipping-method-block-checkout.js
2 weeks ago
shipping-method-block-checkout.min.js
2 weeks ago
shipping-method-block-checkout.js
312 lines
| 1 | ( function() { |
| 2 | const config = window.__fsShippingMethodLogoBlocks || {}; |
| 3 | |
| 4 | const ensureArray = ( value ) => ( Array.isArray( value ) ? value : [] ); |
| 5 | |
| 6 | const getMetaValue = ( metaData, key ) => { |
| 7 | if ( ! Array.isArray( metaData ) ) { |
| 8 | return null; |
| 9 | } |
| 10 | |
| 11 | const item = metaData.find( ( entry ) => entry && entry.key === key ); |
| 12 | return item ? item.value : null; |
| 13 | }; |
| 14 | |
| 15 | const decodeBase64 = ( value ) => { |
| 16 | if ( ! value || 'string' !== typeof value ) { |
| 17 | return ''; |
| 18 | } |
| 19 | |
| 20 | try { |
| 21 | return window.atob( value ); |
| 22 | } catch ( error ) { |
| 23 | return ''; |
| 24 | } |
| 25 | }; |
| 26 | |
| 27 | const getRateDescription = ( rate ) => { |
| 28 | if ( ! rate ) { |
| 29 | return ''; |
| 30 | } |
| 31 | |
| 32 | const encodedDescription = getMetaValue( rate.meta_data, config.description_encoded_key || 'description_base64encoded' ); |
| 33 | if ( encodedDescription ) { |
| 34 | const decodedDescription = decodeBase64( encodedDescription ); |
| 35 | if ( decodedDescription ) { |
| 36 | return decodedDescription; |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | return getMetaValue( rate.meta_data, config.description_key || 'description' ) || ''; |
| 41 | }; |
| 42 | |
| 43 | const getNormalizedText = ( value ) => ( value || '' ).replace( /\s+/g, ' ' ).trim(); |
| 44 | |
| 45 | const getNormalizedHtmlText = ( value ) => { |
| 46 | const wrapper = document.createElement( 'div' ); |
| 47 | wrapper.innerHTML = value || ''; |
| 48 | |
| 49 | return getNormalizedText( wrapper.textContent || '' ); |
| 50 | }; |
| 51 | |
| 52 | const isVisible = ( element ) => { |
| 53 | let current = element; |
| 54 | |
| 55 | while ( current && current !== document.body ) { |
| 56 | const style = window.getComputedStyle( current ); |
| 57 | |
| 58 | if ( 'none' === style.display || 'hidden' === style.visibility ) { |
| 59 | return false; |
| 60 | } |
| 61 | |
| 62 | current = current.parentElement; |
| 63 | } |
| 64 | |
| 65 | return Boolean( element ); |
| 66 | }; |
| 67 | |
| 68 | const descriptionMatchesElement = ( description, element ) => { |
| 69 | if ( ! description || ! isVisible( element ) ) { |
| 70 | return false; |
| 71 | } |
| 72 | |
| 73 | const descriptionText = getNormalizedHtmlText( description ); |
| 74 | |
| 75 | return '' !== descriptionText && getNormalizedText( element.textContent || '' ).includes( descriptionText ); |
| 76 | }; |
| 77 | |
| 78 | const getRateId = ( rate ) => rate?.rate_id || rate?.id || ''; |
| 79 | |
| 80 | const isFlexibleShippingRate = ( rate ) => { |
| 81 | if ( ! rate ) { |
| 82 | return false; |
| 83 | } |
| 84 | |
| 85 | return ensureArray( config.method_ids ).includes( rate.method_id ); |
| 86 | }; |
| 87 | |
| 88 | const escapeCssValue = ( value ) => { |
| 89 | if ( window.CSS && 'function' === typeof window.CSS.escape ) { |
| 90 | return window.CSS.escape( value ); |
| 91 | } |
| 92 | |
| 93 | return value.replace( /["\\]/g, '\\$&' ); |
| 94 | }; |
| 95 | |
| 96 | const getOptionForRate = ( rateId ) => { |
| 97 | if ( ! rateId ) { |
| 98 | return null; |
| 99 | } |
| 100 | |
| 101 | const selector = `.wc-block-components-radio-control__option input[value="${ escapeCssValue( rateId ) }"]`; |
| 102 | const input = document.querySelector( selector ); |
| 103 | |
| 104 | return input ? input.closest( '.wc-block-components-radio-control__option' ) : null; |
| 105 | }; |
| 106 | |
| 107 | const findDescriptionElement = ( option, input ) => { |
| 108 | const selector = '[id$="__secondary-description"], [id$="__description"]'; |
| 109 | |
| 110 | if ( input ) { |
| 111 | const describedBy = input.getAttribute( 'aria-describedby' ); |
| 112 | if ( describedBy ) { |
| 113 | const descriptionIds = describedBy.split( /\s+/ ).filter( Boolean ); |
| 114 | |
| 115 | for ( const id of descriptionIds ) { |
| 116 | const element = document.getElementById( id ); |
| 117 | if ( element && element.matches( selector ) ) { |
| 118 | return element; |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | return option ? option.querySelector( selector ) : null; |
| 125 | }; |
| 126 | |
| 127 | const getInsertionAnchor = ( option, input ) => { |
| 128 | if ( ! option ) { |
| 129 | return null; |
| 130 | } |
| 131 | |
| 132 | const labelGroup = option.querySelector( '.wc-block-components-radio-control__label-group' ); |
| 133 | if ( labelGroup ) { |
| 134 | return labelGroup; |
| 135 | } |
| 136 | |
| 137 | return findDescriptionElement( option, input ) || option.querySelector( 'label' ) || option; |
| 138 | }; |
| 139 | |
| 140 | const createLogoNode = ( logoUrl, logoAlt ) => { |
| 141 | const wrapper = document.createElement( 'span' ); |
| 142 | wrapper.classList.add( config.wrapper_class || 'flexible-shipping-method-logo-block' ); |
| 143 | wrapper.style.display = 'block'; |
| 144 | wrapper.style.marginTop = '6px'; |
| 145 | |
| 146 | const image = document.createElement( 'img' ); |
| 147 | image.src = logoUrl; |
| 148 | image.alt = logoAlt || ''; |
| 149 | image.style.maxHeight = '40px'; |
| 150 | image.style.width = 'auto'; |
| 151 | image.loading = 'lazy'; |
| 152 | |
| 153 | wrapper.appendChild( image ); |
| 154 | |
| 155 | return wrapper; |
| 156 | }; |
| 157 | |
| 158 | const createDescriptionNode = ( description ) => { |
| 159 | const wrapper = document.createElement( 'div' ); |
| 160 | wrapper.className = config.description_class || 'shipping-method-description flexible-shipping-method-description-block wc-block-components-radio-control__secondary-description'; |
| 161 | wrapper.innerHTML = description; |
| 162 | |
| 163 | return wrapper; |
| 164 | }; |
| 165 | |
| 166 | const getRatesFromCart = ( cartData ) => { |
| 167 | if ( ! cartData || ! Array.isArray( cartData.shippingRates ) ) { |
| 168 | return []; |
| 169 | } |
| 170 | |
| 171 | const rates = []; |
| 172 | |
| 173 | cartData.shippingRates.forEach( ( shippingPackage ) => { |
| 174 | const packageRates = shippingPackage?.shipping_rates ?? []; |
| 175 | packageRates.forEach( ( rate ) => rates.push( rate ) ); |
| 176 | } ); |
| 177 | |
| 178 | return rates; |
| 179 | }; |
| 180 | |
| 181 | const renderLogos = () => { |
| 182 | if ( ! window.wp || ! window.wp.data || 'function' !== typeof window.wp.data.select ) { |
| 183 | return; |
| 184 | } |
| 185 | |
| 186 | const cartStore = window.wp.data.select( 'wc/store/cart' ); |
| 187 | if ( ! cartStore || 'function' !== typeof cartStore.getCartData ) { |
| 188 | return; |
| 189 | } |
| 190 | |
| 191 | const rates = getRatesFromCart( cartStore.getCartData() ); |
| 192 | if ( ! rates.length ) { |
| 193 | return; |
| 194 | } |
| 195 | |
| 196 | rates.forEach( ( rate ) => { |
| 197 | if ( ! isFlexibleShippingRate( rate ) ) { |
| 198 | return; |
| 199 | } |
| 200 | |
| 201 | const description = getRateDescription( rate ); |
| 202 | const logoUrl = getMetaValue( rate.meta_data, config.logo_url_key || 'method_logo_url' ); |
| 203 | const option = getOptionForRate( getRateId( rate ) ); |
| 204 | if ( ! option ) { |
| 205 | return; |
| 206 | } |
| 207 | |
| 208 | const wrapperClass = config.wrapper_class || 'flexible-shipping-method-logo-block'; |
| 209 | const descriptionClass = ( config.description_class || 'shipping-method-description flexible-shipping-method-description-block wc-block-components-radio-control__secondary-description' ) |
| 210 | .split( ' ' ) |
| 211 | .filter( Boolean ) |
| 212 | .map( ( className ) => `.${ className }` ) |
| 213 | .join( '' ); |
| 214 | const existingDescription = descriptionClass ? option.querySelector( descriptionClass ) : null; |
| 215 | const existingLogo = option.querySelector( `.${ wrapperClass }` ); |
| 216 | const input = option.querySelector( 'input' ); |
| 217 | const nativeDescription = findDescriptionElement( option, input ); |
| 218 | const insertionAnchor = getInsertionAnchor( option, input ); |
| 219 | const logoAlt = getMetaValue( rate.meta_data, config.logo_alt_key || 'method_logo_alt' ) || ''; |
| 220 | const hasNativeDescription = descriptionMatchesElement( description, nativeDescription ); |
| 221 | |
| 222 | if ( description ) { |
| 223 | if ( hasNativeDescription ) { |
| 224 | if ( existingDescription ) { |
| 225 | existingDescription.remove(); |
| 226 | } |
| 227 | } else if ( existingDescription ) { |
| 228 | if ( existingDescription.innerHTML !== description ) { |
| 229 | existingDescription.innerHTML = description; |
| 230 | } |
| 231 | } else if ( insertionAnchor ) { |
| 232 | insertionAnchor.insertAdjacentElement( 'afterend', createDescriptionNode( description ) ); |
| 233 | } |
| 234 | } else if ( existingDescription ) { |
| 235 | existingDescription.remove(); |
| 236 | } |
| 237 | |
| 238 | const descriptionAnchor = option.querySelector( descriptionClass ) || nativeDescription || insertionAnchor; |
| 239 | |
| 240 | if ( ! logoUrl ) { |
| 241 | if ( existingLogo ) { |
| 242 | existingLogo.remove(); |
| 243 | } |
| 244 | return; |
| 245 | } |
| 246 | |
| 247 | if ( existingLogo ) { |
| 248 | const existingImage = existingLogo.querySelector( 'img' ); |
| 249 | |
| 250 | if ( existingImage ) { |
| 251 | if ( existingImage.src !== logoUrl ) { |
| 252 | existingImage.src = logoUrl; |
| 253 | } |
| 254 | |
| 255 | if ( existingImage.alt !== logoAlt ) { |
| 256 | existingImage.alt = logoAlt; |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | if ( descriptionAnchor && existingLogo.previousElementSibling !== descriptionAnchor ) { |
| 261 | descriptionAnchor.insertAdjacentElement( 'afterend', existingLogo ); |
| 262 | } |
| 263 | |
| 264 | return; |
| 265 | } |
| 266 | |
| 267 | const logoNode = createLogoNode( logoUrl, logoAlt ); |
| 268 | |
| 269 | if ( descriptionAnchor ) { |
| 270 | descriptionAnchor.insertAdjacentElement( 'afterend', logoNode ); |
| 271 | return; |
| 272 | } |
| 273 | |
| 274 | const label = option.querySelector( 'label' ) || option; |
| 275 | label.appendChild( logoNode ); |
| 276 | } ); |
| 277 | }; |
| 278 | |
| 279 | const boot = () => { |
| 280 | let scheduled = false; |
| 281 | |
| 282 | const scheduleRender = () => { |
| 283 | if ( scheduled ) { |
| 284 | return; |
| 285 | } |
| 286 | |
| 287 | scheduled = true; |
| 288 | window.requestAnimationFrame( () => { |
| 289 | scheduled = false; |
| 290 | renderLogos(); |
| 291 | } ); |
| 292 | }; |
| 293 | |
| 294 | if ( window.wp && window.wp.data && 'function' === typeof window.wp.data.subscribe ) { |
| 295 | window.wp.data.subscribe( scheduleRender ); |
| 296 | } |
| 297 | |
| 298 | new MutationObserver( scheduleRender ).observe( document.body, { |
| 299 | childList: true, |
| 300 | subtree: true, |
| 301 | } ); |
| 302 | |
| 303 | scheduleRender(); |
| 304 | }; |
| 305 | |
| 306 | if ( 'loading' === document.readyState ) { |
| 307 | document.addEventListener( 'DOMContentLoaded', boot ); |
| 308 | } else { |
| 309 | boot(); |
| 310 | } |
| 311 | }() ); |
| 312 |